def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner(self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency) 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)
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()
def from_config(cls, config, **kwargs): """ Create a new Simulation instance from a Configuration object. Parameters ---------- config : tardis.io.config_reader.Configuration **kwargs Allow overriding some structures, such as model, plasma, atomic data and the runner, instead of creating them from the configuration object. Returns ------- Simulation """ # Allow overriding some config structures. This is useful in some # unit tests, and could be extended in all the from_config classmethods. if "model" in kwargs: model = kwargs["model"] else: model = Radial1DModel.from_config(config) if "plasma" in kwargs: plasma = kwargs["plasma"] else: plasma = assemble_plasma(config, model, atom_data=kwargs.get("atom_data", None)) if "runner" in kwargs: runner = kwargs["runner"] else: runner = MontecarloRunner.from_config(config) luminosity_nu_start = config.supernova.luminosity_wavelength_end.to(u.Hz, u.spectral()) try: luminosity_nu_end = config.supernova.luminosity_wavelength_start.to(u.Hz, u.spectral()) except ZeroDivisionError: luminosity_nu_end = np.inf * u.Hz last_no_of_packets = config.montecarlo.last_no_of_packets if last_no_of_packets is None or last_no_of_packets < 0: last_no_of_packets = config.montecarlo.no_of_packets last_no_of_packets = int(last_no_of_packets) return cls( iterations=config.montecarlo.iterations, model=model, plasma=plasma, runner=runner, no_of_packets=int(config.montecarlo.no_of_packets), no_of_virtual_packets=int(config.montecarlo.no_of_virtual_packets), luminosity_nu_start=luminosity_nu_start, luminosity_nu_end=luminosity_nu_end, last_no_of_packets=last_no_of_packets, luminosity_requested=config.supernova.luminosity_requested.cgs, convergence_strategy=config.montecarlo.convergence_strategy, nthreads=config.montecarlo.nthreads, )
def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner(self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency) 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)
def runner(): config_fname = "tardis/io/tests/data/tardis_configv1_verysimply.yml" config = config_reader.Configuration.from_yaml(config_fname) runner = MontecarloRunner.from_config(config) return runner
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
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()
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 Simulation(object): converged = False def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner(self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency, tardis_config.supernova.get('distance', None)) 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) def run_single_montecarlo(self, model, no_of_packets, no_of_virtual_packets=0,last_run=False): """ Will do a single TARDIS iteration with the given model Parameters ---------- model: ~tardis.model.Radial1DModel no_of_packet: ~int no_of_virtual_packets: ~int default is 0 and switches of the virtual packet mode. Recommended is 3. Returns ------- : None """ self.runner.run(model, no_of_packets, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads,last_run=last_run) (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.") def calculate_emitted_luminosity(self): """ Returns ------- """ return self.runner.calculate_emitted_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def calculate_reabsorbed_luminosity(self): return self.runner.calculate_reabsorbed_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def estimate_t_inner(self, input_t_inner, luminosity_requested, t_inner_update_exponent=-0.5): emitted_luminosity = self.calculate_emitted_luminosity() luminosity_ratios = ( (emitted_luminosity / luminosity_requested).to(1).value) return input_t_inner * luminosity_ratios ** t_inner_update_exponent def get_convergence_status(self, t_rad, w, t_inner, estimated_t_rad, estimated_w, estimated_t_inner): convergence_section = self.tardis_config.montecarlo.convergence_strategy no_of_shells = self.tardis_config.structure.no_of_shells convergence_t_rad = (abs(t_rad - estimated_t_rad) / estimated_t_rad).value convergence_w = (abs(w - estimated_w) / estimated_w) convergence_t_inner = (abs(t_inner - estimated_t_inner) / estimated_t_inner).value if convergence_section.type == 'specific': fraction_t_rad_converged = ( np.count_nonzero( convergence_t_rad < convergence_section.t_rad.threshold) / no_of_shells) t_rad_converged = ( fraction_t_rad_converged > convergence_section.t_rad.threshold) fraction_w_converged = ( np.count_nonzero( convergence_w < convergence_section.w.threshold) / no_of_shells) w_converged = ( fraction_w_converged > convergence_section.w.threshold) t_inner_converged = ( convergence_t_inner < convergence_section.t_inner.threshold) if np.all([t_rad_converged, w_converged, t_inner_converged]): return True else: return False else: return False def log_run_results(self, emitted_luminosity, absorbed_luminosity): logger.info("Luminosity emitted = {0:.5e} " "Luminosity absorbed = {1:.5e} " "Luminosity requested = {2:.5e}".format( emitted_luminosity, absorbed_luminosity, self.tardis_config.supernova.luminosity_requested)) def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, next_t_inner, log_sampling=5): """ Logging the change of the plasma state Parameters ---------- t_rad: ~astropy.units.Quanity current t_rad w: ~astropy.units.Quanity current w next_t_rad: ~astropy.units.Quanity next t_rad next_w: ~astropy.units.Quanity next_w log_sampling: ~int the n-th shells to be plotted Returns ------- """ plasma_state_log = pd.DataFrame(index=np.arange(len(t_rad)), columns=['t_rad', 'next_t_rad', 'w', 'next_w']) plasma_state_log['t_rad'] = t_rad plasma_state_log['next_t_rad'] = next_t_rad plasma_state_log['w'] = w plasma_state_log['next_w'] = next_w plasma_state_log.index.name = 'Shell' plasma_state_log = str(plasma_state_log[::log_sampling]) plasma_state_log = ''.join(['\t%s\n' % item for item in plasma_state_log.split('\n')]) logger.info('Plasma stratification:\n%s\n', plasma_state_log) logger.info('t_inner {0:.3f} -- next t_inner {1:.3f}'.format( t_inner, next_t_inner)) @staticmethod def damped_converge(value, estimated_value, damping_factor): return value + damping_factor * (estimated_value - value) def calculate_next_plasma_state(self, t_rad, w, t_inner, estimated_w, estimated_t_rad, estimated_t_inner): convergence_strategy = ( self.tardis_config.montecarlo.convergence_strategy) if (convergence_strategy.type == 'damped' or convergence_strategy.type == 'specific'): next_t_rad = self.damped_converge( t_rad, estimated_t_rad, convergence_strategy.t_rad.damping_constant) next_w = self.damped_converge( w, estimated_w, convergence_strategy.w.damping_constant) next_t_inner = self.damped_converge( t_inner, estimated_t_inner, convergence_strategy.t_inner.damping_constant) return next_t_rad, next_w, next_t_inner else: raise ValueError('Convergence strategy type is ' 'neither damped nor specific ' '- input is {0}'.format(convergence_strategy.type)) def legacy_run_simulation(self, model, hdf_path_or_buf=None, hdf_mode='full', hdf_last_only=True): """ Parameters ---------- model : tardis.model.Radial1DModel hdf_path_or_buf : str, optional A path to store the data of each simulation iteration (the default value is None, which means that nothing will be stored). hdf_mode : {'full', 'input'}, optional If 'full' all plasma properties will be stored to HDF, if 'input' only input plasma properties will be stored. hdf_last_only: bool, optional If True, only the last iteration of the simulation will be stored to the HDFStore. Returns ------- """ if hdf_path_or_buf is not None: if hdf_mode == 'full': plasma_properties = None elif hdf_mode == 'input': plasma_properties = [Input] else: raise ValueError('hdf_mode must be "full" or "input"' ', not "{}"'.format(type(hdf_mode))) start_time = time.time() self.iterations_remaining = self.tardis_config.montecarlo.iterations self.iterations_max_requested = self.tardis_config.montecarlo.iterations self.iterations_executed = 0 converged = False convergence_section = ( self.tardis_config.montecarlo.convergence_strategy) while self.iterations_remaining > 1: logger.info('Remaining run %d', self.iterations_remaining) self.run_single_montecarlo( model, self.tardis_config.montecarlo.no_of_packets) self.log_run_results(self.calculate_emitted_luminosity(), self.calculate_reabsorbed_luminosity()) self.iterations_executed += 1 self.iterations_remaining -= 1 estimated_t_rad, estimated_w = ( self.runner.calculate_radiationfield_properties()) estimated_t_inner = self.estimate_t_inner( model.t_inner, self.tardis_config.supernova.luminosity_requested) converged = self.get_convergence_status( model.t_rads, model.ws, model.t_inner, estimated_t_rad, estimated_w, estimated_t_inner) next_t_rad, next_w, next_t_inner = self.calculate_next_plasma_state( model.t_rads, model.ws, model.t_inner, estimated_w, estimated_t_rad, estimated_t_inner) self.log_plasma_state(model.t_rads, model.ws, model.t_inner, next_t_rad, next_w, next_t_inner) model.t_rads = next_t_rad model.ws = next_w model.t_inner = next_t_inner model.j_blue_estimators = self.runner.j_blue_estimator model.calculate_j_blues(init_detailed_j_blues=False) model.update_plasmas(initialize_nlte=False) if hdf_path_or_buf is not None and not hdf_last_only: self.to_hdf(model, hdf_path_or_buf, 'simulation{}'.format(self.iterations_executed), plasma_properties) # if switching into the hold iterations mode or out back to the normal one # if it is in either of these modes already it will just stay there if converged and not self.converged: self.converged = True # UMN - used to be 'hold_iterations_wrong' but this is # currently not in the convergence_section namespace... self.iterations_remaining = ( convergence_section["hold_iterations"]) elif not converged and self.converged: # UMN Warning: the following two iterations attributes of the Simulation object don't exist self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False else: # either it is converged and the status of the simulation is # converged OR it is not converged and the status of the # simulation is not converged - Do nothing. pass if converged: self.iterations_remaining = ( convergence_section["hold_iterations"]) #Finished second to last loop running one more time logger.info('Doing last run') if self.tardis_config.montecarlo.last_no_of_packets is not None: no_of_packets = self.tardis_config.montecarlo.last_no_of_packets else: no_of_packets = self.tardis_config.montecarlo.no_of_packets no_of_virtual_packets = ( self.tardis_config.montecarlo.no_of_virtual_packets) self.run_single_montecarlo(model, no_of_packets, no_of_virtual_packets, last_run=True) self.runner.legacy_update_spectrum(no_of_virtual_packets) self.legacy_set_final_model_properties(model) model.Edotlu_estimators = self.runner.Edotlu_estimator #the following instructions, passing down information to the model are #required for the gui model.no_of_packets = no_of_packets model.no_of_virtual_packets = no_of_virtual_packets model.converged = converged model.iterations_executed = self.iterations_executed model.iterations_max_requested = self.iterations_max_requested logger.info("Finished in {0:d} iterations and took {1:.2f} s".format( self.iterations_executed, time.time()-start_time)) if hdf_path_or_buf is not None: if hdf_last_only: name = 'simulation' else: name = 'simulation{}'.format(self.iterations_executed) self.to_hdf(model, hdf_path_or_buf, name, plasma_properties) def legacy_set_final_model_properties(self, model): """Sets additional model properties to be compatible with old model design The runner object is given to the model and other packet diagnostics are set. Parameters ---------- model: ~tardis.model.Radial1DModel Returns ------- : None """ #pass the runner to the model model.runner = self.runner #TODO: pass packet diagnostic arrays (montecarlo_nu, montecarlo_energies, model.j_estimators, model.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, model.last_interaction_type, model.last_line_interaction_shell_id) = model.runner.legacy_return() model.montecarlo_nu = self.runner.output_nu model.montecarlo_luminosity = self.runner.packet_luminosity model.last_line_interaction_in_id = model.atom_data.lines_index.index.values[last_line_interaction_in_id] model.last_line_interaction_in_id = model.last_line_interaction_in_id[last_line_interaction_in_id != -1] model.last_line_interaction_out_id = model.atom_data.lines_index.index.values[last_line_interaction_out_id] model.last_line_interaction_out_id = model.last_line_interaction_out_id[last_line_interaction_out_id != -1] model.last_line_interaction_angstrom = model.montecarlo_nu[last_line_interaction_in_id != -1].to('angstrom', u.spectral()) # required for gui model.current_no_of_packets = model.tardis_config.montecarlo.no_of_packets def to_hdf(self, model, path_or_buf, path='', plasma_properties=None): """ Store the simulation to an HDF structure. Parameters ---------- model : tardis.model.Radial1DModel path_or_buf Path or buffer to the HDF store path : str Path inside the HDF store to store the simulation plasma_properties `None` or a `PlasmaPropertyCollection` which will be passed as the collection argument to the plasma.to_hdf method. Returns ------- None """ self.runner.to_hdf(path_or_buf, path) model.to_hdf(path_or_buf, path, plasma_properties)
class Simulation(object): converged = False def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner(self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency) 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) def run_single_montecarlo(self, model, no_of_packets, no_of_virtual_packets=0): """ Will do a single TARDIS iteration with the given model Parameters ---------- model: ~tardis.model.Radial1DModel no_of_packet: ~int no_of_virtual_packets: ~int default is 0 and switches of the virtual packet mode. Recommended is 3. Returns ------- : None """ self.runner.run(model, no_of_packets, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads) (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.") def calculate_emitted_luminosity(self): """ Returns ------- """ return self.runner.calculate_emitted_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def calculate_reabsorbed_luminosity(self): return self.runner.calculate_reabsorbed_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def estimate_t_inner(self, input_t_inner, luminosity_requested, t_inner_update_exponent=-0.5): emitted_luminosity = self.calculate_emitted_luminosity() luminosity_ratios = ( (emitted_luminosity / luminosity_requested).to(1).value) return input_t_inner * luminosity_ratios ** t_inner_update_exponent def get_convergence_status(self, t_rad, w, t_inner, estimated_t_rad, estimated_w, estimated_t_inner): convergence_section = self.tardis_config.montecarlo.convergence_strategy no_of_shells = self.tardis_config.structure.no_of_shells convergence_t_rad = (abs(t_rad - estimated_t_rad) / estimated_t_rad).value convergence_w = (abs(w - estimated_w) / estimated_w) convergence_t_inner = (abs(t_inner - estimated_t_inner) / estimated_t_inner).value if convergence_section.type == 'specific': fraction_t_rad_converged = ( np.count_nonzero( convergence_t_rad < convergence_section.t_rad.threshold) / no_of_shells) t_rad_converged = ( fraction_t_rad_converged > convergence_section.t_rad.threshold) fraction_w_converged = ( np.count_nonzero( convergence_w < convergence_section.w.threshold) / no_of_shells) w_converged = ( fraction_w_converged > convergence_section.w.threshold) t_inner_converged = ( convergence_t_inner < convergence_section.t_inner.threshold) if np.all([t_rad_converged, w_converged, t_inner_converged]): return True else: return False else: return False def log_run_results(self, emitted_luminosity, absorbed_luminosity): logger.info("Luminosity emitted = {0:.5e} " "Luminosity absorbed = {1:.5e} " "Luminosity requested = {2:.5e}".format( emitted_luminosity, absorbed_luminosity, self.tardis_config.supernova.luminosity_requested)) def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, next_t_inner, log_sampling=5): """ Logging the change of the plasma state Parameters ---------- t_rad: ~astropy.units.Quanity current t_rad w: ~astropy.units.Quanity current w next_t_rad: ~astropy.units.Quanity next t_rad next_w: ~astropy.units.Quanity next_w log_sampling: ~int the n-th shells to be plotted Returns ------- """ plasma_state_log = pd.DataFrame(index=np.arange(len(t_rad)), columns=['t_rad', 'next_t_rad', 'w', 'next_w']) plasma_state_log['t_rad'] = t_rad plasma_state_log['next_t_rad'] = next_t_rad plasma_state_log['w'] = w plasma_state_log['next_w'] = next_w plasma_state_log.index.name = 'Shell' plasma_state_log = str(plasma_state_log[::log_sampling]) plasma_state_log = ''.join(['\t%s\n' % item for item in plasma_state_log.split('\n')]) logger.info('Plasma stratification:\n%s\n', plasma_state_log) logger.info('t_inner {0:.3f} -- next t_inner {1:.3f}'.format( t_inner, next_t_inner)) @staticmethod def damped_converge(value, estimated_value, damping_factor): return value + damping_factor * (estimated_value - value) def calculate_next_plasma_state(self, t_rad, w, t_inner, estimated_w, estimated_t_rad, estimated_t_inner): convergence_strategy = ( self.tardis_config.montecarlo.convergence_strategy) if (convergence_strategy.type == 'damped' or convergence_strategy.type == 'specific'): next_t_rad = self.damped_converge( t_rad, estimated_t_rad, convergence_strategy.t_rad.damping_constant) next_w = self.damped_converge( w, estimated_w, convergence_strategy.w.damping_constant) next_t_inner = self.damped_converge( t_inner, estimated_t_inner, convergence_strategy.t_inner.damping_constant) return next_t_rad, next_w, next_t_inner else: raise ValueError('Convergence strategy type is ' 'neither damped nor specific ' '- input is {0}'.format(convergence_strategy.type)) def legacy_run_simulation(self, model): start_time = time.time() iterations_remaining = self.tardis_config.montecarlo.iterations iterations_max_requested = self.tardis_config.montecarlo.iterations iterations_executed = 0 converged = False while iterations_remaining > 1: logger.info('Remaining run %d', iterations_remaining) self.run_single_montecarlo( model, self.tardis_config.montecarlo.no_of_packets) self.log_run_results(self.calculate_emitted_luminosity(), self.calculate_reabsorbed_luminosity()) iterations_executed += 1 iterations_remaining -= 1 estimated_t_rad, estimated_w = ( self.runner.calculate_radiationfield_properties()) estimated_t_inner = self.estimate_t_inner( model.t_inner, self.tardis_config.supernova.luminosity_requested) converged = self.get_convergence_status( model.t_rads, model.ws, model.t_inner, estimated_t_rad, estimated_w, estimated_t_inner) next_t_rad, next_w, next_t_inner = self.calculate_next_plasma_state( model.t_rads, model.ws, model.t_inner, estimated_w, estimated_t_rad, estimated_t_inner) self.log_plasma_state(model.t_rads, model.ws, model.t_inner, next_t_rad, next_w, next_t_inner) model.t_rads = next_t_rad model.ws = next_w model.t_inner = next_t_inner model.j_blue_estimators = self.runner.j_blue_estimator model.calculate_j_blues(init_detailed_j_blues=False) model.update_plasmas(initialize_nlte=False) # if switching into the hold iterations mode or out back to the normal one # if it is in either of these modes already it will just stay there if converged and not self.converged: self.converged = True iterations_remaining = ( convergence_section.global_convergence_parameters. hold_iterations_wrong) elif not converged and self.converged: # UMN Warning: the following two iterations attributes of the Simulation object don't exist self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False else: # either it is converged and the status of the simulation is # converged OR it is not converged and the status of the # simulation is not converged - Do nothing. pass if converged: convergence_section = ( self.tardis_config.montecarlo.convergence_strategy) iterations_remaining = ( convergence_section.global_convergence_parameters. hold_iterations) #Finished second to last loop running one more time logger.info('Doing last run') if self.tardis_config.montecarlo.last_no_of_packets is not None: no_of_packets = self.tardis_config.montecarlo.last_no_of_packets else: no_of_packets = self.tardis_config.montecarlo.no_of_packets no_of_virtual_packets = ( self.tardis_config.montecarlo.no_of_virtual_packets) self.run_single_montecarlo(model, no_of_packets, no_of_virtual_packets) self.legacy_update_spectrum(model, no_of_virtual_packets) self.legacy_set_final_model_properties(model) #the following instructions, passing down information to the model are #required for the gui model.no_of_packets = no_of_packets model.no_of_virtual_packets = no_of_virtual_packets model.converged = converged model.iterations_executed = iterations_executed model.iterations_max_requested = iterations_max_requested logger.info("Finished in {0:d} iterations and took {1:.2f} s".format( iterations_executed, time.time()-start_time)) def legacy_update_spectrum(self, model, no_of_virtual_packets): 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 model.spectrum.update_luminosity(montecarlo_emitted_luminosity) model.spectrum_reabsorbed.update_luminosity(montecarlo_reabsorbed_luminosity) if no_of_virtual_packets > 0: model.montecarlo_virtual_luminosity = ( self.runner.legacy_montecarlo_virtual_luminosity * 1 * u.erg / model.time_of_simulation)[:-1] model.spectrum_virtual.update_luminosity( model.montecarlo_virtual_luminosity) def legacy_set_final_model_properties(self, model): """Sets additional model properties to be compatible with old model design The runner object is given to the model and other packet diagnostics are set. Parameters ---------- model: ~tardis.model.Radial1DModel Returns ------- : None """ #pass the runner to the model model.runner = self.runner #TODO: pass packet diagnostic arrays (montecarlo_nu, montecarlo_energies, model.j_estimators, model.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, model.last_interaction_type, model.last_line_interaction_shell_id) = model.runner.legacy_return() model.montecarlo_nu = self.runner.output_nu model.montecarlo_luminosity = self.runner.packet_luminosity model.last_line_interaction_in_id = model.atom_data.lines_index.index.values[last_line_interaction_in_id] model.last_line_interaction_in_id = model.last_line_interaction_in_id[last_line_interaction_in_id != -1] model.last_line_interaction_out_id = model.atom_data.lines_index.index.values[last_line_interaction_out_id] model.last_line_interaction_out_id = model.last_line_interaction_out_id[last_line_interaction_out_id != -1] model.last_line_interaction_angstrom = model.montecarlo_nu[last_line_interaction_in_id != -1].to('angstrom', u.spectral()) # required for gui model.current_no_of_packets = model.tardis_config.montecarlo.no_of_packets
class Simulation(object): converged = False def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner(self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency) 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) def run_single_montecarlo(self, model, no_of_packets, no_of_virtual_packets=0): """ Will do a single TARDIS iteration with the given model Parameters ---------- model: ~tardis.model.Radial1DModel no_of_packet: ~int no_of_virtual_packets: ~int default is 0 and switches of the virtual packet mode. Recommended is 3. Returns ------- : None """ self.runner.run(model, no_of_packets, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads) (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.") def calculate_emitted_luminosity(self): """ Returns ------- """ return self.runner.calculate_emitted_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def calculate_reabsorbed_luminosity(self): return self.runner.calculate_reabsorbed_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def estimate_t_inner(self, input_t_inner, luminosity_requested, t_inner_update_exponent=-0.5): emitted_luminosity = self.calculate_emitted_luminosity() luminosity_ratios = ( (emitted_luminosity / luminosity_requested).to(1).value) return input_t_inner * luminosity_ratios ** t_inner_update_exponent def get_convergence_status(self, t_rad, w, t_inner, estimated_t_rad, estimated_w, estimated_t_inner): convergence_section = self.tardis_config.montecarlo.convergence_strategy no_of_shells = self.tardis_config.structure.no_of_shells convergence_t_rad = (abs(t_rad - estimated_t_rad) / estimated_t_rad).value convergence_w = (abs(w - estimated_w) / estimated_w) convergence_t_inner = (abs(t_inner - estimated_t_inner) / estimated_t_inner).value if convergence_section.type == 'specific': fraction_t_rad_converged = ( np.count_nonzero( convergence_t_rad < convergence_section.t_rad.threshold) / no_of_shells) t_rad_converged = ( fraction_t_rad_converged > convergence_section.t_rad.threshold) fraction_w_converged = ( np.count_nonzero( convergence_w < convergence_section.w.threshold) / no_of_shells) w_converged = ( fraction_w_converged > convergence_section.w.threshold) t_inner_converged = ( convergence_t_inner < convergence_section.t_inner.threshold) if np.all([t_rad_converged, w_converged, t_inner_converged]): return True else: return False else: return False def log_run_results(self, emitted_luminosity, absorbed_luminosity): logger.info("Luminosity emitted = {0:.5e} " "Luminosity absorbed = {1:.5e} " "Luminosity requested = {2:.5e}".format( emitted_luminosity, absorbed_luminosity, self.tardis_config.supernova.luminosity_requested)) def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, next_t_inner, log_sampling=5): """ Logging the change of the plasma state Parameters ---------- t_rad: ~astropy.units.Quanity current t_rad w: ~astropy.units.Quanity current w next_t_rad: ~astropy.units.Quanity next t_rad next_w: ~astropy.units.Quanity next_w log_sampling: ~int the n-th shells to be plotted Returns ------- """ plasma_state_log = pd.DataFrame(index=np.arange(len(t_rad)), columns=['t_rad', 'next_t_rad', 'w', 'next_w']) plasma_state_log['t_rad'] = t_rad plasma_state_log['next_t_rad'] = next_t_rad plasma_state_log['w'] = w plasma_state_log['next_w'] = next_w plasma_state_log.index.name = 'Shell' plasma_state_log = str(plasma_state_log[::log_sampling]) plasma_state_log = ''.join(['\t%s\n' % item for item in plasma_state_log.split('\n')]) logger.info('Plasma stratification:\n%s\n', plasma_state_log) logger.info('t_inner {0:.3f} -- next t_inner {1:.3f}'.format( t_inner, next_t_inner)) @staticmethod def damped_converge(value, estimated_value, damping_factor): return value + damping_factor * (estimated_value - value) def calculate_next_plasma_state(self, t_rad, w, t_inner, estimated_w, estimated_t_rad, estimated_t_inner): convergence_strategy = ( self.tardis_config.montecarlo.convergence_strategy) if (convergence_strategy.type == 'damped' or convergence_strategy.type == 'specific'): next_t_rad = self.damped_converge( t_rad, estimated_t_rad, convergence_strategy.t_rad.damping_constant) next_w = self.damped_converge( w, estimated_w, convergence_strategy.w.damping_constant) next_t_inner = self.damped_converge( t_inner, estimated_t_inner, convergence_strategy.t_inner.damping_constant) return next_t_rad, next_w, next_t_inner else: raise ValueError('Convergence strategy type is ' 'neither damped nor specific ' '- input is {0}'.format(convergence_strategy.type)) def legacy_run_simulation(self, model): start_time = time.time() iterations_remaining = self.tardis_config.montecarlo.iterations iterations_max_requested = self.tardis_config.montecarlo.iterations iterations_executed = 0 converged = False while iterations_remaining > 1: logger.info('Remaining run %d', iterations_remaining) self.run_single_montecarlo( model, self.tardis_config.montecarlo.no_of_packets) self.log_run_results(self.calculate_emitted_luminosity(), self.calculate_reabsorbed_luminosity()) iterations_executed += 1 iterations_remaining -= 1 estimated_t_rad, estimated_w = ( self.runner.calculate_radiationfield_properties()) estimated_t_inner = self.estimate_t_inner( model.t_inner, self.tardis_config.supernova.luminosity_requested) converged = self.get_convergence_status( model.t_rads, model.ws, model.t_inner, estimated_t_rad, estimated_w, estimated_t_inner) next_t_rad, next_w, next_t_inner = self.calculate_next_plasma_state( model.t_rads, model.ws, model.t_inner, estimated_w, estimated_t_rad, estimated_t_inner) self.log_plasma_state(model.t_rads, model.ws, model.t_inner, next_t_rad, next_w, next_t_inner) model.t_rads = next_t_rad model.ws = next_w model.t_inner = next_t_inner model.calculate_j_blues(init_detailed_j_blues=False) model.update_plasmas(initialize_nlte=False) # if switching into the hold iterations mode or out back to the normal one # if it is in either of these modes already it will just stay there if converged and not self.converged: self.converged = True iterations_remaining = ( convergence_section.global_convergence_parameters. hold_iterations_wrong) elif not converged and self.converged: # UMN Warning: the following two iterations attributes of the Simulation object don't exist self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False else: # either it is converged and the status of the simulation is # converged OR it is not converged and the status of the # simulation is not converged - Do nothing. pass if converged: convergence_section = ( self.tardis_config.montecarlo.convergence_strategy) iterations_remaining = ( convergence_section.global_convergence_parameters. hold_iterations) #Finished second to last loop running one more time logger.info('Doing last run') if self.tardis_config.montecarlo.last_no_of_packets is not None: no_of_packets = self.tardis_config.montecarlo.last_no_of_packets else: no_of_packets = self.tardis_config.montecarlo.no_of_packets no_of_virtual_packets = ( self.tardis_config.montecarlo.no_of_virtual_packets) self.run_single_montecarlo(model, no_of_packets, no_of_virtual_packets) self.legacy_update_spectrum(model, no_of_virtual_packets) self.legacy_set_final_model_properties(model) #the following instructions, passing down information to the model are #required for the gui model.no_of_packets = no_of_packets model.no_of_virtual_packets = no_of_virtual_packets model.converged = converged model.iterations_executed = iterations_executed model.iterations_max_requested = iterations_max_requested logger.info("Finished in {0:d} iterations and took {1:.2f} s".format( iterations_executed, time.time()-start_time)) def legacy_update_spectrum(self, model, no_of_virtual_packets): 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 model.spectrum.update_luminosity(montecarlo_emitted_luminosity) model.spectrum_reabsorbed.update_luminosity(montecarlo_reabsorbed_luminosity) if no_of_virtual_packets > 0: model.montecarlo_virtual_luminosity = ( self.runner.legacy_montecarlo_virtual_luminosity * 1 * u.erg / model.time_of_simulation)[:-1] model.spectrum_virtual.update_luminosity( model.montecarlo_virtual_luminosity) def legacy_set_final_model_properties(self, model): """Sets additional model properties to be compatible with old model design The runner object is given to the model and other packet diagnostics are set. Parameters ---------- model: ~tardis.model.Radial1DModel Returns ------- : None """ #pass the runner to the model model.runner = self.runner #TODO: pass packet diagnostic arrays (montecarlo_nu, montecarlo_energies, model.j_estimators, model.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, model.last_interaction_type, model.last_line_interaction_shell_id) = model.runner.legacy_return() model.montecarlo_nu = self.runner.output_nu model.montecarlo_luminosity = self.runner.packet_luminosity model.last_line_interaction_in_id = model.atom_data.lines_index.index.values[last_line_interaction_in_id] model.last_line_interaction_in_id = model.last_line_interaction_in_id[last_line_interaction_in_id != -1] model.last_line_interaction_out_id = model.atom_data.lines_index.index.values[last_line_interaction_out_id] model.last_line_interaction_out_id = model.last_line_interaction_out_id[last_line_interaction_out_id != -1] model.last_line_interaction_angstrom = model.montecarlo_nu[last_line_interaction_in_id != -1].to('angstrom', u.spectral()) # required for gui model.current_no_of_packets = model.tardis_config.montecarlo.no_of_packets
def from_config(cls, config, **kwargs): """ Create a new Simulation instance from a Configuration object. Parameters ---------- config : tardis.io.config_reader.Configuration **kwargs Allow overriding some structures, such as model, plasma, atomic data and the runner, instead of creating them from the configuration object. Returns ------- Simulation """ # Allow overriding some config structures. This is useful in some # unit tests, and could be extended in all the from_config classmethods. if 'model' in kwargs: model = kwargs['model'] else: model = Radial1DModel.from_config(config) if 'plasma' in kwargs: plasma = kwargs['plasma'] else: plasma = assemble_plasma(config, model, atom_data=kwargs.get('atom_data', None)) if 'runner' in kwargs: runner = kwargs['runner'] else: runner = MontecarloRunner.from_config(config) luminosity_nu_start = config.supernova.luminosity_wavelength_end.to( u.Hz, u.spectral()) try: luminosity_nu_end = config.supernova.luminosity_wavelength_start.to( u.Hz, u.spectral()) except ZeroDivisionError: luminosity_nu_end = np.inf * u.Hz last_no_of_packets = config.montecarlo.last_no_of_packets if last_no_of_packets is None or last_no_of_packets < 0: last_no_of_packets = config.montecarlo.no_of_packets last_no_of_packets = int(last_no_of_packets) return cls( iterations=config.montecarlo.iterations, model=model, plasma=plasma, runner=runner, no_of_packets=int(config.montecarlo.no_of_packets), no_of_virtual_packets=int(config.montecarlo.no_of_virtual_packets), luminosity_nu_start=luminosity_nu_start, luminosity_nu_end=luminosity_nu_end, last_no_of_packets=last_no_of_packets, luminosity_requested=config.supernova.luminosity_requested.cgs, convergence_strategy=config.montecarlo.convergence_strategy, nthreads=config.montecarlo.nthreads)
class Simulation(object): converged = False def __init__(self, tardis_config): self.tardis_config = tardis_config self.runner = MontecarloRunner( self.tardis_config.montecarlo.seed, tardis_config.spectrum.frequency, tardis_config.supernova.get('distance', None)) 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) def run_single_montecarlo(self, model, no_of_packets, no_of_virtual_packets=0, last_run=False): """ Will do a single TARDIS iteration with the given model Parameters ---------- model: ~tardis.model.Radial1DModel no_of_packet: ~int no_of_virtual_packets: ~int default is 0 and switches of the virtual packet mode. Recommended is 3. Returns ------- : None """ self.runner.run(model, no_of_packets, no_of_virtual_packets=no_of_virtual_packets, nthreads=self.tardis_config.montecarlo.nthreads, last_run=last_run) (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.") def calculate_emitted_luminosity(self): """ Returns ------- """ return self.runner.calculate_emitted_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def calculate_reabsorbed_luminosity(self): return self.runner.calculate_reabsorbed_luminosity( self.tardis_config.supernova.luminosity_nu_start, self.tardis_config.supernova.luminosity_nu_end) def estimate_t_inner(self, input_t_inner, luminosity_requested, t_inner_update_exponent=-0.5): emitted_luminosity = self.calculate_emitted_luminosity() luminosity_ratios = ((emitted_luminosity / luminosity_requested).to(1).value) return input_t_inner * luminosity_ratios**t_inner_update_exponent def get_convergence_status(self, t_rad, w, t_inner, estimated_t_rad, estimated_w, estimated_t_inner): convergence_section = self.tardis_config.montecarlo.convergence_strategy no_of_shells = self.tardis_config.structure.no_of_shells convergence_t_rad = (abs(t_rad - estimated_t_rad) / estimated_t_rad).value convergence_w = (abs(w - estimated_w) / estimated_w) convergence_t_inner = (abs(t_inner - estimated_t_inner) / estimated_t_inner).value if convergence_section.type == 'specific': fraction_t_rad_converged = (np.count_nonzero( convergence_t_rad < convergence_section.t_rad.threshold) / no_of_shells) t_rad_converged = (fraction_t_rad_converged > convergence_section.t_rad.threshold) fraction_w_converged = (np.count_nonzero( convergence_w < convergence_section.w.threshold) / no_of_shells) w_converged = (fraction_w_converged > convergence_section.w.threshold) t_inner_converged = (convergence_t_inner < convergence_section.t_inner.threshold) if np.all([t_rad_converged, w_converged, t_inner_converged]): return True else: return False else: return False def log_run_results(self, emitted_luminosity, absorbed_luminosity): logger.info("Luminosity emitted = {0:.5e} " "Luminosity absorbed = {1:.5e} " "Luminosity requested = {2:.5e}".format( emitted_luminosity, absorbed_luminosity, self.tardis_config.supernova.luminosity_requested)) def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, next_t_inner, log_sampling=5): """ Logging the change of the plasma state Parameters ---------- t_rad: ~astropy.units.Quanity current t_rad w: ~astropy.units.Quanity current w next_t_rad: ~astropy.units.Quanity next t_rad next_w: ~astropy.units.Quanity next_w log_sampling: ~int the n-th shells to be plotted Returns ------- """ plasma_state_log = pd.DataFrame( index=np.arange(len(t_rad)), columns=['t_rad', 'next_t_rad', 'w', 'next_w']) plasma_state_log['t_rad'] = t_rad plasma_state_log['next_t_rad'] = next_t_rad plasma_state_log['w'] = w plasma_state_log['next_w'] = next_w plasma_state_log.index.name = 'Shell' plasma_state_log = str(plasma_state_log[::log_sampling]) plasma_state_log = ''.join( ['\t%s\n' % item for item in plasma_state_log.split('\n')]) logger.info('Plasma stratification:\n%s\n', plasma_state_log) logger.info('t_inner {0:.3f} -- next t_inner {1:.3f}'.format( t_inner, next_t_inner)) @staticmethod def damped_converge(value, estimated_value, damping_factor): return value + damping_factor * (estimated_value - value) def calculate_next_plasma_state(self, t_rad, w, t_inner, estimated_w, estimated_t_rad, estimated_t_inner): convergence_strategy = ( self.tardis_config.montecarlo.convergence_strategy) if (convergence_strategy.type == 'damped' or convergence_strategy.type == 'specific'): next_t_rad = self.damped_converge( t_rad, estimated_t_rad, convergence_strategy.t_rad.damping_constant) next_w = self.damped_converge( w, estimated_w, convergence_strategy.w.damping_constant) next_t_inner = self.damped_converge( t_inner, estimated_t_inner, convergence_strategy.t_inner.damping_constant) return next_t_rad, next_w, next_t_inner else: raise ValueError('Convergence strategy type is ' 'neither damped nor specific ' '- input is {0}'.format( convergence_strategy.type)) def legacy_run_simulation(self, model, hdf_path_or_buf=None, hdf_mode='full', hdf_last_only=True): """ Parameters ---------- model : tardis.model.Radial1DModel hdf_path_or_buf : str, optional A path to store the data of each simulation iteration (the default value is None, which means that nothing will be stored). hdf_mode : {'full', 'input'}, optional If 'full' all plasma properties will be stored to HDF, if 'input' only input plasma properties will be stored. hdf_last_only: bool, optional If True, only the last iteration of the simulation will be stored to the HDFStore. Returns ------- """ if hdf_path_or_buf is not None: if hdf_mode == 'full': plasma_properties = None elif hdf_mode == 'input': plasma_properties = [Input] else: raise ValueError('hdf_mode must be "full" or "input"' ', not "{}"'.format(type(hdf_mode))) start_time = time.time() self.iterations_remaining = self.tardis_config.montecarlo.iterations self.iterations_max_requested = self.tardis_config.montecarlo.iterations self.iterations_executed = 0 converged = False convergence_section = ( self.tardis_config.montecarlo.convergence_strategy) while self.iterations_remaining > 1: logger.info('Remaining run %d', self.iterations_remaining) self.run_single_montecarlo( model, self.tardis_config.montecarlo.no_of_packets) self.log_run_results(self.calculate_emitted_luminosity(), self.calculate_reabsorbed_luminosity()) self.iterations_executed += 1 self.iterations_remaining -= 1 estimated_t_rad, estimated_w = ( self.runner.calculate_radiationfield_properties()) estimated_t_inner = self.estimate_t_inner( model.t_inner, self.tardis_config.supernova.luminosity_requested) converged = self.get_convergence_status(model.t_rads, model.ws, model.t_inner, estimated_t_rad, estimated_w, estimated_t_inner) next_t_rad, next_w, next_t_inner = self.calculate_next_plasma_state( model.t_rads, model.ws, model.t_inner, estimated_w, estimated_t_rad, estimated_t_inner) self.log_plasma_state(model.t_rads, model.ws, model.t_inner, next_t_rad, next_w, next_t_inner) model.t_rads = next_t_rad model.ws = next_w model.t_inner = next_t_inner model.j_blue_estimators = self.runner.j_blue_estimator model.calculate_j_blues(init_detailed_j_blues=False) model.update_plasmas(initialize_nlte=False) if hdf_path_or_buf is not None and not hdf_last_only: self.to_hdf(model, hdf_path_or_buf, 'simulation{}'.format(self.iterations_executed), plasma_properties) # if switching into the hold iterations mode or out back to the normal one # if it is in either of these modes already it will just stay there if converged and not self.converged: self.converged = True # UMN - used to be 'hold_iterations_wrong' but this is # currently not in the convergence_section namespace... self.iterations_remaining = ( convergence_section["hold_iterations"]) elif not converged and self.converged: # UMN Warning: the following two iterations attributes of the Simulation object don't exist self.iterations_remaining = self.iterations_max_requested - self.iterations_executed self.converged = False else: # either it is converged and the status of the simulation is # converged OR it is not converged and the status of the # simulation is not converged - Do nothing. pass if converged: self.iterations_remaining = ( convergence_section["hold_iterations"]) #Finished second to last loop running one more time logger.info('Doing last run') if self.tardis_config.montecarlo.last_no_of_packets is not None: no_of_packets = self.tardis_config.montecarlo.last_no_of_packets else: no_of_packets = self.tardis_config.montecarlo.no_of_packets no_of_virtual_packets = ( self.tardis_config.montecarlo.no_of_virtual_packets) self.run_single_montecarlo(model, no_of_packets, no_of_virtual_packets, last_run=True) self.runner.legacy_update_spectrum(no_of_virtual_packets) self.legacy_set_final_model_properties(model) model.Edotlu_estimators = self.runner.Edotlu_estimator #the following instructions, passing down information to the model are #required for the gui model.no_of_packets = no_of_packets model.no_of_virtual_packets = no_of_virtual_packets model.converged = converged model.iterations_executed = self.iterations_executed model.iterations_max_requested = self.iterations_max_requested logger.info("Finished in {0:d} iterations and took {1:.2f} s".format( self.iterations_executed, time.time() - start_time)) if hdf_path_or_buf is not None: if hdf_last_only: name = 'simulation' else: name = 'simulation{}'.format(self.iterations_executed) self.to_hdf(model, hdf_path_or_buf, name, plasma_properties) def legacy_set_final_model_properties(self, model): """Sets additional model properties to be compatible with old model design The runner object is given to the model and other packet diagnostics are set. Parameters ---------- model: ~tardis.model.Radial1DModel Returns ------- : None """ #pass the runner to the model model.runner = self.runner #TODO: pass packet diagnostic arrays (montecarlo_nu, montecarlo_energies, model.j_estimators, model.nubar_estimators, last_line_interaction_in_id, last_line_interaction_out_id, model.last_interaction_type, model.last_line_interaction_shell_id) = model.runner.legacy_return() model.montecarlo_nu = self.runner.output_nu model.montecarlo_luminosity = self.runner.packet_luminosity model.last_line_interaction_in_id = model.atom_data.lines_index.index.values[ last_line_interaction_in_id] model.last_line_interaction_in_id = model.last_line_interaction_in_id[ last_line_interaction_in_id != -1] model.last_line_interaction_out_id = model.atom_data.lines_index.index.values[ last_line_interaction_out_id] model.last_line_interaction_out_id = model.last_line_interaction_out_id[ last_line_interaction_out_id != -1] model.last_line_interaction_angstrom = model.montecarlo_nu[ last_line_interaction_in_id != -1].to('angstrom', u.spectral()) # required for gui model.current_no_of_packets = model.tardis_config.montecarlo.no_of_packets def to_hdf(self, model, path_or_buf, path='', plasma_properties=None): """ Store the simulation to an HDF structure. Parameters ---------- model : tardis.model.Radial1DModel path_or_buf Path or buffer to the HDF store path : str Path inside the HDF store to store the simulation plasma_properties `None` or a `PlasmaPropertyCollection` which will be passed as the collection argument to the plasma.to_hdf method. Returns ------- None """ self.runner.to_hdf(path_or_buf, path) model.to_hdf(path_or_buf, path, plasma_properties)