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