Skip to content

Commit 784002a

Browse files
authored
Merge pull request #62 from paranoya/feature/add-mass-frac-time
Add mass frac time method
2 parents 90abe69 + 58a2ae6 commit 784002a

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

src/pst/models.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,19 @@ class ChemicalEvolutionModel(ABC):
8080
(Z-SFR) need to be implemented in a subclass.
8181
"""
8282

83+
@property
84+
def today(self):
85+
return self._today
86+
87+
@today.setter
88+
def today(self, value):
89+
if value is not None:
90+
self._today = check_unit(value, u.Gyr)
91+
else:
92+
self._today = None
93+
8394
def __init__(self, **kwargs):
84-
pass
95+
self.today = kwargs.get("today")
8596

8697
@abstractmethod
8798
def stellar_mass_formed(self, time) -> u.Quantity:
@@ -141,6 +152,48 @@ def interpolate_ssp_masses(self, ssp: SSPBase, t_obs: u.Gyr,
141152
metallicities=bin_metallicity,
142153
masses=bin_mass)
143154

155+
def time_at_stellar_mass_frac(self, frac, today=None, time_res=30 << u.Myr):
156+
"""
157+
Get the cosmic time at which the model formed a fraction of the total stellar mass.
158+
159+
Parameters
160+
----------
161+
frac : float
162+
Input fraction.
163+
today : float or astropy.units.Quantity
164+
Age of the Universe at the time of the observation. If not provided,
165+
the defult is to use ``self.today'' (returning an error if not set).
166+
time_res : float or astropy.units.Quantity
167+
Time-step resolution for sampling the SFH.
168+
Returns
169+
-------
170+
frac_times : astropy.units.Quantity
171+
Fraction formation times.
172+
"""
173+
frac = np.atleast_1d(frac)
174+
if today is None:
175+
if self.today is None:
176+
raise ValueError(
177+
"current CEM does not have attribute 'today' set,"
178+
+ " it must be provided by the user")
179+
else:
180+
today = self.today
181+
else:
182+
today = check_unit(today, u.Gyr)
183+
184+
time_res = check_unit(time_res, u.Gyr)
185+
dummy_time = np.arange(0, today.to_value("Gyr"),
186+
time_res.to_value("Gyr")) << u.Gyr
187+
mass_history = self.stellar_mass_formed(dummy_time)
188+
frac_history = mass_history / mass_history[-1]
189+
idx = np.searchsorted(frac_history, frac).clip(
190+
min=1, max=frac_history.size - 1)
191+
t_frac = (
192+
dummy_time[idx - 1] * (frac - frac_history[idx]) / (frac_history[idx] - frac_history[idx - 1])
193+
+ dummy_time[idx] * (1 - (frac - frac_history[idx]) / (frac_history[idx] - frac_history[idx - 1])
194+
))
195+
return t_frac
196+
144197
def compute_SED(self, ssp : SSPBase, t_obs : u.Quantity,
145198
allow_negative=False) -> u.Quantity:
146199
"""

tutorials/models/analytical.ipynb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@
166166
"sfr = np.diff(mfh) / np.diff(cosmic_time)\n",
167167
"t_sfr = (cosmic_time[1:] + cosmic_time[:-1]) / 2\n",
168168
"\n",
169+
"# Estimate the times at which the model formed a given fraction of the present\n",
170+
"# mass\n",
171+
"mass_fractions = [0.1, 0.5, 0.9, 0.99]\n",
172+
"times_frac = model.time_at_stellar_mass_frac(mass_fractions)\n",
173+
"\n",
169174
"fig, axes = plt.subplots(nrows=3, sharex=True)\n",
170175
"fig.suptitle(\"Log-normal SFH with a quenching event\")\n",
171176
"fig.supxlabel(f\"cosmic time ({cosmic_time.unit})\")\n",
@@ -175,6 +180,12 @@
175180
"ax.set_yscale('log')\n",
176181
"ax.plot(cosmic_time, mfh.to_value(u.Msun), '.-')\n",
177182
"\n",
183+
"for t, m in zip(times_frac, mass_fractions):\n",
184+
" ax.annotate(f\"{m * 100:.1f}%\", xy=(t.value, 1e9), va=\"top\", ha=\"center\",\n",
185+
" arrowprops=dict(facecolor='black', shrink=0.05))\n",
186+
" # ax.arrow(t.value, 1e9, dx=0, dy=3e9, width=0.03,\n",
187+
" # head_width=0.1, head_length=0.1)\n",
188+
"\n",
178189
"ax = axes[1]\n",
179190
"ax.set_ylabel(r\"SFR (M$_\\odot$/yr)\")\n",
180191
"ax.set_yscale('log')\n",
@@ -183,7 +194,7 @@
183194
"ax = axes[2]\n",
184195
"ax.set_ylabel(r\"Z\")\n",
185196
"ax.set_yscale('log')\n",
186-
"ax.plot(cosmic_time, model.ism_metallicity(cosmic_time), '.-')\n",
197+
"ax.plot(cosmic_time, model.ism_metallicity(cosmic_time), '.-') \n",
187198
"\n",
188199
"for ax in axes:\n",
189200
" ax.axvline(quenching_time.to_value(cosmic_time.unit), color='r', label=\"Quenching event\")\n",
@@ -243,7 +254,7 @@
243254
"ax.set_yscale('log')\n",
244255
"ax.set_ylim(5e-5, 0.05)\n",
245256
"\n",
246-
"mappable = ax.pcolormesh(ssp.ages.to_value(\"Gyr\"), ssp.metallicities, ssp_weights.to_value(u.Msun), norm=LogNorm())\n",
257+
"mappable = ax.pcolormesh(ssp.ages.to_value(\"Gyr\"), ssp.metallicities.value, ssp_weights.to_value(u.Msun), norm=LogNorm())\n",
247258
"\n",
248259
"plt.colorbar(mappable, ax=ax, label=r\"SSP mass (M$_\\odot$)\")\n",
249260
"\n",

tutorials/models/particles.ipynb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,10 @@
183183
"ax.set_yscale('log')\n",
184184
"ax.set_ylim(5e-5, 0.05)\n",
185185
"\n",
186-
"mappable = ax.pcolormesh(ssp.ages.to_value(\"Gyr\"), ssp.metallicities, ssp_weights, norm=LogNorm())\n",
187-
"ax.plot((today - particles_t_form).to_value(u.Gyr), particles_z, 'k,', alpha=.25)\n",
186+
"mappable = ax.pcolormesh(ssp.ages.to_value(\"Gyr\"), ssp.metallicities.value,\n",
187+
" ssp_weights.value, norm=LogNorm())\n",
188+
"ax.plot((today - particles_t_form).to_value(u.Gyr), particles_z,\n",
189+
" 'k,', alpha=.25)\n",
188190
"\n",
189191
"plt.colorbar(mappable, ax=ax, label=r\"SSP mass (M$_\\odot$)\")"
190192
]
@@ -226,9 +228,9 @@
226228
],
227229
"metadata": {
228230
"kernelspec": {
229-
"display_name": "test-pst",
231+
"display_name": "base",
230232
"language": "python",
231-
"name": "test-pst"
233+
"name": "python3"
232234
},
233235
"language_info": {
234236
"codemirror_mode": {
@@ -240,7 +242,7 @@
240242
"name": "python",
241243
"nbconvert_exporter": "python",
242244
"pygments_lexer": "ipython3",
243-
"version": "3.8.10"
245+
"version": "3.11.5"
244246
}
245247
},
246248
"nbformat": 4,

0 commit comments

Comments
 (0)