From f2c8418f53420f7408ad0948f18c9e65cc56c5dc Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 3 Apr 2013 20:26:03 -0400 Subject: [PATCH 01/17] fixed problems with the collision data. It should work now. --- tardis/atomic.py | 16 +++++++++++----- tardis/model_radial_oned.py | 20 ++++++++++++-------- tardis/montecarlo_multizone.pyx | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/tardis/atomic.py b/tardis/atomic.py index dcdfbd0e74a..51a4530a66a 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -7,6 +7,8 @@ import os import h5py +import pdb + from astropy import table, units, constants from collections import OrderedDict @@ -546,11 +548,9 @@ def prepare_nlte_indices(self, nlte_species): def get_collision_coefficients(self, atomic_number, ion_number, level_number_lower, level_number_upper, t_electron): if self.has_collision_data: try: - C_lus = self.collision_data.ix[ - (atomic_number, ion_number, level_number_lower, level_number_upper)].values[1:] - C_lu = np.interp(t_electron, self.collision_data_temperatures, C_lus) - C_ul = C_lu * self.collision_data.ix[ - (atomic_number, ion_number, level_number_lower, level_number_upper)].values[0] + collision_data = self.collision_data.ix[ + (atomic_number, ion_number, level_number_lower, level_number_upper)] + except pd.core.indexing.IndexingError: C_lu = 0 @@ -558,6 +558,12 @@ def get_collision_coefficients(self, atomic_number, ion_number, level_number_low logger.debug('Could not find collision data for atom=%d ion=%d lvl_lower=%d lvl_upper=%d', atomic_number, ion_number, level_number_lower, level_number_upper) else: + C_ul = np.interp(t_electron, self.collision_data_temperatures, collision_data.values[2:]) + g_ratio = collision_data['g_ratio'] + delta_e = collision_data['delta_e'] + C_lu = C_ul * np.exp(-delta_e / t_electron) / g_ratio + #(atomic_number, ion_number, level_number_lower, level_number_upper)].values[0] + logger.debug('Found collision data for atom=%d ion=%d lvl_lower=%d lvl_upper=%d', atomic_number, ion_number, level_number_lower, level_number_upper) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index 6d5eafe9356..1eac0da61fd 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -120,13 +120,13 @@ def __init__(self, tardis_config): self.plasma_type = tardis_config.plasma_type self.radiative_rates_type = tardis_config.radiative_rates_type if self.plasma_type == 'lte': - self.plasma_class = plasma.LTEPlasma + plasma_class = plasma.LTEPlasma if tardis_config.ws is not None: raise ValueError( "the dilution factor W ('ws') can only be specified when selecting plasma_type='nebular'") elif self.plasma_type == 'nebular': - self.plasma_class = plasma.NebularPlasma + plasma_class = plasma.NebularPlasma if not self.atom_data.has_zeta_data: raise ValueError("Requiring Recombination coefficients Zeta for 'nebular' plasma_type") else: @@ -156,7 +156,7 @@ def __init__(self, tardis_config): assert len(tardis_config.initial_t_rad) == self.no_of_shells self.t_rads = np.array(tardis_config.initial_t_rad, dtype=np.float64) - self.initialize_plasmas() + self.initialize_plasmas(plasma_class) @property @@ -203,7 +203,7 @@ def create_packets(self): no_of_packets = self.current_no_of_packets self.packet_src.create_packets(no_of_packets, self.t_inner) - def initialize_plasmas(self): + def initialize_plasmas(self, plasma_class): self.plasmas = [] self.tau_sobolevs = np.zeros((self.no_of_shells, len(self.atom_data.lines))) self.line_list_nu = self.atom_data.lines['nu'] @@ -214,8 +214,8 @@ def initialize_plasmas(self): if self.plasma_type == 'lte': for i, ((tmp_index, current_abundances), current_t_rad) in \ enumerate(zip(self.number_densities.iterrows(), self.t_rads)): - current_plasma = self.plasma_class(current_abundances, self.atom_data, self.time_explosion, - nlte_species=self.tardis_config.nlte_species, zone_id=i) + current_plasma = plasma_class(current_abundances, self.atom_data, self.time_explosion, + nlte_species=self.tardis_config.nlte_species, zone_id=i) logger.debug('Initializing Shell %d Plasma with T=%.3f' % (i, current_t_rad)) if self.radiative_rates_type in ('lte', 'detailed'): j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) @@ -234,8 +234,8 @@ def initialize_plasmas(self): elif self.plasma_type == 'nebular': for i, ((tmp_index, current_abundances), current_t_rad, current_w) in \ enumerate(zip(self.number_densities.iterrows(), self.t_rads, self.ws)): - current_plasma = self.plasma_class(current_abundances, self.atom_data, self.time_explosion, - nlte_species=self.tardis_config.nlte_species, zone_id=i) + current_plasma = plasma_class(current_abundances, self.atom_data, self.time_explosion, + nlte_species=self.tardis_config.nlte_species, zone_id=i) logger.debug('Initializing Shell %d Plasma with T=%.3f W=%.4f' % (i, current_t_rad, current_w)) if self.radiative_rates_type in ('lte',): j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) @@ -507,6 +507,10 @@ def plot_spectrum(self, ax=None, mode='wavelength', virtual=True): ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) + def save_spectrum(self, prefix): + np.savetxt(prefix + '_virtual_spec.dat', zip(self.spec_angstrom, self.spec_virtual_flux_angstrom)) + np.savetxt(prefix + '_spec.dat', zip(self.spec_angstrom, self.spec_flux_angstrom)) + class ModelHistory(object): """ diff --git a/tardis/montecarlo_multizone.pyx b/tardis/montecarlo_multizone.pyx index 40cee55d57d..0d38ba9e985 100644 --- a/tardis/montecarlo_multizone.pyx +++ b/tardis/montecarlo_multizone.pyx @@ -522,6 +522,7 @@ def montecarlo_radial1d(model, int_type_t virtual_packet_flag=0): #linelists current_line_id = binary_search(storage.line_list_nu, comov_current_nu, 0, storage.no_of_lines) + if current_line_id == storage.no_of_lines: #setting flag that the packet is off the red end of the line list last_line = 1 From 809e7c8799793ea31600884ac0292709a34c0067 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 5 Apr 2013 18:24:17 -0400 Subject: [PATCH 02/17] CRITICAL Error prevented NLTE level populations making it in the tau_sobolev calculations (they were overwritten beforehand). --- tardis/model_radial_oned.py | 16 +++++++++++++++- tardis/plasma.py | 4 +--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index 1eac0da61fd..77c3145a7f2 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -181,7 +181,8 @@ def line_interaction_type(self, value): self._line_interaction_type = value #final preparation for atom_data object - currently building data self.atom_data.prepare_atom_data(self.selected_atomic_numbers, - line_interaction_type=self.line_interaction_type, max_ion_number=None) + line_interaction_type=self.line_interaction_type, max_ion_number=None, + nlte_species=self.tardis_config.nlte_species) @property @@ -209,7 +210,13 @@ def initialize_plasmas(self, plasma_class): self.line_list_nu = self.atom_data.lines['nu'] if self.line_interaction_id in (1, 2): + if self.line_interaction_id == 1: + logger.info('Downbranch selected - creating transition probabilities') + else: + logger.info('Macroatom selected - creating transition probabilties') self.transition_probabilities = [] + else: + logger.info('Scattering selected - no transition probabilities created') if self.plasma_type == 'lte': for i, ((tmp_index, current_abundances), current_t_rad) in \ @@ -522,23 +529,30 @@ def __init__(self, tardis_config): self.ws = pd.DataFrame(index=np.arange(tardis_config.no_of_shells)) self.level_populations = {} self.j_blues = {} + self.tau_sobolevs = {} def store_all(self, radial1d_mdl, iteration): self.t_rads['iter%d' % iteration] = radial1d_mdl.t_rads self.ws['iter%d' % iteration] = radial1d_mdl.ws + #current_ion_populations = pd.DataFrame(index=radial1d_mdl.atom_data.) current_level_populations = pd.DataFrame(index=radial1d_mdl.atom_data.levels.index) current_j_blues = pd.DataFrame(index=radial1d_mdl.atom_data.lines.index) + current_tau_sobolevs = pd.DataFrame(index=radial1d_mdl.atom_data.lines.index) for i, plasma in enumerate(radial1d_mdl.plasmas): current_level_populations[i] = plasma.level_populations current_j_blues[i] = plasma.j_blues + current_tau_sobolevs[i] = plasma.tau_sobolevs self.level_populations['iter%d' % iteration] = current_level_populations.copy() self.j_blues['iter%d' % iteration] = current_j_blues.copy() + self.tau_sobolevs['iter%d' % iteration] = current_tau_sobolevs.copy() + def finalize(self): self.level_populations = pd.Panel.from_dict(self.level_populations) self.j_blues = pd.Panel.from_dict(self.j_blues) + self.tau_sobolevs = pd.Panel.from_dict(self.tau_sobolevs) diff --git a/tardis/plasma.py b/tardis/plasma.py index 450451f9547..f1d73b4b622 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -321,7 +321,7 @@ def calculate_level_populations(self): else: level_populations = Series(level_populations, index=self.atom_data.levels.index) - self.level_populations.update(level_populations[~self.atom_data.nlte_mask]) + self.level_populations[~self.atom_data.nlte_mask] = level_populations[~self.atom_data.nlte_mask] def calculate_nlte_level_populations(self): """ @@ -758,8 +758,6 @@ def calculate_level_populations(self): #only change between lte plasma and nebular level_populations[~self.atom_data.levels['metastable']] *= self.w - self.level_populations = Series(level_populations, index=self.atom_data.levels.index) - if self.initialize: self.level_populations = Series(level_populations, index=self.atom_data.levels.index) From 0e63f4bd06106c4ff055de1559dc747feddf8e2b Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 7 Apr 2013 20:10:32 -0400 Subject: [PATCH 03/17] added electron density to history --- tardis/model_radial_oned.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index 77c3145a7f2..deb550db2ab 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -527,6 +527,7 @@ class ModelHistory(object): def __init__(self, tardis_config): self.t_rads = pd.DataFrame(index=np.arange(tardis_config.no_of_shells)) self.ws = pd.DataFrame(index=np.arange(tardis_config.no_of_shells)) + self.electron_density = pd.DataFrame(index=np.arange(tardis_config.no_of_shells)) self.level_populations = {} self.j_blues = {} self.tau_sobolevs = {} @@ -535,6 +536,7 @@ def __init__(self, tardis_config): def store_all(self, radial1d_mdl, iteration): self.t_rads['iter%d' % iteration] = radial1d_mdl.t_rads self.ws['iter%d' % iteration] = radial1d_mdl.ws + self.electron_density['iter%d' % iteration] = radial1d_mdl.electron_density #current_ion_populations = pd.DataFrame(index=radial1d_mdl.atom_data.) current_level_populations = pd.DataFrame(index=radial1d_mdl.atom_data.levels.index) From a861691ea8704342faab44a2827a66cf0aac1eee Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 7 Apr 2013 20:11:01 -0400 Subject: [PATCH 04/17] debugging problems - with debug statements in there DO NOT USE --- tardis/plasma.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index f1d73b4b622..d372430cb29 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -351,7 +351,11 @@ def calculate_nlte_level_populations(self): n_lower = self.level_populations.ix[atomic_number, ion_number, level_number_lower] n_upper = self.level_populations.ix[atomic_number, ion_number, level_number_upper] - cur_beta_sobolev = self.beta_sobolevs[i] + #cur_beta_sobolev = self.beta_sobolevs[i] + #cur_j_blue = self.j_blues[i] + + cur_beta_sobolev = 1.0 + cur_j_blue = 0.0 stimulated_emission_term = (1 - (n_upper * line['B_ul']) / (n_lower * line['B_lu'])) @@ -361,8 +365,9 @@ def calculate_nlte_level_populations(self): C_lu, C_ul = self.atom_data.get_collision_coefficients(atomic_number, ion_number, level_number_lower, level_number_upper, self.t_electron) - r_lu = line['B_lu'] * cur_beta_sobolev * self.j_blues[ - i] * stimulated_emission_term + C_lu * self.electron_density + + r_lu = line[ + 'B_lu'] * cur_beta_sobolev * cur_j_blue * stimulated_emission_term + C_lu * self.electron_density r_ul = line['A_ul'] * cur_beta_sobolev + C_ul * self.electron_density rates_matrix[level_number_upper, level_number_lower] = r_lu From 3ccf5864e2bcf1bb51bcded7d0cb8533e4ff9439 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 18:15:48 -0400 Subject: [PATCH 05/17] minor changes to model_history --- tardis/model_radial_oned.py | 12 ++++++------ tardis/plasma.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index deb550db2ab..bc5f4d1abd6 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -534,9 +534,9 @@ def __init__(self, tardis_config): def store_all(self, radial1d_mdl, iteration): - self.t_rads['iter%d' % iteration] = radial1d_mdl.t_rads - self.ws['iter%d' % iteration] = radial1d_mdl.ws - self.electron_density['iter%d' % iteration] = radial1d_mdl.electron_density + self.t_rads['iter%03d' % iteration] = radial1d_mdl.t_rads + self.ws['iter%03d' % iteration] = radial1d_mdl.ws + self.electron_density['iter%03d' % iteration] = radial1d_mdl.electron_density #current_ion_populations = pd.DataFrame(index=radial1d_mdl.atom_data.) current_level_populations = pd.DataFrame(index=radial1d_mdl.atom_data.levels.index) @@ -547,9 +547,9 @@ def store_all(self, radial1d_mdl, iteration): current_j_blues[i] = plasma.j_blues current_tau_sobolevs[i] = plasma.tau_sobolevs - self.level_populations['iter%d' % iteration] = current_level_populations.copy() - self.j_blues['iter%d' % iteration] = current_j_blues.copy() - self.tau_sobolevs['iter%d' % iteration] = current_tau_sobolevs.copy() + self.level_populations['iter%03d' % iteration] = current_level_populations.copy() + self.j_blues['iter%03d' % iteration] = current_j_blues.copy() + self.tau_sobolevs['iter%03d' % iteration] = current_tau_sobolevs.copy() def finalize(self): diff --git a/tardis/plasma.py b/tardis/plasma.py index d372430cb29..6231771beee 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -410,7 +410,7 @@ def calculate_nlte_level_populations(self): self.level_populations.ix[species].count(), self.zone_id) if float(self.cleaned_levels.ix[species]) / self.level_populations.ix[species].count() > 0.5: - logger.warn('Number of cleaned levels very high %d of %d (zone id=%s, , lowest_cleaned_level=%d)', + logger.warn('Number of cleaned levels very high %d of %d (zone id=%s, lowest_cleaned_level=%d)', self.cleaned_levels.ix[species], self.level_populations.ix[species].count(), self.zone_id, self.lowest_cleaned_level) From 5875556c352417a0bb9c1677350a8f4574642906 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 19:17:52 -0400 Subject: [PATCH 06/17] first construction of the collision_matrix --- tardis/atomic.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tardis/atomic.py b/tardis/atomic.py index 51a4530a66a..8780b352e26 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -529,6 +529,7 @@ def prepare_atom_data(self, selected_atomic_numbers, line_interaction_type='scat #Setting NLTE species self.set_nlte_mask(nlte_species) + self.prepare_collision_coefficient_matrix(nlte_species) def set_nlte_mask(self, nlte_species): @@ -545,6 +546,33 @@ def set_nlte_mask(self, nlte_species): def prepare_nlte_indices(self, nlte_species): pass + def prepare_collision_coefficient_matrix(self, nlte_species): + self.C_ul_interpolator = {} + self.delta_E_matrices = {} + self.g_ratio_matrices = {} + collision_group = self.collision_data.groupby(level=['atomic_number', 'ion_number']) + for species in nlte_species: + no_of_levels = self.levels.ix[species].energy.count() + C_ul_matrix = np.zeros((no_of_levels, no_of_levels, len(self.collision_data_temperatures))) + delta_E_matrix = np.zeros((no_of_levels, no_of_levels)) + g_ratio_matrix = np.zeros((no_of_levels, no_of_levels)) + + for (atomic_number, ion_number, level_number_lower, level_number_upper), line in \ + collision_group.get_group(species).iterrows(): + C_ul_matrix[level_number_lower, level_number_upper, :] = line.values[2:] + delta_E_matrix[level_number_lower, level_number_upper] = line['delta_e'] + g_ratio_matrix[level_number_lower, level_number_upper] = line['g_ratio'] + self.C_ul_interpolator[species] = interpolate.interp1d(self.collision_data_temperatures, C_ul_matrix) + self.delta_E_matrices[species] = delta_E_matrix + self.g_ratio_matrices[species] = g_ratio_matrix + + + def get_collision_matrix(self, species, t_electron): + c_ul_matrix = self.C_ul_interpolator[species][t_electron] + c_lu_matrix = c_ul_matrix * np.exp(-self.delta_E_matrices[species] / t_electron) / self.g_ratio_matrices[ + species] + return c_ul_matrix + c_lu_matrix + def get_collision_coefficients(self, atomic_number, ion_number, level_number_lower, level_number_upper, t_electron): if self.has_collision_data: try: From c7abaf418c0e09455b027fb1aa26093b3014e213 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 22:33:01 -0400 Subject: [PATCH 07/17] implemented new_matrix --- tardis/atomic.py | 34 +++++++++++++++++++++--- tardis/plasma.py | 68 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/tardis/atomic.py b/tardis/atomic.py index 8780b352e26..e812d01a2ec 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -527,6 +527,8 @@ def prepare_atom_data(self, selected_atomic_numbers, line_interaction_type='scat self.macro_atom_data['destination_level_idx'] = (np.ones(len(self.macro_atom_data)) * -1).astype( np.int64) + self.nlte_data = NLTEData(self, nlte_species) + #Setting NLTE species self.set_nlte_mask(nlte_species) self.prepare_collision_coefficient_matrix(nlte_species) @@ -561,17 +563,22 @@ def prepare_collision_coefficient_matrix(self, nlte_species): collision_group.get_group(species).iterrows(): C_ul_matrix[level_number_lower, level_number_upper, :] = line.values[2:] delta_E_matrix[level_number_lower, level_number_upper] = line['delta_e'] - g_ratio_matrix[level_number_lower, level_number_upper] = line['g_ratio'] + #TODO TARDISATOMIC fix change the g_ratio to be the otherway round - I flip them now here. + g_ratio_matrix[level_number_lower, level_number_upper] = 1 / line['g_ratio'] self.C_ul_interpolator[species] = interpolate.interp1d(self.collision_data_temperatures, C_ul_matrix) self.delta_E_matrices[species] = delta_E_matrix + self.g_ratio_matrices[species] = g_ratio_matrix def get_collision_matrix(self, species, t_electron): - c_ul_matrix = self.C_ul_interpolator[species][t_electron] - c_lu_matrix = c_ul_matrix * np.exp(-self.delta_E_matrices[species] / t_electron) / self.g_ratio_matrices[ + c_ul_matrix = self.C_ul_interpolator[species](t_electron) + + c_ul_matrix[np.isnan(c_ul_matrix)] = 0.0 + #TODO in tardisatomic the g_ratio is the other way round - here I'll flip it in prepare_collision matrix + c_lu_matrix = c_ul_matrix * np.exp(-self.delta_E_matrices[species] / t_electron) * self.g_ratio_matrices[ species] - return c_ul_matrix + c_lu_matrix + return c_ul_matrix + c_lu_matrix.transpose() def get_collision_coefficients(self, atomic_number, ion_number, level_number_lower, level_number_upper, t_electron): if self.has_collision_data: @@ -605,6 +612,25 @@ def __repr__(self): (self.uuid1, self.md5, self.lines_data.atomic_number.count(), self.levels_data.energy.count()) +class NLTEData(object): + def __init__(self, atom_data, nlte_species): + self.atom_data = atom_data + self.lines = atom_data.lines.reset_index() + self.nlte_species = nlte_species + self._init_indices() + pass + def _init_indices(self): + self.lines_idx = {} + self.lines_level_number_lower = {} + self.lines_level_number_upper = {} + self.lines_matrix_idx = {} + for species in self.nlte_species: + self.lines_idx[species] = np.where((self.lines.atomic_number == species[0]) & + (self.lines.ion_number == species[1])) + self.lines_level_number_lower[species] = self.lines.level_number_lower.values[ + self.lines_idx[species]].astype(int) + self.lines_level_number_upper[species] = self.lines.level_number_upper.values[ + self.lines_idx[species]].astype(int) diff --git a/tardis/plasma.py b/tardis/plasma.py index 6231771beee..0572cf04bc3 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -13,6 +13,8 @@ logger = logging.getLogger(__name__) import pdb + +import time #Bnu = lambda nu, t: (2 * constants.h * nu ** 3 / constants.c ** 2) * np.exp( # 1 / ((constants.h * nu) / (constants.kb * t))) @@ -323,7 +325,7 @@ def calculate_level_populations(self): level_populations = Series(level_populations, index=self.atom_data.levels.index) self.level_populations[~self.atom_data.nlte_mask] = level_populations[~self.atom_data.nlte_mask] - def calculate_nlte_level_populations(self): + def calculate_nlte_level_populations(self, coronal_case=False): """ Calculating the NLTE level populations for specific ions @@ -334,9 +336,53 @@ def calculate_nlte_level_populations(self): macro_atom.calculate_beta_sobolev(self.tau_sobolevs, self.beta_sobolevs) + if coronal_case: + beta_sobolevs = np.ones_like(self.beta_sobolevs) + j_blues = np.zeros_like(self.j_blues) + else: + beta_sobolevs = self.beta_sobolevs + j_blues = self.j_blues + for species in self.nlte_species: logger.info('Calculating rates for species %s', species) number_of_levels = self.level_populations.ix[species].size + + #new RATES MATRIX + start_time = time.time() + level_populations = self.level_populations.ix[species].values + lnl = self.atom_data.nlte_data.lines_level_number_lower[species] + lnu = self.atom_data.nlte_data.lines_level_number_upper[species] + + lines_index = self.atom_data.nlte_data.lines_idx[species] + A_uls = self.atom_data.lines.A_ul.values[lines_index] + B_uls = self.atom_data.lines.B_ul.values[lines_index] + B_lus = self.atom_data.lines.B_lu.values[lines_index] + + r_lu_index = lnu * number_of_levels + lnl + r_ul_index = lnl * number_of_levels + lnu + + r_ul_matrix = np.zeros((number_of_levels, number_of_levels), dtype=np.float64) + r_ul_matrix.ravel()[r_ul_index] = A_uls + r_ul_matrix.ravel()[r_ul_index] *= beta_sobolevs[lines_index] + + stimulated_emission_matrix = np.zeros_like(r_ul_matrix) + stimulated_emission_matrix.ravel()[r_lu_index] = 1 - (level_populations[lnu] * B_uls) / ( + level_populations[lnl] * B_lus) + + r_lu_matrix = np.zeros_like(r_ul_matrix) + r_lu_matrix.ravel()[r_lu_index] = B_lus * j_blues[lines_index] * beta_sobolevs[lines_index] + r_lu_matrix *= stimulated_emission_matrix + + collision_matrix = self.atom_data.get_collision_matrix(species, self.t_electron) * self.electron_density + + rates_matrix2 = r_lu_matrix + r_ul_matrix + collision_matrix + + print "new_matrix took %s" % (time.time() - start_time) + + start_time = time.time() + + # old_rates_matrix + rates_matrix = np.zeros((number_of_levels, number_of_levels), dtype=np.float64) for i, (line_id, line) in enumerate(self.atom_data.lines.iterrows()): @@ -351,11 +397,8 @@ def calculate_nlte_level_populations(self): n_lower = self.level_populations.ix[atomic_number, ion_number, level_number_lower] n_upper = self.level_populations.ix[atomic_number, ion_number, level_number_upper] - #cur_beta_sobolev = self.beta_sobolevs[i] - #cur_j_blue = self.j_blues[i] - - cur_beta_sobolev = 1.0 - cur_j_blue = 0.0 + cur_beta_sobolev = beta_sobolevs[i] + cur_j_blue = j_blues[i] stimulated_emission_term = (1 - (n_upper * line['B_ul']) / (n_lower * line['B_lu'])) @@ -368,7 +411,11 @@ def calculate_nlte_level_populations(self): r_lu = line[ 'B_lu'] * cur_beta_sobolev * cur_j_blue * stimulated_emission_term + C_lu * self.electron_density + #r_lu = stimulated_emission_term + r_ul = line['A_ul'] * cur_beta_sobolev + C_ul * self.electron_density + #r_ul = line['A_ul'] * cur_beta_sobolev + #r_ul = 0.0 rates_matrix[level_number_upper, level_number_lower] = r_lu rates_matrix[level_number_lower, level_number_upper] = r_ul @@ -376,9 +423,18 @@ def calculate_nlte_level_populations(self): rates_matrix[level_number_lower, level_number_lower] -= r_lu rates_matrix[level_number_upper, level_number_upper] -= r_ul + + + # add them like this later: + #for i in range(29) + # rates_matrix[i, i] = -np.sum(collision_matrix[:, i]) + print "old_matrix took %s" % (time.time() - start_time) + pdb.set_trace() + rates_matrix[0] = 1.0 x = np.zeros(rates_matrix.shape[0]) x[0] = 1.0 + self.level_populations.ix[species] = np.linalg.solve(rates_matrix, x) * self.ion_number_density.ix[species] From c6d61628f79ff867843496f4a72c495bd1acd043 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 22:54:54 -0400 Subject: [PATCH 08/17] bug fixing new rates matrix --- tardis/plasma.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index 0572cf04bc3..1e1f20ea7f2 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -367,7 +367,7 @@ def calculate_nlte_level_populations(self, coronal_case=False): stimulated_emission_matrix = np.zeros_like(r_ul_matrix) stimulated_emission_matrix.ravel()[r_lu_index] = 1 - (level_populations[lnu] * B_uls) / ( - level_populations[lnl] * B_lus) + level_populations[lnl] * B_lus) r_lu_matrix = np.zeros_like(r_ul_matrix) r_lu_matrix.ravel()[r_lu_index] = B_lus * j_blues[lines_index] * beta_sobolevs[lines_index] @@ -377,6 +377,9 @@ def calculate_nlte_level_populations(self, coronal_case=False): rates_matrix2 = r_lu_matrix + r_ul_matrix + collision_matrix + for i in xrange(number_of_levels): + rates_matrix2[i, i] = -np.sum(rates_matrix2[:, i]) + print "new_matrix took %s" % (time.time() - start_time) start_time = time.time() @@ -423,19 +426,18 @@ def calculate_nlte_level_populations(self, coronal_case=False): rates_matrix[level_number_lower, level_number_lower] -= r_lu rates_matrix[level_number_upper, level_number_upper] -= r_ul - - - # add them like this later: - #for i in range(29) - # rates_matrix[i, i] = -np.sum(collision_matrix[:, i]) print "old_matrix took %s" % (time.time() - start_time) - pdb.set_trace() rates_matrix[0] = 1.0 + rates_matrix2[0] = 1.0 + x = np.zeros(rates_matrix.shape[0]) x[0] = 1.0 + relative_level_populations = np.linalg.solve(rates_matrix, x) + relative_level_populations2 = np.linalg.solve(rates_matrix2, x) - self.level_populations.ix[species] = np.linalg.solve(rates_matrix, x) * self.ion_number_density.ix[species] + 1 / 0 + self.level_populations.ix[species] = relative_level_populations * self.ion_number_density.ix[species] #Cleaning Level populations From 66e5efb10fc297f14fed2e9daad31986d4d609b7 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 23:38:08 -0400 Subject: [PATCH 09/17] fixed up rates_matrix just changing nlte container class. speedup of 3000 --- tardis/plasma.py | 60 ++++-------------------------------------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index 1e1f20ea7f2..d3baa531a0e 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -325,7 +325,7 @@ def calculate_level_populations(self): level_populations = Series(level_populations, index=self.atom_data.levels.index) self.level_populations[~self.atom_data.nlte_mask] = level_populations[~self.atom_data.nlte_mask] - def calculate_nlte_level_populations(self, coronal_case=False): + def calculate_nlte_level_populations(self, coronal_case=True): """ Calculating the NLTE level populations for specific ions @@ -347,8 +347,6 @@ def calculate_nlte_level_populations(self, coronal_case=False): logger.info('Calculating rates for species %s', species) number_of_levels = self.level_populations.ix[species].size - #new RATES MATRIX - start_time = time.time() level_populations = self.level_populations.ix[species].values lnl = self.atom_data.nlte_data.lines_level_number_lower[species] lnu = self.atom_data.nlte_data.lines_level_number_upper[species] @@ -375,70 +373,20 @@ def calculate_nlte_level_populations(self, coronal_case=False): collision_matrix = self.atom_data.get_collision_matrix(species, self.t_electron) * self.electron_density - rates_matrix2 = r_lu_matrix + r_ul_matrix + collision_matrix + rates_matrix = r_lu_matrix + r_ul_matrix + collision_matrix for i in xrange(number_of_levels): - rates_matrix2[i, i] = -np.sum(rates_matrix2[:, i]) - - print "new_matrix took %s" % (time.time() - start_time) - - start_time = time.time() - - # old_rates_matrix - - rates_matrix = np.zeros((number_of_levels, number_of_levels), dtype=np.float64) - - for i, (line_id, line) in enumerate(self.atom_data.lines.iterrows()): - atomic_number = int(line['atomic_number']) - ion_number = int(line['ion_number']) - if (atomic_number, ion_number) != species: - continue - - level_number_lower = int(line['level_number_lower']) - level_number_upper = int(line['level_number_upper']) - - n_lower = self.level_populations.ix[atomic_number, ion_number, level_number_lower] - n_upper = self.level_populations.ix[atomic_number, ion_number, level_number_upper] - - cur_beta_sobolev = beta_sobolevs[i] - cur_j_blue = j_blues[i] - - stimulated_emission_term = (1 - (n_upper * line['B_ul']) / (n_lower * line['B_lu'])) - - if stimulated_emission_term < 0: - logger.warn('Stimulated emission term less than 0 %g n_upper=%g n_lower=%g (zone_id=%s)', - stimulated_emission_term, n_upper, n_lower, self.zone_id) - - C_lu, C_ul = self.atom_data.get_collision_coefficients(atomic_number, ion_number, level_number_lower, - level_number_upper, self.t_electron) - - r_lu = line[ - 'B_lu'] * cur_beta_sobolev * cur_j_blue * stimulated_emission_term + C_lu * self.electron_density - #r_lu = stimulated_emission_term - - r_ul = line['A_ul'] * cur_beta_sobolev + C_ul * self.electron_density - #r_ul = line['A_ul'] * cur_beta_sobolev - #r_ul = 0.0 - - rates_matrix[level_number_upper, level_number_lower] = r_lu - rates_matrix[level_number_lower, level_number_upper] = r_ul - - rates_matrix[level_number_lower, level_number_lower] -= r_lu - rates_matrix[level_number_upper, level_number_upper] -= r_ul - - print "old_matrix took %s" % (time.time() - start_time) + rates_matrix[i, i] = -np.sum(rates_matrix[:, i]) rates_matrix[0] = 1.0 - rates_matrix2[0] = 1.0 x = np.zeros(rates_matrix.shape[0]) x[0] = 1.0 relative_level_populations = np.linalg.solve(rates_matrix, x) - relative_level_populations2 = np.linalg.solve(rates_matrix2, x) - 1 / 0 self.level_populations.ix[species] = relative_level_populations * self.ion_number_density.ix[species] + 1 / 0 #Cleaning Level populations self.cleaned_levels.ix[species] = 0 From 8f5491c051a2775fc76ab49fae9bcd327f28b3b6 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 8 Apr 2013 23:56:34 -0400 Subject: [PATCH 10/17] changed to NLTE data --- tardis/atomic.py | 106 +++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 67 deletions(-) diff --git a/tardis/atomic.py b/tardis/atomic.py index e812d01a2ec..fdce91da7ac 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -529,31 +529,57 @@ def prepare_atom_data(self, selected_atomic_numbers, line_interaction_type='scat self.nlte_data = NLTEData(self, nlte_species) - #Setting NLTE species - self.set_nlte_mask(nlte_species) - self.prepare_collision_coefficient_matrix(nlte_species) + + def __repr__(self): + return "" % \ + (self.uuid1, self.md5, self.lines_data.atomic_number.count(), self.levels_data.energy.count()) + + +class NLTEData(object): + def __init__(self, atom_data, nlte_species): + logger.info('Preparing the NLTE data') + self.atom_data = atom_data + self.lines = atom_data.lines.reset_index() + self.nlte_species = nlte_species + self._init_indices() + self._create_nlte_mask() - def set_nlte_mask(self, nlte_species): + def _init_indices(self): + self.lines_idx = {} + self.lines_level_number_lower = {} + self.lines_level_number_upper = {} + self.A_uls = {} + self.B_uls = {} + self.B_lus = {} + + for species in self.nlte_species: + lines_idx = np.where((self.lines.atomic_number == species[0]) & + (self.lines.ion_number == species[1])) + self.lines_idx[species] = lines_idx + self.lines_level_number_lower[species] = self.lines.level_number_lower.values[lines_idx].astype(int) + self.lines_level_number_upper[species] = self.lines.level_number_upper.values[lines_idx].astype(int) - logger.debug('Setting NLTE Species Mask for %s' % nlte_species) + self.A_uls[species] = self.atom_data.lines.A_ul.values[lines_idx] + self.B_uls[species] = self.atom_data.lines.B_ul.values[lines_idx] + self.B_lus[species] = self.atom_data.lines.B_lu.values[lines_idx] + + def _create_nlte_mask(self): self.nlte_mask = np.zeros(self.levels.shape[0]).astype(bool) - for species in nlte_species: - current_mask = (self.levels.index.get_level_values(0) == species[0]) & \ - (self.levels.index.get_level_values(1) == species[1]) + for species in self.nlte_species: + current_mask = (self.atom_data.levels.index.get_level_values(0) == species[0]) & \ + (self.atom_data.levels.index.get_level_values(1) == species[1]) self.nlte_mask |= current_mask - def prepare_nlte_indices(self, nlte_species): - pass - def prepare_collision_coefficient_matrix(self, nlte_species): + def _create_collision_coefficient_matrix(self): self.C_ul_interpolator = {} self.delta_E_matrices = {} self.g_ratio_matrices = {} - collision_group = self.collision_data.groupby(level=['atomic_number', 'ion_number']) - for species in nlte_species: + collision_group = self.atom_data.collision_data.groupby(level=['atomic_number', 'ion_number']) + for species in self.nlte_species: no_of_levels = self.levels.ix[species].energy.count() C_ul_matrix = np.zeros((no_of_levels, no_of_levels, len(self.collision_data_temperatures))) delta_E_matrix = np.zeros((no_of_levels, no_of_levels)) @@ -580,57 +606,3 @@ def get_collision_matrix(self, species, t_electron): species] return c_ul_matrix + c_lu_matrix.transpose() - def get_collision_coefficients(self, atomic_number, ion_number, level_number_lower, level_number_upper, t_electron): - if self.has_collision_data: - try: - collision_data = self.collision_data.ix[ - (atomic_number, ion_number, level_number_lower, level_number_upper)] - - - except pd.core.indexing.IndexingError: - C_lu = 0 - C_ul = 0 - logger.debug('Could not find collision data for atom=%d ion=%d lvl_lower=%d lvl_upper=%d', - atomic_number, ion_number, level_number_lower, level_number_upper) - else: - C_ul = np.interp(t_electron, self.collision_data_temperatures, collision_data.values[2:]) - g_ratio = collision_data['g_ratio'] - delta_e = collision_data['delta_e'] - C_lu = C_ul * np.exp(-delta_e / t_electron) / g_ratio - #(atomic_number, ion_number, level_number_lower, level_number_upper)].values[0] - - logger.debug('Found collision data for atom=%d ion=%d lvl_lower=%d lvl_upper=%d', - atomic_number, ion_number, level_number_lower, level_number_upper) - - return C_lu, C_ul - - else: - return 0., 0. - - def __repr__(self): - return "" % \ - (self.uuid1, self.md5, self.lines_data.atomic_number.count(), self.levels_data.energy.count()) - - -class NLTEData(object): - def __init__(self, atom_data, nlte_species): - self.atom_data = atom_data - self.lines = atom_data.lines.reset_index() - self.nlte_species = nlte_species - self._init_indices() - pass - - def _init_indices(self): - self.lines_idx = {} - self.lines_level_number_lower = {} - self.lines_level_number_upper = {} - self.lines_matrix_idx = {} - for species in self.nlte_species: - self.lines_idx[species] = np.where((self.lines.atomic_number == species[0]) & - (self.lines.ion_number == species[1])) - self.lines_level_number_lower[species] = self.lines.level_number_lower.values[ - self.lines_idx[species]].astype(int) - self.lines_level_number_upper[species] = self.lines.level_number_upper.values[ - self.lines_idx[species]].astype(int) - - From 0ff4ca7f630293c7a17aeed433beedc3ab715a95 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Tue, 9 Apr 2013 01:08:30 -0400 Subject: [PATCH 11/17] restructure complete ready for merge --- tardis/atomic.py | 12 +++++++----- tardis/config_reader.py | 10 +++++++++- tardis/model_radial_oned.py | 9 +++++---- tardis/plasma.py | 29 ++++++++++++++--------------- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tardis/atomic.py b/tardis/atomic.py index fdce91da7ac..9a6d82bbbdb 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -543,6 +543,7 @@ def __init__(self, atom_data, nlte_species): self.nlte_species = nlte_species self._init_indices() self._create_nlte_mask() + self._create_collision_coefficient_matrix() def _init_indices(self): @@ -565,7 +566,7 @@ def _init_indices(self): self.B_lus[species] = self.atom_data.lines.B_lu.values[lines_idx] def _create_nlte_mask(self): - self.nlte_mask = np.zeros(self.levels.shape[0]).astype(bool) + self.nlte_mask = np.zeros(self.atom_data.levels.energy.count()).astype(bool) for species in self.nlte_species: current_mask = (self.atom_data.levels.index.get_level_values(0) == species[0]) & \ @@ -580,8 +581,8 @@ def _create_collision_coefficient_matrix(self): self.g_ratio_matrices = {} collision_group = self.atom_data.collision_data.groupby(level=['atomic_number', 'ion_number']) for species in self.nlte_species: - no_of_levels = self.levels.ix[species].energy.count() - C_ul_matrix = np.zeros((no_of_levels, no_of_levels, len(self.collision_data_temperatures))) + no_of_levels = self.atom_data.levels.ix[species].energy.count() + C_ul_matrix = np.zeros((no_of_levels, no_of_levels, len(self.atom_data.collision_data_temperatures))) delta_E_matrix = np.zeros((no_of_levels, no_of_levels)) g_ratio_matrix = np.zeros((no_of_levels, no_of_levels)) @@ -590,8 +591,9 @@ def _create_collision_coefficient_matrix(self): C_ul_matrix[level_number_lower, level_number_upper, :] = line.values[2:] delta_E_matrix[level_number_lower, level_number_upper] = line['delta_e'] #TODO TARDISATOMIC fix change the g_ratio to be the otherway round - I flip them now here. - g_ratio_matrix[level_number_lower, level_number_upper] = 1 / line['g_ratio'] - self.C_ul_interpolator[species] = interpolate.interp1d(self.collision_data_temperatures, C_ul_matrix) + g_ratio_matrix[level_number_lower, level_number_upper] = line['g_ratio'] + self.C_ul_interpolator[species] = interpolate.interp1d(self.atom_data.collision_data_temperatures, + C_ul_matrix) self.delta_E_matrices[species] = delta_E_matrix self.g_ratio_matrices[species] = g_ratio_matrix diff --git a/tardis/config_reader.py b/tardis/config_reader.py index b1576ad5bcb..f0b106270e5 100644 --- a/tardis/config_reader.py +++ b/tardis/config_reader.py @@ -519,7 +519,15 @@ def from_yaml(cls, fname, args=None): logger.warn('"w_epsilon" not specified in plasma section - setting it to 1e-10') config_dict['w_epsilon'] = 1e-10 - ##### spectrum section ###### + ##### NLTE Section ##### + + nlte_section = yaml_dict.pop('nlte', None) + if nlte_section is None: + config_dict['coronal_case'] = False + + config_dict['coronal_case'] = nlte_section['coronal_case'] + + ##### spectrum section ###### spectrum_section = yaml_dict.pop('spectrum') spectrum_start = parse2quantity(spectrum_section['start']).to('angstrom', units.spectral()) spectrum_end = parse2quantity(spectrum_section['end']).to('angstrom', units.spectral()) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index bc5f4d1abd6..145492d0e73 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -232,7 +232,7 @@ def initialize_plasmas(self, plasma_class): ' be "lte" or "detailed"' % (self.plasma_type)) current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(current_t_rad) + current_plasma.update_radiationfield(current_t_rad, coronal_case=self.tardis_config.coronal_case) self.tau_sobolevs[i] = current_plasma.tau_sobolevs self.plasmas.append(current_plasma) @@ -253,7 +253,8 @@ def initialize_plasmas(self, plasma_class): ' be "lte" or "detailed" or "nebular"' % (self.plasma_type)) current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(current_t_rad, current_w) + current_plasma.update_radiationfield(current_t_rad, current_w, + coronal_case=self.tardis_config.coronal_case) self.tau_sobolevs[i] = current_plasma.tau_sobolevs @@ -327,7 +328,7 @@ def update_plasmas(self): current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(new_trad) + current_plasma.update_radiationfield(new_trad, coronal_case=self.tardis_config.coronal_case) self.tau_sobolevs[i] = current_plasma.tau_sobolevs elif self.plasma_type == 'nebular': @@ -346,7 +347,7 @@ def update_plasmas(self): current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(new_trad, new_ws) + current_plasma.update_radiationfield(new_trad, new_ws, coronal_case=self.tardis_config.coronal_case) self.tau_sobolevs[i] = current_plasma.tau_sobolevs if self.line_interaction_id in (1, 2): diff --git a/tardis/plasma.py b/tardis/plasma.py index d3baa531a0e..ab8d8417f43 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -4,7 +4,6 @@ import numpy as np import logging from astropy import table, units, constants -from collections import OrderedDict from pandas import DataFrame, Series, Index, lib as pdlib import pandas as pd @@ -14,9 +13,7 @@ logger = logging.getLogger(__name__) import pdb -import time -#Bnu = lambda nu, t: (2 * constants.h * nu ** 3 / constants.c ** 2) * np.exp( -# 1 / ((constants.h * nu) / (constants.kb * t))) + #Defining soboleve constant @@ -131,7 +128,7 @@ def __init__(self, abundances, atom_data, time_explosion, max_ion_number=None, self.nlte_species = nlte_species - def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshold=0.05): + def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshold=0.05, coronal_case=False): """ This functions updates the radiation temperature `t_rad` and calculates the beta_rad Parameters. Then calculating :math:`g_e=\\left(\\frac{2 \\pi m_e k_\\textrm{B}T}{h^2}\\right)^{3/2}`. @@ -178,7 +175,7 @@ def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshol self.calculate_level_populations() self.calculate_tau_sobolev() - self.calculate_nlte_level_populations() + self.calculate_nlte_level_populations(coronal_case=coronal_case) if self.initialize: self.initialize = False @@ -323,9 +320,10 @@ def calculate_level_populations(self): else: level_populations = Series(level_populations, index=self.atom_data.levels.index) - self.level_populations[~self.atom_data.nlte_mask] = level_populations[~self.atom_data.nlte_mask] + self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) + - def calculate_nlte_level_populations(self, coronal_case=True): + def calculate_nlte_level_populations(self, coronal_case=False): """ Calculating the NLTE level populations for specific ions @@ -352,9 +350,9 @@ def calculate_nlte_level_populations(self, coronal_case=True): lnu = self.atom_data.nlte_data.lines_level_number_upper[species] lines_index = self.atom_data.nlte_data.lines_idx[species] - A_uls = self.atom_data.lines.A_ul.values[lines_index] - B_uls = self.atom_data.lines.B_ul.values[lines_index] - B_lus = self.atom_data.lines.B_lu.values[lines_index] + A_uls = self.atom_data.nlte_data.A_uls[species] + B_uls = self.atom_data.nlte_data.B_uls[species] + B_lus = self.atom_data.nlte_data.B_lus[species] r_lu_index = lnu * number_of_levels + lnl r_ul_index = lnl * number_of_levels + lnu @@ -371,7 +369,8 @@ def calculate_nlte_level_populations(self, coronal_case=True): r_lu_matrix.ravel()[r_lu_index] = B_lus * j_blues[lines_index] * beta_sobolevs[lines_index] r_lu_matrix *= stimulated_emission_matrix - collision_matrix = self.atom_data.get_collision_matrix(species, self.t_electron) * self.electron_density + collision_matrix = self.atom_data.nlte_data.get_collision_matrix(species, + self.t_electron) * self.electron_density rates_matrix = r_lu_matrix + r_ul_matrix + collision_matrix @@ -536,7 +535,7 @@ def __init__(self, abundances, atom_data, time_explosion, nlte_species=[], t_ele self.nlte_species = nlte_species - def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_threshold=0.05): + def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_threshold=0.05, coronal_case=False): BasePlasma.update_radiationfield(self, t_rad) self.w = w @@ -570,7 +569,7 @@ def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_thres self.calculate_level_populations() self.calculate_tau_sobolev() - self.calculate_nlte_level_populations() + self.calculate_nlte_level_populations(coronal_case=coronal_case) if self.initialize: self.initialize = False @@ -774,6 +773,6 @@ def calculate_level_populations(self): else: level_populations = Series(level_populations, index=self.atom_data.levels.index) - self.level_populations.update(level_populations[~self.atom_data.nlte_mask]) + self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) From 22b1a81e7a269a6647c5f6cc6b49b15e4d65f948 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 13 Apr 2013 19:43:43 -0400 Subject: [PATCH 12/17] added classical nebular and coronal approximations. --- tardis/config_reader.py | 15 +++- tardis/model_radial_oned.py | 15 ++-- tardis/plasma.py | 134 ++++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/tardis/config_reader.py b/tardis/config_reader.py index f0b106270e5..5ad82463471 100644 --- a/tardis/config_reader.py +++ b/tardis/config_reader.py @@ -523,9 +523,20 @@ def from_yaml(cls, fname, args=None): nlte_section = yaml_dict.pop('nlte', None) if nlte_section is None: - config_dict['coronal_case'] = False + config_dict['coronal_approximation'] = False + config_dict['classical_nebular'] = False + else: + config_dict['coronal_approximation'] = nlte_section['coronal_approximation'] + config_dict['classical_nebular'] = nlte_section['classical_nebular'] + + if config_dict['coronal_approximation']: + logger.warn( + 'Using the coronal approximation - this is only for debuggging (j_blues=0.0 and beta_sobolevs=1.0)') + + if config_dict['classical_nebular']: + logger.warn('Using classical nebular is for debugging only (beta_sobolevs=1.0)') + - config_dict['coronal_case'] = nlte_section['coronal_case'] ##### spectrum section ###### spectrum_section = yaml_dict.pop('spectrum') diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index 145492d0e73..505b7073f47 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -232,7 +232,9 @@ def initialize_plasmas(self, plasma_class): ' be "lte" or "detailed"' % (self.plasma_type)) current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(current_t_rad, coronal_case=self.tardis_config.coronal_case) + current_plasma.update_radiationfield(current_t_rad, + coronal_approximation=self.tardis_config.coronal_approximation, + classical_nebular=self.tardis_config.classical_nebular) self.tau_sobolevs[i] = current_plasma.tau_sobolevs self.plasmas.append(current_plasma) @@ -254,7 +256,8 @@ def initialize_plasmas(self, plasma_class): current_plasma.set_j_blues(j_blues) current_plasma.update_radiationfield(current_t_rad, current_w, - coronal_case=self.tardis_config.coronal_case) + coronal_approximation=self.tardis_config.coronal_approximation, + classical_nebular=self.tardis_config.classical_nebular) self.tau_sobolevs[i] = current_plasma.tau_sobolevs @@ -328,7 +331,9 @@ def update_plasmas(self): current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(new_trad, coronal_case=self.tardis_config.coronal_case) + current_plasma.update_radiationfield(new_trad, + coronal_approximation=self.tardis_config.coronal_approximation, + classical_nebular=self.tardis_config.classical_nebular) self.tau_sobolevs[i] = current_plasma.tau_sobolevs elif self.plasma_type == 'nebular': @@ -347,7 +352,9 @@ def update_plasmas(self): current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(new_trad, new_ws, coronal_case=self.tardis_config.coronal_case) + current_plasma.update_radiationfield(new_trad, new_ws, + coronal_approximation=self.tardis_config.coronal_approximation, + classical_nebular=self.tardis_config.classical_nebular) self.tau_sobolevs[i] = current_plasma.tau_sobolevs if self.line_interaction_id in (1, 2): diff --git a/tardis/plasma.py b/tardis/plasma.py index ab8d8417f43..31ee57ca62f 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -128,7 +128,8 @@ def __init__(self, abundances, atom_data, time_explosion, max_ion_number=None, self.nlte_species = nlte_species - def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshold=0.05, coronal_case=False): + def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshold=0.05, coronal_approximation=False, + classical_nebular=False): """ This functions updates the radiation temperature `t_rad` and calculates the beta_rad Parameters. Then calculating :math:`g_e=\\left(\\frac{2 \\pi m_e k_\\textrm{B}T}{h^2}\\right)^{3/2}`. @@ -175,7 +176,8 @@ def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshol self.calculate_level_populations() self.calculate_tau_sobolev() - self.calculate_nlte_level_populations(coronal_case=coronal_case) + self.calculate_nlte_level_populations(coronal_approximation=coronal_approximation, + classical_nebular=classical_nebular) if self.initialize: self.initialize = False @@ -323,7 +325,7 @@ def calculate_level_populations(self): self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) - def calculate_nlte_level_populations(self, coronal_case=False): + def calculate_nlte_level_populations(self, coronal_approximation=False, classical_nebular=False): """ Calculating the NLTE level populations for specific ions @@ -334,13 +336,17 @@ def calculate_nlte_level_populations(self, coronal_case=False): macro_atom.calculate_beta_sobolev(self.tau_sobolevs, self.beta_sobolevs) - if coronal_case: + if coronal_approximation: beta_sobolevs = np.ones_like(self.beta_sobolevs) j_blues = np.zeros_like(self.j_blues) else: beta_sobolevs = self.beta_sobolevs j_blues = self.j_blues + if classical_nebular: + print "setting classical nebular = True" + beta_sobolevs[:] = 1.0 + for species in self.nlte_species: logger.info('Calculating rates for species %s', species) number_of_levels = self.level_populations.ix[species].size @@ -365,6 +371,8 @@ def calculate_nlte_level_populations(self, coronal_case=False): stimulated_emission_matrix.ravel()[r_lu_index] = 1 - (level_populations[lnu] * B_uls) / ( level_populations[lnl] * B_lus) + #stimulated_emission_matrix[stimulated_emission_matrix < 0.] = 0.0 + r_lu_matrix = np.zeros_like(r_ul_matrix) r_lu_matrix.ravel()[r_lu_index] = B_lus * j_blues[lines_index] * beta_sobolevs[lines_index] r_lu_matrix *= stimulated_emission_matrix @@ -385,8 +393,10 @@ def calculate_nlte_level_populations(self, coronal_case=False): self.level_populations.ix[species] = relative_level_populations * self.ion_number_density.ix[species] - 1 / 0 + return + +""" #Cleaning Level populations self.cleaned_levels.ix[species] = 0 self.lowest_cleaned_level = 100000 @@ -418,84 +428,86 @@ def calculate_nlte_level_populations(self, coronal_case=False): logger.warn('Number of cleaned levels very high %d of %d (zone id=%s, lowest_cleaned_level=%d)', self.cleaned_levels.ix[species], self.level_populations.ix[species].count(), self.zone_id, self.lowest_cleaned_level) +""" - def calculate_tau_sobolev(self): - """ - This function calculates the Sobolev optical depth :math:`\\tau_\\textrm{Sobolev}` - - +def calculate_tau_sobolev(self): + """ + This function calculates the Sobolev optical depth :math:`\\tau_\\textrm{Sobolev}` - .. math:: - C_\\textrm{Sobolev} = \\frac{\\pi e^2}{m_e c} - \\tau_\\textrm{Sobolev} = C_\\textrm{Sobolev}\, \\lambda\\, f_{\\textrm{lower}\\rightarrow\\textrm{upper}}\\, - t_\\textrm{explosion}\, N_\\textrm{lower} + .. math:: + C_\\textrm{Sobolev} = \\frac{\\pi e^2}{m_e c} + \\tau_\\textrm{Sobolev} = C_\\textrm{Sobolev}\, \\lambda\\, f_{\\textrm{lower}\\rightarrow\\textrm{upper}}\\, + t_\\textrm{explosion}\, N_\\textrm{lower} - .. note:: - Currently we're ignoring the term for stimulated emission: - :math:`(1 - \\frac{g_\\textrm{lower}}{g_\\textrm{upper}}\\frac{N_\\textrm{upper}}{N_\\textrm{lower}})` - """ + .. note:: + Currently we're ignoring the term for stimulated emission: + :math:`(1 - \\frac{g_\\textrm{lower}}{g_\\textrm{upper}}\\frac{N_\\textrm{upper}}{N_\\textrm{lower}})` - f_lu = self.atom_data.lines['f_lu'].values - wavelength = self.atom_data.lines['wavelength_cm'].values - n_lower = self.level_populations.values[self.atom_data.lines_lower2level_idx] - self.tau_sobolevs = sobolev_coefficient * f_lu * wavelength * self.time_explosion * n_lower - def calculate_bound_free(self): - """ - :return: - """ - nu_bins = range(1000, 10000, 1000) #TODO: get the binning from the input file. - try: - bf = np.zeros(len(self.atom_data.levels), len(self.atom_data.selected_atomic_numbers), len(nu_bins)) - except AttributeError: - logger.critical("Err creating the bf array.") + """ - phis = self.calculate_saha() - nnlevel = self.level_populations - for nu in nu_bins: - for i, (level_id, level) in enumerate(self.atom_data.levels.iterrows()): - atomic_number = level.name[0] - ion_number = level.name[1] - level_number = level.name[2] - sigma_bf_th = self.atom_data.ion_cx_th.ix[atomic_number, ion_number, level_number] - phi = phis.ix[atomic_number, ion_number] + f_lu = self.atom_data.lines['f_lu'].values + wavelength = self.atom_data.lines['wavelength_cm'].values + n_lower = self.level_populations.values[self.atom_data.lines_lower2level_idx] + self.tau_sobolevs = sobolev_coefficient * f_lu * wavelength * self.time_explosion * n_lower - def update_macro_atom(self): - """ - Updating the Macro Atom computations +def calculate_bound_free(self): + """ + :return: + """ + nu_bins = range(1000, 10000, 1000) #TODO: get the binning from the input file. + try: + bf = np.zeros(len(self.atom_data.levels), len(self.atom_data.selected_atomic_numbers), len(nu_bins)) + except AttributeError: + logger.critical("Err creating the bf array.") + + phis = self.calculate_saha() + nnlevel = self.level_populations + for nu in nu_bins: + for i, (level_id, level) in enumerate(self.atom_data.levels.iterrows()): + atomic_number = level.name[0] + ion_number = level.name[1] + level_number = level.name[2] + sigma_bf_th = self.atom_data.ion_cx_th.ix[atomic_number, ion_number, level_number] + phi = phis.ix[atomic_number, ion_number] + + +def update_macro_atom(self): + """ + Updating the Macro Atom computations - """ + """ - macro_tau_sobolevs = self.tau_sobolevs[self.atom_data.macro_atom_data['lines_idx'].values.astype(int)] + macro_tau_sobolevs = self.tau_sobolevs[self.atom_data.macro_atom_data['lines_idx'].values.astype(int)] - beta_sobolevs = np.zeros_like(macro_tau_sobolevs) + beta_sobolevs = np.zeros_like(macro_tau_sobolevs) - macro_atom.calculate_beta_sobolev(macro_tau_sobolevs, beta_sobolevs) + macro_atom.calculate_beta_sobolev(macro_tau_sobolevs, beta_sobolevs) - transition_probabilities = self.atom_data.macro_atom_data['transition_probability'] * beta_sobolevs + transition_probabilities = self.atom_data.macro_atom_data['transition_probability'] * beta_sobolevs - transition_up_filter = self.atom_data.macro_atom_data['transition_type'] == 1 + transition_up_filter = self.atom_data.macro_atom_data['transition_type'] == 1 - j_blues = self.j_blues[self.atom_data.macro_atom_data['lines_idx'].values[transition_up_filter.__array__()]] + j_blues = self.j_blues[self.atom_data.macro_atom_data['lines_idx'].values[transition_up_filter.__array__()]] - transition_probabilities[transition_up_filter.__array__()] *= j_blues + transition_probabilities[transition_up_filter.__array__()] *= j_blues - reference_levels = np.hstack((0, self.atom_data.macro_atom_references['count_total'].__array__().cumsum())) + reference_levels = np.hstack((0, self.atom_data.macro_atom_references['count_total'].__array__().cumsum())) - #Normalizing the probabilities - #TODO speedup possibility save the new blockreferences with 0 and last block - block_references = np.hstack((self.atom_data.macro_atom_references['block_references'], - len(self.atom_data.macro_atom_data))) - macro_atom.normalize_transition_probabilities(transition_probabilities, block_references) + #Normalizing the probabilities + #TODO speedup possibility save the new blockreferences with 0 and last block + block_references = np.hstack((self.atom_data.macro_atom_references['block_references'], + len(self.atom_data.macro_atom_data))) + macro_atom.normalize_transition_probabilities(transition_probabilities, block_references) - return transition_probabilities + return transition_probabilities class NebularPlasma(LTEPlasma): @@ -535,7 +547,8 @@ def __init__(self, abundances, atom_data, time_explosion, nlte_species=[], t_ele self.nlte_species = nlte_species - def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_threshold=0.05, coronal_case=False): + def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_threshold=0.05, + coronal_approximation=False, classical_nebular=True): BasePlasma.update_radiationfield(self, t_rad) self.w = w @@ -569,7 +582,8 @@ def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_thres self.calculate_level_populations() self.calculate_tau_sobolev() - self.calculate_nlte_level_populations(coronal_case=coronal_case) + self.calculate_nlte_level_populations(coronal_approximation=coronal_approximation, + classical_nebular=classical_nebular) if self.initialize: self.initialize = False From 1d05bb64a50c1bc8100caed05a871bf1b5b3c0fd Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 14 Apr 2013 12:56:14 -0400 Subject: [PATCH 13/17] changed the BasePlasma class initializer. Added a classmethod to read from an abundance dictionary --- tardis/plasma.py | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index 31ee57ca62f..928afbe1be2 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -64,16 +64,31 @@ class BasePlasma(object): maximum used ionization of atom used in the calculation (inclusive the number) """ - #TODO make density a astropy.quantity - def __init__(self, abundances, atom_data, time_explosion, density_unit='g/cm^3', max_ion_number=None, - use_macro_atom=False, zone_id=None): + + @classmethod + def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, t_electron=None, + use_macro_atom=False, zone_id=None, nlte_species=[]): + + + def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, + zone_id=None, nlte_species=[]): + self.number_density = number_density self.atom_data = atom_data - # self.selected_atoms = self.atom_data.selected_atoms - # self.selected_atomic_numbers = self.atom_data.selected_atomic_numbers - self.abundances = abundances self.initialize = True self.zone_id = zone_id self.time_explosion = time_explosion + self.update_radiationfield(t_rad, w) + self.nlte_species = nlte_species + + + @property + def t_rad(self): + return self._t_rad + + @t_rad.setter + def t_rad(self, value): + self._t_rad = value + self._beta_rad = 1 / (constants.k_B.cgs.value * self._t_rad) def validate_atom_data(self): @@ -83,7 +98,7 @@ def validate_atom_data(self): raise ValueError('AtomData incomplete missing') - def update_radiationfield(self, t_rad): + def update_radiationfield(self, t_rad, w): """ This functions updates the radiation temperature `t_rad` and calculates the beta_rad Parameters @@ -93,7 +108,7 @@ def update_radiationfield(self, t_rad): """ self.t_rad = t_rad - self.beta_rad = 1 / (constants.k_B.cgs.value * t_rad) + self.w = w class LTEPlasma(BasePlasma): @@ -119,9 +134,9 @@ class LTEPlasma(BasePlasma): """ - def __init__(self, abundances, atom_data, time_explosion, max_ion_number=None, - use_macro_atom=False, nlte_species=[], zone_id=None): - BasePlasma.__init__(self, abundances, atom_data, time_explosion, + def __init__(self, number_density, atom_data, time_explosion, max_ion_number=None, + use_macro_atom=False, zone_id=None): + BasePlasma.__init__(self, number_density, atom_data, time_explosion, max_ion_number=max_ion_number, use_macro_atom=use_macro_atom, zone_id=zone_id) self.ion_number_density = None @@ -160,7 +175,7 @@ def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshol phis = self.calculate_saha() #initialize electron density with the sum of number densities - electron_density = self.abundances.sum() + electron_density = self.number_density.sum() n_e_iterations = 0 @@ -292,7 +307,7 @@ def calculate_ionization_balance(self, phis, electron_density): current_phis = groups.values / electron_density phis_product = np.cumproduct(current_phis) - neutral_atom_density = self.abundances.ix[atomic_number] / (1 + np.sum(phis_product)) + neutral_atom_density = self.number_density.ix[atomic_number] / (1 + np.sum(phis_product)) ion_densities = [neutral_atom_density] + list(neutral_atom_density * phis_product) self.ion_number_density.ix[atomic_number] = ion_densities @@ -536,10 +551,11 @@ class NebularPlasma(LTEPlasma): """ - def __init__(self, abundances, atom_data, time_explosion, nlte_species=[], t_electron=None, density_unit='g/cm^3', + def __init__(self, number_density, atom_data, time_explosion, nlte_species=[], t_electron=None, + density_unit='g/cm^3', max_ion_number=None, use_macro_atom=False, zone_id=None): - BasePlasma.__init__(self, abundances, atom_data, time_explosion=time_explosion, density_unit=density_unit, + BasePlasma.__init__(self, number_density, atom_data, time_explosion=time_explosion, density_unit=density_unit, max_ion_number=max_ion_number, use_macro_atom=use_macro_atom, zone_id=zone_id) @@ -565,7 +581,7 @@ def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_thres phis = self.calculate_saha() #initialize electron density with the sum of number densities - electron_density = self.abundances.sum() + electron_density = self.number_density.sum() n_e_iterations = 0 From 9bc275cada9ed9b5bf6a39723c2e0c9bf2bd0d32 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 14 Apr 2013 13:42:46 -0400 Subject: [PATCH 14/17] changed the BasePlasma class initializer. Added a classmethod to read from an abundance dictionary --- tardis/plasma.py | 62 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index 928afbe1be2..ab0f1a030ad 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -1,17 +1,16 @@ #Calculations of the Plasma conditions -#import constants + import numpy as np import logging -from astropy import table, units, constants - -from pandas import DataFrame, Series, Index, lib as pdlib +from astropy import constants import pandas as pd - import macro_atom +from .config_reader import reformat_element_symbol + logger = logging.getLogger(__name__) -import pdb + @@ -68,6 +67,37 @@ class BasePlasma(object): @classmethod def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, zone_id=None, nlte_species=[]): + """ + :param cls: + :param t_rad: + :param w: + :param abundance: + :param density: + :param atom_data: + :param time_explosion: + :param t_electron: + :param use_macro_atom: + :param zone_id: + :param nlte_species: + :return: + """ + + abundance_sum = + for symbol in abundance: + symbol = reformat_element_symbol(symbol) + + if all(abundances[atomic_number].isnull()): + del abundances[atomic_number] + continue + else: + abundances[abundances[atomic_number].isnull()] == 0.0 + + #normalizing + abundances = abundances.divide(abundances.sum(axis=1), axis=0) + atom_mass = self.atom_data.atom_data.ix[abundances.columns].mass + number_densities = (abundances.mul(self.mean_densities, axis=0)).divide(atom_mass) + + return number_densities def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, @@ -238,8 +268,8 @@ def group_calculate_partition_function(group): self.partition_functions = self.atom_data.levels.groupby(level=['atomic_number', 'ion_number']).apply( group_calculate_partition_function) - self.atom_data.atom_ion_index = Series(np.arange(len(self.partition_functions)), - self.partition_functions.index) + self.atom_data.atom_ion_index = pd.Series(np.arange(len(self.partition_functions)), + self.partition_functions.index) self.atom_data.levels_index2atom_ion_index = self.atom_data.atom_ion_index.ix[ self.atom_data.levels.index.droplevel(2)].values else: @@ -276,7 +306,7 @@ def calculate_phis(group): phis = self.partition_functions.groupby(level='atomic_number').apply(calculate_phis) - phis = Series(phis.values, phis.index.droplevel(0)) + phis = pd.Series(phis.values, phis.index.droplevel(0)) phis *= self.ge * np.exp(-self.beta_rad * self.atom_data.ionization_data.ix[phis.index]['ionization_energy']) @@ -333,10 +363,10 @@ def calculate_level_populations(self): level_populations = (levels_g / Z) * ion_number_density * np.exp(-self.beta_rad * levels_energy) if self.initialize: - self.level_populations = Series(level_populations, index=self.atom_data.levels.index) + self.level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) else: - level_populations = Series(level_populations, index=self.atom_data.levels.index) + level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) @@ -642,8 +672,8 @@ def group_calculate_partition_function(group): self.partition_functions = self.atom_data.levels.groupby(level=['atomic_number', 'ion_number']).apply( group_calculate_partition_function) - self.atom_data.atom_ion_index = Series(np.arange(len(self.partition_functions)), - self.partition_functions.index) + self.atom_data.atom_ion_index = pd.Series(np.arange(len(self.partition_functions)), + self.partition_functions.index) self.atom_data.levels_index2atom_ion_index = self.atom_data.atom_ion_index.ix[ self.atom_data.levels.index.droplevel(2)].values else: @@ -691,7 +721,7 @@ def calculate_saha(self): delta = self.calculate_radiation_field_correction() - zeta = Series(index=phis.index) + zeta = pd.Series(index=phis.index) for idx in zeta.index: try: @@ -799,10 +829,10 @@ def calculate_level_populations(self): level_populations[~self.atom_data.levels['metastable']] *= self.w if self.initialize: - self.level_populations = Series(level_populations, index=self.atom_data.levels.index) + self.level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) else: - level_populations = Series(level_populations, index=self.atom_data.levels.index) + level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) From 750d6fa21b2a0243516dacb3878d103f826fa922 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 14 Apr 2013 14:49:05 -0400 Subject: [PATCH 15/17] finished class method for baseplasma --- tardis/plasma.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/tardis/plasma.py b/tardis/plasma.py index ab0f1a030ad..013962be0c9 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -82,22 +82,29 @@ def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, :return: """ - abundance_sum = + number_density = pd.Series(index=np.arange(1, 120)) for symbol in abundance: - symbol = reformat_element_symbol(symbol) + element_symbol = reformat_element_symbol(symbol) + if element_symbol not in atom_data.symbol2atomic_number: + raise ValueError('Element %s provided in config unknown' % element_symbol) - if all(abundances[atomic_number].isnull()): - del abundances[atomic_number] - continue - else: - abundances[abundances[atomic_number].isnull()] == 0.0 + z = atom_data.symbol2atomic_number[element_symbol] - #normalizing - abundances = abundances.divide(abundances.sum(axis=1), axis=0) - atom_mass = self.atom_data.atom_data.ix[abundances.columns].mass - number_densities = (abundances.mul(self.mean_densities, axis=0)).divide(atom_mass) + number_density.ix[z] = abundance[symbol] - return number_densities + number_density = number_density[~number_density.isnull()] + + abundance_sum = number_density.sum() + + if abs(abundance_sum - 1.) > 0.02: + logger.warning('Abundances do not sum up to 1 (%g)- normalizing', abundance_sum) + + number_density /= abundance_sum + + number_density *= density + number_density /= atom_data.atom_data.mass[number_density.index] + + return number_density def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, From 0398623d88edc46c68d0c4c2ee4f51c622618960 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 14 Apr 2013 21:54:18 -0400 Subject: [PATCH 16/17] restructured plasma --- .../plasma_plots/lte_ionization_balance.py | 4 +- .../nebular_ionization_balance_w.5.py | 4 +- .../nebular_ionization_balance_w1.py | 4 +- tardis/model_radial_oned.py | 2 +- tardis/plasma.py | 723 +++++++----------- 5 files changed, 301 insertions(+), 436 deletions(-) diff --git a/docs/plasma_doc/plasma_plots/lte_ionization_balance.py b/docs/plasma_doc/plasma_plots/lte_ionization_balance.py index 7d117dd3181..81f713a70e1 100644 --- a/docs/plasma_doc/plasma_plots/lte_ionization_balance.py +++ b/docs/plasma_doc/plasma_plots/lte_ionization_balance.py @@ -2,7 +2,7 @@ from tardis import atomic, plasma, model_radial_oned atom_data = atomic.AtomData.from_hdf5() -number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si':1}, density=1e-13) +number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si': 1}, density=1e-13) atom_data.prepare_atom_data(set(number_density.index.values.astype(int)), max_ion_number=3) lte_plasma = plasma.LTEPlasma(number_density, atom_data, max_ion_number=3) siI = [] @@ -13,7 +13,7 @@ for t_rad in t_rads: lte_plasma.update_radiationfield(t_rad) si_number_density = number_density.get_value(14, 'number_density') - ion_density = lte_plasma.ion_number_density / si_number_density + ion_density = lte_plasma.ion_populations / si_number_density siI.append(ion_density.get_value((14, 0))) siII.append(ion_density.get_value((14, 1))) siIII.append(ion_density.get_value((14, 2))) diff --git a/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w.5.py b/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w.5.py index a58cb17e2b9..aa3216d4e02 100644 --- a/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w.5.py +++ b/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w.5.py @@ -6,7 +6,7 @@ atom_fname = os.path.expanduser('~/.tardis/kurucz_atom.h5') atom_data = atomic.AtomData.from_hdf5(atom_fname, use_zeta_data=True) -number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si':1}, density=1e-13) +number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si': 1}, density=1e-13) atom_data.prepare_atom_data(set(number_density.index.values.astype(int)), max_ion_number=3) nebular_plasma = plasma.NebularPlasma(number_density, atom_data, max_ion_number=3) siI = [] @@ -17,7 +17,7 @@ for t_rad in t_rads: nebular_plasma.update_radiationfield(t_rad, w=.5) si_number_density = number_density.get_value(14, 'number_density') - ion_density = nebular_plasma.ion_number_density / si_number_density + ion_density = nebular_plasma.ion_populations / si_number_density siI.append(ion_density.get_value((14, 0))) siII.append(ion_density.get_value((14, 1))) siIII.append(ion_density.get_value((14, 2))) diff --git a/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w1.py b/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w1.py index de6fcacac33..3d30cc88688 100644 --- a/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w1.py +++ b/docs/plasma_doc/plasma_plots/nebular_ionization_balance_w1.py @@ -6,7 +6,7 @@ atom_fname = os.path.expanduser('~/.tardis/kurucz_atom.h5') atom_data = atomic.AtomData.from_hdf5(atom_fname, use_zeta_data=True) -number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si':1}, density=1e-13) +number_density = model_radial_oned.calculate_atom_number_densities(atom_data, {'Si': 1}, density=1e-13) atom_data.prepare_atom_data(set(number_density.index.values.astype(int)), max_ion_number=3) nebular_plasma = plasma.NebularPlasma(number_density, atom_data, max_ion_number=3) siI = [] @@ -17,7 +17,7 @@ for t_rad in t_rads: nebular_plasma.update_radiationfield(t_rad, w=1.) si_number_density = number_density.get_value(14, 'number_density') - ion_density = nebular_plasma.ion_number_density / si_number_density + ion_density = nebular_plasma.ion_populations / si_number_density siI.append(ion_density.get_value((14, 0))) siII.append(ion_density.get_value((14, 1))) siIII.append(ion_density.get_value((14, 2))) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index 505b7073f47..c52a744d887 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -275,7 +275,7 @@ def calculate_transition_probabilities(self): self.transition_probabilities = [] for current_plasma in self.plasmas: - self.transition_probabilities.append(current_plasma.update_macro_atom().values) + self.transition_probabilities.append(current_plasma.calculate_transition_probabilities().values) self.transition_probabilities = np.array(self.transition_probabilities, dtype=np.float64) diff --git a/tardis/plasma.py b/tardis/plasma.py index 013962be0c9..535b60f8596 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -66,7 +66,7 @@ class BasePlasma(object): @classmethod def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, t_electron=None, - use_macro_atom=False, zone_id=None, nlte_species=[]): + use_macro_atom=False, nlte_species=[], nlte_options={}, zone_id=None, saha_treatment='lte'): """ :param cls: :param t_rad: @@ -104,19 +104,40 @@ def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, number_density *= density number_density /= atom_data.atom_data.mass[number_density.index] - return number_density + return cls(t_rad=t_rad, w=w, number_density=number_density, atom_data=atom_data, time_explosion=time_explosion, + t_electron=t_electron, use_macro_atom=use_macro_atom, zone_id=zone_id, nlte_species=nlte_species, + nlte_options=nlte_options, saha_treatment=saha_treatment) - def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, - zone_id=None, nlte_species=[]): + def __init__(self, t_rad, w, number_density, atom_data, time_explosion, j_blues=None, t_electron=None, + use_macro_atom=False, nlte_species=[], nlte_options={}, zone_id=None, saha_treatment='lte'): self.number_density = number_density + self.electron_density = self.number_density.sum() + + if saha_treatment == 'lte': + self.calculate_saha = self.calculate_saha_lte + elif saha_treatment == 'nebular': + self.calculate_saha = self.calculate_saha_nebular + else: + raise ValueError('keyword "saha_treatment" can only be "lte" or "nebular" - %s chosen' % saha_treatment) + self.atom_data = atom_data self.initialize = True - self.zone_id = zone_id + self.t_rad = t_rad + self.w = w + self.t_electron = t_electron + + self.set_j_blues(j_blues) + self.time_explosion = time_explosion - self.update_radiationfield(t_rad, w) + self.nlte_species = nlte_species + self.nlte_options = nlte_options + self.zone_id = zone_id + + self.update_radiationfield(self.t_rad, self.w) + #Properties @property def t_rad(self): @@ -125,63 +146,30 @@ def t_rad(self): @t_rad.setter def t_rad(self, value): self._t_rad = value - self._beta_rad = 1 / (constants.k_B.cgs.value * self._t_rad) - - - def validate_atom_data(self): - required_attributes = ['lines', 'levels'] - for attribute in required_attributes: - if not hasattr(self.atom_data, attribute): - raise ValueError('AtomData incomplete missing') - - - def update_radiationfield(self, t_rad, w): - """ - This functions updates the radiation temperature `t_rad` and calculates the beta_rad - Parameters - ---------- - t_rad : float - - - """ - self.t_rad = t_rad - self.w = w - - -class LTEPlasma(BasePlasma): - """ - Model for BasePlasma using a local thermodynamic equilibrium approximation. - - Parameters - ---------- - - abundances : `~dict` - A dictionary with the abundances for each element - - t_rad : `~float` - Temperature in Kelvin for the plasma - - density : `float` - density in g/cm^3 - - .. warning:: - Instead of g/cm^ will later use the keyword `density_unit` as unit + self.beta_rad = 1 / (constants.k_B.cgs.value * self._t_rad) + self.ge = ((2 * np.pi * constants.m_e.cgs.value / self.beta_rad) / (constants.h.cgs.value ** 2)) ** 1.5 - atom_data : `~tardis.atomic.AtomData`-object + @property + def t_electron(self): + if self._t_electron is None: + return self.t_rad * self.link_t_rad_electron + else: + return self._t_electron - """ + @t_electron.setter + def t_electron(self, value): + if value is None: + self.link_t_rad_electron = 0.9 + self._t_electron = None + else: + self._t_electron = self.link_t_rad_electron * self.trad - def __init__(self, number_density, atom_data, time_explosion, max_ion_number=None, - use_macro_atom=False, zone_id=None): - BasePlasma.__init__(self, number_density, atom_data, time_explosion, - max_ion_number=max_ion_number, use_macro_atom=use_macro_atom, zone_id=zone_id) + self.beta_electron = 1 / (constants.k_B.cgs.value * self.t_electron) - self.ion_number_density = None - self.nlte_species = nlte_species + #Functions - def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshold=0.05, coronal_approximation=False, - classical_nebular=False): + def update_radiationfield(self, t_rad, w, n_e_convergence_threshold=0.05): """ This functions updates the radiation temperature `t_rad` and calculates the beta_rad Parameters. Then calculating :math:`g_e=\\left(\\frac{2 \\pi m_e k_\\textrm{B}T}{h^2}\\right)^{3/2}`. @@ -197,51 +185,47 @@ def update_radiationfield(self, t_rad, t_electron=None, n_e_convergence_threshol ionization balance. """ - BasePlasma.update_radiationfield(self, t_rad) - if t_electron is None: - self.t_electron = t_rad * 0.9 - else: - self.t_electron = t_electron + self.t_rad = t_rad + self.w = w + + self.calculate_partition_functions() - self.calculate_partition_functions(initialize=self.initialize) - self.ge = ((2 * np.pi * constants.m_e.cgs.value / self.beta_rad) / (constants.h.cgs.value ** 2)) ** 1.5 #Calculate the Saha ionization balance fractions phis = self.calculate_saha() #initialize electron density with the sum of number densities - electron_density = self.number_density.sum() - n_e_iterations = 0 while True: - self.calculate_ionization_balance(phis, electron_density) - ion_numbers = np.array([item[1] for item in self.ion_number_density.index]) - new_electron_density = np.sum(self.ion_number_density.values * ion_numbers) + self.calculate_ion_populations(phis) + ion_numbers = np.array([item[1] for item in self.ion_populations.index]) + new_electron_density = np.sum(self.ion_populations.values * ion_numbers) n_e_iterations += 1 - if abs(new_electron_density - electron_density) / electron_density < n_e_convergence_threshold: break - electron_density = 0.5 * (new_electron_density + electron_density) + if abs( + new_electron_density - self.electron_density) / self.electron_density < n_e_convergence_threshold: break + self.electron_density = 0.5 * (new_electron_density + self.electron_density) self.electron_density = new_electron_density logger.debug('Took %d iterations to converge on electron density' % n_e_iterations) self.calculate_level_populations() self.calculate_tau_sobolev() - self.calculate_nlte_level_populations(coronal_approximation=coronal_approximation, - classical_nebular=classical_nebular) + if self.nlte_species != []: + self.calculate_nlte_level_populations() if self.initialize: self.initialize = False - def set_j_blues(self, j_blues=None): - if j_blues is None: - self.j_blues = intensity_black_body(self.atom_data.lines['nu'].values, self.t_rad) - else: - self.j_blues = j_blues + def validate_atom_data(self): + required_attributes = ['lines', 'levels'] + for attribute in required_attributes: + if not hasattr(self.atom_data, attribute): + raise ValueError('AtomData incomplete missing') - def calculate_partition_functions(self, initialize=False): + def calculate_partition_functions(self): """ Calculate partition functions for the ions using the following formula, where :math:`i` is the atomic_number, :math:`j` is the ion_number and :math:`k` is the level number. @@ -292,8 +276,7 @@ def group_calculate_partition_function(group): else: self.partition_functions.ix[species] = np.sum(group['g'] * np.exp(-group['energy'] * self.beta_rad)) - - def calculate_saha(self): + def calculate_saha_lte(self): """ Calculating the ionization equilibrium using the Saha equation, where i is atomic number, j is the ion_number, :math:`n_e` is the electron density, :math:`Z_{i, j}` are the partition functions @@ -319,8 +302,117 @@ def calculate_phis(group): return phis + def calculate_saha_nebular(self): + """ + Calculating the ionization equilibrium using the Saha equation, where i is atomic number, + j is the ion_number, :math:`n_e` is the electron density, :math:`Z_{i, j}` are the partition functions + and :math:`\chi` is the ionization energy. For the `NebularPlasma` we first calculate the + ionization balance assuming LTE conditions (:math:`\\Phi_{i, j}(\\textrm{LTE})`) and use factors to more accurately + describe the plasma. The two important factors are :math:`\\zeta` - a correction factor to take into account + ionizations from excited states. The second factor is :math:`\\delta` , adjusting the ionization balance for the fact that + there's more line blanketing in the blue. + + The :math:`\\zeta` factor for different temperatures is read in to the `~tardis.atomic.NebularAtomData` and then + interpolated for the current temperature. + + The :math:`\\delta` factor is calculated with :method:`calculate_radiation_field_correction`. + + Finally the ionization balance is adjusted (as equation 14 in :cite:`1993A&A...279..447M`): + + .. math:: + + + \\Phi_{i,j} =& \\frac{N_{i, j+1} n_e}{N_{i, j}} \\\\ + + \\Phi_{i, j} =& W \\times[\\delta \\zeta + W ( 1 - \\zeta)] \\left(\\frac{T_\\textrm{e}}{T_\\textrm{R}}\\right)^{1/2} + \\Phi_{i, j}(\\textrm{LTE}) + + """ + + phis = super(NebularPlasma, self).calculate_saha() + + delta = self.calculate_radiation_field_correction() + + zeta = pd.Series(index=phis.index) + + for idx in zeta.index: + try: + current_zeta = self.atom_data.zeta_data[idx](self.t_rad) + except KeyError: + current_zeta = 1.0 + + zeta.ix[idx] = current_zeta + + phis *= self.w * (delta.ix[phis.index] * zeta + self.w * (1 - zeta)) * \ + (self.t_electron / self.t_rad) ** .5 + + return phis + + def calculate_radiation_field_correction(self, departure_coefficient=None, + chi_threshold_species=(20, 1)): + """ + Calculating radiation field correction factors according to Mazzali & Lucy 1993 (:cite:`1993A&A...279..447M`; henceforth ML93) + + + In ML93 the radiation field correction factor is denoted as :math:`\\delta` and is calculated in Formula 15 & 20 + + The radiation correction factor changes according to a ionization energy threshold :math:`\\chi_\\textrm{T}` + and the species ionization threshold (from the ground state) :math:`\\chi_0`. + + For :math:`\\chi_\\textrm{T} \\ge \\chi_0` + + .. math:: + \\delta = \\frac{T_\\textrm{e}}{b_1 W T_\\textrm{R}} \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - + \\frac{\\chi_0}{k T_\\textrm{e}}) + + For :math:`\\chi_\\textrm{T} < \\chi_0` + + .. math:: + \\delta = 1 - \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - \\frac{\\chi_0}{k T_\\textrm{R}}) + \\frac{T_\\textrm{e}}{b_1 W T_\\textrm{R}} \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - + \\frac{\\chi_0}{k T_\\textrm{e}}), + + where :math:`T_\\textrm{R}` is the radiation field Temperature, :math:`T_\\textrm{e}` is the electron temperature and W is the + dilution factor. + + Parameters + ---------- + phi_table : `~astropy.table.Table` + a table containing the field 'atomic_number', 'ion_number', 'phi' + + departure_coefficient : `~float` or `~None`, optional + departure coefficient (:math:`b_1` in ML93) For the default (`None`) it is set to 1/W. + + chi_threshold_species : `~tuple`, optional + This describes which ionization energy to use for the threshold. Default is Calcium II + (1044 Angstrom; useful for Type Ia) + For Type II supernovae use Lyman break (912 Angstrom) or (1,1) as the tuple + + Returns + ------- + + This function adds a field 'delta' to the phi table given to the function + + """ + #factor delta ML 1993 + if departure_coefficient is None: + departure_coefficient = 1 / float(self.w) + + chi_threshold = self.atom_data.ionization_data['ionization_energy'].ix[chi_threshold_species] + + radiation_field_correction = (self.t_electron / (departure_coefficient * self.w * self.t_rad)) * \ + np.exp(self.beta_rad * chi_threshold - self.beta_electron * + self.atom_data.ionization_data['ionization_energy']) + + less_than_chi_threshold = self.atom_data.ionization_data['ionization_energy'] < chi_threshold + + radiation_field_correction[less_than_chi_threshold] += 1 - \ + np.exp(self.beta_rad * chi_threshold - self.beta_rad * + self.atom_data.ionization_data[ + less_than_chi_threshold]['ionization_energy']) + + return radiation_field_correction - def calculate_ionization_balance(self, phis, electron_density): + def calculate_ion_populations(self, phis): """ Calculate the ionization balance @@ -335,40 +427,46 @@ def calculate_ionization_balance(self, phis, electron_density): """ - - if self.ion_number_density is None: - self.ion_number_density = pd.Series(index=self.partition_functions.index.copy()) + #TODO see if self.ion_populations is None is needed (first class should be enough) + if not hasattr(self, 'ion_populations') or self.ion_populations is None: + self.ion_populations = pd.Series(index=self.partition_functions.index.copy()) self.cleaned_levels = pd.Series(index=self.partition_functions.index.copy()) for atomic_number, groups in phis.groupby(level='atomic_number'): - current_phis = groups.values / electron_density + current_phis = groups.values / self.electron_density phis_product = np.cumproduct(current_phis) neutral_atom_density = self.number_density.ix[atomic_number] / (1 + np.sum(phis_product)) ion_densities = [neutral_atom_density] + list(neutral_atom_density * phis_product) - self.ion_number_density.ix[atomic_number] = ion_densities - + self.ion_populations.ix[atomic_number] = ion_densities def calculate_level_populations(self): """ - Calculate the level populations and storing in self.level_populations table. + Calculate the level populations and putting them in the column 'number-density' of the self.levels table. :math:`N` denotes the ion number density calculated with `calculate_ionization_balance`, i is the atomic number, - j is the ion number and k is the level number. + j is the ion number and k is the level number. For non-metastable levels we add the dilution factor (W) to the calculation. .. math:: - \\frac{g_k}{Z_{i,j}} \\times N_{i, j} \\times e^{-\\beta_\\textrm{rad} \\times E_k} - """ + N_{i, j, k}(\\textrm{metastable}) &= \\frac{g_k}{Z_{i, j}}\\times N_{i, j} \\times e^{-\\beta_\\textrm{rad} E_k} \\\\ + N_{i, j, k}(\\textrm{not metastable}) &= W\\frac{g_k}{Z_{i, j}}\\times N_{i, j} \\times e^{-\\beta_\\textrm{rad} E_k} \\\\ + + This function updates the 'number_density' column on the levels table (or adds it if non-existing) + """ Z = self.partition_functions.values[self.atom_data.levels_index2atom_ion_index] - ion_number_density = self.ion_number_density.values[self.atom_data.levels_index2atom_ion_index] + ion_number_density = self.ion_populations.values[self.atom_data.levels_index2atom_ion_index] levels_g = self.atom_data.levels['g'].values levels_energy = self.atom_data.levels['energy'].values level_populations = (levels_g / Z) * ion_number_density * np.exp(-self.beta_rad * levels_energy) + + #only change between lte plasma and nebular + level_populations[~self.atom_data.levels['metastable']] *= self.w + if self.initialize: self.level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) @@ -377,7 +475,33 @@ def calculate_level_populations(self): self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) - def calculate_nlte_level_populations(self, coronal_approximation=False, classical_nebular=False): + def calculate_tau_sobolev(self): + """ + This function calculates the Sobolev optical depth :math:`\\tau_\\textrm{Sobolev}` + + + + .. math:: + C_\\textrm{Sobolev} = \\frac{\\pi e^2}{m_e c} + + \\tau_\\textrm{Sobolev} = C_\\textrm{Sobolev}\, \\lambda\\, f_{\\textrm{lower}\\rightarrow\\textrm{upper}}\\, + t_\\textrm{explosion}\, N_\\textrm{lower} + + + + .. note:: + Currently we're ignoring the term for stimulated emission: + :math:`(1 - \\frac{g_\\textrm{lower}}{g_\\textrm{upper}}\\frac{N_\\textrm{upper}}{N_\\textrm{lower}})` + + + """ + + f_lu = self.atom_data.lines['f_lu'].values + wavelength = self.atom_data.lines['wavelength_cm'].values + n_lower = self.level_populations.values[self.atom_data.lines_lower2level_idx] + self.tau_sobolevs = sobolev_coefficient * f_lu * wavelength * self.time_explosion * n_lower + + def calculate_nlte_level_populations(self): """ Calculating the NLTE level populations for specific ions @@ -388,14 +512,14 @@ def calculate_nlte_level_populations(self, coronal_approximation=False, classica macro_atom.calculate_beta_sobolev(self.tau_sobolevs, self.beta_sobolevs) - if coronal_approximation: + if self.nlte_options.get('coronal_approximation', False): beta_sobolevs = np.ones_like(self.beta_sobolevs) j_blues = np.zeros_like(self.j_blues) else: beta_sobolevs = self.beta_sobolevs j_blues = self.j_blues - if classical_nebular: + if self.nlte_options.get('classical_nebular', False): print "setting classical nebular = True" beta_sobolevs[:] = 1.0 @@ -443,126 +567,105 @@ def calculate_nlte_level_populations(self, coronal_approximation=False, classica x[0] = 1.0 relative_level_populations = np.linalg.solve(rates_matrix, x) - self.level_populations.ix[species] = relative_level_populations * self.ion_number_density.ix[species] + self.level_populations.ix[species] = relative_level_populations * self.ion_populations.ix[species] return + def calculate_transition_probabilities(self): + """ + Updating the Macro Atom computations + """ -""" - #Cleaning Level populations - self.cleaned_levels.ix[species] = 0 - self.lowest_cleaned_level = 100000 - for i in xrange(1, number_of_levels): - n_upper = self.level_populations.ix[species][i] - n_lower = self.level_populations.ix[species][i - 1] - - g_upper = float(self.atom_data.levels.ix[species]['g'][i]) - g_lower = float(self.atom_data.levels.ix[species]['g'][i - 1]) - - current_stim_ems = (n_upper / n_lower) * (g_lower / g_upper) - - if current_stim_ems > 1.: - self.level_populations.ix[species[0], species[1], i] = (1 - 1e-12) * (g_upper / g_lower) * n_lower - self.cleaned_levels.ix[species] += 1 - self.lowest_cleaned_level = min(i, self.lowest_cleaned_level) - #### After cleaning check if the normalization is good: - - if abs((self.level_populations.ix[species].sum() / self.ion_number_density.ix[species] - 1)) > 0.02: - logger.warn("NLTE populations (after cleaning) does not sum up to 1 within 2 percent " - "(%.2f / 1.0 - zone id = %s, lowest_cleaned_level=%d)", - ((1 - self.level_populations.ix[species].sum() / self.ion_number_density.ix[species])), - self.zone_id, self.lowest_cleaned_level) - - logger.debug('Number of cleaned levels %d of %d (zone id =%s)', self.cleaned_levels.ix[species], - self.level_populations.ix[species].count(), self.zone_id) + macro_tau_sobolevs = self.tau_sobolevs[self.atom_data.macro_atom_data['lines_idx'].values.astype(int)] - if float(self.cleaned_levels.ix[species]) / self.level_populations.ix[species].count() > 0.5: - logger.warn('Number of cleaned levels very high %d of %d (zone id=%s, lowest_cleaned_level=%d)', - self.cleaned_levels.ix[species], - self.level_populations.ix[species].count(), self.zone_id, self.lowest_cleaned_level) -""" + beta_sobolevs = np.zeros_like(macro_tau_sobolevs) + macro_atom.calculate_beta_sobolev(macro_tau_sobolevs, beta_sobolevs) -def calculate_tau_sobolev(self): - """ - This function calculates the Sobolev optical depth :math:`\\tau_\\textrm{Sobolev}` + transition_probabilities = self.atom_data.macro_atom_data['transition_probability'] * beta_sobolevs + transition_up_filter = self.atom_data.macro_atom_data['transition_type'] == 1 + j_blues = self.j_blues[self.atom_data.macro_atom_data['lines_idx'].values[transition_up_filter.__array__()]] - .. math:: - C_\\textrm{Sobolev} = \\frac{\\pi e^2}{m_e c} + transition_probabilities[transition_up_filter.__array__()] *= j_blues - \\tau_\\textrm{Sobolev} = C_\\textrm{Sobolev}\, \\lambda\\, f_{\\textrm{lower}\\rightarrow\\textrm{upper}}\\, - t_\\textrm{explosion}\, N_\\textrm{lower} + reference_levels = np.hstack((0, self.atom_data.macro_atom_references['count_total'].__array__().cumsum())) + #Normalizing the probabilities + #TODO speedup possibility save the new blockreferences with 0 and last block + block_references = np.hstack((self.atom_data.macro_atom_references['block_references'], + len(self.atom_data.macro_atom_data))) + macro_atom.normalize_transition_probabilities(transition_probabilities, block_references) + return transition_probabilities - .. note:: - Currently we're ignoring the term for stimulated emission: - :math:`(1 - \\frac{g_\\textrm{lower}}{g_\\textrm{upper}}\\frac{N_\\textrm{upper}}{N_\\textrm{lower}})` - + def set_j_blues(self, j_blues=None): + if j_blues is None: + self.j_blues = self.w * intensity_black_body(self.atom_data.lines['nu'].values, self.t_rad) + else: + self.j_blues = j_blues - """ + def calculate_bound_free(self): + """ + :return: + """ + nu_bins = range(1000, 10000, 1000) #TODO: get the binning from the input file. + try: + bf = np.zeros(len(self.atom_data.levels), len(self.atom_data.selected_atomic_numbers), len(nu_bins)) + except AttributeError: + logger.critical("Err creating the bf array.") - f_lu = self.atom_data.lines['f_lu'].values - wavelength = self.atom_data.lines['wavelength_cm'].values - n_lower = self.level_populations.values[self.atom_data.lines_lower2level_idx] - self.tau_sobolevs = sobolev_coefficient * f_lu * wavelength * self.time_explosion * n_lower + phis = self.calculate_saha() + nnlevel = self.level_populations + for nu in nu_bins: + for i, (level_id, level) in enumerate(self.atom_data.levels.iterrows()): + atomic_number = level.name[0] + ion_number = level.name[1] + level_number = level.name[2] + sigma_bf_th = self.atom_data.ion_cx_th.ix[atomic_number, ion_number, level_number] + phi = phis.ix[atomic_number, ion_number] -def calculate_bound_free(self): - """ - :return: - """ - nu_bins = range(1000, 10000, 1000) #TODO: get the binning from the input file. - try: - bf = np.zeros(len(self.atom_data.levels), len(self.atom_data.selected_atomic_numbers), len(nu_bins)) - except AttributeError: - logger.critical("Err creating the bf array.") - - phis = self.calculate_saha() - nnlevel = self.level_populations - for nu in nu_bins: - for i, (level_id, level) in enumerate(self.atom_data.levels.iterrows()): - atomic_number = level.name[0] - ion_number = level.name[1] - level_number = level.name[2] - sigma_bf_th = self.atom_data.ion_cx_th.ix[atomic_number, ion_number, level_number] - phi = phis.ix[atomic_number, ion_number] - - -def update_macro_atom(self): - """ - Updating the Macro Atom computations - +class LTEPlasma(BasePlasma): """ + Model for BasePlasma using a local thermodynamic equilibrium approximation. - macro_tau_sobolevs = self.tau_sobolevs[self.atom_data.macro_atom_data['lines_idx'].values.astype(int)] - - beta_sobolevs = np.zeros_like(macro_tau_sobolevs) + Parameters + ---------- - macro_atom.calculate_beta_sobolev(macro_tau_sobolevs, beta_sobolevs) + abundances : `~dict` + A dictionary with the abundances for each element - transition_probabilities = self.atom_data.macro_atom_data['transition_probability'] * beta_sobolevs + t_rad : `~float` + Temperature in Kelvin for the plasma - transition_up_filter = self.atom_data.macro_atom_data['transition_type'] == 1 + density : `float` + density in g/cm^3 - j_blues = self.j_blues[self.atom_data.macro_atom_data['lines_idx'].values[transition_up_filter.__array__()]] + .. warning:: + Instead of g/cm^ will later use the keyword `density_unit` as unit - transition_probabilities[transition_up_filter.__array__()] *= j_blues + atom_data : `~tardis.atomic.AtomData`-object - reference_levels = np.hstack((0, self.atom_data.macro_atom_references['count_total'].__array__().cumsum())) + """ - #Normalizing the probabilities - #TODO speedup possibility save the new blockreferences with 0 and last block - block_references = np.hstack((self.atom_data.macro_atom_references['block_references'], - len(self.atom_data.macro_atom_data))) - macro_atom.normalize_transition_probabilities(transition_probabilities, block_references) + @classmethod + def from_abundance(cls, t_rad, abundance, density, atom_data, time_explosion, t_electron=None, + use_macro_atom=False, nlte_species=[], nlte_options={}, zone_id=None): + return super(LTEPlasma, cls).from_abundance(t_rad, 1., abundance, density, atom_data, time_explosion, + t_electron=t_electron, use_macro_atom=use_macro_atom, + nlte_species=nlte_species, nlte_options=nlte_options, + zone_id=zone_id) - return transition_probabilities + def __init__(self, t_rad, number_density, atom_data, time_explosion, w=1., t_electron=None, use_macro_atom=False, + nlte_species=[], nlte_options=None, zone_id=None, saha_treatment='lte'): + super(LTEPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, t_electron=t_electron, + use_macro_atom=use_macro_atom, nlte_species=nlte_species, + nlte_options=nlte_options, zone_id=zone_id, saha_treatment=saha_treatment) -class NebularPlasma(LTEPlasma): +class NebularPlasma(BasePlasma): """ Model for BasePlasma using the Nebular approximation @@ -588,258 +691,20 @@ class NebularPlasma(LTEPlasma): """ - def __init__(self, number_density, atom_data, time_explosion, nlte_species=[], t_electron=None, - density_unit='g/cm^3', - max_ion_number=None, - use_macro_atom=False, zone_id=None): - BasePlasma.__init__(self, number_density, atom_data, time_explosion=time_explosion, density_unit=density_unit, - max_ion_number=max_ion_number, - use_macro_atom=use_macro_atom, zone_id=zone_id) - - self.ion_number_density = None - self.nlte_species = nlte_species - - - def update_radiationfield(self, t_rad, w, t_electron=None, n_e_convergence_threshold=0.05, - coronal_approximation=False, classical_nebular=True): - BasePlasma.update_radiationfield(self, t_rad) - - self.w = w - - if t_electron is None: - self.t_electron = 0.9 * self.t_rad - - self.beta_electron = 1 / (self.t_electron * constants.k_B.cgs.value) - - self.calculate_partition_functions() - - self.ge = ((2 * np.pi * constants.m_e.cgs.value / self.beta_rad) / (constants.h.cgs.value ** 2)) ** 1.5 - #Calculate the Saha ionization balance fractions - phis = self.calculate_saha() - - #initialize electron density with the sum of number densities - electron_density = self.number_density.sum() - - n_e_iterations = 0 - - while True: - self.calculate_ionization_balance(phis, electron_density) - ion_numbers = np.array([item[1] for item in self.ion_number_density.index]) - new_electron_density = np.sum(self.ion_number_density.values * ion_numbers) - n_e_iterations += 1 - if abs(new_electron_density - electron_density) / electron_density < n_e_convergence_threshold: break - electron_density = 0.5 * (new_electron_density + electron_density) - - self.electron_density = new_electron_density - logger.debug('Took %d iterations to converge on electron density' % n_e_iterations) - - self.calculate_level_populations() - self.calculate_tau_sobolev() - self.calculate_nlte_level_populations(coronal_approximation=coronal_approximation, - classical_nebular=classical_nebular) - - if self.initialize: - self.initialize = False - - def set_j_blues(self, j_blues=None): - if j_blues is None: - self.j_blues = self.w * intensity_black_body(self.atom_data.lines['nu'].values, self.t_rad) - else: - self.j_blues = j_blues - - def calculate_partition_functions(self): - """ - Calculate partition functions for the ions using the following formula, where - :math:`i` is the atomic_number, :math:`j` is the ion_number and :math:`k` is the level number. - - .. math:: - Z_{i,j} = \\underbrace{\\sum_{k=0}^{max(k)_{i,j}} g_k \\times e^{-E_k / (k_\\textrm{b} T)}}_\\textrm{metastable levels} + - \\underbrace{W\\times\\sum_{k=0}^{max(k)_{i,j}} g_k \\times e^{-E_k / (k_\\textrm{b} T)}}_\\textrm{non-metastable levels} - - - - Returns - ------- - - partition_functions : `~astropy.table.Table` - with fields atomic_number, ion_number, partition_function - - """ - - def group_calculate_partition_function(group): - metastable = group['metastable'] - meta_z = np.sum(group['g'][metastable] * np.exp(-group['energy'][metastable] * self.beta_rad)) - non_meta_z = np.sum(group['g'][~metastable] * np.exp(-group['energy'][~metastable] * self.beta_rad)) - return meta_z + self.w * non_meta_z - - - if self.initialize: - logger.debug('Initializing the partition functions and indices') - - self.partition_functions = self.atom_data.levels.groupby(level=['atomic_number', 'ion_number']).apply( - group_calculate_partition_function) - - self.atom_data.atom_ion_index = pd.Series(np.arange(len(self.partition_functions)), - self.partition_functions.index) - self.atom_data.levels_index2atom_ion_index = self.atom_data.atom_ion_index.ix[ - self.atom_data.levels.index.droplevel(2)].values - else: - if not hasattr(self, 'partition_functions'): - raise ValueError("Called calculate partition_functions without initializing at least once") - - for species, group in self.atom_data.levels.groupby(level=['atomic_number', 'ion_number']): - if species in self.nlte_species: - ground_level_population = self.level_populations[species][0] - self.partition_functions.ix[species] = self.atom_data.levels.ix[species]['g'][0] * \ - np.sum(self.level_populations[ - species].values / ground_level_population) - else: - self.partition_functions.ix[species] = np.sum(group['g'] * np.exp(-group['energy'] * self.beta_rad)) - - - def calculate_saha(self): - """ - Calculating the ionization equilibrium using the Saha equation, where i is atomic number, - j is the ion_number, :math:`n_e` is the electron density, :math:`Z_{i, j}` are the partition functions - and :math:`\chi` is the ionization energy. For the `NebularPlasma` we first calculate the - ionization balance assuming LTE conditions (:math:`\\Phi_{i, j}(\\textrm{LTE})`) and use factors to more accurately - describe the plasma. The two important factors are :math:`\\zeta` - a correction factor to take into account - ionizations from excited states. The second factor is :math:`\\delta` , adjusting the ionization balance for the fact that - there's more line blanketing in the blue. - - The :math:`\\zeta` factor for different temperatures is read in to the `~tardis.atomic.NebularAtomData` and then - interpolated for the current temperature. - - The :math:`\\delta` factor is calculated with :method:`calculate_radiation_field_correction`. - - Finally the ionization balance is adjusted (as equation 14 in :cite:`1993A&A...279..447M`): - - .. math:: - - - \\Phi_{i,j} =& \\frac{N_{i, j+1} n_e}{N_{i, j}} \\\\ - - \\Phi_{i, j} =& W \\times[\\delta \\zeta + W ( 1 - \\zeta)] \\left(\\frac{T_\\textrm{e}}{T_\\textrm{R}}\\right)^{1/2} - \\Phi_{i, j}(\\textrm{LTE}) - - """ - - phis = super(NebularPlasma, self).calculate_saha() - - delta = self.calculate_radiation_field_correction() - - zeta = pd.Series(index=phis.index) - - for idx in zeta.index: - try: - current_zeta = self.atom_data.zeta_data[idx](self.t_rad) - except KeyError: - current_zeta = 1.0 - - zeta.ix[idx] = current_zeta - - phis *= self.w * (delta.ix[phis.index] * zeta + self.w * (1 - zeta)) * \ - (self.t_electron / self.t_rad) ** .5 - - return phis - - - def calculate_radiation_field_correction(self, departure_coefficient=None, - chi_threshold_species=(20, 1)): - """ - Calculating radiation field correction factors according to Mazzali & Lucy 1993 (:cite:`1993A&A...279..447M`; henceforth ML93) - - - In ML93 the radiation field correction factor is denoted as :math:`\\delta` and is calculated in Formula 15 & 20 - - The radiation correction factor changes according to a ionization energy threshold :math:`\\chi_\\textrm{T}` - and the species ionization threshold (from the ground state) :math:`\\chi_0`. - - For :math:`\\chi_\\textrm{T} \\ge \\chi_0` - - .. math:: - \\delta = \\frac{T_\\textrm{e}}{b_1 W T_\\textrm{R}} \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - - \\frac{\\chi_0}{k T_\\textrm{e}}) - - For :math:`\\chi_\\textrm{T} < \\chi_0` - - .. math:: - \\delta = 1 - \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - \\frac{\\chi_0}{k T_\\textrm{R}}) + \\frac{T_\\textrm{e}}{b_1 W T_\\textrm{R}} \\exp(\\frac{\\chi_\\textrm{T}}{k T_\\textrm{R}} - - \\frac{\\chi_0}{k T_\\textrm{e}}), - - where :math:`T_\\textrm{R}` is the radiation field Temperature, :math:`T_\\textrm{e}` is the electron temperature and W is the - dilution factor. - - Parameters - ---------- - phi_table : `~astropy.table.Table` - a table containing the field 'atomic_number', 'ion_number', 'phi' - - departure_coefficient : `~float` or `~None`, optional - departure coefficient (:math:`b_1` in ML93) For the default (`None`) it is set to 1/W. - - chi_threshold_species : `~tuple`, optional - This describes which ionization energy to use for the threshold. Default is Calcium II - (1044 Angstrom; useful for Type Ia) - For Type II supernovae use Lyman break (912 Angstrom) or (1,1) as the tuple - - Returns - ------- - - This function adds a field 'delta' to the phi table given to the function - - """ - #factor delta ML 1993 - if departure_coefficient is None: - departure_coefficient = 1 / float(self.w) - - chi_threshold = self.atom_data.ionization_data['ionization_energy'].ix[chi_threshold_species] - - radiation_field_correction = (self.t_electron / (departure_coefficient * self.w * self.t_rad)) * \ - np.exp(self.beta_rad * chi_threshold - self.beta_electron * - self.atom_data.ionization_data['ionization_energy']) - - less_than_chi_threshold = self.atom_data.ionization_data['ionization_energy'] < chi_threshold - - radiation_field_correction[less_than_chi_threshold] += 1 - \ - np.exp(self.beta_rad * chi_threshold - self.beta_rad * - self.atom_data.ionization_data[ - less_than_chi_threshold]['ionization_energy']) - - return radiation_field_correction - - - def calculate_level_populations(self): - """ - Calculate the level populations and putting them in the column 'number-density' of the self.levels table. - :math:`N` denotes the ion number density calculated with `calculate_ionization_balance`, i is the atomic number, - j is the ion number and k is the level number. For non-metastable levels we add the dilution factor (W) to the calculation. + def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, + nlte_species=[], nlte_options=None, zone_id=None, saha_treatment='lte'): + super(NebularPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, t_electron=t_electron, + use_macro_atom=use_macro_atom, nlte_species=nlte_species, + nlte_options=nlte_options, zone_id=zone_id, saha_treatment=saha_treatment) - .. math:: - N_{i, j, k}(\\textrm{metastable}) &= \\frac{g_k}{Z_{i, j}}\\times N_{i, j} \\times e^{-\\beta_\\textrm{rad} E_k} \\\\ - N_{i, j, k}(\\textrm{not metastable}) &= W\\frac{g_k}{Z_{i, j}}\\times N_{i, j} \\times e^{-\\beta_\\textrm{rad} E_k} \\\\ - This function updates the 'number_density' column on the levels table (or adds it if non-existing) - """ - Z = self.partition_functions.values[self.atom_data.levels_index2atom_ion_index] - ion_number_density = self.ion_number_density.values[self.atom_data.levels_index2atom_ion_index] - levels_g = self.atom_data.levels['g'].values - levels_energy = self.atom_data.levels['energy'].values - level_populations = (levels_g / Z) * ion_number_density * np.exp(-self.beta_rad * levels_energy) - #only change between lte plasma and nebular - level_populations[~self.atom_data.levels['metastable']] *= self.w - if self.initialize: - self.level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) - else: - level_populations = pd.Series(level_populations, index=self.atom_data.levels.index) - self.level_populations.update(level_populations[~self.atom_data.nlte_data.nlte_mask]) From 23bbb74dd6dc6827cc535632d915cc87772e6bff Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Tue, 16 Apr 2013 09:57:06 -0400 Subject: [PATCH 17/17] mainly fixed the synpp writer --- tardis/atomic.py | 3 +- tardis/config_reader.py | 15 +--- tardis/data/synpp_default.yaml | 37 +++++++++ tardis/model_radial_oned.py | 138 ++++++++++++-------------------- tardis/montecarlo_multizone.pyx | 4 +- tardis/plasma.py | 43 +++++----- 6 files changed, 119 insertions(+), 121 deletions(-) create mode 100644 tardis/data/synpp_default.yaml diff --git a/tardis/atomic.py b/tardis/atomic.py index 9a6d82bbbdb..907c4c61216 100644 --- a/tardis/atomic.py +++ b/tardis/atomic.py @@ -543,7 +543,8 @@ def __init__(self, atom_data, nlte_species): self.nlte_species = nlte_species self._init_indices() self._create_nlte_mask() - self._create_collision_coefficient_matrix() + if atom_data.has_collision_data: + self._create_collision_coefficient_matrix() def _init_indices(self): diff --git a/tardis/config_reader.py b/tardis/config_reader.py index 5ad82463471..eefc2856146 100644 --- a/tardis/config_reader.py +++ b/tardis/config_reader.py @@ -521,20 +521,7 @@ def from_yaml(cls, fname, args=None): ##### NLTE Section ##### - nlte_section = yaml_dict.pop('nlte', None) - if nlte_section is None: - config_dict['coronal_approximation'] = False - config_dict['classical_nebular'] = False - else: - config_dict['coronal_approximation'] = nlte_section['coronal_approximation'] - config_dict['classical_nebular'] = nlte_section['classical_nebular'] - - if config_dict['coronal_approximation']: - logger.warn( - 'Using the coronal approximation - this is only for debuggging (j_blues=0.0 and beta_sobolevs=1.0)') - - if config_dict['classical_nebular']: - logger.warn('Using classical nebular is for debugging only (beta_sobolevs=1.0)') + config_dict['nlte_options'] = yaml_dict.pop('nlte', {}) diff --git a/tardis/data/synpp_default.yaml b/tardis/data/synpp_default.yaml new file mode 100644 index 00000000000..e705d7122fe --- /dev/null +++ b/tardis/data/synpp_default.yaml @@ -0,0 +1,37 @@ +#- +#- Need to document... +#- +--- +output : + min_wl : 100.0 # min. wavelength in AA + max_wl : 10000.0 # max. wavelength in AA + wl_step : 5.0 # wavelength spacing in AA +grid : + bin_width : 0.3 # opacity bin size in kkm/s + v_size : 20 # size of line-forming region grid + v_outer_max : 30.0 # fastest ejecta velocity in kkm/s +opacity : + line_dir : /Users/wkerzend/software/synapps-linedb/es-data/lines # path to atomic line data + ref_file : /Users/wkerzend/software/synapps-linedb/es-data/refs.dat # path to ref. line data + form : exp # parameterization (only exp for now) + v_ref : 10.0 # reference velocity for parameterization + log_tau_min : -5.0 # opacity threshold +source : + mu_size : 10 # number of angles for source integration +spectrum : + p_size : 60 # number of phot. impact parameters for spectrum + flatten : No # divide out continuum or not +setups : + - a0 : 1.0 # constant term + a1 : 0.0 # linear warp term + a2 : 0.0 # quadratic warp term + v_phot : 10.0 # velocity at photosphere (kkm/s) + v_outer : 30.0 # outer velocity of line forming region (kkm/s) + t_phot : 10.0 # blackbody photosphere temperature (kK) + ions : [ 1400, 1401, 1402, 1403 ] # ions (100*Z+I, I=0 is neutral) + active : [ Yes, Yes, Yes, Yes ] # actually use the ion or not + log_tau : [-99, -99 , -99, -99] # ref. line opacity at v_ref + v_min : [ 10.0, 10.0, 10.0, 10.0 ] # lower cutoff (kkm/s) + v_max : [ 30.0, 30.0, 30.0, 30.0 ] # upper cutoff (kkm/s) + aux : [ 1e300 ,1e300, 1e300, 1e300 ] # e-folding for exp form + temp : [ 10.0, 10.0, 10.0, 10.0 ] # Boltzmann exc. temp. (kK) diff --git a/tardis/model_radial_oned.py b/tardis/model_radial_oned.py index c52a744d887..d4bf4fab0f8 100644 --- a/tardis/model_radial_oned.py +++ b/tardis/model_radial_oned.py @@ -22,7 +22,7 @@ w_estimator_constant = (c ** 2 / (2 * h)) * (15 / np.pi ** 4) * (h / kb) ** 4 / (4 * np.pi) -synpp_default_yaml = os.path.join(os.path.dirname(__file__), 'data', 'synpp_default.yaml') +synpp_default_yaml_fname = os.path.join(os.path.dirname(__file__), 'data', 'synpp_default.yaml') class Radial1DModel(object): @@ -121,7 +121,7 @@ def __init__(self, tardis_config): self.radiative_rates_type = tardis_config.radiative_rates_type if self.plasma_type == 'lte': plasma_class = plasma.LTEPlasma - if tardis_config.ws is not None: + if hasattr(tardis_config, 'ws') and tardis_config.ws is not None: raise ValueError( "the dilution factor W ('ws') can only be specified when selecting plasma_type='nebular'") @@ -146,7 +146,10 @@ def __init__(self, tardis_config): #setting dilution factors - self.ws = 0.5 * (1 - np.sqrt(1 - self.r_inner[0] ** 2 / self.r_middle ** 2)) + if self.plasma_type == 'lte': + self.ws = np.ones_like(self.r_middle) + else: + self.ws = 0.5 * (1 - np.sqrt(1 - self.r_inner[0] ** 2 / self.r_middle ** 2)) #initializing temperatures @@ -218,50 +221,26 @@ def initialize_plasmas(self, plasma_class): else: logger.info('Scattering selected - no transition probabilities created') - if self.plasma_type == 'lte': - for i, ((tmp_index, current_abundances), current_t_rad) in \ - enumerate(zip(self.number_densities.iterrows(), self.t_rads)): - current_plasma = plasma_class(current_abundances, self.atom_data, self.time_explosion, - nlte_species=self.tardis_config.nlte_species, zone_id=i) - logger.debug('Initializing Shell %d Plasma with T=%.3f' % (i, current_t_rad)) - if self.radiative_rates_type in ('lte', 'detailed'): - j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) - current_plasma.set_j_blues(j_blues) - else: - raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' - ' be "lte" or "detailed"' % (self.plasma_type)) - - current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(current_t_rad, - coronal_approximation=self.tardis_config.coronal_approximation, - classical_nebular=self.tardis_config.classical_nebular) - self.tau_sobolevs[i] = current_plasma.tau_sobolevs - - self.plasmas.append(current_plasma) + for i, ((tmp_index, number_density), current_t_rad, current_w) in \ + enumerate(zip(self.number_densities.iterrows(), self.t_rads, self.ws)): + + logger.debug('Initializing Shell %d Plasma with T=%.3f W=%.4f' % (i, current_t_rad, current_w)) + if self.radiative_rates_type in ('lte',): + j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) + elif self.radiative_rates_type in ('nebular', 'detailed'): + j_blues = current_w * plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) + else: + raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' + ' be "lte" or "detailed" or "nebular"' % (self.plasma_type)) + current_plasma = plasma_class(t_rad=current_t_rad, w=current_w, number_density=number_density, + atom_data=self.atom_data, time_explosion=self.time_explosion, + nlte_species=self.tardis_config.nlte_species, + nlte_options=self.tardis_config.nlte_options, zone_id=i, j_blues=j_blues) - elif self.plasma_type == 'nebular': - for i, ((tmp_index, current_abundances), current_t_rad, current_w) in \ - enumerate(zip(self.number_densities.iterrows(), self.t_rads, self.ws)): - current_plasma = plasma_class(current_abundances, self.atom_data, self.time_explosion, - nlte_species=self.tardis_config.nlte_species, zone_id=i) - logger.debug('Initializing Shell %d Plasma with T=%.3f W=%.4f' % (i, current_t_rad, current_w)) - if self.radiative_rates_type in ('lte',): - j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) - elif self.radiative_rates_type in ('nebular', 'detailed'): - j_blues = current_w * plasma.intensity_black_body(self.atom_data.lines.nu.values, current_t_rad) - else: - raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' - ' be "lte" or "detailed" or "nebular"' % (self.plasma_type)) - - current_plasma.set_j_blues(j_blues) - current_plasma.update_radiationfield(current_t_rad, current_w, - coronal_approximation=self.tardis_config.coronal_approximation, - classical_nebular=self.tardis_config.classical_nebular) - - self.tau_sobolevs[i] = current_plasma.tau_sobolevs - - self.plasmas.append(current_plasma) + self.tau_sobolevs[i] = current_plasma.tau_sobolevs + + self.plasmas.append(current_plasma) self.tau_sobolevs = np.array(self.tau_sobolevs, dtype=float) self.j_blues = np.zeros_like(self.tau_sobolevs) @@ -318,44 +297,24 @@ def normalize_j_blues(self): i].t_rad) def update_plasmas(self): - if self.plasma_type == 'lte': - for i, (current_plasma, new_trad) in enumerate(zip(self.plasmas, self.t_rads)): - logger.debug('Updating Shell %d Plasma with T=%.3f' % (i, new_trad)) - if self.radiative_rates_type == 'lte': - j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, new_trad) - elif self.radiative_rates_type == 'detailed': - j_blues = self.j_blues[i] - else: - raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' - ' be "lte" or "detailed"' % (self.plasma_type)) - - current_plasma.set_j_blues(j_blues) - - current_plasma.update_radiationfield(new_trad, - coronal_approximation=self.tardis_config.coronal_approximation, - classical_nebular=self.tardis_config.classical_nebular) - self.tau_sobolevs[i] = current_plasma.tau_sobolevs + for i, (current_plasma, new_trad, new_ws) in enumerate(zip(self.plasmas, self.t_rads, self.ws)): + logger.debug('Updating Shell %d Plasma with T=%.3f W=%.4f' % (i, new_trad, new_ws)) + if self.radiative_rates_type == 'lte': + j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, new_trad) + elif self.radiative_rates_type == 'nebular': + j_blues = new_ws * plasma.intensity_black_body(self.atom_data.lines.nu.values, new_trad) + + elif self.radiative_rates_type == 'detailed': + j_blues = self.j_blues[i] + else: + raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' + ' be "lte" or "detailed" or "nebular"' % (self.plasma_type)) - elif self.plasma_type == 'nebular': - for i, (current_plasma, new_trad, new_ws) in enumerate(zip(self.plasmas, self.t_rads, self.ws)): - logger.debug('Updating Shell %d Plasma with T=%.3f W=%.4f' % (i, new_trad, new_ws)) - if self.radiative_rates_type == 'lte': - j_blues = plasma.intensity_black_body(self.atom_data.lines.nu.values, new_trad) - elif self.radiative_rates_type == 'nebular': - j_blues = new_ws * plasma.intensity_black_body(self.atom_data.lines.nu.values, new_trad) - - elif self.radiative_rates_type == 'detailed': - j_blues = self.j_blues[i] - else: - raise ValueError('For the current plasma_type (%s) the radiative_rates_type can only' - ' be "lte" or "detailed" or "nebular"' % (self.plasma_type)) - - current_plasma.set_j_blues(j_blues) - - current_plasma.update_radiationfield(new_trad, new_ws, - coronal_approximation=self.tardis_config.coronal_approximation, - classical_nebular=self.tardis_config.classical_nebular) - self.tau_sobolevs[i] = current_plasma.tau_sobolevs + current_plasma.set_j_blues(j_blues) + if self.plasma_type == 'lte': + new_ws = 1.0 + current_plasma.update_radiationfield(new_trad, w=new_ws) + self.tau_sobolevs[i] = current_plasma.tau_sobolevs if self.line_interaction_id in (1, 2): self.calculate_transition_probabilities() @@ -461,6 +420,7 @@ def update_radiationfield(self, update_mode='dampened', damping_constant=0.5, lo def create_synpp_yaml(self, fname, lines_db=None): + logger.warning('Currently only works with Si and a special setup') if not self.atom_data.has_synpp_refs: raise ValueError( 'The current atom dataset does not contain the necesarry reference files (please contact the authors)') @@ -476,14 +436,21 @@ def create_synpp_yaml(self, fname, lines_db=None): relevant_synpp_refs = self.atom_data.synpp_refs[self.atom_data.synpp_refs['ref_log_tau'] > -50] - yaml_reference = yaml.load(file(synpp_default_yaml)) + yaml_reference = yaml.load(file(synpp_default_yaml_fname)) if lines_db is not None: yaml_reference['opacity']['line_dir'] = os.path.join(lines_db, 'lines') yaml_reference['opacity']['line_dir'] = os.path.join(lines_db, 'refs.dat') - yaml_setup = yaml_reference['setups'][0] + yaml_reference['output']['min_wl'] = float(self.spec_angstrom.min()) + yaml_reference['output']['max_wl'] = float(self.spec_angstrom.max()) + + yaml_reference['opacity']['v_ref'] = float(self.v_inner[0] / 1e8) + yaml_reference['grid']['v_outer_max'] = float(self.v_outer[-1] / 1e8) + #pdb.set_trace() + + yaml_setup = yaml_reference['setups'][0] yaml_setup['ions'] = [] yaml_setup['log_tau'] = [] yaml_setup['active'] = [] @@ -501,7 +468,8 @@ def create_synpp_yaml(self, fname, lines_db=None): yaml_setup['v_max'].append(yaml_reference['grid']['v_outer_max']) yaml_setup['aux'].append(1e200) - yaml.dump(yaml_reference, file(fname, 'w')) + yaml.dump(yaml_reference, stream=file(fname, 'w'), explicit_start=True) + def plot_spectrum(self, ax=None, mode='wavelength', virtual=True): if ax is None: diff --git a/tardis/montecarlo_multizone.pyx b/tardis/montecarlo_multizone.pyx index 0d38ba9e985..f0b7ecb3256 100644 --- a/tardis/montecarlo_multizone.pyx +++ b/tardis/montecarlo_multizone.pyx @@ -374,7 +374,7 @@ cdef float_type_t move_packet(float_type_t*r, cdef void increment_j_blue_estimator(int_type_t*current_line_id, float_type_t*current_nu, float_type_t*current_energy, float_type_t*mu, float_type_t*r, float_type_t d_line, int_type_t j_blue_idx, StorageModel storage): - cdef float_type_t comov_energy, r_interaction, mu_interaction, distance, doppler_factor + cdef float_type_t comov_energy, comov_nu, r_interaction, mu_interaction, distance, doppler_factor distance = d_line @@ -384,6 +384,8 @@ cdef void increment_j_blue_estimator(int_type_t*current_line_id, float_type_t*cu doppler_factor = (1 - (mu_interaction * r_interaction * storage.inverse_time_explosion * inverse_c)) comov_energy = current_energy[0] * doppler_factor + comov_nu = current_nu[0] * doppler_factor + storage.line_lists_j_blues[j_blue_idx] += (comov_energy / current_nu[0]) #print "incrementing j_blues = %g" % storage.line_lists_j_blues[j_blue_idx] diff --git a/tardis/plasma.py b/tardis/plasma.py index 535b60f8596..a3a6f4fee31 100644 --- a/tardis/plasma.py +++ b/tardis/plasma.py @@ -6,7 +6,7 @@ from astropy import constants import pandas as pd import macro_atom - +import pdb from .config_reader import reformat_element_symbol logger = logging.getLogger(__name__) @@ -65,7 +65,7 @@ class BasePlasma(object): """ @classmethod - def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, t_electron=None, + def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, j_blues=None, t_electron=None, use_macro_atom=False, nlte_species=[], nlte_options={}, zone_id=None, saha_treatment='lte'): """ :param cls: @@ -104,9 +104,9 @@ def from_abundance(cls, t_rad, w, abundance, density, atom_data, time_explosion, number_density *= density number_density /= atom_data.atom_data.mass[number_density.index] - return cls(t_rad=t_rad, w=w, number_density=number_density, atom_data=atom_data, time_explosion=time_explosion, - t_electron=t_electron, use_macro_atom=use_macro_atom, zone_id=zone_id, nlte_species=nlte_species, - nlte_options=nlte_options, saha_treatment=saha_treatment) + return cls(t_rad=t_rad, w=w, number_density=number_density, atom_data=atom_data, j_blues=j_blues, + time_explosion=time_explosion, t_electron=t_electron, use_macro_atom=use_macro_atom, zone_id=zone_id, + nlte_species=nlte_species, nlte_options=nlte_options, saha_treatment=saha_treatment) def __init__(self, t_rad, w, number_density, atom_data, time_explosion, j_blues=None, t_electron=None, @@ -162,7 +162,7 @@ def t_electron(self, value): self.link_t_rad_electron = 0.9 self._t_electron = None else: - self._t_electron = self.link_t_rad_electron * self.trad + self._t_electron = value self.beta_electron = 1 / (constants.k_B.cgs.value * self.t_electron) @@ -544,10 +544,10 @@ def calculate_nlte_level_populations(self): r_ul_matrix.ravel()[r_ul_index] *= beta_sobolevs[lines_index] stimulated_emission_matrix = np.zeros_like(r_ul_matrix) - stimulated_emission_matrix.ravel()[r_lu_index] = 1 - (level_populations[lnu] * B_uls) / ( - level_populations[lnl] * B_lus) + stimulated_emission_matrix.ravel()[r_lu_index] = 1 - ((level_populations[lnu] * B_uls) / ( + level_populations[lnl] * B_lus)) - #stimulated_emission_matrix[stimulated_emission_matrix < 0.] = 0.0 + stimulated_emission_matrix[stimulated_emission_matrix < 0.] = 0.0 r_lu_matrix = np.zeros_like(r_ul_matrix) r_lu_matrix.ravel()[r_lu_index] = B_lus * j_blues[lines_index] * beta_sobolevs[lines_index] @@ -651,17 +651,18 @@ class LTEPlasma(BasePlasma): """ @classmethod - def from_abundance(cls, t_rad, abundance, density, atom_data, time_explosion, t_electron=None, + def from_abundance(cls, t_rad, abundance, density, atom_data, time_explosion, j_blues=None, t_electron=None, use_macro_atom=False, nlte_species=[], nlte_options={}, zone_id=None): return super(LTEPlasma, cls).from_abundance(t_rad, 1., abundance, density, atom_data, time_explosion, - t_electron=t_electron, use_macro_atom=use_macro_atom, - nlte_species=nlte_species, nlte_options=nlte_options, - zone_id=zone_id) + j_blues=j_blues, t_electron=t_electron, + use_macro_atom=use_macro_atom, nlte_species=nlte_species, + nlte_options=nlte_options, zone_id=zone_id) - def __init__(self, t_rad, number_density, atom_data, time_explosion, w=1., t_electron=None, use_macro_atom=False, + def __init__(self, t_rad, number_density, atom_data, time_explosion, w=1., j_blues=None, t_electron=None, + use_macro_atom=False, nlte_species=[], nlte_options=None, zone_id=None, saha_treatment='lte'): - super(LTEPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, t_electron=t_electron, - use_macro_atom=use_macro_atom, nlte_species=nlte_species, + super(LTEPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, j_blues=j_blues, + t_electron=t_electron, use_macro_atom=use_macro_atom, nlte_species=nlte_species, nlte_options=nlte_options, zone_id=zone_id, saha_treatment=saha_treatment) @@ -691,11 +692,13 @@ class NebularPlasma(BasePlasma): """ - def __init__(self, t_rad, w, number_density, atom_data, time_explosion, t_electron=None, use_macro_atom=False, + def __init__(self, t_rad, w, number_density, atom_data, time_explosion, j_blues=None, t_electron=None, + use_macro_atom=False, nlte_species=[], nlte_options=None, zone_id=None, saha_treatment='lte'): - super(NebularPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, t_electron=t_electron, - use_macro_atom=use_macro_atom, nlte_species=nlte_species, - nlte_options=nlte_options, zone_id=zone_id, saha_treatment=saha_treatment) + super(NebularPlasma, self).__init__(t_rad, w, number_density, atom_data, time_explosion, j_blues=j_blues, + t_electron=t_electron, use_macro_atom=use_macro_atom, + nlte_species=nlte_species, nlte_options=nlte_options, zone_id=zone_id, + saha_treatment=saha_treatment)