Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic ssp dev #37

Draft
wants to merge 48 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
bdcd3b5
not sure at this point how to organize everything but just for the sa…
AlexaVillaume Aug 22, 2023
af1f5b1
fixing a small issue with getting data_path
AlexaVillaume Aug 24, 2023
b470e88
fixing a small issue with getting data_path
AlexaVillaume Aug 24, 2023
9a44b80
renaming to be more informative
AlexaVillaume Aug 24, 2023
42216fa
whoops...this is why the renaming necessary
AlexaVillaume Aug 24, 2023
adb101a
need some safeguards wrt coverage between the isochrones and the stel…
AlexaVillaume Aug 24, 2023
a7f889b
making progress on getting MLCRs, including fixing SSP synthesis
AlexaVillaume Sep 11, 2023
c264505
adding salpeter imf to help troubleshoot and to help figure out where…
AlexaVillaume Sep 11, 2023
70fd5db
getting salpeter imf working
AlexaVillaume Sep 11, 2023
47f52ad
adding to the normalization functionality in the abstract IMF class
AlexaVillaume Sep 13, 2023
f3d4d8d
adding unit test for imf normalization
AlexaVillaume Sep 14, 2023
36355a8
the wonders of unit tests
AlexaVillaume Sep 14, 2023
f15e04d
added class for abritary two slope powerlawe IMF
AlexaVillaume Sep 14, 2023
69880e3
mostly just refactoring the forward function to make it easier for me…
AlexaVillaume Sep 14, 2023
5e3adba
small changes
AlexaVillaume Sep 14, 2023
522c0ef
leaving this to fix mist isochrone issue
AlexaVillaume Nov 21, 2023
01ad142
Wondering if the error I'm getting with some of the isochrones, e.g.,…
AlexaVillaume Nov 21, 2023
37de906
should not be making selections based on anything other than age at t…
AlexaVillaume Nov 22, 2023
ea875dd
Now that I have a clearer understanding of nature of the problem -- s…
AlexaVillaume Nov 22, 2023
9ec205f
at the very least the selection by metalliicity is wrong in get_isoch…
AlexaVillaume Nov 22, 2023
9726b68
close but no cigar selecting on both metallicity AS WELL AS age...but…
AlexaVillaume Nov 22, 2023
5773c3d
the issues with the determining the metallicity and age indices does …
AlexaVillaume Nov 22, 2023
785e37a
There is indeed a serious bug in the construction of the isochrone gr…
AlexaVillaume Nov 22, 2023
5e43c31
making progress fixing the bugs, something still not right about the …
AlexaVillaume Nov 22, 2023
0afb706
several bug fixes involving indexing, data typing, and some float iss…
AlexaVillaume Nov 22, 2023
dbf0ecd
checking full ssp synthesis in light of the bug fixes I made to the i…
AlexaVillaume Nov 22, 2023
76367f2
introducing the use of a lighthouse environment variable in the hopes…
AlexaVillaume Nov 22, 2023
425825b
trying to find all instances where environment variable should be used
AlexaVillaume Nov 22, 2023
ed73aa9
trying to find all instances where environment variable should be used
AlexaVillaume Nov 22, 2023
fbc3104
added unit test for enviornment variable
AlexaVillaume Nov 22, 2023
a3ae6f0
using environment variable
AlexaVillaume Nov 22, 2023
f87806b
misplaced forward slash
AlexaVillaume Nov 22, 2023
36e0990
think this is the last of directory_path variales that need to be rep…
AlexaVillaume Nov 22, 2023
c7506d1
fixed all the path stuff st I can delete the notebooks from this repo…
AlexaVillaume Nov 22, 2023
308d6e7
Merge branch 'main' into isochrone_dev
AlexaVillaume Nov 22, 2023
b48ef9f
cleaned up the function a bit
AlexaVillaume Nov 24, 2023
9fc441a
the normalization constant is a property of the form of the IMF alone
AlexaVillaume Nov 24, 2023
5dd697d
refactoring the Basic_SSP class to help make the whole development pr…
AlexaVillaume Nov 25, 2023
cca0633
refactoring Basic_SSP class in light of issues that came up while deb…
AlexaVillaume Nov 25, 2023
e34d809
found the reason for the discrepancy in the spectral unitsgit status …
AlexaVillaume Dec 4, 2023
d3a6be2
adding phase selection back in
AlexaVillaume Dec 7, 2023
d7eac3f
finally getting around to fixing the retrieval of the stellar atmosph…
AlexaVillaume Dec 7, 2023
8c4df1b
small fixes to convert variables to torch datatypes
AlexaVillaume Dec 7, 2023
2d92203
added lower and upper mass limits in get_isochrone
AlexaVillaume Dec 7, 2023
424651f
isochrone retrieval bug fixes
AlexaVillaume Dec 8, 2023
d35bb7e
changes to go with verify_ssps notebook
AlexaVillaume Dec 8, 2023
2da5cea
finally found the source of the normalization bug
AlexaVillaume Dec 8, 2023
3d54339
pushing everything else because now the Kroupa and Salpeter SSPs in L…
AlexaVillaume Dec 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 120 additions & 33 deletions lighthouse/SSP/basic_ssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,148 @@
from time import process_time as time

