-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathomega.py
5801 lines (4632 loc) · 215 KB
/
omega.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# coding=utf-8
from __future__ import (division, print_function, absolute_import,
unicode_literals)
'''
GCE OMEGA (One-zone Model for the Evolution of Galaxies) module
Functionality
=============
This tool allows one to simulate the chemical evolution of single-zone galaxies.
Having the star formation history as one of the input parameters, OMEGA can
target local galaxies by using observational data found in the literature.
Made by
=======
FEB2015: C. Ritter, B. Cote
MAY2015: B. Cote
The code inherits the chem_evol class, which contains common functions shared by
SYGMA and OMEGA. The code in chem_evol has been developed by :
v0.1 NOV2013: C. Fryer, C. Ritter
v0.2 JAN2014: C. Ritter
v0.3 APR2014: C. Ritter, J. F. Navarro, F. Herwig, C. Fryer, E. Starkenburg,
M. Pignatari, S. Jones, K. Venn1, P. A. Denissenkov &
the NuGrid collaboration
v0.4 FEB2015: C. Ritter, B. Cote
v0.5 MAR2015: B. Cote
v0.6 OCT2016: B. Cote
Stop keeking track of version from now on.
MARCH2018: B. Cote
- Switched to Python 3
- Capability to include radioactive isotopes
FEB2019: A. Yagüe, B. Cote
- Optimized to code to run faster
Note
====
Please do not use "tabs" when introducing new lines of code.
Usage
=====
Import the module:
>>> import omega as o
Get help:
>>> help o
Get more information:
>>> o.omega?
Create a custom galaxy (closed box):
>>> o1 = o.omega(cte_sfr=1.0, mgal=1.5e10)
Simulate a known galaxy (open box):
>>> o2 = o.omega(galaxy='sculptor', in_out_control=True, mgal=1e6, mass_loading=8, in_out_ratio=1.5)
Analysis functions: See the Sphinx documentation
'''
# Standard packages
import copy
import math
import random
import os
# Define where is the working directory
# This is where the NuPyCEE code will be extracted
nupy_path = os.path.dirname(os.path.realpath(__file__))
# Import NuPyCEE codes
import NuPyCEE.sygma as sygma
from NuPyCEE.chem_evol import *
class omega( chem_evol ):
'''
Input parameters (OMEGA)
================
Important : By default, a closed box model is always assumed.
galaxy : string
Name of the target galaxy. By using a known galaxy, the code
automatically selects the corresponding star formation history, stellar
mass, and total mass (when available). By using 'none', the user has
perfect control of these three last parameters.
Choices : 'milky_way', 'milky_way_cte', 'sculptor', 'carina', 'fornax',
'none'
Default value : 'none'
Special note : The 'milky_way_cte' option uses the Milky Way's
characteristics, but with a constant star formation history.
cte_sfr : float
Constant star formation history in [Mo/yr].
Default value : 1.0
rand_sfh : float
Maximum possible ratio between the maximum and the minimum values of a star
formation history that is randomly generated.
Default value : 0.0 (deactivated)
Special note : A value greater than zero automatically generates a random
star formation history, which pypasses the use of the cte_sfr parameter.
sfh_file : string
Path to a file containing an input star formation history. The first and
second columns must be the age of the galaxy in [yr] and the star
formation rate in [Mo/yr].
Default value : 'none' (deactivated)
Special note : When a path is specified, it by passes the cte_sfr and the
and_sfh parameters.
stellar_mass_0 : float
Current stellar mass of the galaxy, in [Mo], at the end of the simulation.
Default value : -1.0 (you need to specify a value with unknown galaxies)
in_out_control : boolean
The in_out_control implementation enables control of the outflow and
the inflow rates independently by using constant values (see outflow_rate
and inflow_rate) or by using a mass-loading factor that connects the
rates to the star formation history (see mass_loading and in_out_ratio).
Default value : False (deactivated)
mass_loading : float
Ratio between the outflow rate and the star formation rate.
Default value : 1.0
outflow_rate : float
Constant outflow rate in [Mo/yr].
Default value : -1.0 (deactivated)
Special note : A value greater or equal to zero activates the constant
utflow mode, which bypasses the use of the mass_loading parameter.
in_out_ratio : float
Used in : in_out_control mode
Ratio between the inflow rate and the outflow rate. This parameter is
used to calculate the inflow rate, not the outflow rate.
Default value : 1.0
inflow_rate : float
Used in : in_out_control mode
Constant inflow rate in [Mo/yr].
Default value : -1.0 (deactivated)
Special note : A value greater or equal to zero activates the constant
inflow mode, which bypasses the use of the in_out_ratio parameter.
SF_law : boolean
The SF_law inplementation assumes a Kennicutt-Schmidt star formation law
and combines it to the known input star formation history in order to
derive the mass of the gas reservoir at every timestep.
Default value : False (deactivated)
sfe : float
Used in : SF_law and DM_evolution modes
Star formation efficiency present in the Kennicutt-Schmidt law.
Default value : 0.1
f_dyn : float
Used in : SF_law and DM_evolution modes
Scaling factor used to calculate the star formation timescale present in
the Kennicutt-Schmidt law. We assume that this timescale is equal to a
fraction of the dynamical timescale of the virialized system (dark and
baryonic matter), t_star = f_dyn * t_dyn.
Default value : 0.1
m_DM_0 : float
Used in : SF_law and DM_evolution modes
Current dark matter halo mass of the galaxy, in [Mo], at the end of the
simulations.
Default value : 1.0e+11
t_star : float
Used in : SF_law and DM_evolution modes
Star formation timescale, in [yr], used in the Kennicutt-Schmidt law.
Default value = -1.0 (deactivated)
Special note : A positive value activates the use of this parameter,
which bypasses the f_dyn parameter.
DM_evolution : boolean
The DM_evolution implementation is an extension of the SF_law option.
In addition to using a Kennicutt-Schmidt star formation law, it assumes
an evolution in the total mass of the galaxy as function of time. With
this prescription, the mass-loading factor has a mass dependency. The
mass_loading parameter then only represents the final value at the end
of the simulation.
Default value : False (deactivated)
exp_ml : float
Used in : DM_evolution mode
Exponent of the mass dependency of the mass-loading factor. This last
factor is proportional to M_vir**(-exp_ml/3), where M_vir is the sum of
dark and baryonic matter.
Default value : 2.0
================
'''
#Combine docstrings from chem_evol with sygma docstring
__doc__ = __doc__+chem_evol.__doc__
##############################################
## Constructor ##
##############################################
def __init__(self, galaxy='none', in_out_control=False, SF_law=False, \
DM_evolution=False, f_dyn=0.1, sfe=0.01, outflow_rate=-1.0, \
inflow_rate=-1.0, rand_sfh=0.0, cte_sfr=1.0, m_DM_0=1.0e12, \
omega_0=0.32, omega_b_0=0.05, lambda_0=0.68, H_0=67.11, \
mass_loading=1.0, t_star=-1.0, sfh_file='none', in_out_ratio=1.0, \
stellar_mass_0=-1.0, z_dependent=True, exp_ml=2.0, beta_crit=1.0, \
skip_zero=False, redshift_f=0.0, long_range_ref=False,\
f_s_enhance=1.0, m_gas_f=-1.0, cl_SF_law=False, \
external_control=False, calc_SSP_ej=False, t_sf_z_dep = 1.0, \
m_crit_on=False, norm_crit_m=8.0e+09, mass_frac_SSP=0.5, \
sfh_array_norm=-1.0, imf_rnd_sampling=False, r_gas_star=-1.0, \
cte_m_gas = -1.0, DM_array=[], sfh_array=[], \
m_inflow_array=[], m_gas_array=[], mdot_ini=[], mdot_ini_t=[], \
r_vir_array=[], mass_sampled=[], scale_cor=[], \
mass_sampled_ssp=[], m_tot_ISM_t_in=[], \
m_inflow_X_array=[], dt_in_SSPs=[], **kwargs):
# Get the name of the instance
import traceback
(filename,line_number,function_name,text)=traceback.extract_stack()[-2]
self.inst_name = text[:text.find('=')].strip()
# Overwrite default chem_evol parameters (if needed)
if not "iniZ" in kwargs:
kwargs["iniZ"] = 0.0
if not "mgal" in kwargs:
kwargs["mgal"] = 8.0e10
# Call the init function of the class inherited by SYGMA
chem_evol.__init__(self, **kwargs)
# Quit if something bad happened in chem_evol ..
if self.need_to_quit:
return
# Announce the beginning of the simulation
if not self.print_off:
print ('OMEGA run in progress..')
start_time = t_module.time()
self.start_time = start_time
# Attributes for chem_evol
self.kwargs = kwargs
# Attribute the input parameters to the current OMEGA object
self.galaxy = galaxy
self.in_out_control = in_out_control
self.SF_law = SF_law
self.DM_evolution = DM_evolution
self.f_dyn = f_dyn
self.sfe = sfe
self.outflow_rate = outflow_rate
self.inflow_rate = inflow_rate
self.rand_sfh = rand_sfh
self.cte_sfr = cte_sfr
self.m_DM_0 = m_DM_0
self.mass_loading = mass_loading
self.t_star = t_star
self.sfh_file = sfh_file
self.in_out_ratio = in_out_ratio
self.stellar_mass_0 = stellar_mass_0
self.z_dependent = z_dependent
self.exp_ml = exp_ml
self.DM_too_low = False
self.skip_zero = skip_zero
self.redshift_f = redshift_f
self.long_range_ref = long_range_ref
self.m_crit_on = m_crit_on
self.norm_crit_m = norm_crit_m
self.sfh_array_norm = sfh_array_norm
self.DM_array = DM_array
self.sfh_array = sfh_array
self.mdot_ini = mdot_ini
self.mdot_ini_t = mdot_ini_t
self.r_gas_star = r_gas_star
self.m_gas_f = m_gas_f
self.cl_SF_law = cl_SF_law
self.external_control = external_control
self.mass_sampled = mass_sampled
self.scale_cor = scale_cor
self.imf_rnd_sampling = imf_rnd_sampling
self.cte_m_gas = cte_m_gas
self.t_sf_z_dep = t_sf_z_dep
self.m_tot_ISM_t_in = m_tot_ISM_t_in
self.m_inflow_array = m_inflow_array
self.len_m_inflow_array = len(m_inflow_array)
self.m_inflow_X_array = m_inflow_X_array
self.len_m_inflow_X_array = len(m_inflow_X_array)
self.m_gas_array = m_gas_array
self.len_m_gas_array = len(m_gas_array)
self.beta_crit = beta_crit
self.r_vir_array = r_vir_array
self.calc_SSP_ej = calc_SSP_ej
self.mass_frac_SSP = -1.0
self.mass_frac_SSP_in = mass_frac_SSP
self.dt_in_SSPs = dt_in_SSPs
# Set cosmological parameters - default is Planck 2013 (used in Caterpillar)
self.omega_0 = omega_0 # Current mass density parameter
self.omega_b_0 = omega_b_0 # Current baryonic mass density parameter
self.lambda_0 = lambda_0 # Current dark energy density parameter
self.H_0 = H_0 # Hubble constant [km s^-1 Mpc^-1]
# Look for errors in the input parameters
if self.__check_inputs_omega():
return
# Calculate the number of CC SNe per Msun formed (if needed)
if self.out_follows_E_rate:
self.__calc_ccsne_per_m()
# Pre-calculate SSPs (if needed)
if self.pre_calculate_SSPs:
self.__run_SSPs()
# Calculate random IFM sampling parameters (if needed)
if self.imf_rnd_sampling:
self.__calc_imf_rnd_param()
# Define whether the open box scenario is used or not
if self.in_out_control or self.SF_law or self.DM_evolution:
self.open_box = True
else:
self.open_box = False
# Refine timesteps (if needed)
if self.SF_law or self.DM_evolution:
self.__refine_timesteps()
# Declare arrays used to follow the evolution of the galaxy
self.__declare_evol_arrays()
# Calculate the average mass fraction ejected by SSPs
# !! This function needd to be before self.__initialize_gal_prop() !!
self.__calc_mass_frac_SSP()
# Set the general properties of the selected galaxy
self.__initialize_gal_prop()
# Fill arrays used to follow the evolution
self.__fill_evol_arrays()
# Read the primordial composition of the inflow gas
if self.in_out_control or self.SF_law or self.DM_evolution:
prim_comp_table = os.path.join('yield_tables', 'iniabu',\
'iniab_bb_walker91.txt')
self.prim_comp = ry.read_yields_Z(os.path.join(nupy_path,\
prim_comp_table), isotopes=self.history.isotopes)
# Add the stellar ejecta coming from external galaxies that just merged
if len(self.mdot_ini) > 0:
self.__add_ext_mdot()
# Initialisation of the composition of the gas reservoir
if len(self.ism_ini) > 0:
for i_ini in range(0,self.len_ymgal):
self.ymgal[0][i_ini] = self.ism_ini[i_ini]
# Copy the outflow-vs-SFR array and re-initialize for delayed outflow
if self.out_follows_E_rate:
self.outflow_test = np.sum(self.m_outflow_t)
self.m_outflow_t_vs_SFR = copy.copy(self.m_outflow_t)
for i_ofer in range(0,self.nb_timesteps):
self.m_outflow_t[i_ofer] = 0.0
# Run the simulation if not controled by an external code
if not self.external_control:
self.__run_simulation(self.mass_sampled, self.scale_cor)
##############################################
# Check Inputs OMEGA #
##############################################
def __check_inputs_omega(self):
'''
This function checks for incompatible input entries, and stops
the simulation if needed.
'''
# Initialize whether or not the code should abord
abord = False
# Input galaxy
if not self.galaxy in ['none', 'milky_way', 'milky_way_cte', \
'sculptor', 'fornax', 'carina']:
print ('Error - Selected galaxy not available.')
abord = True
# Random SFH
if self.rand_sfh > 0.0 and self.stellar_mass_0 < 0.0:
print ('Error - You need to choose a current stellar mass.')
abord = True
# Inflow control when non-available
if self.in_out_control and (self.SF_law or self.DM_evolution):
print ('Error - Cannot control inflows and outflows when SF_law or'\
'DM_evolution is equal to True.')
abord = True
# Inflow and outflow control when the dark matter mass if evolving
if (self.outflow_rate >= 0.0 or self.inflow_rate >= 0.0) and \
self.DM_evolution:
print ('Error - Cannot fix inflow and outflow rates when the mass'\
'of the dark matter halo is evolving.')
abord = True
# Inflow array when input
if self.len_m_inflow_array > 0:
if not self.len_m_inflow_array == self.nb_timesteps:
print ('Error - len(m_inflow_array) needs to equal nb_timesteps.')
abord = True
# Inflow X array when input
if self.len_m_inflow_X_array > 0:
if not self.len_m_inflow_X_array == self.nb_timesteps:
print ('Error - len(m_inflow_X_array) needs to equal nb_timesteps.')
abord = True
if not len(self.m_inflow_X_array[0]) == self.nb_isotopes:
print ('Error - len(m_inflow_X_array[i]) needs to equal nb_isotopes.')
abord = True
# Mgas array when input
if self.len_m_gas_array > 0:
if not self.len_m_gas_array == (self.nb_timesteps+1):
print ('Error - len(m_gas_array) needs to equal nb_timesteps+1.')
abord = True
# Return whether or not the code should abord
return abord
##############################################
# Calc CCSNe per M #
##############################################
def __calc_ccsne_per_m(self):
'''
Calculate the number of core-collapse SNe that will occur
in total per units of stellar mass formed.
'''
# IMF constant for a 1 Msun PopIII stellar population
A_pop3 = 1.0 / self._imf(self.imf_bdys_pop3[0], self.imf_bdys_pop3[1],2)
A = 1.0 / self._imf(self.imf_bdys[0], self.imf_bdys[1],2)
# Number of CC SNe per stellar mass formed
self.nb_ccsne_per_m_pop3 = \
A_pop3 * self._imf(self.imf_yields_range_pop3[0], \
self.imf_yields_range_pop3[1],1)
self.nb_ccsne_per_m = \
A * self._imf(self.transitionmass,\
self.imf_yields_range[1],1)
##############################################
# Run SSPs #
##############################################
def __run_SSPs(self):
'''
Run all stellar populations at all metallicties.
'''
# Calculate all SSPs
self.__run_all_ssps()
# Declare the arrays that will contain the interpolated isotopes
self.ej_SSP_int = np.zeros((self.nb_steps_table, self.nb_isotopes))
if self.len_decay_file > 0 or self.use_decay_module:
self.ej_SSP_int_radio = np.zeros((self.nb_steps_table, self.nb_radio_iso))
##############################################
# Calc IMF rnd Param #
##############################################
def __calc_imf_rnd_param(self):
'''
Calculate parameters that will be used when the IFM is randomly sampled.
'''
# Print info about the IMF sampling
self.m_pop_max = 1.0e4
print ('IMF random sampling for SSP with M < ',self.m_pop_max)
# Calculate the stellar mass associated with the
# highest IMF value (needed for Monte Carlo)
# This only samples massive stars
self.A_rdm = 1.0 / self.transitionmass**(-2.3)
self.m_frac_massive_rdm = self.A_rdm * \
self._imf(self.transitionmass, self.imf_bdys[1], 2)
##############################################
# Refine Timesteps #
##############################################
def __refine_timesteps(self):
'''
Refine the timesteps and increase the sizes of relevant arrays if needed.
'''
# Initialize evolution arrays that will determine the refinement needs
self.t_SF_t = np.zeros(self.nb_timesteps+1)
self.redshift_t = np.zeros(self.nb_timesteps+1)
# Fill the evolution arrays
self.calculate_redshift_t()
self.__calculate_t_SF_t()
# Define whether refinement is needed
need_t_raf = False
for i_raf in range(self.nb_timesteps):
if self.history.timesteps[i_raf] > self.t_SF_t[i_raf] / self.sfe:
need_t_raf = True
break
# Refine timesteps (if needed)
if need_t_raf:
if self.long_range_ref:
self.__rafine_steps_lr()
else:
self.__rafine_steps()
# Re-Create entries for the mass-loss rate of massive stars
self.massive_ej_rate = np.zeros(self.nb_timesteps+1)
self.sn1a_ej_rate = np.zeros(self.nb_timesteps+1)
##############################################
# Refine Steps #
##############################################
def __rafine_steps(self):
'''
This function increases the number of timesteps if the star formation
will eventually consume all the gas, which occurs when dt > (t_star/sfe).
'''
# Declaration of the new timestep array
if not self.print_off:
print ('..Time refinement..')
new_dt = []
# For every timestep ...
for i_rs in range(0,len(self.history.timesteps)):
# Calculate the critical time delay
t_raf = self.t_SF_t[i_rs] / self.sfe
# If the step needs to be refined ...
if self.history.timesteps[i_rs] > t_raf:
# Calculate the split factor
nb_split = int(self.history.timesteps[i_rs] / t_raf) + 1
# Split the step
for i_sp_st in range(0,nb_split):
new_dt.append(self.history.timesteps[i_rs]/nb_split)
# If ok, don't change anything
else:
new_dt.append(self.history.timesteps[i_rs])
# Update the timestep information
self.nb_timesteps = len(new_dt)
self.history.timesteps = new_dt
# Update self.history.age
self.history.age = [0]
for ii in range(self.nb_timesteps):
self.history.age.append(self.history.age[-1] + new_dt[ii])
self.history.age = np.array(self.history.age)
# If a timestep needs to be added to be synchronized with
# the external program managing merger trees ...
if self.t_merge > 0.0:
# Find the interval where the step needs to be added
i_temp = 0
t_temp = new_dt[0]
while t_temp / self.t_merge < 0.9999999:
i_temp += 1
t_temp += new_dt[i_temp]
# Keep the t_merger index in memory
self.i_t_merger = i_temp
# Update/redeclare all the arrays (stable isotopes)
ymgal = self._get_iniabu()
self.len_ymgal = len(ymgal)
self.mdot, self.ymgal, self.ymgal_massive, self.ymgal_agb, \
self.ymgal_1a, self.ymgal_nsm, \
self.ymgal_delayed_extra, self.mdot_massive, \
self.mdot_agb, self.mdot_1a, self.mdot_nsm, \
self.mdot_delayed_extra, \
self.sn1a_numbers, self.sn2_numbers, self.nsm_numbers, \
self.delayed_extra_numbers, self.imf_mass_ranges, \
self.imf_mass_ranges_contribution, self.imf_mass_ranges_mtot = \
self._get_storing_arrays(ymgal, len(self.history.isotopes))
# Update/redeclare all the arrays (unstable isotopes)
if self.len_decay_file > 0 or self.use_decay_module:
ymgal_radio = np.zeros(self.nb_radio_iso)
# Initialisation of the storing arrays for radioactive isotopes
self.mdot_radio, self.ymgal_radio, self.ymgal_massive_radio, \
self.ymgal_agb_radio, self.ymgal_1a_radio, self.ymgal_nsm_radio, \
self.ymgal_delayed_extra_radio, \
self.mdot_massive_radio, self.mdot_agb_radio, self.mdot_1a_radio, \
self.mdot_nsm_radio, \
self.mdot_delayed_extra_radio, dummy, dummy, dummy, dummy, dummy, \
dummy, dummy, dummy = \
self._get_storing_arrays(ymgal_radio, self.nb_radio_iso)
# Recalculate the simulation time (used in chem_evol)
self.t_ce = []
self.t_ce.append(self.history.timesteps[0])
for i_init in range(1,self.nb_timesteps):
self.t_ce.append(self.t_ce[i_init-1] + self.history.timesteps[i_init])
##############################################
# Rafine Steps LR #
##############################################
def __rafine_steps_lr(self):
'''
This function increases the number of timesteps if the star formation
will eventually consume all the gas, which occurs when dt > (t_star/sfe).
'''
# Declaration of the new timestep array
if not self.print_off:
print ('..Time refinement (long range)..')
new_dt = []
# For every timestep ...
for i_rs in range(0,len(self.history.timesteps)):
# Calculate the critical time delay
t_raf = self.t_SF_t[i_rs] / self.sfe
# If the step needs to be refined ...
if self.history.timesteps[i_rs] > t_raf:
# Calculate the number of remaining steps
nb_step_rem = len(self.history.timesteps) - i_rs
t_rem = 0.0
for i_rs in range(0,len(self.history.timesteps)):
t_rem += self.history.timesteps[i_rs]
# Calculate the split factor
nb_split = int(t_rem / t_raf) + 1
# Split the step
for i_sp_st in range(0,nb_split):
new_dt.append(t_rem/nb_split)
# Quit the for loop
break
# If ok, don't change anything
else:
new_dt.append(self.history.timesteps[i_rs])
# Update the timestep information
self.nb_timesteps = len(new_dt)
self.history.timesteps = new_dt
# Update self.history.age
self.history.age = [0]
for ii in range(self.nb_timesteps):
self.history.age.append(self.history.age[-1] + new_dt[ii])
self.history.age = np.array(self.history.age)
# If a timestep needs to be added to be synchronized with
# the external program managing merger trees ...
if self.t_merge > 0.0:
# Find the interval where the step needs to be added
i_temp = 0
t_temp = new_dt[0]
while t_temp / self.t_merge < 0.9999999:
i_temp += 1
t_temp += new_dt[i_temp]
# Keep the t_merger index in memory
self.i_t_merger = i_temp
# Update/redeclare all the arrays (stable isotopes)
ymgal = self._get_iniabu()
self.len_ymgal = len(ymgal)
self.mdot, self.ymgal, self.ymgal_massive, self.ymgal_agb, \
self.ymgal_1a, self.ymgal_nsm, \
self.ymgal_delayed_extra, self.mdot_massive, \
self.mdot_agb, self.mdot_1a, self.mdot_nsm, \
self.mdot_delayed_extra, \
self.sn1a_numbers, self.sn2_numbers, self.nsm_numbers, \
self.delayed_extra_numbers, self.imf_mass_ranges, \
self.imf_mass_ranges_contribution, self.imf_mass_ranges_mtot = \
self._get_storing_arrays(ymgal, len(self.history.isotopes))
# Update/redeclare all the arrays (unstable isotopes)
if self.len_decay_file > 0 or self.use_decay_module:
ymgal_radio = np.zeros(self.nb_radio_iso)
# Initialisation of the storing arrays for radioactive isotopes
self.mdot_radio, self.ymgal_radio, self.ymgal_massive_radio, \
self.ymgal_agb_radio, self.ymgal_1a_radio, self.ymgal_nsm_radio, \
self.ymgal_delayed_extra_radio, \
self.mdot_massive_radio, self.mdot_agb_radio, self.mdot_1a_radio, \
self.mdot_nsm_radio, \
self.mdot_delayed_extra_radio, dummy, dummy, dummy, dummy, dummy, \
dummy, dummy, dummy = \
self._get_storing_arrays(ymgal_radio, self.nb_radio_iso)
# Recalculate the simulation time (used in chem_evol)
self.t_ce = []
self.t_ce.append(self.history.timesteps[0])
for i_init in range(1,self.nb_timesteps):
self.t_ce.append(self.t_ce[i_init-1] + self.history.timesteps[i_init])
##############################################
# Calc Mass Frac SSP #
##############################################
def __calc_mass_frac_SSP(self):
'''
Calculate the average mass fraction returned by stellar populations.
This refers to the ratio between the ejected mass and the initial
stellar population mass.
'''
# If the mass fraction is self-calculated from SSPs ..
if self.calc_SSP_ej:
# Set the stellar population mass to 1 Msun
self.kwargs["mgal"] = 1.0
# Define the 5 metallicities that will be used
Z = [0.02, 0.01, 0.006, 0.001, 0.0001]
# Run SYGMA with different metallicities and cumulate ejected mass
self.mass_frac_SSP = 0.0
for i_Z_SSP in range(0,len(Z)):
self.kwargs["iniZ"] = Z[i_Z_SSP]
s_inst = sygma.sygma(**self.kwargs)
self.mass_frac_SSP += np.sum(s_inst.ymgal[-1])
# Calculate the average mass fraction returned
self.mass_frac_SSP = self.mass_frac_SSP / len(Z)
print ('Average SSP mass fraction returned = ',self.mass_frac_SSP)
# Take the input value if SSP calculations are not used
else:
self.mass_frac_SSP = self.mass_frac_SSP_in
##############################################
# Declare Evol Arrays #
##############################################
def __declare_evol_arrays(self):
'''
This function declares the arrays used to follow the evolution of the
galaxy regarding its growth and the exchange of gas with its surrounding.
'''
# Arrays with specific values at every timestep
self.sfr_input = np.zeros(self.nb_timesteps+1) # Star formation rate [Mo yr^-1]
self.m_DM_t = np.zeros(self.nb_timesteps+1) # Mass of the dark matter halo
self.r_vir_DM_t= np.zeros(self.nb_timesteps+1) # Virial radius of the dark matter halo
self.v_vir_DM_t= np.zeros(self.nb_timesteps+1) # Virial velocity of the halo
self.m_tot_ISM_t = np.zeros(self.nb_timesteps+1) # Mass of the ISM in gas
self.m_outflow_t = np.zeros(self.nb_timesteps) # Mass of the outflow at every timestep
self.eta_outflow_t = np.zeros(self.nb_timesteps) # Mass-loading factor == M_outflow / SFR
self.t_SF_t = np.zeros(self.nb_timesteps+1) # Star formation timescale at every timestep
self.m_crit_t = np.zeros(self.nb_timesteps+1) # Critital ISM mass below which no SFR
self.redshift_t = np.zeros(self.nb_timesteps+1) # Redshift associated to every timestep
self.m_inflow_t = np.zeros(self.nb_timesteps) # Mass of the inflow at every timestep
##############################################
# Initialize Gal Prop #
##############################################
def __initialize_gal_prop(self):
'''
This function sets the properties of the selected galaxy, such as its
SFH, its total mass, and its stellar mass.
'''
# No specific galaxy - Use input parameters
if self.galaxy == 'none':
#If an array is used for the SFH ..
if len(self.sfh_array) > 0:
self.__copy_sfr_array()
# If an input file is used for the SFH ...
elif not self.sfh_file == 'none':
self.__copy_sfr_input(self.sfh_file)
# If a star formation law is used in a closed box ...
elif self.cl_SF_law and not self.open_box:
self.__calculate_sfe_cl()
# If a random SFH is chosen ...
elif self.rand_sfh > 0.0:
self.__generate_rand_sfh()
# If the SFH is constant ...
else:
for i_cte_sfr in range(0, self.nb_timesteps+1):
self.sfr_input[i_cte_sfr] = self.cte_sfr
# Milky Way galaxy ...
elif self.galaxy == 'milky_way' or self.galaxy == 'milky_way_cte':
# Set the current dark and stellar masses (corrected for mass loss)
self.m_DM_0 = 1.0e12
self.stellar_mass_0 = 5.0e10
# Read Chiappini et al. (2001) SFH
if self.galaxy == 'milky_way':
self.__copy_sfr_input('stellab_data/milky_way_data/sfh_mw_cmr01.txt')
# Read constant SFH
else:
self.__copy_sfr_input('stellab_data/milky_way_data/sfh_cte.txt')
# Sculptor dwarf galaxy ...
elif self.galaxy == 'sculptor':
# Set the current dark and stellar masses (corrected for mass loss)
self.m_DM_0 = 1.5e9
self.stellar_mass_0 = 7.8e6
self.stellar_mass_0 = self.stellar_mass_0 * (1-self.mass_frac_SSP)
# Read deBoer et al. (2012) SFH
self.__copy_sfr_input('stellab_data/sculptor_data/sfh_deBoer12.txt')
# Fornax dwarf galaxy ...
elif self.galaxy == 'fornax':
# Set the current dark and stellar masses (corrected for mass loss)
self.m_DM_0 = 7.08e8
self.stellar_mass_0 = 4.3e7
self.stellar_mass_0 = self.stellar_mass_0 * (1-self.mass_frac_SSP)
# Read deBoer et al. (2012) SFH
self.__copy_sfr_input('stellab_data/fornax_data/sfh_fornax_deboer_et_al_2012.txt')
# Carina dwarf galaxy ...
elif self.galaxy == 'carina':
# Set the current dark and stellar masses (corrected for mass loss)
self.m_DM_0 = 3.4e6
self.stellar_mass_0 = 1.07e6
self.stellar_mass_0 = self.stellar_mass_0 * (1-self.mass_frac_SSP)
# Read deBoer et al. (2014) SFH
self.__copy_sfr_input('stellab_data/carina_data/sfh_deBoer14.txt')
# Interpolate the last timestep
if len(self.sfr_input) > 3:
aa = (self.sfr_input[-2] - self.sfr_input[-3])/\
self.history.timesteps[-2]
bb = self.sfr_input[-2]- (self.history.tend-self.history.timesteps[-1])*aa
self.sfr_input[-1] = aa*self.history.tend + bb
# Keep the SFH in memory
self.history.sfr_abs = self.sfr_input
##############################################
## Copy SFR Array ##
##############################################
def __copy_sfr_array(self):
'''
See copy_sfr_input() for more info.
'''
# Variable to keep track of the OMEGA's timestep
i_dt_csa = 0
t_csa = 0.0
nb_dt_csa = self.nb_timesteps + 1
# Variable to keep track of the total stellar mass from the input SFH
m_stel_sfr_in = 0.0
# For every timestep given in the array (starting at the second step)
for i_csa in range(1,len(self.sfh_array)):
# Calculate the SFR interpolation coefficient
a_sfr = (self.sfh_array[i_csa][1] - self.sfh_array[i_csa-1][1]) / \
(self.sfh_array[i_csa][0] - self.sfh_array[i_csa-1][0])
b_sfr = self.sfh_array[i_csa][1] - a_sfr * self.sfh_array[i_csa][0]
# While we stay in the same time bin ...
while t_csa <= self.sfh_array[i_csa][0]:
# Interpolate the SFR
self.sfr_input[i_dt_csa] = a_sfr * t_csa + b_sfr
# Cumulate the stellar mass formed
m_stel_sfr_in += self.sfr_input[i_dt_csa] * \
self.history.timesteps[i_dt_csa]
# Exit the loop if the array is full
if i_dt_csa >= nb_dt_csa:
break
# Calculate the new time
t_csa += self.history.timesteps[i_dt_csa]
i_dt_csa += 1
# Exit the loop if the array is full
if (i_dt_csa + 1) >= nb_dt_csa:
break
# If the array has been read completely, but the sfr_input array is
# not full, fil the rest of the array with the last read value
if self.sfh_array[-1][1] == 0.0:
sfr_temp = 0.0
else:
sfr_temp = self.sfr_input[i_dt_csa-1]
while i_dt_csa < nb_dt_csa - 1:
self.sfr_input[i_dt_csa] = sfr_temp
m_stel_sfr_in += self.sfr_input[i_dt_csa] * \
self.history.timesteps[i_dt_csa]
t_csa += self.history.timesteps[i_dt_csa]