10967 lines
350 KiB
Python
10967 lines
350 KiB
Python
#!/usr/bin/env python3
|
||
#-*- coding: utf-8 -*-
|
||
|
||
"""Python wrapper for synspec
|
||
|
||
Calculation of synthetic spectra of stars and convolution with a rotational/Gaussian kernel.
|
||
Makes the use of synspec simpler, and retains the main functionalities (when used from
|
||
python). The command line interface is even simpler but fairly limited.
|
||
|
||
For information on
|
||
synspec visit http://nova.astro.umd.edu/Synspec43/synspec.html.
|
||
|
||
Example
|
||
-------
|
||
|
||
To compute the solar spectrum between 6160 and 6164 angstroms, using a model atmosphere in
|
||
the file ksun.mod (provided with the distribution), with the output going into the file
|
||
sun.syn
|
||
|
||
$synple.py ksun.mod 6160. 6164.
|
||
|
||
To force a micro of 1.1 km/s, and convolve the spectrum with a Gaussian kernel with a fwhm
|
||
of 0.1 angstroms
|
||
|
||
$synple.py ksun.mod 6160. 6164. 1.1 0.1
|
||
|
||
To perform the calculations above in python and compare the emergent normalized profiles
|
||
|
||
>>> from synple import syn
|
||
>>> s = syn('ksun.mod', (6160.,6164.))
|
||
>>> s2 = syn('ksun.mod', (6160.,6164.), vmicro=1.1, fwhm=0.1)
|
||
|
||
in plain python
|
||
>>> import matplotlib.pyplot as plt
|
||
>>> plt.ion()
|
||
>>> plt.plot(s[0],s[1]/s[2], s2[0], s2[1]/s2[2])
|
||
|
||
or ipython
|
||
In [1]: %pylab
|
||
In [2]: plot(s[0],s[1]/s[2], s2[0], s2[1]/s2[2])
|
||
|
||
|
||
"""
|
||
# Python封装Synspec工具包
|
||
# 用于计算恒星合成光谱并进行旋转/高斯核卷积
|
||
# 简化Synspec的使用,保留主要功能
|
||
# 命令行界面简单但功能有限
|
||
# Synspec详细信息请访问:http://nova.astro.umd.edu/Synspec43/synspec.html
|
||
|
||
import os # 操作系统接口
|
||
import re # 正则表达式
|
||
import sys # 系统相关功能
|
||
import stat # 文件状态
|
||
import string # 字符串处理
|
||
import random # 随机数生成
|
||
import subprocess # 子进程管理
|
||
import glob # 文件模式匹配
|
||
import time # 时间处理
|
||
import copy # 对象拷贝
|
||
import gzip # 压缩文件处理
|
||
import yaml # YAML解析
|
||
import numpy as np # 数值计算库
|
||
import matplotlib.pyplot as plt # 绘图库
|
||
from math import ceil # 数学函数
|
||
from scipy import interpolate # 插值计算
|
||
from scipy.signal import savgol_filter # 信号处理
|
||
from scipy.optimize import curve_fit # 曲线拟合
|
||
from itertools import product # 笛卡尔积
|
||
from astropy.io import fits # FITS文件处理
|
||
import astropy.table as tbl # 表格处理
|
||
import astropy.units as units # 单位系统
|
||
import datetime # 日期时间处理
|
||
import platform # 平台信息
|
||
import pandas as pd
|
||
import shutil
|
||
# 配置变量
|
||
# synple目录路径
|
||
synpledir = os.path.dirname(os.path.realpath(__file__))
|
||
|
||
# 相对路径设置
|
||
modeldir = os.path.join(synpledir, "models") # 模型目录
|
||
modelatomdir = os.path.join(synpledir, "data") # 模型原子数据目录
|
||
confdir = os.path.join(synpledir, "config") # 配置目录
|
||
griddir = os.path.join(synpledir, "grids") # 网格目录
|
||
atlasdir = os.path.join(synpledir, "atlases") # 光谱图目录
|
||
linelistdir = os.path.join(synpledir, "linelists") # 线表目录
|
||
linelist0 = ['gfATOc.19', 'gfMOLsun.20', 'gfTiO.20', 'H2O-8.20'] # 默认线表列表
|
||
bindir = os.path.join(synpledir, "bin") # 可执行文件目录
|
||
synspec = os.path.join(bindir, "synspec54") # Synspec可执行文件路径
|
||
rotin = os.path.join(bindir, "rotin") # Rotin可执行文件路径
|
||
|
||
# Synspec内部数据文件
|
||
isdf = ['CIA_H2H2.dat', 'CIA_H2H.dat', 'CIA_H2He.dat', 'CIA_HHe.dat', 'irwin_bc.dat', 'tremblay.dat', 'tsuji.molec_bc2']
|
||
|
||
# 其他常量
|
||
clight = 299792.458 # 光速(km/s)
|
||
epsilon = 0.6 # CLV系数
|
||
bolk = 1.38054e-16 # 玻尔兹曼常数(erg/K)
|
||
zero = " 0 " # 空字符串占位符
|
||
one = " 1 " # 空字符串占位符
|
||
two = " 2 " # 空字符串占位符
|
||
|
||
def syn(modelfile, wrange, dw=None, strength=1e-4, vmicro=None, abu=None, linelist=linelist0, atom='ap18', vrot=0.0, fwhm=0.0, vmacro=0.0, steprot=0.0, stepfwhm=0.0, intensity=False, lineid=False, tag=False, clean=True, save=False, synfile=None, lte=None, compute=True, tmpdir=None):
|
||
"""
|
||
计算合成光谱
|
||
通过Fortran代码synspec/rotin计算合成光谱,仅需两个必填参数:模型大气文件和波长范围
|
||
自动选择频率网格,可指定波长步长(dw)
|
||
允许设置元素丰度和微观湍流速度,支持旋转和高斯展宽
|
||
可保存计算结果到文件
|
||
|
||
参数:
|
||
-----------
|
||
modelfile : str
|
||
模型大气文件路径
|
||
wrange: tuple of float
|
||
波长范围(埃)
|
||
dw: float, 可选
|
||
输出通量的波长步长(负值表示对数插值,默认自动选择)
|
||
strength: float, 可选
|
||
线连续比阈值(默认1e-4)
|
||
vmicro: float
|
||
微观湍流速度(km/s,负值使用APOGEE DR14公式,默认取模型文件值)
|
||
abu: array of float (99元素)
|
||
元素丰度相对氢(默认取模型文件值)
|
||
linelist: list of str
|
||
线表文件名列表(默认linelist0)
|
||
atom: str
|
||
连续谱模型('ap18'/'yo19'/'hhm',默认'ap18')
|
||
vrot: float
|
||
投影旋转速度(km/s,默认0)
|
||
fwhm: float
|
||
高斯展宽(埃,默认0)
|
||
vmacro: float
|
||
径向切向宏观湍流(km/s,默认0)
|
||
steprot: float
|
||
旋转卷积波长步长(默认自动)
|
||
stepfwhm: float
|
||
高斯卷积波长步长(默认自动)
|
||
intensity: bool
|
||
是否返回各角度强度(默认False)
|
||
lineid: bool
|
||
是否添加谱线标识(默认False)
|
||
tag: bool
|
||
是否绘制谱线标识图(默认False)
|
||
clean: bool
|
||
是否清理临时文件(默认True)
|
||
save: bool
|
||
是否保存结果文件(默认False)
|
||
synfile: str
|
||
输出文件名(默认使用模型文件名)
|
||
lte: bool
|
||
是否强制LTE计算(默认False)
|
||
compute: bool
|
||
是否执行计算(默认True)
|
||
tmpdir: str
|
||
临时目录路径(默认随机生成)
|
||
返回:
|
||
-------
|
||
wave: numpy array
|
||
波长(埃)
|
||
flux: numpy array
|
||
通量(erg/s/cm²/Å)
|
||
cont: numpy array
|
||
连续谱通量
|
||
(若intensity=True)
|
||
inte: 2D numpy array
|
||
各角度强度(10个角度)
|
||
continte: 2D numpy array
|
||
各角度连续谱强度
|
||
(若lineid=True)
|
||
lalilo: list of arrays
|
||
谱线波长、标识、EW预测值
|
||
"""
|
||
# 检查线表和模型文件
|
||
linelist, modelfile = checksynspec(linelist, modelfile)
|
||
|
||
# 读取模型大气参数
|
||
atmostype, teff, logg, vmicro2, abu2, nd, atmos = read_model(modelfile)
|
||
if vmicro is None:
|
||
vmicro = vmicro2 # 默认取模型值
|
||
elif vmicro < 0.0:
|
||
# 使用APOGEE DR14公式计算微观湍流
|
||
vmicro = 2.478 - 0.325 * logg
|
||
|
||
if abu is None:
|
||
abu = abu2 # 默认取模型丰度
|
||
|
||
# 自动选择波长步长
|
||
space = np.mean(wrange) / clight * 2.355 / 3.0 * np.sqrt(
|
||
0.1289**2 * np.min(atmos['t']) / 100.0 + (vmicro**2)/2.0
|
||
)
|
||
|
||
# 输入参数验证
|
||
imode = checkinput(wrange, vmicro, linelist)
|
||
|
||
# 处理Tlusty模型的额外参数
|
||
if atmostype == 'tlusty':
|
||
madaffile, nonstdfile, nonstd, numpar, datadir, inlte, atommode, atominfo = read_tlusty_extras(modelfile)
|
||
if inlte == -1:
|
||
nonstd['IBFAC'] = 1
|
||
if lte:
|
||
inlte = 0
|
||
else:
|
||
nonstd = None
|
||
inlte = 0
|
||
atommode = None
|
||
atominfo = None
|
||
|
||
# 创建临时目录
|
||
if tmpdir is None:
|
||
tmpdir = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
|
||
startdir = os.getcwd()
|
||
logfile = os.path.join(startdir, os.path.split(tmpdir)[-1]) + "_syn.log"
|
||
try:
|
||
if tmpdir != '.':
|
||
os.mkdir(tmpdir)
|
||
except OSError:
|
||
print(f"无法创建临时目录 {tmpdir}")
|
||
|
||
# 进入临时目录
|
||
try:
|
||
os.chdir(tmpdir)
|
||
except OSError:
|
||
print(f"无法进入临时目录 {tmpdir}")
|
||
|
||
# 清理Fortran临时文件
|
||
cleanup_fort()
|
||
if os.path.islink('data'):
|
||
os.unlink('data')
|
||
if os.path.isfile('tas'):
|
||
os.remove('tas')
|
||
|
||
# 创建数据目录符号链接
|
||
if atmostype == 'tlusty':
|
||
hdd, dd = os.path.split(datadir)
|
||
os.symlink(datadir, dd)
|
||
if dd == 'data':
|
||
for entry in isdf:
|
||
assert os.path.isfile(os.path.join(dd, entry)), f"缺少数据文件:{dd}/{entry}"
|
||
else:
|
||
os.symlink(modelatomdir, './data')
|
||
|
||
# 写入丰度/连续谱参数文件(fort.5)
|
||
write5(teff, logg, abu, atom, inlte=inlte, atommode=atommode, atominfo=atominfo)
|
||
|
||
# 写入模型大气文件(fort.8)
|
||
write8(teff, logg, nd, atmos, atmostype)
|
||
|
||
# 设置输出模式
|
||
iprin = 0
|
||
if tag:
|
||
lineid = True
|
||
if lineid:
|
||
iprin = 2
|
||
|
||
# 设置截止波长
|
||
cutoff0 = 250.0
|
||
if logg > 3.0:
|
||
if teff < 4000.0:
|
||
cutoff0 = 500.0
|
||
if teff < 3500.0:
|
||
cutoff0 = 1000.0
|
||
if teff < 3000.0:
|
||
cutoff0 = 1500.0
|
||
|
||
# 写入控制文件(fort.55)
|
||
write55(wrange, dw=space, imode=imode, iprin=iprin, inlte=inlte, hydprf=2,
|
||
cutoff0=cutoff0, strength=strength, vmicro=vmicro, linelist=linelist,
|
||
atmostype=atmostype, intensity=intensity)
|
||
|
||
# 写入非标准参数文件(tas)
|
||
writetas('tas', nd, linelist, nonstd=nonstd)
|
||
|
||
# 创建线表链接
|
||
create_links(linelist)
|
||
|
||
# 跳过计算时的处理
|
||
if not compute:
|
||
wave, flux, cont = None, None, None
|
||
else:
|
||
# 执行Synspec计算
|
||
with open('fort.5', 'r') as synin, open(logfile, 'w') as synout:
|
||
start = time.time()
|
||
p = subprocess.Popen([synspec], stdin=synin, stdout=synout, stderr=synout)
|
||
p.wait()
|
||
synout.flush()
|
||
|
||
# 验证输出文件
|
||
assert os.path.isfile('fort.7'), f"错误:无法读取{tmpdir}/fort.7,请检查日志{logfile}"
|
||
assert os.path.isfile('fort.17'), f"错误:无法读取{tmpdir}/fort.17,请检查日志{logfile}"
|
||
|
||
# 读取通量数据
|
||
wave, flux = np.loadtxt('fort.7', unpack=True)
|
||
wave2, flux2 = np.loadtxt('fort.17', unpack=True)
|
||
|
||
# 处理重复波长
|
||
if np.any(np.diff(wave) <= 0.0):
|
||
wave, win = np.unique(wave, return_index=True)
|
||
flux = flux[win]
|
||
if np.any(np.diff(wave2) <= 0.0):
|
||
wave2, win = np.unique(wave2, return_index=True)
|
||
flux2 = flux2[win]
|
||
|
||
# 处理强度数据
|
||
if intensity:
|
||
iwave, inte = read10('fort.10')
|
||
contiwave, continte1 = read10('fort.18')
|
||
if np.any(np.diff(iwave) <= 0.0):
|
||
iwave, win = np.unique(iwave, return_index=True)
|
||
inte = inte[win, :]
|
||
clight_cgs = clight * 1e5 # 转换为cm
|
||
iwave_cgs = iwave * 1e-8 # 转换为cm
|
||
# 单位转换(I_nu → I_lambda)
|
||
inte = (inte.T * clight_cgs / iwave_cgs**2 * 1e-8).T
|
||
continte = np.zeros((len(iwave), 10))
|
||
for entry in range(10):
|
||
continte[:, entry] = np.interp(iwave, contiwave, continte1[:, entry])
|
||
continte = (continte.T * clight_cgs / iwave_cgs**2 * 1e-8).T
|
||
assert np.max(iwave - wave) < 1e-7, "强度波长与通量波长不匹配"
|
||
assert fwhm < 1e-7 and vmacro < 1e-7 and vrot < 1e-7, "强度计算不支持展宽参数"
|
||
|
||
# 生成连续谱
|
||
if dw is None and fwhm <= 0.0 and vrot <= 0.0:
|
||
cont = np.interp(wave, wave2, flux2)
|
||
|
||
end = time.time()
|
||
print(f"Synspec计算耗时:{end - start:.2f}秒")
|
||
|
||
# 执行展宽卷积
|
||
if vrot > 0.0 or fwhm > 0.0 or vmacro > 0.0:
|
||
start = time.time()
|
||
wave, flux = call_rotin(wave, flux, vrot, fwhm, vmacro, space, steprot, stepfwhm, clean=False, reuseinputfiles=True, logfile=logfile)
|
||
if dw is None:
|
||
cont = np.interp(wave, wave2, flux2)
|
||
end = time.time()
|
||
print(f"卷积耗时:{end - start:.2f}秒")
|
||
|
||
# 波长重采样
|
||
if dw is not None:
|
||
if dw < 0.0:
|
||
ldw = np.abs(dw) / np.mean(wrange)
|
||
nsamples = int((np.log(wrange[1]) - np.log(wrange[0])) / ldw) + 1
|
||
wave3 = np.exp(np.arange(nsamples) * ldw + np.log(wrange[0]))
|
||
else:
|
||
nsamples = int((wrange[1] - wrange[0]) / dw) + 1
|
||
wave3 = np.arange(nsamples) * dw + wrange[0]
|
||
cont = np.interp(wave3, wave2, flux2)
|
||
flux = np.interp(wave3, wave, flux)
|
||
if intensity:
|
||
inte2 = np.zeros((nsamples, 10))
|
||
continte2 = np.zeros((nsamples, 10))
|
||
for entry in range(10):
|
||
inte2[:, entry] = np.interp(wave3, wave, inte[:, entry])
|
||
continte2[:, entry] = np.interp(wave3, wave, continte[:, entry])
|
||
inte = inte2
|
||
continte = continte2
|
||
wave = wave3
|
||
|
||
# 读取谱线标识数据
|
||
if lineid:
|
||
assert os.path.isfile('fort.12'), f"错误:无法读取{tmpdir}/fort.12,请检查日志{logfile}"
|
||
data = np.loadtxt('fort.12', usecols=(0,1,2,3,4,5,6), dtype=str)
|
||
la = np.array(data[:, 0], dtype=float)
|
||
li = np.array([''.join(row[1:3]) for row in data], dtype=str)
|
||
lo = np.array(data[:, 6], dtype=float)
|
||
if os.path.isfile('fort.15'):
|
||
data2 = np.loadtxt('fort.15', usecols=(0,1,2,3,4,5), dtype=str)
|
||
la2 = np.array(data2[:, 0], dtype=float)
|
||
li2 = np.array(data2[:, 1], dtype=str)
|
||
lo2 = np.array(data2[:, 5], dtype=float)
|
||
la = np.concatenate((la, la2))
|
||
li = np.concatenate((li, li2))
|
||
lo = np.concatenate((lo, lo2))
|
||
|
||
# 清理临时文件
|
||
if clean:
|
||
cleanup_fort()
|
||
if os.path.islink('data'):
|
||
os.unlink('data')
|
||
|
||
# 返回原始目录
|
||
os.chdir(startdir)
|
||
|
||
if clean:
|
||
try:
|
||
shutil.rmtree(tmpdir)
|
||
except (OSError, shutil.Error) as e:
|
||
print(f"无法删除临时目录 {tmpdir}: {e}")
|
||
|
||
# 保存结果文件
|
||
if save:
|
||
header = (
|
||
f"MODEL = {modelfile}\n"
|
||
f"TEFF = {teff}\n"
|
||
f"LOGG = {logg}\n"
|
||
f"VMICRO = {vmicro}\n"
|
||
f"WRANGE = {' '.join(map(str, wrange))}\n"
|
||
f"STRENGTH= {strength}\n"
|
||
f"LINELIST= {' '.join(linelist)}\n"
|
||
f"ATOM = {atom}\n"
|
||
f"VROT = {vrot}\n"
|
||
f"FWHM = {fwhm}\n"
|
||
f"VMACRO = {vmacro}\n"
|
||
f"STEPROT = {steprot}\n"
|
||
f"STEPFWHM= {stepfwhm}\n"
|
||
f"LTE = {lte}\n"
|
||
f"ABU = {' '.join(map(str, abu))}\n"
|
||
)
|
||
if synfile is None:
|
||
tmpstr = os.path.split(modelfile)[-1]
|
||
synfile = tmpstr[:tmpstr.rfind('.')] + '.syn'
|
||
# pd.DataFrame({'wave': wave, 'flux': flux, 'cont': cont}).to_csv(synfile, header=header, index=False)
|
||
np.savetxt(synfile, np.vstack((wave, flux, cont)), header=header)
|
||
|
||
# 返回结果
|
||
result = [wave, flux, cont]
|
||
if intensity:
|
||
result.append(inte)
|
||
result.append(continte)
|
||
if lineid:
|
||
result.append([la, li, lo])
|
||
return tuple(result)
|
||
|
||
|
||
def mpsyn(modelfile, wrange, dw=None, strength=1e-4, vmicro=None, abu=None, \
|
||
linelist=linelist0, atom='ap18', vrot=0.0, fwhm=0.0, vmacro=0.0, \
|
||
steprot=0.0, stepfwhm=0.0, intensity=False, lineid=False, tag=False, \
|
||
clean=True, save=False, synfile=None, \
|
||
lte=False, compute=True, nthreads=0):
|
||
"""
|
||
通过多线程并行计算分割后的光谱段,加速广域光谱合成
|
||
Parameters
|
||
-----------
|
||
modelfile : str
|
||
大气模型文件路径
|
||
wrange: tuple of two floats
|
||
波长范围(埃)
|
||
dw: float, 可选
|
||
输出通量的波长步长(负值表示对数插值,默认自动选择)
|
||
strength: float, 可选
|
||
线连续比阈值(默认1e-4)
|
||
vmicro: float
|
||
微观湍流速度(km/s,负值触发APOGEE DR14公式,默认取模型值)
|
||
abu: list of floats (99元素)
|
||
元素丰度相对氢的数密度比(默认取模型值)
|
||
linelist: list of str
|
||
线表文件名列表(默认使用linelist0)
|
||
atom: str
|
||
连续谱模型('ap18'/'yo19'/'hhm',默认'ap18')
|
||
vrot: float
|
||
投影旋转速度(km/s,默认0)
|
||
fwhm: float
|
||
高斯展宽(埃,默认0)
|
||
vmacro: float
|
||
径向切向宏观湍流(km/s,默认0)
|
||
steprot: float
|
||
旋转卷积波长步长(默认自动)
|
||
stepfwhm: float
|
||
高斯卷积波长步长(默认自动)
|
||
intensity: bool
|
||
是否返回不同角度的强度(默认False)
|
||
lineid: bool
|
||
是否添加谱线标识(默认False)
|
||
tag: bool
|
||
是否绘制谱线标识图(默认False)
|
||
clean: bool
|
||
是否清理临时文件(默认True)
|
||
save: bool
|
||
是否保存结果文件(默认False)
|
||
synfile: str
|
||
输出文件名(默认使用模型文件名)
|
||
lte: bool
|
||
是否强制LTE计算(默认False)
|
||
compute: bool
|
||
是否执行计算(默认True)
|
||
nthreads: int
|
||
并行使用的线程数(默认0表示使用总核心数-1)
|
||
Returns
|
||
-------
|
||
wave: numpy array
|
||
波长(埃)
|
||
flux: numpy array
|
||
通量(erg/s/cm²/Å)
|
||
cont: numpy array
|
||
连续谱通量
|
||
(若intensity=True)
|
||
inte: 2D numpy array
|
||
不同角度的强度(10个角度)
|
||
continte: 2D numpy array
|
||
连续谱强度
|
||
(若lineid=True)
|
||
lalilo: list of arrays
|
||
谱线波长、标识和预测等效宽度
|
||
"""
|
||
from multiprocessing import Pool, cpu_count # 导入并行计算模块
|
||
|
||
# 基础参数验证和模型文件处理
|
||
linelist, modelfile = checksynspec(linelist, modelfile)
|
||
|
||
# 读取模型大气参数
|
||
atmostype, teff, logg, vmicro2, abu2, nd, atmos = read_model(modelfile)
|
||
|
||
# 处理微观湍流速度
|
||
if vmicro is None:
|
||
vmicro = vmicro2
|
||
elif vmicro < 0.0:
|
||
# 使用APOGEE DR14公式计算
|
||
vmicro = 2.478 - 0.325 * logg
|
||
|
||
if abu is None:
|
||
abu = abu2
|
||
|
||
# 设置线程数
|
||
if nthreads == 0:
|
||
nthreads = int(cpu_count() - 1) # 默认使用总核心数-1
|
||
|
||
# 设置谱线标识参数
|
||
if tag:
|
||
lineid = True
|
||
|
||
tmpdir = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16)) # 生成随机临时目录名
|
||
|
||
# 对数分割波长范围以平衡计算负载
|
||
power = 1 # 控制波长分割方式(power=1为线性分割)
|
||
delta = ( (np.log10(wrange[1]))**(1./power) - (np.log10(wrange[0]))**(1./power) ) / nthreads
|
||
|
||
# 构造并行任务参数列表
|
||
pars = []
|
||
for i in range(nthreads):
|
||
# 计算每个线程的波长子范围
|
||
lambda_min = 10.**((np.log10(wrange[0])** (1./power) + delta*i)**power)
|
||
lambda_max = 10.**((np.log10(wrange[0])** (1./power) + delta*(i+1))**power)
|
||
wrange1 = (lambda_min, lambda_max)
|
||
|
||
# 构造参数元组(排除synfile参数,使用独立临时目录)
|
||
pararr = [modelfile, wrange1, dw, strength, vmicro, abu,
|
||
linelist, atom, vrot, fwhm, vmacro,
|
||
steprot, stepfwhm, lineid, intensity, tag,
|
||
clean, False, None, lte, compute, f"{tmpdir}-{i}"]
|
||
pars.append(pararr)
|
||
|
||
# 启动并行计算
|
||
pool = Pool(nthreads)
|
||
results = pool.starmap(syn, pars) # 分布式计算各子波段
|
||
pool.close()
|
||
pool.join()
|
||
|
||
# 合并计算结果
|
||
x, y, z = results[0][0], results[0][1], results[0][2]
|
||
if lineid:
|
||
la, li, lo = results[0][3]
|
||
|
||
# 合并后续线程结果(跳过首元素避免重复)
|
||
if len(results) > 1:
|
||
for i in range(len(results)-1):
|
||
x = np.concatenate((x, results[i+1][0][1:])) # 波长合并
|
||
y = np.concatenate((y, results[i+1][1][1:])) # 通量合并
|
||
z = np.concatenate((z, results[i+1][2][1:])) # 连续谱合并
|
||
if lineid:
|
||
la2, li2, lo2 = results[i+1][3]
|
||
la = np.concatenate((la, la2[1:])) # 谱线波长合并
|
||
li = np.concatenate((li, li2[1:])) # 谱线标识合并
|
||
lo = np.concatenate((lo, lo2[1:])) # 等效宽度合并
|
||
|
||
# 保存结果文件
|
||
if save:
|
||
header = [
|
||
f"MODEL = {modelfile}\n",
|
||
f"TEFF = {teff}\n",
|
||
f"LOGG = {logg}\n",
|
||
f"VMICRO = {vmicro}\n",
|
||
f"WRANGE = {' '.join(map(str, wrange))}\n",
|
||
f"STRENGTH= {strength}\n",
|
||
f"LINELIST= {' '.join(linelist)}\n",
|
||
f"ATOM = {atom}\n",
|
||
f"VROT = {vrot}\n",
|
||
f"FWHM = {fwhm}\n",
|
||
f"VMACRO = {vmacro}\n",
|
||
f"STEPROT = {steprot}\n",
|
||
f"STEPFWHM= {stepfwhm}\n",
|
||
f"LTE = {lte}\n",
|
||
f"ABU = {' '.join(map(str, abu))}\n"
|
||
]
|
||
header = ''.join(header)
|
||
if synfile is None:
|
||
tmpstr = os.path.split(modelfile)[-1]
|
||
synfile = tmpstr[:tmpstr.rfind('.')] + '.syn'
|
||
np.savetxt(synfile, np.vstack((x, y, z)), header=header)
|
||
|
||
# 组合返回结果
|
||
if lineid:
|
||
s = (x, y, z, [la, li, lo])
|
||
else:
|
||
s = (x, y, z)
|
||
|
||
if tag:
|
||
tags(s)
|
||
|
||
return s
|
||
|
||
|
||
def raysyn(modelfile, wrange, dw=None, strength=1e-4, vmicro=None, abu=None, \
|
||
linelist=linelist0, atom='ap18', vrot=0.0, fwhm=0.0, vmacro=0.0, \
|
||
steprot=0.0, stepfwhm=0.0, intensity=False, lineid=False, tag=False, \
|
||
clean=True, save=False, synfile=None, \
|
||
lte=False, compute=True, nthreads=0):
|
||
"""
|
||
通过Ray框架并行计算分割后的光谱段,加速广域光谱合成
|
||
Parameters
|
||
-----------
|
||
modelfile : str
|
||
大气模型文件路径
|
||
wrange: tuple of two floats
|
||
波长范围(埃)
|
||
dw: float, 可选
|
||
输出通量的波长步长(负值表示对数插值,默认自动选择)
|
||
strength: float, 可选
|
||
线连续比阈值(默认1e-4)
|
||
vmicro: float
|
||
微观湍流速度(km/s,负值触发APOGEE DR14公式,默认取模型值)
|
||
abu: list of floats (99元素)
|
||
元素丰度相对氢的数密度比(默认取模型值)
|
||
linelist: list of str
|
||
线表文件名列表(默认使用linelist0)
|
||
atom: str
|
||
连续谱模型('ap18'/'yo19'/'hhm',默认'ap18')
|
||
vrot: float
|
||
投影旋转速度(km/s,默认0)
|
||
fwhm: float
|
||
高斯展宽(埃,默认0)
|
||
vmacro: float
|
||
径向切向宏观湍流(km/s,默认0)
|
||
steprot: float
|
||
旋转卷积波长步长(默认自动)
|
||
stepfwhm: float
|
||
高斯卷积波长步长(默认自动)
|
||
intensity: bool
|
||
是否返回不同角度的强度(默认False)
|
||
lineid: bool
|
||
是否添加谱线标识(默认False)
|
||
tag: bool
|
||
是否绘制谱线标识图(默认False)
|
||
clean: bool
|
||
是否清理临时文件(默认True)
|
||
save: bool
|
||
是否保存结果文件(默认False)
|
||
synfile: str
|
||
输出文件名(默认使用模型文件名)
|
||
lte: bool
|
||
是否强制LTE计算(默认False)
|
||
compute: bool
|
||
是否执行计算(默认True)
|
||
nthreads: int
|
||
并行使用的线程数(默认0表示使用物理核心数-1)
|
||
Returns
|
||
-------
|
||
wave: numpy array
|
||
波长(埃)
|
||
flux: numpy array
|
||
通量(erg/s/cm²/Å)
|
||
cont: numpy array
|
||
连续谱通量
|
||
(若intensity=True)
|
||
inte: 2D numpy array
|
||
不同角度的强度(10个角度)
|
||
continte: 2D numpy array
|
||
连续谱强度
|
||
(若lineid=True)
|
||
lalilo: list of arrays
|
||
谱线波长、标识和预测等效宽度
|
||
"""
|
||
import psutil # 获取系统核心信息
|
||
import ray # 导入Ray分布式计算框架
|
||
|
||
@ray.remote # 定义远程任务函数
|
||
def fun(vari, cons):
|
||
# 参数拆分:变量参数(波长范围和临时目录)和常量参数
|
||
wrange_i, tmpdir = vari
|
||
modelfile, dw, strength, vmicro_i, abu_i, linelist_i, \
|
||
atom_i, vrot_i, fwhm_i, vmacro_i, steprot_i, stepfwhm_i, intensity_i, \
|
||
lineid_i, tag_i, clean_i, save_i, synfile_i, compute_i = cons
|
||
|
||
# 调用主计算函数
|
||
s = syn(modelfile, wrange_i, dw, strength, vmicro_i, abu_i,
|
||
linelist_i, atom_i, vrot_i, fwhm_i, vmacro_i,
|
||
steprot_i, stepfwhm_i, intensity_i,
|
||
lineid_i, tag_i, clean_i, save_i, synfile_i,
|
||
lte, compute_i, tmpdir)
|
||
|
||
return s
|
||
|
||
# 基础参数验证和模型文件处理
|
||
linelist, modelfile = checksynspec(linelist, modelfile)
|
||
|
||
# 读取模型大气参数
|
||
atmostype, teff, logg, vmicro2, abu2, nd, atmos = read_model(modelfile)
|
||
|
||
# 处理微观湍流速度
|
||
if vmicro is None:
|
||
vmicro = vmicro2
|
||
elif vmicro < 0.0:
|
||
# 使用APOGEE DR14公式计算
|
||
vmicro = 2.478 - 0.325 * logg
|
||
|
||
if abu is None:
|
||
abu = abu2
|
||
|
||
# 设置线程数
|
||
if nthreads == 0:
|
||
nthreads = int(psutil.cpu_count(logical=False) - 1) # 默认使用物理核心数-1
|
||
|
||
# 设置谱线标识参数
|
||
if tag:
|
||
lineid = True
|
||
|
||
print('nthreads=', nthreads) # 输出线程数
|
||
|
||
tmpdir = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16)) # 生成随机临时目录名
|
||
|
||
# 初始化Ray框架
|
||
ray.init(num_cpus=nthreads)
|
||
|
||
# 构造常量参数元组
|
||
constants = (modelfile, dw, strength, vmicro, abu, linelist,
|
||
atom, vrot, fwhm, vmacro, steprot, stepfwhm, intensity,
|
||
lineid, tag, clean, False, None, compute)
|
||
constants_id = ray.put(constants) # 将常量参数存入Ray对象存储
|
||
|
||
# 对数分割波长范围以平衡计算负载
|
||
power = 1 # 控制波长分割方式(power=1为线性分割)
|
||
delta = ( (np.log10(wrange[1]))**(1./power) - (np.log10(wrange[0]))**(1./power) ) / nthreads
|
||
|
||
# 构造并行任务参数列表
|
||
pars = []
|
||
for i in range(nthreads):
|
||
# 计算每个线程的波长子范围
|
||
lambda_min = 10.**((np.log10(wrange[0])** (1./power) + delta*i)**power)
|
||
lambda_max = 10.**((np.log10(wrange[0])** (1./power) + delta*(i+1))**power)
|
||
wrange_i = (lambda_min, lambda_max)
|
||
folder = f"{tmpdir}-{i}"
|
||
pars.append( (wrange_i, folder) )
|
||
|
||
# 提交并行任务
|
||
results = ray.get([fun.remote(pars[i], constants_id) for i in range(nthreads)])
|
||
ray.shutdown() # 关闭Ray框架
|
||
|
||
# 合并计算结果
|
||
x, y, z = results[0][0], results[0][1], results[0][2]
|
||
if lineid:
|
||
la, li, lo = results[0][3]
|
||
|
||
# 合并后续线程结果(跳过首元素避免重复)
|
||
if len(results) > 1:
|
||
for i in range(len(results)-1):
|
||
x = np.concatenate((x, results[i+1][0][1:])) # 波长合并
|
||
y = np.concatenate((y, results[i+1][1][1:])) # 通量合并
|
||
z = np.concatenate((z, results[i+1][2][1:])) # 连续谱合并
|
||
if lineid:
|
||
la2, li2, lo2 = results[i+1][3]
|
||
la = np.concatenate((la, la2[1:])) # 谱线波长合并
|
||
li = np.concatenate((li, li2[1:])) # 谱线标识合并
|
||
lo = np.concatenate((lo, lo2[1:])) # 等效宽度合并
|
||
|
||
# 保存结果文件
|
||
if save:
|
||
header = [
|
||
f"MODEL = {modelfile}\n",
|
||
f"TEFF = {teff}\n",
|
||
f"LOGG = {logg}\n",
|
||
f"VMICRO = {vmicro}\n",
|
||
f"WRANGE = {' '.join(map(str, wrange))}\n",
|
||
f"STRENGTH= {strength}\n",
|
||
f"LINELIST= {' '.join(linelist)}\n",
|
||
f"ATOM = {atom}\n",
|
||
f"VROT = {vrot}\n",
|
||
f"FWHM = {fwhm}\n",
|
||
f"VMACRO = {vmacro}\n",
|
||
f"STEPROT = {steprot}\n",
|
||
f"STEPFWHM= {stepfwhm}\n",
|
||
f"LTE = {lte}\n",
|
||
f"ABU = {' '.join(map(str, abu))}\n"
|
||
]
|
||
header = ''.join(header)
|
||
if synfile is None:
|
||
tmpstr = os.path.split(modelfile)[-1]
|
||
synfile = tmpstr[:tmpstr.rfind('.')] + '.syn'
|
||
np.savetxt(synfile, np.vstack((x, y, z)), header=header)
|
||
|
||
# 组合返回结果
|
||
if lineid:
|
||
s = (x, y, z, [la, li, lo])
|
||
else:
|
||
s = (x, y, z)
|
||
|
||
if tag:
|
||
tags(s)
|
||
|
||
return s
|
||
|
||
|
||
def multisyn(modelfiles, wrange, dw=None, strength=1e-4, abu=None, \
|
||
vmicro=None, vrot=0.0, fwhm=0.0, vmacro=0.0, nfe=0.0, \
|
||
linelist=linelist0, atom='ap18', \
|
||
steprot=0.0, stepfwhm=0.0, clean=True, save=None, lte=False, nthreads=0):
|
||
"""
|
||
为多个模型文件计算合成光谱,支持参数组合遍历
|
||
支持微观湍流速度、旋转速度、高斯展宽和氮丰度的多参数组合计算,并统一波长网格进行插值
|
||
Parameters
|
||
-----------
|
||
modelfiles : list of str
|
||
大气模型文件列表
|
||
wrange: tuple of two floats
|
||
波长范围(埃)
|
||
dw: float, 可选
|
||
输出通量的波长步长(负值表示对数插值,默认基于第一个模型自动选择)
|
||
strength: float, 可选
|
||
线连续比阈值(默认1e-4)
|
||
abu: list of floats (99元素), 可选
|
||
元素丰度相对氢的数密度比(默认取模型值)
|
||
vmicro: float或可迭代对象
|
||
微观湍流速度(km/s,负值触发APOGEE公式,默认取模型值)
|
||
vrot: float或可迭代对象
|
||
投影旋转速度(km/s,默认0)
|
||
fwhm: float或可迭代对象
|
||
高斯展宽(埃,默认0)
|
||
vmacro: float或可迭代对象
|
||
径向切向宏观湍流(km/s,默认0)
|
||
nfe: float或可迭代对象
|
||
氮元素丰度相对铁的改变量(dex,默认0)
|
||
linelist: list of str
|
||
线表文件名列表(默认使用linelist0)
|
||
atom: str
|
||
连续谱模型('ap18'/'yo19'/'hhm',默认'ap18')
|
||
steprot: float
|
||
旋转卷积波长步长(默认自动)
|
||
stepfwhm: float
|
||
高斯卷积波长步长(默认自动)
|
||
clean: bool
|
||
是否清理临时文件(默认True)
|
||
save: bool或str
|
||
若为True,保存结果文件;若为字符串,指定文件前缀(默认False)
|
||
lte: bool
|
||
是否强制LTE计算(默认False)
|
||
nthreads: int
|
||
并行使用的线程数(默认0表示使用总核心数-1)
|
||
Returns
|
||
-------
|
||
wave: numpy array
|
||
统一后的波长网格(1D)
|
||
flux: numpy array (2D)
|
||
通量矩阵(行数=模型组合总数)
|
||
cont: numpy array (2D)
|
||
连续谱矩阵(行数=模型组合总数)
|
||
"""
|
||
|
||
# 处理可迭代参数(非迭代参数转换为单元素列表)
|
||
try:
|
||
nvmicro = len(vmicro)
|
||
vmicros = vmicro
|
||
except TypeError:
|
||
nvmicro = 1
|
||
vmicros = [vmicro]
|
||
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [vrot]
|
||
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [fwhm]
|
||
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [vmacro]
|
||
|
||
try:
|
||
nnfe = len(nfe)
|
||
nfes = nfe
|
||
except TypeError:
|
||
nnfe = 1
|
||
nfes = [nfe]
|
||
|
||
assert len(modelfiles) > 0, 'multisyn需要至少一个模型文件'
|
||
|
||
wave = None
|
||
flux = None
|
||
cont = None
|
||
|
||
for entry in modelfiles: # 遍历所有模型文件
|
||
for vmicro1 in vmicros: # 遍历微观湍流参数组合
|
||
for nfe1 in nfes: # 遍历氮丰度参数组合
|
||
abu1 = copy.copy(abu)
|
||
|
||
# 调整氮元素丰度(Z=7)
|
||
if abs(nfe1) > 1e-7:
|
||
if abu1 is None:
|
||
# 读取原始丰度
|
||
linelist, entry = checksynspec(linelist, entry)
|
||
_, _, _, _, abu1, _, _ = read_model(entry)
|
||
abu1[6] = abu1[6] * 10.**nfe1 # Z=7为氮元素
|
||
|
||
# 计算基础光谱(未展宽)
|
||
x, y, z = mpsyn(entry, wrange, dw=None, strength=strength,
|
||
vmicro=vmicro1, abu=abu1, linelist=linelist,
|
||
atom=atom, clean=clean, save=save, lte=lte,
|
||
nthreads=nthreads)
|
||
|
||
space = np.mean(np.diff(x)) # 计算原始波长步长
|
||
|
||
for vrot1 in vrots: # 遍历旋转速度组合
|
||
for fwhm1 in fwhms: # 遍历高斯展宽组合
|
||
for vmacro1 in vmacros: # 遍历宏观湍流组合
|
||
if fwhm1 > 0. or vrot1 > 0. or vmacro1 > 0.:
|
||
# 执行展宽卷积
|
||
print(entry, vmicro1, nfe1, vrot1, fwhm1, vmacro1, space)
|
||
start = time.time()
|
||
x2, y2 = call_rotin(x, y, vrot1, fwhm1, vmacro1,
|
||
space, steprot, stepfwhm,
|
||
clean=False, reuseinputfiles=True)
|
||
z2 = np.interp(x2, x, z)
|
||
end = time.time()
|
||
print(f'展宽耗时:{end - start:.2f}秒')
|
||
else:
|
||
x2, y2, z2 = x, y, z
|
||
|
||
# 统一波长网格
|
||
if (entry == modelfiles[0] and
|
||
vmicro1 == vmicros[0] and
|
||
vrot1 == vrots[0] and
|
||
fwhm1 == fwhms[0] and
|
||
vmacro1 == vmacros[0] and
|
||
nfe1 == nfes[0]):
|
||
# 初始化统一网格
|
||
if dw is None:
|
||
dw = np.median(np.diff(x2))
|
||
if dw < 0.:
|
||
# 对数插值步长
|
||
ldw = abs(dw)/np.mean(wrange)
|
||
nsamples = int((np.log(wrange[1])-np.log(wrange[0]))/ldw) + 1
|
||
wave = np.exp(np.arange(nsamples)*ldw + np.log(wrange[0]))
|
||
else:
|
||
# 线性插值步长
|
||
nsamples = int((wrange[1]-wrange[0])/dw) + 1
|
||
wave = np.arange(nsamples)*dw + wrange[0]
|
||
|
||
# 初始化结果矩阵
|
||
flux = np.interp(wave, x2, y2)
|
||
cont = np.interp(wave, x2, z2)
|
||
else:
|
||
# 合并新计算结果到矩阵
|
||
flux = np.vstack( (flux, np.interp(wave, x, y)) )
|
||
cont = np.vstack( (cont, np.interp(wave, x, z)) )
|
||
|
||
return wave, flux, cont
|
||
|
||
|
||
def polydelta(modelfile, wrange, elem, enhance=0.2, strength=1e-4, vmicro=None, abu=None, \
|
||
linelist=linelist0, atom='ap18', vrot=0.0, fwhm=0.0, vmacro=0.0, \
|
||
steprot=0.0, stepfwhm=0.0, lte=False):
|
||
|
||
"""Sets a a dir tree to compute synthetic spectra for an input model, and then as
|
||
many spectra as elements are input in the elem array (symbols), increasing their
|
||
abundances for one at a time.
|
||
|
||
|
||
Parameters
|
||
----------
|
||
modelfile : str
|
||
file with a model atmosphere
|
||
wrange: tuple or list of two floats
|
||
initial and ending wavelengths (angstroms)
|
||
elem: array of str
|
||
symbols for the elements for which the abundance will be enhanced
|
||
enhance: float, optional
|
||
abundance enhancement (dex)
|
||
strength: float, optional
|
||
threshold in the line-to-continuum opacity ratio for
|
||
selecting lines (default is 1e-4)
|
||
vmicro: float
|
||
microturbulence (km/s)
|
||
a negative value triggers the use of the APOGEE DR14 equation
|
||
(default is None to take it from the model atmosphere)
|
||
abu: array of floats (99 elements), optional
|
||
chemical abundances relative to hydrogen (N(X)/N(H))
|
||
(default taken from input model atmosphere)
|
||
linelist: array of str
|
||
filenames of the line lists, the first one corresponds to
|
||
the atomic lines and all the following ones (optional) to
|
||
molecular lines
|
||
(default is in array linelist0)
|
||
atom: str
|
||
'ap18' -- generic opacities used in Allende Prieto+ 2018
|
||
'yo19' -- restricted set for NLTE calculations for APOGEE 2019 (Osorio+ 2019)
|
||
'hhm' -- continuum opacity is simplified to H and H-
|
||
(default 'ap18')
|
||
vrot: float
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.)
|
||
vmacro: float
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
steprot: float
|
||
wavelength step for convolution with rotational kernel (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
stepfwhm: float
|
||
wavelength step for Gaussian convolution (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
lte: bool
|
||
this flag can be set to True to enforce LTE in NLTE models. MARCS, Kurucz, the
|
||
class of Phoenix models used here are always LTE models. Tlusty models
|
||
can be LTE or NLTE, and this keyword will ignore the populations and compute
|
||
assuming LTE for a input NLTE Tlusty model.
|
||
(default False)
|
||
|
||
Returns
|
||
-------
|
||
A directory tree set up to perform calculations (hyd0000001/2/3...). Each
|
||
folder contains a *.job script. The output spectra go to files named
|
||
0000001fort.7 in each .
|
||
"""
|
||
|
||
#synspec does not currently run in parallel
|
||
nthreads = 1
|
||
|
||
atmostype, teff, logg, vmicro2, abu2, nd, atmos = read_model(modelfile)
|
||
|
||
if vmicro is None:
|
||
vmicro = vmicro2
|
||
elif vmicro < 0.0:
|
||
#Holtzman et al. 2015, AJ 150, 148
|
||
vmicro = 2.478 - 0.325 * logg
|
||
|
||
|
||
if abu is None: abu = abu2
|
||
|
||
|
||
#change chemical symbols into indices
|
||
symbol, mass, sol = elements()
|
||
index = []
|
||
for entry in elem: index.append(symbol.index(entry))
|
||
|
||
idir = 0
|
||
for j in range(len(elem)+1):
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
try:
|
||
if dir != '.': os.mkdir(dir)
|
||
except OSError:
|
||
print( "cannot create dir hyd%07d" % (idir) )
|
||
try:
|
||
os.chdir(dir)
|
||
except OSError:
|
||
print( "cannot change dir to hyd%07d" % (idir) )
|
||
|
||
if modelfile == 'missing':
|
||
pass
|
||
else:
|
||
#setup the slurm script
|
||
sfile = dir+".job"
|
||
now=time.strftime("%c")
|
||
s = open(sfile ,"w")
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple on "+now+" \n")
|
||
s.write("#SBATCH -J "+dir+" \n")
|
||
s.write("#SBATCH -o "+dir+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+dir+"_%j.err"+" \n")
|
||
#s.write("#SBATCH -n "+str(nthreads)+" \n")
|
||
s.write("#SBATCH --ntasks-per-node="+str(1)+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(1)+" \n")
|
||
s.write("#SBATCH -t 00:15:00"+" \n") #hh:mm:ss
|
||
s.write("#SBATCH -D "+os.path.abspath(os.curdir)+" \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n\n\n")
|
||
|
||
abu3 = abu[:]
|
||
if j > 0: abu3[index[j-1]] = abu3[index[j-1]]*10.**(enhance)
|
||
|
||
x, y, z = syn(modelfile, wrange, dw=None, strength=1e-4, vmicro=vmicro, abu=abu3, \
|
||
linelist=linelist, atom=atom, vrot=vrot, fwhm=fwhm, vmacro=vmacro, \
|
||
steprot=steprot, stepfwhm=stepfwhm, clean=False, lte=lte, \
|
||
compute=False, tmpdir='.')
|
||
|
||
s.write(synspec+" < "+"fort.5"+"\n")
|
||
|
||
si = open("fort.55",'r')
|
||
for i in range(6): line = si.readline()
|
||
entries = line.split()
|
||
space = float(entries[5])
|
||
si.close()
|
||
|
||
iconv = 1
|
||
inconv = ("%07dfort.5" % (iconv) )
|
||
outconv = ("'%07dfort.7'" % (iconv) )
|
||
if fwhm> 0. or vrot > 0.:
|
||
f = open(inconv,'w')
|
||
f.write( ' %s %s %s \n' % ("'fort.7'", "'fort.17'", outconv) )
|
||
f.write( ' %f %f %f \n' % (vrot, space, steprot) )
|
||
f.write( ' %f %f %f \n' % (fwhm, stepfwhm, vmacro) )
|
||
print('stepfwhm=',stepfwhm)
|
||
f.write( ' %f %f %i \n' % (wrange[0], wrange[1], 0) )
|
||
f.close()
|
||
s.write(rotin+" < "+inconv+"\n")
|
||
else:
|
||
s.write("cp "+" fort.7 "+outconv[1:-1]+"\n")
|
||
|
||
s.close()
|
||
os.chmod(sfile ,0o755)
|
||
|
||
try:
|
||
os.chdir('..')
|
||
except OSError:
|
||
print( "cannot exit dir hyd%07d" % (idir) )
|
||
|
||
|
||
return(None,None,None)
|
||
|
||
def collectdelta(modelfile, wrange, elem, enhance=0.2,
|
||
strength=1e-4, vmicro=None, abu=None, \
|
||
linelist=linelist0, atom='ap18', vrot=0.0, fwhm=0.0, vmacro=0.0, \
|
||
steprot=0.0, stepfwhm=0.0, lte=False):
|
||
|
||
"""Collects the spectra, after computed, in a dir tree created with polydelta, and writes them out to an output file (modelfile.dlt)
|
||
|
||
|
||
Parameters
|
||
----------
|
||
modelfile : str
|
||
file with a model atmosphere
|
||
wrange: tuple or list of two floats
|
||
initial and ending wavelengths (angstroms)
|
||
elem: array of str
|
||
symbols for the elements for which the abundance will be enhanced
|
||
enhance: float, optional
|
||
abundance enhancement (dex)
|
||
strength: float, optional
|
||
threshold in the line-to-continuum opacity ratio for
|
||
selecting lines (default is 1e-4)
|
||
vmicro: float
|
||
microturbulence (km/s)
|
||
a negative value triggers the use of the APOGEE DR14 equation
|
||
(default is None to take it from the model atmosphere)
|
||
abu: array of floats (99 elements), optional
|
||
chemical abundances relative to hydrogen (N(X)/N(H))
|
||
(default taken from input model atmosphere)
|
||
linelist: array of str
|
||
filenames of the line lists, the first one corresponds to
|
||
the atomic lines and all the following ones (optional) to
|
||
molecular lines
|
||
(default is in array linelist0)
|
||
atom: str
|
||
'ap18' -- generic opacities used in Allende Prieto+ 2018
|
||
'yo19' -- restricted set for NLTE calculations for APOGEE 2019 (Osorio+ 2019)
|
||
'hhm' -- continuum opacity is simplified to H and H-
|
||
(default 'ap18')
|
||
vrot: float
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.)
|
||
vmacro: float
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
steprot: float
|
||
wavelength step for convolution with rotational kernel (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
stepfwhm: float
|
||
wavelength step for Gaussian convolution (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
lte: bool
|
||
this flag can be set to True to enforce LTE in NLTE models. MARCS, Kurucz, the
|
||
class of Phoenix models used here are always LTE models. Tlusty models
|
||
can be LTE or NLTE, and this keyword will ignore the populations and compute
|
||
assuming LTE for a input NLTE Tlusty model.
|
||
(default False)
|
||
|
||
Returns
|
||
-------
|
||
No data are return, but a file (modelfile.dlt) is produced with a header, and then
|
||
the wavelength array, the flux for the input abundances, and the perturbed flux with
|
||
enhanced abundance for each of the elements in elem.
|
||
"""
|
||
|
||
|
||
atmostype, teff, logg, vmicro2, abu2, nd, atmos = read_model(modelfile)
|
||
|
||
if vmicro is None:
|
||
vmicro = vmicro2
|
||
elif vmicro < 0.0:
|
||
#Holtzman et al. 2015, AJ 150, 148
|
||
vmicro = 2.478 - 0.325 * logg
|
||
|
||
|
||
if abu is None: abu = abu2
|
||
|
||
if save:
|
||
out = open(modelfile+'.dlt','w')
|
||
out.write('MODEL = '+modelfile+'\n')
|
||
out.write('TEFF = '+str(teff)+'\n')
|
||
out.write('LOGG = '+str(logg)+'\n')
|
||
out.write('VMICRO = '+str(vmicro)+'\n')
|
||
out.write('WRANGE = '+' '.join(map(str,wrange))+'\n')
|
||
out.write('ELEM = '+' '.join(elem)+'\n')
|
||
out.write('ENHANCE = '+str(enhance)+'\n')
|
||
out.write('STRENGTH= '+str(strength)+'\n')
|
||
out.write('LINELIST= '+' '.join(linelist)+'\n')
|
||
out.write('ATOM = '+atom+'\n')
|
||
out.write('VROT = '+str(vrot)+'\n')
|
||
out.write('FWHM = '+str(fwhm)+'\n')
|
||
out.write('VMACRO = '+str(vmacro)+'\n')
|
||
out.write('STEPROT = '+str(steprot)+'\n')
|
||
out.write('STEPFWHM= '+str(stepfwhm)+'\n')
|
||
out.write('LTE = '+str(lte)+'\n')
|
||
out.write('ABU = '+' '.join(map(str,abu))+'\n')
|
||
|
||
idir = 0
|
||
for j in range(len(elem)+1):
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
|
||
#print('j,idir=',j,idir)
|
||
|
||
try:
|
||
os.chdir(dir)
|
||
except OSError:
|
||
print( "cannot change dir to hyd%07d" % (idir) )
|
||
|
||
x, y = np.loadtxt('0000001fort.7', unpack=True)
|
||
|
||
if j == 0:
|
||
xx = x
|
||
if save:
|
||
np.savetxt(out,[xx], fmt='%12.5e')
|
||
np.savetxt(out,[y], fmt='%12.5e')
|
||
else:
|
||
yy2 = np.interp(xx, x, y)
|
||
#yy2 = interp_spl(xx, x, y)
|
||
if save: np.savetxt(out,[yy2], fmt='%12.5e')
|
||
|
||
|
||
try:
|
||
os.chdir('..')
|
||
except OSError:
|
||
print( "cannot exit dir hyd%07d" % (idir) )
|
||
|
||
out.close()
|
||
|
||
return None
|
||
|
||
def mkflt(dltfile,wavelengths,blocks=[],fwhm=0.0,unit='km/s',outdir='.'):
|
||
|
||
"""produces FERRE filters from a dlt file (output from collectdelta)
|
||
|
||
|
||
Parameters
|
||
----------
|
||
dltfile : str
|
||
file with the fluxes for abundance perturbations in various elements, produced by
|
||
polydelta+collectdelta
|
||
wavelengths: float array
|
||
wavelength array for which we want the filters to be resampled (angstroms). These
|
||
should match those of the FERRE grid with which the filters will be used
|
||
blocks: array of 2-element arrays/tuples defining wavelength intervals to be blocked in the filters (set to zero values, i.e. not to be used by FERRE). Should have the same units as the wavelengths array (angstroms).
|
||
(default is [], so no region is to be blocked)
|
||
fwhm: float
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms or km/s)
|
||
(default 0.)
|
||
unit: str
|
||
Units for the FWHM of the Gaussian kernel ('km/s' or 'A')
|
||
(default 'km/s')
|
||
|
||
outdir: str
|
||
Folder to place the output flt files
|
||
(default is './')
|
||
|
||
Returns
|
||
-------
|
||
No data are return, but a FERRE filter file is written for each of the elements which
|
||
abundance has been perturned in the input dlt file (see ELEM in the header of the file).
|
||
"""
|
||
|
||
assert (unit == 'km/s' or unit == 'A'),'unit for FWHM must be km/s or A (Angstroms)'
|
||
|
||
f = open(dltfile,'r')
|
||
flux = False
|
||
|
||
k = 0
|
||
hd = {}
|
||
for line in f:
|
||
#print('line=',line)
|
||
if '=' in line:
|
||
b = line.split('=')
|
||
hd[b[0].strip()] = b[1]
|
||
else:
|
||
if flux:
|
||
y = np.array(line.split(), dtype=float)
|
||
if fwhm > 0.0:
|
||
if unit == 'km/s':
|
||
xc,yc = vgconv(x,y,fwhm)
|
||
else:
|
||
xc,yc = lgconv(x,y,fwhm)
|
||
else:
|
||
xc = x[:]
|
||
yc = y[:]
|
||
|
||
y2 = np.interp(wavelengths, xc, yc)
|
||
#y2 = interp_spl(wavelengths, xc, yc)
|
||
|
||
if k == 0:
|
||
wrange = list(map(float,hd['WRANGE'].split()))
|
||
assert(np.min(wavelengths) >= wrange[0]),'Attempted to interpolate to wavelengths shorter than the minimum in the input dlt file '+dltfile
|
||
assert(np.max(wavelengths) <= wrange[1]),'Attempted to interpolate to wavelengths longer than the maximum in the input dlt file '+dltfile
|
||
yref = y2
|
||
else:
|
||
#f2 = open(elem[k-1]+'.flt','w')
|
||
we = y2/yref/np.median(y2/yref)
|
||
wp = we > 1.0
|
||
we[wp] = 1.0
|
||
if np.min(we) > 0.999:
|
||
we[:] = 1.0
|
||
#else:
|
||
#we = (we - np.min(we))/(np.max(we) - np.min(we))
|
||
we = 1.- we
|
||
|
||
#apply blocks
|
||
if len(blocks) > 0:
|
||
for entry in blocks:
|
||
wblock = (wavelengths >= entry[0]) & (wavelengths <= entry[1])
|
||
if len(np.where(wblock)[0]) > 1: we[wblock] = 0.0
|
||
|
||
if k == 1:
|
||
yy = we
|
||
else:
|
||
yy = np.vstack ( (yy, we ) )
|
||
#np.savetxt(f2,we, fmt='%12.5e')
|
||
#f2.close()
|
||
k = k + 1
|
||
else:
|
||
x = np.array(line.split(), dtype=float)
|
||
flux = True
|
||
elem = hd['ELEM'].strip().split()
|
||
|
||
f.close()
|
||
|
||
#print(yy.shape)
|
||
|
||
|
||
#write out the filter files
|
||
k = 0
|
||
total = np.sum(yy,0)
|
||
for el in elem:
|
||
f = open(os.path.join(outdir,el+'.flt'),'w')
|
||
res = 2.* yy[k,:] - total
|
||
wn = res < 0.
|
||
res[wn] = 0.0
|
||
np.savetxt(f,res, fmt='%12.5e')
|
||
k = k + 1
|
||
f.close()
|
||
|
||
return None
|
||
|
||
|
||
def polysyn(modelfiles, wrange, strength=1e-4, abu=None, \
|
||
vmicro=None, vrot=0.0, fwhm=0.0, vmacro=0.0, \
|
||
linelist=linelist0, atom='ap18', \
|
||
steprot=0.0, stepfwhm=0.0, clean=True, save=None, lte=True,
|
||
nchem=1, **kargs):
|
||
|
||
"""Sets up a directory tree for computing synthetic spectra for a list of files in
|
||
parallel. The values of vmicro, vrot, fwhm, and nfe can be iterables.
|
||
|
||
|
||
Parameters
|
||
----------
|
||
modelfiles : list of str
|
||
files with model atmospheres
|
||
wrange: tuple or list of two floats
|
||
initial and ending wavelengths (angstroms)
|
||
strength: float, optional
|
||
threshold in the line-to-continuum opacity ratio for
|
||
selecting lines (default is 1e-4)
|
||
abu: array of floats (99 elements), optional
|
||
chemical abundances relative to hydrogen (N(X)/N(H))
|
||
(default taken from input model atmosphere)
|
||
vmicro: float, can be an iterable
|
||
microturbulence (km/s)
|
||
a negative value triggers the use of the APOGEE DR14 equation
|
||
(default is None to take it from the model atmosphere)
|
||
vrot: float, can be an iterable
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float, can be an iterable
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.)
|
||
vmacro: float
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
nfe: float, can be an iterable
|
||
[N/Fe] nitrogen abundance change from the one specified in the array 'abu' (dex)
|
||
(default 0.)
|
||
linelist: array of str
|
||
filenames of the line lists, the first one corresponds to
|
||
the atomic lines and all the following ones (optional) to
|
||
molecular lines
|
||
(default is in array linelist0)
|
||
atom: str
|
||
'ap18' -- generic opacities used in Allende Prieto+ 2018
|
||
'yo19' -- restricted set for NLTE calculations for APOGEE 2019 (Osorio+ 2019)
|
||
'hhm' -- continuum opacity is simplified to H and H-
|
||
(default 'ap18')
|
||
steprot: float
|
||
wavelength step for convolution with rotational kernel (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
stepfwhm: float
|
||
wavelength step for Gaussian convolution (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
clean: bool
|
||
True by the default, set to False to avoid the removal of the synspec
|
||
temporary files/links (default True)
|
||
save: bool
|
||
set to True to save the computed spectra to files (default False)
|
||
the root of the model atmosphere file, with an extension ".syn" will be used
|
||
if multiple values of vmicro, vrot, fwhm or nfe are used, their values are
|
||
prepended to the file names
|
||
(default None)
|
||
lte: bool
|
||
this flag can be set to True to enforce LTE in NLTE models. MARCS, Kurucz, the
|
||
class of Phoenix models used here are always LTE models. Tlusty models
|
||
can be LTE or NLTE, and this keyword will ignore the populations and compute
|
||
assuming LTE for a input NLTE Tlusty model.
|
||
(default False)
|
||
nchem: int
|
||
number of combinations of abundances to include in the synspec
|
||
calculations when kargs are pairs (irregular grids).
|
||
All these will be performed for every model in modelfiles.
|
||
Which elements to
|
||
vary and the range of values for each are specified through kargs. Note:
|
||
calculations for regular grids will become irregular when nchem > 1.
|
||
This parameter is updated automatically in the subroutine for regular
|
||
grids.
|
||
(default 1)
|
||
kargs: tuples
|
||
For irregular grids with random abundances as many pairs as necessary,
|
||
indicating the range for elemental variations [X/Fe]
|
||
e.g. Na=(-0.2,0.2), Al=(-0.5, 0.2), ...
|
||
For regular grids as many triplets as necessary, giving the number of
|
||
steps, the lower limits and the stepsize for elemental variations [X/Fe]
|
||
e.g. Na=(9,-0.2,0.05), ...
|
||
All entries must be pairs or triplets.
|
||
|
||
|
||
Returns
|
||
-------
|
||
builds a directory tree ready to perform synspec calculations
|
||
|
||
"""
|
||
|
||
#synspec does not currently run in parallel
|
||
nthreads = 1
|
||
|
||
|
||
#when vmicro, vrot, fwhm or nitrogen are not iterables, we create ones, otherwise we copy them
|
||
try:
|
||
nvmicro = len(vmicro)
|
||
vmicros = vmicro
|
||
except TypeError:
|
||
nvmicro = 1
|
||
vmicros = [ vmicro ]
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [ vrot ]
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [ vmacro ]
|
||
chems = dict() # abundance variations [X/Fe]
|
||
symbols = [] #elemental symbols X
|
||
for entry in list(map(str,kargs.keys())): symbols.append(entry)
|
||
iel = 0
|
||
for entry in kargs.values():
|
||
print(entry)
|
||
if iel == 0:
|
||
n_p = []
|
||
symbol, mass, sol = elements()
|
||
zatom = dict()
|
||
for i in range(len(symbol)):
|
||
zatom[symbol[i]] = i + 1
|
||
assert(len(entry) == 2 or len(entry) == 3),'kargs entries must have 2 (irregular grids with random values) or 3 (regular grids) entries'
|
||
if len(entry) == 2:
|
||
chems[symbols[iel]] = np.random.random_sample(nchem)*(entry[1]-entry[0])+entry[0]
|
||
else:
|
||
chems[symbols[iel]] = np.arange(entry[0])*entry[2]+entry[1]
|
||
n_p.append(entry[0])
|
||
iel += 1
|
||
|
||
lenen = 2
|
||
if iel > 0 and len(entry) == 3:
|
||
lenen = 3
|
||
kems = np.array(list(product(*chems.values())))
|
||
nchem = len(kems[:,0])
|
||
iel = 0
|
||
for entry in kargs.keys():
|
||
chems[entry] = kems[:,iel]
|
||
iel += 1
|
||
|
||
|
||
|
||
idir = 0
|
||
ichem = -1
|
||
dirfile = open('dirtree.txt','w')
|
||
for entry in modelfiles:
|
||
for vmicro1 in vmicros:
|
||
for ichem in range(nchem):
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
cadena = str(idir)+' folder='+dir+' model='+entry+' vmicro='+str(vmicro1)
|
||
iel = 0
|
||
for el in symbols:
|
||
cadena = cadena + ' ['+el+'/Fe]='+str(chems[el][ichem]) + ' '
|
||
iel += 1
|
||
cadena = cadena + '\n'
|
||
dirfile.write(cadena)
|
||
try:
|
||
if dir != '.': os.mkdir(dir)
|
||
except OSError:
|
||
print( "cannot create dir hyd%07d" % (idir) )
|
||
try:
|
||
os.chdir(dir)
|
||
except OSError:
|
||
print( "cannot change dir to hyd%07d" % (idir) )
|
||
|
||
if entry == 'missing' or os.path.getsize(entry) == 0:
|
||
pass
|
||
else:
|
||
atmostype, teff, logg, vmicro2, abu1, nd, atmos = read_model(entry)
|
||
if teff is None:
|
||
pass
|
||
else:
|
||
#setup the slurm script
|
||
sfile = dir+".job"
|
||
now=time.strftime("%c")
|
||
s = open(sfile ,"w")
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple on "+now+" \n")
|
||
s.write("#SBATCH -J "+dir+" \n")
|
||
s.write("#SBATCH -o "+dir+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+dir+"_%j.err"+" \n")
|
||
#s.write("#SBATCH -n "+str(nthreads)+" \n")
|
||
s.write("#SBATCH --ntasks-per-node="+str(1)+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(1)+" \n")
|
||
s.write("#SBATCH -t 04:00:00"+" \n") #hh:mm:ss
|
||
s.write("#SBATCH -D "+os.path.abspath(os.curdir)+" \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n\n\n")
|
||
|
||
|
||
abu1 = copy.copy(abu)
|
||
|
||
#if need be, adjust nitrogen abundance according to nfe
|
||
if (ichem > -1):
|
||
if (abu1 == None):
|
||
linelist, entry = checksynspec(linelist,entry)
|
||
atmostype, teff, logg, vmicro2, abu1, nd, atmos = read_model(entry)
|
||
iel = 0
|
||
for el in symbols:
|
||
abu1[zatom[el]-1] = abu1[zatom[el]-1] * 10.**chems[el][ichem]
|
||
iel += 1
|
||
|
||
|
||
x, y, z = syn(entry, wrange, dw=None, strength=strength, vmicro=vmicro1, \
|
||
abu=abu1, linelist=linelist, atom=atom, lte=lte, clean=False, compute=False, tmpdir=".")
|
||
|
||
s.write("cd "+os.path.abspath(os.curdir)+" \n")
|
||
s.write(synspec+" < "+"fort.5"+"\n")
|
||
|
||
si = open("fort.55",'r')
|
||
for i in range(6): line = si.readline()
|
||
entries = line.split()
|
||
space = float(entries[5])
|
||
si.close()
|
||
|
||
iconv = 0
|
||
for vrot1 in vrots:
|
||
for fwhm1 in fwhms:
|
||
for vmacro1 in vmacros:
|
||
|
||
print('iconv=',iconv)
|
||
|
||
dirfile.write(' -- '+str(iconv+1)+' vrot='+str(vrot1)+' fwhm='+str(fwhm1)+'\n')
|
||
iconv = iconv + 1
|
||
inconv = ("%07dfort.5" % (iconv) )
|
||
outconv = ("'%07dfort.7'" % (iconv) )
|
||
if vrot1 > 0.0 or fwhm1 > 0. or vmacro1 > 0.:
|
||
f = open(inconv,'w')
|
||
f.write( ' %s %s %s \n' % ("'fort.7'", "'fort.17'", outconv) )
|
||
f.write( ' %f %f %f \n' % (vrot1, space, steprot) )
|
||
f.write( ' %f %f %f \n' % (fwhm1, stepfwhm, vmacro1) )
|
||
print('stepfwhm=',stepfwhm)
|
||
f.write( ' %f %f %i \n' % (wrange[0], wrange[1], 0) )
|
||
f.close()
|
||
s.write(rotin+" < "+inconv+"\n")
|
||
else:
|
||
s.write("cp "+" fort.7 "+outconv[1:-1]+"\n")
|
||
|
||
s.close()
|
||
os.chmod(sfile ,0o755)
|
||
|
||
try:
|
||
os.chdir('..')
|
||
except OSError:
|
||
print( "cannot exit dir hyd%07d" % (idir) )
|
||
|
||
|
||
return()
|
||
|
||
|
||
def build_exomol(isosum_folder='isosum_data'):
|
||
|
||
# code to build linelists from exomol data using Ivan Hubeny's scripts
|
||
|
||
isosum_files = sorted(glob.glob(os.path.join(isosum_folder,'*.5')))
|
||
|
||
for entry in isosum_files:
|
||
print(entry,'build_exomol_diatomic(entry)')
|
||
fh = open(entry,'r')
|
||
zs = fh.readline()
|
||
print('first line of ',entry,'=',zs)
|
||
fh.close()
|
||
data = np.loadtxt(entry, skiprows=1, dtype=str)
|
||
if data.ndim == 1:
|
||
niso = 1
|
||
elif data.ndim == 2:
|
||
niso = data.shape[0]
|
||
else:
|
||
print('error!! the isosum data in ',entry,' does not conform to expectations')
|
||
continue
|
||
|
||
|
||
tmp = os.path.split(entry)
|
||
molecule = tmp[-1][:-2]
|
||
|
||
#create folder
|
||
try:
|
||
folder = os.path.join('linelists',molecule)
|
||
os.mkdir(folder)
|
||
print('created folder ',folder)
|
||
except OSError:
|
||
print('cannot create folder ',folder)
|
||
|
||
#write the .5 file
|
||
fh = open(os.path.join(folder,molecule+'.5'),'w')
|
||
fh.write(zs)
|
||
if niso == 1:
|
||
fh.write(' '+' '.join(data)+'\n')
|
||
else:
|
||
for i in range(niso):
|
||
fh.write(' '+' '.join(data[i,:])+'\n')
|
||
fh.close()
|
||
|
||
#write the download.list file
|
||
fh = open(os.path.join(folder,'download.list'),'w')
|
||
for i in range(niso):
|
||
if niso == 1:
|
||
cad = data[2][1:-4]
|
||
else:
|
||
cad = data[i,2][1:-4]
|
||
parts = cad.split('__')
|
||
iso = parts[0]
|
||
tag = parts[1]
|
||
if i == 0:
|
||
url = os.path.join('https://www.exomol.com/db',molecule,tag+'_README.txt')
|
||
fh.write(url+'\n')
|
||
url = os.path.join('https://www.exomol.com/db',molecule,iso,tag,cad+'.states.bz2')
|
||
fh.write(url+'\n')
|
||
url = os.path.join('https://www.exomol.com/db',molecule,iso,tag,cad+'.trans.bz2')
|
||
fh.write(url+'\n')
|
||
url = os.path.join('https://www.exomol.com/db',molecule,iso,tag,cad+'.pf')
|
||
fh.write(url+'\n')
|
||
fh.close()
|
||
|
||
#write the cc?.5 files
|
||
zzo = np.array(np.sort(np.array(zs.split(),dtype=int)),dtype=str)
|
||
if int(zzo[1]) < 10: zzo[1] = '0'+zzo[1]
|
||
if molecule[-1] == '+':
|
||
zzs = zzo[0]+zzo[1]+'.01'
|
||
else:
|
||
zzs = zzo[0]+zzo[1]+'.00'
|
||
zz = zs.split()
|
||
for i in range(niso):
|
||
fh = open(os.path.join(folder,'cc'+str(i+1)+'.5'),'w')
|
||
fh.write(str(zzs)+" '"+molecule+"'"+'\n')
|
||
if niso == 1:
|
||
fh.write(' '.join([zz[0],data[0],zz[1],data[1]])+'\n')
|
||
fh.write(data[2][:-4]+"'"+'\n')
|
||
else:
|
||
fh.write(' '.join([zz[0],data[i,0],zz[1],data[i,1]])+'\n')
|
||
fh.write(data[i,2][:-4]+"'"+'\n')
|
||
fh.write('100 100000 -9.0\n')
|
||
fh.close()
|
||
|
||
#write the R0 file
|
||
fh = open(os.path.join(folder,'R0'),'w')
|
||
fh.write('awk '+"'"+'{print "wget "$0}'+"'"+' download.list |sh \n')
|
||
fh.write('bunzip2 *bz2 \n')
|
||
fh.write('ln -s -f ../../xprog/isotops . \n')
|
||
fh.write('../../xprog/isosum.exe < '+molecule+'.5 > '+molecule+'.log \n')
|
||
fh.write('cp fort.10 '+molecule+'.pf \n')
|
||
fh.close()
|
||
os.chmod(os.path.join(folder,'R0') ,0o755)
|
||
|
||
#write the R1 file
|
||
fh = open(os.path.join(folder,'R1'),'w')
|
||
for i in range(niso):
|
||
fh.write('echo building cc'+str(i+1)+'.list \n')
|
||
fh.write('../../xprog/list.exe <cc'+str(i+1)+'.5 >cc'+str(i+1)+'.log \n')
|
||
fh.write('../../xprog/reverse.exe <fort.10 >cc'+str(i+1)+'.list \n')
|
||
if i > 0:
|
||
fh.write('echo merging cc'+str(i)+'.list and cc'+str(i+1)+'.list \n')
|
||
fh.write('ln -s -f cc'+str(i)+'.list fort.10 \n')
|
||
fh.write('../../xprog/merge.exe <cc'+str(i+1)+'.list >cc'+str(i)+str(i+1)+'.log \n')
|
||
fh.write('mv fort.11 cc'+str(i+1)+'.list \n')
|
||
|
||
fh.write('mv cc'+str(i+1)+'.list '+molecule+'.list \n')
|
||
fh.close()
|
||
os.chmod(os.path.join(folder,'R1'),0o755)
|
||
|
||
return()
|
||
|
||
|
||
|
||
def polyopt(wrange=(9.e2,1.e5), dlw=2.1e-5, binary=False, strength=1e-4, inttab=1, \
|
||
abu=None, linelist=linelist0, \
|
||
tlt = (20,3.08,0.068), tlrho = (20,-14.0,0.59), \
|
||
tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0), tcfe=(1,0.0,0.0), tnfe=(1,0.0,0.0), \
|
||
tofe=(1,0.0,0.0), trfe=(1,0.0,0.0), tsfe=(1,0.0,0.0), tvmicro=(1,1.0,0.0), \
|
||
zexclude=None, atom='ap18'):
|
||
|
||
"""Sets up a directory tree for computing opacity tables for TLUSTY. The table collection forms
|
||
a regular grid defined by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in (log10 of) temperature (tlt) and (log10 of) density
|
||
(tlrho) are mandatory. Triads in [Fe/H] (tfeh), [alpha/Fe] (tafe), [C/Fe] (tcfe),
|
||
[N/Fe] (tnfe), [O/Fe] (tofe), [r/Fe] (rfe), and [s/Fe] (sfe) are optional since
|
||
arrays with just one 0.0 are included by default.
|
||
|
||
Parameters
|
||
----------
|
||
wrange: tuple or list of two floats
|
||
initial and ending wavelengths (angstroms)
|
||
dlw: float
|
||
step in log10(lambda) for the output opacity table.
|
||
Unlike in 'syn', interpolation to a constant step will always be done
|
||
to build the opacity table. If one wishes to resolve lines, it is advisable
|
||
to use dlw ~ 1e-6 or smaller. However, for tables used to build model atmospheres
|
||
dlw ~ 1e-5 suffices in more cases. NOTE that the actual calculation uses a different
|
||
step, computed internally to ensure that lines are resolved.
|
||
(default value is 2.1e-5)
|
||
binary: boolean
|
||
when true, the output table is written in binary format to speed up
|
||
reading it from tlusty
|
||
(default is False)
|
||
strength: float, optional
|
||
threshold in the line-to-continuum opacity ratio for
|
||
selecting lines (default is 1e-4)
|
||
inttab: int
|
||
a switch for determining the mode of transformation of opacities from
|
||
the actual calculation to the opacity table:
|
||
1 indicates that the opacities in the table are simply interpolated
|
||
any other integer will cause that the stored opacities are averaged
|
||
over bins to approximately preserve the integral over wavelength.
|
||
This latter option can be useful for model construction, where the integral
|
||
is what matters, and be sufficient to work with values dlw>1e-5.
|
||
(default is 1)
|
||
abu: array of floats (99 elements), optional
|
||
chemical abundances relative to hydrogen (N(X)/N(H))
|
||
(default is solar -- see the function 'elements' )
|
||
linelist: array of str
|
||
filenames of the line lists, the first one corresponds to
|
||
the atomic lines and all the following ones (optional) to
|
||
molecular lines. Give an empty array for a continuum-only (+ H and HeII lines) table
|
||
(default is in array linelist0)
|
||
tlt: tuple
|
||
log10(T) triad (n, llimit, step) for opacity grid
|
||
(default values chosen for grid lt = np.arange(20)*0.068 + 3.08,
|
||
to cover the range in the DR16 APOGEE MARCS grids)
|
||
tlrho: tuple
|
||
log10(rho) triad (n, llimit, step) for opacity grid
|
||
(default values chosen for grid lrho = np.arange(20)*0.59 -14.0,
|
||
to cover the range in the DR16 APOGEE MARCS grids)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tnfe: tuple
|
||
[N/Fe] triad
|
||
tofe: tuple
|
||
[O/Fe] triad
|
||
trfeh: tuple
|
||
[r/Fe] triad (r-elements abundance ratio)
|
||
tsfeh: tuple
|
||
[s.Fe] triad (s-elements abundance ratio)
|
||
tvmidro: tuple
|
||
vmicro triad (km/s)
|
||
zexclude: list
|
||
atomic numbers of the elements whose opacity is NOT to be
|
||
included in the table
|
||
(default None)
|
||
atom: str
|
||
'ap18' -- generic opacities used in Allende Prieto+ 2018
|
||
'yo19' -- restricted set for NLTE calculations for APOGEE 2019 (Osorio+ 2019)
|
||
'hhm' -- continuum opacity is simplified to H and H-
|
||
(default 'ap18')
|
||
"""
|
||
|
||
#synspec does not currently run in parallel
|
||
nthreads = 1
|
||
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
ncfe = len(tcfe)
|
||
assert (ncfe == 3), 'Error: cfe triad must have three elements (n, llimit, step)'
|
||
cfes = np.arange(tcfe[0])*tcfe[2] + tcfe[1]
|
||
except TypeError:
|
||
print('Error: cfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nnfe = len(tnfe)
|
||
assert (nnfe == 3), 'Error: nfe triad must have three elements (n, llimit, step)'
|
||
nfes = np.arange(tnfe[0])*tnfe[2] + tnfe[1]
|
||
except TypeError:
|
||
print('Error: nfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nofe = len(tofe)
|
||
assert (nofe == 3), 'Error: ofe triad must have three elements (n, llimit, step)'
|
||
ofes = np.arange(tofe[0])*tofe[2] + tofe[1]
|
||
except TypeError:
|
||
print('Error: ofe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nrfe = len(trfe)
|
||
assert (nrfe == 3), 'Error: rfe triad must have three elements (n, llimit, step)'
|
||
rfes = np.arange(trfe[0])*trfe[2] + trfe[1]
|
||
except TypeError:
|
||
print('Error: rfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nsfe = len(tsfe)
|
||
assert (nsfe == 3), 'Error: sfe triad must have three elements (n, llimit, step)'
|
||
sfes = np.arange(tsfe[0])*tsfe[2] + tsfe[1]
|
||
except TypeError:
|
||
print('Error: sfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nvmicro = len(tvmicro)
|
||
assert (nvmicro == 3), 'Error: vmicro triad must have three elements (n, llimit, step)'
|
||
vmicros = np.arange(tvmicro[0])*tvmicro[2] + tvmicro[1]
|
||
except TypeError:
|
||
print('Error: vmicro triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
|
||
#ranges for the opacity table
|
||
try:
|
||
nlt = len(tlt)
|
||
assert (nlt == 3), 'Error: lt triad must have three elements (n, llimit, step)'
|
||
lt = np.arange(tlt[0])*tlt[2] + tlt[1] #log10(T)
|
||
except TypeError:
|
||
print('Error: tlt triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlrho = len(tlrho)
|
||
assert (nlrho == 3), 'Error: lrho triad must have three elements (n, llimit, step)'
|
||
lrho = np.arange(tlrho[0])*tlrho[2] + tlrho[1] #log10(density)
|
||
except TypeError:
|
||
print('Error: tlrho triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
linelist = checklinelistpath(linelist)
|
||
|
||
space = np.mean(wrange) / clight * 2.355 / 3. * np.sqrt(0.1289**2 * np.min(10.**lt) / 100. + np.min(vmicros)** 2 / 2.)
|
||
|
||
cutoff0=250.
|
||
if 10.**min(lt) < 4000.: cutoff0=500.
|
||
if 10.**min(lt) < 3500.: cutoff0=1000.
|
||
if 10.**min(lt) < 3000.: cutoff0=1500.
|
||
|
||
|
||
symbol, mass, sol = elements()
|
||
z_metals = np.arange(97,dtype=int) + 3
|
||
#Ar usually included among alphas in MARCS and not in Kurucz/Meszaros
|
||
z_alphas = np.array([8,10,12,14,16,18,20,22],dtype=int)
|
||
# rs increases: notes and data below from comments in the MARCS code
|
||
# (provided by B.Edvardsson)
|
||
# Fractional r-process abundance for Ga-Bi (r+s simply assumed == 100%) | Date 2000-01-18
|
||
# (Note: Ga-Sr (31-38) was just copied from Kaeppeler et al. 1989, below)
|
||
# s-process from Stellar models: Arlandini C., Kaeppeler F., Wisshak K.,
|
||
# Gallino R., Busso M., Straniero O., 1999, Astrophys J. 525, 886-900
|
||
# Fractions corrected to the revised meteoritic abundances
|
||
# of Grevesse N., Sauval A.J. 1998, Space Science Review 85, 161-174
|
||
# -0.99 is assigned to unstable elements
|
||
z_rs = np.arange(62,dtype=int) + 31
|
||
rfrac= np.array([.43, .47, .81, .85, .39, .47,
|
||
.41, .11, .08, .17, .15, .50,-.99, .68, .86,
|
||
.54, .80, .48, .65, .35, .75, .83, .80, .80,
|
||
.85, .19, .38, .23, .51, .44,-.99, .71, .93,
|
||
.85, .93, .85, .92, .83, .87, .67, .80, .44,
|
||
.59, .44, .91, .91, .99, .95, .94, .41, .24,
|
||
.54, .95,-.99,-.99,-.99,-.99,-.99,-.99, 1.0,
|
||
-.99, 1.0], dtype=float)
|
||
|
||
|
||
|
||
idir = 0
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
for cfe in cfes:
|
||
for nfe in nfes:
|
||
for ofe in ofes:
|
||
for rfe in rfes:
|
||
for sfe in sfes:
|
||
for vmicro in vmicros:
|
||
|
||
print(feh,afe,cfe,nfe,ofe,rfe,sfe)
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
try:
|
||
if dir != '.': os.mkdir(dir)
|
||
except OSError:
|
||
print( "cannot create dir hyd%07d" % (idir) )
|
||
try:
|
||
os.chdir(dir)
|
||
except OSError:
|
||
print( "cannot change dir to hyd%07d" % (idir) )
|
||
|
||
#check input parameters are valid
|
||
imode = checkinput(wrange, vmicro, linelist)
|
||
|
||
#setup the slurm script
|
||
sfile = dir+".job"
|
||
now=time.strftime("%c")
|
||
s = open(sfile ,"w")
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple on "+now+" \n")
|
||
s.write("#SBATCH -J "+dir+" \n")
|
||
s.write("#SBATCH -o "+dir+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+dir+"_%j.err"+" \n")
|
||
#s.write("#SBATCH -n "+str(nthreads)+" \n")
|
||
#s.write("#SBATCH --ntasks-per-node "+str(4)+" \n")
|
||
s.write("#SBATCH --ntasks-per-node="+str(1)+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(1)+" \n")
|
||
s.write("#SBATCH -t 48:00:00"+" \n") #hh:mm:ss
|
||
s.write("#SBATCH -D "+os.path.abspath(os.curdir)+" \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n\n\n")
|
||
|
||
|
||
if abu is None:
|
||
abu2 = copy.copy(sol)
|
||
else:
|
||
abu2 = copy.copy(abu)
|
||
|
||
if (abs(feh) > 1e-7):
|
||
for i in range(len(z_metals)):
|
||
abu2[z_metals[i] - 1] = abu2[z_metals[i] - 1] * 10.**feh
|
||
if (abs(afe) > 1e-7):
|
||
for i in range(len(z_alphas)):
|
||
abu2[z_alphas[i] - 1] = abu2[z_alphas[i] - 1] * 10.**afe
|
||
if (abs(cfe) > 1e-7): abu2[5] = abu2[5] * 10.**cfe
|
||
if (abs(nfe) > 1e-7): abu2[6] = abu2[6] * 10.**nfe
|
||
if (abs(ofe) > 1e-7): abu2[7] = abu2[7] * 10.**ofe
|
||
if (abs(rfe) > 1e-7):
|
||
for i in range(len(z_rs)):
|
||
if rfrac[i] > 0.0: abu2[z_rs[i] - 1] = abu2[z_rs[i] - 1] * rfrac[i] * 10.**rfe
|
||
if (abs(sfe) > 1e-7):
|
||
for i in range(len(z_rs)):
|
||
if rfrac[i] > 0.0: abu2[z_rs[i] - 1] = abu2[z_rs[i] - 1] * (1.0 - rfrac[i]) * 10.**sfe
|
||
|
||
if (len(linelist) == 0):
|
||
imode = -4
|
||
else: imode = -3
|
||
|
||
|
||
|
||
write55(wrange,dw=space,imode=imode,iprin=0,inlte=0,hydprf=0, \
|
||
cutoff0=cutoff0, strength=strength, vmicro=vmicro, \
|
||
linelist=linelist)
|
||
|
||
write5(9999.,9.9,abu2,atom)
|
||
|
||
writetas('tas',1,linelist)
|
||
|
||
|
||
write2(lt,lrho,wrange, filename='opt.data', \
|
||
dlw=dlw, binary=binary,strength=strength,inttab=inttab)
|
||
|
||
if zexclude != None:
|
||
write3(zexclude)
|
||
|
||
create_links(linelist)
|
||
|
||
assert (not os.path.isdir('data')), 'A subdirectory *data* exists in this folder, and that prevents the creation of a link to the data directory for synple'
|
||
|
||
#link the data folder (synspec data + default tlusty model atoms)
|
||
os.symlink(modelatomdir,'./data') #data directory
|
||
|
||
|
||
s.write('time ' + synspec + " < "+"fort.5"+"\n")
|
||
s.close()
|
||
os.chmod(sfile ,0o755)
|
||
|
||
try:
|
||
os.chdir('..')
|
||
except OSError:
|
||
print( "cannot exit dir hyd%07d" % (idir) )
|
||
|
||
return()
|
||
|
||
def merge_slurm(path='./',ext='slurm',nmerge=2,concurrent=False):
|
||
"""identifies all the *.slurm files in the path and merge them
|
||
in groups of nmerge so that there are fewer/longer jobs.
|
||
The scripts are named job-*.slurm and written to the current folder
|
||
|
||
Parameters
|
||
----------
|
||
path: str
|
||
path under which the slurm jobs are to be found
|
||
(default is './')
|
||
ext: str
|
||
extension of the slurm jobs to be found
|
||
(default is 'slurm')
|
||
nmerge: int
|
||
size of the groups to be created
|
||
(default is 2)
|
||
concurrent: bool
|
||
whether or not the grouped jobs are to be executed concurrently
|
||
(default is False, so the jobs in a group are to be executed serially)
|
||
|
||
Returns
|
||
-------
|
||
None
|
||
|
||
"""
|
||
|
||
slurms = glob.glob(os.path.join(path,'**','*'+ext), recursive=True)
|
||
|
||
nfiles = len(slurms)
|
||
|
||
assert nfiles > 0, 'There are no input files ending in '+ext
|
||
|
||
k = 0
|
||
wtime = -1
|
||
print('nfiles=',nfiles)
|
||
for i in range(nfiles):
|
||
f1 = open(slurms[i],'r')
|
||
j = i % nmerge
|
||
if j == 0:
|
||
k = k + 1
|
||
if k > 1:
|
||
if wtime > -1:
|
||
if concurrent: time = int(time*3.) #factor 3 is a safety margin
|
||
entries = header[wtime].split('=')
|
||
header[wtime] = entries[0]+'='+str(time)+'\n'
|
||
f2.writelines(header)
|
||
if concurrent: body.append("wait\n")
|
||
f2.writelines(body)
|
||
f2.close()
|
||
f2 = open('job-'+"{:04d}".format(k)+'.slurm','w')
|
||
time = 0
|
||
header = []
|
||
body = []
|
||
if concurrent:
|
||
body.append("(\n")
|
||
for line in f1:
|
||
if line[0] == "#":
|
||
if j == 0: header.append(line)
|
||
if '--time' in line:
|
||
entries = line.split('=')
|
||
newtime = int(entries[1])
|
||
if concurrent:
|
||
if newtime > time: time = newtime
|
||
else:
|
||
time = time + newtime
|
||
if j == 0: wtime = len(header)-1
|
||
else:
|
||
body.append(line)
|
||
if concurrent:
|
||
body.append(") & \n")
|
||
|
||
if wtime > -1:
|
||
if concurrent: time = int(time*3.) #factor 3 is a safety margin
|
||
entries = header[wtime].split('=')
|
||
header[wtime] = entries[0]+'='+str(time)+'\n'
|
||
f2.writelines(header)
|
||
if concurrent: body.append("wait\n")
|
||
f2.writelines(body)
|
||
f2.close()
|
||
|
||
|
||
print(slurms)
|
||
|
||
return None
|
||
|
||
|
||
def merge_slurm_parallel(path='./',ext='slurm',nmerge=2,ncpu=2):
|
||
"""identifies all the *.slurm files in the path and merge them
|
||
in groups of nmerge so that there are fewer/longer jobs.
|
||
Inside of the new jobs, gnu parallel will be used to have ncpu
|
||
of the input jobs run simultaneously.
|
||
The new scripts are named job-*.slurm and written to the current
|
||
folder
|
||
|
||
Parameters
|
||
----------
|
||
path: str
|
||
path under which the slurm jobs are to be found
|
||
(default is './')
|
||
ext: str extension of the slurm jobs to be found
|
||
(default is 'slurm')
|
||
nmerge: int
|
||
size of the groups to be created
|
||
(default is 2)
|
||
|
||
ncpu: int
|
||
number of the input jobs to be run simultaneously
|
||
as part of the output jobs
|
||
|
||
Returns
|
||
-------
|
||
None
|
||
|
||
"""
|
||
|
||
slurms = glob.glob(os.path.join(path,'**','*'+ext), recursive=True)
|
||
|
||
nfiles = len(slurms)
|
||
|
||
assert nfiles > 0, 'There are no input files ending in '+ext
|
||
|
||
k = 0
|
||
wtime = -1
|
||
print('nfiles=',nfiles)
|
||
for i in range(nfiles):
|
||
f1 = open(slurms[i],'r')
|
||
j = i % nmerge
|
||
if j == 0:
|
||
k = k + 1
|
||
if k > 1:
|
||
if wtime > -1:
|
||
#if concurrent: time = int(time*3.) #factor 3 is a safety margin
|
||
entries = header[wtime].split('=')
|
||
header[wtime] = entries[0]+'='+str(time)+'\n'
|
||
f2.writelines(header)
|
||
f2.write('module load gnuparallel\n')
|
||
f2.writelines('parallel -j'+str(ncpu)+" :::: "+"input-"+"{:04d}".format(k-1)+".txt\n")
|
||
for entry in infiles: f3.write(entry+'\n')
|
||
f2.close()
|
||
f3.close()
|
||
f2 = open('job-'+"{:04d}".format(k)+'.slurm','w')
|
||
f3 = open('input-'+"{:04d}".format(k)+'.txt','w')
|
||
time = 0
|
||
header = []
|
||
infiles = []
|
||
|
||
infiles.append(slurms[i])
|
||
|
||
for line in f1:
|
||
if line[0] == "#":
|
||
if j == 0: header.append(line)
|
||
if '--time' in line:
|
||
entries = line.split('=')
|
||
newtime = int(entries[1])
|
||
time = time + newtime
|
||
if j == 0: wtime = len(header)-1
|
||
|
||
if wtime > -1:
|
||
time = int(time/ncpu*1.3) #factor 1.3 is a safety margin
|
||
entries = header[wtime].split('=')
|
||
header[wtime] = entries[0]+'='+str(time)+'\n'
|
||
|
||
f2.writelines(header)
|
||
f2.write('module load gnuparallel\n')
|
||
f2.write('module load gnu\n')
|
||
f2.write('parallel -j'+str(ncpu)+" :::: "+"input-"+"{:04d}".format(k)+".txt\n")
|
||
for entry in infiles: f3.write(entry+'\n')
|
||
f2.close()
|
||
f3.close()
|
||
|
||
|
||
|
||
print(slurms)
|
||
|
||
return None
|
||
|
||
|
||
|
||
def grid_builder(config, modeldir=modeldir):
|
||
|
||
conf = load_conf(config)
|
||
|
||
wrange = tuple(map(float,conf['wrange'].split()))
|
||
if 'vmicro' in conf: vmicro = float(conf['vmicro'])
|
||
|
||
for entry in conf['grids']:
|
||
print('grid=',entry)
|
||
os.mkdir(entry)
|
||
os.chdir(entry)
|
||
if 'vmicro' in conf[entry]: vmicro = float(conf[entry]['vmicro'])
|
||
if conf[entry]['type'] == 'marcs':
|
||
files = collect_marcs(modeldir=modeldir,
|
||
tteff = tuple(map(float,conf[entry]['tteff'].split())),
|
||
tlogg = tuple(map(float,conf[entry]['tlogg'].split())),
|
||
tfeh = tuple(map(float,conf[entry]['tfeh'].split())),
|
||
tafe = tuple(map(float,conf[entry]['tafe'].split())),
|
||
tcfe = tuple(map(float,conf[entry]['tcfe'].split())),
|
||
ignore_missing_models = True,
|
||
ext = 'mod.gz')
|
||
elif conf[entry]['type'] == 'kurucz':
|
||
files = collect_kurucz(modeldir=modeldir,
|
||
tteff = tuple(map(float,conf[entry]['tteff'].split())),
|
||
tlogg = tuple(map(float,conf[entry]['tlogg'].split())),
|
||
tfeh = tuple(map(float,conf[entry]['tfeh'].split())),
|
||
tafe = tuple(map(float,conf[entry]['tafe'].split())),
|
||
tcfe = tuple(map(float,conf[entry]['tcfe'].split())),
|
||
ignore_missing_models = True,
|
||
ext = 'mod')
|
||
else:
|
||
print('only APOGEE marcs or kurucz models are accepted')
|
||
continue
|
||
|
||
polysyn(files, wrange = wrange, vmicro = vmicro )
|
||
|
||
os.chdir('..')
|
||
|
||
return(None)
|
||
|
||
|
||
#gather config. info
|
||
def load_conf(config='desi-n.yaml',confdir='.'):
|
||
|
||
try:
|
||
yfile=open(os.path.join(confdir,config),'r')
|
||
except:
|
||
print('ERROR in load_conf: cannot find the file ',config)
|
||
return(None)
|
||
#conf=yaml.full_load(yfile)
|
||
conf=yaml.load(yfile, Loader=yaml.SafeLoader)
|
||
yfile.close()
|
||
|
||
return(conf)
|
||
|
||
|
||
def collect_marcs(modeldir=modeldir, tteff=None, tlogg=None, \
|
||
tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0), \
|
||
tcfe=(1,0.0,0.0), tnfe=(1,0.0,0.0), \
|
||
tofe=(1,0.0,0.0), trfe=(1,0.0,0.0), tsfe=(1,0.0,0.0), \
|
||
files_in_folders = True, \
|
||
tie_afe=False, ignore_missing_models=False, ext='mod.gz'):
|
||
|
||
"""Collects all the MARCS models in modeldir that are part of a regular grid defined
|
||
by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff (tteff) and logg
|
||
(tlogg) are mandatory. Triads in [Fe/H] (tfeh), [alpha/Fe] (tafe), [C/Fe] (tcfe),
|
||
[N/Fe] (tnfe), [O/Fe] (tofe), [r/Fe] (rfe), and [s/Fe] (sfe) are optional since
|
||
arrays with just one 0.0 are included by default.
|
||
|
||
Parameters
|
||
----------
|
||
modeldir: str
|
||
directory where model atmosphere files are
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tnfe: tuple
|
||
[N/Fe] triad
|
||
tofe: tuple
|
||
[O/Fe] triad
|
||
rfeh: tuple
|
||
[r/Fe] triad (r-elements abundance ratio)
|
||
sfeh: tuple
|
||
[s.Fe] triad (s-elements abundance ratio)
|
||
files_in_folders: bool
|
||
True when model files are organized in subfolders by metallicity
|
||
(e.g. mod_z+0.00 for solar-metallicity models)
|
||
tie_afe: boolean
|
||
if active, when there is no loop in [alpha/Fe] (n in tafe is 1),
|
||
[alpha/Fe] is tied to [Fe/H]:
|
||
[alpha/Fe] is 0.5, 0.25, and 0. for [Fe/H]<=-1.5, -1 and -0.5, and >=0,
|
||
respectively
|
||
(default: False)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
|
||
Returns
|
||
-------
|
||
files: list of str
|
||
file names with MARCS models that are in modeldir and match
|
||
the parameters in the requested grid
|
||
|
||
"""
|
||
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nteff = len(tteff)
|
||
assert (nteff == 3), 'Error: Teff triad must have three elements (n, llimit, step)'
|
||
teffs = np.arange(tteff[0])*tteff[2] + tteff[1]
|
||
except TypeError:
|
||
print('Error: Teff triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlogg = len(tlogg)
|
||
assert (nlogg == 3), 'Error: logg triad must have three elements (n, llimit, step)'
|
||
loggs = np.arange(tlogg[0])*tlogg[2] + tlogg[1]
|
||
except TypeError:
|
||
print('Error: logg triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
ncfe = len(tcfe)
|
||
assert (ncfe == 3), 'Error: cfe triad must have three elements (n, llimit, step)'
|
||
cfes = np.arange(tcfe[0])*tcfe[2] + tcfe[1]
|
||
except TypeError:
|
||
print('Error: cfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nnfe = len(tnfe)
|
||
assert (nnfe == 3), 'Error: nfe triad must have three elements (n, llimit, step)'
|
||
nfes = np.arange(tnfe[0])*tnfe[2] + tnfe[1]
|
||
except TypeError:
|
||
print('Error: nfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nofe = len(tofe)
|
||
assert (nofe == 3), 'Error: ofe triad must have three elements (n, llimit, step)'
|
||
ofes = np.arange(tofe[0])*tofe[2] + tofe[1]
|
||
except TypeError:
|
||
print('Error: ofe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nrfe = len(trfe)
|
||
assert (nrfe == 3), 'Error: rfe triad must have three elements (n, llimit, step)'
|
||
rfes = np.arange(trfe[0])*trfe[2] + trfe[1]
|
||
except TypeError:
|
||
print('Error: rfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nsfe = len(tsfe)
|
||
assert (nsfe == 3), 'Error: sfe triad must have three elements (n, llimit, step)'
|
||
sfes = np.arange(tsfe[0])*tsfe[2] + tsfe[1]
|
||
except TypeError:
|
||
print('Error: sfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
if not os.path.isabs(modeldir):
|
||
modeldir = os.path.join (os.getcwd(), modeldir)
|
||
|
||
files = []
|
||
|
||
fi = open('files.txt','w')
|
||
|
||
for teff in teffs:
|
||
for logg in loggs:
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
for cfe in cfes:
|
||
for nfe in nfes:
|
||
for ofe in ofes:
|
||
for rfe in rfes:
|
||
for sfe in sfes:
|
||
|
||
if tie_afe and len(afes) == 1:
|
||
if feh <= -1.5:
|
||
afe = 0.5
|
||
elif feh <= -0.4:
|
||
afe = 0.25
|
||
else:
|
||
afe = 0.0
|
||
|
||
print(teff,logg,feh,afe,cfe,nfe,ofe,rfe,sfe)
|
||
code = 'm*_t*_x3'
|
||
|
||
if logg >= 3.5:
|
||
a1 = 'p'
|
||
else:
|
||
a1 = 's'
|
||
|
||
#make [O/Fe] = [alpha/Fe]
|
||
if ((tofe[0] == 1) and (abs(tofe[1]) < 1e-7) and (abs(tofe[2]) < 1e-7) ):
|
||
ofe = afe
|
||
|
||
sformat = "%s%4i_g%+.1f_%s_z%+.2f_a%+.2f_c%+.2f_n%+.2f_o%+.2f_r%+.2f_s%+.2f."+ext
|
||
filename = (sformat % (a1,teff,logg,code,feh,afe,cfe,nfe,ofe,rfe,sfe) )
|
||
|
||
if files_in_folders:
|
||
folder = ("mod_z%+.2f" % (feh) )
|
||
else:
|
||
folder = ''
|
||
|
||
file = glob.glob(os.path.join(modeldir,folder,filename))
|
||
print(filename,file)
|
||
|
||
assert len(file) < 2, 'Multiple files matching the pattern'
|
||
if len(file) < 1:
|
||
file = 'missing'
|
||
else:
|
||
file = file[0]
|
||
|
||
if ignore_missing_models == False:
|
||
assert file != 'missing'
|
||
assert os.path.isfile(file), 'Cannot find model '+filename+' in modeldir '+modeldir
|
||
else:
|
||
if file != 'missing':
|
||
if not os.path.isfile(file): file = 'missing'
|
||
|
||
files.append(file)
|
||
|
||
fi.write( "%s %4i %+.1f %+.2f %+.2f %+.2f %+.2f %+.2f %+.2f %+.2f\n" % (files[-1],teff,logg,feh,afe,cfe,nfe,ofe,rfe,sfe) )
|
||
|
||
|
||
|
||
fi.close()
|
||
|
||
return(files)
|
||
|
||
def collect_kurucz(modeldir=modeldir, tteff=None, tlogg=None, tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0), \
|
||
tcfe=(1,0.0,0.0), tie_afe=False, ignore_missing_models=False, ext='mod'):
|
||
|
||
"""Collects all the (APOGEE ATLAS9) Kurucz models in modeldir that are part of a regular grid defined
|
||
by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff (tteff) and logg
|
||
(tlogg) are mandatory. Triads in [Fe/H] (tfeh), [alpha/Fe] (tafe), [C/Fe] (tcfe) are optional since
|
||
arrays with just one 0.0 are included by default.
|
||
|
||
Parameters
|
||
----------
|
||
modeldir: str
|
||
directory where model atmosphere files are
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tie_afe: boolean
|
||
if active, when there is no loop in [alpha/Fe] (n in tafe is 1),
|
||
[alpha/Fe] is tied to [Fe/H]:
|
||
[alpha/Fe] is 0.5, 0.25, and 0. for [Fe/H]<=-1.5, -1 and -0.5, and >=0,
|
||
respectively
|
||
(default: False)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
|
||
Returns
|
||
-------
|
||
files: list of str
|
||
file names with Kurucz models that are in modeldir and match
|
||
the parameters in the requested grid
|
||
|
||
"""
|
||
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nteff = len(tteff)
|
||
assert (nteff == 3), 'Error: Teff triad must have three elements (n, llimit, step)'
|
||
teffs = np.arange(tteff[0])*tteff[2] + tteff[1]
|
||
except TypeError:
|
||
print('Error: Teff triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlogg = len(tlogg)
|
||
assert (nlogg == 3), 'Error: logg triad must have three elements (n, llimit, step)'
|
||
loggs = np.arange(tlogg[0])*tlogg[2] + tlogg[1]
|
||
except TypeError:
|
||
print('Error: logg triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
ncfe = len(tcfe)
|
||
assert (ncfe == 3), 'Error: cfe triad must have three elements (n, llimit, step)'
|
||
cfes = np.arange(tcfe[0])*tcfe[2] + tcfe[1]
|
||
except TypeError:
|
||
print('Error: cfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
if not os.path.isabs(modeldir):
|
||
modeldir = os.path.join (os.getcwd(), modeldir)
|
||
|
||
files = []
|
||
|
||
fi = open('files.txt','w')
|
||
|
||
for teff in teffs:
|
||
for logg in loggs:
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
for cfe in cfes:
|
||
|
||
if tie_afe and len(afes) == 1:
|
||
if feh <= -1.5:
|
||
afe = 0.5
|
||
elif feh <= -0.4:
|
||
afe = 0.25
|
||
else:
|
||
afe = 0.0
|
||
print(teff,logg,feh,afe,cfe)
|
||
mcode = 'm'
|
||
acode = 'm'
|
||
ccode = 'm'
|
||
if afe >= 0.: acode='p'
|
||
if cfe >= 0.: ccode='p'
|
||
if feh >= 0.: mcode='p'
|
||
|
||
sformat = "t%05ig%3.1fm%s%02ic%s%02io%s%02i."+ext
|
||
filename = (sformat % (teff,logg,mcode,ceil(abs(feh)*10.),ccode,cfe*10.,acode,ceil(abs(afe)*10.)))
|
||
|
||
file = glob.glob(os.path.join(modeldir,filename))
|
||
print(filename,file)
|
||
assert len(file) < 2, 'Multiple files matching the pattern'
|
||
if len(file) < 1:
|
||
file = 'missing'
|
||
else:
|
||
file = file[0]
|
||
|
||
print('file=',file)
|
||
|
||
if ignore_missing_models == False:
|
||
assert file != 'missing'
|
||
assert os.path.isfile(file), 'Cannot find model '+filename+' in modeldir '+modeldir
|
||
else:
|
||
if file != 'missing':
|
||
if not os.path.isfile(file): file = 'missing'
|
||
|
||
files.append(file)
|
||
|
||
fi.write( "%s %4i %+.1f %+.2f %+.2f %+.2f \n" % (files[-1],teff,logg,feh,afe,cfe) )
|
||
|
||
|
||
|
||
fi.close()
|
||
|
||
return(files)
|
||
|
||
|
||
|
||
|
||
def collect_k2odfnew(modeldir=modeldir, tteff=None, tlogg=None, tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0), \
|
||
tie_afe=False, ignore_missing_models=False):
|
||
|
||
"""Collects all the ODFNEW Castelli/Kurucz models in modeldir that are part of a regular grid defined
|
||
by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff (tteff) and logg
|
||
(tlogg) are mandatory. Triads in [Fe/H] (tfeh), and [alpha/Fe] (tafe) are optional since
|
||
arrays with just one 0.0 are included by default.
|
||
|
||
NOTE: There are ODFNEW models with only afe=[alpha/Fe]=0.0 or 0.4. The latter are used whenever
|
||
afe takes values > 0.0, while the afe=0.0 models are used otherwise.
|
||
|
||
Parameters
|
||
----------
|
||
modeldir: str
|
||
directory where model atmosphere files are
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tie_afe: boolean
|
||
if active, when there is no loop in [alpha/Fe] (n in tafe is 1),
|
||
[alpha/Fe] is tied to [Fe/H]:
|
||
[alpha/Fe] is 0.4 and 0. for [Fe/H]<=-1. and any other case,
|
||
respectively
|
||
(default: False)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
ext: str
|
||
extension of the model files, usually 'mod' for MARCS but
|
||
could be '.7' or '.22' for Tlusty NLTE models based on MARCS
|
||
(default 'mod')
|
||
|
||
Returns
|
||
-------
|
||
files: list of str
|
||
file names with Kurucz ODFNEWS models that are in modeldir and match
|
||
the parameters in the requested grid
|
||
|
||
"""
|
||
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nteff = len(tteff)
|
||
assert (nteff == 3), 'Error: Teff triad must have three elements (n, llimit, step)'
|
||
teffs = np.arange(tteff[0])*tteff[2] + tteff[1]
|
||
except TypeError:
|
||
print('Error: Teff triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlogg = len(tlogg)
|
||
assert (nlogg == 3), 'Error: logg triad must have three elements (n, llimit, step)'
|
||
loggs = np.arange(tlogg[0])*tlogg[2] + tlogg[1]
|
||
except TypeError:
|
||
print('Error: logg triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
if not os.path.isabs(modeldir):
|
||
modeldir = os.path.join (os.getcwd(), modeldir)
|
||
|
||
files = []
|
||
|
||
fi = open('files.txt','w')
|
||
|
||
for teff in teffs:
|
||
for logg in loggs:
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
|
||
if tie_afe and len(afes) == 1:
|
||
if feh <= -1.:
|
||
afe = 0.4
|
||
else:
|
||
afe = 0.0
|
||
|
||
print(teff,logg,feh,afe)
|
||
code = 'k2odfnew.dat'
|
||
|
||
if afe > 0.0:
|
||
a1 = 'a'
|
||
else:
|
||
a1 = ''
|
||
|
||
if feh < 0.0:
|
||
a2 = 'am'
|
||
else:
|
||
a2 = 'ap'
|
||
|
||
|
||
|
||
sformat = "t%05ig%.1f%s%02i%s"
|
||
filename = (sformat % (teff,logg,a2,int(abs(feh)*10),a1+code) )
|
||
|
||
file = os.path.join(modeldir,filename)
|
||
|
||
if ignore_missing_models == False:
|
||
assert os.path.isfile(file), 'Cannot find model '+filename+' in modeldir '+modeldir
|
||
else:
|
||
if not os.path.isfile(file): file = 'missing'
|
||
|
||
files.append(file)
|
||
|
||
fi.write( "%s %4i %+.1f %+.2f %+.2f \n" % (files[-1],teff,logg,feh,afe) )
|
||
|
||
fi.close()
|
||
|
||
return(files)
|
||
|
||
|
||
def mkgrid(synthfile=None, tteff=None, tlogg=None,
|
||
tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0),
|
||
tcfe=(1,0.0,0.0), tnfe=(1,0.0,0.0), tofe=(1,0.0,0.0),
|
||
trfe=(1,0.0,0.0), tsfe=(1,0.0,0.0),
|
||
vmicro=None, nfe=0.0, vrot=0.0, fwhm=0.0, vmacro=0.0,
|
||
wrange=None, dw=None, logw=0, ignore_missing_models=False, **elements):
|
||
|
||
|
||
|
||
"""Collects the synthetic spectra part of a regular grid defined
|
||
by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff (tteff)
|
||
and logg (tlogg) are mandatory. Triads in [Fe/H] (tfeh), [alpha/Fe] (tafe),
|
||
[C/Fe] (tcfe), [N/Fe] (tnfe), [O/Fe] (tofe), [r/Fe] (rfe), and
|
||
[s/Fe] (sfe) are optional since arrays with just one 0.0 are included by default.
|
||
The wavelength sampling can be chosen (the spectral range must be limited
|
||
to the range of the computations), but the default is to take it from
|
||
the first model.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the output FERRE synth file
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tnfe: tuple
|
||
[N/Fe] triad
|
||
tofe: tuple
|
||
[O/Fe] triad
|
||
rfeh: tuple
|
||
[r/Fe] triad (r-elements abundance ratio)
|
||
sfeh: tuple
|
||
[s.Fe] triad (s-elements abundance ratio)
|
||
vmicro: float, optional, can be an iterable
|
||
microturbulence (km/s)
|
||
(default is None to adopt whatever was used in the calculation)
|
||
nfe: float, can be an iterable
|
||
[N/Fe] nitrogen abundance change from the one specified in the array
|
||
'abu' (dex)
|
||
(default 0.)
|
||
vrot: float, can be an iterable
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float, can be an iterable
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.0)
|
||
vmacro: float, can be an iterable
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
wrange: tuple or list of two floats, optional
|
||
initial and ending wavelengths (angstroms)
|
||
(default None -- chosen by the code from the first input spectrum)
|
||
dw: float, optional
|
||
wavelength step for the output fluxes
|
||
(default is None for automatic frequency selection)
|
||
logw: int
|
||
parameter that indicates whether the wavelength scale should be
|
||
linear (0), log10 (1), or log (2)
|
||
(default 0)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
elements: tuples
|
||
as many triads as necessary, for other elemental variations [X/Fe]
|
||
e.g. Na=(3,-0.2,0.2), Al=(9, -0.5, 0.1), ...
|
||
|
||
|
||
Returns
|
||
-------
|
||
None
|
||
|
||
"""
|
||
|
||
|
||
pars = []
|
||
n_p = []
|
||
steps = []
|
||
llimits = []
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nteff = len(tteff)
|
||
assert (nteff == 3), 'Error: Teff triad must have three elements (n, llimit, step)'
|
||
teffs = np.arange(tteff[0])*tteff[2] + tteff[1]
|
||
if len(teffs) > 1:
|
||
pars.append('teff')
|
||
n_p.append(len(teffs))
|
||
steps.append(tteff[2])
|
||
llimits.append(tteff[1])
|
||
except TypeError:
|
||
print('Error: Teff triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlogg = len(tlogg)
|
||
assert (nlogg == 3), 'Error: logg triad must have three elements (n, llimit, step)'
|
||
loggs = np.arange(tlogg[0])*tlogg[2] + tlogg[1]
|
||
if len(loggs) > 1:
|
||
pars.append('logg')
|
||
n_p.append(len(loggs))
|
||
steps.append(tlogg[2])
|
||
llimits.append(tlogg[1])
|
||
except TypeError:
|
||
print('Error: logg triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
if len(fehs) > 1:
|
||
pars.append('feh')
|
||
n_p.append(len(fehs))
|
||
steps.append(tfeh[2])
|
||
llimits.append(tfeh[1])
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
if len(afes) > 1:
|
||
pars.append('afe')
|
||
n_p.append(len(afes))
|
||
steps.append(tafe[2])
|
||
llimits.append(tafe[1])
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
ncfe = len(tcfe)
|
||
assert (ncfe == 3), 'Error: cfe triad must have three elements (n, llimit, step)'
|
||
cfes = np.arange(tcfe[0])*tcfe[2] + tcfe[1]
|
||
if len(cfes) > 1:
|
||
pars.append('cfe')
|
||
n_p.append(len(cfes))
|
||
steps.append(tcfe[2])
|
||
llimits.append(tcfe[1])
|
||
except TypeError:
|
||
print('Error: cfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nnfe = len(tnfe)
|
||
assert (nnfe == 3), 'Error: nfe triad must have three elements (n, llimit, step)'
|
||
nfes = np.arange(tnfe[0])*tnfe[2] + tnfe[1]
|
||
if len(nfes) > 1:
|
||
pars.append('nfe')
|
||
n_p.append(len(nfes))
|
||
steps.append(tnfe[2])
|
||
llimits.append(tnfe[1])
|
||
except TypeError:
|
||
print('Error: nfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nofe = len(tofe)
|
||
assert (nofe == 3), 'Error: ofe triad must have three elements (n, llimit, step)'
|
||
ofes = np.arange(tofe[0])*tofe[2] + tofe[1]
|
||
if len(ofes) > 1:
|
||
pars.append('ofe')
|
||
n_p.append(len(ofes))
|
||
steps.append(tofe[2])
|
||
llimits.append(tofe[1])
|
||
except TypeError:
|
||
print('Error: ofe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nrfe = len(trfe)
|
||
assert (nrfe == 3), 'Error: rfe triad must have three elements (n, llimit, step)'
|
||
rfes = np.arange(trfe[0])*trfe[2] + trfe[1]
|
||
if len(rfes) > 1:
|
||
pars.append('rfe')
|
||
n_p.append(len(rfes))
|
||
steps.append(trfe[2])
|
||
llimits.append(trfe[1])
|
||
except TypeError:
|
||
print('Error: rfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nsfe = len(tsfe)
|
||
assert (nsfe == 3), 'Error: sfe triad must have three elements (n, llimit, step)'
|
||
sfes = np.arange(tsfe[0])*tsfe[2] + tsfe[1]
|
||
if len(sfes) > 1:
|
||
pars.append('sfe')
|
||
n_p.append(len(sfes))
|
||
steps.append(tsfe[2])
|
||
llimits.append(tsfe[1])
|
||
except TypeError:
|
||
print('Error: sfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nvmicro = len(vmicro)
|
||
vmicros = vmicro
|
||
pars.append('vmicro')
|
||
n_p.append(len(vmicros))
|
||
steps.append(vmicros[1]-vmicros[0])
|
||
llimits.append(vmicros[0])
|
||
except TypeError:
|
||
nvmicro = 1
|
||
vmicros = [ vmicro ]
|
||
try:
|
||
nnfe1 = len(nfe)
|
||
nfes1 = nfe
|
||
pars.append('nfe')
|
||
n_p.append(len(nfes1))
|
||
steps.append(nfes1[1]-nfes1[0])
|
||
llimits.append(nfes1[0])
|
||
except TypeError:
|
||
nnfe1 = 1
|
||
nfes1 = [ nfe ]
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [ vrot ]
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [ vmacro ]
|
||
|
||
for entry in elements.keys():
|
||
triad = elements[entry]
|
||
nentry = len(triad)
|
||
assert (nentry == 3), 'Error: element '+entry+' triad must have three elements (n, llimit, step)'
|
||
vals = np.arange(triad[0])*triad[2] + triad[1]
|
||
if len(vals) > 1:
|
||
pars.append(entry)
|
||
n_p.append(len(vals))
|
||
steps.append(triad[2])
|
||
llimits.append(triad[1])
|
||
|
||
|
||
|
||
#define indices for grid loops
|
||
ll = []
|
||
ind_n_p = []
|
||
i = 0
|
||
print('pars=',pars)
|
||
for entry in pars:
|
||
ind_n_p.append(i)
|
||
ll.append(np.arange(n_p[i]))
|
||
i = i + 1
|
||
ind = np.array(list(product(*ll)))
|
||
|
||
print
|
||
|
||
|
||
hdr = mkhdr(tteff=tteff, tlogg=tlogg, tfeh=tfeh, tafe=tafe,
|
||
tcfe=tcfe, tnfe=tnfe, tofe=tofe, trfe=trfe, tsfe=tsfe,
|
||
vmicro=vmicro, nfe=nfe, vrot=vrot, fwhm=fwhm, vmacro=vmacro, **elements)
|
||
|
||
if os.path.isfile(synthfile):
|
||
print('Warning -- the output file ',synthfile,' exists and will be overwritten')
|
||
f = open(synthfile,'w')
|
||
f.close()
|
||
|
||
f = open(synthfile,'a')
|
||
|
||
#look for the first sucessful calculation and define the wavelength for the grid and write the header
|
||
nfreq = 0
|
||
break_out = False
|
||
idir = 0
|
||
|
||
j = 0
|
||
for i in ind:
|
||
j = j + 1
|
||
print('line ',j)
|
||
print(i,steps,llimits)
|
||
par = i*steps+llimits
|
||
print(par)
|
||
|
||
dir = ( "hyd%07d" % (j) )
|
||
|
||
iconv = 1
|
||
if vrot < 1e-7 and fwhm < 1e-7 and vmacro < 1e-7:
|
||
outconv = "fort.7"
|
||
else:
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if os.path.isfile(file):
|
||
print('first successful calculation is for idir=',idir)
|
||
#assert os.path.isfile(file), 'Cannot find model '+file
|
||
|
||
try:
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
if wrange is None:
|
||
minwave = np.min(wave)
|
||
maxwave = np.max(wave)
|
||
else:
|
||
minwave = wrange[0]
|
||
maxwave = wrange[1]
|
||
|
||
if dw is None:
|
||
dw = np.median(np.diff(wave))
|
||
|
||
nfreq = np.floor((maxwave - minwave)/dw + 1)
|
||
|
||
if logw == 0:
|
||
x = minwave + np.arange(nfreq)*dw
|
||
elif logw == 1:
|
||
minwave = np.log10(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2./np.log(10.)
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = 10.**x
|
||
elif logw == 2:
|
||
minwave = np.log(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2.
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = np.exp(x)
|
||
else:
|
||
print('Error: logw can only be 0, 1 or 2')
|
||
sys.exit()
|
||
|
||
hdr['SYNTHFILE_INTERNAL'] = "'"+synthfile+"'"
|
||
hdr['ID'] = "'"+synthfile[2:]+"'"
|
||
hdr['NPIX'] = str(int(nfreq))
|
||
hdr['WAVE'] = str(minwave) + ' ' + str(dw)
|
||
hdr['LOGW'] = str(int(logw))
|
||
if fwhm > 0.0:
|
||
hdr['RESOLUTION'] = str(np.min(x)/np.max(fwhm))
|
||
f.write(' &SYNTH\n')
|
||
for entry in hdr: f.write(' '+entry + ' = ' + hdr[entry] + '\n')
|
||
f.write(' /\n')
|
||
break_out = True
|
||
break
|
||
|
||
except OSError:
|
||
if ignore_missing_models == False:
|
||
print('Error reading file ',file,' ... aborting!')
|
||
sys.exit(1)
|
||
else:
|
||
print('Error reading file:', file,' ... skipping it ...')
|
||
continue
|
||
|
||
|
||
assert nfreq > 0, 'could not find a single successful calculation in this grid'
|
||
|
||
#now read, interpolate and write out the calculations
|
||
j = 0
|
||
|
||
for i in ind:
|
||
j = j + 1
|
||
print('line ',j)
|
||
print(i,steps,llimits)
|
||
par = i*steps+llimits
|
||
print(par)
|
||
|
||
dir = ( "hyd%07d" % (j) )
|
||
|
||
iconv = 0
|
||
for vrot1 in vrots:
|
||
for fwhm1 in fwhms:
|
||
for vmacro1 in vmacros:
|
||
|
||
iconv = iconv + 1
|
||
|
||
if vrot < 1e-7 and fwhm < 1e-7 and vmacro < 1e-7:
|
||
outconv = "fort.7"
|
||
else:
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if os.path.isfile(file):
|
||
try:
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
except OSError:
|
||
if ignore_missing_models == False:
|
||
print('Cannot read model ',file,' ... aborting!')
|
||
sys.exit(1)
|
||
else:
|
||
wave, flux = (np.array([np.min(x),np.max(x)]), np.array([0.0, 0.0]))
|
||
else:
|
||
if ignore_missing_models == False:
|
||
assert os.path.isfile(file), 'Cannot find model '+file
|
||
else:
|
||
wave, flux = (np.array([np.min(x),np.max(x)]), np.array([0.0, 0.0]))
|
||
|
||
print('idir,iconv, dw=',idir,iconv,dw)
|
||
print(wave.shape,flux.shape)
|
||
y = np.interp(x, wave, flux)
|
||
print(x.shape,y.shape)
|
||
#plt.plot(wave,flux,'b',x,y,'.')
|
||
#plt.show()
|
||
np.savetxt(f,[y], fmt='%12.5e')
|
||
|
||
f.close()
|
||
|
||
return(None)
|
||
|
||
|
||
def mkgrid_old(synthfile=None, tteff=None, tlogg=None,
|
||
tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0),
|
||
tcfe=(1,0.0,0.0), tnfe=(1,0.0,0.0), tofe=(1,0.0,0.0),
|
||
trfe=(1,0.0,0.0), tsfe=(1,0.0,0.0),
|
||
vmicro=None, nfe=0.0, vrot=0.0, fwhm=0.0, vmacro=0.0,
|
||
wrange=None, dw=None, logw=0, ignore_missing_models=False):
|
||
|
||
|
||
|
||
"""Collects the synthetic spectra part of a regular grid defined
|
||
by triads in various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff (tteff) and logg (tlogg) are mandatory. Triads in [Fe/H] (tfeh), [alpha/Fe] (tafe), [C/Fe] (tcfe), [N/Fe] (tnfe), [O/Fe] (tofe), [r/Fe] (rfe), and [s/Fe] (sfe) are optional since arrays with just one 0.0 are included by default. The wavelength sampling can be chosen (the spectral range must be limited to the range of the computations), but the default is to take it from the first model.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the output FERRE synth file
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tnfe: tuple
|
||
[N/Fe] triad
|
||
tofe: tuple
|
||
[O/Fe] triad
|
||
rfeh: tuple
|
||
[r/Fe] triad (r-elements abundance ratio)
|
||
sfeh: tuple
|
||
[s.Fe] triad (s-elements abundance ratio)
|
||
vmicro: float, optional, can be an iterable
|
||
microturbulence (km/s)
|
||
(default is taken from the model atmosphere)
|
||
nfe: float, can be an iterable
|
||
[N/Fe] nitrogen abundance change from the one specified in the array
|
||
'abu' (dex)
|
||
(default 0.)
|
||
vrot: float, can be an iterable
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float, can be an iterable
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.0)
|
||
vmacro: float, can be an iterable
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
wrange: tuple or list of two floats, optional
|
||
initial and ending wavelengths (angstroms)
|
||
(default None -- chosen by the code from the first input spectrum)
|
||
dw: float, optional
|
||
wavelength step for the output fluxes
|
||
(default is None for automatic frequency selection)
|
||
logw: int
|
||
parameter that indicates whether the wavelength scale should be
|
||
linear (0), log10 (1), or log (2)
|
||
(default 0)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
|
||
Returns
|
||
-------
|
||
None
|
||
|
||
"""
|
||
|
||
#expanding the triads t* into iterables
|
||
try:
|
||
nteff = len(tteff)
|
||
assert (nteff == 3), 'Error: Teff triad must have three elements (n, llimit, step)'
|
||
teffs = np.arange(tteff[0])*tteff[2] + tteff[1]
|
||
except TypeError:
|
||
print('Error: Teff triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nlogg = len(tlogg)
|
||
assert (nlogg == 3), 'Error: logg triad must have three elements (n, llimit, step)'
|
||
loggs = np.arange(tlogg[0])*tlogg[2] + tlogg[1]
|
||
except TypeError:
|
||
print('Error: logg triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nfeh = len(tfeh)
|
||
assert (nfeh == 3), 'Error: feh triad must have three elements (n, llimit, step)'
|
||
fehs = np.arange(tfeh[0])*tfeh[2] + tfeh[1]
|
||
except TypeError:
|
||
print('Error: feh triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nafe = len(tafe)
|
||
assert (nafe == 3), 'Error: afe triad must have three elements (n, llimit, step)'
|
||
afes = np.arange(tafe[0])*tafe[2] + tafe[1]
|
||
except TypeError:
|
||
print('Error: afe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
ncfe = len(tcfe)
|
||
assert (ncfe == 3), 'Error: cfe triad must have three elements (n, llimit, step)'
|
||
cfes = np.arange(tcfe[0])*tcfe[2] + tcfe[1]
|
||
except TypeError:
|
||
print('Error: cfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nnfe = len(tnfe)
|
||
assert (nnfe == 3), 'Error: nfe triad must have three elements (n, llimit, step)'
|
||
nfes = np.arange(tnfe[0])*tnfe[2] + tnfe[1]
|
||
except TypeError:
|
||
print('Error: nfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nofe = len(tofe)
|
||
assert (nofe == 3), 'Error: ofe triad must have three elements (n, llimit, step)'
|
||
ofes = np.arange(tofe[0])*tofe[2] + tofe[1]
|
||
except TypeError:
|
||
print('Error: ofe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nrfe = len(trfe)
|
||
assert (nrfe == 3), 'Error: rfe triad must have three elements (n, llimit, step)'
|
||
rfes = np.arange(trfe[0])*trfe[2] + trfe[1]
|
||
except TypeError:
|
||
print('Error: rfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nsfe = len(tsfe)
|
||
assert (nsfe == 3), 'Error: sfe triad must have three elements (n, llimit, step)'
|
||
sfes = np.arange(tsfe[0])*tsfe[2] + tsfe[1]
|
||
except TypeError:
|
||
print('Error: sfe triad must have three elements (n, llimit, step)')
|
||
return ()
|
||
|
||
try:
|
||
nvmicro = len(vmicro)
|
||
vmicros = vmicro
|
||
except TypeError:
|
||
nvmicro = 1
|
||
vmicros = [ vmicro ]
|
||
try:
|
||
nnfe1 = len(nfe)
|
||
nfes1 = nfe
|
||
except TypeError:
|
||
nnfe1 = 1
|
||
nfes1 = [ nfe ]
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [ vrot ]
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [ vmacro ]
|
||
|
||
|
||
hdr = mkhdr(tteff=tteff, tlogg=tlogg, tfeh=tfeh, tafe=tafe,
|
||
tcfe=tcfe, tnfe=tnfe, tofe=tofe, trfe=trfe, tsfe=tsfe,
|
||
vmicro=vmicro, nfe=nfe, vrot=vrot, fwhm=fwhm, vmacro=vmacro)
|
||
|
||
if os.path.isfile(synthfile):
|
||
print('Warning -- the output file ',synthfile,' exists and will be overwritten')
|
||
f = open(synthfile,'w')
|
||
f.close()
|
||
|
||
f = open(synthfile,'a')
|
||
|
||
#look for the first sucessful calculation and define the wavelength for the grid and write the header
|
||
nfreq = 0
|
||
break_out = False
|
||
idir = 0
|
||
|
||
for teff in teffs:
|
||
for logg in loggs:
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
for cfe in cfes:
|
||
for nfe in nfes:
|
||
for ofe in ofes:
|
||
for rfe in rfes:
|
||
for sfe in sfes:
|
||
for vmicro1 in vmicros:
|
||
for nfe1 in nfes1:
|
||
|
||
print(teff,logg,feh,afe,cfe,nfe,ofe,rfe,sfe,vmicro1,nfe1)
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
|
||
iconv = 1
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if os.path.isfile(file):
|
||
print('first successful calculation is for idir=',idir)
|
||
assert os.path.isfile(file), 'Cannot find model '+file
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
if wrange is None:
|
||
minwave = np.min(wave)
|
||
maxwave = np.max(wave)
|
||
else:
|
||
minwave = wrange[0]
|
||
maxwave = wrange[1]
|
||
|
||
if dw is None:
|
||
dw = np.median(np.diff(wave))
|
||
|
||
nfreq = np.floor((maxwave - minwave)/dw + 1)
|
||
|
||
if logw == 0:
|
||
x = minwave + np.arange(nfreq)*dw
|
||
elif logw == 1:
|
||
minwave = np.log10(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2./np.log(10.)
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = 10.**x
|
||
elif logw == 2:
|
||
minwave = np.log(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2.
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = np.exp(x)
|
||
else:
|
||
print('Error: logw can only be 0, 1 or 2')
|
||
sys.exit()
|
||
|
||
hdr['SYNTHFILE_INTERNAL'] = "'"+synthfile+"'"
|
||
hdr['ID'] = "'"+synthfile[2:]+"'"
|
||
hdr['NPIX'] = str(int(nfreq))
|
||
hdr['WAVE'] = str(minwave) + ' ' + str(dw)
|
||
hdr['LOGW'] = str(int(logw))
|
||
if fwhm > 0.0:
|
||
hdr['RESOLUTION'] = str(np.min(x)/np.max(fwhm))
|
||
f.write(' &SYNTH\n')
|
||
for entry in hdr: f.write(' '+entry + ' = ' + hdr[entry] + '\n')
|
||
f.write(' /\n')
|
||
break_out = True
|
||
break
|
||
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
if break_out: break
|
||
|
||
|
||
|
||
assert nfreq > 0, 'could not find a single successful calculation in this grid'
|
||
|
||
#now read, interpolate and write out the calculations
|
||
idir = 0
|
||
|
||
for teff in teffs:
|
||
for logg in loggs:
|
||
for feh in fehs:
|
||
for afe in afes:
|
||
for cfe in cfes:
|
||
for nfe in nfes:
|
||
for ofe in ofes:
|
||
for rfe in rfes:
|
||
for sfe in sfes:
|
||
for vmicro1 in vmicros:
|
||
for nfe1 in nfes1:
|
||
|
||
print(teff,logg,feh,afe,cfe,nfe,ofe,rfe,sfe,vmicro1,nfe1)
|
||
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
|
||
iconv = 0
|
||
for vrot1 in vrots:
|
||
for fwhm1 in fwhms:
|
||
for vmacro1 in vmacros:
|
||
|
||
iconv = iconv + 1
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if os.path.isfile(file):
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
else:
|
||
if ignore_missing_models == False:
|
||
assert os.path.isfile(file), 'Cannot find model '+file
|
||
else:
|
||
wave, flux = (np.array([np.min(x),np.max(x)]), np.array([0.0, 0.0]))
|
||
|
||
print('idir,iconv, dw=',idir,iconv,dw)
|
||
print(wave.shape,flux.shape)
|
||
y = np.interp(x, wave, flux)
|
||
print(x.shape,y.shape)
|
||
#plt.plot(wave,flux,'b',x,y,'.')
|
||
#plt.show()
|
||
np.savetxt(f,[y], fmt='%12.5e')
|
||
|
||
f.close()
|
||
|
||
return(None)
|
||
|
||
def mkhdr(tteff=None, tlogg=None, tfeh=(1,0.0,0.0), tafe=(1,0.0,0.0), \
|
||
tcfe=(1,0.0,0.0), tnfe=(1,0.0,0.0), tofe=(1,0.0,0.0), \
|
||
trfe=(1,0.0,0.0), tsfe=(1,0.0,0.0), \
|
||
vmicro=None, nfe=0.0, vrot=0.0, fwhm=0.0, vmacro=0.0, **elements):
|
||
|
||
"""Returns a dictionary for a FERRE regular grid defined by triads in
|
||
various parameters. Each triad has three values (n, llimit, step)
|
||
that define an array x = np.range(n)*step + llimit. Triads in teff
|
||
(tteff) and logg (tlogg) are mandatory. Triads in [Fe/H] (tfeh),
|
||
[alpha/Fe] (tafe), [C/Fe] (tcfe), [N/Fe] (tnfe), [O/Fe] (tofe),
|
||
[r/Fe] (rfe), and [s/Fe] (sfe) are meant to be associated to changes
|
||
in the model atmospheres and are optional since arrays with
|
||
just one 0.0 are included by default. The keyword arguments 'elements'
|
||
can accommodate any other variations in chemistry, even those in the
|
||
elements already included in the previous parameters, and in any
|
||
arbitrary order.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the output FERRE synth file
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tafe: tuple
|
||
[alpha/Fe] triad
|
||
tcfe: tuple
|
||
[C/Fe] triad
|
||
tnfe: tuple
|
||
[N/Fe] triad
|
||
tofe: tuple
|
||
[O/Fe] triad
|
||
rfeh: tuple
|
||
[r/Fe] triad (r-elements abundance ratio)
|
||
sfeh: tuple
|
||
[s.Fe] triad (s-elements abundance ratio)
|
||
vmicro: float, optional, can be an iterable
|
||
microturbulence (km/s)
|
||
(default is taken from the model atmosphere)
|
||
nfe: float, can be an iterable
|
||
[N/Fe] nitrogen abundance change from the one specified in the array
|
||
'abu' (dex)
|
||
(default 0.)
|
||
vrot: float, can be an iterable
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float, can be an iterable
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.0)
|
||
vmacro: float, can be an iterable
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
wrange: tuple or list of two floats, optional
|
||
initial and ending wavelengths (angstroms)
|
||
(default None -- chosen by the code from the first input spectrum)
|
||
dw: float, optional
|
||
wavelength step for the output fluxes
|
||
(default is None for automatic frequency selection)
|
||
logw: int
|
||
parameter that indicates whether the wavelength scale should be
|
||
linear (0), log10 (1), or log (2)
|
||
(default 0)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
elements: tuples
|
||
as many triads as necessary, for other elemental variations [X/Fe]
|
||
e.g. Na=(3,-0.2,0.2), Al=(9, -0.5, 0.1), ...
|
||
"""
|
||
|
||
ndim = 0
|
||
n_p = []
|
||
labels = []
|
||
llimits = []
|
||
steps = []
|
||
if tteff[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tteff[0]))
|
||
labels.append('Teff')
|
||
llimits.append(tteff[1])
|
||
steps.append(tteff[2])
|
||
if tlogg[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tlogg[0]))
|
||
labels.append('logg')
|
||
llimits.append(tlogg[1])
|
||
steps.append(tlogg[2])
|
||
if tfeh[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tfeh[0]))
|
||
labels.append('[Fe/H]')
|
||
llimits.append(tfeh[1])
|
||
steps.append(tfeh[2])
|
||
if tafe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tafe[0]))
|
||
labels.append('[a/Fe]')
|
||
llimits.append(tafe[1])
|
||
steps.append(tafe[2])
|
||
if tcfe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tcfe[0]))
|
||
labels.append('[C/Fe]')
|
||
llimits.append(tcfe[1])
|
||
steps.append(tcfe[2])
|
||
if tnfe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tnfe[0]))
|
||
labels.append('[N/Fe]')
|
||
llimits.append(tnfe[1])
|
||
steps.append(tnfe[2])
|
||
if tofe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tofe[0]))
|
||
labels.append('[O/Fe]')
|
||
llimits.append(tofe[1])
|
||
steps.append(tofe[2])
|
||
if trfe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(trfe[0]))
|
||
labels.append('[r/Fe]')
|
||
llimits.append(trfe[1])
|
||
steps.append(trfe[2])
|
||
if tsfe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(int(tsfe[0]))
|
||
labels.append('[s/Fe]')
|
||
llimits.append(tsfe[1])
|
||
steps.append(tsfe[2])
|
||
if np.abs(np.max(vmicro)) > 1e-7 and not np.isscalar(vmicro):
|
||
ndim = ndim + 1
|
||
n_p.append(len(vmicro))
|
||
labels.append('vmicro')
|
||
llimits.append(vmicro[0])
|
||
steps.append(vmicro[1]-vmicro[0])
|
||
dvmicro=np.diff(vmicro)
|
||
if np.max(dvmicro) - np.min(dvmicro) > 1.e-7:
|
||
vmicro = np.log10(vmicro)
|
||
dvmicro=np.diff(vmicro)
|
||
print(vmicro)
|
||
print(dvmicro)
|
||
assert np.max(dvmicro) - np.min(dvmicro) < 1.e-7, 'Vmicro values are neither linearly spaced or linearly spaced in log!'
|
||
labels[-1]='log10vmicro'
|
||
llimits[-1]=vmicro[0]
|
||
steps[-1]=vmicro[1]-vmicro[0]
|
||
if tnfe[0] > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(len(nfe))
|
||
labels.append('[N/Fe]')
|
||
llimits.append(nfe[0])
|
||
steps.append(nfe[1]-nfe[0])
|
||
dnfe=np.diff(nfe)
|
||
assert np.max(dnfe) - np.min(dnfe) < 1.e-7, '[N/Fe] values are not linearly spaced!'
|
||
|
||
|
||
for entry in elements.keys():
|
||
print('entry=',entry)
|
||
triad = elements[entry]
|
||
nentry = len(triad)
|
||
assert (nentry == 3), 'Error: element '+entry+' triad must have three elements (n, llimit, step)'
|
||
vals = np.arange(triad[0])*triad[2] + triad[1]
|
||
print('vals=',vals)
|
||
if len(vals) > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(len(vals))
|
||
labels.append(entry)
|
||
llimits.append(triad[1])
|
||
steps.append(triad[2])
|
||
dvals=np.diff(vals)
|
||
assert np.max(dvals) - np.min(dvals) < 1.e-7, 'values for element '+entry+' are not linearly spaced!'
|
||
|
||
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [ vrot ]
|
||
if np.abs(np.max(vrots)) > 1e-7 and nvrot > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(len(vrots))
|
||
labels.append('vrot')
|
||
llimits.append(vrots[0])
|
||
steps.append(vrots[1]-vrots[0])
|
||
dvrot=np.diff(vrots)
|
||
if np.max(dvrot) - np.min(dvrot) > 1.e-7:
|
||
vrots = np.log10(vrots)
|
||
dvrot = np.diff(vrots)
|
||
assert np.max(dvrot) - np.min(dvrot) < 1.e-7, 'Vrot values are neither linearly spaced or linearly spaced in log!'
|
||
labels[-1]='log10vrot'
|
||
llimits[-1]=vrots[0]
|
||
steps[-1]=vrots[1]-vrots[0]
|
||
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
if np.abs(np.max(fwhms)) > 1e-7 and len(fwhms) > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(len(fwhms))
|
||
labels.append('FWHM')
|
||
llimits.append(fwhms[0])
|
||
steps.append(fwhms[1]-fwhms[0])
|
||
dfwhm=np.diff(fwhms)
|
||
if np.max(dfwhm) - np.min(dfwhm) > 1.e-7:
|
||
fwhms = np.log10(fwhms)
|
||
dfwhm=np.diff(fwhms)
|
||
assert np.max(dfwhm) - np.min(dfwhm) < 1.e-7, 'FWHM values are neither linearly spaced or linearly spaced in log!'
|
||
labels[-1]='log10FWHM'
|
||
llimits[-1]=fwhms[0]
|
||
steps[-1]=fwhms[1]-fwhms[0]
|
||
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [ vmacro ]
|
||
if np.abs(np.min(vmacros)) > 1e-7 and len(vmacros) > 1:
|
||
ndim = ndim + 1
|
||
n_p.append(len(vmacros))
|
||
labels.append('VMACRO')
|
||
llimits.append(vmacros[0])
|
||
steps.append(vmacros[1]-vmacros[0])
|
||
dvmacro=np.diff(vmacros)
|
||
if np.max(dvmacro) - np.min(dvmacro) > 1.e-7:
|
||
vmacros = np.log10(vmacros)
|
||
dvmacro=np.diff(vmacros)
|
||
assert np.max(dvmacro) - np.min(dvmacro) < 1.e-7, 'vmacro values are neither linearly spaced or linearly spaced in log!'
|
||
labels[-1]='log10vmacro'
|
||
llimits[-1]=vmacros[0]
|
||
steps[-1]=vmacros[1]-vmacros[0]
|
||
|
||
|
||
pwd=os.path.abspath(os.curdir)
|
||
nowtime=time.ctime(time.time())
|
||
osinfo=os.uname()
|
||
|
||
|
||
hdr = {}
|
||
hdr['DATE'] = "'"+nowtime+"'"
|
||
hdr['N_OF_DIM'] = str(ndim)
|
||
hdr['N_P'] = ' '.join(map(str,n_p))
|
||
for i in range(ndim): hdr['LABEL('+str(i+1)+")"] = "'"+labels[i]+"'"
|
||
hdr['LLIMITS'] = ' '.join(map(str,llimits))
|
||
hdr['STEPS'] = ' '.join(map(str,steps))
|
||
hdr['COMMENTS1'] = "'mixed and computed with synple-synspec'"
|
||
hdr['COMMENTS2'] = "'"+osinfo[0]+' '+osinfo[2]+'.'+osinfo[4]+' running on '+osinfo[1]+"'"
|
||
hdr['COMMENTS3'] = "'pwd is "+pwd+"'"
|
||
|
||
return(hdr)
|
||
|
||
|
||
def mkgrid_irregular(synthfile=None, teff=True, logg=True, feh=True,
|
||
vmicro=None, vrot=0.0, fwhm=0.0, vmacro=0.0,
|
||
wrange=None, dw=None, logw=0, ignore_missing_models=False,**elem):
|
||
|
||
|
||
|
||
"""Collects the synthetic spectra part of an irregular grid.
|
||
To track changes in Teff, logg and [Fe/H] one can activate the booleans teff,
|
||
logg and feh. To track changes in other elements additional booleans (e.g. Ca=True)
|
||
can be made active at the end of the parameter list (**element).
|
||
The wavelength sampling can be chosen (the spectral range must be limited
|
||
to the range of the computations), but the default is to take it from the first model.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the output FERRE synth file
|
||
teff: boolean
|
||
Activate to track this parameter
|
||
logg: boolean
|
||
Activate to track this parameter
|
||
feh: boolean
|
||
Activate to track this parameter
|
||
vmicro: float, optional, can be an iterable
|
||
microturbulence (km/s)
|
||
(default is taken from the model atmosphere: 'model')
|
||
vrot: float, can be an iterable
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float, can be an iterable
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.0)
|
||
vmacro: float, can be an iterable
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
wrange: tuple or list of two floats, optional
|
||
initial and ending wavelengths (angstroms)
|
||
(default None -- chosen by the code from the first input spectrum)
|
||
dw: float, optional
|
||
wavelength step for the output fluxes
|
||
(default is None for automatic frequency selection)
|
||
logw: int
|
||
parameter that indicates whether the wavelength scale should be
|
||
linear (0), log10 (1), or log (2)
|
||
(default 0)
|
||
ignore_missing_models: bool
|
||
set to True to avoid stopping when a model is missing,
|
||
in which case a None is entered in the returning list
|
||
|
||
**elem: booleans
|
||
Activate to track additional chemical elements
|
||
e.g. Na=True, Ca=True
|
||
|
||
Returns
|
||
-------
|
||
None
|
||
|
||
"""
|
||
|
||
try:
|
||
nvmicro = len(vmicro)
|
||
vmicros = vmicro
|
||
except TypeError:
|
||
nvmicro = 1
|
||
vmicros = [ vmicro ]
|
||
try:
|
||
nvrot = len(vrot)
|
||
vrots = vrot
|
||
except TypeError:
|
||
nvrot = 1
|
||
vrots = [ vrot ]
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
try:
|
||
nvmacro = len(vmacro)
|
||
vmacros = vmacro
|
||
except TypeError:
|
||
nvmacro = 1
|
||
vmacros = [ vmacro ]
|
||
|
||
pars = []
|
||
if teff: pars.append('teff')
|
||
if logg: pars.append('logg')
|
||
if feh: pars.append('feh')
|
||
if nvmicro > 1: pars.append('vmicro')
|
||
for entry in elem.keys():
|
||
pars.append('['+entry+'/H]')
|
||
if nvrot > 1: pars.append('vrot')
|
||
if nfwhm > 1: pars.append('fwhm')
|
||
if nvmacro > 1: pars.append('vmacro')
|
||
|
||
#track solar reference abundances to set the scale
|
||
symbol, mass, sol = elements()
|
||
solabu = dict()
|
||
for i in range(len(symbol)):
|
||
solabu[symbol[i]] = sol[i]
|
||
|
||
|
||
hdr = mkhdr_irregular(pars)
|
||
|
||
if os.path.isfile(synthfile):
|
||
print('Warning -- the output file ',synthfile,' exists and will be overwritten')
|
||
f = open(synthfile,'w')
|
||
f.close()
|
||
|
||
f = open(synthfile,'a')
|
||
|
||
#look for the first sucessful calculation and define the wavelength for the grid and write the header
|
||
nfreq = 0
|
||
break_out = False
|
||
idir = 0
|
||
|
||
folders = sorted(glob.glob('hyd*'))
|
||
|
||
ntot = 0
|
||
for entry in folders:
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
|
||
madaffile = os.path.join(entry,'fort.5')
|
||
if ignore_missing_models == False:
|
||
assert os.path.isfile(madaffile), 'Cannot find madaf file '+madaffile
|
||
else:
|
||
if not os.path.isfile(madaffile): continue
|
||
|
||
teff2,logg2,vmicro2,abu = read_madaf(madaffile,startdir=entry)
|
||
imode, iprin, inmod, inlte, hydprf, wrange, cutoff, \
|
||
strength, dw, molls, vmicro1 = read55(os.path.join(entry,'fort.55'))
|
||
feh2 = np.log10(abu['Fe'])+12-7.50
|
||
|
||
print(teff2,logg2,feh2,vmicro1,vmicro2)
|
||
|
||
ntot = ntot + 1
|
||
|
||
iconv = 1
|
||
if vrot < 1e-7 and fwhm < 1e-7 and vmacro < 1e-7:
|
||
outconv = "fort.7"
|
||
else:
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if break_out == False and os.path.isfile(file) and os.path.getsize(file) > 0:
|
||
fh = open(file,'r')
|
||
fdata = fh.read()
|
||
fh.close()
|
||
if 'NaN' not in fdata:
|
||
print('first successful calculation is for idir=',idir)
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
if wrange is None:
|
||
minwave = np.min(wave)
|
||
maxwave = np.max(wave)
|
||
else:
|
||
minwave = wrange[0]
|
||
maxwave = wrange[1]
|
||
|
||
if dw is None:
|
||
dw = np.median(np.diff(wave))
|
||
|
||
nfreq = np.floor((maxwave - minwave)/dw + 1)
|
||
|
||
if logw == 0:
|
||
x = minwave + np.arange(nfreq)*dw
|
||
elif logw == 1:
|
||
minwave = np.log10(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2./np.log(10.)
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = 10.**x
|
||
elif logw == 2:
|
||
minwave = np.log(minwave)
|
||
dw = dw/(np.max(wave)+np.min(wave))*2.
|
||
x = minwave + np.arange(nfreq)*dw
|
||
x = np.exp(x)
|
||
else:
|
||
print('Error: logw can only be 0, 1 or 2')
|
||
sys.exit()
|
||
|
||
hdr['SYNTHFILE_INTERNAL'] = "'"+synthfile+"'"
|
||
hdr['ID'] = "'"+synthfile[2:]+"'"
|
||
hdr['NPIX'] = str(int(nfreq))
|
||
hdr['WAVE'] = str(minwave) + ' ' + str(dw)
|
||
hdr['LOGW'] = str(int(logw))
|
||
if fwhm > 0.:
|
||
hdr['RESOLUTION'] = str(np.min(x)/np.max(fwhm))
|
||
|
||
break_out = True
|
||
|
||
#if break_out: pass
|
||
|
||
|
||
assert nfreq > 0, 'could not find a single successful calculation in this grid'
|
||
ntot = ntot * nvrot * nfwhm * nvmacro
|
||
hdr['NTOT'] = str(ntot)
|
||
|
||
#write header
|
||
f.write(' &SYNTH\n')
|
||
for entry in hdr: f.write(' '+entry + ' = ' + hdr[entry] + '\n')
|
||
f.write(' /\n')
|
||
|
||
|
||
#now read, interpolate and write out the calculations
|
||
idir = 0
|
||
for entry in folders:
|
||
idir = idir + 1
|
||
dir = ( "hyd%07d" % (idir) )
|
||
|
||
madaffile = os.path.join(entry,'fort.5')
|
||
if ignore_missing_models == False:
|
||
assert os.path.isfile(madaffile), 'Cannot find madaf file '+madaffile
|
||
else:
|
||
if not os.path.isfile(madaffile): continue
|
||
|
||
|
||
teff2,logg2,vmicro2,abu = read_madaf(madaffile,startdir=entry)
|
||
|
||
imode, iprin, inmod, inlte, hydprf, wrange, cutoff, \
|
||
strength, dw, molls, vmicro1 = read55(os.path.join(entry,'fort.55'))
|
||
feh2 = np.log10(abu['Fe']) - np.log10(solabu['Fe'])
|
||
|
||
print(teff2,logg2,feh2,vmicro1,vmicro2)
|
||
|
||
pars = []
|
||
if teff: pars.append(teff2)
|
||
if logg: pars.append(logg2)
|
||
if feh: pars.append(feh2)
|
||
if nvmicro > 1: pars.append(vmicro1)
|
||
for el in elem.keys():
|
||
pars.append(np.log10(abu[el]) - np.log10(solabu[el]) )
|
||
iconv = 0
|
||
for vrot1 in vrots:
|
||
for fwhm1 in fwhms:
|
||
for vmacro1 in vmacros:
|
||
|
||
iconv = iconv + 1
|
||
if vrot < 1e-7 and fwhm < 1e-7 and vmacro < 1e-7:
|
||
outconv = "fort.7"
|
||
else:
|
||
outconv = ("%07dfort.7" % (iconv) )
|
||
|
||
file = os.path.join(dir,outconv)
|
||
|
||
if nvrot > 1: pars.append(vrot1)
|
||
if nfwhm > 1: pars.append(fwhm1)
|
||
if nvmacro > 1: pars.append(vmacro1)
|
||
|
||
fgood = False
|
||
if os.path.isfile(file) and os.path.getsize(file) > 0:
|
||
fh = open(file,'r')
|
||
fdata = fh.read()
|
||
fh.close()
|
||
if 'NaN' not in fdata:
|
||
fgood = True
|
||
|
||
if fgood == True:
|
||
wave, flux = np.loadtxt(file, unpack=True)
|
||
else:
|
||
if ignore_missing_models == False:
|
||
sys.exit('Cannot find model '+file+' or it contains no data or NaNs')
|
||
else:
|
||
wave, flux = (np.array([np.min(x),np.max(x)]), np.array([0.0, 0.0]))
|
||
|
||
print('idir,iconv, dw=',idir,iconv,dw)
|
||
print(wave.shape,flux.shape)
|
||
y = np.interp(x, wave, flux)
|
||
print(x.shape,y.shape)
|
||
#plt.plot(wave,flux,'b',x,y,'.')
|
||
#plt.show()
|
||
np.savetxt(f,[pars+list(y)], fmt='%12.5e')
|
||
|
||
f.close()
|
||
|
||
return(None)
|
||
|
||
def mkhdr_irregular(pars):
|
||
|
||
ndim = len(pars)
|
||
|
||
pwd=os.path.abspath(os.curdir)
|
||
nowtime=time.ctime(time.time())
|
||
osinfo=os.uname()
|
||
|
||
hdr = {}
|
||
hdr['DATE'] = "'"+nowtime+"'"
|
||
hdr['N_OF_DIM'] = str(ndim)
|
||
for i in range(ndim): hdr['LABEL('+str(i+1)+")"] = "'"+pars[i]+"'"
|
||
hdr['TYPE'] = "'irregular'"
|
||
hdr['COMMENTS1'] = "'mixed and computed with synple-synspec'"
|
||
hdr['COMMENTS2'] = "'"+osinfo[0]+' '+osinfo[2]+'.'+osinfo[4]+' running on '+osinfo[1]+"'"
|
||
hdr['COMMENTS3'] = "'pwd is "+pwd+"'"
|
||
|
||
return(hdr)
|
||
|
||
|
||
#create a regular grid of Kurucz model atmospheres
|
||
def create_regular_kurucz(tteff=None, tlogg =None, \
|
||
tfeh = (1,0.0,0.0), tmicro = (1, 1.0, 0.0), \
|
||
tie_afe=False, **kargs):
|
||
|
||
"""Creates scripts to compute a regular grid of Kurucz models using Sbordone's version
|
||
of ATLAS9. The model grid is defined by triads of various parameters. Each triad has
|
||
three values (n, llimit, step) that define an array x = np.range(n)*step + llimit.
|
||
Triads in teff (tteff) and logg (tlogg) are mandatory. Triads in [Fe/H] (tfeh) and
|
||
microturbulence (tmicro) are optional since arrays with just one 0.0 are included by
|
||
default. Any other chemical element can be added with additional triads, e.g. to
|
||
vary sodium with 3 values [Na/Fe] = -0.2, 0.0 and +0.2 one would add a parameter
|
||
Na=(3,-0.2,0.2). The perl script mkk and Kurucz's atlas9 needs to be installed.
|
||
|
||
Parameters
|
||
----------
|
||
tteff: tuple
|
||
Teff triad (n, llimit, step)
|
||
tlogg: tuple
|
||
logg triad (n, llimit, step)
|
||
tfeh: tuple
|
||
[Fe/H] triad
|
||
tmicro: tuple
|
||
microturbulence triad
|
||
tie_afe: boolean
|
||
if active, when there is no loop in [alpha/Fe] (n in tafe is 1),
|
||
[alpha/Fe] is tied to [Fe/H]:
|
||
[alpha/Fe] is 0.5, for [Fe/H]<=-1.5, 0.0 for [Fe/H] >=0 and changes
|
||
linearly in between
|
||
(default: False)
|
||
kargs: tuples
|
||
as many triads as necessary, for other elemental variations [X/Fe]
|
||
e.g. Na=(3,-0.2,0.2), Al=(9, -0.5, 0.1), ...
|
||
"""
|
||
|
||
|
||
n_p = [tteff[0],tlogg[0], tfeh[0], tmicro[0]]
|
||
llimits = [tteff[1], tlogg[1], tfeh[1], tmicro[1]]
|
||
steps = [tteff[2], tlogg[2] , tfeh[2], tmicro[2]]
|
||
tags = ['teff', 'logg', 'METALS','MICRO']
|
||
|
||
for entry in list(map(str,kargs.keys())): tags.append(entry)
|
||
for entry in kargs.values():
|
||
print(entry)
|
||
n_p.append(entry[0])
|
||
llimits.append(entry[1])
|
||
steps.append(entry[2])
|
||
|
||
aa = getaa(n_p)
|
||
|
||
for i in range(len(aa[:,0])):
|
||
dir = ( "kur%07d" % (i+1) )
|
||
try:
|
||
os.mkdir(dir)
|
||
except OSError:
|
||
print( "cannot create dir kur%07d" % (i+1) )
|
||
|
||
#setup the slurm script
|
||
sfile = os.path.join(dir,dir+".job")
|
||
now=time.strftime("%c")
|
||
s = open(sfile ,"w")
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple on "+now+" \n")
|
||
s.write("#SBATCH -J "+dir+" \n")
|
||
s.write("#SBATCH -o "+dir+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+dir+"_%j.err"+" \n")
|
||
#s.write("#SBATCH -n "+str(nthreads)+" \n")
|
||
s.write("#SBATCH --ntasks-per-node="+str(1)+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(1)+" \n")
|
||
s.write("#SBATCH -t 04:00:00"+" \n") #hh:mm:ss
|
||
s.write("#SBATCH -D "+os.path.abspath(os.curdir)+" \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n\n\n")
|
||
|
||
comm = 'mkk '
|
||
for j in [0,1]:
|
||
sst = ('%+.3f ' % (aa[i,j]*steps[j]+llimits[j]) )
|
||
comm = comm + sst
|
||
for j in range(len(tags)-2):
|
||
sst = ('%+.3f ' % (aa[i,j+2]*steps[j+2]+llimits[j+2]) )
|
||
comm = comm + tags[j+2] + '=' + sst
|
||
if tie_afe and tags[j+2] == "METALS":
|
||
feh = aa[i,j+2]*steps[j+2]+llimits[j+2]
|
||
if feh <= -1.5:
|
||
afe = 0.5
|
||
elif feh >= 0.:
|
||
afe = 0.0
|
||
else:
|
||
afe = -1./3. * feh
|
||
alphas = ['O', 'Ne','Mg', 'Si', 'S', 'Ca', 'Ti']
|
||
for entry in alphas:
|
||
sst = ('%+.3f ' % (afe) )
|
||
comm = comm + entry + '=' + sst
|
||
|
||
print(sst)
|
||
print(comm)
|
||
s.write(comm+'\n')
|
||
st = os.stat(sfile)
|
||
os.chmod(sfile, st.st_mode | stat.S_IEXEC)
|
||
|
||
s.close()
|
||
|
||
return()
|
||
|
||
#create an irregular grid of Kurucz model atmospheres
|
||
def create_irregular_kurucz(n,pteff=None, plogg =None, \
|
||
pfeh = (0.0,0.0), pmicro = (1.0, 1.0), \
|
||
tie_afe=False, **kargs):
|
||
|
||
"""Creates scripts to compute an iregular grid of Kurucz models using Sbordone's version
|
||
of ATLAS9. The model grid is defined by pairs of various parameters. Each pair has
|
||
two values (llimit, ulimit) that define the sampling interval.
|
||
Pairs in teff (pteff) and logg (plogg) are mandatory. Pairs in [Fe/H] (pfeh) and
|
||
microturbulence (pmicro) are optional since arrays with just one value (0.0 for [Fe/H]
|
||
and 1.0 for microturbulence) are included by default. Any other chemical element can
|
||
be added with additional pairs, e.g. to vary sodium in the range -0.2 <= [Na/Fe] <= +0.2
|
||
one would add a parameter Na=(-0.2,0.2). The perl script mkk and Kurucz's atlas9 needs
|
||
to be installed.
|
||
|
||
Parameters
|
||
----------
|
||
n: int
|
||
Number of models to produce
|
||
pteff: tuple
|
||
Teff pair (llimit, ulimit)
|
||
plogg: tuple
|
||
logg pair (llimit, ulimit)
|
||
pfeh: tuple
|
||
[Fe/H] pair
|
||
pmicro: tuple
|
||
microturbulence pair
|
||
tie_afe: boolean
|
||
if active, when there is no loop in [alpha/Fe] (n in tafe is 1),
|
||
[alpha/Fe] is tied to [Fe/H]:
|
||
[alpha/Fe] is 0.5, for [Fe/H]<=-1.5, 0.0 for [Fe/H] >=0 and changes
|
||
linearly in between
|
||
(default: False)
|
||
kargs: tuples
|
||
as many pairs as necessary, for other elemental variations [X/Fe]
|
||
e.g. Na=(-0.2,0.2), Al=(-0.5, 0.2), ...
|
||
"""
|
||
|
||
|
||
teff = np.random.random_sample(n)*(pteff[1]-pteff[0])+pteff[0]
|
||
logg = np.random.random_sample(n)*(plogg[1]-plogg[0])+plogg[0]
|
||
feh = np.random.random_sample(n)*(pfeh[1]-pfeh[0])+pfeh[0]
|
||
micro = np.random.random_sample(n)*(pmicro[1]-pmicro[0])+pmicro[0]
|
||
pars = np.vstack ((teff,logg,feh,micro))
|
||
tags = ['teff', 'logg', 'METALS','MICRO']
|
||
for entry in list(map(str,kargs.keys())): tags.append(entry)
|
||
for entry in kargs.values():
|
||
print(entry)
|
||
newpar = np.random.random_sample(n)*(entry[1]-entry[0])+entry[0]
|
||
pars = np.vstack ((pars, newpar))
|
||
|
||
for i in range(len(pars[0,:])):
|
||
dir = ( "kur%07d" % (i+1) )
|
||
try:
|
||
os.mkdir(dir)
|
||
except OSError:
|
||
print( "cannot create dir kur%07d" % (i+1) )
|
||
|
||
#setup the slurm script
|
||
sfile = os.path.join(dir,dir+".job")
|
||
now=time.strftime("%c")
|
||
s = open(sfile ,"w")
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple on "+now+" \n")
|
||
s.write("#SBATCH -J "+dir+" \n")
|
||
s.write("#SBATCH -o "+dir+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+dir+"_%j.err"+" \n")
|
||
#s.write("#SBATCH -n "+str(nthreads)+" \n")
|
||
s.write("#SBATCH --ntasks-per-node="+str(1)+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(1)+" \n")
|
||
s.write("#SBATCH -t 04:00:00"+" \n") #hh:mm:ss
|
||
s.write("#SBATCH -D "+os.path.abspath(os.curdir)+" \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n\n\n")
|
||
|
||
comm = 'mkk '
|
||
|
||
for j in [0,1]:
|
||
sst = ('%+.3f ' % (pars[j,i]) )
|
||
comm = comm + sst
|
||
|
||
for j in range(len(tags)-2):
|
||
sst = ('%+.3f ' % (pars[j+2,i]) )
|
||
comm = comm + tags[j+2] + '=' + sst
|
||
if tie_afe and tags[j+2] == "METALS":
|
||
feh = pars[j+2,i]
|
||
if feh <= -1.5:
|
||
afe = 0.5
|
||
elif feh >= 0.:
|
||
afe = 0.0
|
||
else:
|
||
afe = -1./3. * feh
|
||
alphas = ['O', 'Ne','Mg', 'Si', 'S', 'Ca', 'Ti']
|
||
for entry in alphas:
|
||
sst = ('%+.3f ' % (afe) )
|
||
comm = comm + entry + '=' + sst
|
||
|
||
|
||
print(comm)
|
||
s.write(comm+'\n')
|
||
st = os.stat(sfile)
|
||
os.chmod(sfile, st.st_mode | stat.S_IEXEC)
|
||
|
||
s.close()
|
||
|
||
return()
|
||
|
||
|
||
#extract the header of a synthfile
|
||
def head_synth(synthfile):
|
||
if synthfile[-6:] == "pickle":
|
||
import pickle
|
||
if not os.path.isfile(synthfile):
|
||
sf = os.path.join(griddir,synthfile)
|
||
if os.path.isfile(sf): synthfile = sf
|
||
print('reading header of the grid '+synthfile+'...')
|
||
file=open(synthfile,'rb')
|
||
header, pars, data = pickle.load(file)
|
||
file.close()
|
||
else:
|
||
meta=0
|
||
multi=0
|
||
if not os.path.isfile(synthfile):
|
||
sf = os.path.join(griddir,synthfile)
|
||
if os.path.isfile(sf): synthfile = sf
|
||
print('reading grid '+synthfile+'...')
|
||
file=open(synthfile,'r')
|
||
line=file.readline()
|
||
header={}
|
||
while (1):
|
||
line=file.readline()
|
||
part=line.split('=')
|
||
if (len(part) < 2):
|
||
meta=meta+1
|
||
if (meta>multi):
|
||
if multi>0: multi_header.append(header)
|
||
break
|
||
else:
|
||
if (meta > 0): multi_header.append(header)
|
||
header={}
|
||
line=file.readline()
|
||
else:
|
||
k=part[0].strip()
|
||
v=part[1].strip()
|
||
header[k]=v
|
||
if k == 'MULTI':
|
||
multi=int(v)
|
||
multi_header=[]
|
||
if (multi > 1): header=multi_header
|
||
|
||
return header
|
||
|
||
#extract the wavelength array for a FERRE synth file
|
||
def lambda_synth(synthfile):
|
||
multi_header=head_synth(synthfile)
|
||
if np.ndim(multi_header) == 0: multi_header=[multi_header]
|
||
x = None
|
||
xx=[]
|
||
for header in multi_header:
|
||
if 'WAVE' in header:
|
||
tmp=header['WAVE'].split()
|
||
npix=int(header['NPIX'])
|
||
step=float(tmp[1])
|
||
x0=float(tmp[0])
|
||
x=np.arange(npix)*step+x0
|
||
if header['LOGW']:
|
||
if int(header['LOGW']) == 1: x=10.**x
|
||
if int(header['LOGW']) == 2: x=np.exp(x)
|
||
if 'WAVELENGTHS' in header:
|
||
x = np.array(header['WAVELENGTHS'].split(),dtype=float)
|
||
|
||
if x is not None:
|
||
if len(xx) == 0:
|
||
xx.append(x)
|
||
else:
|
||
if (np.size(x) != len(xx)):
|
||
xx.append(x)
|
||
else:
|
||
if ((xx-x).max() > 0.): xx.append(x)
|
||
|
||
if len(xx)>1: x=xx[:]
|
||
|
||
return x
|
||
|
||
#read a synthfile
|
||
def read_synth(synthfile,nd=False):
|
||
"""
|
||
Reads a FERRE spectral grid from disk. It can be pickled.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: string
|
||
Name of the FERRE synthfile to read. The code will automatically
|
||
look for it in the current directory but if not present will attempt
|
||
to read it from the synple default griddir.
|
||
nd: boolean
|
||
Keyword to reshape the data array from 2D to the actual
|
||
grid dimensions, given in the array N_P, included in the header
|
||
|
||
Returns
|
||
-------
|
||
header: dict
|
||
Dictionary containing the header of the grid
|
||
pars: numpy float array
|
||
2D array with the parameters corresponding to the data in the grid
|
||
data: numpy float array
|
||
2D array with the grid data and dimentions product(N_P) x NPIX
|
||
(multidimensional if the grid has more than 1 parameters and
|
||
the boolean ND is set to True)
|
||
|
||
|
||
"""
|
||
|
||
if synthfile[-6:] == "pickle":
|
||
import pickle
|
||
if not os.path.isfile(synthfile):
|
||
sf = os.path.join(griddir,synthfile)
|
||
if os.path.isfile(sf): synthfile = sf
|
||
print('reading grid '+synthfile+'...')
|
||
file=open(synthfile,'rb')
|
||
header, pars, data = pickle.load(file)
|
||
file.close()
|
||
else:
|
||
#header
|
||
meta=0
|
||
multi=0
|
||
if not os.path.isfile(synthfile):
|
||
sf = os.path.join(griddir,synthfile)
|
||
if os.path.isfile(sf): synthfile = sf
|
||
print('reading grid '+synthfile+'...')
|
||
file=open(synthfile,'r')
|
||
line=file.readline()
|
||
header={}
|
||
nlines=1
|
||
while (1):
|
||
line=file.readline()
|
||
nlines+=1
|
||
part=line.split('=')
|
||
if (len(part) < 2):
|
||
meta=meta+1
|
||
if (meta>multi):
|
||
if multi>0: multi_header.append(header)
|
||
break
|
||
else:
|
||
if (meta > 0): multi_header.append(header)
|
||
header={}
|
||
line=file.readline()
|
||
nlines+=1
|
||
else:
|
||
k=part[0].strip()
|
||
v=part[1].strip()
|
||
header[k]=v.strip("'")
|
||
if k == 'MULTI':
|
||
multi=int(v)
|
||
multi_header=[]
|
||
if (multi > 1):
|
||
|
||
header=multi_header
|
||
file.close()
|
||
|
||
if type(header) is list:
|
||
header0 = header[1]
|
||
else:
|
||
header0 = header
|
||
|
||
#data
|
||
data=np.loadtxt(synthfile, skiprows=nlines, dtype=float)
|
||
|
||
#parameters
|
||
ndim = int(header0['N_OF_DIM'])
|
||
if ('TYPE' in header0 and 'irregular' in header0['TYPE']):
|
||
pars = data[:,0:ndim]
|
||
data = data[:,ndim:]
|
||
else:
|
||
llimits = np.array(header0['LLIMITS'].split(),dtype=float)
|
||
steps = np.array(header0['STEPS'].split(),dtype=float)
|
||
snp=header0['N_P']
|
||
n_p = tuple(np.array(snp.split(),dtype=int)) + (-1,)
|
||
if nd: data = np.reshape( data, n_p)
|
||
n_p = n_p[:-1]
|
||
pars = getaa(n_p, dtype=float)*steps + llimits
|
||
|
||
|
||
|
||
return header,pars,data
|
||
|
||
def pickle_synth(synthfile,outsynthfile=None):
|
||
"""
|
||
Reads a (text) FERRE grid and rewrites it to disk in pickle
|
||
(binary) format with the extension .pickle
|
||
"""
|
||
import pickle
|
||
|
||
if outsynthfile is None:
|
||
outsynthfile = synthfile[:-3]+'pickle'
|
||
|
||
h, p, d = read_synth(synthfile)
|
||
file = open(outsynthfile, 'wb')
|
||
pickle.dump( (h, p, d), file)
|
||
file.close()
|
||
|
||
return()
|
||
|
||
def write_synth(synthfile,p,d,hdr=None,irregular=False):
|
||
"""
|
||
Writes a FERRE spectral grid to disk
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the output synthfile
|
||
p: numpy array of floats
|
||
2D array with the parameters
|
||
(nmodel rows x nparam columns)
|
||
d: numpy array
|
||
2D array with the data (fluxes)
|
||
(nmodel rows x nfreqencies columns)
|
||
hdr: dict
|
||
header
|
||
(made up with basic data if None)
|
||
irregular: bool
|
||
force the output grid to be irregular
|
||
|
||
Returns
|
||
-------
|
||
writes out the data to a synthfile
|
||
|
||
|
||
"""
|
||
|
||
#ndim = d.ndim-1
|
||
#n_p = d.shape[:-1]
|
||
#npix = d.shape[-1]
|
||
|
||
ndim = len(p[0,:])
|
||
npix = len(d[0,:])
|
||
ntot = len(p[:,0])
|
||
|
||
pwd=os.path.abspath(os.curdir)
|
||
nowtime=time.ctime(time.time())
|
||
osinfo=os.uname()
|
||
|
||
if hdr is None:
|
||
#minimal header
|
||
hdr = {}
|
||
hdr['DATE'] = "'"+nowtime+"'"
|
||
hdr['N_OF_DIM'] = str(ndim)
|
||
#hdr['N_P'] = ' '.join(map(str,n_p))
|
||
for i in range(ndim): hdr['LABEL('+str(i+1)+")"] = "'"+"unknown"+"'"
|
||
hdr['LLIMITS'] = ' '.join(map(str,np.zeros(ndim)))
|
||
hdr['STEPS'] = ' '.join(map(str,np.ones(ndim)))
|
||
hdr['COMMENTS1'] = "'created by write_synth, without axis information'"
|
||
hdr['COMMENTS2'] = "'"+osinfo[0]+' '+osinfo[2]+'.'+osinfo[4]+' running on '+osinfo[1]+"'"
|
||
hdr['COMMENTS3'] = "'pwd is "+pwd+"'"
|
||
|
||
else:
|
||
|
||
if type(hdr) is list:
|
||
hdr0 = hdr[1]
|
||
else:
|
||
hdr0 = hdr.copy()
|
||
hdr = [hdr]
|
||
|
||
ndim = int(hdr0['N_OF_DIM'])
|
||
#n_p = list(map(int,hdr['N_P'].split()))
|
||
if 'TYPE' in hdr0:
|
||
if 'irregular' in hdr0['TYPE']:
|
||
irregular = True
|
||
|
||
if irregular:
|
||
#drop models with zeros and add the model parameteres to the data
|
||
ds = np.sum(d,1)
|
||
wi = np.where(ds + 1e-31 > 1e-30)[0]
|
||
d = d[wi,:]
|
||
p = p[wi,:]
|
||
d = np.hstack((p,d))
|
||
ntot = len(p[:,0])
|
||
for block in hdr:
|
||
block['TYPE'] = "'irregular'"
|
||
block['NTOT'] = str(ntot)
|
||
|
||
|
||
fout = open(synthfile,'w')
|
||
for block in hdr:
|
||
fout.write(' &SYNTH\n')
|
||
for entry in block:
|
||
value = block[entry]
|
||
fout.write(' '+entry + ' = ' + str(value) + '\n')
|
||
fout.write(' /\n')
|
||
|
||
#now the data
|
||
#if ndim > 1:
|
||
# dd = np.reshape( d, (np.product(n_p), npix) )
|
||
for entry in range(ntot):
|
||
d[entry,:].tofile(fout,sep=" ",format="%0.4e")
|
||
fout.write("\n")
|
||
|
||
return(None)
|
||
|
||
def paste_synth(synthfile,outsynthfile=None):
|
||
|
||
"""Pastes various synthfiles with the same parameters but
|
||
different spectral ranges into one synthfile with a multiheader.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: iterable of strings
|
||
Names of the synthfiles to merge
|
||
outsynthfile: name of the output synthfile
|
||
|
||
Returns
|
||
-------
|
||
hh: dict or list
|
||
header of the merged synthfile
|
||
p: numpy array of floats
|
||
parameters
|
||
dd: numpy array of floats
|
||
data (fluxes)
|
||
"""
|
||
|
||
k = 0
|
||
synthfile2 = ''
|
||
for entry in synthfile:
|
||
|
||
h,p,d = read_synth(entry)
|
||
synthfile2 += entry
|
||
print('synthfile2=',synthfile2)
|
||
|
||
if k == 0:
|
||
if type(h) is list:
|
||
hh = h.copy()
|
||
else:
|
||
hh = [h]
|
||
pp = p.copy()
|
||
dd = d.copy()
|
||
else:
|
||
if type(h) is list:
|
||
for block in h:
|
||
hh.append(block)
|
||
else:
|
||
hh.append(h)
|
||
assert(np.max(pp - p) < 1e-30),'The parameters of the file '+entry+ ' do not match those for preceding synthfiles'
|
||
dd = np.hstack((dd,d))
|
||
|
||
k += 1
|
||
|
||
h0 = dict()
|
||
h0['MULTI'] = len(hh)
|
||
h0['ID'] = synthfile2
|
||
h0['COMMENTS1'] = 'merged with synple.paste_synth'
|
||
h0['N_OF_DIM'] = hh[0]['N_OF_DIM']
|
||
if 'NTOT' in hh[0]:
|
||
h0['NTOT'] = hh[0]['NTOT']
|
||
if 'TYPE' in hh[0]:
|
||
h0['TYPE'] = hh[0]['TYPE']
|
||
|
||
hh.insert(0,h0)
|
||
|
||
if outsynthfile is None: outsynthfile = synthfile2 + '.dat'
|
||
write_synth(outsynthfile,p,dd,hdr=hh)
|
||
|
||
return(hh,p,dd)
|
||
|
||
|
||
def merge_synth(synthfile,outsynthfile=None):
|
||
|
||
"""Merges various synthfiles with the same parameters and
|
||
the same spectral range/resolution into one irregular grid.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: iterable of strings
|
||
Names of the synthfiles to merge
|
||
outsynthfile: name of the output synthfile
|
||
|
||
Returns
|
||
-------
|
||
hh: dict
|
||
header of the merged synthfile
|
||
pp: numpy array of floats
|
||
parameters
|
||
dd: numpy array of floats
|
||
data (fluxes)
|
||
"""
|
||
|
||
k = 0
|
||
synthfile2 = 'merged_'
|
||
for entry in synthfile:
|
||
|
||
h,p,d = read_synth(entry)
|
||
synthfile2 += entry
|
||
print('synthfile2=',synthfile2)
|
||
|
||
if k == 0:
|
||
if type(h) is list:
|
||
hh = h.copy()
|
||
else:
|
||
hh = [h]
|
||
pp = p.copy()
|
||
dd = d.copy()
|
||
|
||
#track LABELS in the first header
|
||
labels = []
|
||
for key in hh[-1].keys():
|
||
if key[:5] == 'LABEL':
|
||
labels.append(hh[-1][key])
|
||
else:
|
||
if type(h) is list:
|
||
for block in h:
|
||
hh.append(block)
|
||
else:
|
||
hh.append(h)
|
||
|
||
#check the LABELS are the same in the rest
|
||
labels2 = []
|
||
for key in hh[-1].keys():
|
||
if key[:5] == 'LABEL':
|
||
labels2.append(hh[-1][key])
|
||
|
||
assert(np.all(np.array(labels) == np.array(labels2))),'The parameters of the file '+entry+ ' do not match those for preceding synthfiles'
|
||
|
||
|
||
pp = np.vstack((pp,p))
|
||
dd = np.vstack((dd,d))
|
||
|
||
|
||
k += 1
|
||
|
||
h0 = dict()
|
||
h0['MULTI'] = len(hh)
|
||
h0['ID'] = synthfile2
|
||
h0['COMMENTS1'] = 'merged with synple.merge_synth'
|
||
h0['N_OF_DIM'] = hh[0]['N_OF_DIM']
|
||
if 'NTOT' in hh[0]:
|
||
h0['NTOT'] = hh[0]['NTOT']
|
||
if 'TYPE' in hh[0]:
|
||
h0['TYPE'] = hh[0]['TYPE']
|
||
|
||
hh.insert(0,h0)
|
||
|
||
if outsynthfile is None: outsynthfile = synthfile2 + '.dat'
|
||
write_synth(outsynthfile,pp,dd,hdr=hh)
|
||
|
||
return(hh,pp,dd)
|
||
|
||
|
||
|
||
def fill_synth(d,kernel='thin_plate_spline', neighbors=100):
|
||
"""
|
||
Completes data rows with zeros interpolating using RBF from
|
||
non-zero rows
|
||
----------
|
||
d: float
|
||
2D array with data (first dim is number of spectra, 2nd number of frequencies)
|
||
kernel: string
|
||
Type of RBF function (linear, thin_plate_spline, cubic, gaussian ...)
|
||
neighbors: int
|
||
Number of nearest neighbors used to compute the interpolation coefficients
|
||
for each grid point
|
||
|
||
Returns
|
||
-------
|
||
a new version of the array d with the rows with zeros replaced by interpolated data
|
||
|
||
"""
|
||
|
||
from scipy.interpolate import RBFInterpolator
|
||
|
||
ndim = d.ndim-1
|
||
n_p = d.shape[:-1]
|
||
nfreq = d.shape[-1]
|
||
|
||
ds = np.sum(d, 1)
|
||
wi = np.where(ds + 1e-31 > 1e-30)[0]
|
||
wo = np.where(ds + 1e-31 < 1e-30)[0]
|
||
|
||
print('ndim=',ndim)
|
||
print('n_p=',n_p)
|
||
|
||
#get loop indices for entries in grid
|
||
iarr = getaa(n_p)
|
||
c = RBFInterpolator(iarr[wi,:], d[wi,:], kernel=kernel, neighbors = neighbors )
|
||
d2 = d.copy()
|
||
d2[wo,:] = c(iarr[wo])
|
||
|
||
return(d2)
|
||
|
||
|
||
def getaa(n_p, dtype=int):
|
||
"""
|
||
Generates a matrix with len(n_p) columns and product(n_p)
|
||
rows which can be used to transform ndim nested loops into a single
|
||
loop
|
||
|
||
Parameters
|
||
----------
|
||
np: iterable giving the elements in each dimension
|
||
|
||
Returns
|
||
-------
|
||
aa: - int array with indices
|
||
|
||
"""
|
||
|
||
ndim = len(n_p)
|
||
ll = []
|
||
for i in np.arange(ndim):
|
||
ll.append(np.arange(n_p[i], dtype=dtype))
|
||
aa = np.array(list(product(*ll)))
|
||
|
||
return(aa)
|
||
|
||
def rbf_get(synthfile, kernel='thin_plate_spline', neighbors=100):
|
||
"""Computes RBF coefficients for interpolation in an input FERRE grid
|
||
Parameters
|
||
----------
|
||
synthfile: string
|
||
Name of the FERRE synthfile to interpolate in
|
||
kernel: string
|
||
Type of RBF function (linear, thin_plate_spline, cubic, gaussian ...)
|
||
neighbors: int
|
||
Number of nearest neighbors used to compute the interpolation coefficients
|
||
for each grid point
|
||
|
||
Returns
|
||
-------
|
||
c: - RBFInterpolator object to interpolate in the grid
|
||
|
||
pmin: array with the minimum values for each parameter in the grid
|
||
|
||
ptp: array with the peak-to-peak values for each parameter in the grid (max - min)
|
||
|
||
"""
|
||
|
||
from scipy.interpolate import RBFInterpolator
|
||
|
||
|
||
h, p, d = read_synth(synthfile)
|
||
|
||
#n_p = np.array(h['N_P'].split(),dtype=int)
|
||
#nfreq = int(h['NPIX'])
|
||
#iarr = getaa(n_p)
|
||
|
||
pmin = p.min(0)
|
||
ptp = p.ptp(0)
|
||
|
||
p2 = (p - pmin) / ptp
|
||
|
||
|
||
print('deriving interpolation coefficients...')
|
||
#c= RBFInterpolator(iarr, d, kernel=kernel, neighbors = neighbors )
|
||
c= RBFInterpolator(p2, d, kernel=kernel, neighbors = neighbors )
|
||
|
||
return(c, pmin, ptp)
|
||
|
||
def rbf_apply(c,pmin,ptp,par):
|
||
"""Interpolates using the RBFInterpolate objects in the array c
|
||
and auxiliary minimum on the parameter range (pmin,ptp) to derive
|
||
fluxes for the parameters in the array par
|
||
|
||
Parameters
|
||
----------
|
||
c: RBFInterpolate object
|
||
previously
|
||
derived from calling rbf_get
|
||
pmin: array of floats
|
||
minimum values for each of the parameters
|
||
ptp: array of floats
|
||
peak-to-peak values for each of the parameters
|
||
par: array of floats
|
||
array of parameters for interpolation
|
||
rows correspond to different interpolations and columns
|
||
should correspond to the parameters
|
||
"""
|
||
|
||
from scipy.interpolate import RBFInterpolator
|
||
|
||
#map the parameters from physical to indices
|
||
par2 = par.copy()
|
||
for i in range(len(par[0,:])):
|
||
#par2[:,i] = (par[:,i] - llimits[i] ) / steps[i]
|
||
par2[:,i] = (par[:,i] - pmin[i] ) / ptp[i]
|
||
|
||
|
||
print('applying coefficients ..')
|
||
res = c(par2)
|
||
|
||
return(res)
|
||
|
||
|
||
|
||
def getallt(modelfiles):
|
||
|
||
"""Collects all the values for temperature, density and electron number density
|
||
in a list of files with model atmospheres
|
||
|
||
Parameters
|
||
----------
|
||
modelfiles : list of str
|
||
files with model atmospheres
|
||
|
||
Returns
|
||
-------
|
||
t: list
|
||
list of all temperatures in all the layers of the input model atmospheres
|
||
rho: list
|
||
list of all values of gas pressure in all the layers of the input model atmospheres
|
||
|
||
ne: list
|
||
list of all values of electron number density in all the layers of the input model atmospheres
|
||
|
||
"""
|
||
|
||
t = []
|
||
rho = []
|
||
ne = []
|
||
|
||
for entry in modelfiles:
|
||
print('reading ',entry)
|
||
teff, logg, vmicro, abu, nd, atmos = read_marcs_model2(entry)
|
||
#atmostype,teff,logg,vmicro,abu,nd,atmos = read_model(entry)
|
||
for value in atmos['t']: t.append(value)
|
||
for value in atmos['rho']: rho.append(value)
|
||
for value in atmos['ne']: ne.append(value)
|
||
|
||
return(t,rho,ne)
|
||
|
||
|
||
|
||
def call_rotin(wave=None, flux=None, vrot=0.0, fwhm=0.0, vmacro=0.0,
|
||
space=1e-2, steprot=0.0, stepfwhm=0.0,
|
||
clean=True, reuseinputfiles=False, logfile='syn.log'):
|
||
|
||
|
||
"""Convolves a synthetic spectrum with a rotation and/or Gaussian kernel
|
||
|
||
Interface to the fortran code rotin.
|
||
|
||
Parameters
|
||
----------
|
||
wave: numpy array of floats
|
||
wavelengths (angstroms)
|
||
flux: numpy array of floats
|
||
flux
|
||
vrot: float
|
||
projected rotational velocity (km/s)
|
||
(default 0.)
|
||
fwhm: float
|
||
Gaussian broadening: macroturbulence, instrumental, etc. (angstroms)
|
||
(default 0.)
|
||
vmacro: float
|
||
Radial-tangential macroturbulence (km/s)
|
||
(default 0.)
|
||
space: float, optional
|
||
characteristic wavelength scale for variations in the spectrum (angstroms)
|
||
(default is 1e-2)
|
||
steprot: float
|
||
wavelength step for convolution with rotational kernel (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
stepfwhm: float
|
||
wavelength step for Gaussian convolution (angstroms)
|
||
set to 0. for automatic adjustment (default 0.)
|
||
clean: bool
|
||
True by the default, set to False to avoid the removal of the rotin
|
||
temporary files (default Tr<ue)
|
||
reuseinputfiles: bool
|
||
set to take the input data from the output synspec file (fort.7) rather than
|
||
from the input arrays (wave, flux)
|
||
logfile: str
|
||
name of the log file
|
||
(default syn.log)
|
||
|
||
Returns
|
||
-------
|
||
wave2: numpy array of floats
|
||
wavelengths (angstroms)
|
||
flux2: numpy array of floats
|
||
flux
|
||
|
||
|
||
"""
|
||
if reuseinputfiles == False:
|
||
f = open('fort.7','w')
|
||
f2 = open('fort.17','w')
|
||
maxflux = np.max(flux)
|
||
for i in range(len(wave)):
|
||
f.write( ' %f %f \n' % (wave[i], flux[i]) )
|
||
f2.write( ' %f %f \n' % (wave[i], maxflux) )
|
||
f.close()
|
||
f2.close()
|
||
|
||
f = open('fort.5','w')
|
||
f.write( ' %s %s %s \n' % ("'fort.7'", "'fort.17'", "'fort.11'") )
|
||
f.write( ' %f %f %f \n' % (vrot, space, steprot) )
|
||
f.write( ' %f %f %f \n' % (fwhm, stepfwhm, vmacro) )
|
||
print('stepfwhm=',stepfwhm)
|
||
f.write( ' %f %f %i \n' % (np.min(wave), np.max(wave), 0) )
|
||
f.close()
|
||
|
||
synin = open('fort.5')
|
||
synout = open(logfile,'a')
|
||
p = subprocess.Popen([rotin], stdin=synin, stdout = synout, stderr = synout)
|
||
p.wait()
|
||
synout.flush()
|
||
synout.close()
|
||
synin.close()
|
||
|
||
assert (os.path.isfile('fort.11')), 'Error: I cannot read the file *fort.11* in '+os.getcwd()+' -- looks like rotin has crashed, please look at syn.log'
|
||
|
||
wave2, flux2 = np.loadtxt('fort.11', unpack=True)
|
||
print(len(wave),len(wave2))
|
||
|
||
if clean == True: cleanup_fort()
|
||
|
||
return(wave2, flux2)
|
||
|
||
def read_model(modelfile):
|
||
"""
|
||
将大气模型文件读取为结构化数据
|
||
参数:
|
||
-----------
|
||
modelfile: str
|
||
大气模型文件路径
|
||
返回:
|
||
-------
|
||
atmostype: str
|
||
模型类型(kurucz/marcs/phoenix/tlusty)
|
||
teff: float
|
||
有效温度(K)
|
||
logg: float
|
||
表面重力加速度对数(log10(g/cm/s²))
|
||
vmicro: float
|
||
微观湍流速度(km/s)
|
||
abu: list
|
||
丰度数组,包含从氢(Z=1)到锿(Z=99)的元素数密度比N(X)/N(H)
|
||
nd: int
|
||
模型层数(深度层数)
|
||
atmos: numpy结构化数组
|
||
包含列质量、温度、气体压强和电子密度的深度分布
|
||
"""
|
||
# 检查文件是否存在,若未找到则尝试在模型目录查找
|
||
if not os.path.isfile(modelfile):
|
||
mf = os.path.join(modeldir, modelfile)
|
||
if os.path.isfile(mf):
|
||
modelfile = mf
|
||
|
||
# 识别模型类型
|
||
atmostype = identify_atmostype(modelfile)
|
||
|
||
# 根据模型类型调用对应的读取函数
|
||
if atmostype == 'kurucz':
|
||
teff, logg, vmicro, abu, nd, atmos = read_kurucz_model(modelfile)
|
||
if atmostype == 'marcs':
|
||
teff, logg, vmicro, abu, nd, atmos = read_marcs_model2(modelfile)
|
||
if atmostype == 'phoenix':
|
||
teff, logg, vmicro, abu, nd, atmos = read_phoenix_model(modelfile)
|
||
if atmostype == 'tlusty':
|
||
teff, logg, vmicro, abu, nd, atmos = read_tlusty_model(modelfile)
|
||
|
||
return (atmostype, teff, logg, vmicro, abu, nd, atmos)
|
||
|
||
def identify_atmostype(modelfile):
|
||
"""
|
||
识别输入文件中的大气模型类型
|
||
有效类型包括:kurucz、marcs、tlusty(.7格式)或phoenix
|
||
参数:
|
||
-----------
|
||
modelfile: str
|
||
大气模型文件路径
|
||
返回:
|
||
-------
|
||
atmostype: str
|
||
模型类型('kurucz', 'marcs', 'tlusty', 'phoenix')
|
||
"""
|
||
# 通过文件名判断Phoenix模型(包含PHOENIX和fits关键字)
|
||
if 'PHOENIX' in modelfile and 'fits' in modelfile:
|
||
atmostype = 'phoenix'
|
||
else:
|
||
# 判断是否为gzip压缩文件
|
||
if modelfile.endswith('.gz'):
|
||
f = gzip.open(modelfile, 'rt') # 以文本模式读取gzip文件
|
||
else:
|
||
f = open(modelfile, 'r')
|
||
|
||
line = f.readline() # 读取首行内容
|
||
# 判断Kurucz模型(首行包含'TEFF')
|
||
if 'TEFF' in line:
|
||
atmostype = 'kurucz'
|
||
else:
|
||
line = f.readline() # 读取第二行
|
||
# 判断MARCS模型(第二行包含'Teff')
|
||
if 'Teff' in line:
|
||
atmostype = 'marcs'
|
||
else:
|
||
atmostype = 'tlusty' # 默认为Tlusty模型
|
||
f.close()
|
||
|
||
return (atmostype)
|
||
|
||
def checksynspec(linelist,modelfile):
|
||
|
||
"""checking that executables and data are where it should be. Prepend
|
||
default directories to linelist and model atmosphere files when necessary
|
||
|
||
Parameters
|
||
----------
|
||
linelist: array of str
|
||
file names of the line lists to be used. The first string should correspond
|
||
to the atomic line list and is mandatory. The remainder are optional and
|
||
correspond to molecular line lists. All files should be in synspec format.
|
||
(see documentation at http://nova.astro.umd.edu/Synspec43/synspec.html)
|
||
|
||
"""
|
||
|
||
dirs = [synpledir,modelatomdir,linelistdir,bindir]
|
||
for entry in dirs: assert (os.path.isdir(entry)), 'dir '+entry+' missing'
|
||
|
||
linelist = checklinelistpath(linelist)
|
||
|
||
files = [synspec,rotin]
|
||
for entry in linelist: files.append(entry)
|
||
for entry in files: assert (os.path.isfile(entry)), 'file '+entry+' missing'
|
||
|
||
if not os.path.isfile(modelfile):
|
||
mf = os.path.join(modeldir,modelfile)
|
||
if os.path.isfile(mf): modelfile = mf
|
||
|
||
#print(modeldir)
|
||
#print(modelfile)
|
||
assert (os.path.isfile(modelfile)),'model atmosphere file '+modelfile+' missing'
|
||
|
||
|
||
return(linelist,modelfile)
|
||
|
||
def checklinelistpath(linelist):
|
||
|
||
"""checking whether the line lists in the array linelists are in the current folder
|
||
or in linelistdir and returning the same array with an absolute path
|
||
"""
|
||
|
||
i = 0
|
||
for entry in linelist:
|
||
if os.path.isfile(entry+".11"):
|
||
entry = entry+".11" #give preference to the binary file
|
||
elif os.path.isfile(entry):
|
||
pass
|
||
else:
|
||
entry = os.path.join(linelistdir,entry)
|
||
if os.path.isfile(entry+".11"): entry = entry+".11"
|
||
|
||
assert(os.path.isfile(entry)), 'The line list '+entry+' is neither accessible in the working directory nor in the linelistdir ('+linelistdir+') folder'
|
||
|
||
linelist[i] = entry
|
||
if not os.path.isabs(entry):
|
||
ll = os.path.join (os.getcwd(), entry)
|
||
if os.path.isfile(ll): linelist[i] = ll
|
||
|
||
i = i + 1
|
||
|
||
return(linelist)
|
||
|
||
#apply lineid tags to an existing plot
|
||
def tags(s, minew=10., normalized=True):
|
||
|
||
x = s[0]
|
||
y = s[1]
|
||
z = s[2]
|
||
|
||
if normalized:
|
||
y = y/z
|
||
ylabel = 'normalized flux'
|
||
else:
|
||
ylabel = 'flux'
|
||
|
||
plt.figure()
|
||
plt.clf()
|
||
plt.ion()
|
||
plt.plot(x,y)
|
||
plt.xlim([np.min(x),np.max(x)])
|
||
plt.ylim([np.min(y)*0.1,np.max(y)*1.1])
|
||
plt.xlabel('wavelength (A)')
|
||
plt.ylabel(ylabel)
|
||
|
||
lalilo = s[3]
|
||
la = lalilo[0]
|
||
li = lalilo[1]
|
||
lo = lalilo[2]
|
||
|
||
pli = 0.
|
||
pla = 0.
|
||
pres = 0.0
|
||
|
||
for i in range(len(la)):
|
||
if lo[i] >= minew:
|
||
res = min(y[(abs(x-la[i]) < la[i]*1e-5)])
|
||
if li[i] == pli and abs(la[i]-pla) < la[i]*1e-4 and abs(res-pres) < res*0.1:
|
||
pass
|
||
else:
|
||
plt.text(la[i]*(1.-2e-5),res-0.07*max(y),li[i])
|
||
pla=la[i]
|
||
pli=li[i]
|
||
pres=res
|
||
|
||
plt.show()
|
||
|
||
return (pla,pli)
|
||
|
||
def checkinput(wrange, vmicro, linelist):
|
||
"""
|
||
验证用户输入参数
|
||
参数:
|
||
-----------
|
||
wrange: tuple or list of two floats
|
||
波长范围(埃)
|
||
vmicro: float, 可选
|
||
微观湍流速度(km/s)
|
||
(默认取模型大气文件值)
|
||
linelist: list of str
|
||
线表文件名列表,第一个文件为原子线表,后续为分子线表
|
||
(默认使用linelist0)
|
||
返回:
|
||
-------
|
||
imode: int
|
||
控制计算模式的参数:
|
||
0:原子和分子线(至少两个线表)
|
||
1:少量线模式(线数≤10)
|
||
2:纯连续谱模式(无线表)
|
||
"""
|
||
# 验证波长范围是否合理
|
||
assert (wrange[1] > wrange[0]), '结束波长必须大于起始波长'
|
||
|
||
# 判断线表数量
|
||
if len(linelist) == 0:
|
||
imode = 2 # 无线表 → 纯连续谱模式
|
||
|
||
else:
|
||
# 获取首个原子线表的波长范围和线数
|
||
nlines, minlambda, maxlambda = getlinelistrange(linelist[0])
|
||
|
||
# 判断波长范围是否在允许范围内
|
||
if nlines > 10:
|
||
assert (wrange[0] > minlambda and wrange[1] < maxlambda), \
|
||
f"波长范围超出允许范围({minlambda}到{maxlambda}埃)"
|
||
imode = 0 # 常规模式(原子+分子线)
|
||
else:
|
||
imode = 1 # 少量线模式
|
||
|
||
# 验证微观湍流速度是否非负
|
||
assert (vmicro >= 0.0), f"微观湍流速度 vmicro={vmicro} 不应小于0"
|
||
|
||
return (imode)
|
||
|
||
def getlinelistrange(atomiclinelist):
|
||
#finds out min and max wavelengths for a line list
|
||
|
||
if atomiclinelist[-3:] == '.11':
|
||
file00 = atomiclinelist[:-3]+'.00'
|
||
assert (os.path.isfile(file00)),'The file '+file00+' reporting linelist statistics is missing'
|
||
f = open(file00,'r')
|
||
lines = f.readlines()
|
||
entries = lines[0].split()
|
||
nlines = np.int64(entries[0])
|
||
entries = lines[1].split()
|
||
minlambda = float(entries[0])*10.
|
||
entries = lines[2].split()
|
||
maxlambda = float(entries[0])*10.
|
||
f.close()
|
||
else:
|
||
f = open(atomiclinelist,'r')
|
||
line = f.readline()
|
||
entries = line.split()
|
||
minlambda = float(entries[0])*10.
|
||
fsize = os.path.getsize(atomiclinelist)
|
||
f.seek(fsize-103)
|
||
line = f.readline()
|
||
f.close()
|
||
entries = line.split()
|
||
maxlambda = float(entries[0])*10.
|
||
nlines = int(0.01 * fsize)
|
||
|
||
|
||
return(nlines, minlambda,maxlambda)
|
||
|
||
|
||
|
||
def writetas(filename,nd,linelist,nonstd=None):
|
||
#write non-std input parameters
|
||
# input: filename -- str -- name of the non-std. param. file to print
|
||
# nd -- int -- number of layers in the model
|
||
# linelist -- list -- names of the linelist files (atomic first, then one
|
||
# or more molecular ones
|
||
# nonstd -- dict -- additional entries to add to the tas file
|
||
#
|
||
|
||
f = open(filename,'w')
|
||
|
||
if nonstd is not None:
|
||
for entry in nonstd.keys():
|
||
f.write(entry + "=" + str(nonstd[entry])+"\n")
|
||
|
||
f.write("ND= "+str(nd)+" \n")
|
||
if len(linelist) > 1: f.write("IFMOL= "+one+" \n")
|
||
f.write("TMOLIM= 8000. \n")
|
||
|
||
|
||
f.close()
|
||
|
||
return()
|
||
|
||
def write3(zexclude):
|
||
|
||
f = open('fort.3','w')
|
||
for z in zexclude:
|
||
f.write( " %d %10.4e \n" % (z, 0.0) )
|
||
f.close()
|
||
|
||
return()
|
||
|
||
|
||
def write2(lt,lrho,wrange, filename='opt.data', dlw=2.1e-5, binary=False,strength=1e-4,inttab=1):
|
||
#write fort.2 file for creating opacity tables for TLUSTY
|
||
|
||
f = open('fort.2','w')
|
||
f.write( " %d %10.4e %10.4e \n" % (len(lt),10.**lt[0],10.**lt[-1]) )
|
||
f.write( " %d \n" % (1) )
|
||
f.write( " %d %10.4e %10.4e \n" % (len(lrho),10.**lrho[0],10.**lrho[-1]) )
|
||
|
||
nsamples = int( (np.log10(wrange[1]) - np.log10(wrange[0]) )/dlw) + 1
|
||
f.write( " %d %d %10.4e %10.4e \n" % (nsamples,inttab,wrange[0],wrange[1]) )
|
||
if binary == True:
|
||
ibingr = 1
|
||
else:
|
||
ibingr = 0
|
||
filename = "'"+filename+"'"
|
||
f.write( " %s %d \n" % (filename,ibingr) )
|
||
f.close()
|
||
|
||
return()
|
||
|
||
def read55(filename='fort.55'):
|
||
|
||
|
||
#imode,idst,iprin
|
||
#inmod,zero,ichang,ichemc
|
||
#lyman,zero,zero,zero,zero
|
||
#one,nlte,icontl,inlist,ifhe2
|
||
#ihydpr,ihe1pr,ihe2pr
|
||
#wstart,wend,cutoff,zero,strength,wdist
|
||
#nmol-linelists unit20 unit21 ...
|
||
#vmicro
|
||
|
||
f = open(filename,'r')
|
||
line = f.readline()
|
||
entries = line.split()
|
||
imode = int(entries[0])
|
||
iprin = int(entries[2])
|
||
#f.write(" "+str(imode)+" "+zero+" "+str(iprin)+"\n")
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
inmod = int(entries[0])
|
||
#f.write(" "+str(inmod)+3*zero+"\n")
|
||
|
||
line = f.readline()
|
||
#f.write(5*zero+"\n")
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
inlte = int(entries[0])
|
||
#f.write(one+str(abs(inlte))+zero+str(inlist)+zero+"\n")
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
hydprf = int(entries[0])
|
||
#f.write(str(hydprf)+2*zero+"\n")
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
wrange = [ float(entries[0]), float(entries[1]) ]
|
||
cutoff0 = float(entries[2])
|
||
strength = float(entries[4])
|
||
dw = float(entries[5])
|
||
#f.write( ' %f %f %f %i %e %f \n ' % (wrange[0], wrange[1], cutoff0, 0, strength, dw) )
|
||
|
||
line = f.readline()
|
||
molls = map(float,line.split())
|
||
|
||
line = f.readline()
|
||
vmicro = float(line)
|
||
f.close()
|
||
|
||
return(imode,iprin,inmod,inlte,hydprf,wrange,cutoff0,strength,dw,molls,vmicro)
|
||
|
||
def write55(wrange,dw=1e-2,imode=0,iprin=0,inlte=0,hydprf=2,cutoff0=200., \
|
||
strength=1e-4,vmicro=1.0, \
|
||
linelist=linelist0, atmostype='kurucz',intensity=False):
|
||
|
||
|
||
#imode,idst,iprin
|
||
#inmod,zero,ichang,ichemc
|
||
#lyman,zero,zero,zero,zero
|
||
#one,nlte,icontl,inlist,ifhe2
|
||
#ihydpr,ihe1pr,ihe2pr
|
||
#wstart,wend,cutoff,zero,strength,wdist
|
||
|
||
if (atmostype == 'tlusty' or atmostype == 'marcs'): inmod = 1
|
||
else: inmod = 0
|
||
|
||
inlist = 0
|
||
all_inlist = []
|
||
for file in linelist:
|
||
inlist = 0
|
||
if file[-3:] == '.11' : inlist = 1
|
||
all_inlist.append(inlist)
|
||
assert (inlist - all_inlist[0] == 0), 'The line list files must be all either text or binary!'
|
||
|
||
f = open('fort.55','w')
|
||
f.write(" "+str(imode)+" "+zero+" "+str(iprin)+"\n")
|
||
f.write(" "+str(inmod)+3*zero+"\n")
|
||
f.write(5*zero+"\n")
|
||
f.write(one+str(abs(inlte))+zero+str(inlist)+zero+"\n")
|
||
f.write(str(hydprf)+2*zero+"\n")
|
||
if imode <= -3:
|
||
f.write( ' %f %f %f %i %e %f \n ' % (wrange[0], -wrange[1], cutoff0, 0, strength, dw) )
|
||
else:
|
||
f.write( ' %f %f %f %i %e %f \n ' % (wrange[0], wrange[1], cutoff0, 0, strength, dw) )
|
||
ll = len(linelist)
|
||
if ll < 2: f.write(2*zero)
|
||
else: f.write(str(ll-1) + ' ' + ' '.join(map(str,np.arange(ll-1)+20)))
|
||
f.write("\n")
|
||
f.write( ' %f \n' % (vmicro) )
|
||
if intensity:
|
||
f.write( ' %i %f %i \n' % (-10, 0.0001, 1) )
|
||
f.write( ' %f %f %f %f %f %f %f %f %f %f \n' % (0.0001,0.001,0.01,0.1,0.25,0.4,0.55,0.7,0.85,1.0) )
|
||
f.close()
|
||
|
||
def write5(teff,logg,abu, atom='ap18', ofile='fort.5', inlte=0, atommode=None, atominfo=None):
|
||
|
||
symbol, mass, sol = elements()
|
||
|
||
f = open(ofile,'w')
|
||
f.write(' '+str(teff)+" "+str(logg).format('%7.4f')+" ! TEFF, GRAV \n")
|
||
if inlte == 0:
|
||
f.write(" T F ! LTE, GRAY \n")
|
||
else:
|
||
f.write(" F F ! LTE, GRAY \n")
|
||
|
||
f.write(" 'tas' ! name of non-standard flags \n")
|
||
f.write(" 50 ! frequencies \n")
|
||
|
||
natom = len(abu)
|
||
f.write(" "+str(natom)+" ! NATOMS \n")
|
||
|
||
assert (atom == 'hhm' or atom == 'ap18' or atom == 'yo19' or atom == 'test'), 'atom must be one of: hhm/ap18/yo19/test!'
|
||
ex = np.ones(natom)
|
||
|
||
#tlusty models provide atommode and atominfo that override the defaults for atom and ex
|
||
if atommode is not None: ex[:len(atommode)] = np.array(atommode)
|
||
if atominfo is not None: atom = 'own'
|
||
|
||
if atom == 'hhm' :
|
||
zex = [1] #atomic numbers of elements included explicitly (contributing cont. opacity)
|
||
elif atom == 'yo19':
|
||
zex = [1,11,12,19,20]
|
||
elif atom == 'test':
|
||
zex = [1,26]
|
||
elif atom == 'ap18':
|
||
zex = [1,2,6,7,8,11,12,13,14,20,26]
|
||
else:
|
||
zex = []
|
||
|
||
for i in zex: ex[i-1] = 2
|
||
|
||
for i in range(natom):
|
||
f.write(' %2d %e %i %s\n' % (np.abs(ex[i]),
|
||
abu[i], 0, ' ! ' +symbol[i]) )
|
||
|
||
for i in range(3): f.write("* \n")
|
||
|
||
if atom == 'hhm': # highly simplified continuum opacities -- just H and H-
|
||
f.write("* ../data_atom for ions \n")
|
||
f.write(" 1 -1 1 0 0 1 ' H 1' 'data/hm.dat' \n" )
|
||
f.write(" 0 0 3 0 \n")
|
||
f.write(" 1 0 9 0 0 0 ' H 1' 'data/h1s.dat' \n")
|
||
f.write(" 1 1 1 1 0 0 ' H 2' ' ' \n")
|
||
f.write(" 0 0 0 -1 0 0 ' ' ' ' \n")
|
||
f.write("* \n")
|
||
f.write("* end \n")
|
||
elif atom == "yo19": # set for NLTE calculations for APOGEE (see Osorio+ 2019 A&A paper)
|
||
f.write("* ../data_atom for ions \n")
|
||
f.write(" 1 -1 1 0 0 1 ' H 0' 'data/hm.dat' \n")
|
||
f.write(" 0 0 3 0 \n")
|
||
f.write(" 1 0 16 0 0 0 ' H 1' 'data/h1_16lev2.dat' \n")
|
||
f.write(" 1 1 1 1 0 0 ' H 2' ' ' \n")
|
||
f.write(" 11 0 42 0 0 0 'Na 1' 'data/NaIkas.tl' \n")
|
||
f.write(" 11 1 1 1 0 0 'Na 2' '' \n")
|
||
f.write(" 12 0 96 0 0 0 'Mg 1' 'data/Mg1kas_F_ccc.sy' \n")
|
||
f.write(" 12 1 29 0 0 0 'Mg 2' 'data/Mg2kas_F_ccc.sy' \n")
|
||
f.write(" 12 2 1 1 0 0 'Mg 3' ' ' \n")
|
||
f.write(" 19 0 31 0 0 0 'K 1' 'data/KIkas.tl' \n")
|
||
f.write(" 19 1 1 1 0 0 'K 2' '' \n")
|
||
f.write(" 20 0 66 0 0 0 'Ca 1' 'data/Ca1kas_F_zat.sy' \n")
|
||
f.write(" 20 1 24 0 0 0 'Ca 2' 'data/Ca2kas_F_zat.sy' \n")
|
||
f.write(" 20 2 1 1 0 0 'Ca 3' ' ' \n")
|
||
f.write(" 0 0 0 -1 0 0 ' ' ' ' \n")
|
||
f.write("* \n")
|
||
f.write("* end \n")
|
||
elif atom == 'test': # generic set used in Allende Prieto+ (2018) A&A paper
|
||
f.write("* ../data for ions \n")
|
||
f.write(" 1 -1 1 0 0 1 ' H 1' 'data/hm.dat' \n")
|
||
f.write(" 0 0 3 0 \n")
|
||
f.write(" 1 0 9 0 0 0 ' H 1' 'data/h1s.dat' \n")
|
||
f.write(" 1 1 1 1 0 0 ' H 2' ' ' \n")
|
||
f.write(" 26 0 49 0 0 0 'Fe 1' 'data/tlusty_fe1_topmod.dat' \n")
|
||
f.write(" 26 1 41 0 0 0 'Fe 2' 'data/tlusty_fe2_topmod.dat' \n")
|
||
f.write(" 26 2 1 1 0 0 'Fe 3' ' ' \n")
|
||
f.write(" 0 0 0 -1 0 0 ' ' ' ' \n")
|
||
f.write("* \n")
|
||
f.write("* end \n")
|
||
elif atom == 'ap18': # generic set used in Allende Prieto+ (2018) A&A paper
|
||
f.write("* ../data for ions \n")
|
||
f.write(" 1 -1 1 0 0 1 ' H 1' 'data/hm.dat' \n")
|
||
f.write(" 0 0 3 0 \n")
|
||
f.write(" 1 0 9 0 0 0 ' H 1' 'data/h1s.dat' \n")
|
||
f.write(" 1 1 1 1 0 0 ' H 2' ' ' \n")
|
||
f.write(" 2 0 14 0 0 0 'He 1' 'data/he1.dat' \n")
|
||
f.write(" 2 1 14 0 0 0 'He 2' 'data/he2.dat ' \n")
|
||
f.write(" 2 2 1 1 0 0 'He 3' ' ' \n")
|
||
f.write(" 6 0 104 0 0 0 ' C 1' 'data/c1.t' \n")
|
||
f.write(" 6 1 40 0 0 0 ' C 2' 'data/c2.t' \n")
|
||
f.write(" 6 2 1 1 0 0 ' C 3' ' ' \n")
|
||
f.write(" 7 0 89 0 0 0 ' N 1' 'data/n1.t' \n")
|
||
f.write(" 7 1 51 0 0 0 ' N 2' 'data/n2.t' \n")
|
||
f.write(" 7 2 1 1 0 0 ' N 3' ' ' \n")
|
||
f.write(" 8 0 54 0 0 0 ' O 1' 'data/o1c.t' \n")
|
||
f.write(" 8 1 74 0 0 0 ' O 2' 'data/o2.t' \n")
|
||
f.write(" 8 2 1 1 0 0 ' O 3' ' ' \n")
|
||
f.write(" 11 0 32 0 0 0 'Na 1' 'data/na1.t' \n")
|
||
f.write(" 11 1 8 0 0 0 'Na 2' 'data/na2.t' \n")
|
||
f.write(" 11 2 1 1 0 0 'Na 3' ' ' \n")
|
||
f.write(" 12 0 71 0 0 0 'Mg 1' 'data/mg1.t' \n")
|
||
f.write(" 12 1 31 0 0 0 'Mg 2' 'data/mg2.t' \n")
|
||
f.write(" 12 2 1 1 0 0 'Mg 3' ' ' \n")
|
||
f.write(" 13 0 33 0 0 0 'Al 1' 'data/al1.t' \n")
|
||
f.write(" 13 1 81 0 0 0 'Al 2' 'data/al2.t' \n")
|
||
f.write(" 13 2 1 1 0 0 'Al 3' ' ' \n")
|
||
f.write(" 14 0 57 0 0 0 'Si 1' 'data/si1.t' \n")
|
||
f.write(" 14 1 46 0 0 0 'Si 2' 'data/si2.t' \n")
|
||
f.write(" 14 2 1 1 0 0 'Si 3' ' ' \n")
|
||
f.write(" 20 0 79 0 0 0 'Ca 1' 'data/ca1.t' \n")
|
||
f.write(" 20 1 32 0 0 0 'Ca 2' 'data/ca2.t' \n")
|
||
f.write(" 20 2 1 1 0 0 'Ca 3' ' ' \n")
|
||
f.write(" 26 0 49 0 0 0 'Fe 1' 'data/tlusty_fe1_topmod.dat' \n")
|
||
f.write(" 26 1 41 0 0 0 'Fe 2' 'data/tlusty_fe2_topmod.dat' \n")
|
||
f.write(" 26 2 1 1 0 0 'Fe 3' ' ' \n")
|
||
f.write(" 0 0 0 -1 0 0 ' ' ' ' \n")
|
||
f.write("* \n")
|
||
f.write("* end \n")
|
||
else:
|
||
for line in atominfo: f.write(line)
|
||
f.close()
|
||
|
||
|
||
def write8(teff, logg, nd, atmos, atmostype, ofile='fort.8',
|
||
kvmicro=2.0, kabu=None,ktitle='written by synple/write8'):
|
||
|
||
"""Writes the model atmosphere for synspec
|
||
|
||
MARCS models can be passed in 'Tlusty' (default, after read with
|
||
read_marcs_models2) or 'Kurucz' format
|
||
Phoenix and Kurucz models are passed to synspec formatted as 'kurucz'
|
||
|
||
kvmicro (microturbulence in km/s), kabu (abundance array), and ktitle
|
||
(informative text to be added to the header)
|
||
are optional and only used when atmostype is 'kurucz'
|
||
|
||
|
||
"""
|
||
|
||
f = open(ofile,'w')
|
||
|
||
if atmostype == 'tlusty':
|
||
|
||
if ('n' in atmos.dtype.names): # 4th column is number density n
|
||
if ('pop' in atmos.dtype.names): # explicit (usually NLTE) populations
|
||
numpop = len(atmos['pop'][0])
|
||
sformat = ' %f %e %e %e'
|
||
i = 5
|
||
for entry in atmos['pop'][0]:
|
||
sformat = sformat + ' %e'
|
||
if i % 6 == 0: sformat = sformat + ' \n'
|
||
i = i + 1
|
||
sformat = sformat + ' \n'
|
||
f.write(" "+str(nd)+" "+str(-(4+numpop))+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
sdata = [atmos['t'][i], atmos['ne'][i], atmos['rho'][i], atmos['n'][i]]
|
||
for j in range(numpop):
|
||
sdata.append(atmos['pop'][i][j])
|
||
f.write( sformat % tuple(sdata) )
|
||
elif ('dep' in atmos.dtype.names): # NLTE departure coefficients
|
||
numpop = len(atmos['dep'][0])
|
||
sformat = ' %f %e %e %e'
|
||
i = 5
|
||
for entry in atmos['dep'][0]:
|
||
sformat = sformat + ' %e'
|
||
if i % 6 == 0: sformat = sformat + ' \n'
|
||
i = i + 1
|
||
sformat = sformat + ' \n'
|
||
f.write(" "+str(nd)+" "+str(-(4+numpop))+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
sdata = [atmos['t'][i], atmos['ne'][i], atmos['rho'][i], atmos['n'][i]]
|
||
for j in range(numpop):
|
||
sdata.append(atmos['dep'][i][j])
|
||
f.write( sformat % tuple(sdata) )
|
||
else: # LTE
|
||
f.write(" "+str(nd)+" "+str(-4)+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
f.write( '%f %e %e %e \n' % (atmos['t'][i], atmos['ne'][i], atmos['rho'][i], atmos['n'][i] ) )
|
||
|
||
else: # n not included, only t, ne and rho given
|
||
if ('pop' in atmos.dtype.names): # explicit (usually NLTE) populations
|
||
numpop = len(atmos['pop'][0])
|
||
sformat = ' %f %e %e '
|
||
i = 5
|
||
for entry in atmos['pop'][0]:
|
||
sformat = sformat + ' %e'
|
||
if i % 6 == 0: sformat = sformat + ' \n'
|
||
i = i + 1
|
||
sformat = sformat + ' \n'
|
||
f.write(" "+str(nd)+" "+str(3+numpop)+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
sdata = [atmos['t'][i], atmos['ne'][i], atmos['rho'][i] ]
|
||
for j in range(numpop):
|
||
sdata.append(atmos['pop'][i][j])
|
||
f.write( sformat % tuple(sdata) )
|
||
elif ('dep' in atmos.dtype.names): # NLTE departure coefficients
|
||
numpop = len(atmos['dep'][0])
|
||
sformat = ' %f %e %e '
|
||
i = 5
|
||
for entry in atmos['dep'][0]:
|
||
sformat = sformat + ' %e'
|
||
if i % 6 == 0: sformat = sformat + ' \n'
|
||
i = i + 1
|
||
format = sformat + ' \n'
|
||
f.write(" "+str(nd)+" "+str(3+numpop)+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
sdata = [atmos['t'][i], atmos['ne'][i], atmos['rho'][i] ]
|
||
for j in range(numpop):
|
||
sdata.append(atmos['dep'][i][j])
|
||
f.write( sformat % tuple(sdata) )
|
||
else: # LTE
|
||
f.write(" "+str(nd)+" "+str(3)+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
if (i+1) % 5 == 0: f.write('\n')
|
||
if (i+1) % 5 != 0: f.write('\n')
|
||
for i in range(nd):
|
||
f.write( '%f %e %e \n' % (atmos['t'][i], atmos['ne'][i], atmos['rho'][i] ) )
|
||
|
||
else:
|
||
|
||
if atmostype == 'marcs':
|
||
f.write(" "+str(nd)+" "+str(-4)+"\n")
|
||
for i in range(nd):
|
||
f.write(' %e ' % atmos['dm'][i])
|
||
f.write("\n")
|
||
for i in range(nd):
|
||
f.write( '%f %e %e %e \n' % (atmos['t'][i], atmos['ne'][i], atmos['rho'][i], atmos['rho'][i]/atmos['mmw'][i]/1.67333e-24 + atmos['ne'][i] ) )
|
||
|
||
else: #Kurucz format
|
||
f.write( 'TEFF %7.0f GRAVITY %7.5f LTE \n' % (teff, logg) )
|
||
if kabu is None:
|
||
for i in range(21): f.write('\n')
|
||
else:
|
||
|
||
abu2 = np.array(kabu,dtype=float)
|
||
ntotalovernh = np.sum(abu2)
|
||
abu2 = abu2 / ntotalovernh
|
||
abu2[2:] = np.log10(abu2[2:])
|
||
|
||
f.write( 'TITLE %30s\n' % (ktitle) )
|
||
opflags='1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0'
|
||
f.write( ' OPACITY IFOP %s\n' % opflags)
|
||
f.write( ' CONVECTION ON 1.25 TURBULENCE OFF 0.00 0.00 0.00 0.00\n')
|
||
f.write ('ABUNDANCE SCALE %8.5f ABUNDANCE CHANGE 1%8.5f 2 %8.5f\n' % (1.00000,abu2[0],abu2[1]) )
|
||
for i in np.arange(16):
|
||
dd = np.zeros(12)
|
||
sixs = np.arange(6, dtype=int)
|
||
dd[np.array(sixs * 2, dtype=int)] = 3 + 6*i + sixs
|
||
dd[np.array(sixs * 2 + 1, dtype=int)] = np.array(abu2,dtype=float)[np.array( 2 + 6*i + sixs, dtype=int)]
|
||
f.write( ' ABUNDANCE CHANGE %2i %6.2f %2i %6.2f %2i %6.2f %2i %6.2f %2i %6.2f %2i %6.2f\n' % tuple(dd) )
|
||
#endfor
|
||
|
||
dd = np.zeros(2)
|
||
dd[0] = 3 + 6*16
|
||
dd[1] = abu2[2 + 6*16]
|
||
f.write(' ABUNDANCE CHANGE %2i %6.2f\n' % tuple(dd) )
|
||
|
||
if kvmicro is None:
|
||
f.write( 'READ DECK6%3i RHOX,T,P,XNE\n' % nd )
|
||
for i in range(nd):
|
||
f.write( '%e %f %e %e\n' % (atmos['dm'][i], atmos['t'][i], atmos['p'][i], atmos['ne'][i]) )
|
||
|
||
else:
|
||
f.write( 'READ DECK6%3i RHOX,T,P,XNE,ZERO,ZERO,VTURB\n' % nd )
|
||
for i in range(nd):
|
||
f.write( '%e %f %e %e %8.2e %8.2e %8.2e\n' % (atmos['dm'][i], atmos['t'][i], atmos['p'][i], atmos['ne'][i], 0.0, 0.0, kvmicro*1e5) )
|
||
|
||
f.close()
|
||
|
||
return()
|
||
|
||
|
||
def write_innterpol_model(filename,feh,cfe,afe,teff,logg,array,
|
||
vmicro=2.0,title='iNNterpolated model'):
|
||
|
||
"""
|
||
write a model atmosphere from iNNterpol to disk
|
||
|
||
Parameters
|
||
----------
|
||
filename: str
|
||
name of the file to contail the model atmosphere
|
||
|
||
feh: float
|
||
metallicity [Fe/H]
|
||
|
||
cfe: float
|
||
carbon to iron abundance [C/Fe]
|
||
|
||
afe: float
|
||
alpha-element to iron abundance [alpha/Fe]
|
||
|
||
teff: float
|
||
effective temperature (K)
|
||
|
||
logg: float
|
||
surface gravity (log10 of the surface gravity in cm/s2)
|
||
|
||
array: numpy array of floats
|
||
atmospheric structure with as many rows as layers and 4 columns
|
||
corresponding to the log10 of column mass, temperature, gas pressure and
|
||
electron density. This array is exactly what iNNterpol returns.
|
||
see https://github.com/cwestend/iNNterpol
|
||
|
||
vmicro: float
|
||
microturbulence (km/s)
|
||
(default is 2.)
|
||
|
||
title: str
|
||
explanatory text to be added to the header
|
||
|
||
Returns
|
||
-------
|
||
|
||
no output, it creates a text file with a Kurucz-formatted model atmosphere
|
||
|
||
"""
|
||
|
||
#we start by repeating the top layer, since it was dropped for
|
||
#building the iNNterpol NNs and it will again be dropped by Synspec
|
||
|
||
array = np.vstack( (array[0,:], array) )
|
||
|
||
nd = len(array[:,0])
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'p','ne'),
|
||
'formats':('f', 'f', 'f','f')})
|
||
|
||
array = 10.**array
|
||
|
||
atmos['dm'] = array[:,0]
|
||
atmos['t'] = array[:,1]
|
||
atmos['p'] = array[:,2]
|
||
atmos['ne'] = array[:,3]
|
||
|
||
symbol, mass, abu = elements()
|
||
if np.abs(feh) > 0.: abu[2:] = abu[2:] * 10.**feh
|
||
if np.abs(cfe) > 0.: abu[5] = abu[5] * 10.**cfe
|
||
if np.abs(afe) > 0.:
|
||
#O, Ne, Mg, Si, S, Ca, and Ti.
|
||
for item in [8,10,12,14,16,20,22]:
|
||
abu[item-1] = abu[item-1] * 10.**afe
|
||
|
||
|
||
write8(teff,logg,nd,atmos,'kurucz',ofile=filename,
|
||
kvmicro=vmicro,kabu=abu,
|
||
ktitle=title)
|
||
|
||
return()
|
||
|
||
|
||
|
||
def read10(file='fort.10'):
|
||
#read output synspec file with specific intensities
|
||
#returning wavelengths and intensities in the file
|
||
|
||
f = open(file,'r')
|
||
x = []
|
||
y = []
|
||
for line in f:
|
||
entries = line.split()
|
||
if len(entries) == 2:
|
||
x.append(entries[0])
|
||
else:
|
||
y.extend(entries)
|
||
|
||
|
||
iwave = np.array(x,dtype=float)
|
||
inte = np.array(y,dtype=float).reshape(len(x),int(len(y)/len(x)))
|
||
|
||
return(iwave,inte)
|
||
|
||
|
||
|
||
def create_links(linelist):
|
||
#create soft links for line lists
|
||
|
||
for i in range(len(linelist)):
|
||
file = linelist[i]
|
||
binaryfile = linelist[i][:-2]+'11'
|
||
if os.path.isfile(binaryfile): file = binaryfile
|
||
if i == 0: os.symlink(file,'fort.19')
|
||
else: os.symlink(file,'fort.'+str(20-1+i))
|
||
|
||
return()
|
||
|
||
def cleanup_fort():
|
||
#cleanup all fort* files
|
||
|
||
|
||
files = os.listdir('.')
|
||
for entry in files:
|
||
if os.path.islink(entry) and entry.startswith('fort'): os.unlink(entry)
|
||
if os.path.isfile(entry) and entry.startswith('fort'): os.remove(entry)
|
||
|
||
return()
|
||
|
||
|
||
def read_kurucz_model(modelfile):
|
||
|
||
"""Reads a Kurucz model atmospheres
|
||
|
||
Parameters
|
||
----------
|
||
modelfile: str
|
||
file name
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s)
|
||
abu : list
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es)
|
||
nd: int
|
||
number of depths (layers) of the model
|
||
atmos: numpy structured array
|
||
array with the run with depth of column mass, temperature, gas pressure
|
||
and electron density
|
||
|
||
"""
|
||
|
||
f = open(modelfile,'r')
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[0] == 'TEFF' and entries[2] == 'GRAVITY'), 'Cannot find Teff and logg in the file header'
|
||
teff = float(entries[1])
|
||
logg = float(entries[3])
|
||
|
||
while entries[0] != 'ABUNDANCE':
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
abu = []
|
||
|
||
if entries[1] == 'SCALE':
|
||
scale = float(entries[2])
|
||
|
||
|
||
while entries[0] == 'ABUNDANCE':
|
||
i = 0
|
||
for word in entries:
|
||
if (word == 'CHANGE'): w = i
|
||
i = i + 1
|
||
for i in range(int((len(entries)-w-1)/2)):
|
||
z = int(entries[w+1+2*i])
|
||
if (z == 1): nhntot = float(entries[w+2+2*i])
|
||
if (z < 3): abu.append(float(entries[w+2+2*i]) / nhntot)
|
||
else: abu.append(scale*10.**(float(entries[w+2+2*i])) / nhntot)
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
assert (entries[0] == 'READ'), 'I cannot find the header of the atmospheric table in the input Kurucz model'
|
||
|
||
nd = int(entries[2]) - 1
|
||
line = f.readline()
|
||
entries = line.split()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
vmicro = float(entries[6])/1e5
|
||
|
||
dm = [ float(entries[0]) ]
|
||
t = [ float(entries[1]) ]
|
||
p = [ float(entries[2]) ]
|
||
ne = [ float(entries[3]) ]
|
||
|
||
good = True
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
for char in entries:
|
||
if char.count('E') > 1:
|
||
good = False
|
||
|
||
if not good: break
|
||
|
||
dm.append(float(entries[0]))
|
||
t.append(float(entries[1]))
|
||
p.append(float(entries[2]))
|
||
ne.append( float(entries[3]))
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'p','ne'),
|
||
'formats':('f', 'f', 'f','f')})
|
||
|
||
if good:
|
||
atmos['dm'] = dm
|
||
atmos['t'] = t
|
||
atmos['p'] = p
|
||
atmos['ne'] = ne
|
||
|
||
else:
|
||
return(None,None,None,None,None,None)
|
||
|
||
return (teff,logg,vmicro,abu,nd,atmos)
|
||
|
||
|
||
def read_marcs_model(modelfile):
|
||
|
||
"""Reads a MARCS model atmospheres
|
||
|
||
Parameters
|
||
----------
|
||
modelfile: str
|
||
file name. It can be a gzipped (.gz) file
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s)
|
||
abu : list
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es)
|
||
nd: int
|
||
number of depths (layers) of the model
|
||
atmos: numpy structured array
|
||
array with the run with depth of column mass, temperature, gas pressure
|
||
and electron density
|
||
|
||
"""
|
||
|
||
if modelfile[-3:] == '.gz':
|
||
f = gzip.open(modelfile,'rt')
|
||
else:
|
||
f = open(modelfile,'r')
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Teff'), 'Cannot find Teff in the file header'
|
||
teff = float(entries[0])
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Surface' and entries[2] == 'gravity'), 'Cannot find logg in the file header'
|
||
logg = np.log10(float(entries[0]))
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Microturbulence'), 'Cannot find vmicro in the file header'
|
||
vmicro = float(entries[0])
|
||
|
||
while entries[0] != 'Logarithmic':
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
abu = []
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
i = 0
|
||
while entries[1] != 'Number':
|
||
for word in entries:
|
||
abu.append( 10.**(float(word)-12.0) )
|
||
i = i + 1
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
if i < 99:
|
||
for j in range(99-i):
|
||
abu.append(1e-111)
|
||
i = i + 1
|
||
|
||
nd = int(entries[0])
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
assert (entries[0] == 'Model'), 'I cannot find the header of the atmospheric table in the input MARCS model'
|
||
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t = [ float(entries[4]) ]
|
||
p = [ float(entries[6]) ]
|
||
ne = [ float(entries[5]) / bolk / float(entries[4]) ]
|
||
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t.append( float(entries[4]))
|
||
p.append( float(entries[6]))
|
||
ne.append( float(entries[5]) / bolk / float(entries[4]))
|
||
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
dm = [ float(entries[-1]) ]
|
||
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
dm.append( float(entries[-1]))
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'p','ne'),
|
||
'formats':('f', 'f', 'f','f')})
|
||
atmos['dm'] = dm
|
||
atmos['t'] = t
|
||
atmos['p'] = p
|
||
atmos['ne'] = ne
|
||
|
||
return (teff,logg,vmicro,abu,nd,atmos)
|
||
|
||
def read_marcs_model2(modelfile):
|
||
|
||
"""Reads a MARCS model atmospheres.
|
||
While read_marcs_model returns T, Pg and Ne in the structure 'atmos'
|
||
read_marcs_model2 returns T, rho, mmw, and Ne.
|
||
|
||
Parameters
|
||
----------
|
||
modelfile: str
|
||
file name. It can be a gzipped (.gz) file
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s)
|
||
abu : list
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es)
|
||
nd: int
|
||
number of depths (layers) of the model
|
||
atmos: numpy structured array
|
||
array with the run with depth of column mass, temperature, density,
|
||
mean molecular weight and electron number density
|
||
|
||
"""
|
||
|
||
if modelfile[-3:] == '.gz':
|
||
f = gzip.open(modelfile,'rt')
|
||
else:
|
||
f = open(modelfile,'r')
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Teff'), 'Cannot find Teff in the file header'
|
||
teff = float(entries[0])
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Surface' and entries[2] == 'gravity'), 'Cannot find logg in the file header'
|
||
logg = np.log10(float(entries[0]))
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[1] == 'Microturbulence'), 'Cannot find vmicro in the file header'
|
||
vmicro = float(entries[0])
|
||
|
||
while entries[0] != 'Logarithmic':
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
abu = []
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
i = 0
|
||
while entries[1] != 'Number':
|
||
for word in entries:
|
||
abu.append( 10.**(float(word)-12.0) )
|
||
i = i + 1
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
if i < 99:
|
||
for j in range(99-i):
|
||
abu.append(1e-111)
|
||
i = i + 1
|
||
|
||
nd = int(entries[0])
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
assert (entries[0] == 'Model'), 'I cannot find the header of the atmospheric table in the input MARCS model'
|
||
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t = [ float(entries[4]) ]
|
||
p = [ float(entries[6]) ]
|
||
ne = [ float(entries[5]) / bolk / float(entries[4]) ]
|
||
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t.append( float(entries[4]))
|
||
p.append( float(entries[6]))
|
||
ne.append( float(entries[5]) / bolk / float(entries[4]))
|
||
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
rho = [ float(entries[3]) ]
|
||
dm = [ float(entries[-1]) ]
|
||
mmw = [ float(entries[4]) ]
|
||
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
rho.append( float(entries[3]))
|
||
dm.append( float(entries[-1]))
|
||
mmw.append( float(entries[4]))
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'rho','mmw','ne'),
|
||
'formats':('f', 'f', 'f','f','f')})
|
||
atmos['dm'] = dm
|
||
atmos['t'] = t
|
||
atmos['rho'] = rho
|
||
atmos['mmw'] = mmw
|
||
atmos['ne'] = ne
|
||
|
||
return (teff,logg,vmicro,abu,nd,atmos)
|
||
|
||
def read_tlusty_model(modelfile, startdir=None):
|
||
"""
|
||
读取Tlusty模型大气文件
|
||
参数:
|
||
-----------
|
||
modelfile: str
|
||
模型文件名(.7、.8或.22格式)。代码会寻找对应的.5文件以读取丰度和微观湍流速度
|
||
startdir: str
|
||
计算初始目录。若使用相对路径,代码在此目录下查找模型原子目录和非标准参数文件
|
||
(默认为当前工作目录)
|
||
返回:
|
||
-------
|
||
teff: float
|
||
有效温度(K)
|
||
logg: float
|
||
表面重力加速度对数(log₁₀(g/cm/s²))
|
||
vmicro: float
|
||
微观湍流速度(km/s,默认1.0,可通过非标准参数文件的VTB参数设置)
|
||
abu: list
|
||
丰度数组,包含从氢(Z=1)到锿(Z=99)的元素数密度比N(X)/N(H)
|
||
nd: int
|
||
模型层数
|
||
atmos: numpy结构化数组
|
||
包含列质量、温度、电子密度、质量密度等深度分布数据
|
||
"""
|
||
# 验证模型文件扩展名
|
||
assert modelfile.endswith(('.8', '.7', '.22')), 'Tlusty模型文件应以.7、.8或.22结尾'
|
||
|
||
# 根据扩展名生成对应的.5文件路径
|
||
if modelfile.endswith(('.7', '.8')):
|
||
madaffile = modelfile[:-1] + "5"
|
||
else:
|
||
madaffile = modelfile[:-3] + "5"
|
||
assert os.path.isfile(madaffile), f'缺少Tlusty模型对应的.5文件:{madaffile}'
|
||
|
||
# 设置初始目录
|
||
if startdir is None:
|
||
startdir = os.getcwd()
|
||
|
||
# 读取madaf文件(.5)中的参数
|
||
teff, logg, vmicro, abu = read_madaf(madaffile, startdir=startdir)
|
||
|
||
# 读取模型结构文件(.8/.22)
|
||
with open(modelfile, 'r') as f:
|
||
line = f.readline()
|
||
entries = line.split()
|
||
nd = int(entries[0]) # 模型层数
|
||
numpar = int(entries[1]) # 参数数量
|
||
|
||
# 检查首行格式
|
||
assert len(entries) == 2, f'模型文件首行包含多余数值:{line}'
|
||
|
||
# 读取列质量数据
|
||
dm = read_multiline_fltarray(f, nd)
|
||
|
||
# 读取所有参数数据
|
||
total_params = nd * abs(numpar)
|
||
atm = read_multiline_fltarray(f, total_params)
|
||
|
||
# 将数据重塑为二维数组(层数×参数数量)
|
||
atm = np.reshape(atm, (nd, abs(numpar)))
|
||
|
||
# 根据参数数量和文件类型定义结构化数组类型
|
||
if numpar < 0:
|
||
# 负值表示包含数密度(n)
|
||
if (abs(numpar) - 4) > 0:
|
||
# 显式粒子数(通常为NLTE模型)
|
||
if modelfile.endswith(('.7', '.8')):
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f'), ('n', 'f'), ('pop', 'f', (abs(numpar)-4))])
|
||
else:
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f'), ('n', 'f'), ('dep', 'f', (abs(numpar)-4))])
|
||
else:
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f'), ('n', 'f')])
|
||
else:
|
||
# 正值表示不含数密度
|
||
if (numpar - 3) > 0:
|
||
# 粒子数或偏离系数(根据文件类型)
|
||
if modelfile.endswith(('.7', '.8')):
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f'), ('pop', 'f', (numpar-3))])
|
||
else:
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f'), ('dep', 'f', (numpar-3))])
|
||
else:
|
||
dtype = np.dtype([('dm', 'f'), ('t', 'f'), ('ne', 'f'), ('rho', 'f')])
|
||
|
||
# 创建结构化数组
|
||
atmos = np.zeros(nd, dtype=dtype)
|
||
|
||
# 赋值基础参数
|
||
atmos['dm'] = dm
|
||
atmos['t'] = atm[:, 0] # 温度
|
||
atmos['ne'] = atm[:, 1] # 电子密度
|
||
atmos['rho'] = atm[:, 2] # 质量密度
|
||
|
||
# 处理包含数密度的情况
|
||
if numpar < 0:
|
||
atmos['n'] = atm[:, 3] # 数密度
|
||
|
||
# 处理粒子数或偏离系数
|
||
if (abs(numpar) - 4) > 0:
|
||
if modelfile.endswith(('.7', '.8')):
|
||
atmos['pop'] = atm[:, 4:] # 粒子数(NLTE模型)
|
||
else:
|
||
atmos['dep'] = atm[:, 4:] # 偏离系数(LTE模型)
|
||
|
||
return (teff, logg, vmicro, list(abu.values()), nd, atmos)
|
||
|
||
def read_madaf(madaffile,startdir=None):
|
||
|
||
"""Reads a Tlusty MADAF (.5) file with parameters and abundances.
|
||
|
||
Parameters
|
||
----------
|
||
madaffile: str
|
||
file name (.5) including the abundances and the micro (when specified in
|
||
the non-std. parameter file)
|
||
|
||
startdir: str
|
||
directory where the calculations are initiated. The code will look at that
|
||
location to find the tlusty model atom directory and the non-std. parameter
|
||
file when a relative path is provided
|
||
(default is None, indicating it is the current working directory)
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s), by default 1.0 unless set with the parameter
|
||
VTB in the non-std. parameter file specified in the .5 file
|
||
abu : dictionary
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es) -- the keys are the elemental symbols
|
||
|
||
"""
|
||
|
||
if startdir is None: startdir = os.getcwd()
|
||
|
||
assert (os.path.isfile(madaffile)),'The input madaf file '+madaffile+' is not good'
|
||
|
||
#we start reading the .5
|
||
f = open(madaffile,'r')
|
||
line = f.readline()
|
||
entries = line.split()
|
||
teff = float(entries[0])
|
||
logg = float(entries[1])
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
nonstdfile = entries[0][1:-1]
|
||
|
||
nonstdfile0 = nonstdfile
|
||
if nonstdfile != '':
|
||
if not os.path.isabs(nonstdfile):
|
||
mf = os.path.join(startdir,nonstdfile)
|
||
if os.path.isfile(mf):
|
||
nonstdfile = mf
|
||
else:
|
||
mf = os.path.join(modeldir,nonstdfile)
|
||
nonstdfile = mf
|
||
|
||
assert (os.path.exists(nonstdfile)), 'The non-std parameter file indicated in the tlusty model, '+nonstdfile0+', is not present'
|
||
|
||
nonstd={}
|
||
if nonstdfile != '':
|
||
assert (os.path.isfile(nonstdfile)),'Tlusty model atmosphere file '+modelfile+' invokes non-std parameter file, '+nonstdfile+' which is not present'
|
||
|
||
|
||
ns = open(nonstdfile,'r')
|
||
nonstdarr = ns.readlines()
|
||
ns.close()
|
||
for entry in nonstdarr:
|
||
entries = entry.replace(',\n','').split(',')
|
||
for piece in entries:
|
||
sides = piece.split('=')
|
||
nonstd[sides[0].replace(' ','')]= sides[1].replace(' ','')
|
||
|
||
print('Tlusty nonstd params=',nonstd)
|
||
|
||
#the micro might be encoded as VTB in the nonstdfile!!
|
||
#this is a temporary patch, but need to parse that file
|
||
vmicro = 1.0
|
||
if 'VTB' in nonstd: vmicro = float(nonstd['VTB'])
|
||
|
||
line = f.readline()
|
||
while line[0] == '*':
|
||
line = f.readline()
|
||
line = f.readline()
|
||
while line[0] == '*':
|
||
line = f.readline()
|
||
entries = line.split()
|
||
natoms = int(entries[0])
|
||
|
||
symbol, mass, sol = elements()
|
||
abu = dict()
|
||
|
||
line = f.readline()
|
||
while line[0] == '*':
|
||
line = f.readline()
|
||
for i in range(natoms):
|
||
entries = line.split()
|
||
#abu.append( float(entries[1]) )
|
||
abu[symbol[i]] = float(entries[1])
|
||
line = f.readline()
|
||
|
||
if i < 98:
|
||
for j in range(98-i):
|
||
#abu.append(1e-111)
|
||
abu[symbol[i+1]] = 1e-111
|
||
i = i + 1
|
||
|
||
f.close()
|
||
|
||
return(teff, logg, vmicro, abu)
|
||
|
||
def read_tlusty_extras(modelfile, startdir=None):
|
||
"""
|
||
识别并读取Tlusty模型的非标准参数文件及其内容,确定模型参数数量、LTE状态以及模型原子数据目录路径
|
||
参数:
|
||
-----------
|
||
modelfile: str
|
||
模型文件名(.8、.7或.22格式)。代码会寻找对应的.5文件以读取丰度等信息
|
||
startdir: str
|
||
计算初始目录。若使用相对路径,代码在此目录下查找模型原子目录和非标准参数文件
|
||
(默认为当前工作目录)
|
||
返回:
|
||
-------
|
||
madaffile: str
|
||
Tlusty模型原子数据和丰度文件(.5格式)
|
||
nonstdfile: str
|
||
非标准参数文件路径
|
||
nonstd: dict
|
||
非标准参数文件内容字典
|
||
numpar: int
|
||
参数数量(负数表示包含数密度)
|
||
datadir: str
|
||
模型原子数据目录路径
|
||
inlte: int
|
||
0:由synspec内部计算(LTE)
|
||
1:模型包含粒子数
|
||
-1:模型包含偏离系数
|
||
atommode: list
|
||
各原子的模式列表:
|
||
0=未考虑
|
||
1=隐式(无连续谱贡献)
|
||
2=显式(参考Synspec手册)
|
||
4=半显式(参考Synspec手册)
|
||
5=准显式(参考Synspec手册)
|
||
atominfo: list
|
||
模型原子信息的所有行内容
|
||
"""
|
||
# 验证模型文件扩展名
|
||
assert modelfile.endswith(('.8', '.7', '.22')), 'Tlusty模型文件应以.7、.8或.22结尾'
|
||
|
||
# 根据扩展名生成对应的.5文件路径
|
||
if modelfile[-2] == '.':
|
||
madaffile = modelfile[:-1] + "5"
|
||
print(madaffile)
|
||
else:
|
||
madaffile = modelfile[:-2] + "5"
|
||
assert os.path.isfile(madaffile), f'缺少对应的Tlusty模型文件{madaffile}'
|
||
|
||
# 设置初始目录
|
||
if startdir is None:
|
||
startdir = os.getcwd()
|
||
|
||
# 读取.5文件内容
|
||
f = open(madaffile, 'r')
|
||
line = f.readline()
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
nonstdfile0 = entries[0][1:-1] # 提取非标准参数文件路径
|
||
|
||
# 处理非标准参数文件路径
|
||
nonstdfile = ''
|
||
if nonstdfile0 != '':
|
||
if not os.path.isabs(nonstdfile0):
|
||
# 尝试在初始目录查找
|
||
mf = os.path.join(startdir, nonstdfile0)
|
||
if os.path.isfile(mf):
|
||
nonstdfile = mf
|
||
else:
|
||
# 在模型目录查找
|
||
mf = os.path.join(modeldir, nonstdfile0)
|
||
nonstdfile = mf
|
||
else:
|
||
nonstdfile = nonstdfile0
|
||
assert os.path.exists(nonstdfile), f'未找到Tlusty模型指定的非标准参数文件{nonstdfile0}'
|
||
|
||
# 读取非标准参数文件内容
|
||
nonstd = {}
|
||
if nonstdfile != '':
|
||
with open(nonstdfile, 'r') as ns:
|
||
nonstdarr = ns.readlines()
|
||
for entry in nonstdarr:
|
||
entries = entry.replace(',\n', '').split(',')
|
||
for piece in entries:
|
||
key, val = piece.split('=')
|
||
nonstd[key.strip()] = val.strip()
|
||
|
||
# 读取原子模式信息
|
||
line = f.readline()
|
||
while line.startswith('*'):
|
||
line = f.readline()
|
||
line = f.readline()
|
||
while line.startswith('*'):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
natoms = int(entries[0])
|
||
|
||
line = f.readline()
|
||
while line.startswith('*'):
|
||
line = f.readline()
|
||
atommode = []
|
||
for _ in range(natoms):
|
||
entries = line.split()
|
||
atommode.append(int(entries[0]))
|
||
line = f.readline()
|
||
|
||
# 读取数据目录路径
|
||
atominfo = []
|
||
while True:
|
||
atominfo.append(line)
|
||
if '.dat' in line:
|
||
break
|
||
line = f.readline()
|
||
entries = line.split()
|
||
cadena = entries[-1][1:-1]
|
||
datadir0 = os.path.dirname(cadena)
|
||
datadir = ''
|
||
if datadir0 != '':
|
||
if not os.path.isabs(datadir0):
|
||
# 尝试在初始目录查找
|
||
mf = os.path.join(startdir, datadir0)
|
||
if os.path.exists(mf):
|
||
datadir = mf
|
||
else:
|
||
# 在synple数据目录查找
|
||
mf = os.path.join(synpledir, 'data', datadir0)
|
||
datadir = mf
|
||
else:
|
||
datadir = datadir0
|
||
assert os.path.exists(datadir), f'未找到Tlusty模型指定的数据目录{datadir0}'
|
||
|
||
# 读取剩余原子信息
|
||
while True:
|
||
line = f.readline()
|
||
if not line:
|
||
break
|
||
atominfo.append(line)
|
||
f.close()
|
||
|
||
# 读取模型文件参数
|
||
with open(modelfile, 'r') as f:
|
||
line = f.readline()
|
||
entries = line.split()
|
||
nd = int(entries[0])
|
||
numpar = int(entries[1])
|
||
if abs(numpar) > 4:
|
||
inlte = 1
|
||
else:
|
||
inlte = 0
|
||
if modelfile.endswith('.22'):
|
||
inlte = -1
|
||
|
||
return (madaffile, nonstdfile, nonstd, numpar, datadir, inlte, atommode, atominfo)
|
||
|
||
def read_phoenix_model(modelfile):
|
||
|
||
"""Reads a FITS Phoenix model atmospheres
|
||
|
||
Parameters
|
||
----------
|
||
modelfile: str
|
||
file name
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s)
|
||
abu : list
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es)
|
||
nd: int
|
||
number of depths (layers) of the model
|
||
atmos: numpy structured array
|
||
array with the run with depth of column mass, temperature, gas pressure
|
||
and electron density
|
||
|
||
"""
|
||
|
||
from astropy.io import fits
|
||
|
||
h = fits.open(modelfile)[0].header
|
||
f = fits.open(modelfile)[1].data
|
||
|
||
nd = len(f['temp'])
|
||
|
||
teff = float(h['PHXTEFF'])
|
||
logg = float(h['PHXLOGG'])
|
||
vmicro = float(h['PHXXI_L'])
|
||
|
||
m_h = float(h['PHXM_H'])
|
||
alpha = float(h['PHXALPHA'])
|
||
|
||
symbol, mass,sol = elements(reference='husser')
|
||
abu = sol
|
||
z_metals = np.arange(97,dtype=int) + 3
|
||
z_alphas = np.array([8,10,12,14,16,20,22],dtype=int)
|
||
for i in range(len(z_metals)): abu[z_metals[i] - 1] = abu[z_metals[i] - 1] + m_h
|
||
for i in range(len(z_alphas)): abu[z_alphas[i] - 1] = abu[z_alphas[i] - 1] + alpha
|
||
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'p','ne'),
|
||
'formats':('f', 'f', 'f','f')})
|
||
|
||
atmos['dm'] = f['pgas'] / 10.**logg
|
||
atmos['t'] = f['temp']
|
||
atmos['p'] = f['pgas']
|
||
atmos['ne'] = f['pe']/ bolk / f['temp']
|
||
|
||
return (teff,logg,vmicro,abu,nd,atmos)
|
||
|
||
|
||
def read_phoenix_text_model(modelfile):
|
||
|
||
|
||
"""Reads a plain-text Phoenix model atmospheres
|
||
|
||
Parameters
|
||
----------
|
||
modelfile: str
|
||
file name
|
||
|
||
Returns
|
||
-------
|
||
|
||
teff : float
|
||
effective temperature (K)
|
||
logg : float
|
||
log10 of the surface gravity (cm s-2)
|
||
vmicro : float
|
||
microturbulence velocity (km/s)
|
||
abu : list
|
||
abundances, number densities of nuclei relative to hydrogen N(X)/N(H)
|
||
for elements Z=1,99 (H to Es)
|
||
nd: int
|
||
number of depths (layers) of the model
|
||
atmos: numpy structured array
|
||
array with the run with depth of column mass, temperature, gas pressure
|
||
and electron density
|
||
|
||
"""
|
||
|
||
|
||
f = open(modelfile,'r')
|
||
line = f.readline()
|
||
while line[0:4] != " no.":
|
||
line = f.readline()
|
||
entries = line.split()
|
||
nd = int(entries[5])
|
||
print('nd=',nd)
|
||
while line[0:14] != " model: teff":
|
||
line = f.readline()
|
||
entries = line.split()
|
||
teff = float(entries[3])
|
||
print('teff=',teff)
|
||
line = f.readline()
|
||
line = f.readline()
|
||
entries = line.split()
|
||
assert (entries[0] == 'log(g):' and entries[2] == '[cm/s**2]'), 'Cannot find logg in the file header'
|
||
logg = float(entries[1])
|
||
print('logg=',logg)
|
||
line = f.readline()
|
||
while line[0:22] != " Element abundances :":
|
||
line = f.readline()
|
||
|
||
|
||
symbol,mass,sol = elements()
|
||
|
||
sy = []
|
||
ab = []
|
||
|
||
while line[0:29] != " Element abundances relative":
|
||
line = f.readline()
|
||
#print(line)
|
||
if line[0:9] == ' element:':
|
||
entries = line.split()
|
||
for word in entries[1:]: sy.append(word)
|
||
if line[0:11] == ' abundance:':
|
||
entries = line.split()
|
||
for word in entries[1:]: ab.append(word)
|
||
|
||
assert (len(sy) == len(ab)), 'different elements in arrays sy (elemental symbols) and ab (abundances)'
|
||
|
||
abu = np.ones(99)*1e-99
|
||
i = 0
|
||
for item in sy:
|
||
try:
|
||
index = symbol.index(item)
|
||
abu[index] = 10.**(float(ab[i])-12.)
|
||
except ValueError:
|
||
print("the symbol ",item," is not recognized as a valid element")
|
||
i = i + 1
|
||
|
||
print('abu=',abu)
|
||
|
||
while line[0:72] != " l tstd temperature pgas pe density mu":
|
||
line = f.readline()
|
||
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t = [ float(entries[2].replace('D','E')) ]
|
||
p = [ float(entries[3].replace('D','E')) ]
|
||
ne = [ float(entries[4].replace('D','E')) / bolk / float(entries[2].replace('D','E')) ]
|
||
dm = [ float(entries[3].replace('D','E')) / 10.**logg ] #assuming hydrostatic equil. and negliglible radiation and turb. pressure
|
||
|
||
for i in range(nd-1):
|
||
line = f.readline()
|
||
entries = line.split()
|
||
|
||
t.append( float(entries[2].replace('D','E')))
|
||
p.append( float(entries[3].replace('D','E')))
|
||
ne.append( float(entries[4].replace('D','E')) / bolk / float(entries[2]))
|
||
dm.append ( float(entries[3].replace('D','E')) / 10.**logg )
|
||
|
||
vmicro = 1.0
|
||
while (line[0:6] != " greli"):
|
||
line = f.readline()
|
||
if line == '':
|
||
print('Cannot find a value for vmicro (vturb) in the model atmosphere file ',modelfile)
|
||
break
|
||
|
||
if line != '':
|
||
entries = line.split()
|
||
vmicro = float(entries[5])
|
||
|
||
atmos = np.zeros(nd, dtype={'names':('dm', 't', 'p','ne'),
|
||
'formats':('f', 'f', 'f','f')})
|
||
atmos['dm'] = dm
|
||
atmos['t'] = t
|
||
atmos['p'] = p
|
||
atmos['ne'] = ne
|
||
|
||
return (teff,logg,vmicro,abu,nd,atmos)
|
||
|
||
|
||
def read_multiline_fltarray(fhandle,arrlen):
|
||
|
||
"""Reads a float array that spans one or multiple lines in a file
|
||
"""
|
||
|
||
ndata = 0
|
||
arr = []
|
||
while ndata < arrlen:
|
||
line = fhandle.readline()
|
||
line = line.replace('D','E')
|
||
line = line.replace('d','e')
|
||
entries = line.split()
|
||
for val in entries: arr.append( float(val) )
|
||
ndata = len(arr)
|
||
|
||
return (arr)
|
||
|
||
def read_opt(filename):
|
||
"""Reads a synspec/tlusty opacity table (see routine polyopt)
|
||
|
||
Parameters
|
||
----------
|
||
filename: string
|
||
name of the file containing the opacity table
|
||
|
||
|
||
Returns
|
||
-------
|
||
lt: float array
|
||
log10(T) for opacity grid (T in K)
|
||
lrho: float array
|
||
log10(rho) for opacity grid (rho in gr/cm3)
|
||
lambda0: numpy float array
|
||
vacuum wavelength (Angstroms)
|
||
lopa: numpy float array
|
||
log10(opacity/rho), where opacity is in cm-1, and opacity/rho in cm2/gr
|
||
abu_eos: array of floats (99 elements)
|
||
chemical abundances relative to hydrogen (N(X)/N(H)) adopted for the
|
||
equation of state (elements not explicit in the table header are assigned
|
||
zero abundance)
|
||
abu_opa: array of floats (99 elements)
|
||
chemical abundances relative to hydrogen (N(X)/N(H)) adopted for the
|
||
opacities in the table (elements not explicit in the table header are assigned
|
||
zero abundance)
|
||
|
||
"""
|
||
|
||
symbol, mass, sol = elements()
|
||
abu_eos = np.zeros(99)
|
||
abu_opa = np.zeros(99)
|
||
|
||
f = open(filename,'r')
|
||
entry = f.readline()
|
||
entry = f.readline()
|
||
entry = f.readline()
|
||
while entry != "\n":
|
||
#print(entry)
|
||
entries = entry.split()
|
||
try:
|
||
index = symbol.index(entries[0])
|
||
abu_eos[index] = float(entries[1])
|
||
abu_opa[index] = float(entries[2])
|
||
except ValueError:
|
||
print("the line ",entry," is not recognized as describing the abundance of an element")
|
||
entry = f.readline()
|
||
|
||
while entry[:13] != "number of fre":
|
||
entry = f.readline()
|
||
|
||
entry = f.readline()
|
||
entries = entry.split()
|
||
nlambda = np.int64(entries[0])
|
||
lambda0 = np.zeros(nlambda, dtype = float)
|
||
nt = int(entries[1])
|
||
nrho = int(entries[2])
|
||
lopa = np.zeros( (nlambda,nrho,nt), dtype=float)
|
||
print('nrho, nt, nlambda = ', nrho, nt, nlambda)
|
||
|
||
|
||
while entry[:13] != "log temperatu":
|
||
entry = f.readline()
|
||
|
||
lt = read_multiline_fltarray(f,nt)
|
||
|
||
while entry[:13] != "log densities":
|
||
entry = f.readline()
|
||
|
||
lrho = read_multiline_fltarray(f,nrho)
|
||
|
||
entry = f.readline()
|
||
i = 0
|
||
while entry != "":
|
||
entry = f.readline()
|
||
if entry[1:13] == "*** frequenc":
|
||
entries = entry.split()
|
||
assert (int(entries[-2]) == i+1), 'The frequencies do not appear in order: '+entries[-2]+' != '+str(i+1)
|
||
lambda0[i] = float(entries[-1])
|
||
entry = f.readline()
|
||
arr = read_multiline_fltarray(f,nt*nrho)
|
||
lopa[i,:,:] = np.reshape(arr, (nrho,nt))
|
||
i += 1
|
||
|
||
return(lrho,lt,lambda0,lopa,abu_eos,abu_opa)
|
||
|
||
def read_copt(filename,nrho,nt):
|
||
"""Reads a synspec/tlusty continuum opacity table (fort.26)
|
||
|
||
Parameters
|
||
----------
|
||
filename: string
|
||
name of the file containing the opacity table,
|
||
this text file only contains opacity and the information
|
||
about the densities/temperatures/abundances needs to
|
||
be extracted from its associated full-opacity table
|
||
|
||
nrho: int
|
||
number of density points in the table
|
||
nt: int
|
||
number of temperature points in the table
|
||
|
||
Returns
|
||
-------
|
||
wave: 3D numpy float array (nwave, nrho, nt)
|
||
wavelenght in Angstroms
|
||
|
||
lopa: 3D numpy float array (nwave, nrho, nt)
|
||
log10(opacity/rho), where opacity is in cm-1, and opacity/rho in cm2/gr
|
||
"""
|
||
|
||
c = np.loadtxt(filename)
|
||
wave = np.transpose( np.reshape(c[:,0], (nt,nrho,int(c.shape[0]/nt/nrho)) ) )
|
||
lopa = np.transpose( np.reshape(c[:,1], (nt,nrho,int(c.shape[0]/nt/nrho)) ) )
|
||
|
||
return(wave,lopa)
|
||
|
||
|
||
def interp_spl(xout, x, y):
|
||
|
||
"""Interpolates in 1D using cubic splines
|
||
|
||
Parameters
|
||
----------
|
||
x: numpy array or list
|
||
input abscissae
|
||
y: numpy array or list
|
||
input ordinates
|
||
xout: numpy array or list
|
||
array of abscissae to interpolate to
|
||
|
||
Returns
|
||
-------
|
||
yout: numpy array or list
|
||
array of interpolated values
|
||
|
||
"""
|
||
|
||
tck = interpolate.splrep(x, y, s=0)
|
||
yout = interpolate.splev(xout, tck, der=0)
|
||
|
||
return(yout)
|
||
|
||
|
||
|
||
def interp_spl2(x0, x, y):
|
||
"""
|
||
Interpolate a 1-D function using cubic splines.
|
||
x0 : a float or an 1d-array
|
||
x : (N,) array_like
|
||
A 1-D array of real/complex values.
|
||
y : (N,) array_like
|
||
A 1-D array of real values. The length of y along the
|
||
interpolation axis must be equal to the length of x.
|
||
|
||
Implement a trick to generate at first step the cholesky matrice L of
|
||
the tridiagonal matrice A (thus L is a bidiagonal matrice that
|
||
can be solved in two distinct loops).
|
||
|
||
additional ref: www.math.uh.edu/~jingqiu/math4364/spline.pdf
|
||
|
||
code from
|
||
https://stackoverflow.com/questions/31543775/how-to-perform-cubic-spline-interpolation-in-python
|
||
"""
|
||
x = np.asfarray(x)
|
||
y = np.asfarray(y)
|
||
|
||
# remove non finite values
|
||
# indexes = np.isfinite(x)
|
||
# x = x[indexes]
|
||
# y = y[indexes]
|
||
|
||
# check if sorted
|
||
if np.any(np.diff(x) < 0):
|
||
indexes = np.argsort(x)
|
||
x = x[indexes]
|
||
y = y[indexes]
|
||
|
||
size = len(x)
|
||
|
||
xdiff = np.diff(x)
|
||
ydiff = np.diff(y)
|
||
|
||
# allocate buffer matrices
|
||
Li = np.empty(size)
|
||
Li_1 = np.empty(size-1)
|
||
z = np.empty(size)
|
||
|
||
# fill diagonals Li and Li-1 and solve [L][y] = [B]
|
||
Li[0] = np.sqrt(2*xdiff[0])
|
||
Li_1[0] = 0.0
|
||
B0 = 0.0 # natural boundary
|
||
z[0] = B0 / Li[0]
|
||
|
||
for i in range(1, size-1, 1):
|
||
Li_1[i] = xdiff[i-1] / Li[i-1]
|
||
Li[i] = np.sqrt(2*(xdiff[i-1]+xdiff[i]) - Li_1[i-1] * Li_1[i-1])
|
||
Bi = 6*(ydiff[i]/xdiff[i] - ydiff[i-1]/xdiff[i-1])
|
||
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
|
||
|
||
i = size - 1
|
||
Li_1[i-1] = xdiff[-1] / Li[i-1]
|
||
Li[i] = np.sqrt(2*xdiff[-1] - Li_1[i-1] * Li_1[i-1])
|
||
Bi = 0.0 # natural boundary
|
||
z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i]
|
||
|
||
# solve [L.T][x] = [y]
|
||
i = size-1
|
||
z[i] = z[i] / Li[i]
|
||
for i in range(size-2, -1, -1):
|
||
z[i] = (z[i] - Li_1[i-1]*z[i+1])/Li[i]
|
||
|
||
# find index
|
||
index = x.searchsorted(x0)
|
||
np.clip(index, 1, size-1, index)
|
||
|
||
xi1, xi0 = x[index], x[index-1]
|
||
yi1, yi0 = y[index], y[index-1]
|
||
zi1, zi0 = z[index], z[index-1]
|
||
hi1 = xi1 - xi0
|
||
|
||
# calculate cubic
|
||
f0 = zi0/(6*hi1)*(xi1-x0)**3 + \
|
||
zi1/(6*hi1)*(x0-xi0)**3 + \
|
||
(yi1/hi1 - zi1*hi1/6)*(x0-xi0) + \
|
||
(yi0/hi1 - zi0*hi1/6)*(xi1-x0)
|
||
return f0
|
||
|
||
|
||
|
||
def elements(reference=None):
|
||
|
||
|
||
"""Reads the solar elemental abundances
|
||
|
||
Parameters
|
||
----------
|
||
reference: string, optional
|
||
set to 'husser; for the abundances adopted for Phoenix models by Huser et al. (2013),
|
||
set to 'basti' for abundances adopted for the BaSTI stellar models (Hidalgo et al. 2018),
|
||
set to 'ags2005' for Asplund et al. (2005) are used -- consistent with
|
||
the MARCS (Gustafsson et al. 2008) models and and Kurucz (Meszaros et al. 2012)
|
||
Kurucz model atmospheres.
|
||
(default, reference=None, will trigger 'ags2005')
|
||
|
||
Returns
|
||
-------
|
||
symbol: numpy array of str
|
||
element symbols
|
||
mass: numpy array of floats
|
||
atomic masses (elements Z=1-99)
|
||
sol: numpy array of floats
|
||
solar abundances N/N(H)
|
||
|
||
"""
|
||
|
||
symbol = [
|
||
'H' ,'He','Li','Be','B' ,'C' ,'N' ,'O' ,'F' ,'Ne',
|
||
'Na','Mg','Al','Si','P' ,'S' ,'Cl','Ar','K' ,'Ca',
|
||
'Sc','Ti','V' ,'Cr','Mn','Fe','Co','Ni','Cu','Zn',
|
||
'Ga','Ge','As','Se','Br','Kr','Rb','Sr','Y' ,'Zr',
|
||
'Nb','Mo','Tc','Ru','Rh','Pd','Ag','Cd','In','Sn',
|
||
'Sb','Te','I' ,'Xe','Cs','Ba','La','Ce','Pr','Nd',
|
||
'Pm','Sm','Eu','Gd','Tb','Dy','Ho','Er','Tm','Yb',
|
||
'Lu','Hf','Ta','W' ,'Re','Os','Ir','Pt','Au','Hg',
|
||
'Tl','Pb','Bi','Po','At','Rn','Fr','Ra','Ac','Th',
|
||
'Pa','U' ,'Np','Pu','Am','Cm','Bk','Cf','Es' ]
|
||
|
||
mass = [ 1.00794, 4.00260, 6.941, 9.01218, 10.811, 12.0107, 14.00674, 15.9994,
|
||
18.99840, 20.1797, 22.98977, 24.3050, 26.98154, 28.0855, 30.97376,
|
||
32.066, 35.4527, 39.948, 39.0983, 40.078, 44.95591, 47.867, 50.9415,
|
||
51.9961, 54.93805, 55.845, 58.93320, 58.6934, 63.546, 65.39, 69.723,
|
||
72.61, 74.92160, 78.96, 79.904, 83.80, 85.4678, 87.62, 88.90585,
|
||
91.224, 92.90638, 95.94, 98., 101.07, 102.90550, 106.42, 107.8682,
|
||
112.411, 114.818, 118.710, 121.760, 127.60, 126.90447, 131.29,
|
||
132.90545, 137.327, 138.9055, 140.116, 140.90765, 144.24, 145, 150.36,
|
||
151.964, 157.25, 158.92534, 162.50, 164.93032, 167.26, 168.93421,
|
||
173.04, 174.967, 178.49, 180.9479, 183.84, 186.207, 190.23, 192.217,
|
||
195.078, 196.96655, 200.59, 204.3833, 207.2, 208.98038, 209., 210.,
|
||
222., 223., 226., 227., 232.0381, 231.03588, 238.0289, 237., 244.,
|
||
243., 247., 247., 251., 252. ]
|
||
|
||
if reference == 'husser':
|
||
#a combination of meteoritic/photospheric abundances from Asplund et al. 2009
|
||
#chosen for the Husser et al. (2013) Phoenix model atmospheres
|
||
sol = [ 12.00, 10.93, 3.26, 1.38, 2.79, 8.43, 7.83, 8.69, 4.56, 7.93,
|
||
6.24, 7.60, 6.45, 7.51, 5.41, 7.12, 5.50, 6.40, 5.08, 6.34,
|
||
3.15, 4.95, 3.93, 5.64, 5.43, 7.50, 4.99, 6.22, 4.19, 4.56,
|
||
3.04, 3.65, 2.30, 3.34, 2.54, 3.25, 2.36, 2.87, 2.21, 2.58,
|
||
1.46, 1.88, -9.99, 1.75, 1.06, 1.65, 1.20, 1.71, 0.76, 2.04,
|
||
1.01, 2.18, 1.55, 2.24, 1.08, 2.18, 1.10, 1.58, 0.72, 1.42,
|
||
-9.99, 0.96, 0.52, 1.07, 0.30, 1.10, 0.48, 0.92, 0.10, 0.92,
|
||
0.10, 0.85, -0.12, 0.65, 0.26, 1.40, 1.38, 1.62, 0.80, 1.17,
|
||
0.77, 2.04, 0.65, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, 0.06,
|
||
-9.99, -0.54, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99 ]
|
||
|
||
|
||
elif reference == 'basti':
|
||
#recommended solar abundances from Lodders (2011) https://ui.adsabs.harvard.edu/abs/2010ASSP...16..379L/abstract
|
||
#except for C, N, O, P, S, K, and Fe for which values are from Caffau et al. (2011)
|
||
#https://ui.adsabs.harvard.edu/abs/2011SoPh..268..255C/abstract
|
||
sol = [ 12.00, 10.925, 3.28, 1.32, 2.81, 8.50, 7.86, 8.76, 4.44, 8.05,
|
||
6.29, 7.54, 6.46, 7.53, 5.46, 7.16, 5.25, 6.50, 5.11, 6.31,
|
||
3.07, 4.93, 3.99, 5.65, 5.50, 7.52, 4.90, 6.22, 4.27, 4.65,
|
||
3.10, 3.59, 2.32, 3.36, 2.56, 3.28, 2.38, 2.90, 2.20, 2.57,
|
||
1.42, 1.94, -9.99, 1.78, 1.10, 1.67, 1.22, 1.73, 0.78, 2.09,
|
||
1.03, 2.20, 1.57, 2.27, 1.10, 2.18, 1.19, 1.60, 0.77, 1.47,
|
||
-9.99, 0.96, 0.53, 1.09, 0.34, 1.14, 0.49, 0.95, 0.14, 0.94,
|
||
0.11, 0.73, -0.14, 0.67, 0.28, 1.37, 1.36, 1.64, 0.82, 1.19,
|
||
0.79, 2.06, 0.67, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, 0.08,
|
||
-9.99, -0.52, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99]
|
||
|
||
elif (reference is None or reference == 'ags2005'):
|
||
#Asplund, Grevesse and Sauval (2005), basically the same as
|
||
#Grevesse N., Asplund M., Sauval A.J. 2007, Space Science Review 130, 205
|
||
sol = [ 0.911, 10.93, 1.05, 1.38, 2.70, 8.39, 7.78, 8.66, 4.56, 7.84,
|
||
6.17, 7.53, 6.37, 7.51, 5.36, 7.14, 5.50, 6.18, 5.08, 6.31,
|
||
3.05, 4.90, 4.00, 5.64, 5.39, 7.45, 4.92, 6.23, 4.21, 4.60,
|
||
2.88, 3.58, 2.29, 3.33, 2.56, 3.28, 2.60, 2.92, 2.21, 2.59,
|
||
1.42, 1.92, -9.99, 1.84, 1.12, 1.69, 0.94, 1.77, 1.60, 2.00,
|
||
1.00, 2.19, 1.51, 2.27, 1.07, 2.17, 1.13, 1.58, 0.71, 1.45,
|
||
-9.99, 1.01, 0.52, 1.12, 0.28, 1.14, 0.51, 0.93, 0.00, 1.08,
|
||
0.06, 0.88, -0.17, 1.11, 0.23, 1.45, 1.38, 1.64, 1.01, 1.13,
|
||
0.90, 2.00, 0.65, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, 0.06,
|
||
-9.99, -0.52, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99, -9.99 ]
|
||
|
||
else:
|
||
print('NOT a valid reference for the solar composition (ags2005, husser, basti)')
|
||
nans = np.empty((99,))
|
||
nans[:] = np.nan
|
||
return (symbol, mass, nans )
|
||
|
||
sol[0] = 1.0
|
||
for i in range(len(sol)-1): sol[i+1] = 10.**(sol[i+1]-12.0)
|
||
|
||
return (symbol,mass,sol)
|
||
|
||
def xyzmass(abu):
|
||
|
||
"""computing the mass fractions for hydrogen, helium and metals X,Y,Z
|
||
"""
|
||
|
||
symbol, mass, sol = elements()
|
||
|
||
total = sum(np.array(mass)*np.array(abu))
|
||
|
||
x = mass[0]*abu[0]/total
|
||
y = mass[1]*abu[1]/total
|
||
z = np.sum(np.array(mass[2:])*np.array(abu[2:])/total)
|
||
|
||
return(x,y,z)
|
||
|
||
|
||
def keepz(abu,zs,dex):
|
||
|
||
"""finds out the array of abundances that keeps the abundance changes in
|
||
the array dex for the elements with the atomic number zs
|
||
and at the same time keeps the metal mass fraction
|
||
constant by modifying the metal abundances
|
||
"""
|
||
|
||
assert (len(zs) == len(dex)),'zs and dex must have the same number of elements'
|
||
|
||
abu1 = abu.copy()
|
||
i = 0
|
||
for entry in zs:
|
||
abu1[entry-1] = abu[entry-1] + dex[i]
|
||
i = i + 1
|
||
|
||
x0, y0, z0 = xyzmass(abu)
|
||
x1, y1, z1 = xyzmass(abu1)
|
||
print(z0,z1)
|
||
diff = np.array(abu1[zs]) - np.array(abu[zs])
|
||
zr = np.abs(z1/z0)
|
||
abu1[2:] = abu[2:]/zr
|
||
diff1 = np.array(abu1[zs]) - np.array(abu[zs])
|
||
|
||
while np.max(diff1 - diff) > 0.0001:
|
||
print(np.max(diff1 - diff))
|
||
x1, y1, z1 = xyzmass(abu1)
|
||
zr = np.abs(z1/z0)
|
||
abu1[2:] = abu[2:]/zr
|
||
diff1 = np.array(abu1) - np.array(abu)
|
||
|
||
return()
|
||
|
||
|
||
|
||
|
||
|
||
def lgconv(xinput, yinput, fwhm, ppr=None):
|
||
|
||
"""convolution with a Gaussian in linear lambda scale
|
||
for a constant resolution
|
||
|
||
Parameters
|
||
----------
|
||
xinput: numpy float array
|
||
wavelengths
|
||
yinput: numpy array of floats
|
||
fluxes
|
||
fwhm: float
|
||
FWHM of the Gaussian (same units as for xinput)
|
||
ppr: float, optional
|
||
Points per resolution element to downsample the convolved spectrum
|
||
(default None, to keep the original sampling)
|
||
|
||
Returns
|
||
-------
|
||
x: numpy float array
|
||
wavelengths after convolution, will be a subset of xinput when that is linear,
|
||
otherwise a subset of the linearly resampled version
|
||
y: numpy array of floats
|
||
fluxes after convolution
|
||
|
||
"""
|
||
|
||
#resampling to a linear lambda wavelength scale if need be
|
||
xx = np.diff(xinput)
|
||
if np.max(xx) - np.min(xx) > 1.e-7: #input not linearly sampled
|
||
nel = len(xinput)
|
||
minx = np.min(xinput)
|
||
maxx = np.max(xinput)
|
||
x = np.linspace(minx,maxx,nel)
|
||
y = np.interp( x, xinput, yinput)
|
||
#y = interp_spl( x, xinput, yinput)
|
||
else: #input linearly sampled
|
||
x = xinput
|
||
y = yinput
|
||
|
||
step = x[1] - x[0]
|
||
|
||
assert (fwhm > 2*step), 'cannot convolve since fwhm is <= 2*step'
|
||
|
||
sigma=fwhm/2.0/np.sqrt(-2.0*np.log(0.5))
|
||
npoints = 2*int(3*fwhm/2./step)+1
|
||
half = npoints * step /2.
|
||
xx = np.linspace(-half,half,npoints)
|
||
kernel = np.exp(-(xx-np.mean(xx))**2/2./sigma**2)
|
||
kernel = kernel/np.sum(kernel)
|
||
|
||
y = np.convolve(y,kernel,'valid')
|
||
#y = ss.fftconvolve(y,kernel,'valid')
|
||
print(npoints)
|
||
edge = int(npoints/2)
|
||
x = x[edge:-edge]
|
||
|
||
print(xinput.size,x.size,y.size)
|
||
|
||
if ppr != None:
|
||
fac = int(fwhm / step / ppr)
|
||
assert (fac != 0),'cannot resample since fac is = 0, increase fwhm or reduce ppr'
|
||
subset = np.arange(x.size / fac, dtype=int) * fac
|
||
x = x[subset]
|
||
y = y[subset]
|
||
|
||
return(x,y)
|
||
|
||
def vgconv(xinput,yinput,fwhm, ppr=None):
|
||
|
||
|
||
"""convolution with a Gaussian in log lambda scale
|
||
for a constant resolving power
|
||
|
||
Parameters
|
||
----------
|
||
xinput: numpy float array
|
||
wavelengths
|
||
yinput: numpy array of floats
|
||
fluxes
|
||
fwhm: float
|
||
FWHM of the Gaussian (km/s)
|
||
ppr: float, optional
|
||
Points per resolution element to downsample the convolved spectrum
|
||
(default None, to keep the original sampling)
|
||
|
||
Returns
|
||
-------
|
||
x: numpy float array
|
||
wavelengths after convolution, will be a subset of xinput when that is equidistant
|
||
in log lambda, otherwise a subset of the resampled version
|
||
y: numpy array of floats
|
||
fluxes after convolution
|
||
|
||
"""
|
||
#resampling to ln(lambda) if need be
|
||
xx = np.diff(np.log(xinput))
|
||
if np.max(xx) - np.min(xx) > 1.e-7: #input not equidist in loglambda
|
||
nel = len(xinput)
|
||
minx = np.log(xinput[0])
|
||
maxx = np.log(xinput[-1])
|
||
x = np.linspace(minx,maxx,nel)
|
||
step = x[1] - x[0]
|
||
x = np.exp(x)
|
||
y = np.interp( x, xinput, yinput)
|
||
#y = interp_spl( x, xinput, yinput)
|
||
else:
|
||
x = xinput
|
||
y = yinput
|
||
step = np.log(xinput[1])-np.log(xinput[0])
|
||
|
||
fwhm = fwhm/clight # inverse of the resolving power
|
||
|
||
assert (fwhm > 2*step), 'cannot convolve since fwhm is <= 2*step'
|
||
|
||
sigma=fwhm/2.0/np.sqrt(-2.0*np.log(0.5))
|
||
npoints = 2*int(3*fwhm/2./step)+1
|
||
half = npoints * step /2.
|
||
xx = np.linspace(-half,half,npoints)
|
||
kernel = np.exp(-(xx-np.mean(xx))**2/2./sigma**2)
|
||
kernel = kernel/np.sum(kernel)
|
||
|
||
y = np.convolve(y,kernel,'valid')
|
||
edge = int(npoints/2)
|
||
x = x[edge:-edge]
|
||
|
||
#print(xinput.size,x.size,y.size)
|
||
|
||
if ppr != None:
|
||
fac = int(fwhm / step / ppr)
|
||
assert (fac != 0),'cannot resample since fac is = 0, increase fwhm or reduce ppr'
|
||
print(fwhm,step,ppr,fac)
|
||
subset = np.arange(x.size / fac, dtype=int) * fac
|
||
x = x[subset]
|
||
y = y[subset]
|
||
|
||
|
||
return(x,y)
|
||
|
||
def rotconv(xinput,yinput,vsini, ppr=None):
|
||
|
||
|
||
"""convolution with a Rotation profile
|
||
|
||
Parameters
|
||
----------
|
||
xinput: numpy float array
|
||
wavelengths
|
||
yinput: numpy array of floats
|
||
fluxes
|
||
vsini: float
|
||
projected rotational velocity (km/s)
|
||
ppr: float, optional
|
||
Points per resolution element to downsample the convolved spectrum
|
||
(default None, to keep the original sampling)
|
||
|
||
Returns
|
||
-------
|
||
x: numpy float array
|
||
wavelengths after convolution, will be a subset of xinput when that is equidistant
|
||
in log lambda, otherwise a subset of the resampled version
|
||
y: numpy array of floats
|
||
fluxes after convolution
|
||
|
||
"""
|
||
|
||
#resampling to ln(lambda) if need be
|
||
xx = np.diff(np.log(xinput))
|
||
if np.max(xx) - np.min(xx) > 1.e-7: #input not equidist in loglambda
|
||
nel = len(xinput)
|
||
minx = np.min(np.log(xinput))
|
||
maxx = np.max(np.log(xinput))
|
||
x = np.linspace(minx,maxx,nel)
|
||
step = x[1] - x[0]
|
||
x = np.exp(x)
|
||
y = np.interp( x, xinput, yinput)
|
||
#y = interp_spl( x, xinput, yinput)
|
||
else:
|
||
x = xinput
|
||
y = yinput
|
||
|
||
deltamax=vsini/clight
|
||
|
||
assert (deltamax > 2*step), 'cannot convolve since vsini is <= 2*step'
|
||
|
||
npoints = 2*int(deltamax/step)+1
|
||
xx = np.linspace(-deltamax,deltamax,npoints)
|
||
c1=2.0*(1.0-epsilon)/np.pi/(1.0-epsilon/3.0)/deltamax
|
||
c2=0.5*epsilon/(1.0-epsilon/3.0)/deltamax
|
||
r2=(xx/deltamax)**2
|
||
kernel = c1*np.sqrt(1.0-r2)+c2*(1.0-r2)
|
||
kernel = kernel/np.sum(kernel)
|
||
|
||
|
||
y = np.convolve(y,kernel,'valid')
|
||
print(xinput.size,x.size,y.size)
|
||
edge = int(npoints/2)
|
||
x = x[edge:-edge]
|
||
|
||
if ppr != None:
|
||
fac = int(deltamax / step / ppr)
|
||
assert (fac != 0),'cannot resample since fac is = 0, reduce fwhm or ppr'
|
||
subset = np.arange(x.size / fac, dtype=int) * fac
|
||
x = x[subset]
|
||
y = y[subset]
|
||
|
||
return(x,y)
|
||
|
||
def smooth(x,n):
|
||
|
||
"""Smooth using a Svitzky-Golay cubic filter
|
||
|
||
|
||
Parameters
|
||
----------
|
||
x: arr
|
||
input array to smooth
|
||
n: int
|
||
window size
|
||
"""
|
||
|
||
x2 = savgol_filter(x, n, 3)
|
||
|
||
return(x2)
|
||
|
||
|
||
def gsynth(synthfile,fwhm=0.0,units='km/s',ebv=0.0,r_v=3.1,rv=0.0,
|
||
outsynthfile=None,ppr=5,wrange=None,freeze=None):
|
||
|
||
"""Convolve and/or redden spectra in a FERRE grid
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
name of the input FERRE synth file
|
||
fwhm: float, can be an iterable
|
||
FWHM of the Gaussian kernel (in A or km/s) for convolution
|
||
(default 0.0, which means no convolution is performed)
|
||
units: str
|
||
units for the FWHM ('A' for a constant resolution in Angstroms,
|
||
'km/s' for a constant resolution in velocity
|
||
(default is 'km/s')
|
||
ebv: float or iterable with floats
|
||
E(B-V) to be applied to the model in the grid
|
||
(default is 0.0)
|
||
r_v: float or iterable with floats
|
||
ratio of total to V-band extinction A_V/E(B-V)
|
||
|
||
rv: float or iterable with floats
|
||
Radial velocity to the applied to the model in the grid
|
||
(default is 0.0)
|
||
outsynthfile: str
|
||
name of the output FERRE synth file
|
||
(default is the same as synth file, but starting with 'n')
|
||
ppr: float, optional
|
||
Points per resolution element to downsample the convolved spectrum
|
||
(default is 5, set to None to keep the original sampling)
|
||
wrange: tuple
|
||
Starting and ending wavelengths (if a smaller range that
|
||
the input's is desired)
|
||
(default None, to keep the original range)
|
||
freeze: dictionary
|
||
Allows to reduce the dimensionality of the grid. The keys are the labels
|
||
of the dimensions to freeze (as given in in the header of the input grid)
|
||
with the values that should be adopted for those 'frozen' dimensions.
|
||
Example: set freeze = {'TEFF': 5000.} to fix that value for the Teff dimension
|
||
in a grid.
|
||
(default None, to retain all the original dimensions)
|
||
Returns
|
||
-------
|
||
writes outsynthfile with the smooth spectra
|
||
|
||
"""
|
||
|
||
from synple import vgconv
|
||
import numpy as np
|
||
if np.max(ebv) > 0.0: from extinction import apply,ccm89
|
||
clight = 299792.458
|
||
|
||
if outsynthfile is None:
|
||
assert synthfile[0] != 'n', 'default output file name starts with n_. Given that the input starts with n too, please choose an anternative outsynthfile'
|
||
outsynthfile='n'+synthfile[1:]
|
||
logw=0
|
||
|
||
#read header, update and write out
|
||
fin = open(synthfile,'r')
|
||
fout = open(outsynthfile,'w')
|
||
hd = []
|
||
labels = []
|
||
type = "'regular'"
|
||
line = fin.readline()
|
||
hd.append(line)
|
||
while line[1] != "/":
|
||
line = fin.readline()
|
||
if "N_P" in line: n_p = np.array(line.split()[2:],dtype=int)
|
||
if "STEPS" in line: steps = np.array(line.split()[2:],dtype=float)
|
||
if "LLIMITS" in line: llimits = np.array(line.split()[2:],dtype=float)
|
||
if "LABEL" in line: labels.append(line.split()[-1][1:-1])
|
||
if "NPIX" in line: npix = int(line.split()[2])
|
||
if "N_OF_DIM" in line: ndim = int(line.split()[2])
|
||
if "WAVE" in line: wave = np.array(line.split()[2:],dtype=float)
|
||
if "LOGW" in line: logw = int(line.split()[2])
|
||
if "RESOLUTION" in line: resolution = float(line.split()[2])
|
||
if "TYPE" in line: type = str(line.split()[2])
|
||
if "NTOT" in line: ntot = int(line.split()[2])
|
||
hd.append(line)
|
||
|
||
if 'irregular' in type:
|
||
assert (len(labels) == ndim), 'The number of LABELS in the header does not agree with the dimension of the grid'
|
||
else:
|
||
assert (len(n_p) == len(steps) & len(n_p) == len(llimits) & len(n_p) == len(labels) & len(n_p) == ndim), 'The dimension of the parameters from the header are inconsistent'
|
||
|
||
assert (units == 'km/s' or units == 'A'), 'units must be either km/s or A'
|
||
|
||
#update header parameters
|
||
x = np.arange(npix)*wave[1]+wave[0]
|
||
if logw == 1: x=10.**x
|
||
if logw == 2: x=np.exp(x)
|
||
|
||
newcol = []
|
||
try:
|
||
nebv = len(ebv)
|
||
ebvs = ebv
|
||
print(ebvs)
|
||
print(ndim,labels)
|
||
#check they are uniformly spaced
|
||
debv = np.diff(ebvs)
|
||
print(np.max(debv),np.min(debv))
|
||
assert np.max(debv) - np.min(debv) < 1.e-7, 'ebv values are not linearly spaced!'
|
||
n_p = np.append(n_p,nebv)
|
||
steps = np.append(steps,ebvs[1]-ebvs[0])
|
||
llimits = np.append(llimits,ebvs[0])
|
||
labels.append('E(B-V)')
|
||
ndim = ndim + 1
|
||
newcol.append(ndim-1)
|
||
#update RESOLUTION?
|
||
print(ndim,labels)
|
||
except TypeError:
|
||
nebv = 1
|
||
ebvs = [ ebv ]
|
||
|
||
try:
|
||
nrv = len(rv)
|
||
rvs = rv
|
||
print(rvs)
|
||
print(ndim,labels)
|
||
#check they are uniformly spaced
|
||
drv = np.diff(rvs)
|
||
print(np.max(drv),np.min(drv))
|
||
assert np.max(drv) - np.min(drv) < 1.e-7, 'rv values are not linearly spaced!'
|
||
n_p = np.append(n_p,nrv)
|
||
steps = np.append(steps,rvs[1]-rvs[0])
|
||
llimits = np.append(llimits,rvs[0])
|
||
labels.append('RV')
|
||
ndim = ndim + 1
|
||
newcol.append(ndim-1)
|
||
print(ndim,labels)
|
||
except TypeError:
|
||
nrv = 1
|
||
ervs = [ rv ]
|
||
|
||
try:
|
||
nfwhm = len(fwhm)
|
||
fwhms = fwhm
|
||
print(fwhms)
|
||
print(ndim,labels)
|
||
#check they are uniformly spaced
|
||
dfwhm = np.diff(fwhms)
|
||
print(np.max(dfwhm),np.min(dfwhm))
|
||
assert np.max(dfwhm) - np.min(dfwhm) < 1.e-7, 'fwhm values are not linearly spaced!'
|
||
n_p = np.append(n_p,nfwhm)
|
||
steps = np.append(steps,fwhms[1]-fwhms[0])
|
||
llimits = np.append(llimits,fwhms[0])
|
||
labels.append('FWHM')
|
||
ndim = ndim + 1
|
||
newcol.append(ndim-1)
|
||
minfwhm=np.min(fwhms)
|
||
#update RESOLUTION?
|
||
print(ndim,labels)
|
||
except TypeError:
|
||
nfwhm = 1
|
||
fwhms = [ fwhm ]
|
||
|
||
|
||
#define indices for grid loops
|
||
if 'irregular' in type:
|
||
ind = range(ntot)
|
||
ind_n_p = list(range(ndim))
|
||
labels2 = list(labels)
|
||
else:
|
||
ll = []
|
||
ind_n_p = []
|
||
i = 0
|
||
print('labels=',labels)
|
||
labels2 = []
|
||
for entry in labels:
|
||
if freeze is not None:
|
||
lfkeys = list(freeze.keys())
|
||
if entry not in lfkeys:
|
||
ind_n_p.append(i)
|
||
labels2.append(entry)
|
||
else:
|
||
ind_n_p.append(i)
|
||
labels2.append(entry)
|
||
ll.append(np.arange(n_p[i]))
|
||
i = i + 1
|
||
ind = np.array(list(product(*ll)))
|
||
|
||
if wrange is not None:
|
||
assert (len(wrange) == 2), 'Error: wrange must have two elements'
|
||
section1 = np.where( (x >= wrange[0]*(1.-10.*np.max(fwhm)/clight)) & (x <= wrange[1]*(1.+10.*np.max(fwhm)/clight)) )
|
||
x = x[section1]
|
||
npix = len(x)
|
||
|
||
if np.min(fwhms) > 1.e-7:
|
||
y = np.ones(npix)
|
||
if units == 'km/s':
|
||
print('min(fwhm)=',np.min(fwhms))
|
||
xx,yy = vgconv(x,y,np.min(fwhms),ppr=ppr)
|
||
else:
|
||
xx,yy = lgconv(x,y,np.min(fwhms),ppr=ppr)
|
||
else:
|
||
print('Warning -- fwhm <= 1.e-7, no convolution will be performed, ppr will be ignored')
|
||
xx = x
|
||
|
||
|
||
if wrange is not None:
|
||
section2 = np.where( (xx >= wrange[0]) & (xx <= wrange[1]) )
|
||
xx = xx [section2]
|
||
|
||
#print(x,xx)
|
||
#print(len(x),len(xx))
|
||
#print(len(section1),len(section2))
|
||
|
||
jlabel = 0
|
||
for line in hd:
|
||
if "N_OF_DIM" in line: line = " N_OF_DIM = "+str(len(ind_n_p))+"\n"
|
||
if "N_P" in line: line = " N_P = "+' '.join(map(str,n_p[ind_n_p]))+"\n"
|
||
if "STEPS" in line: line = " STEPS = "+' '.join(map(str,steps[ind_n_p]))+"\n"
|
||
if "LLIMITS" in line: line = " LLIMITS = "+' '.join(map(str,llimits[ind_n_p]))+"\n"
|
||
if "LABEL" in line:
|
||
if jlabel == 0:
|
||
for entry in labels2:
|
||
jlabel = jlabel + 1
|
||
ilabel = "'"+entry+"'"
|
||
line = " LABEL("+str(jlabel)+") = "+ilabel+"\n"
|
||
print('line=',line)
|
||
fout.write(line)
|
||
continue
|
||
else:
|
||
continue
|
||
if "NPIX" in line: line = " NPIX = "+str(len(xx))+"\n"
|
||
if "WAVE" in line:
|
||
if units == 'km/s':
|
||
line = " WAVE = "+str(np.log10(xx[0]))+" "+str(np.log10(xx[1])-np.log10(xx[0]))+"\n"
|
||
else:
|
||
line = " WAVE = "+str(xx[0])+" "+str(xx[1]-xx[0])+"\n"
|
||
if "LOGW" in line:
|
||
if units == 'km/s':
|
||
line = " LOGW = 1 \n"
|
||
else:
|
||
line = " LOGW = 0 \n"
|
||
if "RESOLUTION" in line:
|
||
if units == 'km/s':
|
||
line = " RESOLUTION = "+str(clight/np.sqrt(clight**2/resolution**2 + np.min(fwhms)**2))+"\n"
|
||
else:
|
||
line = " RESOLUTION = "+str(np.mean(xx)/np.sqrt(np.mean(xx)**2/resolution**2 + np.min(fwhms)**2))+"\n"
|
||
if line[1] != "/": fout.write(line)
|
||
|
||
try: resolution
|
||
except NameError:
|
||
if units == 'km/s':
|
||
line = " RESOLUTION = "+str(clight/np.min(fwhms))+"\n"
|
||
else:
|
||
line = " RESOLUTION = "+str(np.mean(wrange)/np.min(fwhms))+"\n"
|
||
fout.write(line)
|
||
|
||
fout.write(" /\n")
|
||
|
||
#smooth and write data
|
||
k = 0 #increases only when a line from the original file is used
|
||
j = 0 #increases as we advance through the array ind
|
||
if 'irregular' in type:
|
||
pass
|
||
else:
|
||
ntot = np.prod(n_p)
|
||
for i in ind:
|
||
j = j + 1
|
||
print('line ',j,' of ',ntot)
|
||
#print(k,ntot,i)
|
||
if len(newcol) == 0 or all(i[newcol] == 0):
|
||
line = fin.readline()
|
||
if freeze is not None:
|
||
skip = True
|
||
for entry in lfkeys:
|
||
if (abs(freeze[entry] - par[labels.index(entry)]) < 1e-6): skip = False
|
||
if skip: continue
|
||
y = np.array(line.split(),dtype=float)
|
||
if 'irregular' in type:
|
||
par = y[:ndim]
|
||
y = y[ndim:]
|
||
else:
|
||
#print(i,steps,llimits)
|
||
par = i*steps+llimits
|
||
print('par=',par)
|
||
#print('newcol=',newcol)
|
||
#print('i[newcol]=',i[newcol])
|
||
|
||
#print('len(y)=',len(y))
|
||
if wrange is not None: y = y [section1]
|
||
|
||
#apply Gaussian convolution
|
||
if 'FWHM' in labels:
|
||
w = np.where(np.array(labels) == 'FWHM')
|
||
fwhmval = par[w[0][0]]
|
||
else:
|
||
fwhmval = fwhm
|
||
#print('fwhmval=',fwhmval)
|
||
if fwhmval > 1.e-7:
|
||
if units == 'km/s':
|
||
xx,yy = vgconv(x,y,fwhmval,ppr=ppr)
|
||
else:
|
||
xx,yy = lgconv(x,y,fwhmval,ppr=ppr)
|
||
else:
|
||
xx,yy = x, y
|
||
|
||
#apply extinction
|
||
if 'E(B-V)' in labels:
|
||
w = np.where(np.array(labels) == 'E(B-V)')
|
||
ebvval = par[w[0][0]]
|
||
#print(ebvval)
|
||
yy = apply(ccm89(xx, ebvval* 3.1, 3.1), yy)
|
||
|
||
#apply RV
|
||
if 'RV' in labels:
|
||
w = np.where(np.array(labels) == 'RV')
|
||
rvval = par[w[0][0]]
|
||
#print(rvval)
|
||
yy = np.interp(xx, xx*(1.+rvval/clight), yy)
|
||
|
||
|
||
if wrange is not None: yy = yy[section2]
|
||
|
||
if 'irregular' in type: yy = np.insert(yy,0,par)
|
||
yy.tofile(fout,sep=" ",format="%0.4e")
|
||
fout.write("\n")
|
||
k = k + 1
|
||
|
||
fin.close()
|
||
fout.close()
|
||
|
||
|
||
def gsynth_old(synthfile,fwhm=0.0,units='km/s',outsynthfile=None,ppr=5,wrange=None,freeze=None):
|
||
|
||
"""Smooth the spectra in a FERRE grid by Gaussian convolution
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
name of the input FERRE synth file
|
||
fwhm: float
|
||
FWHM of the Gaussian kernel in A or km/s
|
||
(default is 0.0, which means no convolution is performed)
|
||
units: str
|
||
units for the FWHM ('A' for a constant resolution in Angstroms,
|
||
'km/s' for a constant resolution in velocity
|
||
(default is 'km/s')
|
||
outsynthfile: str
|
||
name of the output FERRE synth file
|
||
(default is the same as synth file, but starting with 'n')
|
||
ppr: float, optional
|
||
Points per resolution element to downsample the convolved spectrum
|
||
(default is 5, set to None to keep the original sampling)
|
||
wrange: tuple
|
||
Starting and ending wavelengths (if a smaller range that
|
||
the input's is desired)
|
||
(default None, to keep the original range)
|
||
freeze: dictionary
|
||
Allows to reduce the dimensionality of the grid. The keys are the labels
|
||
of the dimensions to freeze (as given in in the header of the input grid)
|
||
with the values that should be adopted for those 'frozen' dimensions.
|
||
Example: set freeze = {'TEFF': 5000.} to fix that value for the Teff dimension
|
||
in a grid.
|
||
(default None, to retain all the original dimensions)
|
||
Returns
|
||
-------
|
||
writes outsynthfile with the smooth spectra
|
||
|
||
"""
|
||
|
||
if outsynthfile is None:
|
||
assert synthfile[0] != 'n', 'default output file name starts with n_. Given that the input starts with n too, please choose an anternative outsynthfile'
|
||
outsynthfile='n'+synthfile[1:]
|
||
logw=0
|
||
|
||
#read header, update and write out
|
||
fin = open(synthfile,'r')
|
||
fout = open(outsynthfile,'w')
|
||
hd = []
|
||
labels = []
|
||
line = fin.readline()
|
||
hd.append(line)
|
||
while line[1] != "/":
|
||
line = fin.readline()
|
||
if "N_P" in line: n_p = np.array(line.split()[2:],dtype=int)
|
||
if "STEPS" in line: steps = np.array(line.split()[2:],dtype=float)
|
||
if "LLIMITS" in line: llimits = np.array(line.split()[2:],dtype=float)
|
||
if "LABEL" in line: labels.append(line.split()[-1][1:-1])
|
||
if "NPIX" in line: npix = int(line.split()[2])
|
||
if "N_OF_DIM" in line: ndim = int(line.split()[2])
|
||
if "WAVE" in line: wave = np.array(line.split()[2:],dtype=float)
|
||
if "LOGW" in line: logw = int(line.split()[2])
|
||
if "RESOLUTION" in line: resolution = float(line.split()[2])
|
||
hd.append(line)
|
||
|
||
assert (len(n_p) == len(steps) & len(n_p) == len(llimits) & len(n_p) == len(labels) & len(n_p) == ndim), 'The dimension of the parameters from the header are inconsistent'
|
||
|
||
assert (units == 'km/s' or units == 'A'), 'units must be either km/s or A'
|
||
|
||
#update header parameters
|
||
x = np.arange(npix)*wave[1]+wave[0]
|
||
if logw == 1: x=10.**x
|
||
if logw == 2: x=np.exp(x)
|
||
|
||
#define indices for grid loops
|
||
ll = []
|
||
ind_n_p = []
|
||
i = 0
|
||
for entry in labels:
|
||
if freeze is not None:
|
||
lfkeys = list(freeze.keys())
|
||
if entry not in lfkeys: ind_n_p.append(i)
|
||
else:
|
||
ind_n_p.append(i)
|
||
ll.append(np.arange(n_p[i]))
|
||
i = i + 1
|
||
ind = list(product(*ll))
|
||
|
||
if wrange is not None:
|
||
assert (len(wrange) == 2), 'Error: wrange must have two elements'
|
||
section1 = np.where( (x >= wrange[0]*(1.-10.*fwhm/clight)) & (x <= wrange[1]*(1.+10.*fwhm/clight)) )
|
||
x = x[section1]
|
||
npix = len(x)
|
||
|
||
if fwhm > 1.e-7:
|
||
y = np.ones(npix)
|
||
if units == 'km/s':
|
||
print('fwhm=',fwhm)
|
||
xx,yy = vgconv(x,y,fwhm,ppr=ppr)
|
||
else:
|
||
xx,yy = lgconv(x,y,fwhm,ppr=ppr)
|
||
else:
|
||
print('Warning -- fwhm <= 1.e-7, no convolution will be performed, ppr will be ignored')
|
||
xx = x
|
||
|
||
|
||
if wrange is not None:
|
||
section2 = np.where( (xx >= wrange[0]) & (xx <= wrange[1]) )
|
||
xx = xx [section2]
|
||
|
||
#print(x,xx)
|
||
#print(len(x),len(xx))
|
||
|
||
jlabel = 0
|
||
for line in hd:
|
||
if "N_OF_DIM" in line: line = " N_OF_DIM = "+str(len(ind_n_p))+"\n"
|
||
if "N_P" in line: line = " N_P = "+' '.join(map(str,n_p[ind_n_p]))+"\n"
|
||
if "STEPS" in line: line = " STEPS = "+' '.join(map(str,steps[ind_n_p]))+"\n"
|
||
if "LLIMITS" in line: line = " LLIMITS = "+' '.join(map(str,llimits[ind_n_p]))+"\n"
|
||
if freeze is not None:
|
||
if "LABEL" in line:
|
||
ilabel = line.split()[-1][1:-1] #drop starting/ending quotes
|
||
if ilabel in lfkeys:
|
||
continue
|
||
else:
|
||
jlabel = jlabel + 1
|
||
line = " LABEL("+str(jlabel)+") = "+ilabel+"\n"
|
||
if "NPIX" in line: line = " NPIX = "+str(len(xx))+"\n"
|
||
if "WAVE" in line:
|
||
if units == 'km/s':
|
||
line = " WAVE = "+str(np.log10(xx[0]))+" "+str(np.log10(xx[1])-np.log10(xx[0]))+"\n"
|
||
else:
|
||
line = " WAVE = "+str(xx[0])+" "+str(xx[1]-xx[0])+"\n"
|
||
if "LOGW" in line:
|
||
if units == 'km/s':
|
||
line = " LOGW = 1 \n"
|
||
else:
|
||
line = " LOGW = 0 \n"
|
||
if "RESOLUTION" in line:
|
||
if units == 'km/s':
|
||
line = " RESOLUTION = "+str(clight/np.sqrt(clight**2/resolution**2 + fwhm**2))+"\n"
|
||
else:
|
||
line = " RESOLUTION = "+str(np.mean(wrange)/np.sqrt(np.mean(wrange)**2/resolution**2 + fwhm**2))+"\n"
|
||
if line[1] != "/": fout.write(line)
|
||
|
||
try: resolution
|
||
except NameError:
|
||
if units == 'km/s':
|
||
line = " RESOLUTION = "+str(clight/fwhm)+"\n"
|
||
else:
|
||
line = " RESOLUTION = "+str(np.mean(wrange)/fwhm)+"\n"
|
||
fout.write(line)
|
||
|
||
fout.write(" /\n")
|
||
|
||
#smooth and write data
|
||
k = 0
|
||
j = 0
|
||
ntot = np.prod(n_p)
|
||
for i in ind:
|
||
j = j + 1
|
||
print('line ',j,' of ',ntot)
|
||
#print(k,ntot,i)
|
||
#print(i,steps,llimits)
|
||
par = i*steps+llimits
|
||
line = fin.readline()
|
||
if freeze is not None:
|
||
skip = True
|
||
for entry in lfkeys:
|
||
if (abs(freeze[entry] - par[labels.index(entry)]) < 1e-6): skip = False
|
||
if skip: continue
|
||
y = np.array(line.split(),dtype=float)
|
||
if wrange is not None: y = y [section1]
|
||
if fwhm > 1.e-7:
|
||
if units == 'km/s':
|
||
xx,yy = vgconv(x,y,fwhm,ppr=ppr)
|
||
else:
|
||
xx,yy = lgconv(x,y,fwhm,ppr=ppr)
|
||
else:
|
||
xx,yy = x, y
|
||
if wrange is not None: yy = yy[section2]
|
||
yy.tofile(fout,sep=" ",format="%0.4e")
|
||
fout.write("\n")
|
||
k = k + 1
|
||
|
||
fin.close()
|
||
fout.close()
|
||
|
||
def xplsf(synthfile,outsynthfile=None,ppr=5):
|
||
|
||
"""Takes an input FERRE grid with sufficient resolution (R>1000)
|
||
and produces an output one with wavelength-dependent resolution
|
||
approximating the Gaia DR3 XP data
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
Name of the Input FERRE synthfile
|
||
Must have R>1000 and a minimum wavelength coverage between 360-990 nm
|
||
outsynthfile: str
|
||
Name of the Output FERRE synthfile
|
||
(default is None)
|
||
ppr: float
|
||
Points per resolution element for the output grid
|
||
|
||
|
||
Returns
|
||
-------
|
||
Creates a synthfile with the nominal resolution of the Gaia XP (DR3) data
|
||
|
||
"""
|
||
|
||
#Table describing the resolution R of the externally calibrated spectra (ECS) XP DR3 data in Montegriffo et al. 2023
|
||
x = np.array([350.,370.,390.,410,430.,450.,470.,490.,510.,530.,550.,570.,590.,610.,630.,640.,660.,680.,700.,720.,740.,760.,780.,800.,820.,840.,860.,880.,900.,920.,940.,960.,980.])
|
||
y = np.array([71.5,67.2,61.4,55.3,49.9,45.0,41.4,38.0,35.0,32.5,30.0,28.3,25.5,25.2,22.0,74.6,77.8,77.8,76.8,74.5,73.2,71.1,68.8,66.9,65.1,63.4,62.1,60.7,58.2,57.2,54.9,53.8,52.0])
|
||
y = x/y # FWHM = lambda/R
|
||
plt.plot(x,x/y,linewidth=3)
|
||
plt.xlabel('wavelength (nm)')
|
||
plt.ylabel('R')
|
||
plt.show()
|
||
|
||
x = x*10.
|
||
y = y*10.
|
||
|
||
vgsynth(synthfile,x,y,outsynthfile=outsynthfile,wrange=(3600.,9900),ppr=ppr)
|
||
|
||
return()
|
||
|
||
def vgsynth(synthfile,wavelength,fwhm,outsynthfile=None,ppr=5,wrange=None,original=False):
|
||
|
||
"""Variable-width Gaussian convolution
|
||
This is similar to gsynth but the FWHM of the Gaussian kernel can change
|
||
with wavelength.
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
name of the input FERRE synth file
|
||
wavelength: array of floats
|
||
Wavelength (angstroms)
|
||
fwhm: array of floats
|
||
FWHM of the Gaussian kernel (in A) for convolution
|
||
outsynthfile: str
|
||
name of the output FERRE synth file
|
||
(default is None, to set the same as synthfile, but starting with 'v')
|
||
ppr: float, optional
|
||
Points per resolution element to sample the convolved spectrum
|
||
(default is 5, set to None to keep the original sampling)
|
||
wrange: tuple
|
||
Starting and ending wavelengths (if a smaller range that
|
||
the input's is desired)
|
||
(default None, to keep the original range of the wavelength/fwhm arrays)
|
||
original: bool
|
||
Switch to retain the wavelength sample of the original grid in the output one
|
||
(default False, and the FWHM is resampled with ppr points)
|
||
|
||
Returns
|
||
-------
|
||
writes outsynthfile with the smooth spectra
|
||
|
||
"""
|
||
|
||
h, p, d = read_synth(synthfile)
|
||
xx = lambda_synth(synthfile)
|
||
|
||
ending = synthfile.find('.dat')
|
||
if ending < -1:
|
||
ending = synthfile.find('.pickle')
|
||
if ending < -1:
|
||
ending = len(synthfile) + 1
|
||
root = synthfile[2:ending]
|
||
if outsynthfile is None: outsynthfile = 'v_'+root+'.dat'
|
||
|
||
gg = np.zeros((len(xx),len(xx)))
|
||
|
||
for i in range(len(xx)):
|
||
sigma=np.interp(xx[i],wavelength,fwhm)/2.0/np.sqrt(-2.0*np.log(0.5))
|
||
kernel = np.exp(-(xx-xx[i])**2/2./sigma**2)
|
||
kernel = kernel/np.sum(kernel)
|
||
#print(xx[i],sigma)
|
||
gg[i,:] = kernel/np.sum(kernel)
|
||
|
||
rr = np.matmul(gg,np.transpose(d))
|
||
d2 = np.transpose(rr)
|
||
|
||
if original:
|
||
xff = xx.copy()
|
||
d3 = d2.copy()
|
||
else:
|
||
if wrange is None: wrange = (wavelength[0],wavelength[-1])
|
||
xf = wrange[0]
|
||
xff = [xf]
|
||
while xf <= wrange[1]:
|
||
xf = xf + np.interp(xf,wavelength,fwhm)/ppr
|
||
xff.append(xf)
|
||
|
||
xff = np.array(xff)
|
||
d3 = np.zeros( (len(d[:,0]),len(xff)) )
|
||
for i in range(len(d[:,0])):
|
||
d3[i,:] = np.interp(xff,xx,d2[i,:])
|
||
|
||
|
||
h3 = h.copy()
|
||
h3['NPIX'] = str(len(xff))
|
||
h3['RESOLUTION'] = str(np.mean(wavelength/fwhm))
|
||
h3['WAVELENGTHS'] = ' '.join(map(str,xff))
|
||
#h3['LOGW'] = str(1)
|
||
h3.pop('LOGW')
|
||
#h3['WAVE'] = str(np.log10(np.min(xff)))+' '+str( ( np.log10(np.max(xff)) - np.log10(np.min(xff)) ) / len(xff) )
|
||
h3.pop('WAVE')
|
||
h3['COMMENTS5'] = "'Data smoothed by Variable-FWHM Gausssian convolution (synple.vgsynth)'"
|
||
#h3['COMMENTS6'] = "'wavelenghts are NOT linear in loglambda'"
|
||
#h3['COMMENTS7'] = "'wavelenghts are as following:"+' '.join(map(str,xff))+"'"
|
||
|
||
|
||
write_synth(outsynthfile,p,d3,hdr=h3)
|
||
xff.tofile(outsynthfile+'.lambda',sep=" ",format="%s")
|
||
|
||
return()
|
||
|
||
|
||
|
||
def fit(xdata, ydata, modelfile, params, bounds,
|
||
vmicro=1.0, abu=None, vrot=0.0, fwhm=0.0, vmacro=0.0,
|
||
dw=None, strength=1e-4, linelist=linelist0, atom='ap18',
|
||
steprot=0.0, stepfwhm=0.0, lte=None, method='Powell', tol = 0.001):
|
||
|
||
"""
|
||
Fitting a piece of the spectrum to find the optimal value of one or
|
||
multiple params
|
||
"""
|
||
from scipy.optimize import minimize
|
||
|
||
#must check that all params are included in possible_parnames
|
||
|
||
atmostype, teff, logg, vmicro2, abu, nd, atmos = read_model(modelfile)
|
||
wrange = ( np.min(xdata) - 0.5 , np.max(xdata) + 0.5 )
|
||
|
||
othernames = np.array(['modelfile','wrange','dw','strength',
|
||
'linelist', 'atom', 'steprot', 'stepfwhm'])
|
||
others = {}
|
||
for entry in othernames:
|
||
others[entry] = locals()[entry]
|
||
|
||
possible_parnames = np.array(['vmicro', 'vrot', 'fwhm', 'vmacro'])
|
||
parnames = []
|
||
parvalues = []
|
||
for entry in possible_parnames:
|
||
if entry in params:
|
||
parnames.append(entry)
|
||
parvalues.append(locals()[entry])
|
||
else:
|
||
others[entry] = locals()[entry]
|
||
|
||
if len(params) > len(parnames):
|
||
symbol, mass, sol = elements()
|
||
for entry in symbol:
|
||
if entry in params:
|
||
elindex = int(np.where(np.array(symbol) == entry)[0])
|
||
parnames.append(entry)
|
||
parvalues.append(np.log10(abu[elindex])-np.log10(sol[elindex])+3.)
|
||
print(entry,elindex)
|
||
|
||
|
||
res = minimize(fun, np.array(parvalues),
|
||
( xdata, ydata, parnames, others), method=method ,
|
||
bounds = bounds, tol = tol )
|
||
|
||
print('res=',res)
|
||
return(res)
|
||
|
||
|
||
def fun(parvalues, *args ):
|
||
"""
|
||
Auxiliary to the routine fit
|
||
"""
|
||
|
||
(xdata, ydata, parnames, others) = args
|
||
|
||
argnames = np.array(['modelfile','wrange','dw','strength',
|
||
'linelist', 'atom', 'steprot', 'stepfwhm'])
|
||
for entry in argnames:
|
||
globals()[entry] = others[entry]
|
||
print('assigning ',entry,' to ',others[entry])
|
||
|
||
possible_parnames = ('vmicro', 'vrot', 'fwhm', 'vmacro')
|
||
k = 0
|
||
for entry in possible_parnames:
|
||
if entry in parnames:
|
||
globals()[entry] = parvalues[k]
|
||
k = k + 1
|
||
else:
|
||
globals()[entry] = others[entry]
|
||
|
||
atmostype, teff, logg, vmicro2, abu, nd, atmos = read_model(modelfile)
|
||
|
||
if len(parvalues) > k:
|
||
symbol, mass, sol = elements()
|
||
for entry in symbol:
|
||
if entry in parnames:
|
||
elindex = int(np.where(np.array(symbol) == entry)[0])
|
||
abu[elindex] = 10.**(parvalues[k] + np.log10(sol[elindex])-3.)
|
||
k = k + 1
|
||
#print('symbol[elindex]=',np.array(symbol)[elindex])
|
||
#print('entry=',entry)
|
||
#print('parnames=',parnames)
|
||
|
||
|
||
print('parnames=',parnames)
|
||
print('parvalues=',parvalues)
|
||
|
||
out = syn( modelfile, wrange , dw=dw, strength=strength , \
|
||
vmicro=vmicro, abu=abu, \
|
||
linelist=linelist, atom=atom, vrot=vrot, fwhm=fwhm, vmacro=vmacro, \
|
||
steprot=steprot, stepfwhm=stepfwhm, intensity=False, \
|
||
lineid=False, tag=False, \
|
||
clean=True, save=False, synfile=None, lte=None, compute=True, tmpdir=None)
|
||
|
||
chi = np.sum( (ydata - np.interp(xdata, out[0], out[1]/out[2]) )**2)
|
||
|
||
#plt.clf()
|
||
#plt.plot(xdata,ydata,xdata,np.interp(xdata, out[0], out[1]/out[2]) )
|
||
#plt.show()
|
||
print('chi=',chi)
|
||
|
||
return(chi)
|
||
|
||
|
||
def cebas(p,d,flx,iva):
|
||
|
||
"""Contrast-Expansion for BAS
|
||
Evaluates the chi-squared between an observed spectrum
|
||
(flx, with inverse variance iva) and an entire model grid (p,d)
|
||
to determine the best-fitting parameters and model
|
||
|
||
Parameters
|
||
----------
|
||
p: 2D numpy array of floats
|
||
parameter table with as many columns as parameters and
|
||
as many rows as spectra in the array d
|
||
|
||
d: 2D numpy array of floats
|
||
spectra table with as many columns as frequencies in the spectra
|
||
and as many rows as spectra
|
||
|
||
flx: 1D numpy array of floats
|
||
observed spectrum
|
||
|
||
iva: 1D numpy array of float
|
||
inverse variance for the observed spectrum
|
||
|
||
Returns
|
||
-------
|
||
res: numpy array of floats
|
||
best-fitting parameters (as many entries as columns in p)
|
||
|
||
eres: numpy array of floats
|
||
uncertainties for the best-fitting parameters
|
||
|
||
cov: numpy array of floats
|
||
top half of the covariance matrix for the best-fitting parameters
|
||
|
||
bflx: numpy array of floats
|
||
best-fitting model (same size as flx)
|
||
|
||
weights: numpy array of floats
|
||
likelihood for the model grid, with as many elements as rows in p and d
|
||
|
||
"""
|
||
|
||
chi = np.sum((d-flx)**2 * iva,-1)
|
||
beta = np.median(chi) / 1490. / 5.
|
||
#print('min/max/median chi=',np.min(chi),np.max(chi),np.median(chi))
|
||
#print('beta=',beta)
|
||
while np.exp(-np.min(chi)/2./beta) <= 0.0:
|
||
beta = beta * 2.
|
||
#print('-- new beta=',beta)
|
||
|
||
#parameters
|
||
ndim = len(p[0,:])
|
||
res = np.zeros(ndim)
|
||
eres = np.zeros(ndim)
|
||
cov = np.zeros(ndim*(ndim+1)//2)
|
||
likeli = np.exp(-chi/2./beta)
|
||
den = np.sum(likeli)
|
||
#print('den=',den)
|
||
k = 0
|
||
for i in range(ndim):
|
||
#parameters
|
||
res[i] = np.sum( likeli * p[:,i])/den
|
||
|
||
#uncertainties
|
||
for j in range(ndim-i):
|
||
cov[k] = np.sum( likeli * (p[:,i] - res[i]) * (p[:,j+i] - res[j+i]) )/den
|
||
if j == 0: eres[i] = np.sqrt(cov[k])
|
||
k = k + 1
|
||
|
||
#best-fitting model
|
||
bflx = np.matmul(likeli,d)/den
|
||
#bflx = [0.0,0.0]
|
||
|
||
print('res=',res,'eres=',eres)
|
||
|
||
return(res,eres,cov,bflx,likeli)
|
||
|
||
|
||
def bas(infile, synthfile=None, outfile=None, target=None, rv=None, ebv=None,
|
||
focus=False, star=True, conti=0, absolut=False, wrange=None):
|
||
|
||
"""Bayesian Algorithm in Synple
|
||
|
||
Parameters
|
||
----------
|
||
infile: str or list
|
||
input FITS file,
|
||
root for the filenames of the input frd/err files,
|
||
(and, if outfile is None, the output opf/mdl files,)
|
||
or a string with wildcards (*?[]) that expand into multiple files,
|
||
or a list of files
|
||
synthfile: str
|
||
name of the model grid
|
||
(default is None and the code attempts to choose the appropriate
|
||
grid according to the source of the input data)
|
||
outfile: str
|
||
output FITS file or
|
||
root for the filenames of the output opdf/mdf files
|
||
target: iterable
|
||
input list of numerals or targetids to select objects
|
||
to process. If the list includes numbers < 10000, they are interpreted
|
||
as the order of the targets in the input file(s). Otherwise they are
|
||
interpreted as a list of target ids. Either way only the target list
|
||
will be analyzed and the others skipped.
|
||
(default is None meaning that all the targets in the input file(s) are analyzed)
|
||
rv: iterable
|
||
this can be an iterable matching the length of target with the RVs to be corrected
|
||
prior to the analysis. When equal to None, if RV is among the parameters in the synthfile,
|
||
RVs will be determined as such, but otherwise RVs are derived by the routine xxc.
|
||
(default is None)
|
||
ebv: iterable
|
||
this can be an iterable matching the length of target with the E(B-V) values
|
||
to be corrected prior to the analysis. If equal to None, if E(B-V) is
|
||
among the parameters in the synthfile, it will be determined as such, but
|
||
otherwise, only for DESI, it will be read from infile and corrected for
|
||
focus: bool
|
||
switch to activate a two-step algorithm in which a coarsely
|
||
subsampled version of the grid is used to identify first where
|
||
the optimal solution is, and then perform an focused analysis
|
||
in that region (a +/- 3 sigma volume)
|
||
star: bool
|
||
switch to limit the analysis of DESI spectra to stars. It has no
|
||
effect on other data sets. Activating target disables star.
|
||
(default True)
|
||
conti: int
|
||
conti > 0 activates the continuum normalization (see 'continuum' function)
|
||
by a running mean with a width of 'conti'
|
||
NOTE that the default (0) is dividing the input/model fluxes in each
|
||
spectrum by their mean value
|
||
(default 0)
|
||
absolut: bool
|
||
activates the output of the absolute fluxes for the best-fitting
|
||
model (.flx file) and the input (unnormalized) fluxes (.frd file)
|
||
wrange: 2-element iterable
|
||
spectral range to use in the fittings
|
||
(default None, and sets wrange to the values of the adopted grid)
|
||
|
||
Returns
|
||
-------
|
||
Creates output FERRE-formatted files with the normalized data (.nrd),
|
||
best-fitting parameters (.opf) and best-fitting models (.mdl).
|
||
|
||
"""
|
||
|
||
if type(infile) is list:
|
||
infiles = infile
|
||
else:
|
||
if type(infile) is str:
|
||
if '*' in infile or '?' in infile or '[' in infile:
|
||
infiles = glob.glob(infile)
|
||
else:
|
||
infiles = [infile]
|
||
|
||
instr, default_synthfile = identify_instrument(infiles[0])
|
||
if synthfile is None: synthfile = default_synthfile
|
||
print('data appear to be from ',instr)
|
||
print('adopting synthfile ',synthfile)
|
||
instr0 = instr
|
||
|
||
#models
|
||
hd, p, d = read_synth(synthfile)
|
||
x = lambda_synth(synthfile)
|
||
lenx = len(x)
|
||
if type(hd) is list:
|
||
hd0 = hd[1]
|
||
else:
|
||
hd0 = hd
|
||
|
||
if wrange is not None:
|
||
assert(len(wrange) == 2),'wrange must be a 2-element array'
|
||
w = (x >= wrange[0]) & (x <= wrange[1])
|
||
d = d[:,w]
|
||
x = x[w]
|
||
lenx = len(x)
|
||
|
||
#normalization
|
||
print('normalizing grid...')
|
||
if absolut: da = d.copy() # da keeps a copy of the original grid
|
||
for entry in range(len(d[:,0])):
|
||
if conti > 0:
|
||
cc = continuum(d[entry,:],window_length=conti)
|
||
else:
|
||
cc = np.mean(d[entry,:])
|
||
d[entry,:] = d[entry,:] / cc
|
||
|
||
|
||
if focus:
|
||
p2 = p
|
||
d2 = d
|
||
nmod = len(p[:,0])
|
||
irnd = np.array(np.random.random_sample(int(nmod*0.1))*nmod,dtype=int)
|
||
p = p[irnd,:]
|
||
d = d[irnd,:]
|
||
|
||
|
||
|
||
#sanity check
|
||
if len(infiles) > 1 and (rv is not None or ebv is not None):
|
||
print('BAS warning: are the same rv/ebv arrays/lists intended for multiple infiles ...??')
|
||
|
||
|
||
for file in infiles:
|
||
|
||
#data
|
||
print('reading data from file '+file+'...')
|
||
instr, default_synthfile = identify_instrument(file)
|
||
if instr0 is None:
|
||
print(rv,instr0)
|
||
#FERRE files, expand lists of wavelengths arrays into a single array
|
||
if type(x) is list: x = np.hstack(x)
|
||
else:
|
||
assert(instr == instr0),'all the input files must be from the same instrument'
|
||
|
||
if ebv is not None:
|
||
if 'EBV' in hd0.values() or 'E(B-V)' in hd0.values():
|
||
ebv = 0
|
||
|
||
|
||
|
||
ids, x2, obs, ivr, xtr = read_spec(file,wavelengths=x,target=target,rv=rv,
|
||
ebv=ebv, star=star)
|
||
lenx2 = len(x2)
|
||
if ivr.ndim == 1:
|
||
obs = obs.reshape((1,lenx2))
|
||
ivr = ivr.reshape((1,lenx2))
|
||
nspec = len(obs[:,0])
|
||
print('nspec in bas:',nspec)
|
||
|
||
if outfile is None or len(infiles) > 1:
|
||
outfile = file
|
||
else:
|
||
if type(outfile) is list:
|
||
assert len(outfile) == 1,'outfile can only be specified when there is a single infile'
|
||
|
||
opffile = outfile + '.opf'
|
||
mdlfile = outfile + '.mdl'
|
||
nrdfile = outfile + '.nrd'
|
||
errfile = outfile + '.err'
|
||
wavfile = outfile + '.wav'
|
||
fmpfile = outfile + '.fmp.fits'
|
||
scrfile = outfile + '.scr.fits'
|
||
if absolut:
|
||
frdfile = outfile + '.frd'
|
||
flxfile = outfile + '.flx'
|
||
|
||
#open output parameter, observed and model file
|
||
opf = open(opffile,'w')
|
||
mdl = open(mdlfile,'w')
|
||
nrd = open(nrdfile,'w')
|
||
err = open(errfile,'w')
|
||
wav = open(wavfile,'w')
|
||
if absolut:
|
||
frd = open(frdfile,'w')
|
||
flx = open(flxfile,'w')
|
||
|
||
|
||
for j in range(nspec):
|
||
|
||
print('spectrum ',j,' of ',nspec,' in ',file)
|
||
|
||
#clean the data
|
||
spec = obs[j,:]
|
||
www = np.where(np.isnan(spec))[0]
|
||
#print('www:',www)
|
||
if len(www) > 0:
|
||
www2 = np.where(not np.isnan(spec))[0]
|
||
xax = np.arange(lenx2)
|
||
flx = np.interp(xax,xax[www2],spec[www2])
|
||
|
||
#normalize
|
||
if conti > 0:
|
||
mspec = continuum(spec, window_length=conti)
|
||
www = (mspec == 0.0)
|
||
mspec[www] = 1.
|
||
else:
|
||
mspec = np.mean(spec)
|
||
if mspec == 0.0: mspec = np.median(spec)
|
||
if mspec == 0.0: mspec = 1.
|
||
|
||
if absolut: rawspec = spec.copy()
|
||
spec = spec / mspec
|
||
ivar = ivr[j,:] * mspec**2
|
||
|
||
#analyze
|
||
res, eres, cov, bmod, weights = cebas( p, d, spec, ivar )
|
||
lchi = np.log10( np.sum((bmod-spec)**2 * ivar) / (len(bmod) - len(res)) )
|
||
print('reduced lchi =',lchi)
|
||
|
||
vrad = 0.0
|
||
if rv is None and 'RV' not in hd0.values() and instr0 is not None:
|
||
|
||
print('type(x2) is ',type(x2))
|
||
vrad, evrad = xxc(x2,spec,ivar,x2,bmod)
|
||
print('RV = ',vrad,' km/s')
|
||
|
||
#correct RV and reanalyze
|
||
spec = np.interp(x2, x2 * (1. - vrad/clight), spec)
|
||
ivar = np.interp(x2, x2 * (1. - vrad/clight), ivar)
|
||
res, eres, cov, bmod, weights = cebas( p, d, spec, ivar )
|
||
|
||
lchi = np.log10( np.sum((bmod-spec)**2 * ivar) / (len(bmod) - len(res)) )
|
||
print('reduced lchi =',lchi)
|
||
|
||
if focus:
|
||
eres[eres < 1e-17] = 1e-17 # avoid division by zero
|
||
w = ( (abs(p2-res)/eres).max(1) < 3. )
|
||
if len(np.where(w)[0]) > 0:
|
||
res, eres, cov, bmod, weights = cebas( p2[w,:], d2[w,:], spec, ivar )
|
||
lchi = np.log10( np.sum((bmod-spec)**2 * ivar) / (len(bmod) - len(res)) )
|
||
print('focus selected ',len(np.where(w)[0]), 'points, giving a reduced lchi =',lchi)
|
||
|
||
if absolut:
|
||
den = np.sum(weights)
|
||
abbmod = np.matmul(weights,da)/den
|
||
|
||
|
||
opf.write(str(ids[j])+' '+' '.join(map(str,res))+' '+
|
||
' '.join(map(str,eres))+' '+
|
||
str(vrad)+' '+str(np.median(spec*np.sqrt(ivar)))+' '+
|
||
str(lchi)+' '+' '.join(map(str,cov))+'\n')
|
||
nrd.write(' '.join(map(str,spec))+'\n')
|
||
mdl.write(' '.join(map(str,bmod))+'\n')
|
||
err.write(' '.join(map(str,1./np.sqrt(ivar)))+'\n')
|
||
if absolut:
|
||
frd.write(' '.join(map(str,rawspec))+'\n')
|
||
flx.write(' '.join(map(str,abbmod))+'\n')
|
||
if j == 0: wav.write(' '.join(map(str,x2))+'\n')
|
||
|
||
|
||
print('closing opf file:',opffile)
|
||
opf.close()
|
||
mdl.close()
|
||
nrd.close()
|
||
err.close()
|
||
wav.close()
|
||
if absolut:
|
||
frd.close()
|
||
flx.close()
|
||
|
||
if instr == 'DESI':
|
||
head, fibermap, scores = xtr
|
||
fmp = tbl.Table(fibermap)
|
||
hdu0 = fits.BinTableHDU(fmp)
|
||
hdu0.writeto(fmpfile)
|
||
scr = tbl.Table(scores)
|
||
hdu0 = fits.BinTableHDU(scr)
|
||
hdu0.writeto(scrfile)
|
||
|
||
|
||
return()
|
||
|
||
|
||
def identify_instrument(infile):
|
||
|
||
"""Identify the instrument that produced infile
|
||
LAMOST, DESI, NGSL(STIS)/CALSPEC, Gaia XP, MILES, INT/IDS-R900V,
|
||
GTC/OSIRIS-R2500U
|
||
|
||
Parameters
|
||
----------
|
||
infile: str
|
||
name of an input (FITS) data file
|
||
|
||
Returns
|
||
-------
|
||
instr: str
|
||
instrument/telescope from which the data comes
|
||
|
||
grid: str
|
||
default BAS grid to adopt for it (see config/bas-grids.yaml
|
||
|
||
"""
|
||
|
||
conf = load_conf(config='bas-grids.yaml',confdir=confdir)
|
||
|
||
instr = None
|
||
path, filename = os.path.split(infile)
|
||
if infile[-4:] == 'fits':
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
if 'TELESCOP' in head:
|
||
telescop = head['TELESCOP']
|
||
if head['TELESCOP'] == 'LAMOST' and filename[:4] == 'spec' and len(fi) == 2:
|
||
instr = 'LAMOST'
|
||
if 'INSTRUME' in head:
|
||
if head['TELESCOP'] == 'HST' and head['INSTRUME'][:4] == 'STIS':
|
||
instr = 'STIS'
|
||
if head['TELESCOP'][:3] == 'INT' and head['INSTRUME'][:3] == 'IDS' and \
|
||
head['CAMERA'][:3] == '235' and head['GRATNAME'][:5] == 'R900V':
|
||
instr = 'IDS-R900V'
|
||
if head['TELESCOP'][:3] == 'GTC' and head['INSTRUME'][:6] == 'OSIRIS' and \
|
||
head['GRISM'][:6] == 'R2500U':
|
||
instr = 'OSIRIS-R2500U'
|
||
else:
|
||
if 'MAPKEY' in head:
|
||
if head['MAPKEY'] == 'calspec':
|
||
instr = 'CALSPEC'
|
||
if (filename[:5] == 'coadd' or filename[:7] == 'spectra') and len(fi) > 10:
|
||
instr = 'DESI'
|
||
if 'TIMEXPOS' in head:
|
||
if head['TIMEXPOS'] == -999.0:
|
||
instr = 'MILES'
|
||
|
||
if instr is None:
|
||
grid = None
|
||
else:
|
||
grid = conf[instr]
|
||
|
||
return(instr,grid)
|
||
|
||
def read_knoao2005(wrange):
|
||
|
||
"""Solar Kurucz 2005 atlas"""
|
||
|
||
file = os.path.join(atlasdir,'solarfluxintwl.fits')
|
||
|
||
d = fits.open(file)[0].data
|
||
w = d[:,0]*10.
|
||
f = d[:,1]
|
||
ind = np.where((w > wrange[0]) & (w < wrange[1]))
|
||
|
||
return(w[ind],f[ind])
|
||
|
||
|
||
def read_iag(wrange):
|
||
|
||
"""Solar IAG atlas (Reiners et al. 2016 A&A 587, 65)"""
|
||
|
||
if wrange[0] < 10300.:
|
||
file = os.path.join(atlasdir,'iag-vis.fits')
|
||
else:
|
||
file = os.path.join(atlasdir,'iag-nir.fits')
|
||
d = fits.open(file)[0].data
|
||
w = vac2air(d[:,0])
|
||
f = d[:,1]
|
||
ind = np.where((w > wrange[0]) & (w < wrange[1]))
|
||
|
||
return(w[ind],f[ind])
|
||
|
||
|
||
def read_arcturus(wrange):
|
||
|
||
"""Arcturus atlas (Hinkle et al. 2000)"""
|
||
|
||
file = os.path.join(atlasdir,'ardata.fits')
|
||
|
||
d = fits.open(file)[1].data
|
||
w = d['wavelength']
|
||
f = d['arcturus']
|
||
ind = np.where((w > wrange[0]) & (w < wrange[1]))
|
||
|
||
return(w[ind],f[ind])
|
||
|
||
|
||
def read_spec(infile,wavelengths=None,target=None,rv=None,ebv=None,star=True):
|
||
"""Read and (if wavelengths is given) resample spectral observations
|
||
|
||
Parameters
|
||
----------
|
||
infile: str
|
||
name of an input FITS file, or root for input FERRE-formatted files
|
||
(frd,err)
|
||
wavelenghts: numpy array of floats
|
||
array with the wavelengths of a grid to resample the observations
|
||
target: iterable of integers/longs
|
||
list of targets to read -- can be either integers indicating
|
||
the order of the targets of interest in the input infile or
|
||
targetids (e.g. for DESI)
|
||
(default is none)
|
||
rv: iterable of floats
|
||
this can be an iterable matching the length of target with the RVs to
|
||
be corrected.
|
||
When equal to None, velocities offsets are not considered.
|
||
(default is None)
|
||
ebv: iterable of floats
|
||
this can be an iterable matching the length of target with the reddening
|
||
to be corrected.
|
||
When equal to None, no reddening correction is applied, except for
|
||
DESI data, for which the SFD values from the DESI files will be used.
|
||
When equal to 0 (integer!), no reddening correction is applied for
|
||
DESI data, or data from any other source
|
||
star: bool
|
||
flag to pre-select only stars for DESI for having any of the
|
||
following targetting bits set:
|
||
STD_FAINT, STD_WD, STD_BRIGHT, MWS_ANY or SCND_ANY.
|
||
It has no effect on other data sets apart from DESI.
|
||
Cannot be combined with target; passing a list in target
|
||
sets star to False
|
||
(default is True)
|
||
|
||
|
||
Returns
|
||
-------
|
||
ids: numpy array of str
|
||
strings identifying the target(s). In some cases it may simply
|
||
be a number
|
||
wav: numpy array of floats
|
||
common wavelength array for the spectra
|
||
which will be identical to the input 'wavelengths' array if provided
|
||
|
||
frd: numpy array of floats
|
||
observed spectra (as many columns as frequencies and as many rows
|
||
as spectra)
|
||
|
||
ivr: numpy array of floats
|
||
inverse variance for frd (same size as frd)
|
||
|
||
xtr: tuple of objects
|
||
the first element is usually a header dictionary
|
||
for DESI it also contains fibermap and scores structures
|
||
|
||
"""
|
||
|
||
from extinction import apply,remove,ccm89
|
||
|
||
if target is not None:
|
||
try:
|
||
_ = (e for e in target)
|
||
except TypeError:
|
||
print('target must be None or an iterable')
|
||
|
||
if rv is not None:
|
||
try:
|
||
_ = (e for e in rv)
|
||
if target is not None:
|
||
assert(len(rv) == len(target)),'rv and target must have the same length when both are input'
|
||
except TypeError:
|
||
pass
|
||
|
||
if ebv is not None:
|
||
try:
|
||
_ = (e for e in ebv)
|
||
if target is not None:
|
||
assert(len(ebv) == len(target)),'ebv and target must have the same length when both are input'
|
||
except TypeError:
|
||
pass
|
||
|
||
|
||
#data
|
||
if infile[-4:] == 'fits':
|
||
instr, synthfile = identify_instrument(infile)
|
||
if instr == 'LAMOST':
|
||
#reading LAMOST spec file
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
s = fi[1].data
|
||
wav = np.transpose(s['WAVELENGTH'])[:,0]
|
||
wav = vac2air(wav)
|
||
lenwav = len(wav)
|
||
flux = np.transpose(s['FLUX'])[:,0]
|
||
ivar = np.transpose(s['IVAR'])[:,0]
|
||
if 'OBJNAME' in head:
|
||
ids = np.array([head['OBJNAME']])
|
||
else:
|
||
ids = np.array([infile])
|
||
|
||
assert(target is None),'target must be None for single-target LAMOST files'
|
||
|
||
xtr = (head)
|
||
wav, frd, ivr = single_target_prep(wav, flux, ivar, rv, ebv, wavelengths=wavelengths)
|
||
|
||
elif instr == 'DESI':
|
||
if wavelengths is not None and type(wavelengths) is not list:
|
||
twavelengths = []
|
||
twavelengths.append(wavelengths[(wavelengths < 5800.)])
|
||
twavelengths.append(wavelengths[(wavelengths >= 5800.) & (wavelengths < 7600.)])
|
||
twavelengths.append(wavelengths[wavelengths >= 7600.])
|
||
wavelengths = twavelengths.copy()
|
||
|
||
i = 0
|
||
for band in ('B','R','Z'):
|
||
wav1,flux1,ivar1,res1,head1,map1,scores1 = read_desispec(infile,band)
|
||
wav1 = vac2air(wav1)
|
||
|
||
if band == 'B': #check if there is a 'target' or 'star' preselection
|
||
ind = []
|
||
if target is None:
|
||
ind = np.arange(len(map1))
|
||
if len(ind) == 0:
|
||
print('no DESI targets in file ',infile)
|
||
return(None,None,None,None)
|
||
else:
|
||
if star:
|
||
print('passing a list in target disables star!')
|
||
star = False
|
||
if np.max(target) < 10000:
|
||
#target is a list with the order of the desired spectra
|
||
ind = target
|
||
else:
|
||
#target is a list with targetids
|
||
ind = np.where(np.isin(map1['targetid'],target))[0]
|
||
|
||
if len(ind) == 0:
|
||
print('no DESI target in file ',infile,' matches the input target=',target)
|
||
return(None,None,None,None)
|
||
|
||
if star:
|
||
ind = []
|
||
j = 0
|
||
for entry in map1['desi_target']:
|
||
bits = desimask(entry)
|
||
print('bits=',bits)
|
||
if 'STD_FAINT' in bits or 'STD_WD' in bits or 'STD_BRIGHT' in bits or 'MWS_ANY' in bits or 'SCND_ANY' in bits:
|
||
ind.append(j)
|
||
j += 1
|
||
|
||
if len(ind) == 0:
|
||
print('no DESI target in file ',infile,' has star targetting bits (STD_BRIGHT/FAINT, STD_WD, MWS_ANY or SCND_ANY)')
|
||
print('processing ALL targets')
|
||
ind = np.arange(len(map1))
|
||
|
||
if len(ind) > 0:
|
||
nspec = len(ind)
|
||
print('read_spec: selecting targets with indices -- ',ind)
|
||
else:
|
||
nspec = len(map1)
|
||
|
||
if rv is None:
|
||
vrad = np.zeros(nspec)
|
||
else:
|
||
vrad = rv
|
||
|
||
if type(ebv) is int and ebv == 0:
|
||
red = np.zeros(nspec)
|
||
print('E(B-V)=0 adopted for all the objects')
|
||
else:
|
||
if ebv is None:
|
||
red = map1['EBV']
|
||
if len(ind) > 0:
|
||
red = red[ind]
|
||
print('Correcting E(B-V) from SFD map')
|
||
else:
|
||
red = ebv
|
||
print('Correcting E(B-V) from values provided by the user')
|
||
|
||
#limit the sample to target/star
|
||
if len(ind) > 0:
|
||
#print('ind=',ind)
|
||
flux1 = flux1[ind,:]
|
||
ivar1 = ivar1[ind,:]
|
||
res1 = res1[ind,:,:]
|
||
map1 = map1[ind]
|
||
scores1 = scores1[ind]
|
||
|
||
#correct reddening
|
||
print('Reddening from SFD map has a mean E(B-V)=',
|
||
np.mean(map1['EBV']),' +/- ', np.std(map1['EBV']))
|
||
|
||
for j in range(nspec):
|
||
if np.abs(red[j]) > 1e-7:
|
||
xtmp = np.array(wav1,dtype=float)
|
||
ytmp = np.array(flux1[j,:],dtype=float)
|
||
tmp = remove(ccm89(xtmp, red[j] * 3.1, 3.1), ytmp)
|
||
ivar1[j,:] = ivar1[j,:] * (np.divide(ytmp,tmp,where=tmp>0))**2
|
||
flux1[j,:] = tmp
|
||
|
||
|
||
if wavelengths is None:
|
||
nfreq = len(wav1)
|
||
#print('nfreq=',nfreq)
|
||
flux2 = np.zeros((nspec,nfreq))
|
||
ivar2 = np.zeros((nspec,nfreq))
|
||
for j in range(nspec):
|
||
flux2[j,:] = np.interp(wav1,wav1*(1.+vrad[j]/clight),flux1[j,:])
|
||
ivar2[j,:] = np.interp(wav1,wav1*(1.+vrad[j]/clight),ivar1[j,:])
|
||
flux1 = flux2
|
||
ivar1 = ivar2
|
||
else:
|
||
assert (type(wavelengths) is list),'A list is expected for the input wavelengths'
|
||
nfreq = len(wavelengths[i])
|
||
#print('nfreq=',nfreq)
|
||
flux2 = np.zeros((nspec,nfreq))
|
||
ivar2 = np.zeros((nspec,nfreq))
|
||
for j in range(nspec):
|
||
flux2[j,:] = np.interp(wavelengths[i],wav1*(1.+vrad[j]/clight),flux1[j,:])
|
||
ivar2[j,:] = np.interp(wavelengths[i],wav1*(1.+vrad[j]/clight),ivar1[j,:])
|
||
flux1 = flux2
|
||
ivar1 = ivar2
|
||
wav1 = wavelengths[i]
|
||
|
||
if band == 'B':
|
||
wav = wav1
|
||
frd = flux1
|
||
wbad = (wav1 >= 4300.) & (wav1 <= 4450.)
|
||
ivar1[:,wbad] = 0.
|
||
ivr = ivar1
|
||
ids = map1['targetid']
|
||
xtr = (head1, map1, scores1)
|
||
else:
|
||
wav = np.concatenate((wav,wav1))
|
||
frd = np.concatenate((frd,flux1),axis=1)
|
||
ivr = np.concatenate((ivr,ivar1),axis=1)
|
||
print(len(wav1),len(wav))
|
||
|
||
i += 1
|
||
|
||
elif instr == "CALSPEC" or instr == "STIS":
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
s = fi[1].data
|
||
wav = s['WAVELENGTH']
|
||
wav = vac2air(wav)
|
||
lenwav = len(wav)
|
||
flux = s['FLUX']
|
||
if 'STATERROR' in s.names:
|
||
err = s['STATERROR']
|
||
elif 'STATERR' in s.names:
|
||
err = s['STATERR']
|
||
else:
|
||
print('Warning: cannot find the statistical error in the file from HST')
|
||
print(' assuming S/N = 20!')
|
||
err = flux * 0.05
|
||
if 'SYSERROR' in s.names: err = flux*0.000001 + err + s['SYSERROR']
|
||
ivar4 = np.divide(1.,err**2, where = (err**2 > 0.) ,dtype = np.float128)
|
||
ivar = np.float64(ivar4)
|
||
if 'TARGETID' in head:
|
||
ids = np.array([head['TARGETID']])
|
||
elif 'TARGNAME' in head:
|
||
ids = np.array([head['TARGNAME']])
|
||
else:
|
||
ids = np.array([infile])
|
||
|
||
assert(target is None),'target must be None for STIS data (1 target per file)'
|
||
|
||
xtr = (head)
|
||
wav, frd, ivr = single_target_prep(wav, flux, ivar, rv, ebv, wavelengths=wavelengths)
|
||
|
||
|
||
elif instr == "MILES":
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
s = fi[0].data
|
||
flux = s[0,:]
|
||
wav = np.arange(len(s[0,:]))*head['CDELTA1']+head['CRVAL1']
|
||
lenwav = len(wav)
|
||
print('Warning: MILES files do not include uncertainties')
|
||
print(' assuming S/N = 20!')
|
||
err = flux * 0.05
|
||
ivar4 = np.divide(1.,err**2, where = (err**2 > 0.) ,dtype = np.float128)
|
||
ivar = np.float64(ivar4)
|
||
|
||
|
||
if 'OBJECT' in head:
|
||
ids = np.array([head['OBJECT']])
|
||
else:
|
||
ids = np.array([infile])
|
||
|
||
assert(target is None),'target must be None for INT-IDS/MILES data (1 target per file)'
|
||
|
||
xtr = (head)
|
||
wav, frd, ivr = single_target_prep(wav, flux, ivar, rv, ebv, wavelengths=wavelengths)
|
||
|
||
elif instr == "IDS-R900V":
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
s = fi[0].data
|
||
flux = s
|
||
wav = np.arange(len(s))*head['CD1_1']+head['CRVAL1']
|
||
lenwav = len(wav)
|
||
print('Warning: IDS files do not include uncertainties')
|
||
print(' assuming S/N = 20!')
|
||
err = flux * 0.05
|
||
ivar4 = np.divide(1.,err**2, where = (err**2 > 0.) ,dtype = np.float128)
|
||
ivar = np.float64(ivar4)
|
||
|
||
if 'OBJECT' in head:
|
||
ids = np.array([head['OBJECT']])
|
||
else:
|
||
ids = np.array([infile])
|
||
|
||
assert(target is None),'target must be None for INT-IDS data (1 target per file)'
|
||
|
||
xtr = (head)
|
||
wav, frd, ivr = single_target_prep(wav, flux, ivar, rv, ebv, wavelengths=wavelengths)
|
||
|
||
elif instr == "OSIRIS-R2500U":
|
||
fi = fits.open(infile)
|
||
head = fi[0].header
|
||
s = fi[0].data
|
||
flux = s
|
||
wav = np.arange(len(s))*head['CD1_1']+head['CRVAL1']
|
||
lenwav = len(wav)
|
||
print('Warning: OSIRIS files do not include uncertainties')
|
||
print(' assuming S/N = 20!')
|
||
err = flux * 0.05
|
||
ivar4 = np.divide(1.,err**2, where = (err**2 > 0.) ,dtype = np.float128)
|
||
ivar = np.float64(ivar4)
|
||
|
||
if 'OBJECT' in head:
|
||
ids = np.array([head['OBJECT']])
|
||
else:
|
||
ids = np.array([infile])
|
||
|
||
assert(target is None),'target must be None for GTC-OSIRIS data (1 target per file)'
|
||
|
||
xtr = (head)
|
||
wav, frd, ivr = single_target_prep(wav, flux, ivar, rv, ebv, wavelengths=wavelengths)
|
||
|
||
|
||
else:
|
||
|
||
assert(target is None),'target must be none for FERRE input files'
|
||
assert(rv is None),'rv must be none for FERRE input files'
|
||
instr = 'FERRE'
|
||
if infile is None: infile = synthfile[2:synthfile.find('.dat')]
|
||
frdfile = infile + '.frd'
|
||
errfile = infile + '.err'
|
||
|
||
#read frdfile and errfile
|
||
frd = np.loadtxt(frdfile,dtype=float)
|
||
err = (np.loadtxt(errfile,dtype=float)**2)
|
||
ivr = np.divide(1.,err, where = (err > 0.) )
|
||
wav = wavelengths
|
||
if frd.ndim == 1:
|
||
ids = np.array([0])
|
||
else:
|
||
ids = np.array(list(map(str,range(len(frd[:,0])))))
|
||
xtr = (dict())
|
||
|
||
return(ids,wav,frd,ivr,xtr)
|
||
|
||
|
||
def single_target_prep(wav,flux,ivar,rv,ebv,wavelengths=None):
|
||
|
||
from extinction import apply,remove,ccm89
|
||
|
||
if rv is None:
|
||
vrad = 0.0
|
||
else:
|
||
try:
|
||
_ = (e for e in rv)
|
||
assert (len(rv) == 1),'rv must have a single value for LAMOST files with a single spectrum'
|
||
vrad = float(rv[0])
|
||
except TypeError:
|
||
vrad = float(rv)
|
||
|
||
if ebv is None:
|
||
red = 0.0
|
||
else:
|
||
try:
|
||
_ = (e for e in ebv)
|
||
assert (len(ebv) == 1),'ebv must be have a single value for LAMOST files with a single spectrum'
|
||
red = float(ebv[0])
|
||
except TypeError:
|
||
red = float(ebv)
|
||
|
||
if np.abs(red) > 1e-7:
|
||
xtmp = np.array(wav,dtype=float)
|
||
ytmp = np.array(flux,dtype=float)
|
||
tmp = remove(ccm89(xtmp, red * 3.1, 3.1), ytmp)
|
||
ivar = ivar * (np.divide(ytmp,tmp,where=tmp>0))**2
|
||
flux = tmp
|
||
|
||
if wavelengths is None:
|
||
frd = np.interp(wav,wav*(1. + vrad/clight),flux)
|
||
ivr = np.interp(wav,wav*(1. + vrad/clight),ivar)
|
||
else:
|
||
lenx = len(wavelengths)
|
||
frd = np.interp(wavelengths,wav,flux)
|
||
ivr = np.interp(wavelengths,wav,ivar)
|
||
wav = wavelengths
|
||
|
||
return(wav,frd,ivr)
|
||
|
||
|
||
def read_desispec(filename,band=None):
|
||
"""Reads a DESI band spectrum, or a (full) SDSS/BOSS spectrum
|
||
|
||
Parameters
|
||
----------
|
||
filename: str
|
||
name of an input DESI (spectra* or coadd*) or SDSS-BOSS (spPlate)
|
||
FITS file
|
||
|
||
band: str
|
||
name of a band for multi-band DESI spectra
|
||
(can be 'B', 'R' or 'Z')
|
||
|
||
Returns
|
||
-------
|
||
wavelength: numpy array of floats
|
||
|
||
flux: numpy array of floats
|
||
observed fluxes
|
||
|
||
ivar: numpy array of floats
|
||
inverse variance for the observed fluxes
|
||
|
||
res: structure
|
||
resolution matrix for DESI
|
||
|
||
header: dict
|
||
very first header of the file
|
||
|
||
fibermap: structure (.data from the FIBERMAP extension)
|
||
fibermap
|
||
|
||
scores: structure (.data from the SCORES extension)
|
||
scores
|
||
|
||
|
||
"""
|
||
|
||
hdu=fits.open(filename)
|
||
|
||
if filename.find('spectra-') > -1 or filename.find('exp_') > -1 or filename.find('coadd') > -1: #DESI
|
||
header=hdu[0].header
|
||
wavelength=hdu[band+'_WAVELENGTH'].data #wavelength array
|
||
flux=hdu[band+'_FLUX'].data #flux array (multiple spectra)
|
||
ivar=hdu[band+'_IVAR'].data #inverse variance (multiple spectra)
|
||
#mask=hdu[band+'_MASK'].data #mask (multiple spectra)
|
||
res=hdu[band+'_RESOLUTION'].data #resolution matrix (multiple spectra)
|
||
#bintable=hdu['BINTABLE'].data #bintable with info (incl. mag, ra_obs, dec_obs)
|
||
fibermap=hdu['FIBERMAP'].data
|
||
scores=hdu['SCORES'].data
|
||
|
||
if filename.find('spPlate') > -1: #SDSS/BOSS
|
||
header=hdu['PRIMARY'].header
|
||
wavelength=header['CRVAL1']+arange(header['NAXIS1'])*header['CD1_1']
|
||
#wavelength array
|
||
wavelength=10.**wavelength
|
||
flux=hdu['PRIMARY'].data #flux array (multiple spectra)
|
||
#ivar=hdu['IVAR'].data #inverse variance (multiple spectra)
|
||
ivar=hdu[1].data #inverse variance (multiple spectra)
|
||
#andmask=hdu['ANDMASK'].data #AND mask (multiple spectra)
|
||
#ormask=hdu['ORMASK'].data #OR mask (multiple spectra)
|
||
#res=hdu['WAVEDISP'].data #FWHM array (multiple spectra)
|
||
res=hdu[4].data #FWHM array (multiple spectra)
|
||
#bintable=hdu['BINTABLE'].data #bintable with info (incl. mag, ra, dec)
|
||
fibermap=hdu['FIBERMAP'].data
|
||
scores=hdu['FIBERMAP'].data
|
||
|
||
return((wavelength,flux,ivar,res,header,fibermap,scores))
|
||
|
||
|
||
def plot_spec(root=None, x=None, n=None, m=None, o=None, xrange=None, yrange=None, nozero=None, res=False):
|
||
"""
|
||
绘制一个或多个光谱
|
||
参数:
|
||
-----------
|
||
root: str
|
||
光谱数据文件的根名称(包含波长、观测数据、模型数据)
|
||
需要存在以下文件:
|
||
- root.wav(波长,Å)
|
||
- root.nrd(观测归一化通量)
|
||
- root.mdl(模型归一化通量)
|
||
- root.opf(参数文件)
|
||
x: list或numpy数组
|
||
波长数据(Å)
|
||
n: list或numpy数组
|
||
观测归一化通量
|
||
m: list或numpy数组
|
||
模型归一化通量(可选)
|
||
o: list或numpy数组
|
||
参数信息(用于标题和注释)
|
||
xrange: tuple of two floats
|
||
波长显示范围(nm)
|
||
yrange: tuple of two floats
|
||
通量显示范围(默认自动计算)
|
||
nozero: bool
|
||
是否过滤归一化通量为零的数据点(默认False)
|
||
res: bool
|
||
是否绘制模型与观测的残差(默认False)
|
||
返回:
|
||
-------
|
||
无(直接绘图)
|
||
"""
|
||
|
||
# 根据root参数加载数据
|
||
if root is not None:
|
||
xx = np.loadtxt(root + '.wav') / 10. # 波长转换为纳米
|
||
n = np.loadtxt(root + '.nrd') # 观测归一化通量
|
||
m = np.loadtxt(root + '.mdl') # 模型归一化通量
|
||
o = np.loadtxt(root + '.opf', dtype=str) # 参数文件(字符串格式)
|
||
|
||
else:
|
||
# 直接使用输入的x数据
|
||
if isinstance(x, list):
|
||
xx = np.hstack(x) / 10. # 合并多段波长数据并转换为纳米
|
||
p = [0]
|
||
for i in range(len(x)):
|
||
p.append(p[i] + len(x[i])) # 记录各段波长的起始索引
|
||
else:
|
||
xx = x / 10. # 单段波长数据转换为纳米
|
||
|
||
# 设置默认波长范围
|
||
if xrange is None:
|
||
xrange = (np.min(xx), np.max(xx))
|
||
|
||
# 判断数据维度
|
||
if xx.ndim == 1:
|
||
nfreq = len(xx)
|
||
nspec = 1 # 单光谱
|
||
|
||
# 绘制单光谱
|
||
plt.clf() # 清除当前图形
|
||
labels = []
|
||
if isinstance(x, list):
|
||
# 多段波长处理
|
||
for i in range(len(x)):
|
||
# 过滤零值数据
|
||
if nozero:
|
||
w = (n[p[i]:p[i+1]] > 0.)
|
||
else:
|
||
w = slice(p[i], p[i+1])
|
||
# 绘制观测数据
|
||
plt.plot(xx[p[i]:p[i+1]][w], n[p[i]:p[i+1]][w])
|
||
labels.append('观测数据')
|
||
# 绘制模型数据
|
||
if m is not None:
|
||
plt.plot(xx[p[i]:p[i+1]][w], m[p[i]:p[i+1]][w])
|
||
labels.append('模型')
|
||
# 绘制残差
|
||
if res:
|
||
plt.plot(xx[p[i]:p[i+1]][w], m[p[i]:p[i+1]][w] - n[p[i]:p[i+1]][w])
|
||
labels.append('残差')
|
||
else:
|
||
# 单段波长处理
|
||
if nozero:
|
||
w = (n > 0.)
|
||
else:
|
||
w = slice(len(xx))
|
||
plt.plot(xx[w], n[w]) # 绘制观测数据
|
||
labels.append('观测数据')
|
||
if m is not None:
|
||
plt.plot(xx[w], m[w]) # 绘制模型数据
|
||
labels.append('模型')
|
||
if res:
|
||
plt.plot(xx[w], m[w] - n[w]) # 绘制残差
|
||
labels.append('残差')
|
||
|
||
# 设置y轴范围
|
||
if yrange is None:
|
||
yrange = [np.min(n)*0.95, np.max(n)*1.05]
|
||
if res:
|
||
yrange[0] = np.min(m[w] - n[w])*1.05
|
||
|
||
# 添加坐标轴标签
|
||
plt.xlabel('波长 (nm)')
|
||
plt.ylabel('归一化通量')
|
||
|
||
# 添加标题和注释
|
||
if o is not None:
|
||
npar = len(o) - 3
|
||
if npar >= 16:
|
||
npar = int(np.sqrt(npar * 1.0 - 1))
|
||
else:
|
||
npar = npar // 2
|
||
# 提取参数并格式化显示
|
||
params = np.array(o[1:npar+1], dtype=float)
|
||
plt.title('参数: ' + ' -- '.join(map("{:.2f}".format, params)))
|
||
# 计算注释位置
|
||
xtext = 0.5 * (xrange[0] + xrange[1])
|
||
ytext = 0.75 * yrange[0] + 0.25 * yrange[1]
|
||
ycurve = np.interp(xtext, xx[w], n[w])
|
||
if abs(ycurve - ytext) < 0.5:
|
||
ytext = 0.25 * yrange[0] + 0.75 * yrange[1]
|
||
plt.text(xtext, ytext, o[0])
|
||
|
||
# 设置坐标范围和图例
|
||
plt.xlim(xrange)
|
||
plt.ylim(yrange)
|
||
if m is not None:
|
||
plt.legend(labels)
|
||
plt.savefig('fig1.png') # 保存图像
|
||
plt.show()
|
||
else:
|
||
# 处理多光谱情况
|
||
nspec = len(n[:,0])
|
||
labels = []
|
||
for j in range(nspec):
|
||
plt.clf() # 清除当前图形
|
||
if isinstance(x, list):
|
||
# 多段波长处理
|
||
for i in range(len(x)):
|
||
if nozero:
|
||
w = (n[j,p[i]:p[i+1]] > 0.)
|
||
else:
|
||
w = np.ones(p[i+1]-p[i], dtype=bool)
|
||
plt.plot(xx[p[i]:p[i+1]][w], n[j,p[i]:p[i+1]][w])
|
||
labels.append('观测数据')
|
||
if m is not None:
|
||
plt.plot(xx[p[i]:p[i+1]][w], m[j,p[i]:p[i+1]][w])
|
||
labels.append('模型')
|
||
if res:
|
||
plt.plot(xx[p[i]:p[i+1]][w], m[j,p[i]:p[i+1]][w] - n[j,p[i]:p[i+1]][w])
|
||
labels.append('残差')
|
||
else:
|
||
# 单段波长处理
|
||
if nozero:
|
||
w = (n[j,:] > 0.)
|
||
else:
|
||
w = np.ones(nfreq, dtype=bool)
|
||
xx2 = xx[w] if xx.ndim == 1 else xx[j,w].T
|
||
plt.plot(xx2, n[j,w])
|
||
labels.append('观测数据')
|
||
if m is not None:
|
||
plt.plot(xx2, m[j,w])
|
||
labels.append('模型')
|
||
if res:
|
||
plt.plot(xx2, m[j,w] - n[j,w])
|
||
labels.append('残差')
|
||
|
||
# 设置y轴范围
|
||
if yrange is None:
|
||
yrange2 = [np.min(n[j,:])*0.95, np.max(n[j,:])*1.05]
|
||
else:
|
||
yrange2 = yrange
|
||
if res:
|
||
yrange2[0] = np.min(m[j,w] - n[j,w])*1.05
|
||
|
||
# 添加坐标轴标签
|
||
plt.xlabel('波长 (nm)')
|
||
plt.ylabel('归一化通量')
|
||
|
||
# 设置坐标范围和图例
|
||
plt.xlim(xrange)
|
||
plt.ylim(yrange2)
|
||
if m is not None:
|
||
plt.legend(labels)
|
||
|
||
# 添加标题和注释
|
||
if o is not None:
|
||
npar = len(o[0,:]) - 3
|
||
if npar >= 16:
|
||
npar = int(np.sqrt(npar * 1.0 - 1))
|
||
else:
|
||
npar = npar // 2
|
||
# 提取参数并格式化显示
|
||
params = np.array(o[j, 1:npar+1], dtype=float)
|
||
plt.title('参数: ' + ' -- '.join(map("{:.2f}".format, params)))
|
||
xtext = 0.5 * (xrange[0] + xrange[1])
|
||
ytext = 0.75 * yrange2[0] + 0.25 * yrange2[1]
|
||
ycurve = np.interp(xtext, xx2, n[j,w])
|
||
if abs(ycurve - ytext) < 0.5:
|
||
ytext = 0.25 * yrange2[0] + 0.75 * yrange2[1]
|
||
plt.text(xtext, ytext, o[j,0])
|
||
|
||
# 保存图像
|
||
plt.savefig(f'fig{j+1}.png')
|
||
|
||
return()
|
||
|
||
|
||
def vac2air(wavelength):
|
||
"""Conversion from vacuum to air for wavelengths based on Ciddor (1996)
|
||
|
||
Parameters
|
||
----------
|
||
wavelength: float, or iterable
|
||
vacuum wavelength(s) in AA
|
||
|
||
Returns
|
||
-------
|
||
wavelength: numpy array
|
||
corresponding air wavelengths in AA (keeps vacuum for lambda<=2000. A)
|
||
|
||
Based on Ciddor (1996). Copied literally from the IDL Astro library
|
||
"""
|
||
if type(wavelength) is list or type(wavelength) is float or type(wavelength) is tuple:
|
||
wavelength = np.array(wavelength)
|
||
g = (wavelength >= 2000.)
|
||
sigma2 = (1e4/wavelength[g])**2
|
||
fact = 1. + 5.792105e-2/(238.0185 - sigma2) + 1.67917e-3/( 57.362 - sigma2)
|
||
wavelength[g] = wavelength[g]/fact
|
||
|
||
return(wavelength)
|
||
|
||
def xc(y1,y2, npoints=None, gaus=False, plot=False):
|
||
"""Determines a pixel offset between two arrays by
|
||
cross correlation.
|
||
|
||
Parameters
|
||
----------
|
||
y1 float array
|
||
Array 1
|
||
|
||
y2 float array
|
||
Array 2
|
||
|
||
npoints int
|
||
Number of points to use in fitting a model to the peak
|
||
of the cross-correlation function
|
||
|
||
gaus bool
|
||
When True a Gaussian model is used instead of the default
|
||
parabola
|
||
|
||
plot bool
|
||
When True the program plots the model fit to the peak of
|
||
the cross-correlation function
|
||
|
||
Returns
|
||
-------
|
||
delta float
|
||
Shift to apply to y2 to match y1 (max of the cross-correlation
|
||
function)
|
||
(units are in pixels)
|
||
|
||
edelta float
|
||
Uncertainty in delta (pixels)
|
||
|
||
"""
|
||
|
||
#set failure values for delta/edetla
|
||
delta = None
|
||
e_delta = None
|
||
|
||
nel = len(y1)
|
||
|
||
#basic checks
|
||
assert nel == len(y2), 'error: the two vectors have different dimensions'
|
||
|
||
#compute the cross-correlation
|
||
|
||
#xlen = len(y1) + len(y2) - 1
|
||
#x = np.arange(xlen)
|
||
#ccf = np.correlate(y1,y2,mode='full')
|
||
#w = np.where(ccf == np.max(ccf))[0]
|
||
|
||
nrange = nel // 2 - 1
|
||
ccf = np.zeros(2*nrange + 1)
|
||
for i in np.arange(2*nrange + 1):
|
||
yy = np.roll(y2,i-nrange)
|
||
ccf[i] = np.sum(y1*yy)
|
||
|
||
x = np.arange(len(ccf),dtype=float)-nrange
|
||
w = np.where(ccf == np.max(ccf))[0]
|
||
w0 = w[0]
|
||
|
||
if (gaus):
|
||
if npoints is None: npoints = 39
|
||
else:
|
||
if npoints is None: npoints = 7
|
||
|
||
assert npoints <= nel, 'error: npoints > number of elements of the input array'
|
||
|
||
#finding the central pixel from weighted average in a symmetric window
|
||
nhalf = (npoints - 1) // 2
|
||
w2 = np.sum( ccf[w0-nhalf:w0+nhalf+1] * x[w0-nhalf:w0+nhalf+1] ) / np.sum( ccf[w0-nhalf:w0+nhalf+1] ) + nrange
|
||
w = int(np.rint(w2))
|
||
|
||
|
||
#dealing with even values of npoints
|
||
nhalf1 = nhalf
|
||
nhalf2 = nhalf
|
||
if (npoints // 2 == int(np.rint(npoints / 2))):
|
||
if w2 > w:
|
||
nhalf2 = nhalf2 + 1
|
||
else:
|
||
nhalf1 = nhalf1 + 1
|
||
|
||
assert nhalf1 + nhalf2 + 1 == npoints, 'error: something is wrong!'
|
||
assert ((w-nhalf1 >= 0) and (w+nhalf2 <= len(x)-1)),'error: not enough points to fit'
|
||
|
||
xx = x[w-nhalf1:w+nhalf2+1]
|
||
yy = ccf[w-nhalf1:w+nhalf2+1]
|
||
|
||
if (gaus):
|
||
p0 = [np.mean(yy)*2., x[w0], 2.0, np.min(yy)]
|
||
coef, covar = curve_fit(gauss, xx, yy, p0=p0)
|
||
delta = coef[1]
|
||
edelta = covar[1,1]
|
||
edelta = np.sqrt(edelta)
|
||
model = gauss(xx,coef[0],coef[1],coef[2],coef[3])
|
||
else:
|
||
coef, covar = np.polyfit(xx, yy, 2, cov=True)
|
||
delta = -coef[1]/2./coef[0]
|
||
edelta = 1./4./coef[0]**2*(covar[1,1] + coef[1]**2/coef[0]**2*covar[0,0]) - coef[1]/2./coef[0]**3*covar[1,0]
|
||
edelta = np.sqrt(edelta)
|
||
model = coef[0]*xx**2+coef[1]*xx+coef[2]
|
||
|
||
if plot:
|
||
plt.plot(xx,yy,'.')
|
||
plt.plot(xx,model)
|
||
plt.show()
|
||
|
||
|
||
return (delta,edelta)
|
||
|
||
|
||
def xc_np(y1,y2, npoints=None, gaus=False, plot=False):
|
||
"""Determines a pixel offset between two arrays by
|
||
cross correlation. Variant of xc using numpy correlate
|
||
|
||
Parameters
|
||
----------
|
||
y1 float array
|
||
Array 1
|
||
|
||
y2 float array
|
||
Array 2
|
||
|
||
npoints int
|
||
Number of points to use in fitting a model to the peak
|
||
of the cross-correlation function
|
||
|
||
gaus bool
|
||
When True a Gaussian model is used instead of the default
|
||
parabola
|
||
|
||
plot bool
|
||
When True the program plots the model fit to the peak of
|
||
the cross-correlation function
|
||
|
||
Returns
|
||
-------
|
||
delta float
|
||
Shift to apply to y2 to match y1 (max of the cross-correlation
|
||
function)
|
||
(units are in pixels)
|
||
|
||
edelta float
|
||
Uncertainty in delta (pixels)
|
||
|
||
"""
|
||
|
||
#set failure values for delta/edelta
|
||
delta = None
|
||
edelta = None
|
||
|
||
xlen = len(y1) + len(y2) - 1
|
||
x = np.arange(xlen, dtype=float) - len(y1)
|
||
ccf = np.correlate(y1,y2,mode='full')
|
||
|
||
|
||
w = np.where(ccf == np.max(ccf))[0]
|
||
w0 = w[0]
|
||
|
||
if (gaus):
|
||
if npoints is None: npoints = 39
|
||
nhalf = (npoints - 1) // 2
|
||
xx = x[w0-nhalf:w0+nhalf+1]
|
||
yy = ccf[w0-nhalf:w0+nhalf+1]
|
||
p0 = [np.mean(yy)*2., x[w0], 2.0, np.min(yy)]
|
||
coef, covar = curve_fit(gauss, xx, yy, p0=p0)
|
||
delta = coef[1]
|
||
edelta = covar[1,1]
|
||
edelta = np.sqrt(edelta)
|
||
model = gauss(xx,coef[0],coef[1],coef[2],coef[3])
|
||
else:
|
||
if npoints is None: npoints = 7
|
||
nhalf = npoints // 2
|
||
xx = x[w0-nhalf:w0+nhalf+1]
|
||
yy = ccf[w0-nhalf:w0+nhalf+1]
|
||
coef, covar = np.polyfit(xx, yy, 2, cov=True)
|
||
delta = -coef[1]/2./coef[0]
|
||
edelta = 1./4./coef[0]**2*(covar[1,1] + coef[1]**2/coef[0]**2*covar[0,0]) - coef[1]/2./coef[0]**3*covar[1,0]
|
||
edelta = np.sqrt(edelta)
|
||
model = coef[0]*xx**2+coef[1]*xx+coef[2]
|
||
|
||
if plot:
|
||
plt.plot(xx,yy,'.')
|
||
plt.plot(xx,model)
|
||
plt.show()
|
||
|
||
|
||
return (delta,edelta)
|
||
|
||
def gauss(x, *p):
|
||
"""Evaluate a Gaussian function from the input parameters
|
||
"""
|
||
A, mu, sigma, base = p
|
||
return A*np.exp(-(x-mu)**2/(2.*sigma**2)) + base
|
||
|
||
def continuum(x, window_length = 500, polyorder = 3):
|
||
|
||
"""Smoothing the data in the 1D array x using a
|
||
Saviztky-Golay filter
|
||
|
||
parameters
|
||
----------
|
||
|
||
x : 1D array
|
||
The data to be filtered. If `x` is not a single or double precision
|
||
floating point array, it will be converted to type ``numpy.float64``
|
||
before filtering.
|
||
window_length : int
|
||
The length of the filter window (i.e., the number of coefficients).
|
||
If `mode` is 'interp', `window_length` must be less than or equal
|
||
to the size of `x`.
|
||
(default 50)
|
||
polyorder : int
|
||
The order of the polynomial used to fit the samples.
|
||
`polyorder` must be less than `window_length`.
|
||
(default 3)
|
||
"""
|
||
|
||
return(savgol_filter(x, window_length, polyorder))
|
||
|
||
|
||
def xxc(x1,y1,iva1,x2,y2, maxv = 1000., plot=False):
|
||
|
||
"""Determines the velocity offset to apply to the template
|
||
spectrum (x2,y2) so that it overlaps with the observed
|
||
spectrum (x1,y1,iva1)
|
||
|
||
Parameters
|
||
----------
|
||
x1: numpy array of floats
|
||
wavelenghts for the observed spectrum (A)
|
||
y1: numpy array of floats
|
||
fluxes for the observed spectrum
|
||
iva1: numpy array of floats
|
||
inverse variance for the fluxes of the observed spectrum
|
||
x2: numpy array of floats
|
||
wavelenghts for the model/template spectrum
|
||
y2: numpy array of floats
|
||
fluxes for the model/template spectrum
|
||
maxv: float
|
||
maximum velocity offset to consider between the observed
|
||
and template spectra
|
||
(default 1000 km/s)
|
||
plot: bool
|
||
switch to show a plot of the chi-square as a function of
|
||
velocity offset
|
||
|
||
Returns
|
||
-------
|
||
delta: float
|
||
velocity offset need to overlap the template on top of the
|
||
observed (y1) spectrum in units of km/s
|
||
edelta: float
|
||
uncertainty in delta
|
||
"""
|
||
|
||
lenx1 = len(x1)
|
||
dv = np.diff(x1).mean()/x1.mean()*clight / 10.
|
||
nv = int(2*maxv/dv)
|
||
v = np.arange(nv)*dv - maxv
|
||
lenv = len(v)
|
||
chi = np.zeros(lenv)
|
||
for i in range(lenv):
|
||
yy2 = np.interp(x1,x2*(1.+v[i]/clight),y2)
|
||
chi[i] = np.sum( ( y1 - yy2 )**2 * iva1 )
|
||
|
||
beta = np.median(chi) / 1490. / 5.
|
||
while np.exp(-np.min(chi)/2./beta) <= 0.0:
|
||
beta = beta * 2.
|
||
|
||
likeli = np.exp(-chi/2./beta)
|
||
den = np.sum(likeli)
|
||
delta = np.sum(v*likeli) / den
|
||
edelta = np.sum( ( v - delta)**2 * likeli) / den
|
||
edelta = np.sqrt(edelta)
|
||
|
||
if plot:
|
||
plt.plot(v,chi)
|
||
plt.show()
|
||
|
||
return(delta,edelta)
|
||
|
||
|
||
def bas_build(synthfile):
|
||
|
||
conf = load_conf(config='bas-build.yaml',confdir=confdir)
|
||
|
||
ending = synthfile.find('.dat')
|
||
if ending < -1:
|
||
ending = synthfile.find('.pickle')
|
||
if ending < -1:
|
||
ending = len(synthfile) + 1
|
||
root = synthfile[2:ending]
|
||
|
||
|
||
for entry in conf.keys():
|
||
for task in conf[entry]:
|
||
print(entry,task)
|
||
for job in conf[entry][task]:
|
||
if 'synthfile' not in job:
|
||
job['synthfile'] = synthfile
|
||
if task == 'pickle_synth':
|
||
if 'synthfile' in job:
|
||
pos = job['synthfile'].find('_')
|
||
job['outsynthfile'] = job['synthfile'][:pos] + '_' + \
|
||
root + '-' + job['synthfile'][pos+1:-3] + 'pickle'
|
||
print('kargs=',job)
|
||
call(task,**job)
|
||
|
||
return()
|
||
|
||
def call(func,**kargs):
|
||
|
||
func_to_run = globals()[func]
|
||
func_to_run(**kargs)
|
||
|
||
return()
|
||
|
||
|
||
def rewrite_synth(synthfile,outsynthfile=None):
|
||
|
||
"""Read a FERRE grid and write it back to disk as an irregular grid
|
||
"""
|
||
|
||
if outsynthfile is None:
|
||
outsynthfile = 'i_'+synthfile[2:]
|
||
|
||
h,p,d = read_synth(synthfile)
|
||
write_synth(outsynthfile, p, d, hdr=h, irregular=True)
|
||
|
||
|
||
return()
|
||
|
||
|
||
|
||
def bas_perfcheck(synthfile,n=1000,snr=1.e6,
|
||
kernel='thin_plate_spline', neighbors=100, focus=False, edgemargin=0.05):
|
||
|
||
"""Carry out a full performance check using bas on a synthetic grid
|
||
|
||
Parameters
|
||
---------
|
||
synthfile: str
|
||
Name of the input synth file
|
||
n: int
|
||
Number of mock spectra to produce for the test
|
||
(default is 1000)
|
||
snr: float
|
||
Signal to noise ratio for the mock spectra
|
||
(default is 1.e6)
|
||
kernel: string
|
||
Type of RBF function (linear, thin_plate_spline, cubic, gaussian ...)
|
||
neighbors: int
|
||
Number of nearest neighbors used to compute the interpolation
|
||
coefficients for each grid point
|
||
focus: bool
|
||
switch to activate a two-step algorithm in which a coarsely
|
||
subsampled version of the grid is used to identify first where
|
||
the optimal solution is, and then perform an focused analysis
|
||
in that region (a +/- 3 sigma volume)
|
||
edgemargin: float
|
||
fraction from the min/max values of the input parameters to exclude
|
||
from the range to sample
|
||
(default 0.05 -- exclude 5% from the edges)
|
||
|
||
|
||
Returns
|
||
------
|
||
result: numpy array of floats
|
||
16-50-86 percentiles for all the parameters
|
||
|
||
"""
|
||
hd = head_synth(synthfile)
|
||
if 'NTOT' in hd:
|
||
ntot = int(hd['NTOT'])
|
||
else:
|
||
if 'N_P' in hd:
|
||
n_p = map(int,hd['N_P'])
|
||
ntot = np.product(n_p)
|
||
else:
|
||
ntot = 0
|
||
|
||
checksynthfile=synthfile+'-check.dat'
|
||
synth_rbf(synthfile,outsynthfile=checksynthfile,n=n,
|
||
rv=False,ebv=False,kernel=kernel,neighbors=neighbors,
|
||
edgemargin=edgemargin)
|
||
bas_test(checksynthfile,snr=snr)
|
||
print('running ... ','bas(',checksynthfile[2:-4],'synthfile=',synthfile,')')
|
||
now = time.time()
|
||
bas(checksynthfile[2:-4],synthfile=synthfile,focus=focus)
|
||
print('this run took ',time.time()-now,' seconds')
|
||
result = fparams(checksynthfile[2:-4],synthfile=synthfile,
|
||
figure=checksynthfile[2:-4]+'-n'+str(n)+'-snr'+str(snr)+'.png')
|
||
|
||
|
||
fh = open('-'.join((synthfile,str(n),kernel,str(neighbors),'bas_perfcheck.dat')),'w')
|
||
fh.write(str(n)+' '+str(ntot)+' '+' '.join(map(str,np.concatenate(result)))+'\n')
|
||
fh.close()
|
||
|
||
|
||
|
||
return(result)
|
||
|
||
|
||
|
||
def bas_test(synthfile,snr=1.e6):
|
||
"""Use the data in synthfile to create mock observations
|
||
for testing purposes
|
||
"""
|
||
h,p,d = read_synth(synthfile)
|
||
ndim = len(p[0,:])
|
||
npix = len(d[0,:])
|
||
ending = synthfile.rfind('.dat')
|
||
if ending < -1:
|
||
ending = synthfile.rfind('.pickle')
|
||
if ending < -1:
|
||
ending = len(synthfile) + 1
|
||
root = synthfile[2:ending]
|
||
vf = open(root+'.ipf','w')
|
||
of = open(root+'.frd','w')
|
||
ef = open(root+'.err','w')
|
||
|
||
for i in range(len(p[:,0])):
|
||
vf.write(str(i)+' '+' '.join(map(str,p[i,:]))+'\n')
|
||
of.write(' '.join(map(str,d[i,:] * (1. +
|
||
1./snr*np.random.normal(size=npix))))+'\n')
|
||
ef.write(' '.join(map(str,d[i,:]/snr))+'\n')
|
||
|
||
vf.close()
|
||
of.close()
|
||
ef.close()
|
||
|
||
return
|
||
|
||
|
||
def synth_rbf(synthfile,outsynthfile=None,n=None,rv=False,ebv=False,
|
||
kernel='thin_plate_spline', neighbors=100, edgemargin=0.0):
|
||
|
||
"""Creates an irregular FERRE grid from a pre-existing regular or
|
||
irregular one
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
name of the input FERRE/BAS synthfile
|
||
|
||
outsynthfile: str
|
||
name of the output FERRE/BAS synthfile
|
||
|
||
n: int
|
||
number of spectra to create by RBF interpolation
|
||
|
||
rv: bool
|
||
if true, fold in a dimension with RV variations
|
||
|
||
ebv: bool
|
||
if true, fold in a dimension with E(B-V) variations
|
||
|
||
kernel: string
|
||
Type of RBF function (linear, thin_plate_spline, cubic, gaussian ...)
|
||
|
||
neighbors: int
|
||
Number of nearest neighbors used to compute the interpolation coefficients
|
||
for each grid point
|
||
|
||
edgemargin: float
|
||
fraction from the min/max values of the input parameters to exclude
|
||
from the range to sample
|
||
(default 0.0 -- use the full range)
|
||
|
||
|
||
|
||
Returns
|
||
-------
|
||
Creates an output grid (outsynthfile)
|
||
|
||
"""
|
||
|
||
from extinction import apply,ccm89
|
||
|
||
if rv or ebv :
|
||
x = lambda_synth(synthfile)
|
||
if type(x) is list: x = np.hstack(x)
|
||
h,p,d = read_synth(synthfile)
|
||
|
||
|
||
ndim = len(p[0,:])
|
||
npix = len(d[0,:])
|
||
ntot = len(p[:,0])
|
||
|
||
if n is None: n = ntot
|
||
|
||
for i in range(ndim):
|
||
|
||
amin = np.min(p[:,i])*(1.0+edgemargin)
|
||
amax = np.max(p[:,i])*(1.0-edgemargin)
|
||
vals = np.random.random_sample(n)*(amax-amin) + amin
|
||
if i == 0:
|
||
p2 = vals
|
||
else:
|
||
p2 = np.vstack((p2,vals))
|
||
|
||
c, pmin, ptp = rbf_get(synthfile, kernel=kernel, neighbors=neighbors)
|
||
d2 = rbf_apply(c, pmin, ptp, np.transpose(p2))
|
||
h2 = h
|
||
|
||
|
||
ndim2 = 0
|
||
if rv:
|
||
if type(h2) is list:
|
||
h0 = h2[1]
|
||
else:
|
||
h0 = h2
|
||
rvmax = 1000.
|
||
if 'RESOLUTION' in h0: rvmax = clight/float(h0['RESOLUTION'])
|
||
vals = np.random.random_sample(n)*2*rvmax - rvmax
|
||
p2 = np.vstack((p2,vals))
|
||
ndim2 += 1
|
||
if type(h2) is list:
|
||
for entry in range(len(h2)):
|
||
h2[entry]['LABEL('+str(ndim+ndim2)+')'] = "'RV'"
|
||
else:
|
||
h2['LABEL('+str(ndim+ndim2)+')'] = "'RV'"
|
||
if ebv:
|
||
ebvmax = 0.25 # mag
|
||
vals = np.random.random_sample(n)*ebvmax
|
||
p2 = np.vstack((p2,vals))
|
||
ndim2 += 1
|
||
if type(h2) is list:
|
||
for entry in range(len(h2)):
|
||
h2[entry]['LABEL('+str(ndim+ndim2)+')'] = "'E(B-V)'"
|
||
else:
|
||
h2['LABEL('+str(ndim+ndim2)+')'] = "'E(B-V)'"
|
||
|
||
p2 = np.transpose(p2)
|
||
ending = synthfile.rfind('.dat')
|
||
if ending < -1:
|
||
ending = synthfile.rfind('.pickle')
|
||
if ending < -1:
|
||
ending = len(synthfile) + 1
|
||
root = synthfile[2:ending]
|
||
if outsynthfile is None:
|
||
outsynthfile = 'n_'+root+'rbf'
|
||
if rv: outsynthfile += '-RV'
|
||
if ebv: outsynthfile += '-EBV'
|
||
outsynthfile += '.dat'
|
||
of = open(outsynthfile,'w')
|
||
print('writing grid ',outsynthfile,'...')
|
||
if type(h2) is not list:
|
||
h2 = [h2]
|
||
for block in h2:
|
||
block['TYPE'] = "'irregular'"
|
||
block['N_OF_DIM'] = str(ndim+ndim2)
|
||
block['NTOT'] = str(n)
|
||
for block in h2:
|
||
of.write(' &SYNTH\n')
|
||
for entry in block: of.write(' '+entry + ' = ' + block[entry] + '\n')
|
||
of.write(' /\n')
|
||
|
||
for i in range(len(p2[:,0])):
|
||
flx = d2[i,:]
|
||
if rv:
|
||
#print(type(x),len(x),type(flx),len(flx))
|
||
flx = np.interp(x,x*(1.+p2[i,ndim]/clight),flx)
|
||
if ebv:
|
||
flx = apply(ccm89(x, p2[i,ndim+ndim2-1]* 3.1, 3.1), flx)
|
||
|
||
#normalization
|
||
#flx = flx / np.mean(flx)
|
||
of.write(' '.join(map(str,p2[i,:]))+' '+' '.join(map(str,flx))+'\n')
|
||
of.close()
|
||
|
||
return
|
||
|
||
def rbf_test(synthfile,n=None, kernel='thin_plate_spline', neighbors=100):
|
||
"""Creates an irregular FERRE grid using RBF interpolation on a
|
||
pre-existing regular or irregular one, and from that one the
|
||
interpolation is repeated to return to the original/input grid
|
||
and estimate interpolation errors
|
||
|
||
Parameters
|
||
----------
|
||
synthfile: str
|
||
name of the input FERRE/BAS synthfile
|
||
|
||
n: int
|
||
number of spectra to create by RBF interpolation
|
||
(default is the number of spectra in the input grid)
|
||
|
||
kernel: string
|
||
Type of RBF function (linear, thin_plate_spline, cubic, gaussian ...)
|
||
|
||
neighbors: int
|
||
Number of nearest neighbors used to compute the interpolation
|
||
coefficients for each grid point
|
||
|
||
|
||
Returns
|
||
-------
|
||
tuple of 3 floats
|
||
16%, 50% and 84% percentiles between the original input and the output.
|
||
|
||
"""
|
||
|
||
|
||
h,p,d = read_synth(synthfile)
|
||
|
||
ndim = len(p[0,:])
|
||
npix = len(d[0,:])
|
||
ntot = len(p[:,0])
|
||
|
||
if n is None: n = ntot
|
||
|
||
#1st interpolation
|
||
synth_rbf(synthfile,outsynthfile=synthfile+'-tmp',n=n,
|
||
kernel=kernel, neighbors=neighbors)
|
||
|
||
h2,p2,d2 = read_synth(synthfile+'-tmp')
|
||
|
||
#2nd interpolation
|
||
c, pmin, ptp = rbf_get(synthfile+'-tmp',
|
||
kernel=kernel, neighbors=neighbors)
|
||
d2 = rbf_apply(c, pmin, ptp, p)
|
||
|
||
per = np.percentile( (d2-d)/d,[15.85,50.,84.15])
|
||
|
||
fh = open('-'.join((synthfile,str(n),kernel,str(neighbors),'rbf_test.dat')),'w')
|
||
fh.write(' '.join(map(str,(n, per[0], per[1], per[2])))+'\n')
|
||
fh.close()
|
||
|
||
return( per[0], per[1], per[2] )
|
||
|
||
|
||
def wferrefits(root, path=None):
|
||
|
||
"""Packs FERRE output into a FITS file
|
||
|
||
|
||
Parameters
|
||
----------
|
||
root: str
|
||
name of the root for input/output FERRE/BAS files
|
||
with extensions .opf, .wav, .nrd, .mdl
|
||
|
||
path: string
|
||
path to files
|
||
(default is None, and the code looks for the FERRE files
|
||
in the current folder)
|
||
|
||
Returns
|
||
-------
|
||
A FITS files with the data, with the same root
|
||
and a fits extension
|
||
|
||
"""
|
||
|
||
if path is None: path=""
|
||
proot=os.path.join(path,root)
|
||
o=glob.glob(proot+".opf")
|
||
|
||
|
||
xbandfiles = sorted(glob.glob(proot+'-*.wav'))
|
||
band = []
|
||
npix = []
|
||
for entry in xbandfiles:
|
||
print('entry=',entry)
|
||
match = re.search('-[\w]*.wav',entry)
|
||
tag = match.group()[1:-4]
|
||
if match: band.append(tag.upper())
|
||
x = loadtxt(proot+'-'+tag+'.wav')
|
||
npix.append(len(x))
|
||
|
||
print('proot+.wav=',proot+'.wav')
|
||
print('xbandfiles=',xbandfiles)
|
||
x = np.loadtxt(proot+'.wav')
|
||
if len(npix) == 0: npix.append(len(x))
|
||
|
||
mdl=glob.glob(proot+".mdl")
|
||
err=glob.glob(proot+".err")
|
||
nrd=glob.glob(proot+".nrd")
|
||
|
||
|
||
success=[]
|
||
targetid=[]
|
||
srcfile=[]
|
||
teff=[]
|
||
logg=[]
|
||
feh=[]
|
||
alphafe=[]
|
||
micro=[]
|
||
param = []
|
||
covar=[]
|
||
snr_med=[]
|
||
chisq_tot=[]
|
||
rv_adop=[]
|
||
rv_err=[]
|
||
bestgrid = []
|
||
of=open(o[0],'r')
|
||
|
||
|
||
for line in of:
|
||
cells=line.split()
|
||
bestgrid.append(o[0])
|
||
id = cells[0]
|
||
cells = cells[1:]
|
||
|
||
m = len(cells)
|
||
ndim = m - 3
|
||
if ndim > 10:
|
||
ndim = int(np.sqrt(4*ndim+1)-1)
|
||
ndim = ndim // 2
|
||
|
||
assert (m > 6), 'Error, the file '+o[0]+' has less than 7 columns, which would correspond to ndim=2'
|
||
par = np.zeros(ndim)
|
||
cov = np.zeros(ndim*ndim+ndim)
|
||
|
||
print('ndim=',ndim)
|
||
print('m=',m)
|
||
|
||
if (ndim == 2):
|
||
#white dwarfs 2 dimensions: id, 2 par, 2err, 0., med_snr, lchi, 2x2 cov
|
||
feh.append(-10.)
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(np.nan)
|
||
micro.append(np.nan)
|
||
|
||
|
||
elif (ndim == 3):
|
||
#Kurucz grids with 3 dimensions: id, 3 par, 3 err, 0., med_snr, lchi, 3x3 cov
|
||
#see Allende Prieto et al. (2018, A&A)
|
||
feh.append(float(cells[2]))
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(np.nan)
|
||
micro.append(np.nan)
|
||
|
||
elif (ndim == 4):
|
||
#Phoenix grid from Sergey or MARCS grid, with 4 dimensions: id, 4 par, 4err, 0., med_snr, lchi, 4x4 cov
|
||
feh.append(float(cells[1]))
|
||
teff.append(float(cells[2]))
|
||
logg.append(float(cells[3]))
|
||
alphafe.append(float(cells[0]))
|
||
micro.append(np.nan)
|
||
|
||
|
||
elif (ndim == 5):
|
||
#Kurucz grids with 5 dimensions: id, 5 par, 5 err, 0., med_snr, lchi, 5x5 cov
|
||
#see Allende Prieto et al. (2018, A&A)
|
||
feh.append(float(cells[0]))
|
||
teff.append(float(cells[3]))
|
||
logg.append(float(cells[4]))
|
||
alphafe.append(float(cells[1]))
|
||
micro.append(float(cells[2]))
|
||
|
||
|
||
chisq_tot.append(10.**float(cells[2+2*ndim]))
|
||
snr_med.append(float(cells[1+2*ndim]))
|
||
rv_adop.append(float(cells[0+2*ndim]))
|
||
rv_err.append(np.nan)
|
||
par = np.array(cells[0:ndim],dtype=float)
|
||
cov = np.array(cells[2*ndim:],dtype=float)
|
||
param.append(par)
|
||
covar.append(cov)
|
||
|
||
|
||
|
||
if (chisq_tot[-1] < 1.5 and snr_med[-1] > 5.): # chi**2<1.5 and S/N>5
|
||
success.append(1)
|
||
else: success.append(0)
|
||
targetid.append(id)
|
||
srcfile.append(root)
|
||
#fiber.append(int32(tmp[1]))
|
||
|
||
#primary extension
|
||
hdu0=fits.PrimaryHDU()
|
||
|
||
#find out processing date and add it to primary header
|
||
now = datetime.datetime.fromtimestamp(time.time())
|
||
nowstr = now.isoformat()
|
||
nowstr = nowstr[:nowstr.rfind('.')]
|
||
hdu0.header['DATE'] = nowstr
|
||
|
||
#find out host machine and add info to header
|
||
try:
|
||
host=os.environ['HOST']
|
||
except:
|
||
host='Unknown'
|
||
hdu0.header['HOST'] = host
|
||
#find out OS name/platform
|
||
osname = os.name
|
||
platf = platform.system() + ' '+ platform.release()
|
||
hdu0.header['OS'] = osname
|
||
hdu0.header['PLATFORM'] = platf
|
||
|
||
#keep track of the number of targets processed and the time it took
|
||
nspec = len(targetid)
|
||
hdu0.header['NSPEC'] = nspec
|
||
|
||
|
||
hdulist = [hdu0]
|
||
|
||
#sptab extension
|
||
cols = {}
|
||
cols['SUCCESS'] = success
|
||
cols['TARGETID'] = targetid
|
||
cols['SRCFILE'] = srcfile
|
||
cols['BESTGRID'] = bestgrid
|
||
cols['TEFF'] = np.array(teff)*units.K
|
||
cols['LOGG'] = np.array(logg)
|
||
cols['FEH'] = np.array(feh)
|
||
cols['ALPHAFE'] = np.array(alphafe)
|
||
cols['LOG10MICRO'] = np.array(micro)
|
||
cols['PARAM'] = np.array(param)
|
||
cols['COVAR'] = np.array(covar)
|
||
cols['CHISQ_TOT'] = np.array(chisq_tot)
|
||
cols['SNR_MED'] = np.array(snr_med)
|
||
cols['RV_ADOP'] = np.array(rv_adop)*units.km/units.s
|
||
cols['RV_ERR'] = np.array(rv_err)*units.km/units.s
|
||
|
||
colcomm = {
|
||
'success': 'Bit indicating whether the code has likely produced useful results',
|
||
'TARGETID': 'DESI targetid',
|
||
'SRCFILE': 'DESI data file',
|
||
'BESTGRID': 'Model grid that produced the best fit',
|
||
'TEFF': 'Effective temperature (K)',
|
||
'LOGG': 'Surface gravity (g in cm/s**2)',
|
||
'FEH': 'Metallicity [Fe/H] = log10(N(Fe)/N(H)) - log10(N(Fe)/N(H))sun' ,
|
||
'ALPHAFE': 'Alpha-to-iron ratio [alpha/Fe]',
|
||
'LOG10MICRO': 'Log10 of Microturbulence (km/s)',
|
||
'PARAM': 'Array of atmospheric parameters ([Fe/H], [a/Fe], log10micro, Teff,logg)',
|
||
'COVAR': 'Covariance matrix for ([Fe/H], [a/Fe], log10micro, Teff,logg)',
|
||
'ELEM': 'Elemental abundance ratios to hydrogen [elem/H]',
|
||
'ELEM_ERR': 'Uncertainties in the elemental abundance ratios',
|
||
'CHISQ_TOT': 'Total chi**2',
|
||
'SNR_MED': 'Median signal-to-ratio',
|
||
'RV_ADOP': 'Adopted Radial Velocity (km/s)',
|
||
'RV_ERR': 'Uncertainty in the adopted Radial Velocity (km/s)'
|
||
}
|
||
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = 'SPTAB')
|
||
#hdu.header['EXTNAME']= ('SPTAB', 'Stellar Parameter Table')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
|
||
#aux extension
|
||
#p = ['[Fe/H]','[a/Fe]','log10micro','Teff','logg']
|
||
#cols = {}
|
||
#colcomm = {}
|
||
#cols['p'] = [p]
|
||
#colcomm['p'] = 'PARAM tags'
|
||
|
||
#table = tbl.Table(cols)
|
||
#hdu=fits.BinTableHDU(table,name = 'AUX')
|
||
|
||
#k = 0
|
||
#for entry in colcomm.keys():
|
||
# print(entry)
|
||
# hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
# k+=1
|
||
#hdulist.append(hdu)
|
||
|
||
|
||
#hdul=fits.HDUList(hdulist)
|
||
#hdul.writeto('sptab_'+root+'.fits')
|
||
|
||
#now we handle the extensions with the fluxes
|
||
|
||
edata=np.loadtxt(err[0])
|
||
if (len(mdl) > 0):
|
||
mdata=np.loadtxt(mdl[0])
|
||
if (len(nrd) > 0):
|
||
odata=np.loadtxt(nrd[0])
|
||
|
||
|
||
i = 0
|
||
j1 = 0
|
||
|
||
|
||
if len(xbandfiles) > 0:
|
||
for entry in band:
|
||
j2 = j1 + npix[i]
|
||
print(entry,i,npix[i],j1,j2)
|
||
#colx = fits.Column(name='wavelength',format='e8', array=array(x[j1:j2]))
|
||
#coldefs = fits.ColDefs([colx])
|
||
#hdu = fits.BinTableHDU.from_columns(coldefs)
|
||
hdu = fits.ImageHDU(name=entry+'_WAVELENGTH', data=x[j1:j2])
|
||
hdulist.append(hdu)
|
||
|
||
cols = {}
|
||
colcomm = {}
|
||
if odata.ndim == 2: tdata = odata[:,j1:j2]
|
||
else: tdata = odata[j1:j2][None,:]
|
||
cols['obs'] = tdata
|
||
colcomm['obs'] = 'Observed spectra as fit'
|
||
if edata.ndim == 2: tdata = edata[:,j1:j2]
|
||
else: tdata = edata[j1:j2][None,:]
|
||
cols['err'] = tdata
|
||
colcomm['err'] = 'Error in spectra as fit'
|
||
if (len(mdl) > 0):
|
||
if mdata.ndim == 2: tdata = mdata[:,j1:j2]
|
||
else: tdata = mdata[j1:j2][None,:]
|
||
cols['fit'] = tdata
|
||
colcomm['fit'] = 'Best-fitting model'
|
||
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = entry+'_MODEL')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
i += 1
|
||
j1 = j2
|
||
|
||
else:
|
||
print('single wavelength entry!')
|
||
#colx = fits.Column(name='wavelength',format='e8', array=array(x[j1:j2]))
|
||
#coldefs = fits.ColDefs([colx])
|
||
#hdu = fits.BinTableHDU.from_columns(coldefs)
|
||
hdu = fits.ImageHDU(name='WAVELENGTH', data=x)
|
||
hdulist.append(hdu)
|
||
|
||
cols = {}
|
||
colcomm = {}
|
||
if odata.ndim == 2: tdata = odata[:,:]
|
||
else: tdata = odata[:][None,:]
|
||
cols['obs'] = tdata
|
||
colcomm['obs'] = 'Observed spectra as fit'
|
||
if edata.ndim == 2: tdata = edata[:,:]
|
||
else: tdata = edata[:][None,:]
|
||
cols['err'] = tdata
|
||
colcomm['err'] = 'Error in spectra as fit'
|
||
if (len(mdl) > 0):
|
||
if mdata.ndim == 2: tdata = mdata[:,:]
|
||
else: tdata = mdata[:][None,:]
|
||
cols['fit'] = tdata
|
||
colcomm['fit'] = 'Best-fitting model'
|
||
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = 'MODEL')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
|
||
|
||
hdul=fits.HDUList(hdulist)
|
||
hdul.writeto(root+'.fits')
|
||
|
||
return None
|
||
|
||
def wtabmodfits(root, path=None):
|
||
|
||
"""Write out DESI MWS SP pipeline output
|
||
|
||
Parameters
|
||
----------
|
||
root: str
|
||
name of the root for input/output FERRE/BAS files
|
||
with extensions .opf, .wav, .nrd, .mdl
|
||
|
||
path: string
|
||
path to files
|
||
(default is None, and the code looks for the FERRE/BAS files
|
||
in the current folder)
|
||
|
||
Returns
|
||
-------
|
||
An SPTAB file with the parameters, and an SPMOD with the spectra
|
||
|
||
"""
|
||
|
||
if path is None: path=""
|
||
proot=os.path.join(path,root)
|
||
|
||
o=glob.glob(proot+".opf")
|
||
|
||
xbandfiles = sorted(glob.glob(proot+'-*.wav'))
|
||
band = []
|
||
npix = []
|
||
for entry in xbandfiles:
|
||
print('entry=',entry)
|
||
match = re.search('-[\w]*.wav',entry)
|
||
tag = match.group()[1:-4]
|
||
if match: band.append(tag.upper())
|
||
x = loadtxt(proot+'-'+tag+'.wav')
|
||
npix.append(len(x))
|
||
|
||
print('proot+.wav=',proot+'.wav')
|
||
print('xbandfiles=',xbandfiles)
|
||
x = np.loadtxt(proot+'.wav')
|
||
if len(npix) == 0: npix.append(len(x))
|
||
|
||
m=glob.glob(proot+".mdl")
|
||
e=glob.glob(proot+".err")
|
||
n=glob.glob(proot+".nrd")
|
||
|
||
fmp=glob.glob(proot+".fmp.fits")
|
||
scr=glob.glob(proot+".scr.fits")
|
||
|
||
if len(fmp) > 0:
|
||
ff=fits.open(fmp[0])
|
||
fibermap=ff[1]
|
||
|
||
if len(scr) > 0:
|
||
fs=fits.open(scr[0])
|
||
scores=fs[1]
|
||
|
||
success=[]
|
||
targetid=[]
|
||
target_ra=[]
|
||
target_dec=[]
|
||
ref_id=[]
|
||
ref_cat=[]
|
||
srcfile=[]
|
||
bestgrid=[]
|
||
teff=[]
|
||
logg=[]
|
||
feh=[]
|
||
alphafe=[]
|
||
micro=[]
|
||
param=[]
|
||
covar=[]
|
||
elem=[]
|
||
elem_err=[]
|
||
snr_med=[]
|
||
chisq_tot=[]
|
||
rv_adop=[]
|
||
rv_err=[]
|
||
|
||
of=open(o[0],'r')
|
||
|
||
for line in of:
|
||
|
||
cells=line.split()
|
||
#k = int(cells[0]) # the very first line gives the index (1,2...) for the successful grid
|
||
bestgrid.append(o[0])
|
||
id = cells[0]
|
||
cells = cells[1:]
|
||
|
||
ncells = len(cells)
|
||
ndim = ncells - 3
|
||
if ndim > 10:
|
||
ndim = int(np.sqrt(4*ndim+1)-1)
|
||
ndim = ndim // 2
|
||
|
||
assert (ncells > 6), 'Error, the file '+o[0]+' has less than 7 columns, which would correspond to ndim=2'
|
||
par = np.zeros(ndim)
|
||
#cov = np.zeros(ndim*ndim+ndim)
|
||
cov = np.zeros((5,5))
|
||
|
||
print('ndim=',ndim)
|
||
print('ncells=',ncells)
|
||
|
||
|
||
if (ndim == 2):
|
||
#white dwarfs 2 dimensions: id, 2 par, 2err, 0., med_snr, lchi, 2x2 cov
|
||
feh.append(-10.)
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(np.nan)
|
||
micro.append(np.nan)
|
||
|
||
|
||
elif (ndim == 3):
|
||
#synple grids with Teff, logg and [Fe/H]
|
||
feh.append(float(cells[2]))
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(np.nan)
|
||
micro.append(np.nan)
|
||
|
||
elif (ndim == 4):
|
||
#Teff, logg, [Fe/H] and [a/Fe]
|
||
feh.append(float(cells[2]))
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(float(cells[3]))
|
||
micro.append(np.nan)
|
||
|
||
elif (ndim == 5):
|
||
#Teff, logg, [Fe/H], [a/Fe] and ?
|
||
#see Allende Prieto et al. (2018, A&A)
|
||
feh.append(float(cells[2]))
|
||
teff.append(float(cells[0]))
|
||
logg.append(float(cells[1]))
|
||
alphafe.append(float(cells[3]))
|
||
micro.append(np.nan)
|
||
|
||
|
||
chisq_tot.append(10.**float(cells[2+2*ndim]))
|
||
snr_med.append(float(cells[1+2*ndim]))
|
||
rv_adop.append(float(cells[0+2*ndim]))
|
||
rv_err.append(np.nan)
|
||
par = np.array(cells[0:ndim],dtype=float)
|
||
cov = np.array(cells[2*ndim:],dtype=float)
|
||
param.append(par)
|
||
covar.append(cov)
|
||
|
||
targetid.append(np.int64(id))
|
||
srcfile.append(root)
|
||
|
||
if (chisq_tot[-1] < 1.5 and snr_med[-1] > 5.): # chi**2<1.5 and S/N>5
|
||
success.append(1)
|
||
else: success.append(0)
|
||
|
||
|
||
|
||
#add info copied from fibermap
|
||
nspec = len(targetid)
|
||
try:
|
||
#targetid=fibermap.data['targetid']
|
||
target_ra=fibermap.data['target_ra']
|
||
target_dec=fibermap.data['target_dec']
|
||
ref_id=fibermap.data['ref_id']
|
||
ref_cat=fibermap.data['ref_cat']
|
||
except NameError:
|
||
target_ra=np.zeros(nspec)
|
||
target_dec=np.zeros(nspec)
|
||
ref_id=np.zeros(nspec,dtype=int64)
|
||
ref_cat=np.array(["" for x in range(nspec)])
|
||
|
||
#primary extension
|
||
hdu0=fits.PrimaryHDU()
|
||
|
||
#find out processing date and add it to primary header
|
||
now = datetime.datetime.fromtimestamp(time.time())
|
||
nowstr = now.isoformat()
|
||
nowstr = nowstr[:nowstr.rfind('.')]
|
||
hdu0.header['DATE'] = nowstr
|
||
#hdu0.header['FCONFIG'] = config
|
||
|
||
#find out host machine and add info to header
|
||
try:
|
||
host=os.environ['HOST']
|
||
except:
|
||
host='Unknown'
|
||
hdu0.header['HOST'] = host
|
||
#find out OS name/platform
|
||
osname = os.name
|
||
platf = platform.system() + ' '+ platform.release()
|
||
hdu0.header['OS'] = osname
|
||
hdu0.header['PLATFORM'] = platf
|
||
|
||
#keep track of the number of targets processed and the time it took
|
||
hdu0.header['NSPEC'] = nspec
|
||
#ftiming = get_ferre_timings(proot)
|
||
#hdu0.header['FTIME'] = ftiming
|
||
#stiming = get_slurm_timings(proot)
|
||
#hdu0.header['STIME'] = stiming
|
||
#ncores = get_slurm_cores(proot)
|
||
#hdu0.header['NCORES'] = ncores
|
||
|
||
#get versions and enter then in primary header
|
||
ver = get_versions()
|
||
for entry in ver.keys(): hdu0.header[entry] = ver[entry]
|
||
|
||
hdulist = [hdu0]
|
||
|
||
#sptab extension
|
||
cols = {}
|
||
cols['SUCCESS'] = success
|
||
cols['TARGETID'] = targetid
|
||
cols['TARGET_RA'] = target_ra
|
||
cols['TARGET_DEC'] = target_dec
|
||
cols['REF_ID'] = ref_id
|
||
cols['REF_CAT'] = ref_cat
|
||
cols['SRCFILE'] = srcfile
|
||
cols['BESTGRID'] = bestgrid
|
||
cols['TEFF'] = np.array(teff)*units.K
|
||
cols['LOGG'] = np.array(logg)
|
||
cols['FEH'] = np.array(feh)
|
||
cols['ALPHAFE'] = np.array(alphafe)
|
||
cols['LOG10MICRO'] = np.array(micro)
|
||
cols['PARAM'] = np.vstack ( (teff, logg, feh, alphafe, micro) ).T
|
||
cols['COVAR'] = np.array(covar) #.reshape(len(success),5,5)
|
||
#cols['ELEM'] = np.array(elem)
|
||
#cols['ELEM_ERR'] = np.array(elem_err)
|
||
cols['CHISQ_TOT'] = np.array(chisq_tot)
|
||
cols['SNR_MED'] = np.array(snr_med)
|
||
cols['RV_ADOP'] = np.array(rv_adop)*units.km/units.s
|
||
cols['RV_ERR'] = np.array(rv_err)*units.km/units.s
|
||
|
||
colcomm = {
|
||
'success': 'Bit indicating whether the code has likely produced useful results',
|
||
'TARGETID': 'DESI targetid',
|
||
'TARGET_RA': 'Target Right Ascension (deg) -- details in FIBERMAP',
|
||
'TARGET_DEC': 'Target Declination (deg) -- details in FIBERMAP',
|
||
'REF_ID': 'Astrometric cat refID (Gaia SOURCE_ID)',
|
||
'REF_CAT': 'Astrometry reference catalog',
|
||
'SRCFILE': 'DESI data file',
|
||
'BESTGRID': 'Model grid that produced the best fit',
|
||
'TEFF': 'Effective temperature (K)',
|
||
'LOGG': 'Surface gravity (g in cm/s**2)',
|
||
'FEH': 'Metallicity [Fe/H] = log10(N(Fe)/N(H)) - log10(N(Fe)/N(H))sun' ,
|
||
'ALPHAFE': 'Alpha-to-iron ratio [alpha/Fe]',
|
||
'LOG10MICRO': 'Log10 of Microturbulence (km/s)',
|
||
'PARAM': 'Array of atmospheric parameters ([Fe/H], [a/Fe], log10micro, Teff,logg)',
|
||
'COVAR': 'Covariance matrix for ([Fe/H], [a/Fe], log10micro, Teff,logg)',
|
||
#'ELEM': 'Elemental abundance ratios to hydrogen [elem/H]',
|
||
#'ELEM_ERR': 'Uncertainties in the elemental abundance ratios',
|
||
'CHISQ_TOT': 'Total chi**2',
|
||
'SNR_MED': 'Median signal-to-ratio',
|
||
'RV_ADOP': 'Adopted Radial Velocity (km/s)',
|
||
'RV_ERR': 'Uncertainty in the adopted Radial Velocity (km/s)'
|
||
}
|
||
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = 'SPTAB')
|
||
#hdu.header['EXTNAME']= ('SPTAB', 'Stellar Parameter Table')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
|
||
#fibermap extension
|
||
if len(fmp) > 0:
|
||
hdu=fits.BinTableHDU.from_columns(fibermap, name='FIBERMAP')
|
||
hdulist.append(hdu)
|
||
ff.close()
|
||
|
||
#scores extension
|
||
if len(scr) > 0:
|
||
hdu=fits.BinTableHDU.from_columns(scores, name='SCORES')
|
||
hdulist.append(hdu)
|
||
fs.close()
|
||
|
||
#aux extension
|
||
#p = ['[Fe/H]','[a/Fe]','log10micro','Teff','logg']
|
||
#if 'elem' in conf: e = conf['elem']
|
||
#cols = {}
|
||
#colcomm = {}
|
||
#cols['p'] = [p]
|
||
#colcomm['p'] = 'PARAM tags'
|
||
#if 'elem' in conf:
|
||
# cols['e'] = [e]
|
||
# colcomm['e']= 'ELEM tags'
|
||
|
||
#table = tbl.Table(cols)
|
||
#hdu=fits.BinTableHDU(table,name = 'AUX')
|
||
|
||
#k = 0
|
||
#for entry in colcomm.keys():
|
||
# print(entry)
|
||
# hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
# k+=1
|
||
#hdulist.append(hdu)
|
||
|
||
|
||
hdul=fits.HDUList(hdulist)
|
||
hdul.writeto(os.path.join(path,'sptab_'+root+'.fits'))
|
||
|
||
#now spmod
|
||
hdulist = [hdu0]
|
||
|
||
|
||
edata=np.loadtxt(e[0])
|
||
mdata=np.loadtxt(m[0])
|
||
odata=np.loadtxt(n[0])
|
||
|
||
i = 0
|
||
j1 = 0
|
||
|
||
if len(xbandfiles) > 0:
|
||
for entry in band:
|
||
j2 = j1 + npix[i]
|
||
print(entry,i,npix[i],j1,j2)
|
||
#colx = fits.Column(name='wavelength',format='e8', array=array(x[j1:j2]))
|
||
#coldefs = fits.ColDefs([colx])
|
||
#hdu = fits.BinTableHDU.from_columns(coldefs)
|
||
hdu = fits.ImageHDU(name=entry+'_WAVELENGTH', data=x[j1:j2])
|
||
hdulist.append(hdu)
|
||
|
||
cols = {}
|
||
colcomm = {}
|
||
if odata.ndim == 2: tdata = odata[:,j1:j2]
|
||
else: tdata = odata[j1:j2][None,:]
|
||
cols['obs'] = tdata
|
||
colcomm['obs'] = 'Observed spectra as fit'
|
||
if edata.ndim == 2: tdata = edata[:,j1:j2]
|
||
else: tdata = edata[j1:j2][None,:]
|
||
cols['err'] = tdata
|
||
colcomm['err'] = 'Error in spectra as fit'
|
||
if mdata.ndim == 2: tdata = mdata[:,j1:j2]
|
||
else: tdata = mdata[j1:j2][None,:]
|
||
cols['fit'] = tdata
|
||
colcomm['fit'] = 'Best-fitting model'
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = entry+'_MODEL')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
i += 1
|
||
j1 = j2
|
||
|
||
else:
|
||
|
||
print('single wavelength entry!')
|
||
#colx = fits.Column(name='wavelength',format='e8', array=array(x[j1:j2]))
|
||
#coldefs = fits.ColDefs([colx])
|
||
#hdu = fits.BinTableHDU.from_columns(coldefs)
|
||
hdu = fits.ImageHDU(name='WAVELENGTH', data=x)
|
||
hdulist.append(hdu)
|
||
|
||
cols = {}
|
||
colcomm = {}
|
||
if odata.ndim == 2: tdata = odata[:,:]
|
||
else: tdata = odata[:][None,:]
|
||
cols['obs'] = tdata
|
||
colcomm['obs'] = 'Observed spectra as fit'
|
||
if edata.ndim == 2: tdata = edata[:,:]
|
||
else: tdata = edata[:][None,:]
|
||
cols['err'] = tdata
|
||
colcomm['err'] = 'Error in spectra as fit'
|
||
if mdata.ndim == 2: tdata = mdata[:,:]
|
||
else: tdata = mdata[:][None,:]
|
||
cols['fit'] = tdata
|
||
colcomm['fit'] = 'Best-fitting model'
|
||
|
||
|
||
table = tbl.Table(cols)
|
||
hdu=fits.BinTableHDU(table,name = 'MODEL')
|
||
k = 0
|
||
for entry in colcomm.keys():
|
||
print(entry)
|
||
hdu.header['TCOMM'+"{:03d}".format(k+1)] = colcomm[entry]
|
||
k+=1
|
||
hdulist.append(hdu)
|
||
|
||
|
||
if len(fmp) > 0:
|
||
ff=fits.open(fmp[0])
|
||
fibermap=ff[1]
|
||
hdu=fits.BinTableHDU.from_columns(fibermap, name='FIBERMAP')
|
||
#hdu.header['EXTNAME']='FIBERMAP'
|
||
hdulist.append(hdu)
|
||
|
||
if len(scr) > 0:
|
||
ff=fits.open(scr[0])
|
||
scores=ff[1]
|
||
hdu=fits.BinTableHDU.from_columns(scores, name='SCORES')
|
||
hdulist.append(hdu)
|
||
|
||
hdul=fits.HDUList(hdulist)
|
||
hdul.writeto(os.path.join(path,'spmod_'+root+'.fits'))
|
||
|
||
return None
|
||
|
||
#get dependencies versions, shamelessly copied from rvspec (Koposov's code)
|
||
def get_dep_versions():
|
||
"""
|
||
Get Packages versions
|
||
"""
|
||
|
||
import importlib
|
||
|
||
packages = [
|
||
'numpy', 'astropy', 'matplotlib', 'scipy',
|
||
'yaml'
|
||
]
|
||
# Ideally you need to check that the list here matches the requirements.txt
|
||
ret = {}
|
||
for curp in packages:
|
||
ret[curp[:8]] = importlib.import_module(curp).__version__
|
||
ret['python'] = str.split(sys.version, ' ')[0]
|
||
return ret
|
||
|
||
|
||
|
||
#find out versions
|
||
def get_versions():
|
||
|
||
ver = get_dep_versions()
|
||
ver['synple'] = 0
|
||
log1file = glob.glob("*.log_01")
|
||
fversion = 'unknown'
|
||
if len(log1file) < 1:
|
||
print("Warning: cannot find any *.log_01 file in the working directory")
|
||
else:
|
||
l1 = open(log1file[0],'r')
|
||
#while 1:
|
||
# line = l1.readline()
|
||
for line in l1:
|
||
if 'f e r r e' in line:
|
||
entries = line.split()
|
||
fversion = entries[-1][1:]
|
||
break
|
||
l1.close()
|
||
ver['ferre'] = fversion
|
||
|
||
return(ver)
|
||
|
||
|
||
def fparams(root,synthfile=None,figure=None):
|
||
""""Evaluates the agreement between input/output parameters in
|
||
FERRE (root.ipf and root.opf) files
|
||
|
||
Parameters
|
||
----------
|
||
root: str
|
||
root for the input/output parameter FERRE files
|
||
synthfile: str
|
||
associated FERRE/BAS synthfile to look for labels and other info
|
||
figure: str
|
||
name for the output file with a summary figure (usually a png file)
|
||
(default is None, and the plot is only shown on the screen)
|
||
|
||
Returns
|
||
-------
|
||
result: numpy array of floats
|
||
16-50-84 percentiles for all the parameters
|
||
|
||
"""
|
||
|
||
if synthfile is None:
|
||
synthfile = 'f_'+root+'.dat'
|
||
if not os.path.exists(synthfile):
|
||
synthfile = 'n_'+root+'.dat'
|
||
if not os.path.exists(synthfile):
|
||
synthfile = 'l_'+root+'.dat'
|
||
if not os.path.exists(synthfile):
|
||
synthfile = 'f_'+root+'.pickle'
|
||
if not os.path.exists(synthfile):
|
||
synthfile = 'n_'+root+'.pickle'
|
||
if not os.path.exists(synthfile):
|
||
synthfile = 'l_'+root+'.pickle'
|
||
assert os.path.exists(synthfile),'cannot find synthfile:'+synthfile
|
||
|
||
h = head_synth(synthfile)
|
||
if type(h) is list: h = h[1]
|
||
ndim = int(h['N_OF_DIM'])
|
||
v = np.loadtxt(root + '.ipf',usecols=np.arange(ndim)+1)
|
||
o = np.loadtxt(root + '.opf',usecols=np.arange(ndim)+1)
|
||
result = np.percentile(o-v,[15.85,50.,84.15],axis=0)
|
||
|
||
|
||
plt.clf()
|
||
for i in range(ndim):
|
||
plt.subplot(2,ndim,i+1)
|
||
plt.plot(v[:,i],o[:,i],'.')
|
||
if 'LABEL('+str(i+1)+')' in h: plt.title(h['LABEL('+str(i+1)+')'])
|
||
if i == 0: plt.ylabel('output')
|
||
for i in range(ndim):
|
||
plt.subplot(2,ndim,i+1+ndim)
|
||
hi = plt.hist(o[:,i]-v[:,i],bins=20)
|
||
if i == 0: plt.xlabel('difference')
|
||
plt.text(hi[1][0],max(hi[0])*0.6, "p16" % result[0,i] )
|
||
plt.text(hi[1][0],max(hi[0])*0.4, "p50" % result[1,i] )
|
||
plt.text(hi[1][0],max(hi[0])*0.2, "p84" % result[2,i] )
|
||
plt.text(result[0,i],max(hi[0])*0.6, "%6.2f" % result[0,i] )
|
||
plt.text(result[1,i],max(hi[0])*0.4, "%6.2f" % result[1,i] )
|
||
plt.text(result[2,i],max(hi[0])*0.2, "%6.2f" % result[2,i] )
|
||
|
||
manager = plt.get_current_fig_manager()
|
||
#manager.resize(*manager.window.maxsize())
|
||
|
||
plt.ion()
|
||
plt.show()
|
||
|
||
if figure is not None:
|
||
plt.savefig(figure)
|
||
|
||
return(result)
|
||
|
||
|
||
def desida(path_to_data='healpix',path_to_output='sp_output',
|
||
synthfile=None, seconds_per_target=2.,star=True):
|
||
|
||
""" Prepare a DESI data for parallel processing
|
||
"""
|
||
|
||
|
||
python_path1=os.environ['HOME']+"/synple"
|
||
pwd=os.path.abspath(os.curdir)
|
||
|
||
try:
|
||
host=os.environ['HOST']
|
||
except:
|
||
host='Unknown'
|
||
now=time.strftime("%c")
|
||
|
||
if synthfile is None:
|
||
synthfile1 = 'None'
|
||
else:
|
||
synthfile1 = "'"+str(synthfile)+"'"
|
||
|
||
|
||
infiles = list(glob.iglob(os.path.join(path_to_data,'**',
|
||
'coadd*fits'), recursive=True))
|
||
|
||
folders = []
|
||
for entry in infiles:
|
||
parts = entry.split('/')
|
||
infile = parts[-1]
|
||
folder = ('/'.join(parts[-5:-1]))
|
||
tpath = os.path.join(path_to_output,folder)
|
||
|
||
os.makedirs(tpath,exist_ok=True)
|
||
|
||
minutes = 4000*seconds_per_target/60.
|
||
root = infile[:-5]
|
||
|
||
sfile = os.path.join(tpath,root+'.job')
|
||
outfile = os.path.join(tpath,root)
|
||
|
||
print('infile=',entry)
|
||
print('outfile=',outfile)
|
||
s = open(sfile,'w')
|
||
s.write("#!/bin/bash \n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("#This script was written by synple.py on "+now+" \n")
|
||
s.write("#SBATCH --time="+str(int(minutes)+1)+"\n") #minutes
|
||
s.write("#SBATCH --ntasks=1" + "\n")
|
||
s.write("#SBATCH --nodes=1" + "\n")
|
||
if (host == 'login1'): #lapalma
|
||
nthreads = 4
|
||
s.write("#SBATCH -J "+str(root)+" \n")
|
||
s.write("#SBATCH -o "+str(root)+"_%j.out"+" \n")
|
||
s.write("#SBATCH -e "+str(root)+"_%j.err"+" \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(16)+"\n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("module load python"+"\n")
|
||
else: # perlmutter
|
||
nthreads = 4
|
||
s.write("#SBATCH --qos=regular" + "\n")
|
||
s.write("#SBATCH --constraint=cpu" + "\n")
|
||
s.write("#SBATCH --account=desi \n")
|
||
s.write("#SBATCH --cpus-per-task="+str(128*2)+"\n")
|
||
s.write("#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# \n")
|
||
s.write("module load python"+"\n")
|
||
|
||
s.write("cd "+pwd+"\n\n")
|
||
|
||
command="python3 -c \"import sys; " + \
|
||
" sys.path.insert(0, '"+python_path1 + "'); " + \
|
||
" nthreads = " + str(nthreads) + ";" + \
|
||
" import os; os.environ['OMP_NUM_THREADS'] = str(nthreads) ; "+ \
|
||
" os.environ['OPENBLAS_NUM_THREADS'] = str(nthreads) ;" + \
|
||
" os.environ['MKL_NUM_THREADS'] = str(nthreads) ; " + \
|
||
" from synple import bas, wtabmodfits; " + \
|
||
" bas(\'" + entry + "\'," + \
|
||
" outfile=\'" + outfile + "\'," + \
|
||
" synthfile=" + str(synthfile1) + ", star= " + str(star) + "); " + \
|
||
" wtabmodfits(\'" + root + "'" + ", path= '" + tpath + "\'" + \
|
||
")\"" + "\n"
|
||
|
||
s.write(command)
|
||
s.close()
|
||
os.chmod(sfile,0o755)
|
||
|
||
|
||
|
||
return()
|
||
|
||
|
||
def desimask(desi_target):
|
||
|
||
"""Returns targetting classes from DESI_TARGET bits in the FIBERMAP
|
||
extension of DESI data files
|
||
"""
|
||
|
||
mask = [('LRG', 0, 1),
|
||
('ELG', 1, 2),
|
||
('QSO', 2, 4),
|
||
('LRG_1PASS', 3, 8),
|
||
('LRG_2PASS', 4, 16),
|
||
('LRG_NORTH', 8, 256),
|
||
('ELG_NORTH', 9, 512),
|
||
('QSO_NORTH', 10, 1024),
|
||
('LRG_SOUTH', 16, 65536),
|
||
('ELG_SOUTH', 17, 131072),
|
||
('QSO_SOUTH', 18, 262144),
|
||
('LRG_1PASS_NORTH', 24, 16777216),
|
||
('LRG_2PASS_NORTH', 25, 33554432),
|
||
('LRG_1PASS_SOUTH', 28, 268435456),
|
||
('LRG_2PASS_SOUTH', 29, 536870912),
|
||
('SKY', 32, 4294967296),
|
||
('STD_FAINT', 33, 8589934592),
|
||
('STD_WD', 34, 17179869184),
|
||
('STD_BRIGHT', 35, 34359738368),
|
||
('BAD_SKY', 36, 68719476736),
|
||
('SUPP_SKY', 37, 137438953472),
|
||
('NO_TARGET', 49, 562949953421312),
|
||
('BRIGHT_OBJECT', 50, 1125899906842624),
|
||
('IN_BRIGHT_OBJECT', 51, 2251799813685248),
|
||
('NEAR_BRIGHT_OBJECT', 52, 4503599627370496),
|
||
('BGS_ANY', 60, 1152921504606846976),
|
||
('MWS_ANY', 61, 2305843009213693952),
|
||
('SCND_ANY', 62, 4611686018427387904)]
|
||
|
||
bits = []
|
||
target = desi_target
|
||
for entry in reversed(mask):
|
||
if target - entry[2] >= 0:
|
||
target = target - entry[2]
|
||
bits.append(entry[0])
|
||
|
||
|
||
return(bits)
|
||
|
||
|
||
def mwsmask(mws_target):
|
||
|
||
"""Returns targetting classes from MWS_TARGET bits in the FIBERMAP
|
||
extension of DESI data files
|
||
"""
|
||
mask = [('MWS_BROAD', 0, 1),
|
||
('MWS_WD', 1, 2),
|
||
('MWS_NEARBY', 2, 4),
|
||
('MWS_BROAD_NORTH', 4, 16),
|
||
('MWS_BROAD_SOUTH', 5, 32),
|
||
('MWS_BHB', 6, 64),
|
||
('MWS_MAIN_BLUE', 8, 256),
|
||
('MWS_MAIN_BLUE_NORTH', 9, 512),
|
||
('MWS_MAIN_BLUE_SOUTH', 10, 1024),
|
||
('MWS_MAIN_RED', 11, 2048),
|
||
('MWS_MAIN_RED_NORTH', 12, 4096),
|
||
('MWS_MAIN_RED_SOUTH', 13, 8192),
|
||
('MWS_FAINT_BLUE', 14, 16384),
|
||
('MWS_FAINT_BLUE_NORTH', 15, 32768),
|
||
('MWS_FAINT_BLUE_SOUTH', 16, 65536),
|
||
('MWS_FAINT_RED', 17, 131072),
|
||
('MWS_FAINT_RED_NORTH', 18, 262144),
|
||
('MWS_FAINT_RED_SOUTH', 19, 524288),
|
||
('GAIA_STD_FAINT', 33, 8589934592),
|
||
('GAIA_STD_WD', 34, 17179869184),
|
||
('GAIA_STD_BRIGHT', 35, 34359738368),
|
||
('BACKUP_DIB', 57, 144115188075855872),
|
||
('BACKUP_GIANT_LOP', 58, 288230376151711744),
|
||
('BACKUP_GIANT', 59, 576460752303423488),
|
||
('BACKUP_BRIGHT', 60, 1152921504606846976),
|
||
('BACKUP_FAINT', 61, 2305843009213693952),
|
||
('BACKUP_VERY_FAINT', 62, 4611686018427387904)]
|
||
|
||
bits = []
|
||
target = mws_target
|
||
for entry in reversed(mask):
|
||
if target - entry[2] >= 0:
|
||
target = target - entry[2]
|
||
bits.append(entry[0])
|
||
|
||
|
||
return(bits)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
|
||
npar = len(sys.argv)
|
||
assert (npar >= 4), 'Synple requires at least 3 input parameters (modelfile wstart wend)'
|
||
assert (npar <= 7), 'Synple requires at maximum 6 input parameters (modelfile wstart wend vmicro vrot fwhm)'
|
||
vmicro = None
|
||
vrot = 0.0
|
||
fwhm = 0.0
|
||
modelfile = sys.argv[1]
|
||
wstart = float(sys.argv[2])
|
||
wend = float(sys.argv[3])
|
||
if (npar > 4):
|
||
vmicro = float(sys.argv[4])
|
||
if (npar > 5):
|
||
fwhm = float(sys.argv[5])
|
||
if (npar > 6):
|
||
vrot = float(sys.argv[6])
|
||
|
||
#symbol, mass, sol = elements()
|
||
s = syn(modelfile, (wstart,wend), save=True, vmicro=vmicro, vrot=vrot, fwhm=fwhm)
|
||
|
||
|