#! /usr/bin/env python
# --------------------------------------------------------------
# The asfgrid is a python module to compute asteroseismic
# parameters for a star with given stellar parameters and vice versa.
# Copyright (C) 2015 Sanjib Sharma, Dennis Stello
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
# --------------------------------------------------------------
"""
A module to compute asteroseismic parameters for
a star with given stellar parameters and vice versa.
Author Sanjib Sharma
Copyright (c) 2015 Sanjib Sharma, Dennis Stello
License: AGPL see .
Data files should be in current directory.
To run as a script
$./asfgrid.py --help
To use as module
::
>>> import asfgrid
>>> evstate=[1,1]
>>> logz=[-1.97,-1.98]
>>> teff=[4659.8,4903.2]
>>> dnu=[8.81,13.1]
>>> numax=[92.36,157.3]
>>> s=asfgrid.Seism()
>>> mass,radius=s.get_mass_radius(evstate,logz,teff,dnu,numax)
>>> print mass,radius
>>> logg=s.mr2logg(mass,radius)
>>> dnu,numax,fdnu=s.get_dnu_numax(evstate,logz,teff,mass,mass,logg)
>>> print dnu,numax
"""
import sys
import ebf
import numpy as np
import scipy.interpolate
__version__ = "0.0.5"
def _tocsv(filename,data,basekey=None,keylist=None,delimiter=', '):
"""
Write a dict or npstruct to a csv file
"""
if type(data) == dict:
with open(filename,'w') as fp:
if keylist==None:
keylist=data.keys()
if basekey == None:
nsize=data[keylist[0]].size
else:
nsize=data[basekey].size
keylist=[key for key in keylist if data[key].size==nsize]
# s=str(keylist)
# s=s[1:-1].replace("'",'')+'\n'
s=delimiter.join([str(key) for key in keylist])+'\n'
fp.write(s)
for i in range(data[keylist[0]].size):
s=', '.join([str(data[key][i]) for key in keylist])+'\n'
fp.write(s)
else:
with open(filename,'w') as fp:
if keylist==None:
s=str(data.dtype.names)
s=s[1:-1].replace("'",'')+'\n'
fp.write(s)
for temp in data:
s=str(temp)
s=s[1:-1].replace("'",'')+'\n'
fp.write(s)
else:
s=str(keylist)
s=s[1:-1].replace("'",'')+'\n'
fp.write(s)
for i in range(data[keylist[0]].size):
s=', '.join([str(data[key][i]) for key in keylist])+'\n'
fp.write(s)
print 'Written file:',filename
class _IGrid():
def __init__(self,data1,keys):
data=np.resize(data1,data1.size)
data.sort(order=keys)
self.keys=keys
self.vnames=[temp for temp in data.dtype.names if temp not in self.keys]
self.points=[np.unique(data[key]) for key in self.keys]
self.values={}
for vname in self.vnames:
self.values[vname]=data[vname].reshape([point.size for point in self.points])
self.points1=tuple([data[key] for key in self.keys])
self.values1={}
for vname in self.vnames:
self.values1[vname]=data[vname]
def homogenize_arrays(self,xi):
xj=[np.asarray(t) for t in xi]
temp=xj[0]
for t in xj:
temp=temp+t
xj=[np.zeros_like(temp)+t for t in xj]
return xj
def get_values(self,vname,xi,fill_value='nearest'):
fill_value1=np.nan
if type(xi) == list:
xi=np.array(self.homogenize_arrays(xi)).transpose()
t1=scipy.interpolate.interpn(self.points,self.values[vname],xi,bounds_error=False,fill_value=fill_value1)
if fill_value == 'nearest':
ind=np.where(np.isfinite(t1)==False)[0]
if ind.size>0:
print 'outside interp range',ind.size,' out of ',t1.size
if (xi.ndim == 1)&(len(self.keys)>1):
xi=xi.reshape([1,xi.size])
t1[ind]=scipy.interpolate.griddata(self.points1,self.values1[vname],xi[ind],method='nearest')
return t1
class Seism():
def __init__(self,datadir=None,z_solar=0.019):
# Change this to appropriate path
if datadir is None:
self.datadir=''
# self.datadir='/work1/sharma/Projects/kepler/data/dnu_grid6/'
else:
self.datadir=datadir
# set solar reference values
# self.radius= 6.958e8
# self.mass=1.99e30
# sun logg=np.log10(100.0*6.67259e-11*1.989e30/(6.958e8*6.958e8))
self.logg_solar=4.43796037457 # cgs unit
self.teff_solar=5777.0 # kelvin
self.numax_solar=3090.0 # micro Hz 3090+-30
# cannot change this
self.dnu_solar=135.1 # micro Hz 135.1
self.z_solar=z_solar # solar metallicity value
data1=ebf.read(self.datadir+'grid_interp1.ebf','/data')
data2=ebf.read(self.datadir+'grid_interp2.ebf','/data')
self.igrid1=_IGrid(data1,['evstate','logz','mass','logg_teff'])
self.igrid2=_IGrid(data2,['evstate','logz','mass_nu','logg_teff'])
def logg2r(self,logg,mass):
"""
From logg and mass compute radius
"""
return np.power(10.0,((self.logg_solar-logg)*0.5))*np.sqrt(mass)
def logg2m(self,logg,radius):
"""
From logg and radius compute mass
"""
return np.power(10.0,logg-self.logg_solar)*radius*radius
def logg2numax(self,logg,teff):
"""
From logg and teff compute numax with numax_solar=3090.0 microHz
"""
return (self.numax_solar)*np.power(10.0,logg-self.logg_solar)/(np.power(teff/self.teff_solar,0.5))
def numax2logg(self,numax,teff):
"""
From logg and teff compute numax with numax_solar=3090.0 microHz
"""
return np.log10((numax/self.numax_solar)*np.sqrt(teff/self.teff_solar))+self.logg_solar
def mr2rho(self,mass,sradius):
"""
From mass and radius compute rho_rho_solar
"""
return mass/np.power(sradius,3)
def mr2logg(self,mass,radius):
"""
From mass and radius compute logg
"""
return self.logg_solar+np.log10(mass/(radius*radius))
def kappa_m(self,dnu,numax):
"""
From dnu and numax compute kappa_m
"""
return np.power(numax/3090.0,3.0)*np.power(dnu/135.1,-4.0)
def kappa_r(self,dnu,numax):
"""
Not in original
From dnu and numax compute kappa_r
"""
return (numax/3090.0)*np.power(dnu/135.1,-2.0)
def mass_sc(self,dnu,numax,teff):
"""
From dnu, numax and teff compute mass according to scaling relation
Assumes dnu_solar=135.1 microHz and numax_solar=3090.0 microHz
"""
return np.power(numax/3090.0,3.0)*np.power(dnu/135.1,-4.0)*np.power(teff/self.teff_solar,1.5)
def _mass_dnu(self,dnu,logg):
"""
From dnu, logg compute mass according to scaling relation
Assumes dnu_solar=135.1 microHz
"""
return np.power(10.0,3*(logg-self.logg_solar))*np.power(dnu/(135.1),-4.0)
def _quantf(self,logg,teff):
"""
From logg and teff compute a quantity for interpolation that
is almost monotonic with age
"""
return np.log10(teff)+0.5*(np.tanh((logg-4.5)/0.25)+1)*logg*0.1
def get_dnu_numax(self,evstate,logz,teff,mini,mass,logg,fill_value='nearest',isfeh=False):
"""
Get average seismic parameters (dnu, numax) by interpolation on
a grid for a given (evstate, logz, teff, dnu, numax).
Assumption numax_solar=3090.0 microHz.
Args:
evstate (array): 1) Pre RGB 2) Post RGB
logz or feh (array): log(Z) log metallcity or [Fe/H]=log(Z/Z_solar)
logz (array): log(Z) log metallcity ([Fe/H]=log(Z/Z_solar))
teff (array): temperature
mini (array): initial mass
mass (array): actual mass with mass loss (mass <= mini).
logg (array): log(gravity)
fill_value : Deafault is 'nearest', to use nearest grid points
in case of input values being out of grid range.
Alternatively, one can use None
Returns:
dnu (array): Large frequency separation (micro Hz)
numax (array): Central frequency of max amplitude (micro Hz)
fdnu (array): the correction factor for Delta nu
"""
evstate=np.asarray(evstate)
logz=np.asarray(logz)
if isfeh is True:
logz=logz+np.log10(self.z_solar)
teff=np.asarray(teff)
mini=np.asarray(mini)
mass=np.asarray(mass)
logg=np.asarray(logg)
numax=self.logg2numax(logg,teff)
sradius=self.logg2r(logg,mass)
logg1=self.mr2logg(mini,sradius)
factor=self._get_fdnu(evstate,logz,teff,mini,logg1,fill_value= fill_value)
dnu=135.1*factor*np.power(mass,0.5)/np.power(sradius,1.5)
if (factor.size == 1)&(numax.ndim == 0):
return dnu[0],numax,factor[0]
else:
return dnu,numax,factor
def _get_fdnu(self,evstate,logz,teff,mass,logg,fill_value='nearest'):
evstate=np.asarray(evstate)
logz=np.asarray(logz)
teff=np.asarray(teff)
mass=np.asarray(mass)
logg=np.asarray(logg)
return self._from_mlogg('fdnu',evstate,logz,teff,mass,logg,fill_value= fill_value)
def _from_mlogg(self,quant,evstate,logz,teff,mini,logg,fill_value='nearest'):
"""
The driver function to perform interpolation on the grid
for a given (evstate, logz, teff, mini, logg)
Args:
quant (str): name of quantity for which answer is needed.
For example 'fdnu', 'age', etc
evstate (array): 1) Pre RGB 2) Post RGB
logz (array): log(Z) log metallcity ([Fe/H]=log(Z/Z_solar))
teff (array): temperature
mini (array): initial mass
logg (array): log(gravity)
"""
logz=np.asarray(logz)
logg_teff=self._quantf(logg,teff)
return self.igrid1.get_values(quant,[evstate,logz,mini,logg_teff],fill_value= fill_value)
def _from_freq(self,quant,evstate,logz,teff,dnu,numax,fill_value='nearest'):
"""
The driver function to perform interpolation on the grid
for a given (evstate, logz, teff, dnu, numax).
Args:
quant (str): name of quantity for which answer is needed.
For example 'fdnu', 'age', etc
evstate (array): 1) Pre RGB 2) Post RGB
logz (array): log(Z) log metallcity ([Fe/H]=log(Z/Z_solar))
teff (array): temperature
dnu (array): Large frequency separation (micro Hz)
numax (array): Central frequency of max amplitude (micro Hz)
"""
logz=np.asarray(logz)
logg=self.numax2logg(numax,teff)
mass_dnu=self._mass_dnu(dnu,logg)
logg_teff=self._quantf(logg,teff)
return self.igrid2.get_values(quant,[evstate,logz,mass_dnu,logg_teff],fill_value= fill_value)
def get_mass_radius(self,evstate,logz,teff,dnu,numax,fill_value='nearest',isfeh=False):
"""
Get mass and radius of stars by interpolation on a grid
for a given (evstate, logz, teff, dnu, numax).
Assumption numax_solar=3090.0 microHz.
Args:
evstate (array): 1) Pre RGB 2) Post RGB
logz or feh (array): log(Z) log metallcity or [Fe/H]=log(Z/Z_solar)
teff (array): temperature
dnu (array): Large frequency separation (micro Hz)
numax (array): Central frequency of max amplitude (micro Hz)
fill_value : Deafault is 'nearest', to use nearest grid points
in case of input values being out of grid range.
Alternatively, one can use None
isfeh : A flag with default value being False. If set to
True, the second argument is considered to be
[Fe/H]
"""
evstate=np.asarray(evstate)
logz=np.asarray(logz)
if isfeh is True:
logz=logz+np.log10(self.z_solar)
teff=np.asarray(teff)
dnu=np.asarray(dnu)
numax=np.asarray(numax)
mass=self._from_freq('mass',evstate,logz,teff,dnu,numax,fill_value= fill_value)
logg=self.numax2logg(numax,teff)
sradius=self.logg2r(logg,mass)
if (mass.size == 1)&(evstate.ndim == 0):
return mass[0],sradius[0]
else:
return mass,sradius
def _usage():
print "NAME:"
print "\t asfgrid 0.0.4 - computes asteroseismic freuqncies or masses"
print "\t Copyright (c) 2015 Sanjib Sharma and Dennis Stello "
print "\t License: AGPL see ."
print "\t Reference: Sharma et al. 2016, ApJ,822,15 \n"
print "USAGE:"
print "\t asfgrid inputfile \n"
print "DESCRIPTION:"
print "\t Outfile name is constructed from filename with suffix .out "
print "\t Input file should be ascii as follows"
print "\t evstate logz teff dnu numax"
print "\t 1 -1.97 4659.8 8.81 92.36"
print "\t 1 -1.98 4903.2 13.1 157.3 \n"
print "\t First line must contain column names"
print "\t Column names can be in any order but need to follow names"
print "\t given below"
print "OPTIONS:"
print "\t Possible input outputs are"
print "\t 1) (evstate, logz, teff, dnu, numax) ->(mass,radius)"
print "\t 2) (evstate, logz, teff, mass, logg) ->(dnu,numax,fdnu)"
print "\t 3) (evstate, logz, teff, mass, logg, mini)->(dnu,numax,fdnu)"
print "\t 4) (evstate, feh, teff, dnu, numax) ->(mass,radius)"
print "\t 5) (evstate, feh, teff, mass, logg) ->(dnu,numax,fdnu)"
print "\t 6) (evstate, feh, teff, mass, logg, mini) ->(dnu,numax,fdnu)"
print "\t Third and sixth option allows for mass loss if mass (dnu, numax,fdnu)'
data['dnu'],data['numax'],data['fdnu']=s.get_dnu_numax(data['evstate'],data['logz'],data['teff'],data['mini'],data['mass'],data['logg'])
keylist=keylist+['dnu','numax','fdnu']
else:
print '(evstate, logz, teff, mass, logg) -> (dnu, numax,fdnu)'
data['dnu'],data['numax'],data['fdnu']=s.get_dnu_numax(data['evstate'],data['logz'],data['teff'],data['mass'],data['mass'],data['logg'])
keylist=keylist+['dnu','numax','fdnu']
_tocsv(filename+'.out',data,keylist=keylist)
elif status2 == 1:
print '(evstate, logz, teff, dnu, numax) -> (mass,radius)'
data['mass'],data['radius']=s.get_mass_radius(data['evstate'],data['logz'],data['teff'],data['dnu'],data['numax'])
keylist=keylist+['mass','radius']
_tocsv(filename+'.out',data,keylist=keylist)