import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import quad

from .. import utils

class Basic_SSP():

def __init__(
self,
isochrone: "Isochrone",
isochrone_model: "Isochrone",
imf: "Initial_Mass_Function",
sas: "Stellar_Atmosphere_Spectrum",
):

self.isochrone = isochrone
self.isochrone_grid = isochrone_model
self.imf = imf
self.sas = sas

def forward(self, metalicity, Tage, alpha) -> torch.Tensor:

isochrone = self.isochrone.get_isochrone(metalicity, Tage)
self.isochrone = None
self.spectrum = None
self._imf_weights = None
self._imf_weights_v2 = None


@property
def imf_weights_v2(self) -> torch.Tensor:

# like alf

if self._imf_weights_v2 is None:

weights = self.imf.get_imf(self.isochrone["initial_mass"], mass_weighted=False)
weights = weights/self.imf.t0_normalization

self._imf_weights_v2 = weights

return self._imf_weights_v2



@property
def imf_weights(self) -> torch.Tensor:

# like fsps


if self._imf_weights is None:

initial_stellar_masses = self.isochrone["initial_mass"]

lower_limit = self.imf.lower_limit
upper_limit = self.imf.upper_limit

weights = torch.zeros(initial_stellar_masses.shape)
for i, mass in enumerate(initial_stellar_masses):
if initial_stellar_masses[i] < lower_limit or initial_stellar_masses[i] > upper_limit:
print("Bounds of isochrone exceed limits of full IMF")
if i == 0:
m1 = lower_limit # ala fsps aka
else:
m1 = initial_stellar_masses[i] - 0.5*(initial_stellar_masses[i] - initial_stellar_masses[i-1])
if i == len(initial_stellar_masses) - 1:
m2 = initial_stellar_masses[i]
else:
m2 = initial_stellar_masses[i] + 0.5*(initial_stellar_masses[i+1] - initial_stellar_masses[i])

if m2 < m1:
print("IMF_WEIGHT WARNING: non-monotonic mass!", m1, m2, m2-m1)
continue

if m2 == m1:
print("m2 == m1")
continue

weights[i], error = quad(self.imf.get_imf,
m1, m2,
args=(False,) ) # i.e., not mass-weighting

self._imf_weights = weights/self.imf.t0_normalization

return self._imf_weights



def spectral_synthesis(self, metalicity, Tage) -> torch.Tensor:

isochrone = self.isochrone
imf_weights = self.imf_weights_v2


