def test_get_last_line_counts(self, line_info_widget, allowed_species, filter_mode, group_mode): """ Test for get_last_line_counts() method. Since this method depends on get_species_interactions() so we need to make sure that we select only allowed species i.e. present within the wavelength range selected by the get_species_interactions(), which is being done here by allowed_species fixture. """ if allowed_species is None: last_line_counts_df = line_info_widget.get_last_line_counts( None, filter_mode, group_mode) # Dataframe contains all falsy values (proxy for empty) assert last_line_counts_df.all(axis=None) == False return for selected_species in allowed_species: last_line_counts_df = line_info_widget.get_last_line_counts( selected_species, filter_mode, group_mode) last_lines_in = ( line_info_widget.line_interaction_analysis[filter_mode]. last_line_in.xs( key=species_string_to_tuple(selected_species), level=["atomic_number", "ion_number"], ).reset_index()) last_lines_out = ( line_info_widget.line_interaction_analysis[filter_mode]. last_line_out.xs( key=species_string_to_tuple(selected_species), level=["atomic_number", "ion_number"], ).reset_index()) if group_mode == "exc": expected_df_length = last_lines_in.groupby("line_id").ngroups elif group_mode == "de-exc": expected_df_length = last_lines_out.groupby("line_id").ngroups elif group_mode == "both": expected_df_length = last_lines_in.groupby( ["line_id", last_lines_out["line_id"]]).ngroups # Test shape of the dataframe assert last_line_counts_df.shape == (expected_df_length, 1)
def test_species_string_to_tuple(species_string, species_tuple): assert species_string_to_tuple(species_string) == species_tuple with pytest.raises(MalformedSpeciesError): species_string_to_tuple('II') with pytest.raises(MalformedSpeciesError): species_string_to_tuple('He Si') with pytest.raises(ValueError): species_string_to_tuple('He IX')
def assemble_plasma(config, model, atom_data=None): """ Create a BasePlasma instance from a Configuration object and a Radial1DModel. Parameters ---------- config : io.config_reader.Configuration model : model.Radial1DModel atom_data : atomic.AtomData If None, an attempt will be made to read the atomic data from config. Returns ------- : plasma.BasePlasma """ # Convert the nlte species list to a proper format. nlte_species = [ species_string_to_tuple(s) for s in config.plasma.nlte.species ] # Convert the continuum interaction species list to a proper format. continuum_interaction_species = [ species_string_to_tuple(s) for s in config.plasma.continuum_interaction.species ] continuum_interaction_species = pd.MultiIndex.from_tuples( continuum_interaction_species, names=["atomic_number", "ion_number"]) if atom_data is None: if "atom_data" in config: if os.path.isabs(config.atom_data): atom_data_fname = config.atom_data else: atom_data_fname = os.path.join(config.config_dirname, config.atom_data) else: raise ValueError("No atom_data option found in the configuration.") logger.info("Reading Atomic Data from %s", atom_data_fname) try: atom_data = AtomData.from_hdf(atom_data_fname) except TypeError as e: print( e, "Error might be from the use of an old-format of the atomic database, \n" "please see https://github.com/tardis-sn/tardis-refdata/tree/master/atom_data" " for the most recent version.", ) raise atom_data.prepare_atom_data( model.abundance.index, line_interaction_type=config.plasma.line_interaction_type, nlte_species=nlte_species, ) # Check if continuum interaction species are in selected_atoms continuum_atoms = continuum_interaction_species.get_level_values( "atomic_number") continuum_atoms_in_selected_atoms = np.all( continuum_atoms.isin(atom_data.selected_atomic_numbers)) if not continuum_atoms_in_selected_atoms: raise PlasmaConfigError("Not all continuum interaction species " "belong to atoms that have been specified " "in the configuration.") kwargs = dict( t_rad=model.t_radiative, abundance=model.abundance, density=model.density, atomic_data=atom_data, time_explosion=model.time_explosion, w=model.dilution_factor, link_t_rad_t_electron=0.9, continuum_interaction_species=continuum_interaction_species, ) plasma_modules = basic_inputs + basic_properties property_kwargs = {} if config.plasma.continuum_interaction.species: line_interaction_type = config.plasma.line_interaction_type if line_interaction_type != "macroatom": raise PlasmaConfigError( "Continuum interactions require line_interaction_type " "macroatom (instead of {}).".format(line_interaction_type)) plasma_modules += continuum_interaction_properties plasma_modules += continuum_interaction_inputs if config.plasma.continuum_interaction.enable_adiabatic_cooling: plasma_modules += adiabatic_cooling_properties if config.plasma.continuum_interaction.enable_two_photon_decay: plasma_modules += two_photon_properties transition_probabilities_outputs = [ plasma_property.transition_probabilities_outputs for plasma_property in plasma_modules if issubclass(plasma_property, TransitionProbabilitiesProperty) ] transition_probabilities_outputs = [ item for sublist in transition_probabilities_outputs for item in sublist ] property_kwargs[MarkovChainTransProbsCollector] = { "inputs": transition_probabilities_outputs } kwargs.update( gamma_estimator=None, bf_heating_coeff_estimator=None, alpha_stim_estimator=None, volume=model.volume, r_inner=model.r_inner, t_inner=model.t_inner, ) if config.plasma.radiative_rates_type == "blackbody": plasma_modules.append(JBluesBlackBody) elif config.plasma.radiative_rates_type == "dilute-blackbody": plasma_modules.append(JBluesDiluteBlackBody) elif config.plasma.radiative_rates_type == "detailed": plasma_modules += detailed_j_blues_properties + detailed_j_blues_inputs kwargs.update( r_inner=model.r_inner, t_inner=model.t_inner, volume=model.volume, j_blue_estimator=None, ) property_kwargs[JBluesDetailed] = { "w_epsilon": config.plasma.w_epsilon } else: raise ValueError( f"radiative_rates_type type unknown - {config.plasma.radiative_rates_type}" ) if config.plasma.excitation == "lte": plasma_modules += lte_excitation_properties elif config.plasma.excitation == "dilute-lte": plasma_modules += dilute_lte_excitation_properties if config.plasma.ionization == "lte": plasma_modules += lte_ionization_properties elif config.plasma.ionization == "nebular": plasma_modules += nebular_ionization_properties if nlte_species: plasma_modules += nlte_properties nlte_conf = config.plasma.nlte plasma_modules.append(LevelBoltzmannFactorNLTE.from_config(nlte_conf)) property_kwargs[StimulatedEmissionFactor] = dict( nlte_species=nlte_species) else: plasma_modules += non_nlte_properties if config.plasma.line_interaction_type in ("downbranch", "macroatom"): plasma_modules += macro_atom_properties if "delta_treatment" in config.plasma: property_kwargs[RadiationFieldCorrection] = dict( delta_treatment=config.plasma.delta_treatment) if config.plasma.helium_treatment == "recomb-nlte": plasma_modules += helium_nlte_properties elif config.plasma.helium_treatment == "numerical-nlte": plasma_modules += helium_numerical_nlte_properties # TODO: See issue #633 if config.plasma.heating_rate_data_file in ["none", None]: raise PlasmaConfigError("Heating rate data file not specified") else: property_kwargs[HeliumNumericalNLTE] = dict( heating_rate_data_file=config.plasma.heating_rate_data_file) else: plasma_modules += helium_lte_properties if model._electron_densities: electron_densities = pd.Series(model._electron_densities.cgs.value) if config.plasma.helium_treatment == "numerical-nlte": property_kwargs[IonNumberDensityHeNLTE] = dict( electron_densities=electron_densities) else: property_kwargs[IonNumberDensity] = dict( electron_densities=electron_densities) kwargs["helium_treatment"] = config.plasma.helium_treatment plasma = BasePlasma( plasma_properties=plasma_modules, property_kwargs=property_kwargs, **kwargs, ) return plasma
def assemble_plasma(config, model, atom_data=None): """ Create a BasePlasma instance from a Configuration object and a Radial1DModel. Parameters ---------- config: ~io.config_reader.Configuration model: ~model.Radial1DModel atom_data: ~atomic.AtomData If None, an attempt will be made to read the atomic data from config. Returns ------- : ~plasma.BasePlasma """ # Convert the nlte species list to a proper format. nlte_species = [species_string_to_tuple(s) for s in config.plasma.nlte.species] if atom_data is None: if 'atom_data' in config: if os.path.isabs(config.atom_data): atom_data_fname = config.atom_data else: atom_data_fname = os.path.join(config.config_dirname, config.atom_data) else: raise ValueError('No atom_data option found in the configuration.') logger.info('Reading Atomic Data from %s', atom_data_fname) try: atom_data = atomic.AtomData.from_hdf(atom_data_fname) except TypeError as e: print (e, 'Error might be from the use of an old-format of the atomic database, \n' 'please see https://github.com/tardis-sn/tardis-refdata/tree/master/atom_data' ',for the most recent version.') raise atom_data.prepare_atom_data( model.abundance.index, line_interaction_type=config.plasma.line_interaction_type, nlte_species=nlte_species) kwargs = dict(t_rad=model.t_radiative, abundance=model.abundance, density=model.density, atomic_data=atom_data, time_explosion=model.time_explosion, w=model.dilution_factor, link_t_rad_t_electron=0.9) plasma_modules = basic_inputs + basic_properties property_kwargs = {} if config.plasma.radiative_rates_type == 'blackbody': plasma_modules.append(JBluesBlackBody) elif config.plasma.radiative_rates_type == 'dilute-blackbody': plasma_modules.append(JBluesDiluteBlackBody) elif config.plasma.radiative_rates_type == 'detailed': plasma_modules += detailed_j_blues_properties + detailed_j_blues_inputs kwargs.update(r_inner=model.r_inner, t_inner=model.t_inner, volume=model.volume, j_blue_estimator=None) property_kwargs[JBluesDetailed] = {'w_epsilon': config.plasma.w_epsilon} else: raise ValueError('radiative_rates_type type unknown - %s', config.plasma.radiative_rates_type) if config.plasma.excitation == 'lte': plasma_modules += lte_excitation_properties elif config.plasma.excitation == 'dilute-lte': plasma_modules += dilute_lte_excitation_properties if config.plasma.ionization == 'lte': plasma_modules += lte_ionization_properties elif config.plasma.ionization == 'nebular': plasma_modules += nebular_ionization_properties if nlte_species: plasma_modules += nlte_properties nlte_conf = config.plasma.nlte plasma_modules.append( LevelBoltzmannFactorNLTE.from_config(nlte_conf) ) property_kwargs[StimulatedEmissionFactor] = dict( nlte_species=nlte_species) else: plasma_modules += non_nlte_properties if config.plasma.line_interaction_type in ('downbranch', 'macroatom'): plasma_modules += macro_atom_properties if 'delta_treatment' in config.plasma: property_kwargs[RadiationFieldCorrection] = dict( delta_treatment=config.plasma.delta_treatment) if config.plasma.helium_treatment == 'recomb-nlte': plasma_modules += helium_nlte_properties elif config.plasma.helium_treatment == 'numerical-nlte': plasma_modules += helium_numerical_nlte_properties # TODO: See issue #633 if config.plasma.heating_rate_data_file in ['none', None]: raise PlasmaConfigError('Heating rate data file not specified') else: property_kwargs[HeliumNumericalNLTE] = dict( heating_rate_data_file=config.plasma.heating_rate_data_file) else: plasma_modules += helium_lte_properties if model._electron_densities: electron_densities = pd.Series(model._electron_densities.cgs.value) if config.plasma.helium_treatment == 'numerical-nlte': property_kwargs[IonNumberDensityHeNLTE] = dict( electron_densities=electron_densities) else: property_kwargs[IonNumberDensity] = dict( electron_densities=electron_densities) kwargs['helium_treatment'] = config.plasma.helium_treatment plasma = BasePlasma(plasma_properties=plasma_modules, property_kwargs=property_kwargs, **kwargs) return plasma
def generate_plot( self, ax=None, cmap=cm.jet, bins=None, xlim=None, ylim=None, nelements=None, twinx=False, species_list=None, ): """Generate the actual "Kromer" plot Parameters ---------- ax : matplotlib.axes or None axes object into which the emission part of the Kromer plot should be plotted; if None, a new one is generated (default None) cmap : matplotlib.cm.ListedColormap or None color map object used for the illustration of the different atomic contributions (default matplotlib.cm.jet) bins : np.ndarray or None array of the wavelength bins used for the illustration of the atomic contributions; if None, the same binning as for the stored virtual spectrum is used (default None) xlim : tuple or array-like or None wavelength limits for the display; if None, the x-axis is automatically scaled (default None) ylim : tuple or array-like or None flux limits for the display; if None, the y-axis is automatically scaled (default None) nelements: int or None number of elements that should be included in the Kromer plots. The top nelements are determined based on those with the most packet interactions twinx : boolean determines where the absorption part of the Kromer plot is placed, if True, the absorption part is attached at the top of the main axes box, otherwise it is placed below the emission part (default False) species_list: list of strings or None list of strings containing the names of species that should be included in the Kromer plots, e.g. ['Si II', 'Ca II'] Returns ------- fig : matplotlib.figure figure instance containing the plot """ self._ax = None self._pax = None self._cmap = cmap self._ax = ax self._ylim = ylim self._twinx = twinx # the species list can contain either a specific element, a specific # ion, a range of ions, or any combination of these if the list # contains a range of ions, separate each one into a new entry in the # species list full_species_list = [] if species_list is not None: for species in species_list: # check if a hyphen is present. If it is, then it indicates a # range of ions. Add each ion in that range to the list if "-" in species: element = species.split(" ")[0] first_ion_numeral = roman_to_int( species.split(" ")[-1].split("-")[0]) second_ion_numeral = roman_to_int( species.split(" ")[-1].split("-")[-1]) for i in np.arange(first_ion_numeral, second_ion_numeral + 1): full_species_list.append(element + " " + int_to_roman(i)) else: full_species_list.append(species) self._species_list = full_species_list else: self._species_list = None if xlim is None: self._xlim = [ np.min(self.mdl.spectrum_wave).value, np.max(self.mdl.spectrum_wave).value, ] else: self._xlim = xlim if bins is None: self._bins = self.mdl.spectrum_wave[::-1] else: self._bins = bins # get the elements/species to be included in the plot self._elements_in_kromer_plot = self.line_info # if no nelements and no species list is specified, then the number of # elements to be included in the colourbar is determined from the list # of unique elements that appear in the model if nelements is None and species_list is None: self._nelements = len( np.unique(self.line_in_and_out_infos_within_xlims. atomic_number.values)) elif nelements is None and species_list is not None: # if species_list has been specified, then the number of elements # to be included is set to the length of that list self._nelements = len(self._species_list) else: # if nelements has been specified, then the number of elements to # be included is set to the length of that list self._nelements = nelements # if the length of self._elements_in_kromer_plot exceeds the requested # number of elements to be included in the colourbar, then this if # statement applies if self._species_list is not None: # if we have specified a species list then only take those species # that are requested mask = np.in1d(self._elements_in_kromer_plot[:, 0], self.requested_species_ids) self._elements_in_kromer_plot = self._elements_in_kromer_plot[mask] elif len(self._elements_in_kromer_plot) > self._nelements: # if nelements is specified, then sort to find the top contributing # elements, pick the top nelements, and sort back by atomic number self._elements_in_kromer_plot = self._elements_in_kromer_plot[ np.argsort(self._elements_in_kromer_plot[:, 1])[::-1]] self._elements_in_kromer_plot = self._elements_in_kromer_plot[:self . _nelements] self._elements_in_kromer_plot = self._elements_in_kromer_plot[ np.argsort(self._elements_in_kromer_plot[:, 0])] else: # if the length of self._elements_in_kromer_plot is less than the # requested number of elements in the model, then this requested # length is updated to be the length of length of # self._elements_in_kromer_plot self._nelements = len(self._elements_in_kromer_plot) # this will reset nelements if species_list is turned on # it's possible to request a species that doesn't appear in the plot # this will ensure that species isn't counted when determining labels # and colours if self._species_list is not None: labels = [] for species in self._species_list: if " " in species: atomic_number = species_string_to_tuple(species)[0] ion_number = species_string_to_tuple(species)[1] species_id = atomic_number * 100 + ion_number if species_id in self._elements_in_kromer_plot: labels.append(species) else: labels.append(species) self._nelements = len(labels) self._axes_handling_preparation() self._generate_emission_part() self._generate_photosphere_part() self._generate_and_add_colormap() self._generate_and_add_legend() self._paxes_handling_preparation() self._generate_absorption_part() self._axis_handling_label_rescale() return plt.gcf()
def line_info(self): """produces list of elements to be included in the kromer plot""" # gets list of elements and number of emitted packets self.last_line_interaction_out_id = self.line_out_infos self.last_line_interaction_out_angstrom = self.line_out_nu.to( units.Angstrom, equivalencies=units.spectral()) self.last_line_interaction_out_id[ "emitted_wavelength"] = self.last_line_interaction_out_angstrom self.line_out_infos_within_xlims = self.last_line_interaction_out_id.loc[ (self.last_line_interaction_out_id.emitted_wavelength >= self._xlim[0]) & (self.last_line_interaction_out_id.emitted_wavelength <= self._xlim[1])] # gets list of elements and number of absorbed packets self.last_line_interaction_in_id = self.line_in_infos self.last_line_interaction_in_angstrom = self.line_in_nu.to( units.Angstrom, equivalencies=units.spectral()) self.last_line_interaction_in_id[ "emitted_wavelength"] = self.last_line_interaction_in_angstrom self.line_in_infos_within_xlims = self.last_line_interaction_in_id.loc[ (self.last_line_interaction_in_id.emitted_wavelength >= self._xlim[0]) & (self.last_line_interaction_in_id.emitted_wavelength <= self._xlim[1])] self.line_in_and_out_infos_within_xlims = pd.concat([ self.line_in_infos_within_xlims, self.line_out_infos_within_xlims ]) # this generates the 4-digit ID for all transitions in the model # (e.g. Fe III line --> 2602) self.line_in_and_out_infos_within_xlims["ion_id"] = ( self.line_in_and_out_infos_within_xlims["atomic_number"] * 100 + self.line_in_and_out_infos_within_xlims["ion_number"]) # this is a list that will hold which elements should all be in the # same colour. This is used if the user requests a mix of ions and # elements. self.keep_colour = [] # this reads in the species specified by user and generates the 4-digit # ID keys for them if self._species_list is not None: # create a list of the ions ids requested by species_list requested_species_ids = [] # check if there are any digits in the species list. If there are # then exit # species_list should only contain species in the Roman numeral # format, e.g. Si II, and each ion must contain a space if any(char.isdigit() for char in " ".join(self._species_list)) == True: raise ValueError( "All species must be in Roman numeral form, e.g. Si II") else: # go through each of the request species. Check whether it is # an element or ion (ions have spaces). If it is an element, # add all possible ions to the ions list. Otherwise just add # the requested ion for species in self._species_list: if " " in species: requested_species_ids.append([ species_string_to_tuple(species)[0] * 100 + species_string_to_tuple(species)[1] ]) else: atomic_number = elements.loc[elements['chem_symbol'] == species.lower(), 'atomic_no'].values[0] requested_species_ids.append([ atomic_number * 100 + i for i in np.arange(atomic_number) ]) self.keep_colour.append(atomic_number) self.requested_species_ids = [ species_id for list in requested_species_ids for species_id in list ] # now we are getting the list of unique values for 'ion_id' if we would # like to use species. Otherwise we get unique atomic numbers if self._species_list is not None: self._elements_in_kromer_plot = np.c_[np.unique( self.line_in_and_out_infos_within_xlims.ion_id.values, return_counts=True, )] else: self._elements_in_kromer_plot = np.c_[np.unique( self.line_in_and_out_infos_within_xlims.atomic_number.values, return_counts=True, )] return self._elements_in_kromer_plot
def assemble_plasma(config, model, atom_data=None): """ Create a BasePlasma instance from a Configuration object and a Radial1DModel. Parameters ---------- config: ~io.config_reader.Configuration model: ~model.Radial1DModel atom_data: ~atomic.AtomData If None, an attempt will be made to read the atomic data from config. Returns ------- : ~plasma.BasePlasma """ # Convert the nlte species list to a proper format. nlte_species = [ species_string_to_tuple(s) for s in config.plasma.nlte.species ] if atom_data is None: if 'atom_data' in config: if os.path.isabs(config.atom_data): atom_data_fname = config.atom_data else: atom_data_fname = os.path.join(config.config_dirname, config.atom_data) else: raise ValueError('No atom_data option found in the configuration.') logger.info('Reading Atomic Data from %s', atom_data_fname) try: atom_data = atomic.AtomData.from_hdf(atom_data_fname) except TypeError as e: print( e, 'Error might be from the use of an old-format of the atomic database, \n' 'please see https://github.com/tardis-sn/tardis-refdata/tree/master/atom_data' ',for the most recent version.') raise atom_data.prepare_atom_data( model.abundance.index, line_interaction_type=config.plasma.line_interaction_type, nlte_species=nlte_species) kwargs = dict(t_rad=model.t_radiative, abundance=model.abundance, density=model.density, atomic_data=atom_data, time_explosion=model.time_explosion, w=model.dilution_factor, link_t_rad_t_electron=0.9) plasma_modules = basic_inputs + basic_properties property_kwargs = {} if config.plasma.radiative_rates_type == 'blackbody': plasma_modules.append(JBluesBlackBody) elif config.plasma.radiative_rates_type == 'dilute-blackbody': plasma_modules.append(JBluesDiluteBlackBody) elif config.plasma.radiative_rates_type == 'detailed': plasma_modules += detailed_j_blues_properties + detailed_j_blues_inputs kwargs.update(r_inner=model.r_inner, t_inner=model.t_inner, volume=model.volume, j_blue_estimator=None) property_kwargs[JBluesDetailed] = { 'w_epsilon': config.plasma.w_epsilon } else: raise ValueError('radiative_rates_type type unknown - %s', config.plasma.radiative_rates_type) if config.plasma.excitation == 'lte': plasma_modules += lte_excitation_properties elif config.plasma.excitation == 'dilute-lte': plasma_modules += dilute_lte_excitation_properties if config.plasma.ionization == 'lte': plasma_modules += lte_ionization_properties elif config.plasma.ionization == 'nebular': plasma_modules += nebular_ionization_properties if nlte_species: plasma_modules += nlte_properties nlte_conf = config.plasma.nlte plasma_modules.append(LevelBoltzmannFactorNLTE.from_config(nlte_conf)) property_kwargs[StimulatedEmissionFactor] = dict( nlte_species=nlte_species) else: plasma_modules += non_nlte_properties if config.plasma.line_interaction_type in ('downbranch', 'macroatom'): plasma_modules += macro_atom_properties if 'delta_treatment' in config.plasma: property_kwargs[RadiationFieldCorrection] = dict( delta_treatment=config.plasma.delta_treatment) if config.plasma.helium_treatment == 'recomb-nlte': plasma_modules += helium_nlte_properties elif config.plasma.helium_treatment == 'numerical-nlte': plasma_modules += helium_numerical_nlte_properties # TODO: See issue #633 if config.plasma.heating_rate_data_file in ['none', None]: raise PlasmaConfigError('Heating rate data file not specified') else: property_kwargs[HeliumNumericalNLTE] = dict( heating_rate_data_file=config.plasma.heating_rate_data_file) else: plasma_modules += helium_lte_properties if model._electron_densities: electron_densities = pd.Series(model._electron_densities.cgs.value) if config.plasma.helium_treatment == 'numerical-nlte': property_kwargs[IonNumberDensityHeNLTE] = dict( electron_densities=electron_densities) else: property_kwargs[IonNumberDensity] = dict( electron_densities=electron_densities) kwargs['helium_treatment'] = config.plasma.helium_treatment plasma = BasePlasma(plasma_properties=plasma_modules, property_kwargs=property_kwargs, **kwargs) return plasma