Example #1
0
 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)
Example #2
0
    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()
Example #3
0
    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,
        )
Example #4
0
 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)
Example #5
0
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
Example #6
0
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
Example #7
0
    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()
Example #8
0
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
Example #9
0
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)
Example #10
0
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
Example #11
0
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
Example #12
0
    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)
Example #13
0
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)