## https://waps.cfa.harvard.edu/MIST/README_tables.pdf
## FSPS phase type defined as follows:
## -1=PMS, 0=MS, 2=RGB, 3=CHeB, 4=EAGB,
## 5=TPAGB, 6=postAGB, 9=WR

# Main Sequence isochrone integration
CHOOSE = isochrone["phase"] <= 2
spectra = torch.stack(tuple(
self.sas.get_spectrum(
tf,
lg,
metalicity,
) for lg, tf in zip(isochrone["log_g"][CHOOSE], isochrone["Teff"][CHOOSE])
MS = torch.logical_and(isochrone["phase"] >= 0, isochrone["phase"] <= 2)
# Horizontal Branch isochrone integration
HB = torch.logical_and(isochrone["phase"] > 2, isochrone["phase"] <= 5)


ms_spectra = torch.stack(tuple(
self.sas.get_spectrum(tf, lg, metalicity,
) for lg, tf in zip(isochrone["log_g"][MS], isochrone["Teff"][MS])
)).T
spectrum = torch.zeros(spectra.shape[0])
spectrum += torch.vmap(partial(torch.trapz, x = isochrone["initial_mass"][CHOOSE]))(
(spectra * self.imf.get_weight(
isochrone["initial_mass"][CHOOSE],
alpha,
) * 10**isochrone["log_l"][CHOOSE]),
)
ms_spectra *= 10**isochrone["log_l"][MS]

# Horizontal Branch isochrone integration
CHOOSE = torch.logical_and(isochrone["phase"] > 2, isochrone["phase"] <= 5)
spectra = torch.stack(tuple(
self.sas.get_spectrum(
tf,
lg,
metalicity,
) for lg, tf in zip(isochrone["log_g"][CHOOSE], isochrone["Teff"][CHOOSE])
hb_spectra = torch.stack(tuple(
self.sas.get_spectrum(tf, lg, metalicity,
) for lg, tf in zip(isochrone["log_g"][HB], isochrone["Teff"][HB])
)).T
spectrum += torch.vmap(partial(torch.trapz, x = isochrone["initial_mass"][CHOOSE]))(
(spectra * self.imf.get_weight(
isochrone["initial_mass"][CHOOSE],
alpha,
) * 10**isochrone["log_l"][CHOOSE]),
)
hb_spectra *= 10**isochrone["log_l"][HB]

spectrum = torch.zeros(ms_spectra.shape[0])

spectrum += torch.vmap(partial(torch.trapz, x = isochrone["initial_mass"][MS])) (
imf_weights[MS]*ms_spectra
)
spectrum += torch.vmap(partial(torch.trapz, x = isochrone["initial_mass"][HB])) (
imf_weights[HB]*hb_spectra
)



# SSP in L_sun Hz^-1, CvD models in L_sun micron^-1, convert
spectrum *= utils.light_speed/self.sas.wavelength**2
spectrum *= utils.light_speed_micron/self.sas.wavelength**2 #for detailed comparisons to alf
#spectrum *= utils.light_speed_cgs/self.sas.wavelength**2 # the correct thing to do;consistent normalization with fsps

return spectrum


def forward(self, metalicity, Tage, synthesize_spectrum=True) -> torch.Tensor:

self.isochrone = self.isochrone_grid.get_isochrone(metalicity, Tage)

if synthesize_spectrum:
spectrum = self.spectral_synthesis(metalicity, Tage)
else:
print("HAVE NOT SYNTHEISIZED A SPECTRUM")
spectrum = None


return spectrum

Expand Down
2 changes: 2 additions & 0 deletions lighthouse/initial_mass_function/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from .kroupa import *
from .salpeter import *
from .two_slope_powerlaw import *
from .initial_mass_function import *

36 changes: 30 additions & 6 deletions lighthouse/initial_mass_function/initial_mass_function.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
from abc import ABC, abstractmethod
from torch import Tensor
# from torch import Tensor

import torch

from scipy.integrate import quad
import numpy as np


__all__ = ("Initial_Mass_Function", )

class Initial_Mass_Function(ABC):

@abstractmethod
def get_weight(self, mass) -> Tensor:
...
def __init__(self):

self.lower_limit = torch.tensor(0.08, dtype=torch.float64)
self.upper_limit = torch.tensor(100., dtype=torch.float64)


self._t0_normalization = None


@abstractmethod
def to(self, dtype=None, device=None):
...
def get_imf(self, mass, mass_weighted=False) -> torch.Tensor:
pass


@property
def t0_normalization(self): ## TODO: change this name

if self._t0_normalization is None:
self._t0_normalization = quad(self.get_imf,
self.lower_limit,
self.upper_limit,
args=(True,) )[0]

return self._t0_normalization

18 changes: 13 additions & 5 deletions lighthouse/initial_mass_function/kroupa.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import torch


from .initial_mass_function import Initial_Mass_Function

__all__ = ("Kroupa", )

class Kroupa(Initial_Mass_Function):

def get_weight(self, mass, alpha) -> torch.Tensor:
def get_imf(self, mass, mass_weighted=False) -> torch.Tensor:

alpha = torch.tensor([1.3, 2.3, 2.3])
mass = torch.tensor(mass)

weight = torch.where(
imf = torch.where(
mass < 0.5,
mass**(-alpha[0]), # mass < 0.5
torch.where(
Expand All @@ -18,8 +22,12 @@ def get_weight(self, mass, alpha) -> torch.Tensor:
)
)

return weight
if mass_weighted:
return mass*imf
else:
return imf


def to(self, dtype=None, device=None):
pass

Expand All @@ -30,8 +38,8 @@ def to(self, dtype=None, device=None):
K = Kroupa()

M = torch.linspace(0.1, 100, 1000)
W = K.get_weight(M, torch.tensor([1.3, 2.3, 2.7]))
IMF = K.get_imf(M)

plt.plot(torch.log10(M), torch.log10(W))
plt.plot(torch.log10(M), torch.log10(IMF))
plt.show()

33 changes: 33 additions & 0 deletions lighthouse/initial_mass_function/salpeter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import torch




from .initial_mass_function import Initial_Mass_Function

__all__ = ("Salpeter", )

class Salpeter(Initial_Mass_Function):

def get_imf(self, mass, mass_weighted=False) -> torch.Tensor:

salpeter_index = torch.tensor(2.35, dtype = torch.float64)
imf = mass**(-salpeter_index)

if mass_weighted:
imf = imf*mass

return imf


if __name__ == "__main__":

import matplotlib.pyplot as plt
K = Salpeter()

M = torch.linspace(0.08, 100, 1000)
IMF = K.get_imf(M)

plt.plot(torch.log10(M), torch.log10(IMF))
plt.show()

38 changes: 38 additions & 0 deletions lighthouse/initial_mass_function/two_slope_powerlaw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import torch


from .initial_mass_function import Initial_Mass_Function

__all__ = ("Two_Slope_Powerlaw", )

class Two_Slope_Powerlaw(Initial_Mass_Function):

"""
Fixed lower mass cutoff
Fixed break
"""

def get_imf(self, mass, mass_weighted=False, alpha1=1.0, alpah2=1.0) -> torch.Tensor:

alpha = torch.tensor([alpha1, alpha2, -2.30])
mass = torch.tensor(mass)

imf = torch.where(
mass < 0.5,
mass**(-alpha[0]), # mass < 0.5
torch.where(
mass < 1.0,
0.5**(-alpha[0] + alpha[1]) * mass**(-alpha[1]), # 0.5 <= mass < 1.0
0.5**(-alpha[0] + alpha[1]) * mass**(-alpha[2]), # mass >= 1.0
)
)

if mass_weighted:
return mass*imf
else:
return imf


if __name__ == "__main__":

pass
Loading
Loading