class TestNebularPlasma(object): def setup(self): atom_data = atomic.AtomData.from_hdf5(helium_test_db) density = 1e-15 * u.Unit('g/cm3') abundance = parse_abundance_dict_to_dataframe({'He':1.0}) abundance = pd.DataFrame({0:abundance}) number_densities = abundance * density.to('g/cm^3').value number_densities = number_densities.div( atom_data.atom_data.mass.ix[number_densities.index], axis=0) self.plasma = LegacyPlasmaArray(number_densities, atomic_data=atom_data, time_explosion=10 * u.day, ionization_mode='nebular') # self.plasma = plasma_array.BasePlasmaArray.from_abundance( # {'He':1.0}, 1e-15*u.Unit('g/cm3'), atom_data, 10 * u.day, # ionization_mode='nebular', excitation_mode='dilute-lte') def test_high_temperature(self): with pytest.raises(ValueError) as excinfo: self.plasma.update_radiationfield(t_rad=[100000.], ws=[0.5], j_blues=None, nlte_config=None) assert str(excinfo.value).startswith('t_rads outside of zeta ' 'factor interpolation')
def test_he_nlte_plasma(number_density, atomic_data, time_explosion, t_rad, w, j_blues): he_nlte_plasma = LegacyPlasmaArray(number_densities=number_density, atomic_data=atomic_data, time_explosion=time_explosion, helium_treatment='recomb-nlte') he_nlte_plasma.update_radiationfield(t_rad, w, j_blues, nlte_config=None) assert np.allclose(he_nlte_plasma.get_value( 'ion_number_density').ix[2].ix[1], number_density.ix[2]) assert np.all(he_nlte_plasma.get_value( 'level_number_density').ix[2].ix[0].ix[0])==0.0 assert np.allclose(he_nlte_plasma.get_value( 'level_number_density').ix[2].sum(), number_density.ix[2])
def test_he_nlte_plasma(number_density, atomic_data, time_explosion, t_rad, w, j_blues): he_nlte_plasma = LegacyPlasmaArray(number_densities=number_density, atomic_data=atomic_data, time_explosion=time_explosion, helium_treatment='recomb-nlte') he_nlte_plasma.update_radiationfield(t_rad, w, j_blues, nlte_config=None) assert np.allclose( he_nlte_plasma.get_value('ion_number_density').ix[2].ix[1], number_density.ix[2]) assert np.all( he_nlte_plasma.get_value( 'level_number_density').ix[2].ix[0].ix[0]) == 0.0 assert np.allclose( he_nlte_plasma.get_value('level_number_density').ix[2].sum(), number_density.ix[2])
class TestNebularPlasma(object): def setup(self): atom_data = atomic.AtomData.from_hdf5(helium_test_db) density = 1e-15 * u.Unit("g/cm3") abundance = parse_abundance_dict_to_dataframe({"He": 1.0}) abundance = pd.DataFrame({0: abundance}) number_densities = abundance * density.to("g/cm^3").value number_densities = number_densities.div(atom_data.atom_data.mass.ix[number_densities.index], axis=0) self.plasma = LegacyPlasmaArray( number_densities, atomic_data=atom_data, time_explosion=10 * u.day, ionization_mode="nebular" ) # self.plasma = plasma_array.BasePlasmaArray.from_abundance( # {'He':1.0}, 1e-15*u.Unit('g/cm3'), atom_data, 10 * u.day, # ionization_mode='nebular', excitation_mode='dilute-lte') def test_high_temperature(self): with pytest.raises(ValueError) as excinfo: self.plasma.update_radiationfield(t_rad=[100000.0], ws=[0.5], j_blues=None, nlte_config=None) assert str(excinfo.value).startswith("t_rads outside of zeta " "factor interpolation")
class TestNebularPlasma(object): def setup(self): atom_data = atomic.AtomData.from_hdf5(helium_test_db) density = 1e-15 * u.Unit('g/cm3') abundance = parse_abundance_dict_to_dataframe({'He':1.0}) abundance = pd.DataFrame({0:abundance}) number_densities = abundance * density.to('g/cm^3').value number_densities = number_densities.div( atom_data.atom_data.mass.ix[number_densities.index], axis=0) self.plasma = LegacyPlasmaArray(number_densities, atomic_data=atom_data, time_explosion=10 * u.day, ionization_mode='nebular') # self.plasma = plasma_array.BasePlasmaArray.from_abundance( # {'He':1.0}, 1e-15*u.Unit('g/cm3'), atom_data, 10 * u.day, # ionization_mode='nebular', excitation_mode='dilute-lte') def test_high_temperature(self): with warnings.catch_warnings(record=True) as w: self.plasma.update_radiationfield(t_rad=[100000.], ws=[0.5], j_blues=None, nlte_config=None) assert str(w[0].message).startswith('t_rads outside of zeta factor' ' interpolation')
class Radial1DModel(object): """ Class to hold the states of the individual shells (the state of the plasma (as a `~plasma.BasePlasma`-object or one of its subclasses), , the plasma parameters (e.g. temperature, dilution factor), the dimensions of the shell). Parameters ---------- tardis_configuration : `tardis.config_reader.Configuration` velocities : `np.ndarray` an array with n+1 (for n shells) velocities (in cm/s) for each of the boundaries (velocities[0] describing the inner boundary and velocities[-1] the outer boundary densities : `np.ndarray` an array with n densities - being the density mid-shell (assumed for the whole shell) abundances : `list` or `dict` a dictionary for uniform abundances throughout all shells, e.g. dict(Fe=0.5, Si=0.5) For a different abundance for each shell list of abundance dictionaries. time_explosion : `float` time since explosion in seconds atom_data : `~tardis.atom_data.AtomData` class or subclass Containing the atom data needed for the plasma calculations ws : `None` or `list`-like ws can only be specified for plasma_type 'nebular'. If `None` is specified at first initialization the class calculates an initial geometric dilution factor. When giving a list positive values will be accepted, whereas negative values trigger the usage of the geometric calculation plasma_type : `str` plasma type currently supports 'lte' (using `tardis.plasma.LTEPlasma`) or 'nebular' (using `tardis.plasma.NebularPlasma`) initial_t_rad : `float`-like or `list`-like initial radiative temperature for each shell, if a scalar is specified it initializes with a uniform temperature for all shells """ @classmethod def from_h5(cls, buffer_or_fname): raise NotImplementedError("This is currently not implemented") def __init__(self, tardis_config): #final preparation for configuration object self.tardis_config = tardis_config self.atom_data = tardis_config.atom_data selected_atomic_numbers = self.tardis_config.abundances.index self.atom_data.prepare_atom_data( selected_atomic_numbers, line_interaction_type=tardis_config.plasma.line_interaction_type, nlte_species=tardis_config.plasma.nlte.species) if tardis_config.plasma.ionization == 'nebular': if not self.atom_data.has_zeta_data: raise ValueError("Requiring Recombination coefficients Zeta " "for 'nebular' plasma ionization") self.t_inner = tardis_config.plasma.t_inner self.ws = self.calculate_geometric_w( tardis_config.structure.r_middle, tardis_config.structure.r_inner[0]) if tardis_config.plasma.t_rads is None: self.t_rads = self._init_t_rad(self.t_inner, tardis_config.structure.v_inner[0], self.v_middle) else: self.t_rads = tardis_config.plasma.t_rads heating_rate_data_file = getattr(tardis_config.plasma, 'heating_rate_data_file', None) self.plasma_array = LegacyPlasmaArray( tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, delta_treatment=tardis_config.plasma.delta_treatment, ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, link_t_rad_t_electron=0.9, helium_treatment=tardis_config.plasma.helium_treatment, heating_rate_data_file=heating_rate_data_file, v_inner=tardis_config.structure.v_inner, v_outer=tardis_config.structure.v_outer) self.spectrum = TARDISSpectrum(tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_virtual = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_reabsorbed = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.calculate_j_blues(init_detailed_j_blues=True) self.update_plasmas(initialize_nlte=True) @property def line_interaction_type(self): return self._line_interaction_type @line_interaction_type.setter def line_interaction_type(self, value): if value in ['scatter', 'downbranch', 'macroatom']: self._line_interaction_type = value self.tardis_config.plasma.line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data( self.tardis_config.number_densities.columns, line_interaction_type=self.line_interaction_type, max_ion_number=None, nlte_species=self.tardis_config.plasma.nlte.species) else: raise ValueError('line_interaction_type can only be ' '"scatter", "downbranch", or "macroatom"') @property def t_inner(self): return self._t_inner @property def v_middle(self): structure = self.tardis_config.structure return 0.5 * (structure.v_inner + structure.v_outer) @t_inner.setter def t_inner(self, value): self._t_inner = value self.luminosity_inner = (4 * np.pi * constants.sigma_sb.cgs * self.tardis_config.structure.r_inner[0]**2 * self.t_inner**4).to('erg/s') self.time_of_simulation = (1.0 * u.erg / self.luminosity_inner) self.j_blues_norm_factor = ( constants.c.cgs * self.tardis_config.supernova.time_explosion / (4 * np.pi * self.time_of_simulation * self.tardis_config.structure.volumes)) @staticmethod def calculate_geometric_w(r, r_inner): return 0.5 * (1 - np.sqrt(1 - (r_inner**2 / r**2).to(1).value)) @staticmethod def _init_t_rad(t_inner, v_boundary, v_middle): lambda_wien_inner = constants.b_wien / t_inner return constants.b_wien / (lambda_wien_inner * (1 + (v_middle - v_boundary) / constants.c)) def calculate_j_blues(self, init_detailed_j_blues=False): nus = self.atom_data.lines.nu.values radiative_rates_type = self.tardis_config.plasma.radiative_rates_type w_epsilon = self.tardis_config.plasma.w_epsilon if radiative_rates_type == 'blackbody': logger.info('Calculating J_blues for radiative_rates_type=lte') j_blues = intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'dilute-blackbody' or init_detailed_j_blues: logger.info( 'Calculating J_blues for radiative_rates_type=dilute-blackbody' ) j_blues = self.ws * intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'detailed': logger.info('Calculating J_blues for radiate_rates_type=detailed') self.j_blues = pd.DataFrame(self.j_blue_estimators * self.j_blues_norm_factor.value, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) for i in xrange(self.tardis_config.structure.no_of_shells): zero_j_blues = self.j_blues[i] == 0.0 self.j_blues[i][zero_j_blues] = ( w_epsilon * intensity_black_body( self.atom_data.lines.nu[zero_j_blues].values, self.t_rads.value[i])) else: raise ValueError('radiative_rates_type type unknown - %s', radiative_rates_type) def update_plasmas(self, initialize_nlte=False): self.plasma_array.update_radiationfield( self.t_rads.value, self.ws, self.j_blues, self.tardis_config.plasma.nlte, initialize_nlte=initialize_nlte, n_e_convergence_threshold=0.05) if self.tardis_config.plasma.line_interaction_type in ('downbranch', 'macroatom'): self.transition_probabilities = ( self.plasma_array.transition_probabilities) def save_spectra(self, fname): self.spectrum.to_ascii(fname) self.spectrum_virtual.to_ascii('virtual_' + fname) def to_hdf5(self, buffer_or_fname, path='', close_h5=True): """ This allows the model to be written to an HDF5 file for later analysis. Currently, the saved properties are specified hard coded in include_from_model_in_hdf5. This is a dict where the key corresponds to the name of the property and the value describes the type. If the value is None the property can be dumped to hdf via its attribute to_hdf or by converting it to a pd.DataFrame. For more complex properties which can not simply be dumped to an hdf file the dict can contain a function which is called with the parameters key, path, and hdf_store. This function then should dump the data to the given hdf_store object. To dump properties of sub-properties of the model, you can use a dict as value. This dict is then treated in the same way as described above. Parameters ---------- buffer_or_fname: buffer or ~str buffer or filename for HDF5 file (see pandas.HDFStore for description) path: ~str, optional path in the HDF5 file close_h5: ~bool close the HDF5 file or not. """ # Functions to save properties of the model without to_hdf attribute and no simple conversion to a pd.DataFrame. #This functions are always called with the parameters key, path and, hdf_store. def _save_luminosity_density(key, path, hdf_store): luminosity_density = pd.DataFrame.from_dict( dict(wave=self.spectrum.wavelength.value, flux=self.spectrum.luminosity_density_lambda.value)) luminosity_density.to_hdf(hdf_store, os.path.join(path, key)) def _save_spectrum_virtual(key, path, hdf_store): if self.spectrum_virtual.luminosity_density_lambda is not None: luminosity_density_virtual = pd.DataFrame.from_dict( dict(wave=self.spectrum_virtual.wavelength.value, flux=self.spectrum_virtual.luminosity_density_lambda. value)) luminosity_density_virtual.to_hdf(hdf_store, os.path.join(path, key)) def _save_configuration_dict(key, path, hdf_store): configuration_dict = dict( t_inner=self.t_inner.value, time_of_simulation=self.time_of_simulation) configuration_dict_path = os.path.join(path, 'configuration') pd.Series(configuration_dict).to_hdf(hdf_store, configuration_dict_path) include_from_plasma_ = { 'level_number_density': None, 'ion_number_density': None, 'tau_sobolevs': None, 'electron_densities': None, 't_rad': None, 'w': None } include_from_runner_ = { 'virt_packet_last_interaction_type': None, 'virt_packet_last_line_interaction_in_id': None, 'virt_packet_last_line_interaction_out_id': None, 'virt_packet_last_interaction_in_nu': None, 'virt_packet_nus': None, 'virt_packet_energies': None } include_from_model_in_hdf5 = { 'plasma_array': include_from_plasma_, 'j_blues': None, 'runner': include_from_runner_, 'last_line_interaction_in_id': None, 'last_line_interaction_out_id': None, 'last_line_interaction_shell_id': None, 'montecarlo_nu': None, 'luminosity_density': _save_luminosity_density, 'luminosity_density_virtual': _save_spectrum_virtual, 'configuration_dict': _save_configuration_dict, 'last_line_interaction_angstrom': None } if isinstance(buffer_or_fname, basestring): hdf_store = pd.HDFStore(buffer_or_fname) elif isinstance(buffer_or_fname, pd.HDFStore): hdf_store = buffer_or_fname else: raise IOError('Please specify either a filename or an HDFStore') logger.info('Writing to path %s', path) def _get_hdf5_path(path, property_name): return os.path.join(path, property_name) def _to_smallest_pandas(object): try: return pd.Series(object) except Exception: return pd.DataFrame(object) def _save_model_property(object, property_name, path, hdf_store): property_path = _get_hdf5_path(path, property_name) try: object.to_hdf(hdf_store, property_path) except AttributeError: _to_smallest_pandas(object).to_hdf(hdf_store, property_path) for key in include_from_model_in_hdf5: if include_from_model_in_hdf5[key] is None: _save_model_property(getattr(self, key), key, path, hdf_store) elif callable(include_from_model_in_hdf5[key]): include_from_model_in_hdf5[key](key, path, hdf_store) else: try: for subkey in include_from_model_in_hdf5[key]: if include_from_model_in_hdf5[key][subkey] is None: _save_model_property( getattr(getattr(self, key), subkey), subkey, os.path.join(path, key), hdf_store) elif callable(include_from_model_in_hdf5[key][subkey]): include_from_model_in_hdf5[key][subkey]( subkey, os.path.join(path, key), hdf_store) else: logger.critical( 'Can not save %s', str(os.path.join(path, key, subkey))) except: logger.critical( 'An error occurred while dumping %s to HDF.', str(os.path.join(path, key))) hdf_store.flush() if close_h5: hdf_store.close() else: return hdf_store
class Radial1DModel(object): """ Class to hold the states of the individual shells (the state of the plasma (as a `~plasma.BasePlasma`-object or one of its subclasses), , the plasma parameters (e.g. temperature, dilution factor), the dimensions of the shell). Parameters ---------- tardis_configuration : `tardis.config_reader.Configuration` velocities : `np.ndarray` an array with n+1 (for n shells) velocities (in cm/s) for each of the boundaries (velocities[0] describing the inner boundary and velocities[-1] the outer boundary densities : `np.ndarray` an array with n densities - being the density mid-shell (assumed for the whole shell) abundances : `list` or `dict` a dictionary for uniform abundances throughout all shells, e.g. dict(Fe=0.5, Si=0.5) For a different abundance for each shell list of abundance dictionaries. time_explosion : `float` time since explosion in seconds atom_data : `~tardis.atom_data.AtomData` class or subclass Containing the atom data needed for the plasma calculations ws : `None` or `list`-like ws can only be specified for plasma_type 'nebular'. If `None` is specified at first initialization the class calculates an initial geometric dilution factor. When giving a list positive values will be accepted, whereas negative values trigger the usage of the geometric calculation plasma_type : `str` plasma type currently supports 'lte' (using `tardis.plasma.LTEPlasma`) or 'nebular' (using `tardis.plasma.NebularPlasma`) initial_t_rad : `float`-like or `list`-like initial radiative temperature for each shell, if a scalar is specified it initializes with a uniform temperature for all shells """ @classmethod def from_h5(cls, buffer_or_fname): raise NotImplementedError("This is currently not implemented") def __init__(self, tardis_config): #final preparation for configuration object self.tardis_config = tardis_config self.gui = None self.converged = False self.atom_data = tardis_config.atom_data selected_atomic_numbers = self.tardis_config.abundances.index self.atom_data.prepare_atom_data(selected_atomic_numbers, line_interaction_type=tardis_config.plasma.line_interaction_type, nlte_species=tardis_config.plasma.nlte.species) if tardis_config.plasma.ionization == 'nebular': if not self.atom_data.has_zeta_data: raise ValueError("Requiring Recombination coefficients Zeta for 'nebular' plasma ionization") self.packet_src = packet_source.SimplePacketSource.from_wavelength(tardis_config.montecarlo.black_body_sampling.start, tardis_config.montecarlo.black_body_sampling.end, blackbody_sampling=tardis_config.montecarlo.black_body_sampling.samples, seed=self.tardis_config.montecarlo.seed) self.current_no_of_packets = tardis_config.montecarlo.no_of_packets self.t_inner = tardis_config.plasma.t_inner self.t_rads = tardis_config.plasma.t_rads self.iterations_max_requested = tardis_config.montecarlo.iterations self.iterations_remaining = self.iterations_max_requested self.iterations_executed = 0 if tardis_config.montecarlo.convergence_strategy.type == 'specific': self.global_convergence_parameters = (tardis_config.montecarlo. convergence_strategy. deepcopy()) self.t_rads = tardis_config.plasma.t_rads t_inner_lock_cycle = [False] * (tardis_config.montecarlo. convergence_strategy. lock_t_inner_cycles) t_inner_lock_cycle[0] = True self.t_inner_update = itertools.cycle(t_inner_lock_cycle) self.ws = (0.5 * (1 - np.sqrt(1 - (tardis_config.structure.r_inner[0] ** 2 / tardis_config.structure.r_middle ** 2).to(1).value))) self.plasma_array = LegacyPlasmaArray(tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, delta_treatment=tardis_config.plasma.delta_treatment, ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, link_t_rad_t_electron=0.9) self.spectrum = TARDISSpectrum(tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_virtual = TARDISSpectrum(tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_reabsorbed = TARDISSpectrum(tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.runner = MontecarloRunner() @property def line_interaction_type(self): return self._line_interaction_type @line_interaction_type.setter def line_interaction_type(self, value): if value in ['scatter', 'downbranch', 'macroatom']: self._line_interaction_type = value self.tardis_config.plasma.line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data(self.tardis_config.number_densities.columns, line_interaction_type=self.line_interaction_type, max_ion_number=None, nlte_species=self.tardis_config.plasma.nlte.species) else: raise ValueError('line_interaction_type can only be "scatter", "downbranch", or "macroatom"') @property def t_inner(self): return self._t_inner @t_inner.setter def t_inner(self, value): self._t_inner = value self.luminosity_inner = (4 * np.pi * constants.sigma_sb.cgs * self.tardis_config.structure.r_inner[0] ** 2 * \ self.t_inner ** 4).to('erg/s') self.time_of_simulation = (1.0 * u.erg / self.luminosity_inner) self.j_blues_norm_factor = constants.c.cgs * self.tardis_config.supernova.time_explosion / \ (4 * np.pi * self.time_of_simulation * self.tardis_config.structure.volumes) def calculate_j_blues(self, init_detailed_j_blues=False): nus = self.atom_data.lines.nu.values radiative_rates_type = self.tardis_config.plasma.radiative_rates_type w_epsilon = self.tardis_config.plasma.w_epsilon if radiative_rates_type == 'blackbody': logger.info('Calculating J_blues for radiative_rates_type=lte') j_blues = intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'dilute-blackbody' or init_detailed_j_blues: logger.info('Calculating J_blues for radiative_rates_type=dilute-blackbody') j_blues = self.ws * intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'detailed': logger.info('Calculating J_blues for radiate_rates_type=detailed') self.j_blues = pd.DataFrame(self.j_blue_estimators.transpose() * self.j_blues_norm_factor.value, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) for i in xrange(self.tardis_config.structure.no_of_shells): zero_j_blues = self.j_blues[i] == 0.0 self.j_blues[i][zero_j_blues] = w_epsilon * intensity_black_body( self.atom_data.lines.nu.values[zero_j_blues], self.t_rads.value[i]) else: raise ValueError('radiative_rates_type type unknown - %s', radiative_rates_type) def update_plasmas(self, initialize_nlte=False): self.plasma_array.update_radiationfield(self.t_rads.value, self.ws, self.j_blues, self.tardis_config.plasma.nlte, initialize_nlte=initialize_nlte, n_e_convergence_threshold=0.05) if self.tardis_config.plasma.line_interaction_type in ('downbranch', 'macroatom'): self.transition_probabilities = self.plasma_array.transition_probabilities def update_radiationfield(self, log_sampling=5): """ Updating radiation field """ convergence_section = self.tardis_config.montecarlo.convergence_strategy updated_t_rads, updated_ws = ( self.runner.calculate_radiationfield_properties()) old_t_rads = self.t_rads.copy() old_ws = self.ws.copy() old_t_inner = self.t_inner luminosity_wavelength_filter = (self.montecarlo_nu > self.tardis_config.supernova.luminosity_nu_start) & \ (self.montecarlo_nu < self.tardis_config.supernova.luminosity_nu_end) emitted_filter = self.montecarlo_luminosity.value >= 0 emitted_luminosity = np.sum(self.montecarlo_luminosity.value[emitted_filter & luminosity_wavelength_filter]) \ * self.montecarlo_luminosity.unit absorbed_luminosity = -np.sum(self.montecarlo_luminosity.value[~emitted_filter & luminosity_wavelength_filter]) \ * self.montecarlo_luminosity.unit updated_t_inner = self.t_inner \ * (emitted_luminosity / self.tardis_config.supernova.luminosity_requested).to(1).value \ ** convergence_section.t_inner_update_exponent #updated_t_inner = np.max([np.min([updated_t_inner, 30000]), 3000]) convergence_t_rads = (abs(old_t_rads - updated_t_rads) / updated_t_rads).value convergence_ws = (abs(old_ws - updated_ws) / updated_ws) convergence_t_inner = (abs(old_t_inner - updated_t_inner) / updated_t_inner).value if convergence_section.type == 'damped' or convergence_section.type == 'specific': self.t_rads += convergence_section.t_rad.damping_constant * (updated_t_rads - self.t_rads) self.ws += convergence_section.w.damping_constant * (updated_ws - self.ws) if self.t_inner_update.next(): t_inner_new = self.t_inner + convergence_section.t_inner.damping_constant * (updated_t_inner - self.t_inner) else: t_inner_new = self.t_inner if convergence_section.type == 'specific': t_rad_converged = (float(np.sum(convergence_t_rads < convergence_section.t_rad['threshold'])) \ / self.tardis_config.structure.no_of_shells) >= convergence_section.t_rad['fraction'] w_converged = (float(np.sum(convergence_t_rads < convergence_section.w['threshold'])) \ / self.tardis_config.structure.no_of_shells) >= convergence_section.w['fraction'] t_inner_converged = convergence_t_inner < convergence_section.t_inner['threshold'] if t_rad_converged and t_inner_converged and w_converged: if not self.converged: self.converged = True self.iterations_remaining = self.global_convergence_parameters['hold_iterations'] else: if self.converged: self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False self.temperature_logging = pd.DataFrame( {'t_rads': old_t_rads.value, 'updated_t_rads': updated_t_rads.value, 'converged_t_rads': convergence_t_rads, 'new_trads': self.t_rads.value, 'ws': old_ws, 'updated_ws': updated_ws, 'converged_ws': convergence_ws, 'new_ws': self.ws}) self.temperature_logging.index.name = 'Shell' temperature_logging = str(self.temperature_logging[::log_sampling]) temperature_logging = ''.join(['\t%s\n' % item for item in temperature_logging.split('\n')]) logger.info('Plasma stratification:\n%s\n', temperature_logging) logger.info("Luminosity emitted = %.5e Luminosity absorbed = %.5e Luminosity requested = %.5e", emitted_luminosity.value, absorbed_luminosity.value, self.tardis_config.supernova.luminosity_requested.value) logger.info('Calculating new t_inner = %.3f', updated_t_inner.value) return t_inner_new def simulate(self, update_radiation_field=True, enable_virtual=False, initialize_j_blues=False, initialize_nlte=False): """ Run a simulation """ if update_radiation_field: t_inner_new = self.update_radiationfield() else: t_inner_new = self.t_inner self.calculate_j_blues(init_detailed_j_blues=initialize_j_blues) self.update_plasmas(initialize_nlte=initialize_nlte) self.t_inner = t_inner_new self.packet_src.create_packets(self.current_no_of_packets, self.t_inner.value) if enable_virtual: no_of_virtual_packets = self.tardis_config.montecarlo.no_of_virtual_packets else: no_of_virtual_packets = 0 if np.any(np.isnan(self.plasma_array.tau_sobolevs.values)) or np.any(np.isinf(self.plasma_array.tau_sobolevs.values)) \ or np.any(np.isneginf(self.plasma_array.tau_sobolevs.values)): raise ValueError('Some tau_sobolevs are nan, inf, -inf in tau_sobolevs. Something went wrong!') self.j_blue_estimators = np.zeros((len(self.t_rads), len(self.atom_data.lines))) self.montecarlo_virtual_luminosity = np.zeros_like(self.spectrum.frequency.value) self.runner.run(self, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads) #self = model (montecarlo_nu, montecarlo_energies, self.j_estimators, self.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, self.last_interaction_type, self.last_line_interaction_shell_id) = self.runner.legacy_return() if np.sum(montecarlo_energies < 0) == len(montecarlo_energies): logger.critical("No r-packet escaped through the outer boundary.") self.montecarlo_nu = self.runner.packet_nu self.montecarlo_luminosity = self.runner.packet_luminosity montecarlo_reabsorbed_luminosity = np.histogram( self.runner.reabsorbed_packet_nu, weights=self.runner.reabsorbed_packet_luminosity, bins=self.tardis_config.spectrum.frequency.value)[0] * u.erg / u.s montecarlo_emitted_luminosity = np.histogram( self.runner.emitted_packet_nu, weights=self.runner.emitted_packet_luminosity, bins=self.tardis_config.spectrum.frequency.value)[0] * u.erg / u.s self.spectrum.update_luminosity(montecarlo_emitted_luminosity) self.spectrum_reabsorbed.update_luminosity(montecarlo_reabsorbed_luminosity) if no_of_virtual_packets > 0: self.montecarlo_virtual_luminosity = self.montecarlo_virtual_luminosity \ * 1 * u.erg / self.time_of_simulation self.spectrum_virtual.update_luminosity(self.montecarlo_virtual_luminosity) self.last_line_interaction_in_id = self.atom_data.lines_index.index.values[last_line_interaction_in_id] self.last_line_interaction_in_id = self.last_line_interaction_in_id[last_line_interaction_in_id != -1] self.last_line_interaction_out_id = self.atom_data.lines_index.index.values[last_line_interaction_out_id] self.last_line_interaction_out_id = self.last_line_interaction_out_id[last_line_interaction_out_id != -1] self.last_line_interaction_angstrom = self.montecarlo_nu[last_line_interaction_in_id != -1].to('angstrom', u.spectral()) self.iterations_executed += 1 self.iterations_remaining -= 1 if self.gui is not None: self.gui.update_data(self) self.gui.show() def save_spectra(self, fname): self.spectrum.to_ascii(fname) self.spectrum_virtual.to_ascii('virtual_' + fname) def to_hdf5(self, buffer_or_fname, path='', close_h5=True): """ This allows the model to be written to an HDF5 file for later analysis. Currently, the saved properties are specified hard coded in include_from_model_in_hdf5. This is a dict where the key corresponds to the name of the property and the value describes the type. If the value is None the property can be dumped to hdf via its attribute to_hdf or by converting it to a pd.DataFrame. For more complex properties which can not simply be dumped to an hdf file the dict can contain a function which is called with the parameters key, path, and hdf_store. This function then should dump the data to the given hdf_store object. To dump properties of sub-properties of the model, you can use a dict as value. This dict is then treated in the same way as described above. Parameters ---------- buffer_or_fname: buffer or ~str buffer or filename for HDF5 file (see pandas.HDFStore for description) path: ~str, optional path in the HDF5 file close_h5: ~bool close the HDF5 file or not. """ # Functions to save properties of the model without to_hdf attribute and no simple conversion to a pd.DataFrame. #This functions are always called with the parameters key, path and, hdf_store. def _save_luminosity_density(key, path, hdf_store): luminosity_density = pd.DataFrame.from_dict(dict(wave=self.spectrum.wavelength.value, flux=self.spectrum.luminosity_density_lambda.value)) luminosity_density.to_hdf(hdf_store, os.path.join(path, key)) def _save_spectrum_virtual(key, path, hdf_store): if self.spectrum_virtual.luminosity_density_lambda is not None: luminosity_density_virtual = pd.DataFrame.from_dict(dict(wave=self.spectrum_virtual.wavelength.value, flux=self.spectrum_virtual.luminosity_density_lambda.value)) luminosity_density_virtual.to_hdf(hdf_store, os.path.join(path, key)) def _save_configuration_dict(key, path, hdf_store): configuration_dict = dict(t_inner=self.t_inner.value) configuration_dict_path = os.path.join(path, 'configuration') pd.Series(configuration_dict).to_hdf(hdf_store, configuration_dict_path) include_from_plasma_ = {'level_number_density': None, 'ion_number_density': None, 'tau_sobolevs': None, 'electron_densities': None, 't_rad': None, 'w': None} include_from_model_in_hdf5 = {'plasma_array': include_from_plasma_, 'j_blues': None, 'last_line_interaction_in_id': None, 'last_line_interaction_out_id': None, 'last_line_interaction_shell_id': None, 'montecarlo_nu': None, 'luminosity_density': _save_luminosity_density, 'luminosity_density_virtual': _save_spectrum_virtual, 'configuration_dict': _save_configuration_dict, 'last_line_interaction_angstrom': None} if isinstance(buffer_or_fname, basestring): hdf_store = pd.HDFStore(buffer_or_fname) elif isinstance(buffer_or_fname, pd.HDFStore): hdf_store = buffer_or_fname else: raise IOError('Please specify either a filename or an HDFStore') logger.info('Writing to path %s', path) def _get_hdf5_path(path, property_name): return os.path.join(path, property_name) def _to_smallest_pandas(object): try: return pd.Series(object) except Exception: return pd.DataFrame(object) def _save_model_property(object, property_name, path, hdf_store): property_path = _get_hdf5_path(path, property_name) try: object.to_hdf(hdf_store, property_path) except AttributeError: _to_smallest_pandas(object).to_hdf(hdf_store, property_path) for key in include_from_model_in_hdf5: if include_from_model_in_hdf5[key] is None: _save_model_property(getattr(self, key), key, path, hdf_store) elif callable(include_from_model_in_hdf5[key]): include_from_model_in_hdf5[key](key, path, hdf_store) else: try: for subkey in include_from_model_in_hdf5[key]: if include_from_model_in_hdf5[key][subkey] is None: _save_model_property(getattr(getattr(self, key), subkey), subkey, os.path.join(path, key), hdf_store) elif callable(include_from_model_in_hdf5[key][subkey]): include_from_model_in_hdf5[key][subkey](subkey, os.path.join(path, key), hdf_store) else: logger.critical('Can not save %s', str(os.path.join(path, key, subkey))) except: logger.critical('An error occurred while dumping %s to HDF.', str(os.path.join(path, key))) hdf_store.flush() if close_h5: hdf_store.close() else: return hdf_store
class Radial1DModel(object): """ Class to hold the states of the individual shells (the state of the plasma (as a `~plasma.BasePlasma`-object or one of its subclasses), , the plasma parameters (e.g. temperature, dilution factor), the dimensions of the shell). Parameters ---------- tardis_configuration : `tardis.config_reader.Configuration` velocities : `np.ndarray` an array with n+1 (for n shells) velocities (in cm/s) for each of the boundaries (velocities[0] describing the inner boundary and velocities[-1] the outer boundary densities : `np.ndarray` an array with n densities - being the density mid-shell (assumed for the whole shell) abundances : `list` or `dict` a dictionary for uniform abundances throughout all shells, e.g. dict(Fe=0.5, Si=0.5) For a different abundance for each shell list of abundance dictionaries. time_explosion : `float` time since explosion in seconds atom_data : `~tardis.atom_data.AtomData` class or subclass Containing the atom data needed for the plasma calculations ws : `None` or `list`-like ws can only be specified for plasma_type 'nebular'. If `None` is specified at first initialization the class calculates an initial geometric dilution factor. When giving a list positive values will be accepted, whereas negative values trigger the usage of the geometric calculation plasma_type : `str` plasma type currently supports 'lte' (using `tardis.plasma.LTEPlasma`) or 'nebular' (using `tardis.plasma.NebularPlasma`) initial_t_rad : `float`-like or `list`-like initial radiative temperature for each shell, if a scalar is specified it initializes with a uniform temperature for all shells """ @classmethod def from_h5(cls, buffer_or_fname): raise NotImplementedError("This is currently not implemented") def __init__(self, tardis_config): #final preparation for configuration object self.tardis_config = tardis_config self.gui = None self.converged = False self.atom_data = tardis_config.atom_data selected_atomic_numbers = self.tardis_config.abundances.index self.atom_data.prepare_atom_data( selected_atomic_numbers, line_interaction_type=tardis_config.plasma.line_interaction_type, nlte_species=tardis_config.plasma.nlte.species) if tardis_config.plasma.ionization == 'nebular': if not self.atom_data.has_zeta_data: raise ValueError( "Requiring Recombination coefficients Zeta for 'nebular' plasma ionization" ) self.packet_src = packet_source.SimplePacketSource.from_wavelength( tardis_config.montecarlo.black_body_sampling.start, tardis_config.montecarlo.black_body_sampling.end, blackbody_sampling=tardis_config.montecarlo.black_body_sampling. samples, seed=self.tardis_config.montecarlo.seed) self.current_no_of_packets = tardis_config.montecarlo.no_of_packets self.t_inner = tardis_config.plasma.t_inner self.t_rads = tardis_config.plasma.t_rads self.iterations_max_requested = tardis_config.montecarlo.iterations self.iterations_remaining = self.iterations_max_requested self.iterations_executed = 0 if tardis_config.montecarlo.convergence_strategy.type == 'specific': self.global_convergence_parameters = ( tardis_config.montecarlo.convergence_strategy.deepcopy()) self.t_rads = tardis_config.plasma.t_rads t_inner_lock_cycle = [False] * ( tardis_config.montecarlo.convergence_strategy.lock_t_inner_cycles) t_inner_lock_cycle[0] = True self.t_inner_update = itertools.cycle(t_inner_lock_cycle) self.ws = ( 0.5 * (1 - np.sqrt(1 - (tardis_config.structure.r_inner[0]**2 / tardis_config.structure.r_middle**2).to(1).value))) self.plasma_array = LegacyPlasmaArray( tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, delta_treatment=tardis_config.plasma.delta_treatment, ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, link_t_rad_t_electron=0.9, helium_treatment=tardis_config.plasma.helium_treatment) self.spectrum = TARDISSpectrum(tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_virtual = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_reabsorbed = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.runner = MontecarloRunner() @property def line_interaction_type(self): return self._line_interaction_type @line_interaction_type.setter def line_interaction_type(self, value): if value in ['scatter', 'downbranch', 'macroatom']: self._line_interaction_type = value self.tardis_config.plasma.line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data( self.tardis_config.number_densities.columns, line_interaction_type=self.line_interaction_type, max_ion_number=None, nlte_species=self.tardis_config.plasma.nlte.species) else: raise ValueError( 'line_interaction_type can only be "scatter", "downbranch", or "macroatom"' ) @property def t_inner(self): return self._t_inner @t_inner.setter def t_inner(self, value): self._t_inner = value self.luminosity_inner = (4 * np.pi * constants.sigma_sb.cgs * self.tardis_config.structure.r_inner[0] ** 2 * \ self.t_inner ** 4).to('erg/s') self.time_of_simulation = (1.0 * u.erg / self.luminosity_inner) self.j_blues_norm_factor = constants.c.cgs * self.tardis_config.supernova.time_explosion / \ (4 * np.pi * self.time_of_simulation * self.tardis_config.structure.volumes) def calculate_j_blues(self, init_detailed_j_blues=False): nus = self.atom_data.lines.nu.values radiative_rates_type = self.tardis_config.plasma.radiative_rates_type w_epsilon = self.tardis_config.plasma.w_epsilon if radiative_rates_type == 'blackbody': logger.info('Calculating J_blues for radiative_rates_type=lte') j_blues = intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'dilute-blackbody' or init_detailed_j_blues: logger.info( 'Calculating J_blues for radiative_rates_type=dilute-blackbody' ) j_blues = self.ws * intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame(j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'detailed': logger.info('Calculating J_blues for radiate_rates_type=detailed') self.j_blues = pd.DataFrame(self.j_blue_estimators.transpose() * self.j_blues_norm_factor.value, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) for i in xrange(self.tardis_config.structure.no_of_shells): zero_j_blues = self.j_blues[i] == 0.0 self.j_blues[i][ zero_j_blues] = w_epsilon * intensity_black_body( self.atom_data.lines.nu.values[zero_j_blues], self.t_rads.value[i]) else: raise ValueError('radiative_rates_type type unknown - %s', radiative_rates_type) def update_plasmas(self, initialize_nlte=False): self.plasma_array.update_radiationfield( self.t_rads.value, self.ws, self.j_blues, self.tardis_config.plasma.nlte, initialize_nlte=initialize_nlte, n_e_convergence_threshold=0.05) if self.tardis_config.plasma.line_interaction_type in ('downbranch', 'macroatom'): self.transition_probabilities = self.plasma_array.transition_probabilities def update_radiationfield(self, log_sampling=5): """ Updating radiation field """ convergence_section = self.tardis_config.montecarlo.convergence_strategy updated_t_rads, updated_ws = ( self.runner.calculate_radiationfield_properties()) old_t_rads = self.t_rads.copy() old_ws = self.ws.copy() old_t_inner = self.t_inner luminosity_wavelength_filter = (self.montecarlo_nu > self.tardis_config.supernova.luminosity_nu_start) & \ (self.montecarlo_nu < self.tardis_config.supernova.luminosity_nu_end) emitted_filter = self.montecarlo_luminosity.value >= 0 emitted_luminosity = np.sum(self.montecarlo_luminosity.value[emitted_filter & luminosity_wavelength_filter]) \ * self.montecarlo_luminosity.unit absorbed_luminosity = -np.sum(self.montecarlo_luminosity.value[~emitted_filter & luminosity_wavelength_filter]) \ * self.montecarlo_luminosity.unit updated_t_inner = self.t_inner \ * (emitted_luminosity / self.tardis_config.supernova.luminosity_requested).to(1).value \ ** convergence_section.t_inner_update_exponent #updated_t_inner = np.max([np.min([updated_t_inner, 30000]), 3000]) convergence_t_rads = (abs(old_t_rads - updated_t_rads) / updated_t_rads).value convergence_ws = (abs(old_ws - updated_ws) / updated_ws) convergence_t_inner = (abs(old_t_inner - updated_t_inner) / updated_t_inner).value if convergence_section.type == 'damped' or convergence_section.type == 'specific': self.t_rads += convergence_section.t_rad.damping_constant * ( updated_t_rads - self.t_rads) self.ws += convergence_section.w.damping_constant * (updated_ws - self.ws) if self.t_inner_update.next(): t_inner_new = self.t_inner + convergence_section.t_inner.damping_constant * ( updated_t_inner - self.t_inner) else: t_inner_new = self.t_inner if convergence_section.type == 'specific': t_rad_converged = (float(np.sum(convergence_t_rads < convergence_section.t_rad['threshold'])) \ / self.tardis_config.structure.no_of_shells) >= convergence_section.t_rad['fraction'] w_converged = (float(np.sum(convergence_t_rads < convergence_section.w['threshold'])) \ / self.tardis_config.structure.no_of_shells) >= convergence_section.w['fraction'] t_inner_converged = convergence_t_inner < convergence_section.t_inner[ 'threshold'] if t_rad_converged and t_inner_converged and w_converged: if not self.converged: self.converged = True self.iterations_remaining = self.global_convergence_parameters[ 'hold_iterations'] else: if self.converged: self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False self.temperature_logging = pd.DataFrame({ 't_rads': old_t_rads.value, 'updated_t_rads': updated_t_rads.value, 'converged_t_rads': convergence_t_rads, 'new_trads': self.t_rads.value, 'ws': old_ws, 'updated_ws': updated_ws, 'converged_ws': convergence_ws, 'new_ws': self.ws }) self.temperature_logging.index.name = 'Shell' temperature_logging = str(self.temperature_logging[::log_sampling]) temperature_logging = ''.join( ['\t%s\n' % item for item in temperature_logging.split('\n')]) logger.info('Plasma stratification:\n%s\n', temperature_logging) logger.info( "Luminosity emitted = %.5e Luminosity absorbed = %.5e Luminosity requested = %.5e", emitted_luminosity.value, absorbed_luminosity.value, self.tardis_config.supernova.luminosity_requested.value) logger.info('Calculating new t_inner = %.3f', updated_t_inner.value) return t_inner_new def simulate(self, update_radiation_field=True, enable_virtual=False, initialize_j_blues=False, initialize_nlte=False): """ Run a simulation """ if update_radiation_field: t_inner_new = self.update_radiationfield() else: t_inner_new = self.t_inner self.calculate_j_blues(init_detailed_j_blues=initialize_j_blues) self.update_plasmas(initialize_nlte=initialize_nlte) self.t_inner = t_inner_new self.packet_src.create_packets(self.current_no_of_packets, self.t_inner.value) if enable_virtual: no_of_virtual_packets = self.tardis_config.montecarlo.no_of_virtual_packets else: no_of_virtual_packets = 0 if np.any(np.isnan(self.plasma_array.tau_sobolevs.values)) or np.any(np.isinf(self.plasma_array.tau_sobolevs.values)) \ or np.any(np.isneginf(self.plasma_array.tau_sobolevs.values)): raise ValueError( 'Some tau_sobolevs are nan, inf, -inf in tau_sobolevs. Something went wrong!' ) self.j_blue_estimators = np.zeros( (len(self.t_rads), len(self.atom_data.lines))) self.montecarlo_virtual_luminosity = np.zeros_like( self.spectrum.frequency.value) self.runner.run( self, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads) #self = model (montecarlo_nu, montecarlo_energies, self.j_estimators, self.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, self.last_interaction_type, self.last_line_interaction_shell_id) = self.runner.legacy_return() if np.sum(montecarlo_energies < 0) == len(montecarlo_energies): logger.critical("No r-packet escaped through the outer boundary.") self.montecarlo_nu = self.runner.packet_nu self.montecarlo_luminosity = self.runner.packet_luminosity montecarlo_reabsorbed_luminosity = np.histogram( self.runner.reabsorbed_packet_nu, weights=self.runner.reabsorbed_packet_luminosity, bins=self.tardis_config.spectrum.frequency.value)[0] * u.erg / u.s montecarlo_emitted_luminosity = np.histogram( self.runner.emitted_packet_nu, weights=self.runner.emitted_packet_luminosity, bins=self.tardis_config.spectrum.frequency.value)[0] * u.erg / u.s self.spectrum.update_luminosity(montecarlo_emitted_luminosity) self.spectrum_reabsorbed.update_luminosity( montecarlo_reabsorbed_luminosity) if no_of_virtual_packets > 0: self.montecarlo_virtual_luminosity = self.montecarlo_virtual_luminosity \ * 1 * u.erg / self.time_of_simulation self.spectrum_virtual.update_luminosity( self.montecarlo_virtual_luminosity) self.last_line_interaction_in_id = self.atom_data.lines_index.index.values[ last_line_interaction_in_id] self.last_line_interaction_in_id = self.last_line_interaction_in_id[ last_line_interaction_in_id != -1] self.last_line_interaction_out_id = self.atom_data.lines_index.index.values[ last_line_interaction_out_id] self.last_line_interaction_out_id = self.last_line_interaction_out_id[ last_line_interaction_out_id != -1] self.last_line_interaction_angstrom = self.montecarlo_nu[ last_line_interaction_in_id != -1].to('angstrom', u.spectral()) self.iterations_executed += 1 self.iterations_remaining -= 1 if self.gui is not None: self.gui.update_data(self) self.gui.show() def save_spectra(self, fname): self.spectrum.to_ascii(fname) self.spectrum_virtual.to_ascii('virtual_' + fname) def to_hdf5(self, buffer_or_fname, path='', close_h5=True): """ This allows the model to be written to an HDF5 file for later analysis. Currently, the saved properties are specified hard coded in include_from_model_in_hdf5. This is a dict where the key corresponds to the name of the property and the value describes the type. If the value is None the property can be dumped to hdf via its attribute to_hdf or by converting it to a pd.DataFrame. For more complex properties which can not simply be dumped to an hdf file the dict can contain a function which is called with the parameters key, path, and hdf_store. This function then should dump the data to the given hdf_store object. To dump properties of sub-properties of the model, you can use a dict as value. This dict is then treated in the same way as described above. Parameters ---------- buffer_or_fname: buffer or ~str buffer or filename for HDF5 file (see pandas.HDFStore for description) path: ~str, optional path in the HDF5 file close_h5: ~bool close the HDF5 file or not. """ # Functions to save properties of the model without to_hdf attribute and no simple conversion to a pd.DataFrame. #This functions are always called with the parameters key, path and, hdf_store. def _save_luminosity_density(key, path, hdf_store): luminosity_density = pd.DataFrame.from_dict( dict(wave=self.spectrum.wavelength.value, flux=self.spectrum.luminosity_density_lambda.value)) luminosity_density.to_hdf(hdf_store, os.path.join(path, key)) def _save_spectrum_virtual(key, path, hdf_store): if self.spectrum_virtual.luminosity_density_lambda is not None: luminosity_density_virtual = pd.DataFrame.from_dict( dict(wave=self.spectrum_virtual.wavelength.value, flux=self.spectrum_virtual.luminosity_density_lambda. value)) luminosity_density_virtual.to_hdf(hdf_store, os.path.join(path, key)) def _save_configuration_dict(key, path, hdf_store): configuration_dict = dict(t_inner=self.t_inner.value) configuration_dict_path = os.path.join(path, 'configuration') pd.Series(configuration_dict).to_hdf(hdf_store, configuration_dict_path) include_from_plasma_ = { 'level_number_density': None, 'ion_number_density': None, 'tau_sobolevs': None, 'electron_densities': None, 't_rad': None, 'w': None } include_from_model_in_hdf5 = { 'plasma_array': include_from_plasma_, 'j_blues': None, 'last_line_interaction_in_id': None, 'last_line_interaction_out_id': None, 'last_line_interaction_shell_id': None, 'montecarlo_nu': None, 'luminosity_density': _save_luminosity_density, 'luminosity_density_virtual': _save_spectrum_virtual, 'configuration_dict': _save_configuration_dict, 'last_line_interaction_angstrom': None } if isinstance(buffer_or_fname, basestring): hdf_store = pd.HDFStore(buffer_or_fname) elif isinstance(buffer_or_fname, pd.HDFStore): hdf_store = buffer_or_fname else: raise IOError('Please specify either a filename or an HDFStore') logger.info('Writing to path %s', path) def _get_hdf5_path(path, property_name): return os.path.join(path, property_name) def _to_smallest_pandas(object): try: return pd.Series(object) except Exception: return pd.DataFrame(object) def _save_model_property(object, property_name, path, hdf_store): property_path = _get_hdf5_path(path, property_name) try: object.to_hdf(hdf_store, property_path) except AttributeError: _to_smallest_pandas(object).to_hdf(hdf_store, property_path) for key in include_from_model_in_hdf5: if include_from_model_in_hdf5[key] is None: _save_model_property(getattr(self, key), key, path, hdf_store) elif callable(include_from_model_in_hdf5[key]): include_from_model_in_hdf5[key](key, path, hdf_store) else: try: for subkey in include_from_model_in_hdf5[key]: if include_from_model_in_hdf5[key][subkey] is None: _save_model_property( getattr(getattr(self, key), subkey), subkey, os.path.join(path, key), hdf_store) elif callable(include_from_model_in_hdf5[key][subkey]): include_from_model_in_hdf5[key][subkey]( subkey, os.path.join(path, key), hdf_store) else: logger.critical( 'Can not save %s', str(os.path.join(path, key, subkey))) except: logger.critical( 'An error occurred while dumping %s to HDF.', str(os.path.join(path, key))) hdf_store.flush() if close_h5: hdf_store.close() else: return hdf_store
class Radial1DModel(object): """ Class to hold the states of the individual shells (the state of the plasma (as a `~plasma.BasePlasma`-object or one of its subclasses), , the plasma parameters (e.g. temperature, dilution factor), the dimensions of the shell). Parameters ---------- tardis_configuration : `tardis.config_reader.Configuration` velocities : `np.ndarray` an array with n+1 (for n shells) velocities (in cm/s) for each of the boundaries (velocities[0] describing the inner boundary and velocities[-1] the outer boundary densities : `np.ndarray` an array with n densities - being the density mid-shell (assumed for the whole shell) abundances : `list` or `dict` a dictionary for uniform abundances throughout all shells, e.g. dict(Fe=0.5, Si=0.5) For a different abundance for each shell list of abundance dictionaries. time_explosion : `float` time since explosion in seconds atom_data : `~tardis.atom_data.AtomData` class or subclass Containing the atom data needed for the plasma calculations ws : `None` or `list`-like ws can only be specified for plasma_type 'nebular'. If `None` is specified at first initialization the class calculates an initial geometric dilution factor. When giving a list positive values will be accepted, whereas negative values trigger the usage of the geometric calculation plasma_type : `str` plasma type currently supports 'lte' (using `tardis.plasma.LTEPlasma`) or 'nebular' (using `tardis.plasma.NebularPlasma`) initial_t_rad : `float`-like or `list`-like initial radiative temperature for each shell, if a scalar is specified it initializes with a uniform temperature for all shells """ @classmethod def from_h5(cls, buffer_or_fname): raise NotImplementedError("This is currently not implemented") def __init__(self, tardis_config): #final preparation for configuration object self.tardis_config = tardis_config self.atom_data = tardis_config.atom_data selected_atomic_numbers = self.tardis_config.abundances.index self.atom_data.prepare_atom_data( selected_atomic_numbers, line_interaction_type=tardis_config.plasma.line_interaction_type, nlte_species=tardis_config.plasma.nlte.species) if tardis_config.plasma.ionization == 'nebular': if not self.atom_data.has_zeta_data: raise ValueError("Requiring Recombination coefficients Zeta " "for 'nebular' plasma ionization") self.t_inner = tardis_config.plasma.t_inner self.ws = self.calculate_geometric_w( tardis_config.structure.r_middle, tardis_config.structure.r_inner[0]) if tardis_config.plasma.t_rads is None: self.t_rads = self._init_t_rad( self.t_inner, tardis_config.structure.v_inner[0], self.v_middle) else: self.t_rads = tardis_config.plasma.t_rads heating_rate_data_file = getattr( tardis_config.plasma, 'heating_rate_data_file', None) self.plasma_array = LegacyPlasmaArray( tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, delta_treatment=tardis_config.plasma.delta_treatment, ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, link_t_rad_t_electron=0.9, helium_treatment=tardis_config.plasma.helium_treatment, heating_rate_data_file=heating_rate_data_file, v_inner=tardis_config.structure.v_inner, v_outer=tardis_config.structure.v_outer) self.spectrum = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_virtual = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.spectrum_reabsorbed = TARDISSpectrum( tardis_config.spectrum.frequency, tardis_config.supernova.distance) self.calculate_j_blues(init_detailed_j_blues=True) self.update_plasmas(initialize_nlte=True) @property def line_interaction_type(self): return self._line_interaction_type @line_interaction_type.setter def line_interaction_type(self, value): if value in ['scatter', 'downbranch', 'macroatom']: self._line_interaction_type = value self.tardis_config.plasma.line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data( self.tardis_config.number_densities.columns, line_interaction_type=self.line_interaction_type, max_ion_number=None, nlte_species=self.tardis_config.plasma.nlte.species) else: raise ValueError('line_interaction_type can only be ' '"scatter", "downbranch", or "macroatom"') @property def t_inner(self): return self._t_inner @property def v_middle(self): structure = self.tardis_config.structure return 0.5 * (structure.v_inner + structure.v_outer) @t_inner.setter def t_inner(self, value): self._t_inner = value self.luminosity_inner = ( 4 * np.pi * constants.sigma_sb.cgs * self.tardis_config.structure.r_inner[0] ** 2 * self.t_inner ** 4).to('erg/s') self.time_of_simulation = (1.0 * u.erg / self.luminosity_inner) self.j_blues_norm_factor = ( constants.c.cgs * self.tardis_config.supernova.time_explosion / (4 * np.pi * self.time_of_simulation * self.tardis_config.structure.volumes)) @staticmethod def calculate_geometric_w(r, r_inner): return 0.5 * (1 - np.sqrt(1 - (r_inner ** 2 / r ** 2).to(1).value)) @staticmethod def _init_t_rad(t_inner, v_boundary, v_middle): lambda_wien_inner = constants.b_wien / t_inner return constants.b_wien / ( lambda_wien_inner * (1 + (v_middle - v_boundary) / constants.c)) def calculate_j_blues(self, init_detailed_j_blues=False): nus = self.atom_data.lines.nu.values radiative_rates_type = self.tardis_config.plasma.radiative_rates_type w_epsilon = self.tardis_config.plasma.w_epsilon if radiative_rates_type == 'blackbody': logger.info('Calculating J_blues for radiative_rates_type=lte') j_blues = intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame( j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'dilute-blackbody' or init_detailed_j_blues: logger.info('Calculating J_blues for radiative_rates_type=dilute-blackbody') j_blues = self.ws * intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame( j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'detailed': logger.info('Calculating J_blues for radiate_rates_type=detailed') self.j_blues = pd.DataFrame( self.j_blue_estimators.transpose() * self.j_blues_norm_factor.value, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) for i in xrange(self.tardis_config.structure.no_of_shells): zero_j_blues = self.j_blues[i] == 0.0 self.j_blues[i][zero_j_blues] = ( w_epsilon * intensity_black_body( self.atom_data.lines.nu.values[zero_j_blues], self.t_rads.value[i])) else: raise ValueError('radiative_rates_type type unknown - %s', radiative_rates_type) def update_plasmas(self, initialize_nlte=False): self.plasma_array.update_radiationfield( self.t_rads.value, self.ws, self.j_blues, self.tardis_config.plasma.nlte, initialize_nlte=initialize_nlte, n_e_convergence_threshold=0.05) if self.tardis_config.plasma.line_interaction_type in ('downbranch', 'macroatom'): self.transition_probabilities = ( self.plasma_array.transition_probabilities) def save_spectra(self, fname): self.spectrum.to_ascii(fname) self.spectrum_virtual.to_ascii('virtual_' + fname) def to_hdf5(self, buffer_or_fname, path='', close_h5=True): """ This allows the model to be written to an HDF5 file for later analysis. Currently, the saved properties are specified hard coded in include_from_model_in_hdf5. This is a dict where the key corresponds to the name of the property and the value describes the type. If the value is None the property can be dumped to hdf via its attribute to_hdf or by converting it to a pd.DataFrame. For more complex properties which can not simply be dumped to an hdf file the dict can contain a function which is called with the parameters key, path, and hdf_store. This function then should dump the data to the given hdf_store object. To dump properties of sub-properties of the model, you can use a dict as value. This dict is then treated in the same way as described above. Parameters ---------- buffer_or_fname: buffer or ~str buffer or filename for HDF5 file (see pandas.HDFStore for description) path: ~str, optional path in the HDF5 file close_h5: ~bool close the HDF5 file or not. """ # Functions to save properties of the model without to_hdf attribute and no simple conversion to a pd.DataFrame. #This functions are always called with the parameters key, path and, hdf_store. def _save_luminosity_density(key, path, hdf_store): luminosity_density = pd.DataFrame.from_dict(dict(wave=self.spectrum.wavelength.value, flux=self.spectrum.luminosity_density_lambda.value)) luminosity_density.to_hdf(hdf_store, os.path.join(path, key)) def _save_spectrum_virtual(key, path, hdf_store): if self.spectrum_virtual.luminosity_density_lambda is not None: luminosity_density_virtual = pd.DataFrame.from_dict(dict(wave=self.spectrum_virtual.wavelength.value, flux=self.spectrum_virtual.luminosity_density_lambda.value)) luminosity_density_virtual.to_hdf(hdf_store, os.path.join(path, key)) def _save_configuration_dict(key, path, hdf_store): configuration_dict = dict(t_inner=self.t_inner.value,time_of_simulation=self.time_of_simulation) configuration_dict_path = os.path.join(path, 'configuration') pd.Series(configuration_dict).to_hdf(hdf_store, configuration_dict_path) include_from_plasma_ = {'level_number_density': None, 'ion_number_density': None, 'tau_sobolevs': None, 'electron_densities': None, 't_rad': None, 'w': None} include_from_runner_ = {'virt_packet_last_interaction_type': None, 'virt_packet_last_line_interaction_in_id': None, 'virt_packet_last_line_interaction_out_id': None, 'virt_packet_last_interaction_in_nu': None, 'virt_packet_nus': None, 'virt_packet_energies': None} include_from_model_in_hdf5 = {'plasma_array': include_from_plasma_, 'j_blues': None, 'runner': include_from_runner_, 'last_line_interaction_in_id': None, 'last_line_interaction_out_id': None, 'last_line_interaction_shell_id': None, 'montecarlo_nu': None, 'luminosity_density': _save_luminosity_density, 'luminosity_density_virtual': _save_spectrum_virtual, 'configuration_dict': _save_configuration_dict, 'last_line_interaction_angstrom': None} if isinstance(buffer_or_fname, basestring): hdf_store = pd.HDFStore(buffer_or_fname) elif isinstance(buffer_or_fname, pd.HDFStore): hdf_store = buffer_or_fname else: raise IOError('Please specify either a filename or an HDFStore') logger.info('Writing to path %s', path) def _get_hdf5_path(path, property_name): return os.path.join(path, property_name) def _to_smallest_pandas(object): try: return pd.Series(object) except Exception: return pd.DataFrame(object) def _save_model_property(object, property_name, path, hdf_store): property_path = _get_hdf5_path(path, property_name) try: object.to_hdf(hdf_store, property_path) except AttributeError: _to_smallest_pandas(object).to_hdf(hdf_store, property_path) for key in include_from_model_in_hdf5: if include_from_model_in_hdf5[key] is None: _save_model_property(getattr(self, key), key, path, hdf_store) elif callable(include_from_model_in_hdf5[key]): include_from_model_in_hdf5[key](key, path, hdf_store) else: try: for subkey in include_from_model_in_hdf5[key]: if include_from_model_in_hdf5[key][subkey] is None: _save_model_property(getattr(getattr(self, key), subkey), subkey, os.path.join(path, key), hdf_store) elif callable(include_from_model_in_hdf5[key][subkey]): include_from_model_in_hdf5[key][subkey](subkey, os.path.join(path, key), hdf_store) else: logger.critical('Can not save %s', str(os.path.join(path, key, subkey))) except: logger.critical('An error occurred while dumping %s to HDF.', str(os.path.join(path, key))) hdf_store.flush() if close_h5: hdf_store.close() else: return hdf_store
class Radial1DModel(object): """ Class to hold the states of the individual shells (the state of the plasma (as a `~plasma.BasePlasma`-object or one of its subclasses), , the plasma parameters (e.g. temperature, dilution factor), the dimensions of the shell). Parameters ---------- tardis_configuration : `tardis.config_reader.Configuration` velocities : `np.ndarray` an array with n+1 (for n shells) velocities (in cm/s) for each of the boundaries (velocities[0] describing the inner boundary and velocities[-1] the outer boundary densities : `np.ndarray` an array with n densities - being the density mid-shell (assumed for the whole shell) abundances : `list` or `dict` a dictionary for uniform abundances throughout all shells, e.g. dict(Fe=0.5, Si=0.5) For a different abundance for each shell list of abundance dictionaries. time_explosion : `float` time since explosion in seconds atom_data : `~tardis.atom_data.AtomData` class or subclass Containing the atom data needed for the plasma calculations ws : `None` or `list`-like ws can only be specified for plasma_type 'nebular'. If `None` is specified at first initialization the class calculates an initial geometric dilution factor. When giving a list positive values will be accepted, whereas negative values trigger the usage of the geometric calculation plasma_type : `str` plasma type currently supports 'lte' (using `tardis.plasma.LTEPlasma`) or 'nebular' (using `tardis.plasma.NebularPlasma`) initial_t_rad : `float`-like or `list`-like initial radiative temperature for each shell, if a scalar is specified it initializes with a uniform temperature for all shells """ @classmethod def from_h5(cls, buffer_or_fname): raise NotImplementedError("This is currently not implemented") def __init__(self, tardis_config): #final preparation for configuration object self.tardis_config = tardis_config self.atom_data = tardis_config.atom_data selected_atomic_numbers = self.tardis_config.abundances.index self.atom_data.prepare_atom_data( selected_atomic_numbers, line_interaction_type=tardis_config.plasma.line_interaction_type, nlte_species=tardis_config.plasma.nlte.species) if tardis_config.plasma.ionization == 'nebular': if not self.atom_data.has_zeta_data: raise ValueError("Requiring Recombination coefficients Zeta " "for 'nebular' plasma ionization") self.t_inner = tardis_config.plasma.t_inner self.v_inner = tardis_config.structure.v_inner self.v_outer = tardis_config.structure.v_outer self.v_middle = 0.5 * (self.v_inner + self.v_outer) self.ws = self.calculate_geometric_w( tardis_config.structure.r_middle, tardis_config.structure.r_inner[0]) if tardis_config.plasma.t_rads is None: self.t_rads = self._init_t_rad( self.t_inner, self.v_inner[0], self.v_middle) else: self.t_rads = tardis_config.plasma.t_rads heating_rate_data_file = getattr( tardis_config.plasma, 'heating_rate_data_file', None) self.plasma = LegacyPlasmaArray( tardis_config.number_densities, tardis_config.atom_data, tardis_config.supernova.time_explosion.to('s').value, nlte_config=tardis_config.plasma.nlte, delta_treatment=tardis_config.plasma.get('delta_treatment', None), ionization_mode=tardis_config.plasma.ionization, excitation_mode=tardis_config.plasma.excitation, line_interaction_type=tardis_config.plasma.line_interaction_type, link_t_rad_t_electron=0.9, helium_treatment=tardis_config.plasma.helium_treatment, heating_rate_data_file=heating_rate_data_file, v_inner=self.v_inner, v_outer=self.v_outer) self.calculate_j_blues(init_detailed_j_blues=True) self.Edotlu = np.zeros(np.shape(self.j_blues.shape)) self.update_plasmas(initialize_nlte=True) @property @deprecated('v1.5', 'spectrum will be removed from model. Use model.runner.spectrum instead.') def spectrum(self): return self.runner.spectrum @property @deprecated('v1.5', 'spectrum_virtual will be removed from model. Use model.runner.spectrum_virtual instead.') def spectrum_virtual(self): return self.runner.spectrum_virtual @property @deprecated('v1.5', 'spectrum_reabsorbed will be removed model. Use model.runner.spectrum_reabsorbed instead.') def spectrum_reabsorbed(self): return self.runner.spectrum_reabsorbed @property @deprecated('v1.5', 'plasma_array has been renamed to plasma and will be removed in the future. Please use model.plasma instead.') def plasma_array(self): return self.plasma @property def line_interaction_type(self): return self._line_interaction_type @line_interaction_type.setter def line_interaction_type(self, value): if value in ['scatter', 'downbranch', 'macroatom']: self._line_interaction_type = value self.tardis_config.plasma.line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data( self.tardis_config.number_densities.columns, line_interaction_type=self.line_interaction_type, max_ion_number=None, nlte_species=self.tardis_config.plasma.nlte.species) else: raise ValueError('line_interaction_type can only be ' '"scatter", "downbranch", or "macroatom"') @property def t_inner(self): return self._t_inner @t_inner.setter def t_inner(self, value): self._t_inner = value self.luminosity_inner = ( 4 * np.pi * constants.sigma_sb.cgs * self.tardis_config.structure.r_inner[0] ** 2 * self.t_inner ** 4).to('erg/s') self.time_of_simulation = (1.0 * u.erg / self.luminosity_inner) self.j_blues_norm_factor = ( constants.c.cgs * self.tardis_config.supernova.time_explosion / (4 * np.pi * self.time_of_simulation * self.tardis_config.structure.volumes)) @staticmethod def calculate_geometric_w(r, r_inner): return 0.5 * (1 - np.sqrt(1 - (r_inner ** 2 / r ** 2).to(1).value)) @staticmethod def _init_t_rad(t_inner, v_boundary, v_middle): lambda_wien_inner = constants.b_wien / t_inner return constants.b_wien / ( lambda_wien_inner * (1 + (v_middle - v_boundary) / constants.c)) def calculate_j_blues(self, init_detailed_j_blues=False): nus = self.atom_data.lines.nu.values radiative_rates_type = self.tardis_config.plasma.radiative_rates_type w_epsilon = self.tardis_config.plasma.w_epsilon if radiative_rates_type == 'blackbody': logger.info('Calculating J_blues for radiative_rates_type=lte') j_blues = intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame( j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'dilute-blackbody' or init_detailed_j_blues: logger.info('Calculating J_blues for radiative_rates_type=dilute-blackbody') j_blues = self.ws * intensity_black_body(nus[np.newaxis].T, self.t_rads.value) self.j_blues = pd.DataFrame( j_blues, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) elif radiative_rates_type == 'detailed': logger.info('Calculating J_blues for radiate_rates_type=detailed') self.j_blues = pd.DataFrame( self.j_blue_estimators * self.j_blues_norm_factor.value, index=self.atom_data.lines.index, columns=np.arange(len(self.t_rads))) for i in xrange(self.tardis_config.structure.no_of_shells): zero_j_blues = self.j_blues[i] == 0.0 self.j_blues[i][zero_j_blues] = ( w_epsilon * intensity_black_body( self.atom_data.lines.nu[zero_j_blues].values, self.t_rads.value[i])) else: raise ValueError('radiative_rates_type type unknown - %s', radiative_rates_type) def update_plasmas(self, initialize_nlte=False): self.plasma.update_radiationfield( self.t_rads.value, self.ws, self.j_blues, self.tardis_config.plasma.nlte, initialize_nlte=initialize_nlte, n_e_convergence_threshold=0.05) if self.tardis_config.plasma.line_interaction_type in ('downbranch', 'macroatom'): self.transition_probabilities = ( self.plasma.transition_probabilities) def save_spectra(self, fname): self.spectrum.to_ascii(fname) self.spectrum_virtual.to_ascii('virtual_' + fname) def to_hdf(self, path_or_buf, path='', plasma_properties=None): """ Store the model to an HDF structure. Parameters ---------- path_or_buf Path or buffer to the HDF store path : str Path inside the HDF store to store the model plasma_properties `None` or a `PlasmaPropertyCollection` which will be passed as the collection argument to the plasma.to_hdf method. Returns ------- None """ model_path = os.path.join(path, 'model') properties = ['t_inner', 'ws', 't_rads', 'v_inner', 'v_outer'] to_hdf(path_or_buf, model_path, {name: getattr(self, name) for name in properties}) self.plasma.to_hdf(path_or_buf, model_path, plasma_properties) metadata = pd.Series({'atom_data_uuid': self.atom_data.uuid1}) metadata.to_hdf(path_or_buf, os.path.join(model_path, 'metadata'))