From 883c60501ef3fa47034e551ff68933fb39c1a5c2 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Tue, 30 Apr 2019 22:31:58 -0400 Subject: [PATCH 001/116] commenting out the montecarlo part --- tardis/montecarlo/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index f11d39c5ae8..23db0f435a2 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -220,11 +220,11 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets) - montecarlo.montecarlo_radial1d( - model, plasma, self, - virtual_packet_flag=no_of_virtual_packets, - nthreads=nthreads, - last_run=last_run) + #montecarlo.montecarlo_radial1d( + # model, plasma, self, + # virtual_packet_flag=no_of_virtual_packets, + # nthreads=nthreads, + # last_run=last_run) # Workaround so that j_blue_estimator is in the right ordering # They are written as an array of dimension (no_of_shells, no_of_lines) # but python expects (no_of_lines, no_of_shells) From 059cbffc2d0d5f407b2276e2997cc8d718b86dc7 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 11:47:46 -0400 Subject: [PATCH 002/116] Add new numba stuff Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- .../montecarlo/montecarlo_numba/__init__.py | 0 .../montecarlo_numba/compute_distance.py | 256 ++++++++++++++++++ tardis/montecarlo/montecarlo_numba/rpacket.py | 27 ++ .../montecarlo_numba/storage_model.py | 172 ++++++++++++ 4 files changed, 455 insertions(+) create mode 100644 tardis/montecarlo/montecarlo_numba/__init__.py create mode 100644 tardis/montecarlo/montecarlo_numba/compute_distance.py create mode 100644 tardis/montecarlo/montecarlo_numba/rpacket.py create mode 100644 tardis/montecarlo/montecarlo_numba/storage_model.py diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py new file mode 100644 index 00000000000..fc7ed21e231 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -0,0 +1,256 @@ +from numba import jitclass, njit +import numpy as np +import scipy.constants + +@njit +def compute_distance2boundary(rpacket, storage): + r = rpacket_get_r(packet) + mu = rpacket_get_mu(packet) + r_outer = storage.r_outer[rpacket_get_current_shell_id (packet)] + r_inner = storage.r_inner[rpacket_get_current_shell_id (packet)] + + distance = 0.0 + + if (mu > 0.0): + # direction outward + rpacket_set_next_shell_id(packet, 1) + distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) + else: + # going inward + check = r_inner * r_inner + (r * r * (mu * my - 1.0)) + + if (check >= 0.0): + # hit inner boundary + rpacket_set_next_shell_id(packet, -1) + distance = -r * mu - np.sqrt(check) + else: + # miss inner boundary + rpacket_set_next_shell_id(packet, 1) + distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) + + rpacket_set_d_boundary(packet, distance) + + + +void +compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) +{ + double r = rpacket_get_r (packet); + double mu = rpacket_get_mu (packet); + double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; + double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; + double check, distance; + if (mu > 0.0) + { // direction outward + rpacket_set_next_shell_id (packet, 1); + distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); + } + else + { // going inward + if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) + { // hit inner boundary + rpacket_set_next_shell_id (packet, -1); + distance = - r * mu - sqrt (check); + } + else + { // miss inner boundary + rpacket_set_next_shell_id (packet, 1); + distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); + } + } + rpacket_set_d_boundary (packet, distance); +} + +tardis_error_t + + +@njit +def compute_distance2line(rpacket, storage): + if not rpacket_get_last_line(packet): + r = rpacket_get_r(packet) + mu = rpacket_get_mu(packet) + nu = rpacket_get_nu(packet) + nu_line = rpacket_get_nu_line(packet) + + distance = 0 + nu_diff = 0 + + ct = storage.time_explosion * c + doppler_factor = rpacket_doppler_factor(packet, storage) + comov_nu = nu * doppler_factor + + if (nu_diff = comov_nu - nu_line) >= 0: + if not storage.full_relativity: + distance = (nu_diff/nu) * ct + else: + double nu_r = nu_line / nu + distance = - mu * r + (ct - nu_r * nu_r * np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) + rpacket_set_d_line (packet, distance) + return TARDIS_ERROR_OK + else: + if not storage.full_relativity: + distance = (nu_diff / nu) * ct + else: + double nu_r = nu_line / nu + +compute_distance2line (rpacket_t * packet, const storage_model_t * storage) +{ + if (!rpacket_get_last_line (packet)) + { + double r = rpacket_get_r (packet); + double mu = rpacket_get_mu (packet); + double nu = rpacket_get_nu (packet); + double nu_line = rpacket_get_nu_line (packet); + double distance, nu_diff; + double ct = storage->time_explosion * c; + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = nu * doppler_factor; + if ( (nu_diff = comov_nu - nu_line) >= 0) + { + if (!storage->full_relativity) + { + distance = (nu_diff / nu) * ct; + } + else + { + double nu_r = nu_line / nu; + distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - + (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r); + } + rpacket_set_d_line (packet, distance); + return TARDIS_ERROR_OK; + } + else + { + if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) + { + fprintf (stderr, "last_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) - 1]); + fprintf (stderr, "Last line in line list reached!"); + } + else if (rpacket_get_next_line_id (packet) == 0) + { + fprintf (stderr, "First line in line list!"); + fprintf (stderr, "next_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) + 1]); + } + else + { + fprintf (stderr, "last_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) - 1]); + fprintf (stderr, "next_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) + 1]); + } + fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); + fprintf (stderr, "comov_nu = %f\n", comov_nu); + fprintf (stderr, "nu_line = %f\n", nu_line); + fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", + (comov_nu - nu_line) / nu_line); + fprintf (stderr, "r = %f\n", r); + fprintf (stderr, "mu = %f\n", mu); + fprintf (stderr, "nu = %f\n", nu); + fprintf (stderr, "doppler_factor = %f\n", doppler_factor); + fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); + return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; + } + } + else + { + rpacket_set_d_line (packet, MISS_DISTANCE); + return TARDIS_ERROR_OK; + } +} + +@ngit +def compute_distance2continuum(packet, storage): + chi_continuum = 0.0 + d_continuum = 0.0 + + chi_electron = storage.electron_densities[rpacket_get_current_shell_id(packet)] * storage.sigma_thomson + + if (storange.full_relativity): + chi_electron *= rpacket_doppler_factor(packet, storage) + + if (storage.cont_status == CONTINUUM_ON): + if (packet.compute_chi_bf): + calculate_chi_bf(packet, storage) + calculate_chi_ff(packet, storage) + else: + packet.compute_chi_bf = True + + chi_continuum = rpacket_get_chi_boundfree(packet) + rpacket_get_chi_freefree(packet) + chi_electron + d_continuum = rpacket_get_tau_event(packet) / chi_continuum + else: + chi_continuum = chi_electron + d_continuum = storage.inverse_electron_densities[rpacket_get_current_shell_id(packet)] * \ + storage.inverse_sigma_thomson * rpacket_get_tau_event(packet) + + if (rpacket_get_virtual_packet(packet) > 0): + # set all continuum distances to MISS_DISTANCE in case of a virtual_packet + d_continuum = MISS_DISTANCE + packet.compute_chi_bf = False + else: + rpacket_set_chi_electron(packet, chi_continuum) + + rpacket_set_chi_continuum(packet, chi_continuum) + rpacket_set_d_continuum(packet, d_continuum) + + +void +compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) +{ + double chi_continuum, d_continuum; + double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * + storage->sigma_thomson; + if (storage->full_relativity) + { + chi_electron *= rpacket_doppler_factor (packet, storage); + } + + if (storage->cont_status == CONTINUUM_ON) + { + if (packet->compute_chi_bf) + { + calculate_chi_bf (packet, storage); + calculate_chi_ff (packet, storage); + } + else + { + packet->compute_chi_bf=true; + } + chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; + d_continuum = rpacket_get_tau_event (packet) / chi_continuum; + } + else + { + chi_continuum = chi_electron; + d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * + storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); + } + + if (rpacket_get_virtual_packet(packet) > 0) + { + //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet + d_continuum = MISS_DISTANCE; + packet->compute_chi_bf = false; + } + else + { + + // fprintf(stderr, "--------\n"); + // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); + // fprintf(stderr, "chi_electron = %e\n", chi_electron); + // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); + // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); + // fprintf(stderr, "--------\n"); + + //rpacket_set_chi_freefree(packet, chi_freefree); + rpacket_set_chi_electron (packet, chi_electron); + } + rpacket_set_chi_continuum (packet, chi_continuum); + rpacket_set_d_continuum (packet, d_continuum); +} \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py new file mode 100644 index 00000000000..e5565475150 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -0,0 +1,27 @@ +from numba import int64, float64 +from numba import jitclass, njit + + +rpacket_spec = [ + ('r', float64), + ('mu', float64), + ('tau_event', float64) + ('nu_line', float64) + ('d_line', float64) # Distance to electron event. + ('d_electron', float64) #/**< Distance to line event. */ + ('d_boundary', float64) # distance to boundary + ('shell_id', int64) +] + +@jitclass(rpacket_rspec) +class RPacket(object): + def __init__(self, r, mu, tau_event): + self.r = r + self.mu = mu + self.nu = nu + self.tau_event = tau_event + self.nu_line = nu_line + + @staticmethod + def get_tau_event(): + return -np.log(np.random.random()) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py new file mode 100644 index 00000000000..6ceef7694ad --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -0,0 +1,172 @@ +from numba import int64, float64 +storage_model_spec = [ + ('*packet_nus', double), + ('*packet_mus', double), + ('*packet_energies', double), + ('*output_nus', double), + ('*output_energies', double), + ('*last_interaction_in_nu', double), + ('*last_line_interaction_in_id', int64_t), + ('*last_line_interaction_out_id', int64_t), + ('*last_line_interaction_shell_id', int64_t), + ('*last_interaction_type', int64_t), + ('*last_interaction_out_type', int64_t), + ('no_of_packets', int64_t), + ('no_of_shells', int64_t), + ('no_of_shells_i', int64_t), + ('*r_inner', double), + ('*r_outer', double), + ('*r_inner_i', double), + ('*r_outer_i', double), + ('*v_inner', double), + ('time_explosion', double), + ('inverse_time_explosion', double), + ('*electron_densities', double), + ('*electron_densities_i', double), + ('*inverse_electron_densities', double), + ('*line_list_nu', double), + ('*continuum_list_nu', double), + ('*line_lists_tau_sobolevs', double), + ('*line_lists_tau_sobolevs_i', double), + ('line_lists_tau_sobolevs_nd', int64_t), + ('*line_lists_j_blues', double), + ('line_lists_j_blues_nd', int64_t), + ('*line_lists_Edotlu', double), + ('no_of_lines', int64_t), + ('no_of_edges', int64_t), + ('line_interaction_id', int64_t), + ('*transition_probabilities', double), + ('transition_probabilities_nd', int64_t), + ('*line2macro_level_upper', int64_t), + ('*macro_block_references', int64_t), + ('*transition_type', int64_t), + ('*destination_level_id', int64_t), + ('*transition_line_id', int64_t), + ('*js', double), + ('*nubars', double), + ('spectrum_start_nu', double), + ('spectrum_delta_nu', double), + ('spectrum_end_nu', double), + ('spectrum_virt_start_nu', double), + ('spectrum_virt_end_nu', double), + ('*spectrum_virt_nu', double), + ('sigma_thomson', double), + ('inverse_sigma_thomson', double), + ('inner_boundary_albedo', double), + ('reflective_inner_boundary', int64_t), + ('current_packet_id', int64_t), + photo_xsect_1level **photo_xsect; + ('*chi_ff_factor', double), + ('*t_electrons', double), + ('*l_pop', double), + ('*l_pop_r', double), + ContinuumProcessesStatus cont_status; + bound_free_treatment bf_treatment; + ('*virt_packet_nus', double), + ('*virt_packet_energies', double), + ('*virt_packet_last_interaction_in_nu', double), + ('*virt_packet_last_interaction_type', int64_t), + ('*virt_packet_last_line_interaction_in_id', int64_t), + ('*virt_packet_last_line_interaction_out_id', int64_t), + ('virt_packet_count', int64_t), + ('virt_array_size', int64_t), + ('kpacket2macro_level', int64_t), + ('*cont_edge2macro_level', int64_t), + ('*photo_ion_estimator', double), + ('*stim_recomb_estimator', double), + ('*photo_ion_estimator_statistics', int64_t), + ('*bf_heating_estimator', double), + ('*ff_heating_estimator', double), + ('*stim_recomb_cooling_estimator', double), + ('full_relativity', int), + ('survival_probability', double), + ('tau_russian', double), + ('*tau_bias', double), + ('enable_biasing', int), +] +typedef struct StorageModel +{ + double *packet_nus; + double *packet_mus; + double *packet_energies; + double *output_nus; + double *output_energies; + double *last_interaction_in_nu; + int64_t *last_line_interaction_in_id; + int64_t *last_line_interaction_out_id; + int64_t *last_line_interaction_shell_id; + int64_t *last_interaction_type; + int64_t *last_interaction_out_type; + int64_t no_of_packets; + int64_t no_of_shells; + int64_t no_of_shells_i; + double *r_inner; + double *r_outer; + double *r_inner_i; + double *r_outer_i; + double *v_inner; + double time_explosion; + double inverse_time_explosion; + double *electron_densities; + double *electron_densities_i; + double *inverse_electron_densities; + double *line_list_nu; + double *continuum_list_nu; + double *line_lists_tau_sobolevs; + double *line_lists_tau_sobolevs_i; + int64_t line_lists_tau_sobolevs_nd; + double *line_lists_j_blues; + int64_t line_lists_j_blues_nd; + double *line_lists_Edotlu; + int64_t no_of_lines; + int64_t no_of_edges; + int64_t line_interaction_id; + double *transition_probabilities; + int64_t transition_probabilities_nd; + int64_t *line2macro_level_upper; + int64_t *macro_block_references; + int64_t *transition_type; + int64_t *destination_level_id; + int64_t *transition_line_id; + double *js; + double *nubars; + double spectrum_start_nu; + double spectrum_delta_nu; + double spectrum_end_nu; + double spectrum_virt_start_nu; + double spectrum_virt_end_nu; + double *spectrum_virt_nu; + double sigma_thomson; + double inverse_sigma_thomson; + double inner_boundary_albedo; + int64_t reflective_inner_boundary; + int64_t current_packet_id; + photo_xsect_1level **photo_xsect; + double *chi_ff_factor; + double *t_electrons; + double *l_pop; + double *l_pop_r; + ContinuumProcessesStatus cont_status; + bound_free_treatment bf_treatment; + double *virt_packet_nus; + double *virt_packet_energies; + double *virt_packet_last_interaction_in_nu; + int64_t *virt_packet_last_interaction_type; + int64_t *virt_packet_last_line_interaction_in_id; + int64_t *virt_packet_last_line_interaction_out_id; + int64_t virt_packet_count; + int64_t virt_array_size; + int64_t kpacket2macro_level; + int64_t *cont_edge2macro_level; + double *photo_ion_estimator; + double *stim_recomb_estimator; + int64_t *photo_ion_estimator_statistics; + double *bf_heating_estimator; + double *ff_heating_estimator; + double *stim_recomb_cooling_estimator; + int full_relativity; + double survival_probability; + double tau_russian; + double *tau_bias; + int enable_biasing; +} storage_model_t; \ No newline at end of file From 532b4145384220897246315c422230857182c8c7 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 14:18:07 -0400 Subject: [PATCH 003/116] add more numba conversions Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- .../montecarlo/montecarlo_numba/__init__.py | 1 + tardis/montecarlo/montecarlo_numba/base.py | 105 +++++ .../montecarlo_numba/compute_distance.py | 426 +++++++++--------- .../montecarlo_numba/packet_loop.py | 64 +++ .../montecarlo_numba/storage_model.py | 219 ++------- 5 files changed, 443 insertions(+), 372 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/base.py create mode 100644 tardis/montecarlo/montecarlo_numba/packet_loop.py diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index e69de29bb2d..ca0b060eaf2 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -0,0 +1 @@ +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py new file mode 100644 index 00000000000..7a336a916b7 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -0,0 +1,105 @@ + +from numba import prange, njit +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket + +def montecarlo_radial1d(model, plasma, runner): + storage_model_kwargs = {'packet_nus', packet_mus, packet_energies, + output_nus, output_energies, no_of_packets, no_of_shells, + r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, line_lists_tau_sobolevs_nd, + no_of_lines, no_of_edges, line_interaction_id, + inverse_sigma_thomson} + pass + #montecarlo.montecarlo_radial1d( + # model, plasma, self, + # virtual_packet_flag=no_of_virtual_packets, + # nthreads=nthreads, + # last_run=last_run) + # Workaround so that j_blue_estimator is in the right ordering + # They are written as an array of dimension (no_of_shells, no_of_lines) + # but python expects (no_of_lines, no_of_shells) + +@njit +def montecarlo_main_loop(storage): + for i in prange(storage.no_of_packets): + from + + +void +montecarlo_main_loop(storage_model_t * storage, int64_t virtual_packet_flag, int nthreads, unsigned long seed) +{ + int64_t finished_packets = 0; + storage->virt_packet_count = 0; +#ifdef WITH_VPACKET_LOGGING + storage->virt_packet_nus = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_energies = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_last_interaction_in_nu = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_last_interaction_type = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_packet_last_line_interaction_in_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_packet_last_line_interaction_out_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_array_size = storage->no_of_packets; +#endif // WITH_VPACKET_LOGGING +#ifdef WITHOPENMP + omp_set_dynamic(0); + if (nthreads > 0) + { + omp_set_num_threads(nthreads); + } + +#pragma omp parallel firstprivate(finished_packets) + { + rk_state mt_state; + rk_seed (seed + omp_get_thread_num(), &mt_state); +#pragma omp master + { + fprintf(stderr, "Running with OpenMP - %d threads\n", omp_get_num_threads()); + print_progress(0, storage->no_of_packets); + } +#else + rk_state mt_state; + rk_seed (seed, &mt_state); + fprintf(stderr, "Running without OpenMP\n"); +#endif + int64_t chi_bf_tmp_size = (storage->cont_status) ? storage->no_of_edges : 0; + double *chi_bf_tmp_partial = safe_malloc(sizeof(double) * chi_bf_tmp_size); + + #pragma omp for + for (int64_t packet_index = 0; packet_index < storage->no_of_packets; ++packet_index) + { + int reabsorbed = 0; + rpacket_t packet; + rpacket_set_id(&packet, packet_index); + rpacket_init(&packet, storage, packet_index, virtual_packet_flag, chi_bf_tmp_partial); + if (virtual_packet_flag > 0) + { + reabsorbed = montecarlo_one_packet(storage, &packet, -1, &mt_state); + } + reabsorbed = montecarlo_one_packet(storage, &packet, 0, &mt_state); + storage->output_nus[packet_index] = rpacket_get_nu(&packet); + if (reabsorbed == 1) + { + storage->output_energies[packet_index] = -rpacket_get_energy(&packet); + } + else + { + storage->output_energies[packet_index] = rpacket_get_energy(&packet); + } + if ( ++finished_packets%100 == 0 ) + { +#ifdef WITHOPENMP + // WARNING: This only works with a static sheduler and gives an approximation of progress. + // The alternative would be to have a shared variable but that could potentially decrease performance when using many threads. + if (omp_get_thread_num() == 0 ) + print_progress(finished_packets * omp_get_num_threads(), storage->no_of_packets); +#else + print_progress(finished_packets, storage->no_of_packets); +#endif + } + } + free(chi_bf_tmp_partial); +#ifdef WITHOPENMP + } +#endif + print_progress(storage->no_of_packets, storage->no_of_packets); + fprintf(stderr,"\n"); +} + diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index fc7ed21e231..d39451e913b 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -1,19 +1,19 @@ from numba import jitclass, njit import numpy as np -import scipy.constants +from astropy import constants as const -@njit -def compute_distance2boundary(rpacket, storage): - r = rpacket_get_r(packet) - mu = rpacket_get_mu(packet) - r_outer = storage.r_outer[rpacket_get_current_shell_id (packet)] - r_inner = storage.r_inner[rpacket_get_current_shell_id (packet)] +C_SPEED_OF_LIGHT = const.c.to('cm/s').value - distance = 0.0 +@njit +def compute_distance2boundary(packet, storage): + r = packet.r + mu = packet.mu + r_outer = storage.r_outer[packet.shell_id] + r_inner = storage.r_inner[packet.shell_id] if (mu > 0.0): # direction outward - rpacket_set_next_shell_id(packet, 1) + packet.shell_id += 1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) else: # going inward @@ -21,236 +21,256 @@ def compute_distance2boundary(rpacket, storage): if (check >= 0.0): # hit inner boundary - rpacket_set_next_shell_id(packet, -1) + packet.shell_id -= 1 distance = -r * mu - np.sqrt(check) else: # miss inner boundary - rpacket_set_next_shell_id(packet, 1) + packet.shell_id += 1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) - rpacket_set_d_boundary(packet, distance) + packet.d_boundary = distance -void -compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) -{ - double r = rpacket_get_r (packet); - double mu = rpacket_get_mu (packet); - double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; - double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; - double check, distance; - if (mu > 0.0) - { // direction outward - rpacket_set_next_shell_id (packet, 1); - distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); - } - else - { // going inward - if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) - { // hit inner boundary - rpacket_set_next_shell_id (packet, -1); - distance = - r * mu - sqrt (check); - } - else - { // miss inner boundary - rpacket_set_next_shell_id (packet, 1); - distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); - } - } - rpacket_set_d_boundary (packet, distance); -} +# void +# compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) +# { +# double r = rpacket_get_r (packet); +# double mu = rpacket_get_mu (packet); +# double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; +# double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; +# double check, distance; +# if (mu > 0.0) +# { // direction outward +# rpacket_set_next_shell_id (packet, 1); +# distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); +# } +# else +# { // going inward +# if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) +# { // hit inner boundary +# rpacket_set_next_shell_id (packet, -1); +# distance = - r * mu - sqrt (check); +# } +# else +# { // miss inner boundary +# rpacket_set_next_shell_id (packet, 1); +# distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); +# } +# } +# rpacket_set_d_boundary (packet, distance); +# } -tardis_error_t +# tardis_error_t @njit -def compute_distance2line(rpacket, storage): +def compute_distance2line(rpacket, storage, c=C_SPEED_OF_LIGHT): if not rpacket_get_last_line(packet): - r = rpacket_get_r(packet) - mu = rpacket_get_mu(packet) - nu = rpacket_get_nu(packet) + r = rpacket.r + mu = rpacket.mu + nu = rpacket.nu nu_line = rpacket_get_nu_line(packet) - distance = 0 - nu_diff = 0 + distance = 0.0 + nu_diff = 0.0 ct = storage.time_explosion * c doppler_factor = rpacket_doppler_factor(packet, storage) comov_nu = nu * doppler_factor - if (nu_diff = comov_nu - nu_line) >= 0: + nu_diff = comov_nu - nu_line + if nu_diff >= 0: if not storage.full_relativity: distance = (nu_diff/nu) * ct else: - double nu_r = nu_line / nu - distance = - mu * r + (ct - nu_r * nu_r * np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) + nu_r = nu_line / nu + distance = - mu * r + (ct - nu_r * nu_r * + np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) rpacket_set_d_line (packet, distance) return TARDIS_ERROR_OK else: - if not storage.full_relativity: - distance = (nu_diff / nu) * ct + if rpacket_get_next_line_id(packet) == storage.no_of_lines - 1: + print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) + print("Last line in line list reached!") + else if rpacket_get_next_line_id(packet) == 0: + print("First line in line list!") + print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) else: - double nu_r = nu_line / nu + print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) + print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) + print("ERROR: Comoving nu less than nu_line!") + print("comov_nu = {}".format(comov_nu)) + print("nu_line = {}".format(nu_line)) + print("(comov_nu - nu_line) / nu_line = {}".format(comov_nu-nu_line/nu_line)) + print("r = {}".format(r)) + print("mu = {}".format(mu)) + print("nu = {}".format(nu)) + print("doppler_factor = {}".format(doppler_factor)) + print("cur_zone_id = {}".format(rpacket_get_current_shell_id(packet)) + return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE + else: + rpacket_set_d_line(packet, MISS_DISTANCE) + return TARDIS_ERROR_OK -compute_distance2line (rpacket_t * packet, const storage_model_t * storage) -{ - if (!rpacket_get_last_line (packet)) - { - double r = rpacket_get_r (packet); - double mu = rpacket_get_mu (packet); - double nu = rpacket_get_nu (packet); - double nu_line = rpacket_get_nu_line (packet); - double distance, nu_diff; - double ct = storage->time_explosion * c; - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = nu * doppler_factor; - if ( (nu_diff = comov_nu - nu_line) >= 0) - { - if (!storage->full_relativity) - { - distance = (nu_diff / nu) * ct; - } - else - { - double nu_r = nu_line / nu; - distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r); - } - rpacket_set_d_line (packet, distance); - return TARDIS_ERROR_OK; - } - else - { - if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) - { - fprintf (stderr, "last_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) - 1]); - fprintf (stderr, "Last line in line list reached!"); - } - else if (rpacket_get_next_line_id (packet) == 0) - { - fprintf (stderr, "First line in line list!"); - fprintf (stderr, "next_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) + 1]); - } - else - { - fprintf (stderr, "last_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) - 1]); - fprintf (stderr, "next_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) + 1]); - } - fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); - fprintf (stderr, "comov_nu = %f\n", comov_nu); - fprintf (stderr, "nu_line = %f\n", nu_line); - fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", - (comov_nu - nu_line) / nu_line); - fprintf (stderr, "r = %f\n", r); - fprintf (stderr, "mu = %f\n", mu); - fprintf (stderr, "nu = %f\n", nu); - fprintf (stderr, "doppler_factor = %f\n", doppler_factor); - fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); - return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; - } - } - else - { - rpacket_set_d_line (packet, MISS_DISTANCE); - return TARDIS_ERROR_OK; - } -} - -@ngit -def compute_distance2continuum(packet, storage): - chi_continuum = 0.0 - d_continuum = 0.0 + + +# compute_distance2line (rpacket_t * packet, const storage_model_t * storage) +# { +# if (!rpacket_get_last_line (packet)) +# { +# double r = rpacket_get_r (packet); +# double mu = rpacket_get_mu (packet); +# double nu = rpacket_get_nu (packet); +# double nu_line = rpacket_get_nu_line (packet); +# double distance, nu_diff; +# double ct = storage->time_explosion * c; +# double doppler_factor = rpacket_doppler_factor (packet, storage); +# double comov_nu = nu * doppler_factor; +# if ( (nu_diff = comov_nu - nu_line) >= 0) +# { +# if (!storage->full_relativity) +# { +# distance = (nu_diff / nu) * ct; +# } +# else +# { +# double nu_r = nu_line / nu; +# distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - +# (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r); +# } +# rpacket_set_d_line (packet, distance); +# return TARDIS_ERROR_OK; +# } +# else +# { +# if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) +# { +# fprintf (stderr, "last_line = %f\n", +# storage-> +# line_list_nu[rpacket_get_next_line_id (packet) - 1]); +# fprintf (stderr, "Last line in line list reached!"); +# } +# else if (rpacket_get_next_line_id (packet) == 0) +# { +# fprintf (stderr, "First line in line list!"); +# fprintf (stderr, "next_line = %f\n", +# storage-> +# line_list_nu[rpacket_get_next_line_id (packet) + 1]); +# } +# else +# { +# fprintf (stderr, "last_line = %f\n", +# storage-> +# line_list_nu[rpacket_get_next_line_id (packet) - 1]); +# fprintf (stderr, "next_line = %f\n", +# storage-> +# line_list_nu[rpacket_get_next_line_id (packet) + 1]); +# } +# fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); +# fprintf (stderr, "comov_nu = %f\n", comov_nu); +# fprintf (stderr, "nu_line = %f\n", nu_line); +# fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", +# (comov_nu - nu_line) / nu_line); +# fprintf (stderr, "r = %f\n", r); +# fprintf (stderr, "mu = %f\n", mu); +# fprintf (stderr, "nu = %f\n", nu); +# fprintf (stderr, "doppler_factor = %f\n", doppler_factor); +# fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); +# return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; +# } +# } +# else +# { +# rpacket_set_d_line (packet, MISS_DISTANCE); +# return TARDIS_ERROR_OK; +# } +# } - chi_electron = storage.electron_densities[rpacket_get_current_shell_id(packet)] * storage.sigma_thomson +@njit +def compute_distance2continuum(packet, storage): + # chi_continuum = 0.0 + # d_continuum = 0.0 + # chi_electron = storage.electron_densities[packet.shell_id] * storage.sigma_thomson - if (storange.full_relativity): - chi_electron *= rpacket_doppler_factor(packet, storage) + # if (storage.full_relativity): + # chi_electron *= packet.doppler_factor(storage) - if (storage.cont_status == CONTINUUM_ON): - if (packet.compute_chi_bf): - calculate_chi_bf(packet, storage) - calculate_chi_ff(packet, storage) - else: - packet.compute_chi_bf = True - - chi_continuum = rpacket_get_chi_boundfree(packet) + rpacket_get_chi_freefree(packet) + chi_electron - d_continuum = rpacket_get_tau_event(packet) / chi_continuum - else: - chi_continuum = chi_electron - d_continuum = storage.inverse_electron_densities[rpacket_get_current_shell_id(packet)] * \ - storage.inverse_sigma_thomson * rpacket_get_tau_event(packet) + # if (storage.cont_status == CONTINUUM_ON): + # if (packet.compute_chi_bf): + # calculate_chi_bf(packet, storage) + # calculate_chi_ff(packet, storage) + # else: + # packet.compute_chi_bf = True + # chi_continuum = packet.chi_boundfree + packet.chi_freefree + chi_electron + # d_continuum = packet.tau_event / chi_continuum - if (rpacket_get_virtual_packet(packet) > 0): - # set all continuum distances to MISS_DISTANCE in case of a virtual_packet - d_continuum = MISS_DISTANCE - packet.compute_chi_bf = False - else: - rpacket_set_chi_electron(packet, chi_continuum) + # else: + # chi_continuum = chi_electron + # d_continuum = storage.inverse_electron_densities[packet.shell_id] * \ + # storage.inverse_sigma_thomson * packet.tau_event - rpacket_set_chi_continuum(packet, chi_continuum) - rpacket_set_d_continuum(packet, d_continuum) + # if (packet.virtual_packet > 0): + # # set all continuum distances to MISS_DISTANCE in case of a virtual_packet + # d_continuum = MISS_DISTANCE + # packet.compute_chi_bf = False + # else: + # packet.chi_electron = chi_electron + packet.d_continuum = storage.inverse_electron_densities[packet.shell_id] * \ + storage.inverse_sigma_thomson * packet.tau_event -void -compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) -{ - double chi_continuum, d_continuum; - double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * - storage->sigma_thomson; - if (storage->full_relativity) - { - chi_electron *= rpacket_doppler_factor (packet, storage); - } +# void +# compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) +# { +# double chi_continuum, d_continuum; +# double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * +# storage->sigma_thomson; +# if (storage->full_relativity) +# { +# chi_electron *= rpacket_doppler_factor (packet, storage); +# } - if (storage->cont_status == CONTINUUM_ON) - { - if (packet->compute_chi_bf) - { - calculate_chi_bf (packet, storage); - calculate_chi_ff (packet, storage); - } - else - { - packet->compute_chi_bf=true; - } - chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; - d_continuum = rpacket_get_tau_event (packet) / chi_continuum; - } - else - { - chi_continuum = chi_electron; - d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * - storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); - } +# if (storage->cont_status == CONTINUUM_ON) +# { +# if (packet->compute_chi_bf) +# { +# calculate_chi_bf (packet, storage); +# calculate_chi_ff (packet, storage); +# } +# else +# { +# packet->compute_chi_bf=true; +# } +# chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; +# d_continuum = rpacket_get_tau_event (packet) / chi_continuum; +# } +# else +# { +# chi_continuum = chi_electron; +# d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * +# storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); +# } - if (rpacket_get_virtual_packet(packet) > 0) - { - //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet - d_continuum = MISS_DISTANCE; - packet->compute_chi_bf = false; - } - else - { +# if (rpacket_get_virtual_packet(packet) > 0) +# { +# //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet +# d_continuum = MISS_DISTANCE; +# packet->compute_chi_bf = false; +# } +# else +# { - // fprintf(stderr, "--------\n"); - // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); - // fprintf(stderr, "chi_electron = %e\n", chi_electron); - // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); - // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); - // fprintf(stderr, "--------\n"); +# // fprintf(stderr, "--------\n"); +# // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); +# // fprintf(stderr, "chi_electron = %e\n", chi_electron); +# // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); +# // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); +# // fprintf(stderr, "--------\n"); - //rpacket_set_chi_freefree(packet, chi_freefree); - rpacket_set_chi_electron (packet, chi_electron); - } - rpacket_set_chi_continuum (packet, chi_continuum); - rpacket_set_d_continuum (packet, d_continuum); -} \ No newline at end of file +# //rpacket_set_chi_freefree(packet, chi_freefree); +# rpacket_set_chi_electron (packet, chi_electron); +# } +# rpacket_set_chi_continuum (packet, chi_continuum); +# rpacket_set_d_continuum (packet, d_continuum); +# } \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py new file mode 100644 index 00000000000..e382c58e5ed --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -0,0 +1,64 @@ +def montecarlo_packet_loop(storage, packet, virtual_packet, mt_state): + packet.tau_event = 0.0 + packet.nu_line = 0.0 + packet.virtual_packet = virtual_packet + packet.status = TARDIS_PACKET_STATUS_IN_PROCESS + # intializing tau_event if it's a real packet + if (virtual_packet == 0): + packet.reset_tau_event(mt_state) + + +int64_t +montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, + int64_t virtual_packet, rk_state *mt_state) +{ + rpacket_set_tau_event (packet, 0.0); + rpacket_set_nu_line (packet, 0.0); + rpacket_set_virtual_packet (packet, virtual_packet); + rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); + // Initializing tau_event if it's a real packet. + if (virtual_packet == 0) + { + rpacket_reset_tau_event (packet,mt_state); + } + // For a virtual packet tau_event is the sum of all the tau's that the packet passes. + while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) + { + // Check if we are at the end of line list. + if (!rpacket_get_last_line (packet)) + { + rpacket_set_nu_line (packet, + storage-> + line_list_nu[rpacket_get_next_line_id + (packet)]); + } + double distance; + get_event_handler (packet, storage, &distance, mt_state) (packet, storage, + distance, mt_state); + if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) + { + double event_random = rk_double (mt_state); + if (event_random > storage->survival_probability) + { + rpacket_set_energy(packet, 0.0); + rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); + } + else + { + rpacket_set_energy(packet, + rpacket_get_energy (packet) / storage->survival_probability * + exp (-1.0 * rpacket_get_tau_event (packet))); + rpacket_set_tau_event (packet, 0.0); + } + } + } + if (virtual_packet > 0) + { + rpacket_set_energy (packet, + rpacket_get_energy (packet) * exp (-1.0 * + rpacket_get_tau_event + (packet))); + } + return rpacket_get_status (packet) == + TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; +} diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index 6ceef7694ad..f1ae4114934 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -1,172 +1,53 @@ from numba import int64, float64 +from astropy import constants as const storage_model_spec = [ - ('*packet_nus', double), - ('*packet_mus', double), - ('*packet_energies', double), - ('*output_nus', double), - ('*output_energies', double), - ('*last_interaction_in_nu', double), - ('*last_line_interaction_in_id', int64_t), - ('*last_line_interaction_out_id', int64_t), - ('*last_line_interaction_shell_id', int64_t), - ('*last_interaction_type', int64_t), - ('*last_interaction_out_type', int64_t), - ('no_of_packets', int64_t), - ('no_of_shells', int64_t), - ('no_of_shells_i', int64_t), - ('*r_inner', double), - ('*r_outer', double), - ('*r_inner_i', double), - ('*r_outer_i', double), - ('*v_inner', double), - ('time_explosion', double), - ('inverse_time_explosion', double), - ('*electron_densities', double), - ('*electron_densities_i', double), - ('*inverse_electron_densities', double), - ('*line_list_nu', double), - ('*continuum_list_nu', double), - ('*line_lists_tau_sobolevs', double), - ('*line_lists_tau_sobolevs_i', double), - ('line_lists_tau_sobolevs_nd', int64_t), - ('*line_lists_j_blues', double), - ('line_lists_j_blues_nd', int64_t), - ('*line_lists_Edotlu', double), - ('no_of_lines', int64_t), - ('no_of_edges', int64_t), - ('line_interaction_id', int64_t), - ('*transition_probabilities', double), - ('transition_probabilities_nd', int64_t), - ('*line2macro_level_upper', int64_t), - ('*macro_block_references', int64_t), - ('*transition_type', int64_t), - ('*destination_level_id', int64_t), - ('*transition_line_id', int64_t), - ('*js', double), - ('*nubars', double), - ('spectrum_start_nu', double), - ('spectrum_delta_nu', double), - ('spectrum_end_nu', double), - ('spectrum_virt_start_nu', double), - ('spectrum_virt_end_nu', double), - ('*spectrum_virt_nu', double), - ('sigma_thomson', double), - ('inverse_sigma_thomson', double), - ('inner_boundary_albedo', double), - ('reflective_inner_boundary', int64_t), - ('current_packet_id', int64_t), - photo_xsect_1level **photo_xsect; - ('*chi_ff_factor', double), - ('*t_electrons', double), - ('*l_pop', double), - ('*l_pop_r', double), - ContinuumProcessesStatus cont_status; - bound_free_treatment bf_treatment; - ('*virt_packet_nus', double), - ('*virt_packet_energies', double), - ('*virt_packet_last_interaction_in_nu', double), - ('*virt_packet_last_interaction_type', int64_t), - ('*virt_packet_last_line_interaction_in_id', int64_t), - ('*virt_packet_last_line_interaction_out_id', int64_t), - ('virt_packet_count', int64_t), - ('virt_array_size', int64_t), - ('kpacket2macro_level', int64_t), - ('*cont_edge2macro_level', int64_t), - ('*photo_ion_estimator', double), - ('*stim_recomb_estimator', double), - ('*photo_ion_estimator_statistics', int64_t), - ('*bf_heating_estimator', double), - ('*ff_heating_estimator', double), - ('*stim_recomb_cooling_estimator', double), - ('full_relativity', int), - ('survival_probability', double), - ('tau_russian', double), - ('*tau_bias', double), - ('enable_biasing', int), + ('packet_nus', float64[:]), + ('packet_mus', float64[:]), + ('packet_energies', float64[:]), + ('output_nus', float64[:]), + ('output_energies', float64[:]), + ('no_of_packets', int64), + ('no_of_shells', int64), + ('r_inner', float64[:]), + ('r_outer', float64[:]), + ('v_inner', float64[:]), + ('time_explosion', float64), + ('inverse_time_explosion', float64), + ('*electron_densities', float64), + ('*inverse_electron_densities', float64), + ('*line_list_nu', float64), + ('*line_lists_tau_sobolevs', float64), + ('line_lists_tau_sobolevs_nd', int64), + ('no_of_lines', int64), + ('no_of_edges', int64), + ('line_interaction_id', int64), +# ('*js', float64), +# ('*nubars', float64), + ('sigma_thomson', float64), + ('inverse_sigma_thomson', float64), ] -typedef struct StorageModel -{ - double *packet_nus; - double *packet_mus; - double *packet_energies; - double *output_nus; - double *output_energies; - double *last_interaction_in_nu; - int64_t *last_line_interaction_in_id; - int64_t *last_line_interaction_out_id; - int64_t *last_line_interaction_shell_id; - int64_t *last_interaction_type; - int64_t *last_interaction_out_type; - int64_t no_of_packets; - int64_t no_of_shells; - int64_t no_of_shells_i; - double *r_inner; - double *r_outer; - double *r_inner_i; - double *r_outer_i; - double *v_inner; - double time_explosion; - double inverse_time_explosion; - double *electron_densities; - double *electron_densities_i; - double *inverse_electron_densities; - double *line_list_nu; - double *continuum_list_nu; - double *line_lists_tau_sobolevs; - double *line_lists_tau_sobolevs_i; - int64_t line_lists_tau_sobolevs_nd; - double *line_lists_j_blues; - int64_t line_lists_j_blues_nd; - double *line_lists_Edotlu; - int64_t no_of_lines; - int64_t no_of_edges; - int64_t line_interaction_id; - double *transition_probabilities; - int64_t transition_probabilities_nd; - int64_t *line2macro_level_upper; - int64_t *macro_block_references; - int64_t *transition_type; - int64_t *destination_level_id; - int64_t *transition_line_id; - double *js; - double *nubars; - double spectrum_start_nu; - double spectrum_delta_nu; - double spectrum_end_nu; - double spectrum_virt_start_nu; - double spectrum_virt_end_nu; - double *spectrum_virt_nu; - double sigma_thomson; - double inverse_sigma_thomson; - double inner_boundary_albedo; - int64_t reflective_inner_boundary; - int64_t current_packet_id; - photo_xsect_1level **photo_xsect; - double *chi_ff_factor; - double *t_electrons; - double *l_pop; - double *l_pop_r; - ContinuumProcessesStatus cont_status; - bound_free_treatment bf_treatment; - double *virt_packet_nus; - double *virt_packet_energies; - double *virt_packet_last_interaction_in_nu; - int64_t *virt_packet_last_interaction_type; - int64_t *virt_packet_last_line_interaction_in_id; - int64_t *virt_packet_last_line_interaction_out_id; - int64_t virt_packet_count; - int64_t virt_array_size; - int64_t kpacket2macro_level; - int64_t *cont_edge2macro_level; - double *photo_ion_estimator; - double *stim_recomb_estimator; - int64_t *photo_ion_estimator_statistics; - double *bf_heating_estimator; - double *ff_heating_estimator; - double *stim_recomb_cooling_estimator; - int full_relativity; - double survival_probability; - double tau_russian; - double *tau_bias; - int enable_biasing; -} storage_model_t; \ No newline at end of file + +class StorageModel(object): + def __init__(self, packet_nus, packet_mus, packet_energies, + output_nus, output_energies, no_of_packets, no_of_shells, + r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, line_lists_tau_sobolevs_nd, + no_of_lines, no_of_edges, line_interaction_id, + inverse_sigma_thomson): + self.packet_nus = packet_nus + self.packet_mus = packet_mus + self.packet_energies = packet_energies + self.output_nus = output_nus + self.output_energies = output_energies + self.r_inner = r_inner + self.r_outer = r_outer + self.v_inner = v_inner + + self.time_explosion = time_explosion + self.inverse_time_explosion = 1 / time_explosion + + self.electron_densities = electron_densities + + self.inverse_electron_densities = 1 / electron_densities + + self.sigma_thomson = const.sigma_T.to('cm^2').value + self.inverse_sigma_thomson = 1 / self.sigma_thomson From 7df78ae18274df04e89e10eec2169794cdb3f764 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 15:02:09 -0400 Subject: [PATCH 004/116] getting closer - TARDIS now fails Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- tardis/montecarlo/base.py | 4 +- tardis/montecarlo/montecarlo_numba/base.py | 109 +++--------- .../montecarlo_numba/packet_loop.py | 155 ++++++++++------- tardis/montecarlo/montecarlo_numba/rpacket.py | 157 ++++++++++++++++-- .../montecarlo_numba/storage_model.py | 34 +++- 5 files changed, 294 insertions(+), 165 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 23db0f435a2..fe50b53257b 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -16,6 +16,8 @@ from tardis.montecarlo import montecarlo, packet_source as source from tardis.montecarlo.formal_integral import FormalIntegrator +from tardis.montecarlo.montecarlo_numba import montecarlo_radial1d + import numpy as np logger = logging.getLogger(__name__) @@ -219,7 +221,7 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets) - + montecarlo_radial1d(model, plasma, self) #montecarlo.montecarlo_radial1d( # model, plasma, self, # virtual_packet_flag=no_of_virtual_packets, diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 7a336a916b7..48872c63f28 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,13 +1,15 @@ from numba import prange, njit -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus +from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel +from tardis.montecarlo.montecarlo_numba.packet_loop import one_packet_loop + + def montecarlo_radial1d(model, plasma, runner): - storage_model_kwargs = {'packet_nus', packet_mus, packet_energies, - output_nus, output_energies, no_of_packets, no_of_shells, - r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, line_lists_tau_sobolevs_nd, - no_of_lines, no_of_edges, line_interaction_id, - inverse_sigma_thomson} + storage_model = initialize_storage_model(model, plasma, runner) + montecarlo_main_loop(storage_model) + pass #montecarlo.montecarlo_radial1d( # model, plasma, self, @@ -19,87 +21,16 @@ def montecarlo_radial1d(model, plasma, runner): # but python expects (no_of_lines, no_of_shells) @njit -def montecarlo_main_loop(storage): +def montecarlo_main_loop(storage_model): for i in prange(storage.no_of_packets): - from - - -void -montecarlo_main_loop(storage_model_t * storage, int64_t virtual_packet_flag, int nthreads, unsigned long seed) -{ - int64_t finished_packets = 0; - storage->virt_packet_count = 0; -#ifdef WITH_VPACKET_LOGGING - storage->virt_packet_nus = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_energies = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_last_interaction_in_nu = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_last_interaction_type = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_packet_last_line_interaction_in_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_packet_last_line_interaction_out_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_array_size = storage->no_of_packets; -#endif // WITH_VPACKET_LOGGING -#ifdef WITHOPENMP - omp_set_dynamic(0); - if (nthreads > 0) - { - omp_set_num_threads(nthreads); - } - -#pragma omp parallel firstprivate(finished_packets) - { - rk_state mt_state; - rk_seed (seed + omp_get_thread_num(), &mt_state); -#pragma omp master - { - fprintf(stderr, "Running with OpenMP - %d threads\n", omp_get_num_threads()); - print_progress(0, storage->no_of_packets); - } -#else - rk_state mt_state; - rk_seed (seed, &mt_state); - fprintf(stderr, "Running without OpenMP\n"); -#endif - int64_t chi_bf_tmp_size = (storage->cont_status) ? storage->no_of_edges : 0; - double *chi_bf_tmp_partial = safe_malloc(sizeof(double) * chi_bf_tmp_size); - - #pragma omp for - for (int64_t packet_index = 0; packet_index < storage->no_of_packets; ++packet_index) - { - int reabsorbed = 0; - rpacket_t packet; - rpacket_set_id(&packet, packet_index); - rpacket_init(&packet, storage, packet_index, virtual_packet_flag, chi_bf_tmp_partial); - if (virtual_packet_flag > 0) - { - reabsorbed = montecarlo_one_packet(storage, &packet, -1, &mt_state); - } - reabsorbed = montecarlo_one_packet(storage, &packet, 0, &mt_state); - storage->output_nus[packet_index] = rpacket_get_nu(&packet); - if (reabsorbed == 1) - { - storage->output_energies[packet_index] = -rpacket_get_energy(&packet); - } - else - { - storage->output_energies[packet_index] = rpacket_get_energy(&packet); - } - if ( ++finished_packets%100 == 0 ) - { -#ifdef WITHOPENMP - // WARNING: This only works with a static sheduler and gives an approximation of progress. - // The alternative would be to have a shared variable but that could potentially decrease performance when using many threads. - if (omp_get_thread_num() == 0 ) - print_progress(finished_packets * omp_get_num_threads(), storage->no_of_packets); -#else - print_progress(finished_packets, storage->no_of_packets); -#endif - } - } - free(chi_bf_tmp_partial); -#ifdef WITHOPENMP - } -#endif - print_progress(storage->no_of_packets, storage->no_of_packets); - fprintf(stderr,"\n"); -} - + r_packet = RPacket(r=storage.r_inner[0], + mu=storage.packet_nus[i], + nu=storage.packet_mus[i], + energy=storage.packet_energies[i]) + r_packet_final_state = one_packet_loop(r_packet) + storage_model.output_nus[i] = rpacket.nu + if r_packet_final_state == PacketStatus.REABSORBED: + storage_model.output_energies[i] = -r_packet.energy + elif r_packet_final_state == PacketStatus.EMITTED: + storage_model.output_energies[i] = r_packet.energy + return None diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index e382c58e5ed..c9cff9d5e04 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -1,64 +1,105 @@ -def montecarlo_packet_loop(storage, packet, virtual_packet, mt_state): +from numba import njit +import numpy as np +from enum import Enum + +class PacketStatus(Enum): + IN_PROCESS = 0 + EMITTED = 1 + REABSORBED = 2 + +@njit +def one_packet_loop(storage, packet, virtual_packet, mt_state): packet.tau_event = 0.0 packet.nu_line = 0.0 packet.virtual_packet = virtual_packet - packet.status = TARDIS_PACKET_STATUS_IN_PROCESS - # intializing tau_event if it's a real packet - if (virtual_packet == 0): + packet.status = PacketStatus.IN_PROCESS + # initializing tau_event if it's a real packet + if virtual_packet == 0: packet.reset_tau_event(mt_state) + + # for a virtual packet tau_event is the sum of all the tau's that the packet passes + while packet.status == PacketStatus.IN_PROCESS: + # Check if we are at the end of line list. + if not packet.last_line: + packet.nu_line = storeage.line_list_nu[packet.next_line_id] + + # FIXME: get_event_handler(packet, storage, &distance, mt_state) (packet, storage, distance, mt_state) + + if virtual_packet > 0 and packet.tau_event > storage.tau_russian: + event_random = rk_double(mt_state) + + if event_random > storage.survival_probability: + packet.energy = 0.0 + packet.status = PacketStatus.EMITTED + else: + packet.energy = packet.energy / storage.survival_probability * \ + np.exp(-1.0 * packet.tau_event) + + if virtual_packet > 0: + packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) + if packet.status == PacketStatus.REABSORBED: + return 1 + else: + return 0 + + +def rpacket_interactions(rpacket): + if int64_t -montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, - int64_t virtual_packet, rk_state *mt_state) -{ - rpacket_set_tau_event (packet, 0.0); - rpacket_set_nu_line (packet, 0.0); - rpacket_set_virtual_packet (packet, virtual_packet); - rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); - // Initializing tau_event if it's a real packet. - if (virtual_packet == 0) - { - rpacket_reset_tau_event (packet,mt_state); - } - // For a virtual packet tau_event is the sum of all the tau's that the packet passes. - while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) - { - // Check if we are at the end of line list. - if (!rpacket_get_last_line (packet)) - { - rpacket_set_nu_line (packet, - storage-> - line_list_nu[rpacket_get_next_line_id - (packet)]); - } - double distance; - get_event_handler (packet, storage, &distance, mt_state) (packet, storage, - distance, mt_state); - if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) - { - double event_random = rk_double (mt_state); - if (event_random > storage->survival_probability) - { - rpacket_set_energy(packet, 0.0); - rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); - } - else - { - rpacket_set_energy(packet, - rpacket_get_energy (packet) / storage->survival_probability * - exp (-1.0 * rpacket_get_tau_event (packet))); - rpacket_set_tau_event (packet, 0.0); - } - } - } - if (virtual_packet > 0) - { - rpacket_set_energy (packet, - rpacket_get_energy (packet) * exp (-1.0 * - rpacket_get_tau_event - (packet))); - } - return rpacket_get_status (packet) == - TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; -} + +# Void +# montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, +# int64_t virtual_packet, rk_state *mt_state) +# { +# rpacket_set_tau_event (packet, 0.0); +# rpacket_set_nu_line (packet, 0.0); +# rpacket_set_virtual_packet (packet, virtual_packet); +# rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); +# // Initializing tau_event if it's a real packet. +# if (virtual_packet == 0) +# { +# rpacket_reset_tau_event (packet,mt_state); +# } +# // For a virtual packet tau_event is the sum of all the tau's that the packet passes. +# while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) +# { +# // Check if we are at the end of line list. +# if (!rpacket_get_last_line (packet)) +# { +# rpacket_set_nu_line (packet, +# storage-> +# line_list_nu[rpacket_get_next_line_id +# (packet)]); +# } +# double distance; +# get_event_handler (packet, storage, &distance, mt_state) (packet, storage, +# distance, mt_state); +# if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) +# { +# double event_random = rk_double (mt_state); +# if (event_random > storage->survival_probability) +# { +# rpacket_set_energy(packet, 0.0); +# rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); +# } +# else +# { +# rpacket_set_energy(packet, +# rpacket_get_energy (packet) / storage->survival_probability * +# exp (-1.0 * rpacket_get_tau_event (packet))); +# rpacket_set_tau_event (packet, 0.0); +# } +# } +# } +# if (virtual_packet > 0) +# { +# rpacket_set_energy (packet, +# rpacket_get_energy (packet) * exp (-1.0 * +# rpacket_get_tau_event +# (packet))); +# } +# return rpacket_get_status (packet) == +# TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; +# } diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index e5565475150..38d527c5623 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -1,27 +1,162 @@ +import numpy as np +from enum import Enum from numba import int64, float64 from numba import jitclass, njit +from tardis.montecarlo.montecarlo_numba.compute_distance import (compute_distance2boundary, + + compute_distance2continuum) + +from astropy import constants as const + + + +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + +class PacketStatus(Enum): + IN_PROCESS = 0 + EMITTED = 1 + REABSORBED = 2 + +class InteractionType(Enum): + ESCATTERING = 0 + BOUNDARY = 1 + LINE = 2 rpacket_spec = [ ('r', float64), ('mu', float64), - ('tau_event', float64) - ('nu_line', float64) - ('d_line', float64) # Distance to electron event. - ('d_electron', float64) #/**< Distance to line event. */ - ('d_boundary', float64) # distance to boundary - ('shell_id', int64) + ('nu', float64), + ('energy', float64) + ('tau_event', float64), + # ('nu_line', float64) + # ('d_line', float64) # Distance to electron event. + ('d_electron', float64), #/**< Distance to line event. */ + ('d_boundary', float64), # distance to boundary + ('current_shell_id', int64), + ('next_shell_id', int64), + ('next_interaction', enum) ] -@jitclass(rpacket_rspec) + +@jitclass(rpacket_spec) class RPacket(object): - def __init__(self, r, mu, tau_event): + def __init__(self, r, mu, nu): self.r = r self.mu = mu self.nu = nu - self.tau_event = tau_event - self.nu_line = nu_line + self.energy = energy + self.tau_event = self.get_tau_event() + #self.nu_line = line_search() # TODO: Implement this + self.current_shell_id = 0 + self.compute_distances() + #self.nu_line = nu_line @staticmethod def get_tau_event(): - return -np.log(np.random.random()) \ No newline at end of file + return np.random.exponential() + + def compute_distances(self, storage): + compute_distance2continuum(self, storage) + compute_distance2boundary(self, storage) + if packet.d_boundary < packet.d_electron: + next_interaction = InteractionType.BOUNDARY + else: + next_interaction = InteractionType.ESCATTERING + packet.next_interaction = next_interaction + +# double +# rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) +# { +# double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; +# if (!storage->full_relativity) +# { +# return 1.0 - rpacket_get_mu (packet) * beta; +# } +# else +# { +# return (1.0 - rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); +# } +# } + + def get_doppler_factor(self, storage): + beta = self.r * storage.inverse_time_explosion / C_SPEED_OF_LIGHT + + if not storage.full_relativity: + return 1.0 - self.mu * beta + else: + return (1.0 - self.mu * beta) / np.sqrt(1 - beta*beta) + + def move_packet(self, storage, distance): + doppler_factor = self.get_doppler_factor(storage) + r = packet.r + if (distance > 0.0): + new_r = np.sqrt (r * r + distance * distance + + 2.0 * r * distance * packet.mu) + packet.mu = (packet.mu * r + distance) / new_r + packet.r = new_r + + + def move_packet_across_shell_boundary(self, storage, distance, mt_state): + self.move_packet(storage, distance) + + if self.virtual_packet > 0: + delta_tau_event = self.chi_continuum * distance + self.tau_event = self.tau_event + delta_tau_event + self.compute_chi_bf = True + else: + self.reset_tau_event(mt_state) + + if ((self.current_shell_id < storage.no_of_shells - 1) and packet.next_shell_id == 1) or () + + void +move_packet_across_shell_boundary (rpacket_t * packet, + storage_model_t * storage, double distance, rk_state *mt_state) +{ + move_packet (packet, storage, distance); + if (rpacket_get_virtual_packet (packet) > 0) + { + double delta_tau_event = rpacket_get_chi_continuum(packet) * distance; + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) + + delta_tau_event); + packet->compute_chi_bf = true; + } + else + { + rpacket_reset_tau_event (packet, mt_state); + } + if ((rpacket_get_current_shell_id (packet) < storage->no_of_shells - 1 + && rpacket_get_next_shell_id (packet) == 1) + || (rpacket_get_current_shell_id (packet) > 0 + && rpacket_get_next_shell_id (packet) == -1)) + { + rpacket_set_current_shell_id (packet, + rpacket_get_current_shell_id (packet) + + rpacket_get_next_shell_id (packet)); + } + else if (rpacket_get_next_shell_id (packet) == 1) + { + rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); + } + else if ((storage->reflective_inner_boundary == 0) || + (rk_double (mt_state) > storage->inner_boundary_albedo)) + { + rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); + } + else + { + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + double comov_energy = rpacket_get_energy (packet) * doppler_factor; + // TODO: correct + rpacket_set_mu (packet, rk_double (mt_state)); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + montecarlo_one_packet (storage, packet, -2, mt_state); + } + } +} \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index f1ae4114934..edbe5a7b924 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -13,13 +13,11 @@ ('v_inner', float64[:]), ('time_explosion', float64), ('inverse_time_explosion', float64), - ('*electron_densities', float64), - ('*inverse_electron_densities', float64), - ('*line_list_nu', float64), - ('*line_lists_tau_sobolevs', float64), - ('line_lists_tau_sobolevs_nd', int64), + ('electron_densities', float64[:]), + ('inverse_electron_densities', float64[:]), # Maybe remove the inverse things + ('line_list_nu', float64[:]), + ('line_lists_tau_sobolevs', float64[:]), ('no_of_lines', int64), - ('no_of_edges', int64), ('line_interaction_id', int64), # ('*js', float64), # ('*nubars', float64), @@ -30,7 +28,7 @@ class StorageModel(object): def __init__(self, packet_nus, packet_mus, packet_energies, output_nus, output_energies, no_of_packets, no_of_shells, - r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, line_lists_tau_sobolevs_nd, + r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, no_of_lines, no_of_edges, line_interaction_id, inverse_sigma_thomson): self.packet_nus = packet_nus @@ -51,3 +49,25 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.sigma_thomson = const.sigma_T.to('cm^2').value self.inverse_sigma_thomson = 1 / self.sigma_thomson + +def initialize_storage_model(model, plasma, runner): + storage_model_kwargs = {'packet_nus': runner.input_nu, + 'packet_mus': runner.input_mu, + 'packet_energies': runner.input_energy, + 'output_nus': _output_nu, + 'output_energies': _output_energy, + 'no_of_packets': runner.input_nu.size, + 'no_of_shells': model.no_of_shells, + 'r_inner': runner.r_inner_cgs, + 'r_outer': runner.r_outer_cgs, + 'v_inner': runner.v_inner_cgs, + 'time_explosion': model.time_explosion.to('s').value, + 'electron_densities': plasma.electron_densities.values, + 'line_list_nu': plasma.atomic_data.lines.nu.values, + 'line_lists_tau_sobolevs': runner.line_lists_tau_sobolevs, + 'no_of_lines': plasma.atomic_data.lines.nu.values.size, + 'line_interaction_id': runner.get_line_interaction_id( + runner.line_interaction_type), + 'inverse_sigma_thomson': 1.0 / storage.sigma_thomson} + + return StorageModel(**storage_model_kwargs)) \ No newline at end of file From 90bfb229708460588c55ef581143de1c65b28700 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 16:40:27 -0400 Subject: [PATCH 005/116] final numba test - seems to work. Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- .../montecarlo/montecarlo_numba/__init__.py | 7 +- tardis/montecarlo/montecarlo_numba/base.py | 38 ++- .../montecarlo_numba/compute_distance.py | 218 ++---------------- .../montecarlo_numba/packet_loop.py | 112 +++------ tardis/montecarlo/montecarlo_numba/rpacket.py | 163 ++++--------- .../montecarlo_numba/storage_model.py | 25 +- 6 files changed, 132 insertions(+), 431 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index ca0b060eaf2..3190e8d8620 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1 +1,6 @@ -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket \ No newline at end of file +from llvmlite import binding +binding.set_option("tmp", "-non-global-value-max-name-size=2048") + +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket +from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d + diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 48872c63f28..b734b2a6172 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,7 +1,6 @@ - from numba import prange, njit -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus -from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, REABSORBED, EMITTED +from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel, initialize_storage_model from tardis.montecarlo.montecarlo_numba.packet_loop import one_packet_loop @@ -9,28 +8,21 @@ def montecarlo_radial1d(model, plasma, runner): storage_model = initialize_storage_model(model, plasma, runner) montecarlo_main_loop(storage_model) - - pass - #montecarlo.montecarlo_radial1d( - # model, plasma, self, - # virtual_packet_flag=no_of_virtual_packets, - # nthreads=nthreads, - # last_run=last_run) - # Workaround so that j_blue_estimator is in the right ordering - # They are written as an array of dimension (no_of_shells, no_of_lines) - # but python expects (no_of_lines, no_of_shells) @njit def montecarlo_main_loop(storage_model): - for i in prange(storage.no_of_packets): - r_packet = RPacket(r=storage.r_inner[0], - mu=storage.packet_nus[i], - nu=storage.packet_mus[i], - energy=storage.packet_energies[i]) - r_packet_final_state = one_packet_loop(r_packet) - storage_model.output_nus[i] = rpacket.nu - if r_packet_final_state == PacketStatus.REABSORBED: + + for i in prange(storage_model.no_of_packets): + r_packet = RPacket(storage_model.r_inner[0], + storage_model.packet_mus[i], + storage_model.packet_nus[i], + storage_model.packet_energies[i]) + r_packet.compute_distances(storage_model) + one_packet_loop(storage_model, r_packet) + storage_model.output_nus[i] = r_packet.nu + #print('!!!! THIS IS THE FINAL STATE !!!!!!!', r_packet_final_state) + #return + if r_packet.status == REABSORBED: storage_model.output_energies[i] = -r_packet.energy - elif r_packet_final_state == PacketStatus.EMITTED: + elif r_packet.status == EMITTED: storage_model.output_energies[i] = r_packet.energy - return None diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index d39451e913b..4588ef0a4c8 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -8,69 +8,37 @@ def compute_distance2boundary(packet, storage): r = packet.r mu = packet.mu - r_outer = storage.r_outer[packet.shell_id] - r_inner = storage.r_inner[packet.shell_id] + r_outer = storage.r_outer[packet.current_shell_id] + r_inner = storage.r_inner[packet.current_shell_id] if (mu > 0.0): # direction outward - packet.shell_id += 1 + packet.delta_shell_id = +1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) else: # going inward - check = r_inner * r_inner + (r * r * (mu * my - 1.0)) + check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) if (check >= 0.0): # hit inner boundary - packet.shell_id -= 1 + packet.delta_shell_id = -1 distance = -r * mu - np.sqrt(check) else: # miss inner boundary - packet.shell_id += 1 + packet.delta_shell_id = + 1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) packet.d_boundary = distance - -# void -# compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) -# { -# double r = rpacket_get_r (packet); -# double mu = rpacket_get_mu (packet); -# double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; -# double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; -# double check, distance; -# if (mu > 0.0) -# { // direction outward -# rpacket_set_next_shell_id (packet, 1); -# distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); -# } -# else -# { // going inward -# if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) -# { // hit inner boundary -# rpacket_set_next_shell_id (packet, -1); -# distance = - r * mu - sqrt (check); -# } -# else -# { // miss inner boundary -# rpacket_set_next_shell_id (packet, 1); -# distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); -# } -# } -# rpacket_set_d_boundary (packet, distance); -# } - -# tardis_error_t - - +""" @njit -def compute_distance2line(rpacket, storage, c=C_SPEED_OF_LIGHT): - if not rpacket_get_last_line(packet): - r = rpacket.r - mu = rpacket.mu - nu = rpacket.nu - nu_line = rpacket_get_nu_line(packet) +def compute_distance2line(packet, storage, c=C_SPEED_OF_LIGHT): + if not packet.last_line: + r = packet.r + mu = packet.mu + nu = packet.nu + nu_line = packet.nu_line distance = 0.0 nu_diff = 0.0 @@ -93,7 +61,7 @@ def compute_distance2line(rpacket, storage, c=C_SPEED_OF_LIGHT): if rpacket_get_next_line_id(packet) == storage.no_of_lines - 1: print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) print("Last line in line list reached!") - else if rpacket_get_next_line_id(packet) == 0: + elif rpacket_get_next_line_id(packet) == 0: print("First line in line list!") print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) else: @@ -114,163 +82,11 @@ def compute_distance2line(rpacket, storage, c=C_SPEED_OF_LIGHT): return TARDIS_ERROR_OK - -# compute_distance2line (rpacket_t * packet, const storage_model_t * storage) -# { -# if (!rpacket_get_last_line (packet)) -# { -# double r = rpacket_get_r (packet); -# double mu = rpacket_get_mu (packet); -# double nu = rpacket_get_nu (packet); -# double nu_line = rpacket_get_nu_line (packet); -# double distance, nu_diff; -# double ct = storage->time_explosion * c; -# double doppler_factor = rpacket_doppler_factor (packet, storage); -# double comov_nu = nu * doppler_factor; -# if ( (nu_diff = comov_nu - nu_line) >= 0) -# { -# if (!storage->full_relativity) -# { -# distance = (nu_diff / nu) * ct; -# } -# else -# { -# double nu_r = nu_line / nu; -# distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - -# (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r); -# } -# rpacket_set_d_line (packet, distance); -# return TARDIS_ERROR_OK; -# } -# else -# { -# if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) -# { -# fprintf (stderr, "last_line = %f\n", -# storage-> -# line_list_nu[rpacket_get_next_line_id (packet) - 1]); -# fprintf (stderr, "Last line in line list reached!"); -# } -# else if (rpacket_get_next_line_id (packet) == 0) -# { -# fprintf (stderr, "First line in line list!"); -# fprintf (stderr, "next_line = %f\n", -# storage-> -# line_list_nu[rpacket_get_next_line_id (packet) + 1]); -# } -# else -# { -# fprintf (stderr, "last_line = %f\n", -# storage-> -# line_list_nu[rpacket_get_next_line_id (packet) - 1]); -# fprintf (stderr, "next_line = %f\n", -# storage-> -# line_list_nu[rpacket_get_next_line_id (packet) + 1]); -# } -# fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); -# fprintf (stderr, "comov_nu = %f\n", comov_nu); -# fprintf (stderr, "nu_line = %f\n", nu_line); -# fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", -# (comov_nu - nu_line) / nu_line); -# fprintf (stderr, "r = %f\n", r); -# fprintf (stderr, "mu = %f\n", mu); -# fprintf (stderr, "nu = %f\n", nu); -# fprintf (stderr, "doppler_factor = %f\n", doppler_factor); -# fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); -# return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; -# } -# } -# else -# { -# rpacket_set_d_line (packet, MISS_DISTANCE); -# return TARDIS_ERROR_OK; -# } -# } + """ + @njit def compute_distance2continuum(packet, storage): - # chi_continuum = 0.0 - # d_continuum = 0.0 - # chi_electron = storage.electron_densities[packet.shell_id] * storage.sigma_thomson - - # if (storage.full_relativity): - # chi_electron *= packet.doppler_factor(storage) - - # if (storage.cont_status == CONTINUUM_ON): - # if (packet.compute_chi_bf): - # calculate_chi_bf(packet, storage) - # calculate_chi_ff(packet, storage) - # else: - # packet.compute_chi_bf = True - # chi_continuum = packet.chi_boundfree + packet.chi_freefree + chi_electron - # d_continuum = packet.tau_event / chi_continuum - - # else: - # chi_continuum = chi_electron - # d_continuum = storage.inverse_electron_densities[packet.shell_id] * \ - # storage.inverse_sigma_thomson * packet.tau_event - - # if (packet.virtual_packet > 0): - # # set all continuum distances to MISS_DISTANCE in case of a virtual_packet - # d_continuum = MISS_DISTANCE - # packet.compute_chi_bf = False - # else: - # packet.chi_electron = chi_electron - - packet.d_continuum = storage.inverse_electron_densities[packet.shell_id] * \ + packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ storage.inverse_sigma_thomson * packet.tau_event -# void -# compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) -# { -# double chi_continuum, d_continuum; -# double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * -# storage->sigma_thomson; -# if (storage->full_relativity) -# { -# chi_electron *= rpacket_doppler_factor (packet, storage); -# } - -# if (storage->cont_status == CONTINUUM_ON) -# { -# if (packet->compute_chi_bf) -# { -# calculate_chi_bf (packet, storage); -# calculate_chi_ff (packet, storage); -# } -# else -# { -# packet->compute_chi_bf=true; -# } -# chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; -# d_continuum = rpacket_get_tau_event (packet) / chi_continuum; -# } -# else -# { -# chi_continuum = chi_electron; -# d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * -# storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); -# } - -# if (rpacket_get_virtual_packet(packet) > 0) -# { -# //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet -# d_continuum = MISS_DISTANCE; -# packet->compute_chi_bf = false; -# } -# else -# { - -# // fprintf(stderr, "--------\n"); -# // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); -# // fprintf(stderr, "chi_electron = %e\n", chi_electron); -# // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); -# // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); -# // fprintf(stderr, "--------\n"); - -# //rpacket_set_chi_freefree(packet, chi_freefree); -# rpacket_set_chi_electron (packet, chi_electron); -# } -# rpacket_set_chi_continuum (packet, chi_continuum); -# rpacket_set_d_continuum (packet, d_continuum); -# } \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index c9cff9d5e04..35336b1d7e1 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -1,105 +1,51 @@ from numba import njit import numpy as np from enum import Enum - -class PacketStatus(Enum): - IN_PROCESS = 0 - EMITTED = 1 - REABSORBED = 2 +from tardis.montecarlo.montecarlo_numba.rpacket import ( + ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) @njit -def one_packet_loop(storage, packet, virtual_packet, mt_state): - packet.tau_event = 0.0 - packet.nu_line = 0.0 - packet.virtual_packet = virtual_packet - packet.status = PacketStatus.IN_PROCESS +def one_packet_loop(storage_model, r_packet): + r_packet.tau_event = 0.0 + r_packet.nu_line = 0.0 + #packet.virtual_packet = virtual_packet + r_packet.status = IN_PROCESS # initializing tau_event if it's a real packet - if virtual_packet == 0: - packet.reset_tau_event(mt_state) # for a virtual packet tau_event is the sum of all the tau's that the packet passes - while packet.status == PacketStatus.IN_PROCESS: + while r_packet.status == IN_PROCESS: + rpacket_interactions(r_packet, storage_model) # Check if we are at the end of line list. - if not packet.last_line: - packet.nu_line = storeage.line_list_nu[packet.next_line_id] + #if not packet.last_line: + # packet.nu_line = storeage.line_list_nu[packet.next_line_id] # FIXME: get_event_handler(packet, storage, &distance, mt_state) (packet, storage, distance, mt_state) - if virtual_packet > 0 and packet.tau_event > storage.tau_russian: - event_random = rk_double(mt_state) + # if virtual_packet > 0 and packet.tau_event > storage.tau_russian: + # event_random = rk_double(mt_state) - if event_random > storage.survival_probability: - packet.energy = 0.0 - packet.status = PacketStatus.EMITTED - else: - packet.energy = packet.energy / storage.survival_probability * \ - np.exp(-1.0 * packet.tau_event) + # if event_random > storage.survival_probability: + # packet.energy = 0.0 + # packet.status = PacketStatus.EMITTED + # else: + # packet.energy = packet.energy / storage.survival_probability * \ + # np.exp(-1.0 * packet.tau_event) - if virtual_packet > 0: - packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) + #if virtual_packet > 0: + # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) - if packet.status == PacketStatus.REABSORBED: + if r_packet.status == REABSORBED: return 1 else: return 0 +@njit +def rpacket_interactions(r_packet, storage_model): + r_packet.move_packet_across_shell_boundary(storage_model) + #if packet.next_interaction == InteractionType.BOUNDARY: + # pass + #else: + # pass -def rpacket_interactions(rpacket): - if -int64_t -# Void -# montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, -# int64_t virtual_packet, rk_state *mt_state) -# { -# rpacket_set_tau_event (packet, 0.0); -# rpacket_set_nu_line (packet, 0.0); -# rpacket_set_virtual_packet (packet, virtual_packet); -# rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); -# // Initializing tau_event if it's a real packet. -# if (virtual_packet == 0) -# { -# rpacket_reset_tau_event (packet,mt_state); -# } -# // For a virtual packet tau_event is the sum of all the tau's that the packet passes. -# while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) -# { -# // Check if we are at the end of line list. -# if (!rpacket_get_last_line (packet)) -# { -# rpacket_set_nu_line (packet, -# storage-> -# line_list_nu[rpacket_get_next_line_id -# (packet)]); -# } -# double distance; -# get_event_handler (packet, storage, &distance, mt_state) (packet, storage, -# distance, mt_state); -# if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) -# { -# double event_random = rk_double (mt_state); -# if (event_random > storage->survival_probability) -# { -# rpacket_set_energy(packet, 0.0); -# rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); -# } -# else -# { -# rpacket_set_energy(packet, -# rpacket_get_energy (packet) / storage->survival_probability * -# exp (-1.0 * rpacket_get_tau_event (packet))); -# rpacket_set_tau_event (packet, 0.0); -# } -# } -# } -# if (virtual_packet > 0) -# { -# rpacket_set_energy (packet, -# rpacket_get_energy (packet) * exp (-1.0 * -# rpacket_get_tau_event -# (packet))); -# } -# return rpacket_get_status (packet) == -# TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; -# } diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 38d527c5623..05ae3cce1cd 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -3,160 +3,99 @@ from numba import int64, float64 from numba import jitclass, njit from tardis.montecarlo.montecarlo_numba.compute_distance import (compute_distance2boundary, - compute_distance2continuum) from astropy import constants as const - - C_SPEED_OF_LIGHT = const.c.to('cm/s').value -class PacketStatus(Enum): - IN_PROCESS = 0 - EMITTED = 1 - REABSORBED = 2 -class InteractionType(Enum): - ESCATTERING = 0 - BOUNDARY = 1 - LINE = 2 +#class PacketStatus(Enum): +IN_PROCESS = 0 +EMITTED = 1 +REABSORBED = 2 +#class InteractionType(Enum): +ESCATTERING = 0 +BOUNDARY = 1 +LINE = 2 rpacket_spec = [ ('r', float64), ('mu', float64), ('nu', float64), - ('energy', float64) + ('energy', float64), ('tau_event', float64), - # ('nu_line', float64) + ('nu_line', float64), # ('d_line', float64) # Distance to electron event. ('d_electron', float64), #/**< Distance to line event. */ ('d_boundary', float64), # distance to boundary ('current_shell_id', int64), - ('next_shell_id', int64), - ('next_interaction', enum) + ('delta_shell_id', int64), + ('next_interaction', int64), + ('status', int64), + ('distance', float64) ] +@njit +def get_tau_event(): + return np.random.exponential() + @jitclass(rpacket_spec) class RPacket(object): - def __init__(self, r, mu, nu): + def __init__(self, r, mu, nu, energy): self.r = r self.mu = mu self.nu = nu self.energy = energy - self.tau_event = self.get_tau_event() + self.tau_event = get_tau_event() #self.nu_line = line_search() # TODO: Implement this self.current_shell_id = 0 - self.compute_distances() + self.delta_shell_id = 0 + self.d_boundary = -1.0 + self.d_electron = -1.0 + self.distance = 0.0 #self.nu_line = nu_line - - @staticmethod - def get_tau_event(): - return np.random.exponential() def compute_distances(self, storage): compute_distance2continuum(self, storage) compute_distance2boundary(self, storage) - if packet.d_boundary < packet.d_electron: - next_interaction = InteractionType.BOUNDARY + if self.d_boundary < self.d_electron: + next_interaction = BOUNDARY + self.distance = self.d_boundary else: - next_interaction = InteractionType.ESCATTERING - packet.next_interaction = next_interaction + next_interaction = ESCATTERING + # TODO: Fixme + self.distance = self.d_boundary + + self.next_interaction = next_interaction -# double -# rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) -# { -# double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; -# if (!storage->full_relativity) -# { -# return 1.0 - rpacket_get_mu (packet) * beta; -# } -# else -# { -# return (1.0 - rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); -# } -# } def get_doppler_factor(self, storage): beta = self.r * storage.inverse_time_explosion / C_SPEED_OF_LIGHT - if not storage.full_relativity: - return 1.0 - self.mu * beta - else: - return (1.0 - self.mu * beta) / np.sqrt(1 - beta*beta) + return 1.0 - self.mu * beta + #else: + # return (1.0 - self.mu * beta) / np.sqrt(1 - beta*beta) def move_packet(self, storage, distance): doppler_factor = self.get_doppler_factor(storage) - r = packet.r + r = self.r if (distance > 0.0): - new_r = np.sqrt (r * r + distance * distance + - 2.0 * r * distance * packet.mu) - packet.mu = (packet.mu * r + distance) / new_r - packet.r = new_r - - - def move_packet_across_shell_boundary(self, storage, distance, mt_state): - self.move_packet(storage, distance) - - if self.virtual_packet > 0: - delta_tau_event = self.chi_continuum * distance - self.tau_event = self.tau_event + delta_tau_event - self.compute_chi_bf = True + new_r = np.sqrt(r * r + distance * distance + + 2.0 * r * distance * self.mu) + self.mu = (self.mu * r + distance) / new_r + self.r = new_r + + def move_packet_across_shell_boundary(self, storage): + self.move_packet(storage, self.distance) + + get_tau_event() + if ((self.current_shell_id < storage.no_of_shells - 1 and self.delta_shell_id == 1) + or (self.current_shell_id > 0 and self.delta_shell_id == -1)): + self.current_shell_id += self.delta_shell_id + elif self.delta_shell_id == 1: + self.status = EMITTED else: - self.reset_tau_event(mt_state) - - if ((self.current_shell_id < storage.no_of_shells - 1) and packet.next_shell_id == 1) or () - - void -move_packet_across_shell_boundary (rpacket_t * packet, - storage_model_t * storage, double distance, rk_state *mt_state) -{ - move_packet (packet, storage, distance); - if (rpacket_get_virtual_packet (packet) > 0) - { - double delta_tau_event = rpacket_get_chi_continuum(packet) * distance; - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) + - delta_tau_event); - packet->compute_chi_bf = true; - } - else - { - rpacket_reset_tau_event (packet, mt_state); - } - if ((rpacket_get_current_shell_id (packet) < storage->no_of_shells - 1 - && rpacket_get_next_shell_id (packet) == 1) - || (rpacket_get_current_shell_id (packet) > 0 - && rpacket_get_next_shell_id (packet) == -1)) - { - rpacket_set_current_shell_id (packet, - rpacket_get_current_shell_id (packet) + - rpacket_get_next_shell_id (packet)); - } - else if (rpacket_get_next_shell_id (packet) == 1) - { - rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); - } - else if ((storage->reflective_inner_boundary == 0) || - (rk_double (mt_state) > storage->inner_boundary_albedo)) - { - rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); - } - else - { - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - double comov_energy = rpacket_get_energy (packet) * doppler_factor; - // TODO: correct - rpacket_set_mu (packet, rk_double (mt_state)); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - montecarlo_one_packet (storage, packet, -2, mt_state); - } - } -} \ No newline at end of file + self.status = REABSORBED diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index edbe5a7b924..e1f7c5d8731 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -1,4 +1,4 @@ -from numba import int64, float64 +from numba import int64, float64, jitclass from astropy import constants as const storage_model_spec = [ ('packet_nus', float64[:]), @@ -24,21 +24,23 @@ ('sigma_thomson', float64), ('inverse_sigma_thomson', float64), ] - +@jitclass(storage_model_spec) class StorageModel(object): def __init__(self, packet_nus, packet_mus, packet_energies, output_nus, output_energies, no_of_packets, no_of_shells, - r_inner, r_outer, v_inner, time_explosion, electron_densities, line_list_nu, line_lists_tau_sobolevs, - no_of_lines, no_of_edges, line_interaction_id, - inverse_sigma_thomson): + r_inner, r_outer, v_inner, time_explosion, electron_densities, + line_list_nu, line_lists_tau_sobolevs, no_of_lines, line_interaction_id, sigma_thomson): self.packet_nus = packet_nus self.packet_mus = packet_mus self.packet_energies = packet_energies + self.no_of_packets = len(self.packet_nus) + self.output_nus = output_nus self.output_energies = output_energies self.r_inner = r_inner self.r_outer = r_outer self.v_inner = v_inner + self.no_of_shells = len(self.v_inner) self.time_explosion = time_explosion self.inverse_time_explosion = 1 / time_explosion @@ -47,15 +49,16 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.inverse_electron_densities = 1 / electron_densities - self.sigma_thomson = const.sigma_T.to('cm^2').value + self.sigma_thomson = sigma_thomson + self.inverse_sigma_thomson = 1 / self.sigma_thomson def initialize_storage_model(model, plasma, runner): storage_model_kwargs = {'packet_nus': runner.input_nu, 'packet_mus': runner.input_mu, 'packet_energies': runner.input_energy, - 'output_nus': _output_nu, - 'output_energies': _output_energy, + 'output_nus': runner._output_nu, + 'output_energies': runner._output_energy, 'no_of_packets': runner.input_nu.size, 'no_of_shells': model.no_of_shells, 'r_inner': runner.r_inner_cgs, @@ -64,10 +67,10 @@ def initialize_storage_model(model, plasma, runner): 'time_explosion': model.time_explosion.to('s').value, 'electron_densities': plasma.electron_densities.values, 'line_list_nu': plasma.atomic_data.lines.nu.values, - 'line_lists_tau_sobolevs': runner.line_lists_tau_sobolevs, + 'line_lists_tau_sobolevs': plasma.tau_sobolevs.values.flatten(order='F'), 'no_of_lines': plasma.atomic_data.lines.nu.values.size, 'line_interaction_id': runner.get_line_interaction_id( runner.line_interaction_type), - 'inverse_sigma_thomson': 1.0 / storage.sigma_thomson} + 'sigma_thomson': runner.sigma_thomson.cgs.value} - return StorageModel(**storage_model_kwargs)) \ No newline at end of file + return StorageModel(**storage_model_kwargs) \ No newline at end of file From d22a1c812a6273dec1e1c34db2f7636f3b1b12ab Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 19:11:11 -0400 Subject: [PATCH 006/116] all sorts of nonsense Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- .../montecarlo/montecarlo_numba/__init__.py | 4 +- tardis/montecarlo/montecarlo_numba/base.py | 21 +++-- .../montecarlo_numba/compute_distance.py | 78 +++++++--------- .../montecarlo_numba/packet_loop.py | 12 +-- tardis/montecarlo/montecarlo_numba/rpacket.py | 93 +++++++++++++++++-- .../montecarlo_numba/storage_model.py | 3 +- 6 files changed, 141 insertions(+), 70 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index 3190e8d8620..dd918494b6a 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") +njit_dict = {'fastmath': True} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket -from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d - +from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index b734b2a6172..a3cf39f1dd4 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,17 +1,21 @@ -from numba import prange, njit +from numba import prange, njit, config +import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, REABSORBED, EMITTED from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel, initialize_storage_model from tardis.montecarlo.montecarlo_numba.packet_loop import one_packet_loop +from tardis.montecarlo.montecarlo_numba import njit_dict - +#config.THREADING_LAYER = 'threadsafe' +#config.DEBUG_ARRAY_OPT=1 def montecarlo_radial1d(model, plasma, runner): storage_model = initialize_storage_model(model, plasma, runner) montecarlo_main_loop(storage_model) -@njit +@njit(**njit_dict)#, parallel=True, nogil=True) def montecarlo_main_loop(storage_model): - + output_nus = np.empty_like(storage_model.output_nus) + output_energies = np.empty_like(storage_model.output_energies) for i in prange(storage_model.no_of_packets): r_packet = RPacket(storage_model.r_inner[0], storage_model.packet_mus[i], @@ -19,10 +23,13 @@ def montecarlo_main_loop(storage_model): storage_model.packet_energies[i]) r_packet.compute_distances(storage_model) one_packet_loop(storage_model, r_packet) - storage_model.output_nus[i] = r_packet.nu + output_nus[i] = r_packet.nu #print('!!!! THIS IS THE FINAL STATE !!!!!!!', r_packet_final_state) #return if r_packet.status == REABSORBED: - storage_model.output_energies[i] = -r_packet.energy + output_energies[i] = -r_packet.energy elif r_packet.status == EMITTED: - storage_model.output_energies[i] = r_packet.energy + output_energies[i] = r_packet.energy + storage_model.output_energies[:] = output_energies[:] + storage_model.output_nus[:] = output_nus[:] + diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index 4588ef0a4c8..d4465460769 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -1,10 +1,12 @@ from numba import jitclass, njit import numpy as np from astropy import constants as const +from tardis.montecarlo.montecarlo_numba import njit_dict C_SPEED_OF_LIGHT = const.c.to('cm/s').value +MISS_DISTANCE = 1e99 -@njit +@njit(**njit_dict) def compute_distance2boundary(packet, storage): r = packet.r mu = packet.mu @@ -31,61 +33,53 @@ def compute_distance2boundary(packet, storage): packet.d_boundary = distance -""" + @njit -def compute_distance2line(packet, storage, c=C_SPEED_OF_LIGHT): +def compute_distance2line(packet, storage_model): if not packet.last_line: r = packet.r mu = packet.mu nu = packet.nu nu_line = packet.nu_line - - distance = 0.0 - nu_diff = 0.0 - ct = storage.time_explosion * c - doppler_factor = rpacket_doppler_factor(packet, storage) + ct = storage_model.time_explosion * C_SPEED_OF_LIGHT + doppler_factor = packet.get_doppler_factor(storage_model) comov_nu = nu * doppler_factor nu_diff = comov_nu - nu_line if nu_diff >= 0: - if not storage.full_relativity: - distance = (nu_diff/nu) * ct - else: - nu_r = nu_line / nu - distance = - mu * r + (ct - nu_r * nu_r * - np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) - rpacket_set_d_line (packet, distance) - return TARDIS_ERROR_OK + distance = (nu_diff/nu) * ct + #else: + # nu_r = nu_line / nu + # distance = - mu * r + (ct - nu_r * nu_r * + # np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) + packet.d_line = distance else: - if rpacket_get_next_line_id(packet) == storage.no_of_lines - 1: - print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) - print("Last line in line list reached!") - elif rpacket_get_next_line_id(packet) == 0: - print("First line in line list!") - print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) - else: - print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) - print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) - print("ERROR: Comoving nu less than nu_line!") - print("comov_nu = {}".format(comov_nu)) - print("nu_line = {}".format(nu_line)) - print("(comov_nu - nu_line) / nu_line = {}".format(comov_nu-nu_line/nu_line)) - print("r = {}".format(r)) - print("mu = {}".format(mu)) - print("nu = {}".format(nu)) - print("doppler_factor = {}".format(doppler_factor)) - print("cur_zone_id = {}".format(rpacket_get_current_shell_id(packet)) - return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE + raise Exception + # if rpacket_get_next_line_id(packet) == storage.no_of_lines - 1: + # print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) + # print("Last line in line list reached!") + # elif rpacket_get_next_line_id(packet) == 0: + # print("First line in line list!") + # print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) + # else: + # print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) + # print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) + # print("ERROR: Comoving nu less than nu_line!") + # print("comov_nu = {}".format(comov_nu)) + # print("nu_line = {}".format(nu_line)) + # print("(comov_nu - nu_line) / nu_line = {}".format(comov_nu-nu_line/nu_line)) + # print("r = {}".format(r)) + # print("mu = {}".format(mu)) + # print("nu = {}".format(nu)) + # print("doppler_factor = {}".format(doppler_factor)) + # print("cur_zone_id = {}".format(rpacket_get_current_shell_id(packet)) + # #return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE else: - rpacket_set_d_line(packet, MISS_DISTANCE) - return TARDIS_ERROR_OK - - - """ + packet.d_line = MISS_DISTANCE + #return TARDIS_ERROR_OK - -@njit +@njit(**njit_dict) def compute_distance2continuum(packet, storage): packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ storage.inverse_sigma_thomson * packet.tau_event diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index 35336b1d7e1..e60ecce96eb 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -3,8 +3,10 @@ from enum import Enum from tardis.montecarlo.montecarlo_numba.rpacket import ( ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) +from tardis.montecarlo.montecarlo_numba import njit_dict -@njit + +@njit(**njit_dict) def one_packet_loop(storage_model, r_packet): r_packet.tau_event = 0.0 r_packet.nu_line = 0.0 @@ -34,13 +36,7 @@ def one_packet_loop(storage_model, r_packet): #if virtual_packet > 0: # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) - if r_packet.status == REABSORBED: - return 1 - else: - return 0 - - -@njit +@njit(**njit_dict) def rpacket_interactions(r_packet, storage_model): r_packet.move_packet_across_shell_boundary(storage_model) #if packet.next_interaction == InteractionType.BOUNDARY: diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 05ae3cce1cd..95efe756085 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -1,10 +1,11 @@ import numpy as np from enum import Enum -from numba import int64, float64 +from numba import int64, float64, boolean from numba import jitclass, njit from tardis.montecarlo.montecarlo_numba.compute_distance import (compute_distance2boundary, - compute_distance2continuum) - + compute_distance2continuum, + compute_distance2line) +from tardis.montecarlo.montecarlo_numba import njit_dict from astropy import constants as const C_SPEED_OF_LIGHT = const.c.to('cm/s').value @@ -27,7 +28,9 @@ ('energy', float64), ('tau_event', float64), ('nu_line', float64), - # ('d_line', float64) # Distance to electron event. + ('last_line', boolean), + ('next_line_id', int64), + ('d_line', float64), # Distance to line event. ('d_electron', float64), #/**< Distance to line event. */ ('d_boundary', float64), # distance to boundary ('current_shell_id', int64), @@ -37,11 +40,16 @@ ('distance', float64) ] -@njit +@njit(**njit_dict) def get_tau_event(): return np.random.exponential() +@njit(**njit_dict) +def get_random_mu(): + return 2.0 * np.random.random() - 1.0 + + @jitclass(rpacket_spec) class RPacket(object): def __init__(self, r, mu, nu, energy): @@ -55,19 +63,19 @@ def __init__(self, r, mu, nu, energy): self.delta_shell_id = 0 self.d_boundary = -1.0 self.d_electron = -1.0 + self.d_line = -1.0 self.distance = 0.0 #self.nu_line = nu_line def compute_distances(self, storage): - compute_distance2continuum(self, storage) + compute_distance2line(self, storage) compute_distance2boundary(self, storage) - if self.d_boundary < self.d_electron: + if self.d_boundary < self.d_line: next_interaction = BOUNDARY self.distance = self.d_boundary else: - next_interaction = ESCATTERING - # TODO: Fixme - self.distance = self.d_boundary + next_interaction = LINE + self.distance = self.d_line self.next_interaction = next_interaction @@ -99,3 +107,68 @@ def move_packet_across_shell_boundary(self, storage): self.status = EMITTED else: self.status = REABSORBED + + def set_line(self, storage): + packet.line_id = np.searchsorted(storage.line_list_nu, nu) + if packet.line_id > len(storage.line_list_nu): + packet.last_line = True + else: + packet.nu_line = storage.line_list_nu[packet.line_id] + packet.last_line = False + + # if (rpacket_get_virtual_packet (packet) > 0) + # { +# rpacket_set_tau_event (packet, +# rpacket_get_tau_event (packet) + tau_line); +# rpacket_set_next_line_id (packet, next_line_id + 1); +# test_for_close_line (packet, storage); +# } + def transform_energy(self, storage_model): + """ + Transform from the LabFrame to the ComovingFrame. Then change the angle + and transform back conserving energy in the ComovingFrame. + """ + old_doppler_factor = self.get_doppler_factor(storage_model) + self.mu = get_random_mu() + inverse_doppler_factor = 1. / self.get_doppler_factor(storage_model) + comov_energy = self.energy * old_doppler_factor + self.energy = comov_energy * inverse_doppler_factor + + def line_scatter(self, storage_model): + next_line_id = self.next_line_id + tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, self.current_shell_id] + # TODO: Fixme + tau_continuum = 0.0 + tau_combined = tau_line + tau_continuum + + if (next_line_id + 1 == storage.no_of_lines): + self.last_line = True + if (rpacket_get_tau_event (packet) < tau_combined): # Line absorption occurs + self.move_packet(storage_model, self.distance) + self.transform_energy(storage_model) +\ #TODO: Fixme + self.line_emission(storage) + else: + self.tau_event -= tau_line + self.next_line_id = next_line_id + 1 + # ??? + self.nu_line = storage_model.line_list_nu[self.next_line_id] + #test_for_close_line (packet, storage); + + def line_emission(self, storage_model): + emission_line_id = packet.next_line_id + inverse_doppler_factor = self.get_doppler_factor(storage) + self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor + #packet.nu_line = storage->line_list_nu[emission_line_id]); + self.next_line_id = emission_line_id + 1 + self.tau_event = get_tau_event() +#void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) +#{ +# if (!rpacket_get_last_line (packet) && +# fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - +# rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* +# 1e-7)) +# { +# rpacket_set_close_line (packet, true); +# } +#} diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index e1f7c5d8731..0ee9a6b3d67 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -1,4 +1,5 @@ from numba import int64, float64, jitclass + from astropy import constants as const storage_model_spec = [ ('packet_nus', float64[:]), @@ -41,7 +42,7 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.r_outer = r_outer self.v_inner = v_inner self.no_of_shells = len(self.v_inner) - + self.time_explosion = time_explosion self.inverse_time_explosion = 1 / time_explosion From 394167fcf343d98ec151d91886f3bbca8092402c Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 20:42:47 -0400 Subject: [PATCH 007/116] commit stuff Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- tardis/montecarlo/montecarlo_numba/base.py | 1 + .../montecarlo_numba/compute_distance.py | 1 + .../montecarlo_numba/packet_loop.py | 22 ++++------ tardis/montecarlo/montecarlo_numba/rpacket.py | 42 ++++++++++++------- .../montecarlo_numba/storage_model.py | 13 +++--- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a3cf39f1dd4..adb33f36cdc 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -21,6 +21,7 @@ def montecarlo_main_loop(storage_model): storage_model.packet_mus[i], storage_model.packet_nus[i], storage_model.packet_energies[i]) + r_packet.set_line(storage_model) r_packet.compute_distances(storage_model) one_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index d4465460769..b45e5686bfe 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -47,6 +47,7 @@ def compute_distance2line(packet, storage_model): comov_nu = nu * doppler_factor nu_diff = comov_nu - nu_line + print('comov_nu', comov_nu, 'nu_line', nu_line, 'ct', ct) if nu_diff >= 0: distance = (nu_diff/nu) * ct #else: diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index e60ecce96eb..233471c118e 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -3,13 +3,10 @@ from enum import Enum from tardis.montecarlo.montecarlo_numba.rpacket import ( ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) -from tardis.montecarlo.montecarlo_numba import njit_dict - - -@njit(**njit_dict) +@njit def one_packet_loop(storage_model, r_packet): r_packet.tau_event = 0.0 - r_packet.nu_line = 0.0 + #r_packet.nu_line = 0.0 #packet.virtual_packet = virtual_packet r_packet.status = IN_PROCESS # initializing tau_event if it's a real packet @@ -25,23 +22,20 @@ def one_packet_loop(storage_model, r_packet): # if virtual_packet > 0 and packet.tau_event > storage.tau_russian: # event_random = rk_double(mt_state) - # if event_random > storage.survival_probability: # packet.energy = 0.0 # packet.status = PacketStatus.EMITTED # else: # packet.energy = packet.energy / storage.survival_probability * \ # np.exp(-1.0 * packet.tau_event) - #if virtual_packet > 0: # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) - -@njit(**njit_dict) +@njit def rpacket_interactions(r_packet, storage_model): - r_packet.move_packet_across_shell_boundary(storage_model) - #if packet.next_interaction == InteractionType.BOUNDARY: + if r_packet.next_interaction == BOUNDARY: + r_packet.move_packet_across_shell_boundary(storage_model) + else: + r_packet.line_scatter(storage_model) # pass #else: - # pass - - + # pass \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 95efe756085..6b5c348b311 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -63,13 +63,14 @@ def __init__(self, r, mu, nu, energy): self.delta_shell_id = 0 self.d_boundary = -1.0 self.d_electron = -1.0 - self.d_line = -1.0 + self.d_line = 1.e99 self.distance = 0.0 - #self.nu_line = nu_line + self.nu_line = -1e99 def compute_distances(self, storage): compute_distance2line(self, storage) compute_distance2boundary(self, storage) + print('d_boundary', self.d_boundary, 'd_line', self.d_line) if self.d_boundary < self.d_line: next_interaction = BOUNDARY self.distance = self.d_boundary @@ -108,13 +109,22 @@ def move_packet_across_shell_boundary(self, storage): else: self.status = REABSORBED - def set_line(self, storage): - packet.line_id = np.searchsorted(storage.line_list_nu, nu) - if packet.line_id > len(storage.line_list_nu): - packet.last_line = True + def set_line(self, storage_model): + inverse_line_list_nu = storage_model.line_list_nu[::-1] + doppler_factor = self.get_doppler_factor(storage_model) + comov_nu = self.nu * doppler_factor + return + next_line_id = np.searchsorted(inverse_line_list_nu, comov_nu) + next_line_id = len(storage_model.line_list_nu) - next_line_id + #print('packet nu', self.nu, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) + print('packet nu', self.nu, 'next_line_id', next_line_id, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) + self.next_line_id = next_line_id + + if self.next_line_id > len(storage_model.line_list_nu): + self.last_line = True else: - packet.nu_line = storage.line_list_nu[packet.line_id] - packet.last_line = False + self.nu_line = storage_model.line_list_nu[self.next_line_id] + self.last_line = False # if (rpacket_get_virtual_packet (packet) > 0) # { @@ -135,19 +145,21 @@ def transform_energy(self, storage_model): self.energy = comov_energy * inverse_doppler_factor def line_scatter(self, storage_model): + print('Line Scattering') next_line_id = self.next_line_id tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, self.current_shell_id] + #tau_line = 3.0 # TODO: Fixme tau_continuum = 0.0 tau_combined = tau_line + tau_continuum - if (next_line_id + 1 == storage.no_of_lines): + if (next_line_id + 1 == storage_model.no_of_lines): self.last_line = True - if (rpacket_get_tau_event (packet) < tau_combined): # Line absorption occurs + if (self.tau_event < tau_combined): # Line absorption occurs self.move_packet(storage_model, self.distance) self.transform_energy(storage_model) -\ #TODO: Fixme - self.line_emission(storage) + self.line_emission(storage_model) + print('rpacket scattered at', self.nu) else: self.tau_event -= tau_line self.next_line_id = next_line_id + 1 @@ -156,10 +168,10 @@ def line_scatter(self, storage_model): #test_for_close_line (packet, storage); def line_emission(self, storage_model): - emission_line_id = packet.next_line_id - inverse_doppler_factor = self.get_doppler_factor(storage) + emission_line_id = self.next_line_id + inverse_doppler_factor = 1 / self.get_doppler_factor(storage_model) self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor - #packet.nu_line = storage->line_list_nu[emission_line_id]); + #self.nu_line = storage->line_list_nu[emission_line_id]); self.next_line_id = emission_line_id + 1 self.tau_event = get_tau_event() #void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index 0ee9a6b3d67..1469f35e1cb 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -1,6 +1,7 @@ from numba import int64, float64, jitclass - +import numpy as np from astropy import constants as const + storage_model_spec = [ ('packet_nus', float64[:]), ('packet_mus', float64[:]), @@ -17,7 +18,7 @@ ('electron_densities', float64[:]), ('inverse_electron_densities', float64[:]), # Maybe remove the inverse things ('line_list_nu', float64[:]), - ('line_lists_tau_sobolevs', float64[:]), + ('line_lists_tau_sobolevs', float64[:, :]), ('no_of_lines', int64), ('line_interaction_id', int64), # ('*js', float64), @@ -54,6 +55,8 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.inverse_sigma_thomson = 1 / self.sigma_thomson + self.line_list_nu = line_list_nu + def initialize_storage_model(model, plasma, runner): storage_model_kwargs = {'packet_nus': runner.input_nu, 'packet_mus': runner.input_mu, @@ -67,11 +70,11 @@ def initialize_storage_model(model, plasma, runner): 'v_inner': runner.v_inner_cgs, 'time_explosion': model.time_explosion.to('s').value, 'electron_densities': plasma.electron_densities.values, - 'line_list_nu': plasma.atomic_data.lines.nu.values, - 'line_lists_tau_sobolevs': plasma.tau_sobolevs.values.flatten(order='F'), + 'line_list_nu': plasma.atomic_data.lines.nu.values, 'no_of_lines': plasma.atomic_data.lines.nu.values.size, 'line_interaction_id': runner.get_line_interaction_id( runner.line_interaction_type), 'sigma_thomson': runner.sigma_thomson.cgs.value} - + storage_model_kwargs['line_lists_tau_sobolevs']= np.ascontiguousarray( + plasma.tau_sobolevs.values.copy(), dtype=np.float64) return StorageModel(**storage_model_kwargs) \ No newline at end of file From f6b0be3ee0b5cc9a92e967669323c89fb715fa23 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 1 May 2019 23:24:02 -0400 Subject: [PATCH 008/116] several edits later Co-authored-by: Christian Vogl Co-authored-by: Alice Harpole Co-authored-by: Yssavo Camacho-Neves --- .../montecarlo_numba/compute_distance.py | 22 ++---------------- .../montecarlo_numba/packet_loop.py | 5 +++- tardis/montecarlo/montecarlo_numba/rpacket.py | 23 +++++++++---------- .../montecarlo_numba/storage_model.py | 4 ++-- 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index b45e5686bfe..64d8c835162 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -47,7 +47,7 @@ def compute_distance2line(packet, storage_model): comov_nu = nu * doppler_factor nu_diff = comov_nu - nu_line - print('comov_nu', comov_nu, 'nu_line', nu_line, 'ct', ct) + print('in compute distance comov_nu', comov_nu, 'nu_line', nu_line, 'ct', ct) if nu_diff >= 0: distance = (nu_diff/nu) * ct #else: @@ -57,26 +57,8 @@ def compute_distance2line(packet, storage_model): packet.d_line = distance else: raise Exception - # if rpacket_get_next_line_id(packet) == storage.no_of_lines - 1: - # print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) - # print("Last line in line list reached!") - # elif rpacket_get_next_line_id(packet) == 0: - # print("First line in line list!") - # print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) - # else: - # print("last_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) - 1])) - # print("next_line = {}".format(storage.line_list_nu[rpacket_get_next_line_id(packet) + 1])) - # print("ERROR: Comoving nu less than nu_line!") - # print("comov_nu = {}".format(comov_nu)) - # print("nu_line = {}".format(nu_line)) - # print("(comov_nu - nu_line) / nu_line = {}".format(comov_nu-nu_line/nu_line)) - # print("r = {}".format(r)) - # print("mu = {}".format(mu)) - # print("nu = {}".format(nu)) - # print("doppler_factor = {}".format(doppler_factor)) - # print("cur_zone_id = {}".format(rpacket_get_current_shell_id(packet)) - # #return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE else: + print('last line is set - should be miss_distance') packet.d_line = MISS_DISTANCE #return TARDIS_ERROR_OK diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index 233471c118e..ade62493d6e 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -32,10 +32,13 @@ def one_packet_loop(storage_model, r_packet): # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) @njit def rpacket_interactions(r_packet, storage_model): + print(r_packet.next_line_id) + r_packet.compute_distances(storage_model) if r_packet.next_interaction == BOUNDARY: r_packet.move_packet_across_shell_boundary(storage_model) else: - r_packet.line_scatter(storage_model) + r_packet.line_scatter(storage_model) + # pass #else: # pass \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 6b5c348b311..fb5bf40b3db 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -113,14 +113,13 @@ def set_line(self, storage_model): inverse_line_list_nu = storage_model.line_list_nu[::-1] doppler_factor = self.get_doppler_factor(storage_model) comov_nu = self.nu * doppler_factor - return - next_line_id = np.searchsorted(inverse_line_list_nu, comov_nu) - next_line_id = len(storage_model.line_list_nu) - next_line_id - #print('packet nu', self.nu, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) - print('packet nu', self.nu, 'next_line_id', next_line_id, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) + next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) + print('in set line comov nu', comov_nu, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) + #print('packet nu', self.nu, 'next_line_id', next_line_id, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) + #self.next_line_id = 3000 self.next_line_id = next_line_id - - if self.next_line_id > len(storage_model.line_list_nu): + print('in set_line nextid and len', self.next_line_id, len(storage_model.line_list_nu)) + if self.next_line_id > (storage_model.no_of_lines - 1): self.last_line = True else: self.nu_line = storage_model.line_list_nu[self.next_line_id] @@ -145,21 +144,21 @@ def transform_energy(self, storage_model): self.energy = comov_energy * inverse_doppler_factor def line_scatter(self, storage_model): - print('Line Scattering') + #print('Line Scattering') next_line_id = self.next_line_id - tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, self.current_shell_id] - #tau_line = 3.0 + #tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, self.current_shell_id] + tau_line = 3.0 # TODO: Fixme tau_continuum = 0.0 tau_combined = tau_line + tau_continuum - if (next_line_id + 1 == storage_model.no_of_lines): + if (next_line_id + 1) == storage_model.no_of_lines: self.last_line = True if (self.tau_event < tau_combined): # Line absorption occurs self.move_packet(storage_model, self.distance) self.transform_energy(storage_model) self.line_emission(storage_model) - print('rpacket scattered at', self.nu) + #print('rpacket scattered at', self.nu) else: self.tau_event -= tau_line self.next_line_id = next_line_id + 1 diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index 1469f35e1cb..9c1dd4e2899 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -54,7 +54,7 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.sigma_thomson = sigma_thomson self.inverse_sigma_thomson = 1 / self.sigma_thomson - + self.no_of_lines = no_of_lines self.line_list_nu = line_list_nu def initialize_storage_model(model, plasma, runner): @@ -71,7 +71,7 @@ def initialize_storage_model(model, plasma, runner): 'time_explosion': model.time_explosion.to('s').value, 'electron_densities': plasma.electron_densities.values, 'line_list_nu': plasma.atomic_data.lines.nu.values, - 'no_of_lines': plasma.atomic_data.lines.nu.values.size, + 'no_of_lines': len(plasma.atomic_data.lines.nu.values), 'line_interaction_id': runner.get_line_interaction_id( runner.line_interaction_type), 'sigma_thomson': runner.sigma_thomson.cgs.value} From 044938766803ac9b3d6cfa59c67d75e65d7eacfe Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 2 May 2019 11:42:35 -0400 Subject: [PATCH 009/116] still working on fixing numba_test Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/base.py | 13 +++- .../montecarlo_numba/compute_distance.py | 3 +- .../montecarlo_numba/packet_loop.py | 2 - tardis/montecarlo/montecarlo_numba/rpacket.py | 71 ++++++++++++------- .../montecarlo_numba/storage_model.py | 1 + 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index adb33f36cdc..44022fa3730 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -14,9 +14,20 @@ def montecarlo_radial1d(model, plasma, runner): @njit(**njit_dict)#, parallel=True, nogil=True) def montecarlo_main_loop(storage_model): + """ + This is the main loop of the MonteCarlo routine that generates packets + and sends them through the ejecta. + + Parameters + ---------- + storage_model : [type] + [description] + """ output_nus = np.empty_like(storage_model.output_nus) output_energies = np.empty_like(storage_model.output_energies) for i in prange(storage_model.no_of_packets): + if i%1000 == 0: + print(i, end='') r_packet = RPacket(storage_model.r_inner[0], storage_model.packet_mus[i], storage_model.packet_nus[i], @@ -25,8 +36,6 @@ def montecarlo_main_loop(storage_model): r_packet.compute_distances(storage_model) one_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu - #print('!!!! THIS IS THE FINAL STATE !!!!!!!', r_packet_final_state) - #return if r_packet.status == REABSORBED: output_energies[i] = -r_packet.energy elif r_packet.status == EMITTED: diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index 64d8c835162..e09adb1d12d 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -47,7 +47,7 @@ def compute_distance2line(packet, storage_model): comov_nu = nu * doppler_factor nu_diff = comov_nu - nu_line - print('in compute distance comov_nu', comov_nu, 'nu_line', nu_line, 'ct', ct) + if nu_diff >= 0: distance = (nu_diff/nu) * ct #else: @@ -58,7 +58,6 @@ def compute_distance2line(packet, storage_model): else: raise Exception else: - print('last line is set - should be miss_distance') packet.d_line = MISS_DISTANCE #return TARDIS_ERROR_OK diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index ade62493d6e..e014e2d2700 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -5,7 +5,6 @@ ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) @njit def one_packet_loop(storage_model, r_packet): - r_packet.tau_event = 0.0 #r_packet.nu_line = 0.0 #packet.virtual_packet = virtual_packet r_packet.status = IN_PROCESS @@ -32,7 +31,6 @@ def one_packet_loop(storage_model, r_packet): # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) @njit def rpacket_interactions(r_packet, storage_model): - print(r_packet.next_line_id) r_packet.compute_distances(storage_model) if r_packet.next_interaction == BOUNDARY: r_packet.move_packet_across_shell_boundary(storage_model) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index fb5bf40b3db..6c6624dd828 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -29,6 +29,7 @@ ('tau_event', float64), ('nu_line', float64), ('last_line', boolean), + ('close_line', boolean), ('next_line_id', int64), ('d_line', float64), # Distance to line event. ('d_electron', float64), #/**< Distance to line event. */ @@ -58,7 +59,6 @@ def __init__(self, r, mu, nu, energy): self.nu = nu self.energy = energy self.tau_event = get_tau_event() - #self.nu_line = line_search() # TODO: Implement this self.current_shell_id = 0 self.delta_shell_id = 0 self.d_boundary = -1.0 @@ -66,11 +66,24 @@ def __init__(self, r, mu, nu, energy): self.d_line = 1.e99 self.distance = 0.0 self.nu_line = -1e99 + self.close_line = False + self.last_line = False def compute_distances(self, storage): - compute_distance2line(self, storage) + """ + Compute all distances (d_line, d_boundary, ???), compare, + and set interaction + + Parameters + ---------- + storage : [type] + [description] + """ + if not self.close_line: + compute_distance2line(self, storage) + else: + self.d_line = 0.0 compute_distance2boundary(self, storage) - print('d_boundary', self.d_boundary, 'd_line', self.d_line) if self.d_boundary < self.d_line: next_interaction = BOUNDARY self.distance = self.d_boundary @@ -85,8 +98,6 @@ def get_doppler_factor(self, storage): beta = self.r * storage.inverse_time_explosion / C_SPEED_OF_LIGHT return 1.0 - self.mu * beta - #else: - # return (1.0 - self.mu * beta) / np.sqrt(1 - beta*beta) def move_packet(self, storage, distance): doppler_factor = self.get_doppler_factor(storage) @@ -114,24 +125,16 @@ def set_line(self, storage_model): doppler_factor = self.get_doppler_factor(storage_model) comov_nu = self.nu * doppler_factor next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) - print('in set line comov nu', comov_nu, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) - #print('packet nu', self.nu, 'next_line_id', next_line_id, 'next_line_nu', storage_model.line_list_nu[next_line_id-1:next_line_id+2]) - #self.next_line_id = 3000 self.next_line_id = next_line_id - print('in set_line nextid and len', self.next_line_id, len(storage_model.line_list_nu)) if self.next_line_id > (storage_model.no_of_lines - 1): self.last_line = True else: self.nu_line = storage_model.line_list_nu[self.next_line_id] self.last_line = False + + ##### FIXME Add close line initializer in a sensible - think about this!1 + self.set_close_line(storage_model) - # if (rpacket_get_virtual_packet (packet) > 0) - # { -# rpacket_set_tau_event (packet, -# rpacket_get_tau_event (packet) + tau_line); -# rpacket_set_next_line_id (packet, next_line_id + 1); -# test_for_close_line (packet, storage); -# } def transform_energy(self, storage_model): """ Transform from the LabFrame to the ComovingFrame. Then change the angle @@ -144,11 +147,9 @@ def transform_energy(self, storage_model): self.energy = comov_energy * inverse_doppler_factor def line_scatter(self, storage_model): - #print('Line Scattering') next_line_id = self.next_line_id - #tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, self.current_shell_id] - tau_line = 3.0 - # TODO: Fixme + tau_line = storage_model.line_lists_tau_sobolevs[ + self.current_shell_id, next_line_id] tau_continuum = 0.0 tau_combined = tau_line + tau_continuum @@ -158,22 +159,40 @@ def line_scatter(self, storage_model): self.move_packet(storage_model, self.distance) self.transform_energy(storage_model) self.line_emission(storage_model) - #print('rpacket scattered at', self.nu) else: self.tau_event -= tau_line self.next_line_id = next_line_id + 1 - # ??? - self.nu_line = storage_model.line_list_nu[self.next_line_id] - #test_for_close_line (packet, storage); + self.nu_line = storage_model.line_list_nu[self.next_line_id] def line_emission(self, storage_model): emission_line_id = self.next_line_id inverse_doppler_factor = 1 / self.get_doppler_factor(storage_model) self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor - #self.nu_line = storage->line_list_nu[emission_line_id]); + self.next_line_id = emission_line_id + 1 + self.nu_line = storage_model.line_list_nu[self.next_line_id] self.tau_event = get_tau_event() -#void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) + self.set_close_line(storage_model) + + + def set_close_line(self, storage_model, line_diff_threshold=1e-7): + """ + The Packet currently sits on next_line_id - 1 and we are checking if the + next line is closer than 1e-7 to set the close_line attribute + + Parameters + ---------- + storage_model : StorageModel + """ + + frac_nu_diff = ((storage_model.line_list_nu[self.next_line_id] - + storage_model.line_list_nu[self.next_line_id - 1]) / + storage_model.line_list_nu[self.next_line_id]) + + if not self.last_line and frac_nu_diff < line_diff_threshold: + self.close_line = True + else: + self.close_line = False #{ # if (!rpacket_get_last_line (packet) && # fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index 9c1dd4e2899..3814344a9ad 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -56,6 +56,7 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.inverse_sigma_thomson = 1 / self.sigma_thomson self.no_of_lines = no_of_lines self.line_list_nu = line_list_nu + self.line_lists_tau_sobolevs = line_lists_tau_sobolevs def initialize_storage_model(model, plasma, runner): storage_model_kwargs = {'packet_nus': runner.input_nu, From c53425f9fb7c49972421a84c633a22d970eb299f Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 2 May 2019 14:56:08 -0400 Subject: [PATCH 010/116] add numba stuff --- tardis/montecarlo/montecarlo_numba/base.py | 2 -- .../montecarlo_numba/compute_distance.py | 6 ++++-- .../montecarlo_numba/packet_loop.py | 1 + tardis/montecarlo/montecarlo_numba/rpacket.py | 21 +++++++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 44022fa3730..6c30449bc50 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -26,8 +26,6 @@ def montecarlo_main_loop(storage_model): output_nus = np.empty_like(storage_model.output_nus) output_energies = np.empty_like(storage_model.output_energies) for i in prange(storage_model.no_of_packets): - if i%1000 == 0: - print(i, end='') r_packet = RPacket(storage_model.r_inner[0], storage_model.packet_mus[i], storage_model.packet_nus[i], diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index e09adb1d12d..bcd7a1a4cad 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -29,14 +29,16 @@ def compute_distance2boundary(packet, storage): # miss inner boundary packet.delta_shell_id = + 1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) - + if distance < 0.0: + print('hello') + pass packet.d_boundary = distance @njit def compute_distance2line(packet, storage_model): - if not packet.last_line: + if not packet.last_line and not packet.close_line: r = packet.r mu = packet.mu nu = packet.nu diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index e014e2d2700..60109409638 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -12,6 +12,7 @@ def one_packet_loop(storage_model, r_packet): # for a virtual packet tau_event is the sum of all the tau's that the packet passes while r_packet.status == IN_PROCESS: + rpacket_interactions(r_packet, storage_model) # Check if we are at the end of line list. #if not packet.last_line: diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 6c6624dd828..a3a53bbcd46 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -79,10 +79,12 @@ def compute_distances(self, storage): storage : [type] [description] """ + if not self.close_line: compute_distance2line(self, storage) else: self.d_line = 0.0 + self.close_line = False compute_distance2boundary(self, storage) if self.d_boundary < self.d_line: next_interaction = BOUNDARY @@ -102,6 +104,10 @@ def get_doppler_factor(self, storage): def move_packet(self, storage, distance): doppler_factor = self.get_doppler_factor(storage) r = self.r + if self.close_line: + if distance != 0.0: + print('hello') + pass if (distance > 0.0): new_r = np.sqrt(r * r + distance * distance + 2.0 * r * distance * self.mu) @@ -148,8 +154,9 @@ def transform_energy(self, storage_model): def line_scatter(self, storage_model): next_line_id = self.next_line_id - tau_line = storage_model.line_lists_tau_sobolevs[ - self.current_shell_id, next_line_id] + storage_model.line_lists_tau_sobolevs + tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, + self.current_shell_id] tau_continuum = 0.0 tau_combined = tau_line + tau_continuum @@ -162,7 +169,10 @@ def line_scatter(self, storage_model): else: self.tau_event -= tau_line self.next_line_id = next_line_id + 1 - self.nu_line = storage_model.line_list_nu[self.next_line_id] + if not self.last_line: + self.nu_line = storage_model.line_list_nu[self.next_line_id] + self.set_close_line(storage_model) + def line_emission(self, storage_model): emission_line_id = self.next_line_id @@ -172,7 +182,7 @@ def line_emission(self, storage_model): self.next_line_id = emission_line_id + 1 self.nu_line = storage_model.line_list_nu[self.next_line_id] self.tau_event = get_tau_event() - self.set_close_line(storage_model) + def set_close_line(self, storage_model, line_diff_threshold=1e-7): @@ -184,6 +194,9 @@ def set_close_line(self, storage_model, line_diff_threshold=1e-7): ---------- storage_model : StorageModel """ + if self.last_line: + self.close_line = False + return frac_nu_diff = ((storage_model.line_list_nu[self.next_line_id] - storage_model.line_list_nu[self.next_line_id - 1]) / From b26410b160a422f196bbd953f24552d62ac35fed Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 2 May 2019 19:50:27 -0400 Subject: [PATCH 011/116] branch point to diverge from c --- tardis/montecarlo/montecarlo_numba/base.py | 1 + .../montecarlo_numba/compute_distance.py | 7 ++-- tardis/montecarlo/montecarlo_numba/rpacket.py | 39 ++++++++++++------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 6c30449bc50..9b422d242a3 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -32,6 +32,7 @@ def montecarlo_main_loop(storage_model): storage_model.packet_energies[i]) r_packet.set_line(storage_model) r_packet.compute_distances(storage_model) + r_packet.i = i one_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu if r_packet.status == REABSORBED: diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index bcd7a1a4cad..054f5abeda2 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -29,9 +29,6 @@ def compute_distance2boundary(packet, storage): # miss inner boundary packet.delta_shell_id = + 1 distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) - if distance < 0.0: - print('hello') - pass packet.d_boundary = distance @@ -51,6 +48,8 @@ def compute_distance2line(packet, storage_model): nu_diff = comov_nu - nu_line if nu_diff >= 0: + if nu_diff / comov_nu < 1e-7: + nu_diff = 0.0 distance = (nu_diff/nu) * ct #else: # nu_r = nu_line / nu @@ -59,6 +58,8 @@ def compute_distance2line(packet, storage_model): packet.d_line = distance else: raise Exception + elif packet.close_line: + packet.d_line = 0.0 else: packet.d_line = MISS_DISTANCE #return TARDIS_ERROR_OK diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index a3a53bbcd46..df181d4538c 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -68,7 +68,15 @@ def __init__(self, r, mu, nu, energy): self.nu_line = -1e99 self.close_line = False self.last_line = False - + + self.comov_nu_history = [] + self.radius_history = [] + self.move_dist_history = [] + self.next_interaction_history = [] + self.mu_history = [] + self.shell_id_history = [] + self.next_line_id_history = [] + def compute_distances(self, storage): """ Compute all distances (d_line, d_boundary, ???), compare, @@ -80,11 +88,8 @@ def compute_distances(self, storage): [description] """ - if not self.close_line: - compute_distance2line(self, storage) - else: - self.d_line = 0.0 - self.close_line = False + + compute_distance2line(self, storage) compute_distance2boundary(self, storage) if self.d_boundary < self.d_line: next_interaction = BOUNDARY @@ -104,15 +109,20 @@ def get_doppler_factor(self, storage): def move_packet(self, storage, distance): doppler_factor = self.get_doppler_factor(storage) r = self.r - if self.close_line: - if distance != 0.0: - print('hello') - pass if (distance > 0.0): new_r = np.sqrt(r * r + distance * distance + 2.0 * r * distance * self.mu) self.mu = (self.mu * r + distance) / new_r self.r = new_r + + self.comov_nu_history.append(self.nu * doppler_factor) + self.radius_history.append(self.r) + self.move_dist_history.append(distance) + self.next_interaction_history.append(self.next_interaction) + self.mu_history.append(self.mu) + self.shell_id_history.append(self.current_shell_id) + self.next_line_id_history.append(self.next_line_id) + def move_packet_across_shell_boundary(self, storage): self.move_packet(storage, self.distance) @@ -139,7 +149,7 @@ def set_line(self, storage_model): self.last_line = False ##### FIXME Add close line initializer in a sensible - think about this!1 - self.set_close_line(storage_model) + #self.set_close_line(storage_model) def transform_energy(self, storage_model): """ @@ -153,6 +163,8 @@ def transform_energy(self, storage_model): self.energy = comov_energy * inverse_doppler_factor def line_scatter(self, storage_model): + if self.distance == 0.0: + self.set_close_line(storage_model) next_line_id = self.next_line_id storage_model.line_lists_tau_sobolevs tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, @@ -171,7 +183,7 @@ def line_scatter(self, storage_model): self.next_line_id = next_line_id + 1 if not self.last_line: self.nu_line = storage_model.line_list_nu[self.next_line_id] - self.set_close_line(storage_model) + def line_emission(self, storage_model): @@ -182,7 +194,7 @@ def line_emission(self, storage_model): self.next_line_id = emission_line_id + 1 self.nu_line = storage_model.line_list_nu[self.next_line_id] self.tau_event = get_tau_event() - + self.set_close_line(storage_model) def set_close_line(self, storage_model, line_diff_threshold=1e-7): @@ -194,6 +206,7 @@ def set_close_line(self, storage_model, line_diff_threshold=1e-7): ---------- storage_model : StorageModel """ + return if self.last_line: self.close_line = False return From 38aae416eea9bd69f8e366eda956e6b5457fec55 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 2 May 2019 20:00:13 -0400 Subject: [PATCH 012/116] working numba implementation --- tardis/montecarlo/montecarlo_numba/base.py | 2 +- tardis/montecarlo/montecarlo_numba/compute_distance.py | 6 +++--- tardis/montecarlo/montecarlo_numba/packet_loop.py | 1 + tardis/montecarlo/montecarlo_numba/rpacket.py | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 9b422d242a3..1eb636a69ec 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -32,7 +32,7 @@ def montecarlo_main_loop(storage_model): storage_model.packet_energies[i]) r_packet.set_line(storage_model) r_packet.compute_distances(storage_model) - r_packet.i = i + #r_packet.i = i one_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu if r_packet.status == REABSORBED: diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py index 054f5abeda2..c50aff672cc 100644 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ b/tardis/montecarlo/montecarlo_numba/compute_distance.py @@ -46,10 +46,10 @@ def compute_distance2line(packet, storage_model): comov_nu = nu * doppler_factor nu_diff = comov_nu - nu_line - - if nu_diff >= 0: - if nu_diff / comov_nu < 1e-7: + if np.abs(nu_diff / comov_nu) < 1e-7: nu_diff = 0.0 + if nu_diff >= 0: + distance = (nu_diff/nu) * ct #else: # nu_r = nu_line / nu diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index 60109409638..44a5e28b2a7 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -12,6 +12,7 @@ def one_packet_loop(storage_model, r_packet): # for a virtual packet tau_event is the sum of all the tau's that the packet passes while r_packet.status == IN_PROCESS: + rpacket_interactions(r_packet, storage_model) # Check if we are at the end of line list. diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index df181d4538c..e9133e7bf2a 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -68,7 +68,7 @@ def __init__(self, r, mu, nu, energy): self.nu_line = -1e99 self.close_line = False self.last_line = False - + """ self.comov_nu_history = [] self.radius_history = [] self.move_dist_history = [] @@ -76,7 +76,7 @@ def __init__(self, r, mu, nu, energy): self.mu_history = [] self.shell_id_history = [] self.next_line_id_history = [] - + """ def compute_distances(self, storage): """ Compute all distances (d_line, d_boundary, ???), compare, @@ -114,7 +114,7 @@ def move_packet(self, storage, distance): 2.0 * r * distance * self.mu) self.mu = (self.mu * r + distance) / new_r self.r = new_r - + """ self.comov_nu_history.append(self.nu * doppler_factor) self.radius_history.append(self.r) self.move_dist_history.append(distance) @@ -122,7 +122,7 @@ def move_packet(self, storage, distance): self.mu_history.append(self.mu) self.shell_id_history.append(self.current_shell_id) self.next_line_id_history.append(self.next_line_id) - + """ def move_packet_across_shell_boundary(self, storage): self.move_packet(storage, self.distance) From 7ce0bf0f4bc0b837da1f03ad731d252f09154dfd Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 13:12:00 -0400 Subject: [PATCH 013/116] numba rewrite Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/base.py | 2 +- .../montecarlo_numba/packet_loop.py | 21 +++-- tardis/montecarlo/montecarlo_numba/rpacket.py | 94 +++++++++++++++++-- .../montecarlo_numba/storage_model.py | 5 + 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 1eb636a69ec..a9cc808292c 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -12,7 +12,7 @@ def montecarlo_radial1d(model, plasma, runner): storage_model = initialize_storage_model(model, plasma, runner) montecarlo_main_loop(storage_model) -@njit(**njit_dict)#, parallel=True, nogil=True) +@njit(**njit_dict, nogil=True) def montecarlo_main_loop(storage_model): """ This is the main loop of the MonteCarlo routine that generates packets diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index 44a5e28b2a7..23cef0726e0 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -3,18 +3,26 @@ from enum import Enum from tardis.montecarlo.montecarlo_numba.rpacket import ( ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) + @njit def one_packet_loop(storage_model, r_packet): - #r_packet.nu_line = 0.0 - #packet.virtual_packet = virtual_packet r_packet.status = IN_PROCESS # initializing tau_event if it's a real packet - - # for a virtual packet tau_event is the sum of all the tau's that the packet passes while r_packet.status == IN_PROCESS: - - rpacket_interactions(r_packet, storage_model) + distance, interaction_type, delta_ shell_id = r_packet.trace_packet(storage_model) + if interaction_type == BOUNDARY: + pass + #new_shell_id = r_packet.shell_id + delta_shell_id + #if delta_shell_id == 1: # Moving outwards + # r_new = storage_model.r_inner[new_shell_id] + #else: # Moving inwards + # r_new = storage_model.r_outer[new_shell_id] + #r_packet.move_packet_across_shell_boundary(new_shell_id, r_new) + + #r_packet.move_packet(storage_model) + #r_packet.interact() + # Check if we are at the end of line list. #if not packet.last_line: # packet.nu_line = storeage.line_list_nu[packet.next_line_id] @@ -31,6 +39,7 @@ def one_packet_loop(storage_model, r_packet): # np.exp(-1.0 * packet.tau_event) #if virtual_packet > 0: # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) + @njit def rpacket_interactions(r_packet, storage_model): r_packet.compute_distances(storage_model) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index e9133e7bf2a..b429928580b 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -9,6 +9,7 @@ from astropy import constants as const C_SPEED_OF_LIGHT = const.c.to('cm/s').value +MISS_DISTANCE = 1e99 #class PacketStatus(Enum): @@ -41,9 +42,6 @@ ('distance', float64) ] -@njit(**njit_dict) -def get_tau_event(): - return np.random.exponential() @njit(**njit_dict) @@ -58,7 +56,7 @@ def __init__(self, r, mu, nu, energy): self.mu = mu self.nu = nu self.energy = energy - self.tau_event = get_tau_event() + self.tau_event = np.random.exponential() self.current_shell_id = 0 self.delta_shell_id = 0 self.d_boundary = -1.0 @@ -77,6 +75,90 @@ def __init__(self, r, mu, nu, energy): self.shell_id_history = [] self.next_line_id_history = [] """ + + def calculate_distance_boundary(self, r_inner, r_outer): + delta_shell = 0 + if (self.mu > 0.0): + # direction outward + distance = np.sqrt(r_outer * r_outer + ((self.mu**2 - 1.0) * self.r**2)) - (self.r * self.mu) + delta_shell = 1 + else: + # going inward + check = r_inner**2 + (self.r**2 * (self.mu**2 - 1.0)) + + if (check >= 0.0): + # hit inner boundary + distance = -self.r * self.mu - np.sqrt(check) + delta_shell = -1 + else: + # miss inner boundary + distance = np.sqrt(r_outer**2 + ((self.mu**2 - 1.0) * self.r**2)) - (self.r * self.mu) + delta_shell = 1 + + return distance, delta_shell + + def calculate_distance_line(self, comov_nu, nu_line, ct): + if not self.last_line: + nu_diff = comov_nu - nu_line + + if np.abs(nu_diff / comov_nu) < 1e-7: + nu_diff = 0.0 + if nu_diff >= 0: + return (nu_diff/self.nu) * ct + else: + #return np.abs((nu_diff/self.nu) * ct) + raise Exception + else: + return MISS_DISTANCE + + def trace_packet(self, storage_model): + r_inner = storage_model.r_inner[self.current_shell_id] + r_outer = storage_model.r_outer[self.current_shell_id] + + distance_boundary, delta_shell = self.calculate_distance_boundary(r_inner, r_outer) + + #defining start for stuff + cur_line_id = self.next_line_id + nu_line = 0.0 + #defining taus + tau_event = np.random.exponential() + tau_trace_line = 0.0 + tau_trace_line_combined = 0.0 + doppler_factor = self.get_doppler_factor(storage_model) + comov_nu = self.nu * doppler_factor + distance_trace = 0.0 + + #d_continuum = f(tau_event) + d_continuum = MISS_DISTANCE + + while True: + if cur_line_id < storage_model.no_of_lines: + nu_line = storage_model.line_list_nu[cur_line_id] + tau_trace_line += storage_model.line_lists_tau_sobolevs[cur_line_id, + self.current_shell_id] + else: + nu_line = 0.0 + tau_trace_line = 0.0 + + tau_trace_line_combined += tau_trace_line + distance_trace = self.calculate_distance_line(comov_nu, nu_line, storage_model.ct) + tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering + + if distance_trace > distance_boundary: + interaction_type = BOUNDARY # BOUNDARY + break + + if distance_trace > d_continuum: + interaction_type = 10 #continuum + break + if tau_trace_combined > tau_event: + interaction_type = LINE #Line + break + + cur_line_id += 1 + + return distance_trace, interaction_type, delta_shell + def compute_distances(self, storage): """ Compute all distances (d_line, d_boundary, ???), compare, @@ -126,8 +208,6 @@ def move_packet(self, storage, distance): def move_packet_across_shell_boundary(self, storage): self.move_packet(storage, self.distance) - - get_tau_event() if ((self.current_shell_id < storage.no_of_shells - 1 and self.delta_shell_id == 1) or (self.current_shell_id > 0 and self.delta_shell_id == -1)): self.current_shell_id += self.delta_shell_id @@ -193,7 +273,7 @@ def line_emission(self, storage_model): self.next_line_id = emission_line_id + 1 self.nu_line = storage_model.line_list_nu[self.next_line_id] - self.tau_event = get_tau_event() + self.tau_event = np.random.exponential() self.set_close_line(storage_model) diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py index 3814344a9ad..2f141ae5f5c 100644 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ b/tardis/montecarlo/montecarlo_numba/storage_model.py @@ -2,6 +2,8 @@ import numpy as np from astropy import constants as const +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + storage_model_spec = [ ('packet_nus', float64[:]), ('packet_mus', float64[:]), @@ -25,7 +27,9 @@ # ('*nubars', float64), ('sigma_thomson', float64), ('inverse_sigma_thomson', float64), + ('ct', float64), ] + @jitclass(storage_model_spec) class StorageModel(object): def __init__(self, packet_nus, packet_mus, packet_energies, @@ -57,6 +61,7 @@ def __init__(self, packet_nus, packet_mus, packet_energies, self.no_of_lines = no_of_lines self.line_list_nu = line_list_nu self.line_lists_tau_sobolevs = line_lists_tau_sobolevs + self.ct = self.time_explosion * C_SPEED_OF_LIGHT def initialize_storage_model(model, plasma, runner): storage_model_kwargs = {'packet_nus': runner.input_nu, From c10a239dd605c423b68b5d3815453ac557d34784 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 14:15:31 -0400 Subject: [PATCH 014/116] finished simple implementation Co-authored-by: Christian Vogl --- .../montecarlo_numba/packet_loop.py | 22 ++++--- tardis/montecarlo/montecarlo_numba/rpacket.py | 62 +++++++++++++------ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py index 23cef0726e0..778c4918a3d 100644 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/packet_loop.py @@ -9,16 +9,20 @@ def one_packet_loop(storage_model, r_packet): r_packet.status = IN_PROCESS # initializing tau_event if it's a real packet while r_packet.status == IN_PROCESS: - rpacket_interactions(r_packet, storage_model) - distance, interaction_type, delta_ shell_id = r_packet.trace_packet(storage_model) + #rpacket_interactions(r_packet, storage_model) + distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) if interaction_type == BOUNDARY: - pass - #new_shell_id = r_packet.shell_id + delta_shell_id - #if delta_shell_id == 1: # Moving outwards - # r_new = storage_model.r_inner[new_shell_id] - #else: # Moving inwards - # r_new = storage_model.r_outer[new_shell_id] - #r_packet.move_packet_across_shell_boundary(new_shell_id, r_new) + r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) + elif interaction_type == LINE: + r_packet.move_packet(distance) + r_packet.transform_energy(storage_model) + r_packet.line_emission(storage_model) + #new_shell_id = r_packet.shell_id + delta_shell_id + #if delta_shell_id == 1: # Moving outwards + # r_new = storage_model.r_inner[new_shell_id] + #else: # Moving inwards + # r_new = storage_model.r_outer[new_shell_id] + #r_packet.move_packet_across_shell_boundary(new_shell_id, r_new) #r_packet.move_packet(storage_model) #r_packet.interact() diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index b429928580b..4bea4775615 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -111,6 +111,10 @@ def calculate_distance_line(self, comov_nu, nu_line, ct): else: return MISS_DISTANCE + def calculate_distance_continuum(self, storage): + packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ + storage.inverse_sigma_thomson * packet.tau_event + def trace_packet(self, storage_model): r_inner = storage_model.r_inner[self.current_shell_id] r_outer = storage_model.r_outer[self.current_shell_id] @@ -130,7 +134,7 @@ def trace_packet(self, storage_model): #d_continuum = f(tau_event) d_continuum = MISS_DISTANCE - + #loop_checker = 0 while True: if cur_line_id < storage_model.no_of_lines: nu_line = storage_model.line_list_nu[cur_line_id] @@ -139,6 +143,8 @@ def trace_packet(self, storage_model): else: nu_line = 0.0 tau_trace_line = 0.0 + interaction_type = BOUNDARY # FIXME: does not work for e-scattering + break tau_trace_line_combined += tau_trace_line distance_trace = self.calculate_distance_line(comov_nu, nu_line, storage_model.ct) @@ -146,16 +152,21 @@ def trace_packet(self, storage_model): if distance_trace > distance_boundary: interaction_type = BOUNDARY # BOUNDARY + self.next_line_id = cur_line_id break if distance_trace > d_continuum: interaction_type = 10 #continuum - break + #break if tau_trace_combined > tau_event: interaction_type = LINE #Line break cur_line_id += 1 + + #loop_checker +=1 + #if loop_checker > 10000: + # raise Exception return distance_trace, interaction_type, delta_shell @@ -188,30 +199,45 @@ def get_doppler_factor(self, storage): return 1.0 - self.mu * beta - def move_packet(self, storage, distance): - doppler_factor = self.get_doppler_factor(storage) + def move_packet(self, distance): + """Move packet a distance and recalculate the new angle mu + + Parameters + ---------- + distance : float + distance in cm + """ + r = self.r if (distance > 0.0): - new_r = np.sqrt(r * r + distance * distance + + new_r = np.sqrt(r**2 + distance**2 + 2.0 * r * distance * self.mu) self.mu = (self.mu * r + distance) / new_r self.r = new_r + + def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells): """ - self.comov_nu_history.append(self.nu * doppler_factor) - self.radius_history.append(self.r) - self.move_dist_history.append(distance) - self.next_interaction_history.append(self.next_interaction) - self.mu_history.append(self.mu) - self.shell_id_history.append(self.current_shell_id) - self.next_line_id_history.append(self.next_line_id) + Move packet across shell boundary - realizing if we are still in the simulation or have + moved out through the inner boundary or outer boundary and updating packet + status. + + Parameters + ---------- + distance : float + distance to move to shell boundary + + delta_shell: int + is +1 if moving outward or -1 if moving inward + + no_of_shells: int + number of shells in TARDIS simulation """ - def move_packet_across_shell_boundary(self, storage): - self.move_packet(storage, self.distance) - if ((self.current_shell_id < storage.no_of_shells - 1 and self.delta_shell_id == 1) - or (self.current_shell_id > 0 and self.delta_shell_id == -1)): - self.current_shell_id += self.delta_shell_id - elif self.delta_shell_id == 1: + self.move_packet(distance) + if ((self.current_shell_id < no_of_shells - 1 and delta_shell == 1) + or (self.current_shell_id > 0 and delta_shell == -1)): + self.current_shell_id += delta_shell + elif delta_shell == 1: self.status = EMITTED else: self.status = REABSORBED From 97028343e3e8f830f42b07596a82a9af4050da5f Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 14:34:27 -0400 Subject: [PATCH 015/116] cleaned up version Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/base.py | 9 +- .../montecarlo_numba/packet_loop.py | 57 ------- tardis/montecarlo/montecarlo_numba/rpacket.py | 145 ++---------------- .../montecarlo_numba/single_packet_loop.py | 18 +++ 4 files changed, 33 insertions(+), 196 deletions(-) delete mode 100644 tardis/montecarlo/montecarlo_numba/packet_loop.py create mode 100644 tardis/montecarlo/montecarlo_numba/single_packet_loop.py diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a9cc808292c..0822b67a349 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -2,7 +2,7 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, REABSORBED, EMITTED from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel, initialize_storage_model -from tardis.montecarlo.montecarlo_numba.packet_loop import one_packet_loop +from tardis.montecarlo.montecarlo_numba.single_packet_loop import single_packet_loop from tardis.montecarlo.montecarlo_numba import njit_dict #config.THREADING_LAYER = 'threadsafe' @@ -12,7 +12,7 @@ def montecarlo_radial1d(model, plasma, runner): storage_model = initialize_storage_model(model, plasma, runner) montecarlo_main_loop(storage_model) -@njit(**njit_dict, nogil=True) +@njit(**njit_dict, nogil=True, parallel=True) def montecarlo_main_loop(storage_model): """ This is the main loop of the MonteCarlo routine that generates packets @@ -30,10 +30,7 @@ def montecarlo_main_loop(storage_model): storage_model.packet_mus[i], storage_model.packet_nus[i], storage_model.packet_energies[i]) - r_packet.set_line(storage_model) - r_packet.compute_distances(storage_model) - #r_packet.i = i - one_packet_loop(storage_model, r_packet) + single_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu if r_packet.status == REABSORBED: output_energies[i] = -r_packet.energy diff --git a/tardis/montecarlo/montecarlo_numba/packet_loop.py b/tardis/montecarlo/montecarlo_numba/packet_loop.py deleted file mode 100644 index 778c4918a3d..00000000000 --- a/tardis/montecarlo/montecarlo_numba/packet_loop.py +++ /dev/null @@ -1,57 +0,0 @@ -from numba import njit -import numpy as np -from enum import Enum -from tardis.montecarlo.montecarlo_numba.rpacket import ( - ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) - -@njit -def one_packet_loop(storage_model, r_packet): - r_packet.status = IN_PROCESS - # initializing tau_event if it's a real packet - while r_packet.status == IN_PROCESS: - #rpacket_interactions(r_packet, storage_model) - distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) - if interaction_type == BOUNDARY: - r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) - elif interaction_type == LINE: - r_packet.move_packet(distance) - r_packet.transform_energy(storage_model) - r_packet.line_emission(storage_model) - #new_shell_id = r_packet.shell_id + delta_shell_id - #if delta_shell_id == 1: # Moving outwards - # r_new = storage_model.r_inner[new_shell_id] - #else: # Moving inwards - # r_new = storage_model.r_outer[new_shell_id] - #r_packet.move_packet_across_shell_boundary(new_shell_id, r_new) - - #r_packet.move_packet(storage_model) - #r_packet.interact() - - # Check if we are at the end of line list. - #if not packet.last_line: - # packet.nu_line = storeage.line_list_nu[packet.next_line_id] - - # FIXME: get_event_handler(packet, storage, &distance, mt_state) (packet, storage, distance, mt_state) - - # if virtual_packet > 0 and packet.tau_event > storage.tau_russian: - # event_random = rk_double(mt_state) - # if event_random > storage.survival_probability: - # packet.energy = 0.0 - # packet.status = PacketStatus.EMITTED - # else: - # packet.energy = packet.energy / storage.survival_probability * \ - # np.exp(-1.0 * packet.tau_event) - #if virtual_packet > 0: - # packet.energy = packet.energy * np.exp(-1.0 * packet.tau_event) - -@njit -def rpacket_interactions(r_packet, storage_model): - r_packet.compute_distances(storage_model) - if r_packet.next_interaction == BOUNDARY: - r_packet.move_packet_across_shell_boundary(storage_model) - else: - r_packet.line_scatter(storage_model) - - # pass - #else: - # pass \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 4bea4775615..63e641438df 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -27,19 +27,9 @@ ('mu', float64), ('nu', float64), ('energy', float64), - ('tau_event', float64), - ('nu_line', float64), - ('last_line', boolean), - ('close_line', boolean), ('next_line_id', int64), - ('d_line', float64), # Distance to line event. - ('d_electron', float64), #/**< Distance to line event. */ - ('d_boundary', float64), # distance to boundary ('current_shell_id', int64), - ('delta_shell_id', int64), - ('next_interaction', int64), ('status', int64), - ('distance', float64) ] @@ -56,25 +46,9 @@ def __init__(self, r, mu, nu, energy): self.mu = mu self.nu = nu self.energy = energy - self.tau_event = np.random.exponential() self.current_shell_id = 0 - self.delta_shell_id = 0 - self.d_boundary = -1.0 - self.d_electron = -1.0 - self.d_line = 1.e99 - self.distance = 0.0 - self.nu_line = -1e99 - self.close_line = False - self.last_line = False - """ - self.comov_nu_history = [] - self.radius_history = [] - self.move_dist_history = [] - self.next_interaction_history = [] - self.mu_history = [] - self.shell_id_history = [] - self.next_line_id_history = [] - """ + self.status = IN_PROCESS + self.next_line_id = -1 def calculate_distance_boundary(self, r_inner, r_outer): delta_shell = 0 @@ -98,19 +72,16 @@ def calculate_distance_boundary(self, r_inner, r_outer): return distance, delta_shell def calculate_distance_line(self, comov_nu, nu_line, ct): - if not self.last_line: - nu_diff = comov_nu - nu_line + nu_diff = comov_nu - nu_line - if np.abs(nu_diff / comov_nu) < 1e-7: - nu_diff = 0.0 - if nu_diff >= 0: - return (nu_diff/self.nu) * ct - else: - #return np.abs((nu_diff/self.nu) * ct) - raise Exception + if np.abs(nu_diff / comov_nu) < 1e-7: + nu_diff = 0.0 + if nu_diff >= 0: + return (nu_diff/self.nu) * ct else: - return MISS_DISTANCE - + #return np.abs((nu_diff/self.nu) * ct) + raise Exception + def calculate_distance_continuum(self, storage): packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ storage.inverse_sigma_thomson * packet.tau_event @@ -169,30 +140,6 @@ def trace_packet(self, storage_model): # raise Exception return distance_trace, interaction_type, delta_shell - - def compute_distances(self, storage): - """ - Compute all distances (d_line, d_boundary, ???), compare, - and set interaction - - Parameters - ---------- - storage : [type] - [description] - """ - - - compute_distance2line(self, storage) - compute_distance2boundary(self, storage) - if self.d_boundary < self.d_line: - next_interaction = BOUNDARY - self.distance = self.d_boundary - else: - next_interaction = LINE - self.distance = self.d_line - - self.next_interaction = next_interaction - def get_doppler_factor(self, storage): beta = self.r * storage.inverse_time_explosion / C_SPEED_OF_LIGHT @@ -242,20 +189,12 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells) else: self.status = REABSORBED - def set_line(self, storage_model): + def initialize_line_id(self, storage_model): inverse_line_list_nu = storage_model.line_list_nu[::-1] doppler_factor = self.get_doppler_factor(storage_model) comov_nu = self.nu * doppler_factor next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) self.next_line_id = next_line_id - if self.next_line_id > (storage_model.no_of_lines - 1): - self.last_line = True - else: - self.nu_line = storage_model.line_list_nu[self.next_line_id] - self.last_line = False - - ##### FIXME Add close line initializer in a sensible - think about this!1 - #self.set_close_line(storage_model) def transform_energy(self, storage_model): """ @@ -268,69 +207,9 @@ def transform_energy(self, storage_model): comov_energy = self.energy * old_doppler_factor self.energy = comov_energy * inverse_doppler_factor - def line_scatter(self, storage_model): - if self.distance == 0.0: - self.set_close_line(storage_model) - next_line_id = self.next_line_id - storage_model.line_lists_tau_sobolevs - tau_line = storage_model.line_lists_tau_sobolevs[next_line_id, - self.current_shell_id] - tau_continuum = 0.0 - tau_combined = tau_line + tau_continuum - - if (next_line_id + 1) == storage_model.no_of_lines: - self.last_line = True - if (self.tau_event < tau_combined): # Line absorption occurs - self.move_packet(storage_model, self.distance) - self.transform_energy(storage_model) - self.line_emission(storage_model) - else: - self.tau_event -= tau_line - self.next_line_id = next_line_id + 1 - if not self.last_line: - self.nu_line = storage_model.line_list_nu[self.next_line_id] - - - def line_emission(self, storage_model): emission_line_id = self.next_line_id inverse_doppler_factor = 1 / self.get_doppler_factor(storage_model) self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor - self.next_line_id = emission_line_id + 1 - self.nu_line = storage_model.line_list_nu[self.next_line_id] - self.tau_event = np.random.exponential() - self.set_close_line(storage_model) - - - def set_close_line(self, storage_model, line_diff_threshold=1e-7): - """ - The Packet currently sits on next_line_id - 1 and we are checking if the - next line is closer than 1e-7 to set the close_line attribute - - Parameters - ---------- - storage_model : StorageModel - """ - return - if self.last_line: - self.close_line = False - return - - frac_nu_diff = ((storage_model.line_list_nu[self.next_line_id] - - storage_model.line_list_nu[self.next_line_id - 1]) / - storage_model.line_list_nu[self.next_line_id]) - - if not self.last_line and frac_nu_diff < line_diff_threshold: - self.close_line = True - else: - self.close_line = False -#{ -# if (!rpacket_get_last_line (packet) && -# fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - -# rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* -# 1e-7)) -# { -# rpacket_set_close_line (packet, true); -# } -#} + self.next_line_id = emission_line_id + 1 \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py new file mode 100644 index 00000000000..4fc8125a04a --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -0,0 +1,18 @@ +from numba import njit +import numpy as np +from enum import Enum +from tardis.montecarlo.montecarlo_numba.rpacket import ( + ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) + +@njit +def single_packet_loop(storage_model, r_packet): + while r_packet.status == IN_PROCESS: + #rpacket_interactions(r_packet, storage_model) + r_packet.initialize_line_id(storage_model) + distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) + if interaction_type == BOUNDARY: + r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) + elif interaction_type == LINE: + r_packet.move_packet(distance) + r_packet.transform_energy(storage_model) + r_packet.line_emission(storage_model) From bfd571dbd439cbca42fe2466368b5939dc89a9df Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 14:59:49 -0400 Subject: [PATCH 016/116] rewrite problem --- .../montecarlo/montecarlo_numba/__init__.py | 2 +- tardis/montecarlo/montecarlo_numba/base.py | 2 +- tardis/montecarlo/montecarlo_numba/rpacket.py | 68 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index dd918494b6a..c68b278dab2 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': True} +njit_dict = {'fastmath': False} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 0822b67a349..840cfd422fb 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -12,7 +12,7 @@ def montecarlo_radial1d(model, plasma, runner): storage_model = initialize_storage_model(model, plasma, runner) montecarlo_main_loop(storage_model) -@njit(**njit_dict, nogil=True, parallel=True) +@njit(**njit_dict, nogil=True) def montecarlo_main_loop(storage_model): """ This is the main loop of the MonteCarlo routine that generates packets diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 63e641438df..0c6402d3a82 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -32,7 +32,39 @@ ('status', int64), ] +@njit(**njit_dict) +def calculate_distance_boundary(r, mu, r_inner, r_outer): + delta_shell = 0 + if (mu > 0.0): + # direction outward + distance = np.sqrt(r_outer * r_outer + ((mu**2 - 1.0) * r**2)) - (r * mu) + delta_shell = 1 + else: + # going inward + check = r_inner**2 + (r**2 * (mu**2 - 1.0)) + + if (check >= 0.0): + # hit inner boundary + distance = -r * mu - np.sqrt(check) + delta_shell = -1 + else: + # miss inner boundary + distance = np.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) + delta_shell = 1 + + return distance, delta_shell +@njit(**njit_dict) +def calculate_distance_line(nu, comov_nu, nu_line, ct): + nu_diff = comov_nu - nu_line + + if np.abs(nu_diff / comov_nu) < 1e-7: + nu_diff = 0.0 + if nu_diff >= 0: + return (nu_diff / nu) * ct + else: + #return np.abs((nu_diff/self.nu) * ct) + raise Exception @njit(**njit_dict) def get_random_mu(): @@ -49,38 +81,6 @@ def __init__(self, r, mu, nu, energy): self.current_shell_id = 0 self.status = IN_PROCESS self.next_line_id = -1 - - def calculate_distance_boundary(self, r_inner, r_outer): - delta_shell = 0 - if (self.mu > 0.0): - # direction outward - distance = np.sqrt(r_outer * r_outer + ((self.mu**2 - 1.0) * self.r**2)) - (self.r * self.mu) - delta_shell = 1 - else: - # going inward - check = r_inner**2 + (self.r**2 * (self.mu**2 - 1.0)) - - if (check >= 0.0): - # hit inner boundary - distance = -self.r * self.mu - np.sqrt(check) - delta_shell = -1 - else: - # miss inner boundary - distance = np.sqrt(r_outer**2 + ((self.mu**2 - 1.0) * self.r**2)) - (self.r * self.mu) - delta_shell = 1 - - return distance, delta_shell - - def calculate_distance_line(self, comov_nu, nu_line, ct): - nu_diff = comov_nu - nu_line - - if np.abs(nu_diff / comov_nu) < 1e-7: - nu_diff = 0.0 - if nu_diff >= 0: - return (nu_diff/self.nu) * ct - else: - #return np.abs((nu_diff/self.nu) * ct) - raise Exception def calculate_distance_continuum(self, storage): packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ @@ -90,7 +90,7 @@ def trace_packet(self, storage_model): r_inner = storage_model.r_inner[self.current_shell_id] r_outer = storage_model.r_outer[self.current_shell_id] - distance_boundary, delta_shell = self.calculate_distance_boundary(r_inner, r_outer) + distance_boundary, delta_shell = calculate_distance_boundary(self.r, self.mu, r_inner, r_outer) #defining start for stuff cur_line_id = self.next_line_id @@ -118,7 +118,7 @@ def trace_packet(self, storage_model): break tau_trace_line_combined += tau_trace_line - distance_trace = self.calculate_distance_line(comov_nu, nu_line, storage_model.ct) + distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering if distance_trace > distance_boundary: From a16f23da40a3b56774e1eecfc3c78d1965ee60fd Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 15:49:41 -0400 Subject: [PATCH 017/116] further simplified r_packet --- tardis/montecarlo/montecarlo_numba/rpacket.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 0c6402d3a82..d90a06e9750 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -66,6 +66,13 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct): #return np.abs((nu_diff/self.nu) * ct) raise Exception +@njit(**njit_dict) +def get_doppler_factor(r, mu, inverse_time_explosion): + beta = (r * inverse_time_explosion) / C_SPEED_OF_LIGHT + + return 1.0 - mu * beta + + @njit(**njit_dict) def get_random_mu(): return 2.0 * np.random.random() - 1.0 @@ -99,7 +106,7 @@ def trace_packet(self, storage_model): tau_event = np.random.exponential() tau_trace_line = 0.0 tau_trace_line_combined = 0.0 - doppler_factor = self.get_doppler_factor(storage_model) + doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 @@ -131,6 +138,7 @@ def trace_packet(self, storage_model): #break if tau_trace_combined > tau_event: interaction_type = LINE #Line + self.next_line_id = cur_line_id break cur_line_id += 1 @@ -140,11 +148,6 @@ def trace_packet(self, storage_model): # raise Exception return distance_trace, interaction_type, delta_shell - - def get_doppler_factor(self, storage): - beta = self.r * storage.inverse_time_explosion / C_SPEED_OF_LIGHT - - return 1.0 - self.mu * beta def move_packet(self, distance): """Move packet a distance and recalculate the new angle mu @@ -191,7 +194,7 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells) def initialize_line_id(self, storage_model): inverse_line_list_nu = storage_model.line_list_nu[::-1] - doppler_factor = self.get_doppler_factor(storage_model) + doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) self.next_line_id = next_line_id @@ -201,15 +204,15 @@ def transform_energy(self, storage_model): Transform from the LabFrame to the ComovingFrame. Then change the angle and transform back conserving energy in the ComovingFrame. """ - old_doppler_factor = self.get_doppler_factor(storage_model) + old_doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) self.mu = get_random_mu() - inverse_doppler_factor = 1. / self.get_doppler_factor(storage_model) + inverse_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_energy = self.energy * old_doppler_factor self.energy = comov_energy * inverse_doppler_factor def line_emission(self, storage_model): emission_line_id = self.next_line_id - inverse_doppler_factor = 1 / self.get_doppler_factor(storage_model) + inverse_doppler_factor = 1 / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor self.next_line_id = emission_line_id + 1 \ No newline at end of file From 17007c81e1a827bd13bd1383ba30cbcec45cf2fe Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 17:00:05 -0400 Subject: [PATCH 018/116] fixing simple implementation Co-authored-by: Christian Vogl --- .../montecarlo/montecarlo_numba/__init__.py | 2 +- tardis/montecarlo/montecarlo_numba/rpacket.py | 26 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index c68b278dab2..dd918494b6a 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': False} +njit_dict = {'fastmath': True} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index d90a06e9750..2a72363e0e1 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -11,7 +11,6 @@ C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 - #class PacketStatus(Enum): IN_PROCESS = 0 EMITTED = 1 @@ -37,7 +36,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): delta_shell = 0 if (mu > 0.0): # direction outward - distance = np.sqrt(r_outer * r_outer + ((mu**2 - 1.0) * r**2)) - (r * mu) + distance = np.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) delta_shell = 1 else: # going inward @@ -56,8 +55,9 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): @njit(**njit_dict) def calculate_distance_line(nu, comov_nu, nu_line, ct): + if nu_line == 0.0: + return MISS_DISTANCE nu_diff = comov_nu - nu_line - if np.abs(nu_diff / comov_nu) < 1e-7: nu_diff = 0.0 if nu_diff >= 0: @@ -69,7 +69,6 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct): @njit(**njit_dict) def get_doppler_factor(r, mu, inverse_time_explosion): beta = (r * inverse_time_explosion) / C_SPEED_OF_LIGHT - return 1.0 - mu * beta @@ -97,6 +96,8 @@ def trace_packet(self, storage_model): r_inner = storage_model.r_inner[self.current_shell_id] r_outer = storage_model.r_outer[self.current_shell_id] + distance = 0.0 + distance_boundary, delta_shell = calculate_distance_boundary(self.r, self.mu, r_inner, r_outer) #defining start for stuff @@ -106,15 +107,14 @@ def trace_packet(self, storage_model): tau_event = np.random.exponential() tau_trace_line = 0.0 tau_trace_line_combined = 0.0 + #Calculating doppler factor doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 - - #d_continuum = f(tau_event) d_continuum = MISS_DISTANCE - #loop_checker = 0 + while True: - if cur_line_id < storage_model.no_of_lines: + if cur_line_id < storage_model.no_of_lines: # last_line nu_line = storage_model.line_list_nu[cur_line_id] tau_trace_line += storage_model.line_lists_tau_sobolevs[cur_line_id, self.current_shell_id] @@ -122,6 +122,7 @@ def trace_packet(self, storage_model): nu_line = 0.0 tau_trace_line = 0.0 interaction_type = BOUNDARY # FIXME: does not work for e-scattering + distance = distance_boundary break tau_trace_line_combined += tau_trace_line @@ -131,6 +132,7 @@ def trace_packet(self, storage_model): if distance_trace > distance_boundary: interaction_type = BOUNDARY # BOUNDARY self.next_line_id = cur_line_id + distance = distance_boundary break if distance_trace > d_continuum: @@ -139,6 +141,7 @@ def trace_packet(self, storage_model): if tau_trace_combined > tau_event: interaction_type = LINE #Line self.next_line_id = cur_line_id + distance = distance_trace break cur_line_id += 1 @@ -147,7 +150,7 @@ def trace_packet(self, storage_model): #if loop_checker > 10000: # raise Exception - return distance_trace, interaction_type, delta_shell + return distance, interaction_type, delta_shell def move_packet(self, distance): """Move packet a distance and recalculate the new angle mu @@ -206,13 +209,12 @@ def transform_energy(self, storage_model): """ old_doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) self.mu = get_random_mu() - inverse_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + inverse_new_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_energy = self.energy * old_doppler_factor - self.energy = comov_energy * inverse_doppler_factor + self.energy = comov_energy * inverse_new_doppler_factor def line_emission(self, storage_model): emission_line_id = self.next_line_id inverse_doppler_factor = 1 / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor - self.next_line_id = emission_line_id + 1 \ No newline at end of file From 11e76db05c88320be31f54cd4a62376c90789103 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 18:56:52 -0400 Subject: [PATCH 019/116] more fixes Co-authored-by: Christian Vogl --- .../montecarlo/montecarlo_numba/__init__.py | 2 +- tardis/montecarlo/montecarlo_numba/base.py | 1 + tardis/montecarlo/montecarlo_numba/rpacket.py | 26 +++++++++---------- .../montecarlo_numba/single_packet_loop.py | 4 +-- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index dd918494b6a..c68b278dab2 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': True} +njit_dict = {'fastmath': False} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 840cfd422fb..4819c3e85fb 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -32,6 +32,7 @@ def montecarlo_main_loop(storage_model): storage_model.packet_energies[i]) single_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu + if r_packet.status == REABSORBED: output_energies[i] = -r_packet.energy elif r_packet.status == EMITTED: diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 2a72363e0e1..00c0317f574 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -1,7 +1,7 @@ import numpy as np from enum import Enum from numba import int64, float64, boolean -from numba import jitclass, njit +from numba import jitclass, njit, gdb from tardis.montecarlo.montecarlo_numba.compute_distance import (compute_distance2boundary, compute_distance2continuum, compute_distance2line) @@ -54,7 +54,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): return distance, delta_shell @njit(**njit_dict) -def calculate_distance_line(nu, comov_nu, nu_line, ct): +def calculate_distance_line(nu, comov_nu, nu_line, ct, i): if nu_line == 0.0: return MISS_DISTANCE nu_diff = comov_nu - nu_line @@ -63,7 +63,7 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct): if nu_diff >= 0: return (nu_diff / nu) * ct else: - #return np.abs((nu_diff/self.nu) * ct) + print('nu', nu, nu_diff, comov_nu, nu_line, ct, np.abs((nu_diff / nu) * ct), i) raise Exception @njit(**njit_dict) @@ -107,14 +107,15 @@ def trace_packet(self, storage_model): tau_event = np.random.exponential() tau_trace_line = 0.0 tau_trace_line_combined = 0.0 + #Calculating doppler factor doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 d_continuum = MISS_DISTANCE - + while True: - if cur_line_id < storage_model.no_of_lines: # last_line + if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] tau_trace_line += storage_model.line_lists_tau_sobolevs[cur_line_id, self.current_shell_id] @@ -123,10 +124,11 @@ def trace_packet(self, storage_model): tau_trace_line = 0.0 interaction_type = BOUNDARY # FIXME: does not work for e-scattering distance = distance_boundary + self.next_line_id = cur_line_id break tau_trace_line_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) + distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct, i) tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering if distance_trace > distance_boundary: @@ -135,9 +137,9 @@ def trace_packet(self, storage_model): distance = distance_boundary break - if distance_trace > d_continuum: - interaction_type = 10 #continuum - #break + #if distance_trace > d_continuum: + # interaction_type = 10 #continuum + # #break if tau_trace_combined > tau_event: interaction_type = LINE #Line self.next_line_id = cur_line_id @@ -145,11 +147,7 @@ def trace_packet(self, storage_model): break cur_line_id += 1 - - #loop_checker +=1 - #if loop_checker > 10000: - # raise Exception - + return distance, interaction_type, delta_shell def move_packet(self, distance): diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 4fc8125a04a..4ef4365c1d1 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -6,9 +6,9 @@ @njit def single_packet_loop(storage_model, r_packet): + r_packet.initialize_line_id(storage_model) + while r_packet.status == IN_PROCESS: - #rpacket_interactions(r_packet, storage_model) - r_packet.initialize_line_id(storage_model) distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) if interaction_type == BOUNDARY: r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) From 44e2fe3336de73f23692e8b5a2c484fdf64e9322 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 19:10:59 -0400 Subject: [PATCH 020/116] several fixes Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/rpacket.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 00c0317f574..ce178fb191d 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -54,7 +54,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): return distance, delta_shell @njit(**njit_dict) -def calculate_distance_line(nu, comov_nu, nu_line, ct, i): +def calculate_distance_line(nu, comov_nu, nu_line, ct): if nu_line == 0.0: return MISS_DISTANCE nu_diff = comov_nu - nu_line @@ -63,7 +63,6 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct, i): if nu_diff >= 0: return (nu_diff / nu) * ct else: - print('nu', nu, nu_diff, comov_nu, nu_line, ct, np.abs((nu_diff / nu) * ct), i) raise Exception @njit(**njit_dict) @@ -128,7 +127,7 @@ def trace_packet(self, storage_model): break tau_trace_line_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct, i) + distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering if distance_trace > distance_boundary: From 735ed5ec80a39d61e3f2bdb2cc37c60aa1abe1b1 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 3 May 2019 19:14:15 -0400 Subject: [PATCH 021/116] final simple version Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/rpacket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index ce178fb191d..bf584c045ee 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -116,7 +116,7 @@ def trace_packet(self, storage_model): while True: if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] - tau_trace_line += storage_model.line_lists_tau_sobolevs[cur_line_id, + tau_trace_line = storage_model.line_lists_tau_sobolevs[cur_line_id, self.current_shell_id] else: nu_line = 0.0 From af2c440ba60f240db2ebb77e726d0c41fc27c8b4 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 09:13:10 -0400 Subject: [PATCH 022/116] adding electron scattering --- .../montecarlo/montecarlo_numba/__init__.py | 2 +- tardis/montecarlo/montecarlo_numba/rpacket.py | 42 +++++++++++-------- .../montecarlo_numba/single_packet_loop.py | 8 ++-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index c68b278dab2..dd918494b6a 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': False} +njit_dict = {'fastmath': True} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index bf584c045ee..1c8b0ff0113 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -2,24 +2,25 @@ from enum import Enum from numba import int64, float64, boolean from numba import jitclass, njit, gdb -from tardis.montecarlo.montecarlo_numba.compute_distance import (compute_distance2boundary, - compute_distance2continuum, - compute_distance2line) +from tardis.montecarlo.montecarlo_numba.compute_distance import ( + compute_distance2boundary, + compute_distance2continuum, + compute_distance2line) from tardis.montecarlo.montecarlo_numba import njit_dict from astropy import constants as const C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 - +INVERSE_SIGMA_THOMSON = 1 / const.sigma_T.to('cm^2').value #class PacketStatus(Enum): IN_PROCESS = 0 EMITTED = 1 REABSORBED = 2 -#class InteractionType(Enum): -ESCATTERING = 0 -BOUNDARY = 1 -LINE = 2 +class InteractionType(Enum): + ESCATTERING = 0 + BOUNDARY = 1 + LINE = 2 rpacket_spec = [ ('r', float64), @@ -65,12 +66,17 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct): else: raise Exception +@njit(**njit_dict) +def calculate_distance_electron(shell_id, inverse_electron_densities, tau_event): + return (inverse_electron_densities[shell_id] * + INVERSE_SIGMA_THOMSON * tau_event) + + @njit(**njit_dict) def get_doppler_factor(r, mu, inverse_time_explosion): beta = (r * inverse_time_explosion) / C_SPEED_OF_LIGHT return 1.0 - mu * beta - @njit(**njit_dict) def get_random_mu(): return 2.0 * np.random.random() - 1.0 @@ -87,9 +93,6 @@ def __init__(self, r, mu, nu, energy): self.status = IN_PROCESS self.next_line_id = -1 - def calculate_distance_continuum(self, storage): - packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ - storage.inverse_sigma_thomson * packet.tau_event def trace_packet(self, storage_model): r_inner = storage_model.r_inner[self.current_shell_id] @@ -97,7 +100,8 @@ def trace_packet(self, storage_model): distance = 0.0 - distance_boundary, delta_shell = calculate_distance_boundary(self.r, self.mu, r_inner, r_outer) + distance_boundary, delta_shell = calculate_distance_boundary( + self.r, self.mu, r_inner, r_outer) #defining start for stuff cur_line_id = self.next_line_id @@ -108,10 +112,12 @@ def trace_packet(self, storage_model): tau_trace_line_combined = 0.0 #Calculating doppler factor - doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + doppler_factor = get_doppler_factor(self.r, self.mu, + storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 - d_continuum = MISS_DISTANCE + d_electron = calculate_distance_electron(self.current_shell_id, + storage_model.inverse_electron_densities, tau_event) while True: if cur_line_id < storage_model.no_of_lines: # not last_line @@ -121,7 +127,7 @@ def trace_packet(self, storage_model): else: nu_line = 0.0 tau_trace_line = 0.0 - interaction_type = BOUNDARY # FIXME: does not work for e-scattering + interaction_type = InteractionType.BOUNDARY # FIXME: does not work for e-scattering distance = distance_boundary self.next_line_id = cur_line_id break @@ -131,7 +137,7 @@ def trace_packet(self, storage_model): tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering if distance_trace > distance_boundary: - interaction_type = BOUNDARY # BOUNDARY + interaction_type = InteractionType.BOUNDARY # BOUNDARY self.next_line_id = cur_line_id distance = distance_boundary break @@ -140,7 +146,7 @@ def trace_packet(self, storage_model): # interaction_type = 10 #continuum # #break if tau_trace_combined > tau_event: - interaction_type = LINE #Line + interaction_type = InteractionType.LINE #Line self.next_line_id = cur_line_id distance = distance_trace break diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 4ef4365c1d1..db3602a987d 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,8 +1,8 @@ from numba import njit import numpy as np from enum import Enum -from tardis.montecarlo.montecarlo_numba.rpacket import ( - ESCATTERING, BOUNDARY, LINE, IN_PROCESS, REABSORBED) +from tardis.montecarlo.montecarlo_numba.rpacket import (IN_PROCESS, REABSORBED) +from tardis.montecarlo.montecarlo_numba.rpacket import InteractionType @njit def single_packet_loop(storage_model, r_packet): @@ -10,9 +10,9 @@ def single_packet_loop(storage_model, r_packet): while r_packet.status == IN_PROCESS: distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) - if interaction_type == BOUNDARY: + if interaction_type == InteractionType.BOUNDARY: r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) - elif interaction_type == LINE: + elif interaction_type == InteractionType.LINE: r_packet.move_packet(distance) r_packet.transform_energy(storage_model) r_packet.line_emission(storage_model) From a27a9dae710a7268882f1dde6a42d64d0c23365f Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 09:29:42 -0400 Subject: [PATCH 023/116] add tau_electron --- tardis/montecarlo/montecarlo_numba/rpacket.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 1c8b0ff0113..5493ac5bca8 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -11,7 +11,8 @@ C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 -INVERSE_SIGMA_THOMSON = 1 / const.sigma_T.to('cm^2').value +SIGMA_THOMSON = const.sigma_T.to('cm^2') +INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON #class PacketStatus(Enum): IN_PROCESS = 0 EMITTED = 1 @@ -67,10 +68,12 @@ def calculate_distance_line(nu, comov_nu, nu_line, ct): raise Exception @njit(**njit_dict) -def calculate_distance_electron(shell_id, inverse_electron_densities, tau_event): - return (inverse_electron_densities[shell_id] * - INVERSE_SIGMA_THOMSON * tau_event) +def calculate_distance_electron(inverse_electron_density, tau_event): + return inverse_electron_density * INVERSE_SIGMA_THOMSON * tau_event +@njit(**njit_dict) +def calculate_tau_electron(electron_density, distance): + return electron_density * SIGMA_THOMSON @njit(**njit_dict) def get_doppler_factor(r, mu, inverse_time_explosion): @@ -103,21 +106,28 @@ def trace_packet(self, storage_model): distance_boundary, delta_shell = calculate_distance_boundary( self.r, self.mu, r_inner, r_outer) - #defining start for stuff + #defining start for line interaction cur_line_id = self.next_line_id nu_line = 0.0 + #defining taus tau_event = np.random.exponential() tau_trace_line = 0.0 tau_trace_line_combined = 0.0 + #e scattering initialization + + cur_electron_density = storage_model.electron_densities[shell_id] + cur_inverse_electron_density = 1 / cur_electron_density + d_electron = calculate_distance_electron( + inverse_electron_densities, tau_event) + + #Calculating doppler factor doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 - d_electron = calculate_distance_electron(self.current_shell_id, - storage_model.inverse_electron_densities, tau_event) while True: if cur_line_id < storage_model.no_of_lines: # not last_line @@ -133,8 +143,10 @@ def trace_packet(self, storage_model): break tau_trace_line_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) - tau_trace_combined = tau_trace_line_combined + 0 #tau_trace_electron electron scattering + distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, + storage_model.ct) + tau_electron = calculate_tau_electron(electron_density, distance_trace) + tau_trace_combined = tau_trace_line_combined + tau_trace_electron #tau_trace_electron electron scattering if distance_trace > distance_boundary: interaction_type = InteractionType.BOUNDARY # BOUNDARY From 77c014ddd48a9c300c0bb4a83f270a9987cf535b Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 12:25:25 -0400 Subject: [PATCH 024/116] add e scattering --- tardis/montecarlo/montecarlo_numba/rpacket.py | 58 ++++++++++++++----- .../montecarlo_numba/single_packet_loop.py | 7 ++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 5493ac5bca8..dc061889c14 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -11,7 +11,7 @@ C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 -SIGMA_THOMSON = const.sigma_T.to('cm^2') +SIGMA_THOMSON = const.sigma_T.to('cm^2').value INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON #class PacketStatus(Enum): IN_PROCESS = 0 @@ -19,9 +19,9 @@ REABSORBED = 2 class InteractionType(Enum): - ESCATTERING = 0 BOUNDARY = 1 LINE = 2 + ESCATTERING = 3 rpacket_spec = [ ('r', float64), @@ -73,7 +73,7 @@ def calculate_distance_electron(inverse_electron_density, tau_event): @njit(**njit_dict) def calculate_tau_electron(electron_density, distance): - return electron_density * SIGMA_THOMSON + return electron_density * SIGMA_THOMSON * distance @njit(**njit_dict) def get_doppler_factor(r, mu, inverse_time_explosion): @@ -117,10 +117,11 @@ def trace_packet(self, storage_model): #e scattering initialization - cur_electron_density = storage_model.electron_densities[shell_id] + cur_electron_density = storage_model.electron_densities[ + self.current_shell_id] cur_inverse_electron_density = 1 / cur_electron_density - d_electron = calculate_distance_electron( - inverse_electron_densities, tau_event) + distance_electron = calculate_distance_electron( + cur_inverse_electron_density, tau_event) #Calculating doppler factor @@ -128,6 +129,7 @@ def trace_packet(self, storage_model): storage_model.inverse_time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 + last_line = False while True: if cur_line_id < storage_model.no_of_lines: # not last_line @@ -135,18 +137,17 @@ def trace_packet(self, storage_model): tau_trace_line = storage_model.line_lists_tau_sobolevs[cur_line_id, self.current_shell_id] else: - nu_line = 0.0 - tau_trace_line = 0.0 - interaction_type = InteractionType.BOUNDARY # FIXME: does not work for e-scattering - distance = distance_boundary + last_line = True self.next_line_id = cur_line_id break tau_trace_line_combined += tau_trace_line distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) - tau_electron = calculate_tau_electron(electron_density, distance_trace) - tau_trace_combined = tau_trace_line_combined + tau_trace_electron #tau_trace_electron electron scattering + tau_trace_electron = calculate_tau_electron(cur_electron_density, + distance_trace) + + tau_trace_combined = tau_trace_line_combined + tau_trace_electron if distance_trace > distance_boundary: interaction_type = InteractionType.BOUNDARY # BOUNDARY @@ -164,8 +165,15 @@ def trace_packet(self, storage_model): break cur_line_id += 1 - - return distance, interaction_type, delta_shell + if not last_line: + return distance, interaction_type, delta_shell + else: + if distance_electron < distance_boundary: + #return distance_boundary, InteractionType.BOUNDARY, delta_shell + return distance_electron, InteractionType.ESCATTERING, delta_shell + else: + return distance_boundary, InteractionType.BOUNDARY, delta_shell + def move_packet(self, distance): """Move packet a distance and recalculate the new angle mu @@ -228,6 +236,28 @@ def transform_energy(self, storage_model): comov_energy = self.energy * old_doppler_factor self.energy = comov_energy * inverse_new_doppler_factor + def scatter(self, storage_model): + """ + General scattering for lines as well as thomson. 1) Move packet + 2) get the doppler factor at that position with the old angle + 3) convert the current energy and nu into the comoving frame with the old + mu + 4) Scatter and draw new mu + 5) Transform the comoving energy and nu back + + Parameters + ---------- + distance : [type] + [description] + """ + doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + comov_energy = self.energy * doppler_factor + comov_nu = self.nu * doppler_factor + self.mu = get_random_mu() + inverse_new_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + self.energy = comov_energy * inverse_new_doppler_factor + self.nu = comov_nu * inverse_new_doppler_factor + def line_emission(self, storage_model): emission_line_id = self.next_line_id inverse_doppler_factor = 1 / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index db3602a987d..71a3b98283e 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -14,5 +14,8 @@ def single_packet_loop(storage_model, r_packet): r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) elif interaction_type == InteractionType.LINE: r_packet.move_packet(distance) - r_packet.transform_energy(storage_model) - r_packet.line_emission(storage_model) + r_packet.scatter(storage_model) + r_packet.next_line_id += 1 + elif interaction_type == InteractionType.ESCATTERING: + r_packet.move_packet(distance) + r_packet.scatter(storage_model) From c440853827efe5d8757ea3b8d5472e265ace4781 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 13:22:07 -0400 Subject: [PATCH 025/116] add electron scattering --- tardis/montecarlo/montecarlo_numba/rpacket.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index dc061889c14..f4cc0d884cb 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -148,16 +148,19 @@ def trace_packet(self, storage_model): distance_trace) tau_trace_combined = tau_trace_line_combined + tau_trace_electron - - if distance_trace > distance_boundary: + + if (distance_boundary <= distance_trace) and (distance_boundary <= distance_electron): interaction_type = InteractionType.BOUNDARY # BOUNDARY self.next_line_id = cur_line_id distance = distance_boundary break - #if distance_trace > d_continuum: - # interaction_type = 10 #continuum - # #break + if (distance_electron < distance_trace) and (distance_electron < distance_boundary): + interaction_type = InteractionType.ESCATTERING + distance = distance_electron + self.next_line_id = cur_line_id + break + if tau_trace_combined > tau_event: interaction_type = InteractionType.LINE #Line self.next_line_id = cur_line_id @@ -169,7 +172,6 @@ def trace_packet(self, storage_model): return distance, interaction_type, delta_shell else: if distance_electron < distance_boundary: - #return distance_boundary, InteractionType.BOUNDARY, delta_shell return distance_electron, InteractionType.ESCATTERING, delta_shell else: return distance_boundary, InteractionType.BOUNDARY, delta_shell From ee7f007c46b40091c4f325a7491365b59b06eaee Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 14:24:46 -0400 Subject: [PATCH 026/116] add first vpacket stuff --- tardis/montecarlo/montecarlo_numba/base.py | 6 +- tardis/montecarlo/montecarlo_numba/rpacket.py | 32 ++---- .../montecarlo_numba/single_packet_loop.py | 8 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 97 +++++++++++++++++++ 4 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/vpacket.py diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 4819c3e85fb..f41c9d9a3ca 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,6 +1,6 @@ from numba import prange, njit, config import numpy as np -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, REABSORBED, EMITTED +from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel, initialize_storage_model from tardis.montecarlo.montecarlo_numba.single_packet_loop import single_packet_loop from tardis.montecarlo.montecarlo_numba import njit_dict @@ -33,9 +33,9 @@ def montecarlo_main_loop(storage_model): single_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu - if r_packet.status == REABSORBED: + if r_packet.status == PacketStatus.REABSORBED: output_energies[i] = -r_packet.energy - elif r_packet.status == EMITTED: + elif r_packet.status == PacketStatus.EMITTED: output_energies[i] = r_packet.energy storage_model.output_energies[:] = output_energies[:] storage_model.output_nus[:] = output_nus[:] diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index f4cc0d884cb..95cae5fc313 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -13,10 +13,11 @@ MISS_DISTANCE = 1e99 SIGMA_THOMSON = const.sigma_T.to('cm^2').value INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON -#class PacketStatus(Enum): -IN_PROCESS = 0 -EMITTED = 1 -REABSORBED = 2 + +class PacketStatus(Enum): + IN_PROCESS = 0 + EMITTED = 1 + REABSORBED = 2 class InteractionType(Enum): BOUNDARY = 1 @@ -93,11 +94,12 @@ def __init__(self, r, mu, nu, energy): self.nu = nu self.energy = energy self.current_shell_id = 0 - self.status = IN_PROCESS + self.status = PacketStatus.IN_PROCESS self.next_line_id = -1 def trace_packet(self, storage_model): + r_inner = storage_model.r_inner[self.current_shell_id] r_outer = storage_model.r_outer[self.current_shell_id] @@ -216,9 +218,9 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells) or (self.current_shell_id > 0 and delta_shell == -1)): self.current_shell_id += delta_shell elif delta_shell == 1: - self.status = EMITTED + self.status = PacketStatus.EMITTED else: - self.status = REABSORBED + self.status = PacketStatus.REABSORBED def initialize_line_id(self, storage_model): inverse_line_list_nu = storage_model.line_list_nu[::-1] @@ -227,16 +229,6 @@ def initialize_line_id(self, storage_model): next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) self.next_line_id = next_line_id - def transform_energy(self, storage_model): - """ - Transform from the LabFrame to the ComovingFrame. Then change the angle - and transform back conserving energy in the ComovingFrame. - """ - old_doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) - self.mu = get_random_mu() - inverse_new_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) - comov_energy = self.energy * old_doppler_factor - self.energy = comov_energy * inverse_new_doppler_factor def scatter(self, storage_model): """ @@ -259,9 +251,3 @@ def scatter(self, storage_model): inverse_new_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) self.energy = comov_energy * inverse_new_doppler_factor self.nu = comov_nu * inverse_new_doppler_factor - - def line_emission(self, storage_model): - emission_line_id = self.next_line_id - inverse_doppler_factor = 1 / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) - self.nu = storage_model.line_list_nu[emission_line_id] * inverse_doppler_factor - self.next_line_id = emission_line_id + 1 \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 71a3b98283e..687135f3169 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,14 +1,14 @@ from numba import njit import numpy as np -from enum import Enum -from tardis.montecarlo.montecarlo_numba.rpacket import (IN_PROCESS, REABSORBED) -from tardis.montecarlo.montecarlo_numba.rpacket import InteractionType + +from tardis.montecarlo.montecarlo_numba.rpacket import ( + InteractionType, PacketStatus) @njit def single_packet_loop(storage_model, r_packet): r_packet.initialize_line_id(storage_model) - while r_packet.status == IN_PROCESS: + while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) if interaction_type == InteractionType.BOUNDARY: r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py new file mode 100644 index 00000000000..f88cedf0dd9 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -0,0 +1,97 @@ +vpacket_spec = [ + ('r', float64), + ('mu', float64), + ('nu', float64), + ('energy', float64), + ('next_line_id', int64), + ('current_shell_id', int64), + ('status', int64), +] + +@jitclass(vpacket_spec) +class VPacket(object): + def __init__(self, r, mu, nu, energy): + self.r = r + self.mu = mu + self.nu = nu + self.energy = energy + self.current_shell_id = 0 + self.status = IN_PROCESS + self.next_line_id = -1 + + def trace_packet(self, storage_model): + + r_inner = storage_model.r_inner[self.current_shell_id] + r_outer = storage_model.r_outer[self.current_shell_id] + + distance = 0.0 + + distance_boundary, delta_shell = calculate_distance_boundary( + self.r, self.mu, r_inner, r_outer) + + #defining start for line interaction + cur_line_id = self.next_line_id + nu_line = 0.0 + + #defining taus + tau_event = np.random.exponential() + tau_trace_line = 0.0 + tau_trace_line_combined = 0.0 + + #e scattering initialization + + cur_electron_density = storage_model.electron_densities[ + self.current_shell_id] + cur_inverse_electron_density = 1 / cur_electron_density + distance_electron = calculate_distance_electron( + cur_inverse_electron_density, tau_event) + + + #Calculating doppler factor + doppler_factor = get_doppler_factor(self.r, self.mu, + storage_model.inverse_time_explosion) + comov_nu = self.nu * doppler_factor + distance_trace = 0.0 + last_line = False + + while True: + if cur_line_id < storage_model.no_of_lines: # not last_line + nu_line = storage_model.line_list_nu[cur_line_id] + tau_trace_line = storage_model.line_lists_tau_sobolevs[cur_line_id, + self.current_shell_id] + else: + last_line = True + self.next_line_id = cur_line_id + break + + tau_trace_line_combined += tau_trace_line + distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, + storage_model.ct) + tau_trace_electron = calculate_tau_electron(cur_electron_density, + distance_trace) + + tau_trace_combined = tau_trace_line_combined + tau_trace_electron + + if (distance_boundary <= distance_trace): + ## current_shell_id +=1 + ## distance_boundary + #unless shell + interaction_type = InteractionType.BOUNDARY # BOUNDARY + self.next_line_id = cur_line_id + distance = distance_boundary + break + + if (distance_electron < distance_trace) and (distance_electron < distance_boundary): + interaction_type = InteractionType.ESCATTERING + distance = distance_electron + self.next_line_id = cur_line_id + break + + cur_line_id += 1 + if not last_line: + return distance, interaction_type, delta_shell + else: + if distance_electron < distance_boundary: + return distance_electron, InteractionType.ESCATTERING, delta_shell + else: + return distance_boundary, InteractionType.BOUNDARY, delta_shell From 54f3151f829865c704ab66bf5838f1525b2d38b9 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 4 May 2019 22:18:35 -0400 Subject: [PATCH 027/116] further restructure of the numba montecarlo --- .../montecarlo_numba/compute_distance.py | 71 ------------------- .../montecarlo_numba/numba_model.py | 36 ++++++++++ tardis/montecarlo/montecarlo_numba/rpacket.py | 20 +++--- 3 files changed, 46 insertions(+), 81 deletions(-) delete mode 100644 tardis/montecarlo/montecarlo_numba/compute_distance.py create mode 100644 tardis/montecarlo/montecarlo_numba/numba_model.py diff --git a/tardis/montecarlo/montecarlo_numba/compute_distance.py b/tardis/montecarlo/montecarlo_numba/compute_distance.py deleted file mode 100644 index c50aff672cc..00000000000 --- a/tardis/montecarlo/montecarlo_numba/compute_distance.py +++ /dev/null @@ -1,71 +0,0 @@ -from numba import jitclass, njit -import numpy as np -from astropy import constants as const -from tardis.montecarlo.montecarlo_numba import njit_dict - -C_SPEED_OF_LIGHT = const.c.to('cm/s').value -MISS_DISTANCE = 1e99 - -@njit(**njit_dict) -def compute_distance2boundary(packet, storage): - r = packet.r - mu = packet.mu - r_outer = storage.r_outer[packet.current_shell_id] - r_inner = storage.r_inner[packet.current_shell_id] - - if (mu > 0.0): - # direction outward - packet.delta_shell_id = +1 - distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) - else: - # going inward - check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) - - if (check >= 0.0): - # hit inner boundary - packet.delta_shell_id = -1 - distance = -r * mu - np.sqrt(check) - else: - # miss inner boundary - packet.delta_shell_id = + 1 - distance = np.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) - packet.d_boundary = distance - - - -@njit -def compute_distance2line(packet, storage_model): - if not packet.last_line and not packet.close_line: - r = packet.r - mu = packet.mu - nu = packet.nu - nu_line = packet.nu_line - - ct = storage_model.time_explosion * C_SPEED_OF_LIGHT - doppler_factor = packet.get_doppler_factor(storage_model) - comov_nu = nu * doppler_factor - - nu_diff = comov_nu - nu_line - if np.abs(nu_diff / comov_nu) < 1e-7: - nu_diff = 0.0 - if nu_diff >= 0: - - distance = (nu_diff/nu) * ct - #else: - # nu_r = nu_line / nu - # distance = - mu * r + (ct - nu_r * nu_r * - # np.sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r) - packet.d_line = distance - else: - raise Exception - elif packet.close_line: - packet.d_line = 0.0 - else: - packet.d_line = MISS_DISTANCE - #return TARDIS_ERROR_OK - -@njit(**njit_dict) -def compute_distance2continuum(packet, storage): - packet.d_electron = storage.inverse_electron_densities[packet.current_shell_id] * \ - storage.inverse_sigma_thomson * packet.tau_event - diff --git a/tardis/montecarlo/montecarlo_numba/numba_model.py b/tardis/montecarlo/montecarlo_numba/numba_model.py new file mode 100644 index 00000000000..761e01b1421 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/numba_model.py @@ -0,0 +1,36 @@ +from numba import float64, jitclass +from tardis import constants as const + + +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + +numba_model_spec = [ + ('r_inner', float64[:]), + ('r_outer', float64[:]), + ('time_explosion', float64), + ('electron_density', float64[:]), + ('ct', float64), +] + + +@jitclass(numba_model_spec) +class NumbaModel(object): + + def __init__(self, r_inner, r_outer, time_explosion, electron_density): + """ + Model for the Numba mode + + Parameters + ---------- + r_inner: numpy.ndarray + r_outer: numpy.ndarray + time_explosion: float + electron_density: numpy.ndarray + """ + self.r_inner = r_inner + self.r_outer = r_outer + self.time_explosion = time_explosion + self.electron_density = electron_density + self.inverse_electron_density = 1 / electron_density + + diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 95cae5fc313..4ddc0db6a55 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -2,12 +2,9 @@ from enum import Enum from numba import int64, float64, boolean from numba import jitclass, njit, gdb -from tardis.montecarlo.montecarlo_numba.compute_distance import ( - compute_distance2boundary, - compute_distance2continuum, - compute_distance2line) + from tardis.montecarlo.montecarlo_numba import njit_dict -from astropy import constants as const +from tardis import constants as const C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 @@ -136,8 +133,8 @@ def trace_packet(self, storage_model): while True: if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] - tau_trace_line = storage_model.line_lists_tau_sobolevs[cur_line_id, - self.current_shell_id] + tau_trace_line = storage_model.line_lists_tau_sobolevs[ + cur_line_id, self.current_shell_id] else: last_line = True self.next_line_id = cur_line_id @@ -151,13 +148,15 @@ def trace_packet(self, storage_model): tau_trace_combined = tau_trace_line_combined + tau_trace_electron - if (distance_boundary <= distance_trace) and (distance_boundary <= distance_electron): + if ((distance_boundary <= distance_trace) and + (distance_boundary <= distance_electron)): interaction_type = InteractionType.BOUNDARY # BOUNDARY self.next_line_id = cur_line_id distance = distance_boundary break - if (distance_electron < distance_trace) and (distance_electron < distance_boundary): + if ((distance_electron < distance_trace) and + (distance_electron < distance_boundary)): interaction_type = InteractionType.ESCATTERING distance = distance_electron self.next_line_id = cur_line_id @@ -174,7 +173,8 @@ def trace_packet(self, storage_model): return distance, interaction_type, delta_shell else: if distance_electron < distance_boundary: - return distance_electron, InteractionType.ESCATTERING, delta_shell + return (distance_electron, InteractionType.ESCATTERING, + delta_shell) else: return distance_boundary, InteractionType.BOUNDARY, delta_shell From 24bc8b285e9014691bf4080d28d41df92c3e2a4b Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 5 May 2019 00:33:04 -0400 Subject: [PATCH 028/116] adding a packet collection --- tardis/montecarlo/montecarlo_numba/base.py | 31 ++++++++++++------- .../{numba_model.py => numba_interface.py} | 25 ++++++++++++++- 2 files changed, 43 insertions(+), 13 deletions(-) rename tardis/montecarlo/montecarlo_numba/{numba_model.py => numba_interface.py} (53%) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index f41c9d9a3ca..6b827267200 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,19 +1,24 @@ from numba import prange, njit, config import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus -from tardis.montecarlo.montecarlo_numba.storage_model import StorageModel, initialize_storage_model +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + PacketCollection) +from tardis.montecarlo.montecarlo_numba.storage_model import initialize_storage_model from tardis.montecarlo.montecarlo_numba.single_packet_loop import single_packet_loop from tardis.montecarlo.montecarlo_numba import njit_dict -#config.THREADING_LAYER = 'threadsafe' -#config.DEBUG_ARRAY_OPT=1 def montecarlo_radial1d(model, plasma, runner): + packet_collection = PacketCollection( + runner.input_nu, runner.input_mu, runner.input_energy, + runner._output_nu, runner._output_energy + ) storage_model = initialize_storage_model(model, plasma, runner) - montecarlo_main_loop(storage_model) + montecarlo_main_loop(packet_collection) + @njit(**njit_dict, nogil=True) -def montecarlo_main_loop(storage_model): +def montecarlo_main_loop(packet_collection): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -23,13 +28,15 @@ def montecarlo_main_loop(storage_model): storage_model : [type] [description] """ - output_nus = np.empty_like(storage_model.output_nus) - output_energies = np.empty_like(storage_model.output_energies) - for i in prange(storage_model.no_of_packets): - r_packet = RPacket(storage_model.r_inner[0], - storage_model.packet_mus[i], - storage_model.packet_nus[i], - storage_model.packet_energies[i]) + output_nus = np.empty_like(packet_collection.packets_output_nu) + output_energies = np.empty_like(packet_collection.packets_output_nu) + + for i in prange(len(output_nus)): + r_packet = RPacket(packet_collection.r_inner[0], + packet_collection.packet_mus[i], + packet_collection.packet_nus[i], + packet_collection.packet_energies[i]) + single_packet_loop(storage_model, r_packet) output_nus[i] = r_packet.nu diff --git a/tardis/montecarlo/montecarlo_numba/numba_model.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py similarity index 53% rename from tardis/montecarlo/montecarlo_numba/numba_model.py rename to tardis/montecarlo/montecarlo_numba/numba_interface.py index 761e01b1421..0940091a5dc 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_model.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -31,6 +31,29 @@ def __init__(self, r_inner, r_outer, time_explosion, electron_density): self.r_outer = r_outer self.time_explosion = time_explosion self.electron_density = electron_density - self.inverse_electron_density = 1 / electron_density +packet_collection_spec = [ + ('packets_nu', float64[:]), + ('packets_mu', float64[:]), + ('packets_energy', float64[:]), + ('packets_output_nu', float64[:]), + ('packets_output_energy', float64[:]), +] + +@jitclass(packet_collection_spec) +class PacketCollection(object): + def __init__(self, packets_input_nu, packets_input_mu, packets_energy, + packets_output_nu, packets_output_energy): + self.packets_input_nu = packets_input_nu + self.packets_input_mu = packets_input_mu + self.packets_energy = packets_energy + self.packets_output_nu = packets_output_nu + self.packets_output_energy = packets_output_energy + + + + +class Estimators(object): + pass + From d59c8f7d83c65ad4b8524b327dcb99a21dbc5bfa Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 5 May 2019 01:13:34 -0400 Subject: [PATCH 029/116] update and clean up the numba interface --- tardis/montecarlo/montecarlo_numba/base.py | 37 +++++--- .../montecarlo_numba/numba_interface.py | 31 ++++--- tardis/montecarlo/montecarlo_numba/rpacket.py | 75 +++++++++------- .../montecarlo_numba/single_packet_loop.py | 26 ++++-- .../montecarlo_numba/storage_model.py | 86 ------------------- 5 files changed, 109 insertions(+), 146 deletions(-) delete mode 100644 tardis/montecarlo/montecarlo_numba/storage_model.py diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 6b827267200..9ad1766691b 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -2,9 +2,10 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( - PacketCollection) -from tardis.montecarlo.montecarlo_numba.storage_model import initialize_storage_model -from tardis.montecarlo.montecarlo_numba.single_packet_loop import single_packet_loop + PacketCollection, NumbaModel, NumbaPlasma) + +from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( + single_packet_loop) from tardis.montecarlo.montecarlo_numba import njit_dict @@ -13,12 +14,21 @@ def montecarlo_radial1d(model, plasma, runner): runner.input_nu, runner.input_mu, runner.input_energy, runner._output_nu, runner._output_energy ) - storage_model = initialize_storage_model(model, plasma, runner) - montecarlo_main_loop(packet_collection) + numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, + model.time_explosion.to('s').value) + numba_plasma = NumbaPlasma(plasma.electron_densities.values, + plasma.atomic_data.lines.nu.values, + np.ascontiguousarray( + plasma.tau_sobolevs.values.copy(), + dtype=np.float64) + ) + + #storage_model = initialize_storage_model(model, plasma, runner) + montecarlo_main_loop(packet_collection, numba_model, numba_plasma) @njit(**njit_dict, nogil=True) -def montecarlo_main_loop(packet_collection): +def montecarlo_main_loop(packet_collection, numba_model, numba_plasma): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -32,18 +42,19 @@ def montecarlo_main_loop(packet_collection): output_energies = np.empty_like(packet_collection.packets_output_nu) for i in prange(len(output_nus)): - r_packet = RPacket(packet_collection.r_inner[0], - packet_collection.packet_mus[i], - packet_collection.packet_nus[i], - packet_collection.packet_energies[i]) + r_packet = RPacket(numba_model.r_inner[0], + packet_collection.packets_input_mu[i], + packet_collection.packets_input_nu[i], + packet_collection.packets_input_energy[i]) - single_packet_loop(storage_model, r_packet) + single_packet_loop(r_packet, numba_model, numba_plasma) output_nus[i] = r_packet.nu if r_packet.status == PacketStatus.REABSORBED: output_energies[i] = -r_packet.energy elif r_packet.status == PacketStatus.EMITTED: output_energies[i] = r_packet.energy - storage_model.output_energies[:] = output_energies[:] - storage_model.output_nus[:] = output_nus[:] + + packet_collection.packets_output_energy[:] = output_energies[:] + packet_collection.packets_output_nu[:] = output_nus[:] diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 0940091a5dc..d9037bd6257 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -1,4 +1,4 @@ -from numba import float64, jitclass +from numba import float64, int64, jitclass from tardis import constants as const @@ -7,16 +7,14 @@ numba_model_spec = [ ('r_inner', float64[:]), ('r_outer', float64[:]), - ('time_explosion', float64), - ('electron_density', float64[:]), - ('ct', float64), + ('time_explosion', float64) ] @jitclass(numba_model_spec) class NumbaModel(object): - def __init__(self, r_inner, r_outer, time_explosion, electron_density): + def __init__(self, r_inner, r_outer, time_explosion): """ Model for the Numba mode @@ -25,29 +23,40 @@ def __init__(self, r_inner, r_outer, time_explosion, electron_density): r_inner: numpy.ndarray r_outer: numpy.ndarray time_explosion: float - electron_density: numpy.ndarray """ self.r_inner = r_inner self.r_outer = r_outer self.time_explosion = time_explosion + +numba_plasma_spec = [ + ('electron_density', float64[:]), + ('line_list_nu', float64[:]), + ('tau_sobolev', float64[:, :]), +] + +@jitclass(numba_plasma_spec) +class NumbaPlasma(object): + def __init__(self, electron_density, line_list_nu, tau_sobolev): self.electron_density = electron_density + self.line_list_nu = line_list_nu + self.tau_sobolev = tau_sobolev packet_collection_spec = [ - ('packets_nu', float64[:]), - ('packets_mu', float64[:]), - ('packets_energy', float64[:]), + ('packets_input_nu', float64[:]), + ('packets_input_mu', float64[:]), + ('packets_input_energy', float64[:]), ('packets_output_nu', float64[:]), ('packets_output_energy', float64[:]), ] @jitclass(packet_collection_spec) class PacketCollection(object): - def __init__(self, packets_input_nu, packets_input_mu, packets_energy, + def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, packets_output_nu, packets_output_energy): self.packets_input_nu = packets_input_nu self.packets_input_mu = packets_input_mu - self.packets_energy = packets_energy + self.packets_input_energy = packets_input_energy self.packets_output_nu = packets_output_nu self.packets_output_energy = packets_output_energy diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 4ddc0db6a55..e4c1098ef15 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -1,5 +1,5 @@ import numpy as np -from enum import Enum +from enum import IntEnum from numba import int64, float64, boolean from numba import jitclass, njit, gdb @@ -11,12 +11,12 @@ SIGMA_THOMSON = const.sigma_T.to('cm^2').value INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON -class PacketStatus(Enum): +class PacketStatus(IntEnum): IN_PROCESS = 0 EMITTED = 1 REABSORBED = 2 -class InteractionType(Enum): +class InteractionType(IntEnum): BOUNDARY = 1 LINE = 2 ESCATTERING = 3 @@ -54,14 +54,14 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): return distance, delta_shell @njit(**njit_dict) -def calculate_distance_line(nu, comov_nu, nu_line, ct): +def calculate_distance_line(nu, comov_nu, nu_line, time_explosion): if nu_line == 0.0: return MISS_DISTANCE nu_diff = comov_nu - nu_line if np.abs(nu_diff / comov_nu) < 1e-7: nu_diff = 0.0 if nu_diff >= 0: - return (nu_diff / nu) * ct + return (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion else: raise Exception @@ -74,8 +74,8 @@ def calculate_tau_electron(electron_density, distance): return electron_density * SIGMA_THOMSON * distance @njit(**njit_dict) -def get_doppler_factor(r, mu, inverse_time_explosion): - beta = (r * inverse_time_explosion) / C_SPEED_OF_LIGHT +def get_doppler_factor(r, mu, time_explosion): + beta = (r / time_explosion) / C_SPEED_OF_LIGHT return 1.0 - mu * beta @njit(**njit_dict) @@ -95,10 +95,21 @@ def __init__(self, r, mu, nu, energy): self.next_line_id = -1 - def trace_packet(self, storage_model): + def trace_packet(self, numba_model, numba_plasma): + """ + + Parameters + ---------- + numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel + numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma + + Returns + ------- + + """ - r_inner = storage_model.r_inner[self.current_shell_id] - r_outer = storage_model.r_outer[self.current_shell_id] + r_inner = numba_model.r_inner[self.current_shell_id] + r_outer = numba_model.r_outer[self.current_shell_id] distance = 0.0 @@ -116,7 +127,7 @@ def trace_packet(self, storage_model): #e scattering initialization - cur_electron_density = storage_model.electron_densities[ + cur_electron_density = numba_plasma.electron_density[ self.current_shell_id] cur_inverse_electron_density = 1 / cur_electron_density distance_electron = calculate_distance_electron( @@ -125,15 +136,15 @@ def trace_packet(self, storage_model): #Calculating doppler factor doppler_factor = get_doppler_factor(self.r, self.mu, - storage_model.inverse_time_explosion) + numba_model.time_explosion) comov_nu = self.nu * doppler_factor distance_trace = 0.0 last_line = False while True: - if cur_line_id < storage_model.no_of_lines: # not last_line - nu_line = storage_model.line_list_nu[cur_line_id] - tau_trace_line = storage_model.line_lists_tau_sobolevs[ + if cur_line_id < len(numba_plasma.line_list_nu): # not last_line + nu_line = numba_plasma.line_list_nu[cur_line_id] + tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, self.current_shell_id] else: last_line = True @@ -141,8 +152,8 @@ def trace_packet(self, storage_model): break tau_trace_line_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, - storage_model.ct) + distance_trace = calculate_distance_line( + self.nu, comov_nu, nu_line, numba_model.time_explosion) tau_trace_electron = calculate_tau_electron(cur_electron_density, distance_trace) @@ -195,7 +206,8 @@ def move_packet(self, distance): self.mu = (self.mu * r + distance) / new_r self.r = new_r - def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells): + def move_packet_across_shell_boundary(self, distance, delta_shell, + no_of_shells): """ Move packet across shell boundary - realizing if we are still in the simulation or have moved out through the inner boundary or outer boundary and updating packet @@ -222,32 +234,35 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells) else: self.status = PacketStatus.REABSORBED - def initialize_line_id(self, storage_model): - inverse_line_list_nu = storage_model.line_list_nu[::-1] - doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + def initialize_line_id(self, numba_plasma, numba_model): + inverse_line_list_nu = numba_plasma.line_list_nu[::-1] + doppler_factor = get_doppler_factor(self.r, self.mu, + numba_model.time_explosion) comov_nu = self.nu * doppler_factor - next_line_id = storage_model.no_of_lines - np.searchsorted(inverse_line_list_nu, comov_nu) + next_line_id = (len(numba_plasma.line_list_nu) - + np.searchsorted(inverse_line_list_nu, comov_nu)) self.next_line_id = next_line_id - def scatter(self, storage_model): + def scatter(self, time_explosion): """ - General scattering for lines as well as thomson. 1) Move packet + General scattering for lines as well as thomson. 2) get the doppler factor at that position with the old angle - 3) convert the current energy and nu into the comoving frame with the old - mu - 4) Scatter and draw new mu - 5) Transform the comoving energy and nu back + 3) convert the current energy and nu into the comoving + frame with the old mu + 4) Scatter and draw new mu - update mu + 5) Transform the comoving energy and nu back using the new mu Parameters ---------- distance : [type] [description] """ - doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) comov_energy = self.energy * doppler_factor comov_nu = self.nu * doppler_factor self.mu = get_random_mu() - inverse_new_doppler_factor = 1. / get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) + inverse_new_doppler_factor = 1. / get_doppler_factor( + self.r, self.mu, time_explosion) self.energy = comov_energy * inverse_new_doppler_factor self.nu = comov_nu * inverse_new_doppler_factor diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 687135f3169..a47625363ab 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -5,17 +5,31 @@ InteractionType, PacketStatus) @njit -def single_packet_loop(storage_model, r_packet): - r_packet.initialize_line_id(storage_model) +def single_packet_loop(r_packet, numba_model, numba_plasma): + """ + + Parameters + ---------- + r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel + numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma + + Returns + ------- + + """ + r_packet.initialize_line_id(numba_plasma, numba_model) while r_packet.status == PacketStatus.IN_PROCESS: - distance, interaction_type, delta_shell = r_packet.trace_packet(storage_model) + distance, interaction_type, delta_shell = r_packet.trace_packet( + numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: - r_packet.move_packet_across_shell_boundary(distance, delta_shell, storage_model.no_of_shells) + r_packet.move_packet_across_shell_boundary(distance, delta_shell, + len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: r_packet.move_packet(distance) - r_packet.scatter(storage_model) + r_packet.scatter(numba_model.time_explosion) r_packet.next_line_id += 1 elif interaction_type == InteractionType.ESCATTERING: r_packet.move_packet(distance) - r_packet.scatter(storage_model) + r_packet.scatter(numba_model.time_explosion) diff --git a/tardis/montecarlo/montecarlo_numba/storage_model.py b/tardis/montecarlo/montecarlo_numba/storage_model.py deleted file mode 100644 index 2f141ae5f5c..00000000000 --- a/tardis/montecarlo/montecarlo_numba/storage_model.py +++ /dev/null @@ -1,86 +0,0 @@ -from numba import int64, float64, jitclass -import numpy as np -from astropy import constants as const - -C_SPEED_OF_LIGHT = const.c.to('cm/s').value - -storage_model_spec = [ - ('packet_nus', float64[:]), - ('packet_mus', float64[:]), - ('packet_energies', float64[:]), - ('output_nus', float64[:]), - ('output_energies', float64[:]), - ('no_of_packets', int64), - ('no_of_shells', int64), - ('r_inner', float64[:]), - ('r_outer', float64[:]), - ('v_inner', float64[:]), - ('time_explosion', float64), - ('inverse_time_explosion', float64), - ('electron_densities', float64[:]), - ('inverse_electron_densities', float64[:]), # Maybe remove the inverse things - ('line_list_nu', float64[:]), - ('line_lists_tau_sobolevs', float64[:, :]), - ('no_of_lines', int64), - ('line_interaction_id', int64), -# ('*js', float64), -# ('*nubars', float64), - ('sigma_thomson', float64), - ('inverse_sigma_thomson', float64), - ('ct', float64), -] - -@jitclass(storage_model_spec) -class StorageModel(object): - def __init__(self, packet_nus, packet_mus, packet_energies, - output_nus, output_energies, no_of_packets, no_of_shells, - r_inner, r_outer, v_inner, time_explosion, electron_densities, - line_list_nu, line_lists_tau_sobolevs, no_of_lines, line_interaction_id, sigma_thomson): - self.packet_nus = packet_nus - self.packet_mus = packet_mus - self.packet_energies = packet_energies - self.no_of_packets = len(self.packet_nus) - - self.output_nus = output_nus - self.output_energies = output_energies - self.r_inner = r_inner - self.r_outer = r_outer - self.v_inner = v_inner - self.no_of_shells = len(self.v_inner) - - self.time_explosion = time_explosion - self.inverse_time_explosion = 1 / time_explosion - - self.electron_densities = electron_densities - - self.inverse_electron_densities = 1 / electron_densities - - self.sigma_thomson = sigma_thomson - - self.inverse_sigma_thomson = 1 / self.sigma_thomson - self.no_of_lines = no_of_lines - self.line_list_nu = line_list_nu - self.line_lists_tau_sobolevs = line_lists_tau_sobolevs - self.ct = self.time_explosion * C_SPEED_OF_LIGHT - -def initialize_storage_model(model, plasma, runner): - storage_model_kwargs = {'packet_nus': runner.input_nu, - 'packet_mus': runner.input_mu, - 'packet_energies': runner.input_energy, - 'output_nus': runner._output_nu, - 'output_energies': runner._output_energy, - 'no_of_packets': runner.input_nu.size, - 'no_of_shells': model.no_of_shells, - 'r_inner': runner.r_inner_cgs, - 'r_outer': runner.r_outer_cgs, - 'v_inner': runner.v_inner_cgs, - 'time_explosion': model.time_explosion.to('s').value, - 'electron_densities': plasma.electron_densities.values, - 'line_list_nu': plasma.atomic_data.lines.nu.values, - 'no_of_lines': len(plasma.atomic_data.lines.nu.values), - 'line_interaction_id': runner.get_line_interaction_id( - runner.line_interaction_type), - 'sigma_thomson': runner.sigma_thomson.cgs.value} - storage_model_kwargs['line_lists_tau_sobolevs']= np.ascontiguousarray( - plasma.tau_sobolevs.values.copy(), dtype=np.float64) - return StorageModel(**storage_model_kwargs) \ No newline at end of file From a0f30b0bfdaa6035f2d57815c12c5fb9bbd1cd41 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 5 May 2019 01:43:44 -0400 Subject: [PATCH 030/116] add estimators --- tardis/montecarlo/montecarlo_numba/base.py | 12 +++++++----- .../montecarlo/montecarlo_numba/numba_interface.py | 11 +++++++++-- tardis/montecarlo/montecarlo_numba/rpacket.py | 14 ++++++++++++-- .../montecarlo_numba/single_packet_loop.py | 11 ++++++++--- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 9ad1766691b..a7100d2785a 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -2,7 +2,7 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( - PacketCollection, NumbaModel, NumbaPlasma) + PacketCollection, NumbaModel, NumbaPlasma, Estimators) from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( single_packet_loop) @@ -22,13 +22,15 @@ def montecarlo_radial1d(model, plasma, runner): plasma.tau_sobolevs.values.copy(), dtype=np.float64) ) + estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator) - #storage_model = initialize_storage_model(model, plasma, runner) - montecarlo_main_loop(packet_collection, numba_model, numba_plasma) + montecarlo_main_loop(packet_collection, numba_model, numba_plasma, + estimators) @njit(**njit_dict, nogil=True) -def montecarlo_main_loop(packet_collection, numba_model, numba_plasma): +def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, + estimators): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -47,7 +49,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma): packet_collection.packets_input_nu[i], packet_collection.packets_input_energy[i]) - single_packet_loop(r_packet, numba_model, numba_plasma) + single_packet_loop(r_packet, numba_model, numba_plasma, estimators) output_nus[i] = r_packet.nu if r_packet.status == PacketStatus.REABSORBED: diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index d9037bd6257..599c30ef959 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -61,8 +61,15 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, self.packets_output_energy = packets_output_energy +estimators_spec = [ + ('j_estimator', float64[:]), + ('nu_bar_estimator', float64[:]), +] - +@jitclass(estimators_spec) class Estimators(object): - pass + def __init__(self, j_estimator, nu_bar_estimator): + self.j_estimator = j_estimator + self.nu_bar_estimator = nu_bar_estimator + diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index e4c1098ef15..c6b4b57e0a7 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -190,7 +190,7 @@ def trace_packet(self, numba_model, numba_plasma): return distance_boundary, InteractionType.BOUNDARY, delta_shell - def move_packet(self, distance): + def move_packet(self, distance, time_explosion, numba_estimator): """Move packet a distance and recalculate the new angle mu Parameters @@ -199,6 +199,15 @@ def move_packet(self, distance): distance in cm """ + + doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) + comov_nu = self.nu * doppler_factor + comov_energy = self.energy * doppler_factor + numba_estimator.j_estimator[self.current_shell_id] += ( + comov_energy * distance) + numba_estimator.nu_bar_estimator[self.current_shell_id] += ( + comov_energy * distance * comov_nu) + r = self.r if (distance > 0.0): new_r = np.sqrt(r**2 + distance**2 + @@ -206,6 +215,8 @@ def move_packet(self, distance): self.mu = (self.mu * r + distance) / new_r self.r = new_r + + def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells): """ @@ -225,7 +236,6 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, number of shells in TARDIS simulation """ - self.move_packet(distance) if ((self.current_shell_id < no_of_shells - 1 and delta_shell == 1) or (self.current_shell_id > 0 and delta_shell == -1)): self.current_shell_id += delta_shell diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index a47625363ab..b2cb005fc41 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -5,7 +5,7 @@ InteractionType, PacketStatus) @njit -def single_packet_loop(r_packet, numba_model, numba_plasma): +def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): """ Parameters @@ -13,6 +13,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma): r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma + estimators: tardis.montecarlo.montecarlo_numba.numba_interface.Estimators Returns ------- @@ -24,12 +25,16 @@ def single_packet_loop(r_packet, numba_model, numba_plasma): distance, interaction_type, delta_shell = r_packet.trace_packet( numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: + r_packet.move_packet(distance, numba_model.time_explosion, + estimators) r_packet.move_packet_across_shell_boundary(distance, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: - r_packet.move_packet(distance) + r_packet.move_packet(distance, numba_model.time_explosion, + estimators) r_packet.scatter(numba_model.time_explosion) r_packet.next_line_id += 1 elif interaction_type == InteractionType.ESCATTERING: - r_packet.move_packet(distance) + r_packet.move_packet(distance, numba_model.time_explosion, + estimators) r_packet.scatter(numba_model.time_explosion) From 29708002a33db20d50517eb31ac669472afdde0c Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 6 May 2019 09:31:22 -0400 Subject: [PATCH 031/116] fix packet init --- .../montecarlo_numba/numba_interface.py | 7 ++ tardis/montecarlo/montecarlo_numba/rpacket.py | 4 +- .../montecarlo_numba/single_packet_loop.py | 8 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 83 ++++++++++++++++--- 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 599c30ef959..0876e870c16 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -72,4 +72,11 @@ def __init__(self, j_estimator, nu_bar_estimator): self.j_estimator = j_estimator self.nu_bar_estimator = nu_bar_estimator +monte_carlo_configuration_spec = [ + ('number_of_vpackets', int64) +] +@jitclass(monte_carlo_configuration_spec) +class MonteCarloConfiguration(object): + def __init__(self, number_of_vpackets): + self.number_of_vpackets = number_of_vpackets diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index c6b4b57e0a7..c150d410906 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -92,7 +92,7 @@ def __init__(self, r, mu, nu, energy): self.energy = energy self.current_shell_id = 0 self.status = PacketStatus.IN_PROCESS - self.next_line_id = -1 + def trace_packet(self, numba_model, numba_plasma): @@ -243,7 +243,7 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, self.status = PacketStatus.EMITTED else: self.status = PacketStatus.REABSORBED - + def initialize_line_id(self, numba_plasma, numba_model): inverse_line_list_nu = numba_plasma.line_list_nu[::-1] doppler_factor = get_doppler_factor(self.r, self.mu, diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index b2cb005fc41..e192c914ff6 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,8 +1,7 @@ from numba import njit -import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import ( - InteractionType, PacketStatus) + InteractionType, PacketStatus, get_doppler_factor) @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): @@ -19,6 +18,11 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): ------- """ + + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, + numba_model.time_explosion) + r_packet.nu /= doppler_factor + r_packet.energy /= doppler_factor r_packet.initialize_line_id(numba_plasma, numba_model) while r_packet.status == PacketStatus.IN_PROCESS: diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index f88cedf0dd9..c096779c08e 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,3 +1,10 @@ +from numba import float64, int64 +import numpy as np + +from tardis.montecarlo.montecarlo_numba.rpacket import ( + calculate_distance_boundary, calculate_distance_electron, + get_doppler_factor, calculate_distance_line, calculate_tau_electron) + vpacket_spec = [ ('r', float64), ('mu', float64), @@ -5,7 +12,6 @@ ('energy', float64), ('next_line_id', int64), ('current_shell_id', int64), - ('status', int64), ] @jitclass(vpacket_spec) @@ -15,11 +21,10 @@ def __init__(self, r, mu, nu, energy): self.mu = mu self.nu = nu self.energy = energy - self.current_shell_id = 0 - self.status = IN_PROCESS + self.current_shell_id = -1 self.next_line_id = -1 - def trace_packet(self, storage_model): + def trace_vpacket(self, storage_model): r_inner = storage_model.r_inner[self.current_shell_id] r_outer = storage_model.r_outer[self.current_shell_id] @@ -54,26 +59,31 @@ def trace_packet(self, storage_model): distance_trace = 0.0 last_line = False + tau_trace_electron = calculate_tau_electron(cur_electron_density, + distance_trace) + + tau_trace_combined = tau_trace_electron + while True: - if cur_line_id < storage_model.no_of_lines: # not last_line + if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] - tau_trace_line = storage_model.line_lists_tau_sobolevs[cur_line_id, - self.current_shell_id] + tau_trace_line = storage_model.line_lists_tau_sobolevs[ + cur_line_id, self.current_shell_id] else: last_line = True self.next_line_id = cur_line_id break - tau_trace_line_combined += tau_trace_line + tau_trace_combined += tau_trace_line distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, storage_model.ct) - tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_trace) tau_trace_combined = tau_trace_line_combined + tau_trace_electron if (distance_boundary <= distance_trace): - ## current_shell_id +=1 + distance_boundary, delta_shell = calculate_distance_boundary( + self.r, self.mu, r_inner, r_outer) + current_shell_id +=1 ## distance_boundary #unless shell interaction_type = InteractionType.BOUNDARY # BOUNDARY @@ -95,3 +105,54 @@ def trace_packet(self, storage_model): return distance_electron, InteractionType.ESCATTERING, delta_shell else: return distance_boundary, InteractionType.BOUNDARY, delta_shell + + def move_packet(self, distance, time_explosion, numba_estimator): + """Move packet a distance and recalculate the new angle mu + + Parameters + ---------- + distance : float + distance in cm + """ + + doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) + comov_nu = self.nu * doppler_factor + comov_energy = self.energy * doppler_factor + numba_estimator.j_estimator[self.current_shell_id] += ( + comov_energy * distance) + numba_estimator.nu_bar_estimator[self.current_shell_id] += ( + comov_energy * distance * comov_nu) + + r = self.r + if (distance > 0.0): + new_r = np.sqrt(r ** 2 + distance ** 2 + + 2.0 * r * distance * self.mu) + self.mu = (self.mu * r + distance) / new_r + self.r = new_r + + def move_packet_across_shell_boundary(self, distance, delta_shell, + no_of_shells): + """ + Move packet across shell boundary - realizing if we are still in the simulation or have + moved out through the inner boundary or outer boundary and updating packet + status. + + Parameters + ---------- + distance : float + distance to move to shell boundary + + delta_shell: int + is +1 if moving outward or -1 if moving inward + + no_of_shells: int + number of shells in TARDIS simulation + """ + + if ((self.current_shell_id < no_of_shells - 1 and delta_shell == 1) + or (self.current_shell_id > 0 and delta_shell == -1)): + self.current_shell_id += delta_shell + elif delta_shell == 1: + self.status = PacketStatus.EMITTED + else: + self.status = PacketStatus.REABSORBED From 1ef454f2bd97f14cba42c27fbdb1947300dd94c3 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 6 May 2019 11:16:44 -0400 Subject: [PATCH 032/116] some optimizations --- tardis/montecarlo/montecarlo_numba/rpacket.py | 347 +++++++++--------- .../montecarlo_numba/single_packet_loop.py | 21 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 19 +- 3 files changed, 191 insertions(+), 196 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index c150d410906..d63ad81a908 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -66,8 +66,8 @@ def calculate_distance_line(nu, comov_nu, nu_line, time_explosion): raise Exception @njit(**njit_dict) -def calculate_distance_electron(inverse_electron_density, tau_event): - return inverse_electron_density * INVERSE_SIGMA_THOMSON * tau_event +def calculate_distance_electron(electron_density, tau_event): + return tau_event / (electron_density * SIGMA_THOMSON) @njit(**njit_dict) def calculate_tau_electron(electron_density, distance): @@ -83,6 +83,8 @@ def get_random_mu(): return 2.0 * np.random.random() - 1.0 + + @jitclass(rpacket_spec) class RPacket(object): def __init__(self, r, mu, nu, energy): @@ -93,186 +95,183 @@ def __init__(self, r, mu, nu, energy): self.current_shell_id = 0 self.status = PacketStatus.IN_PROCESS - - - def trace_packet(self, numba_model, numba_plasma): - """ +@njit(**njit_dict) +def trace_packet(r_packet, numba_model, numba_plasma): + """ - Parameters - ---------- - numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel - numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma + Parameters + ---------- + numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel + numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma - Returns - ------- + Returns + ------- - """ - - r_inner = numba_model.r_inner[self.current_shell_id] - r_outer = numba_model.r_outer[self.current_shell_id] - - distance = 0.0 + """ - distance_boundary, delta_shell = calculate_distance_boundary( - self.r, self.mu, r_inner, r_outer) - - #defining start for line interaction - cur_line_id = self.next_line_id - nu_line = 0.0 - - #defining taus - tau_event = np.random.exponential() - tau_trace_line = 0.0 - tau_trace_line_combined = 0.0 - - #e scattering initialization + r_inner = numba_model.r_inner[r_packet.current_shell_id] + r_outer = numba_model.r_outer[r_packet.current_shell_id] + + distance = 0.0 + + distance_boundary, delta_shell = calculate_distance_boundary( + r_packet.r, r_packet.mu, r_inner, r_outer) + + # defining start for line interaction + cur_line_id = r_packet.next_line_id + nu_line = 0.0 - cur_electron_density = numba_plasma.electron_density[ - self.current_shell_id] - cur_inverse_electron_density = 1 / cur_electron_density - distance_electron = calculate_distance_electron( - cur_inverse_electron_density, tau_event) + # defining taus + tau_event = np.random.exponential() + tau_trace_line_combined = 0.0 + # e scattering initialization - #Calculating doppler factor - doppler_factor = get_doppler_factor(self.r, self.mu, + cur_electron_density = numba_plasma.electron_density[ + r_packet.current_shell_id] + distance_electron = calculate_distance_electron( + cur_electron_density, tau_event) + + # Calculating doppler factor + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, numba_model.time_explosion) - comov_nu = self.nu * doppler_factor - distance_trace = 0.0 - last_line = False - - while True: - if cur_line_id < len(numba_plasma.line_list_nu): # not last_line - nu_line = numba_plasma.line_list_nu[cur_line_id] - tau_trace_line = numba_plasma.tau_sobolev[ - cur_line_id, self.current_shell_id] - else: - last_line = True - self.next_line_id = cur_line_id - break - - tau_trace_line_combined += tau_trace_line - distance_trace = calculate_distance_line( - self.nu, comov_nu, nu_line, numba_model.time_explosion) - tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_trace) - - tau_trace_combined = tau_trace_line_combined + tau_trace_electron - - if ((distance_boundary <= distance_trace) and - (distance_boundary <= distance_electron)): - interaction_type = InteractionType.BOUNDARY # BOUNDARY - self.next_line_id = cur_line_id - distance = distance_boundary - break - - if ((distance_electron < distance_trace) and - (distance_electron < distance_boundary)): - interaction_type = InteractionType.ESCATTERING - distance = distance_electron - self.next_line_id = cur_line_id - break - - if tau_trace_combined > tau_event: - interaction_type = InteractionType.LINE #Line - self.next_line_id = cur_line_id - distance = distance_trace - break - - cur_line_id += 1 - if not last_line: - return distance, interaction_type, delta_shell + comov_nu = r_packet.nu * doppler_factor + last_line = False + + while True: + if cur_line_id < len(numba_plasma.line_list_nu): # not last_line + nu_line = numba_plasma.line_list_nu[cur_line_id] + tau_trace_line = numba_plasma.tau_sobolev[ + cur_line_id, r_packet.current_shell_id] else: - if distance_electron < distance_boundary: - return (distance_electron, InteractionType.ESCATTERING, - delta_shell) - else: - return distance_boundary, InteractionType.BOUNDARY, delta_shell - - - def move_packet(self, distance, time_explosion, numba_estimator): - """Move packet a distance and recalculate the new angle mu - - Parameters - ---------- - distance : float - distance in cm - """ - - - doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) - comov_nu = self.nu * doppler_factor - comov_energy = self.energy * doppler_factor - numba_estimator.j_estimator[self.current_shell_id] += ( - comov_energy * distance) - numba_estimator.nu_bar_estimator[self.current_shell_id] += ( - comov_energy * distance * comov_nu) - - r = self.r - if (distance > 0.0): - new_r = np.sqrt(r**2 + distance**2 + - 2.0 * r * distance * self.mu) - self.mu = (self.mu * r + distance) / new_r - self.r = new_r - - - - def move_packet_across_shell_boundary(self, distance, delta_shell, - no_of_shells): - """ - Move packet across shell boundary - realizing if we are still in the simulation or have - moved out through the inner boundary or outer boundary and updating packet - status. - - Parameters - ---------- - distance : float - distance to move to shell boundary - - delta_shell: int - is +1 if moving outward or -1 if moving inward - - no_of_shells: int - number of shells in TARDIS simulation - """ - - if ((self.current_shell_id < no_of_shells - 1 and delta_shell == 1) - or (self.current_shell_id > 0 and delta_shell == -1)): - self.current_shell_id += delta_shell - elif delta_shell == 1: - self.status = PacketStatus.EMITTED + last_line = True + r_packet.next_line_id = cur_line_id + break + + tau_trace_line_combined += tau_trace_line + distance_trace = calculate_distance_line( + r_packet.nu, comov_nu, nu_line, numba_model.time_explosion) + tau_trace_electron = calculate_tau_electron(cur_electron_density, + distance_trace) + + tau_trace_combined = tau_trace_line_combined + tau_trace_electron + + if ((distance_boundary <= distance_trace) and + (distance_boundary <= distance_electron)): + interaction_type = InteractionType.BOUNDARY # BOUNDARY + r_packet.next_line_id = cur_line_id + distance = distance_boundary + break + + if ((distance_electron < distance_trace) and + (distance_electron < distance_boundary)): + interaction_type = InteractionType.ESCATTERING + distance = distance_electron + r_packet.next_line_id = cur_line_id + break + + if tau_trace_combined > tau_event: + interaction_type = InteractionType.LINE # Line + r_packet.next_line_id = cur_line_id + distance = distance_trace + break + + cur_line_id += 1 + + if not last_line: + return distance, interaction_type, delta_shell + else: + if distance_electron < distance_boundary: + return (distance_electron, InteractionType.ESCATTERING, + delta_shell) else: - self.status = PacketStatus.REABSORBED - - def initialize_line_id(self, numba_plasma, numba_model): - inverse_line_list_nu = numba_plasma.line_list_nu[::-1] - doppler_factor = get_doppler_factor(self.r, self.mu, - numba_model.time_explosion) - comov_nu = self.nu * doppler_factor - next_line_id = (len(numba_plasma.line_list_nu) - - np.searchsorted(inverse_line_list_nu, comov_nu)) - self.next_line_id = next_line_id - - - def scatter(self, time_explosion): - """ - General scattering for lines as well as thomson. - 2) get the doppler factor at that position with the old angle - 3) convert the current energy and nu into the comoving - frame with the old mu - 4) Scatter and draw new mu - update mu - 5) Transform the comoving energy and nu back using the new mu + return distance_boundary, InteractionType.BOUNDARY, delta_shell + + +@njit(**njit_dict) +def move_packet(r_packet, distance, time_explosion, numba_estimator): + """Move packet a distance and recalculate the new angle mu + + Parameters + ---------- + distance : float + distance in cm + """ + + + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) + comov_nu = r_packet.nu * doppler_factor + comov_energy = r_packet.energy * doppler_factor + numba_estimator.j_estimator[r_packet.current_shell_id] += ( + comov_energy * distance) + numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * comov_nu) + + r = r_packet.r + if (distance > 0.0): + new_r = np.sqrt(r**2 + distance**2 + + 2.0 * r * distance * r_packet.mu) + r_packet.mu = (r_packet.mu * r + distance) / new_r + r_packet.r = new_r + +@njit(**njit_dict) +def move_packet_across_shell_boundary(r_packet, distance, delta_shell, + no_of_shells): + """ + Move packet across shell boundary - realizing if we are still in the simulation or have + moved out through the inner boundary or outer boundary and updating packet + status. + + Parameters + ---------- + distance : float + distance to move to shell boundary - Parameters - ---------- - distance : [type] - [description] - """ - doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) - comov_energy = self.energy * doppler_factor - comov_nu = self.nu * doppler_factor - self.mu = get_random_mu() - inverse_new_doppler_factor = 1. / get_doppler_factor( - self.r, self.mu, time_explosion) - self.energy = comov_energy * inverse_new_doppler_factor - self.nu = comov_nu * inverse_new_doppler_factor + delta_shell: int + is +1 if moving outward or -1 if moving inward + + no_of_shells: int + number of shells in TARDIS simulation + """ + + if ((r_packet.current_shell_id < no_of_shells - 1 and delta_shell == 1) + or (r_packet.current_shell_id > 0 and delta_shell == -1)): + r_packet.current_shell_id += delta_shell + elif delta_shell == 1: + r_packet.status = PacketStatus.EMITTED + else: + r_packet.status = PacketStatus.REABSORBED + +@njit(**njit_dict) +def initialize_line_id(r_packet, numba_plasma, numba_model): + inverse_line_list_nu = numba_plasma.line_list_nu[::-1] + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, + numba_model.time_explosion) + comov_nu = r_packet.nu * doppler_factor + next_line_id = (len(numba_plasma.line_list_nu) - + np.searchsorted(inverse_line_list_nu, comov_nu)) + r_packet.next_line_id = next_line_id + +@njit(**njit_dict) +def scatter(r_packet, time_explosion): + """ + General scattering for lines as well as thomson. + 2) get the doppler factor at that position with the old angle + 3) convert the current energy and nu into the comoving + frame with the old mu + 4) Scatter and draw new mu - update mu + 5) Transform the comoving energy and nu back using the new mu + + Parameters + ---------- + distance : [type] + [description] + """ + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) + comov_energy = r_packet.energy * doppler_factor + comov_nu = r_packet.nu * doppler_factor + r_packet.mu = get_random_mu() + inverse_new_doppler_factor = 1. / get_doppler_factor( + r_packet.r, r_packet.mu, time_explosion) + r_packet.energy = comov_energy * inverse_new_doppler_factor + r_packet.nu = comov_nu * inverse_new_doppler_factor diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index e192c914ff6..c6740816ae5 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,7 +1,8 @@ from numba import njit from tardis.montecarlo.montecarlo_numba.rpacket import ( - InteractionType, PacketStatus, get_doppler_factor) + InteractionType, PacketStatus, get_doppler_factor, trace_packet, + move_packet_across_shell_boundary, move_packet, scatter, initialize_line_id) @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): @@ -23,22 +24,22 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): numba_model.time_explosion) r_packet.nu /= doppler_factor r_packet.energy /= doppler_factor - r_packet.initialize_line_id(numba_plasma, numba_model) + initialize_line_id(r_packet, numba_plasma, numba_model) while r_packet.status == PacketStatus.IN_PROCESS: - distance, interaction_type, delta_shell = r_packet.trace_packet( - numba_model, numba_plasma) + distance, interaction_type, delta_shell = trace_packet( + r_packet, numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: - r_packet.move_packet(distance, numba_model.time_explosion, + move_packet(r_packet, distance, numba_model.time_explosion, estimators) - r_packet.move_packet_across_shell_boundary(distance, delta_shell, + move_packet_across_shell_boundary(r_packet, distance, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: - r_packet.move_packet(distance, numba_model.time_explosion, + move_packet(r_packet, distance, numba_model.time_explosion, estimators) - r_packet.scatter(numba_model.time_explosion) + scatter(r_packet, numba_model.time_explosion) r_packet.next_line_id += 1 elif interaction_type == InteractionType.ESCATTERING: - r_packet.move_packet(distance, numba_model.time_explosion, + move_packet(r_packet, distance, numba_model.time_explosion, estimators) - r_packet.scatter(numba_model.time_explosion) + scatter(r_packet, numba_model.time_explosion) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index c096779c08e..e0001d8e61f 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,6 +1,7 @@ from numba import float64, int64 import numpy as np +from from tardis.montecarlo.montecarlo_numba.rpacket import ( calculate_distance_boundary, calculate_distance_electron, get_doppler_factor, calculate_distance_line, calculate_tau_electron) @@ -64,7 +65,7 @@ def trace_vpacket(self, storage_model): tau_trace_combined = tau_trace_electron - while True: + while self.status == INPROCESS: if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] tau_trace_line = storage_model.line_lists_tau_sobolevs[ @@ -81,9 +82,12 @@ def trace_vpacket(self, storage_model): tau_trace_combined = tau_trace_line_combined + tau_trace_electron if (distance_boundary <= distance_trace): + current_shell_id += delta + self.move_packet_across_shell_boundary(distance_boundary) + distance_trace = distance_boundary, delta_shell = calculate_distance_boundary( self.r, self.mu, r_inner, r_outer) - current_shell_id +=1 + ## distance_boundary #unless shell interaction_type = InteractionType.BOUNDARY # BOUNDARY @@ -106,7 +110,7 @@ def trace_vpacket(self, storage_model): else: return distance_boundary, InteractionType.BOUNDARY, delta_shell - def move_packet(self, distance, time_explosion, numba_estimator): + def move_vpacket(self, distance): """Move packet a distance and recalculate the new angle mu Parameters @@ -114,15 +118,6 @@ def move_packet(self, distance, time_explosion, numba_estimator): distance : float distance in cm """ - - doppler_factor = get_doppler_factor(self.r, self.mu, time_explosion) - comov_nu = self.nu * doppler_factor - comov_energy = self.energy * doppler_factor - numba_estimator.j_estimator[self.current_shell_id] += ( - comov_energy * distance) - numba_estimator.nu_bar_estimator[self.current_shell_id] += ( - comov_energy * distance * comov_nu) - r = self.r if (distance > 0.0): new_r = np.sqrt(r ** 2 + distance ** 2 + From c4179a2bb675a08162b18f74b3d8cc02177dcec1 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 6 May 2019 13:11:27 -0400 Subject: [PATCH 033/116] add vpacket stuff --- tardis/montecarlo/montecarlo_numba/vpacket.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e0001d8e61f..3f93722b5d5 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,10 +1,11 @@ -from numba import float64, int64 +from numba import float64, int64, jitclass import numpy as np from from tardis.montecarlo.montecarlo_numba.rpacket import ( calculate_distance_boundary, calculate_distance_electron, - get_doppler_factor, calculate_distance_line, calculate_tau_electron) + get_doppler_factor, calculate_distance_line, calculate_tau_electron, + PacketStatus) vpacket_spec = [ ('r', float64), @@ -13,6 +14,7 @@ ('energy', float64), ('next_line_id', int64), ('current_shell_id', int64), + ('status', int64) ] @jitclass(vpacket_spec) @@ -24,6 +26,7 @@ def __init__(self, r, mu, nu, energy): self.energy = energy self.current_shell_id = -1 self.next_line_id = -1 + self.status = -1 def trace_vpacket(self, storage_model): From 7878bbd87863b9bfd04a85d366e04158f95d884c Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 16 May 2019 22:54:34 -0400 Subject: [PATCH 034/116] several changes to vpacket --- tardis/montecarlo/montecarlo_numba/vpacket.py | 186 +++++++++++++----- 1 file changed, 141 insertions(+), 45 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 3f93722b5d5..e53c919ae1e 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -3,9 +3,8 @@ from from tardis.montecarlo.montecarlo_numba.rpacket import ( - calculate_distance_boundary, calculate_distance_electron, - get_doppler_factor, calculate_distance_line, calculate_tau_electron, - PacketStatus) + calculate_distance_boundary, get_doppler_factor, calculate_distance_line, + calculate_tau_electron, PacketStatus) vpacket_spec = [ ('r', float64), @@ -51,67 +50,45 @@ def trace_vpacket(self, storage_model): cur_electron_density = storage_model.electron_densities[ self.current_shell_id] - cur_inverse_electron_density = 1 / cur_electron_density - distance_electron = calculate_distance_electron( - cur_inverse_electron_density, tau_event) - #Calculating doppler factor doppler_factor = get_doppler_factor(self.r, self.mu, storage_model.inverse_time_explosion) - comov_nu = self.nu * doppler_factor - distance_trace = 0.0 last_line = False tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_trace) + distance_boundary) tau_trace_combined = tau_trace_electron - while self.status == INPROCESS: + while True: if cur_line_id < storage_model.no_of_lines: # not last_line nu_line = storage_model.line_list_nu[cur_line_id] tau_trace_line = storage_model.line_lists_tau_sobolevs[ cur_line_id, self.current_shell_id] + tau_trace_combined += tau_trace_line + distance_trace = calculate_distance_line(self.nu, comov_nu, + nu_line, + storage_model.ct) + cur_line_id += 1 else: - last_line = True - self.next_line_id = cur_line_id - break + distance_trace = distance_boundary + 1.0 - tau_trace_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, nu_line, - storage_model.ct) - tau_trace_combined = tau_trace_line_combined + tau_trace_electron - if (distance_boundary <= distance_trace): - current_shell_id += delta + + if distance_boundary <= distance_trace: + self.current_shell_id += delta_shell self.move_packet_across_shell_boundary(distance_boundary) - distance_trace = - distance_boundary, delta_shell = calculate_distance_boundary( - self.r, self.mu, r_inner, r_outer) - - ## distance_boundary - #unless shell - interaction_type = InteractionType.BOUNDARY # BOUNDARY - self.next_line_id = cur_line_id - distance = distance_boundary - break - - if (distance_electron < distance_trace) and (distance_electron < distance_boundary): - interaction_type = InteractionType.ESCATTERING - distance = distance_electron - self.next_line_id = cur_line_id - break - - cur_line_id += 1 - if not last_line: - return distance, interaction_type, delta_shell - else: - if distance_electron < distance_boundary: - return distance_electron, InteractionType.ESCATTERING, delta_shell - else: - return distance_boundary, InteractionType.BOUNDARY, delta_shell + if not self.status == PacketStatus.IN_PROCESS: + break + + comov_nu = self.nu * doppler_factor + tau_electron = calculate_tau_electron(cur_electron_density, + distance_boundary) + tau_trace_combined += tau_electron + + return tau_trace_combined def move_vpacket(self, distance): """Move packet a distance and recalculate the new angle mu @@ -154,3 +131,122 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, self.status = PacketStatus.EMITTED else: self.status = PacketStatus.REABSORBED + + +def create_single_vpacket(): + +def create_volley_vpacket(): + + if cur_r_packet.r > r_innerst: + mu_min = -np.sqrt(1 - r_innerst / cur_r_packet.r) ** 2 + else: + mu_min = 0.0 + + double + mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag(packet); + rpacket_set_mu( & virt_packet, mu_min + (i + rk_double(mt_state)) * mu_bin); + { + if ((rpacket_get_nu (packet) > storage->spectrum_virt_start_nu) && (rpacket_get_nu(packet) < storage->spectrum_virt_end_nu)) + { + for (int64_t i = 0; i < rpacket_get_virtual_packet_flag (packet); i++) + { + double weight; + rpacket_t virt_packet = *packet; + double mu_min; + if (rpacket_get_r(&virt_packet) > storage->r_inner[0]) + { + mu_min = + -1.0 * sqrt (1.0 - + (storage->r_inner[0] / rpacket_get_r(&virt_packet)) * + (storage->r_inner[0] / rpacket_get_r(&virt_packet))); + + if (storage->full_relativity) + { + // Need to transform the angular size of the photosphere into the CMF + mu_min = angle_aberration_LF_to_CMF (&virt_packet, storage, mu_min); + } + } + else + { + mu_min = 0.0; + } + double mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag (packet); + rpacket_set_mu(&virt_packet,mu_min + (i + rk_double (mt_state)) * mu_bin); + switch (virtual_mode) + { + case -2: + weight = 1.0 / rpacket_get_virtual_packet_flag (packet); + break; + case -1: + weight = + 2.0 * rpacket_get_mu(&virt_packet) / + rpacket_get_virtual_packet_flag (packet); + break; + case 1: + weight = + (1.0 - + mu_min) / 2.0 / rpacket_get_virtual_packet_flag (packet); + break; + default: + fprintf (stderr, "Something has gone horribly wrong!\n"); + // FIXME MR: we need to somehow signal an error here + // I'm adding an exit() here to inform the compiler about the impossible path + exit(1); + } + angle_aberration_CMF_to_LF (&virt_packet, storage); + double doppler_factor_ratio = + rpacket_doppler_factor (packet, storage) / + rpacket_doppler_factor (&virt_packet, storage); + rpacket_set_energy(&virt_packet, + rpacket_get_energy (packet) * doppler_factor_ratio); + rpacket_set_nu(&virt_packet,rpacket_get_nu (packet) * doppler_factor_ratio); + reabsorbed = montecarlo_one_packet_loop (storage, &virt_packet, 1, mt_state); +#ifdef WITH_VPACKET_LOGGING +#ifdef WITHOPENMP +#pragma omp critical + { +#endif // WITHOPENMP + if (storage->virt_packet_count >= storage->virt_array_size) + { + storage->virt_array_size *= 2; + storage->virt_packet_nus = safe_realloc(storage->virt_packet_nus, sizeof(double) * storage->virt_array_size); + storage->virt_packet_energies = safe_realloc(storage->virt_packet_energies, sizeof(double) * storage->virt_array_size); + storage->virt_packet_last_interaction_in_nu = safe_realloc(storage->virt_packet_last_interaction_in_nu, sizeof(double) * storage->virt_array_size); + storage->virt_packet_last_interaction_type = safe_realloc(storage->virt_packet_last_interaction_type, sizeof(int64_t) * storage->virt_array_size); + storage->virt_packet_last_line_interaction_in_id = safe_realloc(storage->virt_packet_last_line_interaction_in_id, sizeof(int64_t) * storage->virt_array_size); + storage->virt_packet_last_line_interaction_out_id = safe_realloc(storage->virt_packet_last_line_interaction_out_id, sizeof(int64_t) * storage->virt_array_size); + } + storage->virt_packet_nus[storage->virt_packet_count] = rpacket_get_nu(&virt_packet); + storage->virt_packet_energies[storage->virt_packet_count] = rpacket_get_energy(&virt_packet) * weight; + storage->virt_packet_last_interaction_in_nu[storage->virt_packet_count] = storage->last_interaction_in_nu[rpacket_get_id (packet)]; + storage->virt_packet_last_interaction_type[storage->virt_packet_count] = storage->last_interaction_type[rpacket_get_id (packet)]; + storage->virt_packet_last_line_interaction_in_id[storage->virt_packet_count] = storage->last_line_interaction_in_id[rpacket_get_id (packet)]; + storage->virt_packet_last_line_interaction_out_id[storage->virt_packet_count] = storage->last_line_interaction_out_id[rpacket_get_id (packet)]; + storage->virt_packet_count += 1; +#ifdef WITHOPENMP + } +#endif // WITHOPENMP +#endif // WITH_VPACKET_LOGGING + if ((rpacket_get_nu(&virt_packet) < storage->spectrum_end_nu) && + (rpacket_get_nu(&virt_packet) > storage->spectrum_start_nu)) + { +#ifdef WITHOPENMP +#pragma omp critical + { +#endif // WITHOPENMP + int64_t virt_id_nu = + floor ((rpacket_get_nu(&virt_packet) - + storage->spectrum_start_nu) / + storage->spectrum_delta_nu); + storage->spectrum_virt_nu[virt_id_nu] += + rpacket_get_energy(&virt_packet) * weight; +#ifdef WITHOPENMP + } +#endif // WITHOPENMP + } + } + } + else + { + return 1; + } \ No newline at end of file From 3d711a317fb1965a4c7fc5455c33ec52bf636f75 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Tue, 4 Jun 2019 18:05:49 +0200 Subject: [PATCH 035/116] Clean up rpacket fix vpacket Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/rpacket.py | 81 +++++----- .../montecarlo_numba/single_packet_loop.py | 10 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 153 +++++++++--------- 3 files changed, 114 insertions(+), 130 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index d63ad81a908..93917e2dfde 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -6,6 +6,9 @@ from tardis.montecarlo.montecarlo_numba import njit_dict from tardis import constants as const +class MonteCarloException(ValueError): + pass + C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 SIGMA_THOMSON = const.sigma_T.to('cm^2').value @@ -63,7 +66,7 @@ def calculate_distance_line(nu, comov_nu, nu_line, time_explosion): if nu_diff >= 0: return (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion else: - raise Exception + raise MonteCarloException('nu difference is less than 0.0') @njit(**njit_dict) def calculate_distance_electron(electron_density, tau_event): @@ -95,6 +98,17 @@ def __init__(self, r, mu, nu, energy): self.current_shell_id = 0 self.status = PacketStatus.IN_PROCESS + def initialize_line_id(self, numba_plasma, numba_model): + inverse_line_list_nu = numba_plasma.line_list_nu[::-1] + doppler_factor = get_doppler_factor(self.r, self.mu, + numba_model.time_explosion) + comov_nu = self.nu * doppler_factor + next_line_id = (len(numba_plasma.line_list_nu) - + np.searchsorted(inverse_line_list_nu, comov_nu)) + self.next_line_id = next_line_id + + + @njit(**njit_dict) def trace_packet(r_packet, numba_model, numba_plasma): """ @@ -112,14 +126,11 @@ def trace_packet(r_packet, numba_model, numba_plasma): r_inner = numba_model.r_inner[r_packet.current_shell_id] r_outer = numba_model.r_outer[r_packet.current_shell_id] - distance = 0.0 - distance_boundary, delta_shell = calculate_distance_boundary( r_packet.r, r_packet.mu, r_inner, r_outer) # defining start for line interaction - cur_line_id = r_packet.next_line_id - nu_line = 0.0 + start_line_id = r_packet.next_line_id # defining taus tau_event = np.random.exponential() @@ -137,23 +148,17 @@ def trace_packet(r_packet, numba_model, numba_plasma): numba_model.time_explosion) comov_nu = r_packet.nu * doppler_factor last_line = False - - while True: - if cur_line_id < len(numba_plasma.line_list_nu): # not last_line - nu_line = numba_plasma.line_list_nu[cur_line_id] - tau_trace_line = numba_plasma.tau_sobolev[ - cur_line_id, r_packet.current_shell_id] - else: - last_line = True - r_packet.next_line_id = cur_line_id - break - + cur_line_id = start_line_id + for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): + nu_line = numba_plasma.line_list_nu[cur_line_id] + tau_trace_line = numba_plasma.tau_sobolev[ + cur_line_id, r_packet.current_shell_id] + tau_trace_line_combined += tau_trace_line distance_trace = calculate_distance_line( r_packet.nu, comov_nu, nu_line, numba_model.time_explosion) tau_trace_electron = calculate_tau_electron(cur_electron_density, distance_trace) - tau_trace_combined = tau_trace_line_combined + tau_trace_electron if ((distance_boundary <= distance_trace) and @@ -175,21 +180,19 @@ def trace_packet(r_packet, numba_model, numba_plasma): r_packet.next_line_id = cur_line_id distance = distance_trace break - - cur_line_id += 1 - - if not last_line: - return distance, interaction_type, delta_shell - else: + else: # Executed when no break occurs in the for loop if distance_electron < distance_boundary: - return (distance_electron, InteractionType.ESCATTERING, - delta_shell) + interaction_type = InteractionType.ESCATTERING else: - return distance_boundary, InteractionType.BOUNDARY, delta_shell + interaction_type = InteractionType.BOUNDARY + + r_packet.next_line_id = cur_line_id + + return distance, interaction_type, delta_shell @njit(**njit_dict) -def move_packet(r_packet, distance, time_explosion, numba_estimator): +def move_rpacket(r_packet, distance, time_explosion, numba_estimator): """Move packet a distance and recalculate the new angle mu Parameters @@ -215,7 +218,7 @@ def move_packet(r_packet, distance, time_explosion, numba_estimator): r_packet.r = new_r @njit(**njit_dict) -def move_packet_across_shell_boundary(r_packet, distance, delta_shell, +def move_packet_across_shell_boundary(packet, distance, delta_shell, no_of_shells): """ Move packet across shell boundary - realizing if we are still in the simulation or have @@ -233,24 +236,14 @@ def move_packet_across_shell_boundary(r_packet, distance, delta_shell, no_of_shells: int number of shells in TARDIS simulation """ + next_shell_id = packet.current_shell_id + delta_shell - if ((r_packet.current_shell_id < no_of_shells - 1 and delta_shell == 1) - or (r_packet.current_shell_id > 0 and delta_shell == -1)): - r_packet.current_shell_id += delta_shell - elif delta_shell == 1: - r_packet.status = PacketStatus.EMITTED + if next_shell_id >= no_of_shells: + packet.status = PacketStatus.EMITTED + elif next_shell_id < 0: + packet.status = PacketStatus.REABSORBED else: - r_packet.status = PacketStatus.REABSORBED - -@njit(**njit_dict) -def initialize_line_id(r_packet, numba_plasma, numba_model): - inverse_line_list_nu = numba_plasma.line_list_nu[::-1] - doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, - numba_model.time_explosion) - comov_nu = r_packet.nu * doppler_factor - next_line_id = (len(numba_plasma.line_list_nu) - - np.searchsorted(inverse_line_list_nu, comov_nu)) - r_packet.next_line_id = next_line_id + packet.current_shell_id = next_shell_id @njit(**njit_dict) def scatter(r_packet, time_explosion): diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index c6740816ae5..96ea6b1a9b1 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -2,7 +2,7 @@ from tardis.montecarlo.montecarlo_numba.rpacket import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, - move_packet_across_shell_boundary, move_packet, scatter, initialize_line_id) + move_packet_across_shell_boundary, move_rpacket, scatter) @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): @@ -24,22 +24,22 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): numba_model.time_explosion) r_packet.nu /= doppler_factor r_packet.energy /= doppler_factor - initialize_line_id(r_packet, numba_plasma, numba_model) + r_packet.initialize_line_id(numba_plasma, numba_model) while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( r_packet, numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: - move_packet(r_packet, distance, numba_model.time_explosion, + move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) move_packet_across_shell_boundary(r_packet, distance, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: - move_packet(r_packet, distance, numba_model.time_explosion, + move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) scatter(r_packet, numba_model.time_explosion) r_packet.next_line_id += 1 elif interaction_type == InteractionType.ESCATTERING: - move_packet(r_packet, distance, numba_model.time_explosion, + move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) scatter(r_packet, numba_model.time_explosion) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e53c919ae1e..d9c1ed5760a 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,10 +1,9 @@ from numba import float64, int64, jitclass import numpy as np -from from tardis.montecarlo.montecarlo_numba.rpacket import ( calculate_distance_boundary, get_doppler_factor, calculate_distance_line, - calculate_tau_electron, PacketStatus) + calculate_tau_electron, PacketStatus, move_packet_across_shell_boundary) vpacket_spec = [ ('r', float64), @@ -27,69 +26,6 @@ def __init__(self, r, mu, nu, energy): self.next_line_id = -1 self.status = -1 - def trace_vpacket(self, storage_model): - - r_inner = storage_model.r_inner[self.current_shell_id] - r_outer = storage_model.r_outer[self.current_shell_id] - - distance = 0.0 - - distance_boundary, delta_shell = calculate_distance_boundary( - self.r, self.mu, r_inner, r_outer) - - #defining start for line interaction - cur_line_id = self.next_line_id - nu_line = 0.0 - - #defining taus - tau_event = np.random.exponential() - tau_trace_line = 0.0 - tau_trace_line_combined = 0.0 - - #e scattering initialization - - cur_electron_density = storage_model.electron_densities[ - self.current_shell_id] - - #Calculating doppler factor - doppler_factor = get_doppler_factor(self.r, self.mu, - storage_model.inverse_time_explosion) - last_line = False - - tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_boundary) - - tau_trace_combined = tau_trace_electron - - while True: - if cur_line_id < storage_model.no_of_lines: # not last_line - nu_line = storage_model.line_list_nu[cur_line_id] - tau_trace_line = storage_model.line_lists_tau_sobolevs[ - cur_line_id, self.current_shell_id] - tau_trace_combined += tau_trace_line - distance_trace = calculate_distance_line(self.nu, comov_nu, - nu_line, - storage_model.ct) - cur_line_id += 1 - else: - distance_trace = distance_boundary + 1.0 - - - - - if distance_boundary <= distance_trace: - self.current_shell_id += delta_shell - self.move_packet_across_shell_boundary(distance_boundary) - if not self.status == PacketStatus.IN_PROCESS: - break - - comov_nu = self.nu * doppler_factor - tau_electron = calculate_tau_electron(cur_electron_density, - distance_boundary) - tau_trace_combined += tau_electron - - return tau_trace_combined - def move_vpacket(self, distance): """Move packet a distance and recalculate the new angle mu @@ -124,23 +60,78 @@ def move_packet_across_shell_boundary(self, distance, delta_shell, number of shells in TARDIS simulation """ - if ((self.current_shell_id < no_of_shells - 1 and delta_shell == 1) - or (self.current_shell_id > 0 and delta_shell == -1)): - self.current_shell_id += delta_shell - elif delta_shell == 1: - self.status = PacketStatus.EMITTED - else: - self.status = PacketStatus.REABSORBED - + next_shell_id = r_packet.current_shell_id + delta_shell -def create_single_vpacket(): - -def create_volley_vpacket(): - - if cur_r_packet.r > r_innerst: - mu_min = -np.sqrt(1 - r_innerst / cur_r_packet.r) ** 2 - else: - mu_min = 0.0 + if next_shell_id >= no_of_shells: + r_packet.status = PacketStatus.EMITTED + elif next_shell_id < 0: + r_packet.status = PacketStatus.REABSORBED + else: + rpacket.current_shell_id = next_shell_id + + +@njit(**njit_dict) +def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): + + r_inner = numba_model.r_inner[v_packet.current_shell_id] + r_outer = numba_model.r_outer[v_packet.current_shell_id] + + distance_boundary, delta_shell = calculate_distance_boundary( + v_packet.r, v_packet.mu, r_inner, r_outer) + + # defining start for line interaction + start_line_id = v_packet.next_line_id + + # defining taus + + + # e scattering initialization + cur_electron_density = numba_plasma.electron_density[ + v_packet.current_shell_id] + tau_electron = calculate_tau_electron(cur_electron_density, + distance_boundary) + tau_trace_combined = tau_electron + + # Calculating doppler factor + doppler_factor = get_doppler_factor(v_packet.r, v_packet.mu, + numba_model.time_explosion) + comov_nu = v_packet.nu * doppler_factor + cur_line_id = start_line_id + + for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): + nu_line = numba_plasma.line_list_nu[cur_line_id] + tau_trace_line = numba_plasma.tau_sobolev[ + cur_line_id, v_packet.current_shell_id] + + tau_trace_combined += tau_trace_line + distance_trace_line = calculate_distance_line( + v_packet.nu, comov_nu, nu_line, numba_model.time_explosion) + + if (distance_boundary <= distance_trace_line): + v_packet.next_line_id = cur_line_id + break + + v_packet.next_line_id = cur_line_id + return tau_trace_combined, distance_boundary, delta_shell + + +def trace_vpacket(v_packet, numba_model, numba_plasma): + tau_trace_combined = 0.0 + while True: + tau_trace_combined_shell, distance_boundary, delta_shell = trace_vpacket_within_shell( + v_packet, numba_model, numba_plasma + ) + tau_trace_combined += tau_trace_combined_shell + move_packet_across_shell_boundary(v_packet, delta_shell, + no_of_shells) + if v_packet.status == PacketStatus.EMITTED: + break + + # Moving the v_packet + new_r = np.sqrt(v_packet.r**2 + distance_boundary**2 + + 2.0 * v_packet.r * distance_boundary * v_packet.mu) + v_packet.mu = (v_packet.mu * v_packet.r + distance_boundary) / new_r + v_packet.r = new_r double mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag(packet); From 77a8a3fb31501e083264d5fb6d7c4b4df562fe60 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 7 Jun 2019 00:30:34 +0200 Subject: [PATCH 036/116] Add vpacket development Co-authored-by: Christian Vogl --- tardis/montecarlo/base.py | 3 +- tardis/montecarlo/montecarlo_numba/base.py | 30 ++- .../montecarlo_numba/numba_interface.py | 19 ++ tardis/montecarlo/montecarlo_numba/rpacket.py | 13 +- .../montecarlo_numba/single_packet_loop.py | 15 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 209 +++++++----------- 6 files changed, 141 insertions(+), 148 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index fe50b53257b..1b5c96f317a 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -221,7 +221,8 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets) - montecarlo_radial1d(model, plasma, self) + + montecarlo_radial1d(model, plasma, self, no_of_virtual_packets) #montecarlo.montecarlo_radial1d( # model, plasma, self, # virtual_packet_flag=no_of_virtual_packets, diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a7100d2785a..9ad233105f2 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,19 +1,20 @@ -from numba import prange, njit, config +from numba import prange, njit, config, int64 import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( - PacketCollection, NumbaModel, NumbaPlasma, Estimators) + PacketCollection, VPacketCollection, NumbaModel, NumbaPlasma, Estimators, MonteCarloConfiguration) from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( single_packet_loop) from tardis.montecarlo.montecarlo_numba import njit_dict -def montecarlo_radial1d(model, plasma, runner): +def montecarlo_radial1d(model, plasma, runner, no_of_virtual_packets): packet_collection = PacketCollection( runner.input_nu, runner.input_mu, runner.input_energy, runner._output_nu, runner._output_energy ) + montecarlo_configuration = MonteCarloConfiguration(no_of_virtual_packets) numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, model.time_explosion.to('s').value) numba_plasma = NumbaPlasma(plasma.electron_densities.values, @@ -24,13 +25,14 @@ def montecarlo_radial1d(model, plasma, runner): ) estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator) - montecarlo_main_loop(packet_collection, numba_model, numba_plasma, - estimators) + v_packets_energy_hist = montecarlo_main_loop(packet_collection, numba_model, numba_plasma, estimators, runner.spectrum_frequency.value, montecarlo_configuration) + + runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, - estimators): + estimators, spectrum_frequency, montecarlo_configuration): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -43,20 +45,34 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, output_nus = np.empty_like(packet_collection.packets_output_nu) output_energies = np.empty_like(packet_collection.packets_output_nu) + v_packets_energy_hist = np.zeros_like(spectrum_frequency) + delta_nu = spectrum_frequency[1] - spectrum_frequency[0] + for i in prange(len(output_nus)): r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], packet_collection.packets_input_nu[i], packet_collection.packets_input_energy[i]) + + vpacket_collection = VPacketCollection(spectrum_frequency, montecarlo_configuration.number_of_vpackets, 20000) + single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection) - single_packet_loop(r_packet, numba_model, numba_plasma, estimators) output_nus[i] = r_packet.nu if r_packet.status == PacketStatus.REABSORBED: output_energies[i] = -r_packet.energy elif r_packet.status == PacketStatus.EMITTED: output_energies[i] = r_packet.energy + + vpackets_nu = vpacket_collection.nus[:vpacket_collection.idx] + vpackets_energy = vpacket_collection.energies[:vpacket_collection.idx] + + v_packets_idx = np.floor((vpackets_nu - spectrum_frequency[0]) / delta_nu).astype(np.int64) + for i, idx in enumerate(v_packets_idx): + v_packets_energy_hist[idx] += vpackets_energy[i] packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] + + return v_packets_energy_hist diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 0876e870c16..fce65992126 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -1,4 +1,6 @@ from numba import float64, int64, jitclass +import numpy as np + from tardis import constants as const @@ -60,7 +62,24 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, self.packets_output_nu = packets_output_nu self.packets_output_energy = packets_output_energy +vpacket_collection_spec = [ + ('spectrum_frequency', float64[:]), + ('nus', float64[:]), + ('energies', float64[:]), + ('idx', int64), + ('number_of_vpackets', int64) +] +@jitclass(vpacket_collection_spec) +class VPacketCollection(object): + def __init__(self, spectrum_frequency, number_of_vpackets, initial_vpacket_bins): + self.spectrum_frequency = spectrum_frequency + self.nus = np.empty(initial_vpacket_bins, dtype=np.float64) + self.energies = np.empty(initial_vpacket_bins, dtype=np.float64) + self.number_of_vpackets = number_of_vpackets + self.idx = 0 + + estimators_spec = [ ('j_estimator', float64[:]), ('nu_bar_estimator', float64[:]), diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 93917e2dfde..ec24d9a5e8b 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -9,6 +9,7 @@ class MonteCarloException(ValueError): pass +CLOSE_LINE_THRESHOLD = 1e-7 C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 SIGMA_THOMSON = const.sigma_T.to('cm^2').value @@ -61,11 +62,12 @@ def calculate_distance_line(nu, comov_nu, nu_line, time_explosion): if nu_line == 0.0: return MISS_DISTANCE nu_diff = comov_nu - nu_line - if np.abs(nu_diff / comov_nu) < 1e-7: + if np.abs(nu_diff / comov_nu) < CLOSE_LINE_THRESHOLD: nu_diff = 0.0 if nu_diff >= 0: return (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion else: + print('nu difference is less than 0.0', nu_diff, comov_nu, nu, nu_line, time_explosion) raise MonteCarloException('nu difference is less than 0.0') @njit(**njit_dict) @@ -153,7 +155,7 @@ def trace_packet(r_packet, numba_model, numba_plasma): nu_line = numba_plasma.line_list_nu[cur_line_id] tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, r_packet.current_shell_id] - + tau_trace_line_combined += tau_trace_line distance_trace = calculate_distance_line( r_packet.nu, comov_nu, nu_line, numba_model.time_explosion) @@ -181,9 +183,14 @@ def trace_packet(r_packet, numba_model, numba_plasma): distance = distance_trace break else: # Executed when no break occurs in the for loop + if cur_line_id == (len(numba_plasma.line_list_nu) - 1): + # Treatment for last line + cur_line_id += 1 if distance_electron < distance_boundary: + distance = distance_electron interaction_type = InteractionType.ESCATTERING else: + distance = distance_boundary interaction_type = InteractionType.BOUNDARY r_packet.next_line_id = cur_line_id @@ -218,7 +225,7 @@ def move_rpacket(r_packet, distance, time_explosion, numba_estimator): r_packet.r = new_r @njit(**njit_dict) -def move_packet_across_shell_boundary(packet, distance, delta_shell, +def move_packet_across_shell_boundary(packet, delta_shell, no_of_shells): """ Move packet across shell boundary - realizing if we are still in the simulation or have diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 96ea6b1a9b1..ff4aec1095a 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,11 +1,14 @@ from numba import njit +import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, move_packet_across_shell_boundary, move_rpacket, scatter) +from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley +from tardis.montecarlo.montecarlo_numba.numba_interface import VPacketCollection @njit -def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): +def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection): """ Parameters @@ -26,20 +29,28 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators): r_packet.energy /= doppler_factor r_packet.initialize_line_id(numba_plasma, numba_model) + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( r_packet, numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) - move_packet_across_shell_boundary(r_packet, distance, delta_shell, + move_packet_across_shell_boundary(r_packet, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) scatter(r_packet, numba_model.time_explosion) r_packet.next_line_id += 1 + + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + elif interaction_type == InteractionType.ESCATTERING: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) scatter(r_packet, numba_model.time_explosion) + + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index d9c1ed5760a..77013174ad1 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,4 +1,8 @@ -from numba import float64, int64, jitclass +from numba import float64, int64 +from numba import jitclass, njit, gdb + +from tardis.montecarlo.montecarlo_numba import njit_dict + import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import ( @@ -17,29 +21,14 @@ @jitclass(vpacket_spec) class VPacket(object): - def __init__(self, r, mu, nu, energy): + def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id): self.r = r self.mu = mu self.nu = nu self.energy = energy - self.current_shell_id = -1 - self.next_line_id = -1 - self.status = -1 - - def move_vpacket(self, distance): - """Move packet a distance and recalculate the new angle mu - - Parameters - ---------- - distance : float - distance in cm - """ - r = self.r - if (distance > 0.0): - new_r = np.sqrt(r ** 2 + distance ** 2 + - 2.0 * r * distance * self.mu) - self.mu = (self.mu * r + distance) / new_r - self.r = new_r + self.current_shell_id = current_shell_id + self.next_line_id = next_line_id + self.status = PacketStatus.IN_PROCESS def move_packet_across_shell_boundary(self, distance, delta_shell, no_of_shells): @@ -78,7 +67,6 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): distance_boundary, delta_shell = calculate_distance_boundary( v_packet.r, v_packet.mu, r_inner, r_outer) - # defining start for line interaction start_line_id = v_packet.next_line_id @@ -99,22 +87,31 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): cur_line_id = start_line_id for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): + if tau_trace_combined > 10: ### FIXME ????? + break + nu_line = numba_plasma.line_list_nu[cur_line_id] tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, v_packet.current_shell_id] - tau_trace_combined += tau_trace_line distance_trace_line = calculate_distance_line( v_packet.nu, comov_nu, nu_line, numba_model.time_explosion) if (distance_boundary <= distance_trace_line): - v_packet.next_line_id = cur_line_id break + + tau_trace_combined += tau_trace_line + + + else: + if cur_line_id == (len(numba_plasma.line_list_nu) - 1): + cur_line_id += 1 v_packet.next_line_id = cur_line_id + return tau_trace_combined, distance_boundary, delta_shell - +@njit(**njit_dict) def trace_vpacket(v_packet, numba_model, numba_plasma): tau_trace_combined = 0.0 while True: @@ -122,122 +119,64 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): v_packet, numba_model, numba_plasma ) tau_trace_combined += tau_trace_combined_shell - move_packet_across_shell_boundary(v_packet, delta_shell, - no_of_shells) + if tau_trace_combined > 10: + break + move_packet_across_shell_boundary(v_packet, delta_shell, + len(numba_model.r_inner)) if v_packet.status == PacketStatus.EMITTED: break - + # Moving the v_packet new_r = np.sqrt(v_packet.r**2 + distance_boundary**2 + 2.0 * v_packet.r * distance_boundary * v_packet.mu) v_packet.mu = (v_packet.mu * v_packet.r + distance_boundary) / new_r v_packet.r = new_r + return tau_trace_combined - double - mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag(packet); - rpacket_set_mu( & virt_packet, mu_min + (i + rk_double(mt_state)) * mu_bin); - { - if ((rpacket_get_nu (packet) > storage->spectrum_virt_start_nu) && (rpacket_get_nu(packet) < storage->spectrum_virt_end_nu)) - { - for (int64_t i = 0; i < rpacket_get_virtual_packet_flag (packet); i++) - { - double weight; - rpacket_t virt_packet = *packet; - double mu_min; - if (rpacket_get_r(&virt_packet) > storage->r_inner[0]) - { - mu_min = - -1.0 * sqrt (1.0 - - (storage->r_inner[0] / rpacket_get_r(&virt_packet)) * - (storage->r_inner[0] / rpacket_get_r(&virt_packet))); - - if (storage->full_relativity) - { - // Need to transform the angular size of the photosphere into the CMF - mu_min = angle_aberration_LF_to_CMF (&virt_packet, storage, mu_min); - } - } - else - { - mu_min = 0.0; - } - double mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag (packet); - rpacket_set_mu(&virt_packet,mu_min + (i + rk_double (mt_state)) * mu_bin); - switch (virtual_mode) - { - case -2: - weight = 1.0 / rpacket_get_virtual_packet_flag (packet); - break; - case -1: - weight = - 2.0 * rpacket_get_mu(&virt_packet) / - rpacket_get_virtual_packet_flag (packet); - break; - case 1: - weight = - (1.0 - - mu_min) / 2.0 / rpacket_get_virtual_packet_flag (packet); - break; - default: - fprintf (stderr, "Something has gone horribly wrong!\n"); - // FIXME MR: we need to somehow signal an error here - // I'm adding an exit() here to inform the compiler about the impossible path - exit(1); - } - angle_aberration_CMF_to_LF (&virt_packet, storage); - double doppler_factor_ratio = - rpacket_doppler_factor (packet, storage) / - rpacket_doppler_factor (&virt_packet, storage); - rpacket_set_energy(&virt_packet, - rpacket_get_energy (packet) * doppler_factor_ratio); - rpacket_set_nu(&virt_packet,rpacket_get_nu (packet) * doppler_factor_ratio); - reabsorbed = montecarlo_one_packet_loop (storage, &virt_packet, 1, mt_state); -#ifdef WITH_VPACKET_LOGGING -#ifdef WITHOPENMP -#pragma omp critical - { -#endif // WITHOPENMP - if (storage->virt_packet_count >= storage->virt_array_size) - { - storage->virt_array_size *= 2; - storage->virt_packet_nus = safe_realloc(storage->virt_packet_nus, sizeof(double) * storage->virt_array_size); - storage->virt_packet_energies = safe_realloc(storage->virt_packet_energies, sizeof(double) * storage->virt_array_size); - storage->virt_packet_last_interaction_in_nu = safe_realloc(storage->virt_packet_last_interaction_in_nu, sizeof(double) * storage->virt_array_size); - storage->virt_packet_last_interaction_type = safe_realloc(storage->virt_packet_last_interaction_type, sizeof(int64_t) * storage->virt_array_size); - storage->virt_packet_last_line_interaction_in_id = safe_realloc(storage->virt_packet_last_line_interaction_in_id, sizeof(int64_t) * storage->virt_array_size); - storage->virt_packet_last_line_interaction_out_id = safe_realloc(storage->virt_packet_last_line_interaction_out_id, sizeof(int64_t) * storage->virt_array_size); - } - storage->virt_packet_nus[storage->virt_packet_count] = rpacket_get_nu(&virt_packet); - storage->virt_packet_energies[storage->virt_packet_count] = rpacket_get_energy(&virt_packet) * weight; - storage->virt_packet_last_interaction_in_nu[storage->virt_packet_count] = storage->last_interaction_in_nu[rpacket_get_id (packet)]; - storage->virt_packet_last_interaction_type[storage->virt_packet_count] = storage->last_interaction_type[rpacket_get_id (packet)]; - storage->virt_packet_last_line_interaction_in_id[storage->virt_packet_count] = storage->last_line_interaction_in_id[rpacket_get_id (packet)]; - storage->virt_packet_last_line_interaction_out_id[storage->virt_packet_count] = storage->last_line_interaction_out_id[rpacket_get_id (packet)]; - storage->virt_packet_count += 1; -#ifdef WITHOPENMP - } -#endif // WITHOPENMP -#endif // WITH_VPACKET_LOGGING - if ((rpacket_get_nu(&virt_packet) < storage->spectrum_end_nu) && - (rpacket_get_nu(&virt_packet) > storage->spectrum_start_nu)) - { -#ifdef WITHOPENMP -#pragma omp critical - { -#endif // WITHOPENMP - int64_t virt_id_nu = - floor ((rpacket_get_nu(&virt_packet) - - storage->spectrum_start_nu) / - storage->spectrum_delta_nu); - storage->spectrum_virt_nu[virt_id_nu] += - rpacket_get_energy(&virt_packet) * weight; -#ifdef WITHOPENMP - } -#endif // WITHOPENMP - } - } - } - else - { - return 1; - } \ No newline at end of file +@njit(**njit_dict) +def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma): + if ((r_packet.nu < vpacket_collection.spectrum_frequency[0]) or + (r_packet.nu > vpacket_collection.spectrum_frequency[-1])): + + return + + no_of_vpackets = vpacket_collection.number_of_vpackets + + v_packets_nu = np.empty(vpacket_collection.number_of_vpackets) + v_packets_energy = np.empty(vpacket_collection.number_of_vpackets) + + ### TODO theoretical check for r_packet nu within vpackets bins + if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary + mu_min = -np.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) + v_packet_on_inner_boundary = False + else: + v_packet_on_inner_boundary = True + mu_min = 0.0 + + mu_bin = (1.0 - mu_min) / no_of_vpackets + r_packet_doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, + numba_model.time_explosion) + for i in range(no_of_vpackets): + v_packet_mu = mu_min + i * mu_bin + np.random.random() * mu_bin + if v_packet_on_inner_boundary: + weight = 2 * v_packet_mu / no_of_vpackets + else: + weight = (1 - mu_min) / (2 * no_of_vpackets) + v_packet_doppler_factor = get_doppler_factor( + r_packet.r, v_packet_mu, numba_model.time_explosion) + # transform between r_packet mu and v_packet_mu + doppler_factor_ratio = ( + r_packet_doppler_factor / v_packet_doppler_factor) + v_packet_nu = r_packet.nu * doppler_factor_ratio + v_packet_energy = r_packet.energy * weight * doppler_factor_ratio + v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, + v_packet_energy, r_packet.current_shell_id, + r_packet.next_line_id) + + tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) + v_packet.energy *= np.exp(-tau_vpacket) + + vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu + vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy + vpacket_collection.idx += 1 + \ No newline at end of file From d2df2d7ac0e5a56b3d3128714e3ece0cd6b28679 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 13 Jun 2019 16:37:56 +0200 Subject: [PATCH 037/116] Final V-Packet addition Co-authored-by: Christian Vogl --- tardis/montecarlo/montecarlo_numba/vpacket.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 77013174ad1..e9617f57e7c 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -135,6 +135,22 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): @njit(**njit_dict) def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma): + """ + Shoot a volley of vpackets (the vpacket collection specifies how many) + from the current position of the rpacket. + + Parameters + ---------- + r_packet : [type] + [description] + vpacket_collection : [type] + [description] + numba_model : [type] + [description] + numba_plasma : [type] + [description] + """ + if ((r_packet.nu < vpacket_collection.spectrum_frequency[0]) or (r_packet.nu > vpacket_collection.spectrum_frequency[-1])): From a520234b95692b2de71dc425d18b3e42b106665c Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 14 Jun 2019 10:48:26 -0400 Subject: [PATCH 038/116] adding initial macroatom setup --- tardis/montecarlo/montecarlo_numba/base.py | 9 +- .../montecarlo_numba/interaction.py | 164 ++++++++++++++++++ .../montecarlo/montecarlo_numba/macro_atom.py | 106 +++++++++++ .../montecarlo_numba/numba_interface.py | 51 +++++- tardis/montecarlo/montecarlo_numba/rpacket.py | 90 +++++++--- .../montecarlo_numba/single_packet_loop.py | 31 +++- tardis/montecarlo/montecarlo_numba/vpacket.py | 60 +++---- 7 files changed, 440 insertions(+), 71 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/interaction.py create mode 100644 tardis/montecarlo/montecarlo_numba/macro_atom.py diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 9ad233105f2..91c57297840 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -2,7 +2,7 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( - PacketCollection, VPacketCollection, NumbaModel, NumbaPlasma, Estimators, MonteCarloConfiguration) + PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, Estimators, MonteCarloConfiguration) from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( single_packet_loop) @@ -17,12 +17,7 @@ def montecarlo_radial1d(model, plasma, runner, no_of_virtual_packets): montecarlo_configuration = MonteCarloConfiguration(no_of_virtual_packets) numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, model.time_explosion.to('s').value) - numba_plasma = NumbaPlasma(plasma.electron_densities.values, - plasma.atomic_data.lines.nu.values, - np.ascontiguousarray( - plasma.tau_sobolevs.values.copy(), - dtype=np.float64) - ) + numba_plasma = numba_plasma_initialize(plasma) estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator) v_packets_energy_hist = montecarlo_main_loop(packet_collection, numba_model, numba_plasma, estimators, runner.spectrum_frequency.value, montecarlo_configuration) diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py new file mode 100644 index 00000000000..93483e4ca48 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -0,0 +1,164 @@ +from enum import IntEnum + +from numba import njit + +from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo.montecarlo_numba.rpacket import get_doppler_factor, \ + get_random_mu + +class LineInteractionType(IntEnum): + SCATTER = 0 + DOWNBRANCH = 1 + MACROATOM = 2 + +@njit(**njit_dict) +def general_scatter(r_packet, time_explosion): + """ + Thomson as well as line scattering + 2) get the doppler factor at that position with the old angle + 3) convert the current energy and nu into the comoving + frame with the old mu + 4) Scatter and draw new mu - update mu + 5) Transform the comoving energy and nu back using the new mu + + Parameters + ---------- + distance : [type] + [description] + """ + old_doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) + comov_energy = r_packet.energy * old_doppler_factor + comov_nu = r_packet.nu * old_doppler_factor + r_packet.mu = get_random_mu() + inverse_new_doppler_factor = 1. / get_doppler_factor( + r_packet.r, r_packet.mu, time_explosion) + r_packet.energy = comov_energy * inverse_new_doppler_factor + r_packet.nu = comov_nu * inverse_new_doppler_factor + +""" +void +montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state) +{ + move_packet (packet, storage, distance); + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + double comov_energy = rpacket_get_energy (packet) * doppler_factor; + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + rpacket_reset_tau_event (packet, mt_state); + storage->last_interaction_type[rpacket_get_id (packet)] = 1; + + angle_aberration_CMF_to_LF (packet, storage); + + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + create_vpacket (storage, packet, mt_state); + } +} + +""" + +def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): + #increment_j_blue_estimator(packet, storage, distance, line2d_idx); + #increment_Edotlu_estimator(packet, storage, distance, line2d_idx); + + general_scatter(r_packet, time_explosion) + # update last_interaction + + if line_interaction_type == LineInteractionType.SCATTER: + line_emission(r_packet, r_packet.next_line_id, time_explosion, numba_plasma) + else: + pass +""" + line_emission() + + if (storage->line_interaction_id == 0) + { + line_emission(packet, storage, next_line_id, mt_state); + } + else if (storage->line_interaction_id >= 1) + { + rpacket_set_macro_atom_activation_level(packet, + storage-> + rpacket_set_macro_atom_activation_level(packet, + storage->line2macro_level_upper[ + next_line_id]); + macro_atom(packet, storage, mt_state); +""" + +def line_emission(r_packet, emission_line_id, time_explosion, + numba_plasma): + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, + time_explosion) + r_packet.nu = numba_plasma.line_list_nu[ + emission_line_id] / doppler_factor + r_packet.next_line_id = emission_line_id + 1 + +""" +void +montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state) +{ + uint64_t next_line_id = rpacket_get_next_line_id (packet); + uint64_t line2d_idx = next_line_id + + storage->no_of_lines * rpacket_get_current_shell_id (packet); + if (rpacket_get_virtual_packet (packet) == 0) + { + } + double tau_line = + storage->line_lists_tau_sobolevs[line2d_idx]; + double tau_continuum = rpacket_get_chi_continuum(packet) * distance; + double tau_combined = tau_line + tau_continuum; + //rpacket_set_next_line_id (packet, rpacket_get_next_line_id (packet) + 1); + + if (next_line_id + 1 == storage->no_of_lines) + { + rpacket_set_last_line (packet, true); + } + if (rpacket_get_virtual_packet (packet) > 0) + { + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) + tau_line); + rpacket_set_next_line_id (packet, next_line_id + 1); + test_for_close_line (packet, storage); + } + else if (rpacket_get_tau_event (packet) < tau_combined) + { // Line absorption occurs + move_packet (packet, storage, distance); + double old_doppler_factor = rpacket_doppler_factor (packet, storage); + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + storage->last_interaction_in_nu[rpacket_get_id (packet)] = + rpacket_get_nu (packet); + storage->last_line_interaction_in_id[rpacket_get_id (packet)] = + next_line_id; + storage->last_line_interaction_shell_id[rpacket_get_id (packet)] = + rpacket_get_current_shell_id (packet); + storage->last_interaction_type[rpacket_get_id (packet)] = 2; + if (storage->line_interaction_id == 0) + { + line_emission (packet, storage, next_line_id, mt_state); + } + else if (storage->line_interaction_id >= 1) + { + rpacket_set_macro_atom_activation_level (packet, + storage->line2macro_level_upper[next_line_id]); + macro_atom (packet, storage, mt_state); + } + } + else + { // Packet passes line without interacting + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) - tau_line); + rpacket_set_next_line_id (packet, next_line_id + 1); + packet->compute_chi_bf = false; + test_for_close_line (packet, storage); + } +} +""" + diff --git a/tardis/montecarlo/montecarlo_numba/macro_atom.py b/tardis/montecarlo/montecarlo_numba/macro_atom.py new file mode 100644 index 00000000000..474a70e761b --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/macro_atom.py @@ -0,0 +1,106 @@ +import numpy as np +from enum import IntEnum + +class MacroAtomError(ValueError): + pass + +class MacroAtomTransitionType(IntEnum): + INTERNAL_UP = 1 + INTERNAL_DOWN = 0 + BB_EMISSION = -1 + BF_EMISSION = -2 + FF_EMISSION = -3 + ADIABATIC_COOLING = -4 + + + +def macro_atom(r_packet, activation_level, numba_plasma): + """ + + Parameters + ---------- + r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + activation_level + numba_plasma + + Returns + ------- + + """ + + current_transition_type = 0 + + while current_transition_type >= 0: + probability = 0.0 + probability_event = np.random.random() + + block_start, block_end = numba_plasma.macro_atom_block_references[ + [activation_level, activate_level + 1]] + + current_transition_probabilities = ( + numba_plasma.transition_probabilities[ + block_start:block_end, r_packet.current_shell_id]) + + for transition_probability in current_transition_probabilities: + probability += transition_probability + if probability > probability_event: + current_transition_type = transition_type[i] + activate_level = destination_level_id[i] + break + else: + raise MacroAtomError( + 'MacroAtom ran out of the block. This should not happen as the sum ' + 'of probabilities is normalized to 1 and the probability_event ' + 'should be less than 1') + + if + +#void +#macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) +#{ + int emit = 0, i = 0, offset = -1; + uint64_t activate_level = rpacket_get_macro_atom_activation_level (packet); + while (emit >= 0) + { + double event_random = rk_double (mt_state); + i = storage->macro_block_references[activate_level] - 1; + double p = 0.0; + offset = storage->transition_probabilities_nd * + rpacket_get_current_shell_id (packet); + do + { + ++i; + p += storage->transition_probabilities[offset + i]; + } + while (p <= event_random); + emit = storage->transition_type[i]; + activate_level = storage->destination_level_id[i]; + } + switch (emit) + { + case BB_EMISSION: + line_emission (packet, storage, storage->transition_line_id[i], mt_state); + break; + + case BF_EMISSION: + rpacket_set_current_continuum_id (packet, storage->transition_line_id[i]); + storage->last_line_interaction_out_id[rpacket_get_id (packet)] = + rpacket_get_current_continuum_id (packet); + + continuum_emission (packet, storage, mt_state, sample_nu_free_bound, 3); + break; + + case FF_EMISSION: + continuum_emission (packet, storage, mt_state, sample_nu_free_free, 4); + break; + + case ADIABATIC_COOLING: + storage->last_interaction_type[rpacket_get_id (packet)] = 5; + rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); + break; + + default: + fprintf (stderr, "This process for macro-atom deactivation should not exist! (emit = %d)\n", emit); + exit(1); + } +} \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index fce65992126..de52b771265 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -34,15 +34,64 @@ def __init__(self, r_inner, r_outer, time_explosion): ('electron_density', float64[:]), ('line_list_nu', float64[:]), ('tau_sobolev', float64[:, :]), + ('transition_probabilities', float64[:, :]), + ('line2macro_level_upper', int64[:]), + ('macro_block_references', int64[:]), + ('transition_type', int64[:]), + ('destination_level_id', int64[:]), + ('transition_line_id', int64[:]), + ] @jitclass(numba_plasma_spec) class NumbaPlasma(object): - def __init__(self, electron_density, line_list_nu, tau_sobolev): + def __init__(self, electron_density, line_list_nu, tau_sobolev, + transition_probabilities, line2macro_level_upper, + macro_block_references, transition_type, destination_level_id, + transition_line_id): + self.electron_density = electron_density self.line_list_nu = line_list_nu self.tau_sobolev = tau_sobolev + #### Macro Atom transition probabilities + self.transition_probabilities = transition_probabilities + self.line2macro_level_upper = line2macro_level_upper + + self.macro_block_references = macro_block_references + self.transition_type = transition_type + + # Destination level is not needed and/or generated for downbranch + self.destination_level_id = destination_level_id + self.transition_line_id = transition_line_id + + +def numba_plasma_initialize(plasma): + electron_densities = plasma.electron_densities.values + line_list_nu = plasma.atomic_data.lines.nu.values + tau_sobolev = np.ascontiguousarray(plasma.tau_sobolevs.values.copy(), + dtype=np.float64) + + transition_probabilities = np.ascontiguousarray( + plasma.transition_probabilities.values.copy(), dtype=np.float64) + line2macro_level_upper = plasma.atomic_data.lines_upper2macro_reference_idx + macro_block_references = plasma.atomic_data.macro_atom_references[ + 'block_references'].values + + transition_type = plasma.atomic_data.macro_atom_data[ + 'transition_type'].values + + # Destination level is not needed and/or generated for downbranch + destination_level_id = plasma.atomic_data.macro_atom_data[ + 'destination_level_idx'].values + transition_line_id = plasma.atomic_data.macro_atom_data[ + 'lines_idx'].values + + return NumbaPlasma(electron_densities, line_list_nu, tau_sobolev, + transition_probabilities, line2macro_level_upper, + macro_block_references, transition_type, + destination_level_id, transition_line_id) + packet_collection_spec = [ ('packets_input_nu', float64[:]), diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index ec24d9a5e8b..6223930c290 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -1,7 +1,7 @@ import numpy as np from enum import IntEnum -from numba import int64, float64, boolean -from numba import jitclass, njit, gdb +from numba import int64, float64 +from numba import jitclass, njit from tardis.montecarlo.montecarlo_numba import njit_dict from tardis import constants as const @@ -15,16 +15,19 @@ class MonteCarloException(ValueError): SIGMA_THOMSON = const.sigma_T.to('cm^2').value INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON + class PacketStatus(IntEnum): IN_PROCESS = 0 EMITTED = 1 REABSORBED = 2 + class InteractionType(IntEnum): BOUNDARY = 1 LINE = 2 ESCATTERING = 3 + rpacket_spec = [ ('r', float64), ('mu', float64), @@ -252,26 +255,73 @@ def move_packet_across_shell_boundary(packet, delta_shell, else: packet.current_shell_id = next_shell_id -@njit(**njit_dict) -def scatter(r_packet, time_explosion): + +def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): """ - General scattering for lines as well as thomson. - 2) get the doppler factor at that position with the old angle - 3) convert the current energy and nu into the comoving - frame with the old mu - 4) Scatter and draw new mu - update mu - 5) Transform the comoving energy and nu back using the new mu - + Parameters ---------- - distance : [type] - [description] + r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + emission_line_id: int + numba_plasma + time_explosion + + Returns + ------- + """ doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) - comov_energy = r_packet.energy * doppler_factor - comov_nu = r_packet.nu * doppler_factor - r_packet.mu = get_random_mu() - inverse_new_doppler_factor = 1. / get_doppler_factor( - r_packet.r, r_packet.mu, time_explosion) - r_packet.energy = comov_energy * inverse_new_doppler_factor - r_packet.nu = comov_nu * inverse_new_doppler_factor + r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor + r_packet.next_line_id = emission_line_id + 1 + + +""" +void +line_emission (rpacket_t * packet, storage_model_t * storage, int64_t emission_line_id, rk_state *mt_state) +{ + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + storage->last_line_interaction_out_id[rpacket_get_id (packet)] = emission_line_id; + if (storage->cont_status == CONTINUUM_ON) + { + storage->last_interaction_out_type[rpacket_get_id (packet)] = 2; + } + + rpacket_set_nu (packet, + storage->line_list_nu[emission_line_id] * inverse_doppler_factor); + rpacket_set_nu_line (packet, storage->line_list_nu[emission_line_id]); + rpacket_set_next_line_id (packet, emission_line_id + 1); + rpacket_reset_tau_event (packet, mt_state); + + angle_aberration_CMF_to_LF (packet, storage); + + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + bool virtual_close_line = false; + if (!rpacket_get_last_line (packet) && + fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - + rpacket_get_nu_line (packet)) < + (rpacket_get_nu_line (packet)* 1e-7)) + { + virtual_close_line = true; + } + // QUESTIONABLE!!! + bool old_close_line = rpacket_get_close_line (packet); + rpacket_set_close_line (packet, virtual_close_line); + create_vpacket (storage, packet, mt_state); + rpacket_set_close_line (packet, old_close_line); + virtual_close_line = false; + } + test_for_close_line (packet, storage); +} + +void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) +{ + if (!rpacket_get_last_line (packet) && + fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - + rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* + 1e-7)) + { + rpacket_set_close_line (packet, true); + } +} +""" \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index ff4aec1095a..3a94a1874bd 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,12 +1,16 @@ from numba import njit -import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, - move_packet_across_shell_boundary, move_rpacket, scatter) + move_packet_across_shell_boundary, move_rpacket) +from tardis.montecarlo.montecarlo_numba.interaction import general_scatter, \ + LineInteractionType from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley -from tardis.montecarlo.montecarlo_numba.numba_interface import VPacketCollection + + + + @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection): """ @@ -17,12 +21,19 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma estimators: tardis.montecarlo.montecarlo_numba.numba_interface.Estimators + vpacket_collection: tardis.montecarlo.montecarlo_numba.numba_interface.VPacketCollection Returns ------- + : None + + This function does not return anything but changes the r_packet object + and if virtual packets are requested - also updates the vpacket_collection """ + line_interaction_type = LineInteractionType.SCATTER + doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, numba_model.time_explosion) r_packet.nu /= doppler_factor @@ -34,23 +45,31 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( r_packet, numba_model, numba_plasma) + if interaction_type == InteractionType.BOUNDARY: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) move_packet_across_shell_boundary(r_packet, delta_shell, len(numba_model.r_inner)) + elif interaction_type == InteractionType.LINE: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) - scatter(r_packet, numba_model.time_explosion) - r_packet.next_line_id += 1 + + if line_interaction_type == LineInteractionType.SCATTER: + general_scatter(r_packet, numba_model.time_explosion) + r_packet.next_line_id += 1 + + else: + pass + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) elif interaction_type == InteractionType.ESCATTERING: move_rpacket(r_packet, distance, numba_model.time_explosion, estimators) - scatter(r_packet, numba_model.time_explosion) + general_scatter(r_packet, numba_model.time_explosion) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e9617f57e7c..39464aaa29a 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -30,33 +30,6 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id): self.next_line_id = next_line_id self.status = PacketStatus.IN_PROCESS - def move_packet_across_shell_boundary(self, distance, delta_shell, - no_of_shells): - """ - Move packet across shell boundary - realizing if we are still in the simulation or have - moved out through the inner boundary or outer boundary and updating packet - status. - - Parameters - ---------- - distance : float - distance to move to shell boundary - - delta_shell: int - is +1 if moving outward or -1 if moving inward - - no_of_shells: int - number of shells in TARDIS simulation - """ - - next_shell_id = r_packet.current_shell_id + delta_shell - - if next_shell_id >= no_of_shells: - r_packet.status = PacketStatus.EMITTED - elif next_shell_id < 0: - r_packet.status = PacketStatus.REABSORBED - else: - rpacket.current_shell_id = next_shell_id @njit(**njit_dict) @@ -70,10 +43,8 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): # defining start for line interaction start_line_id = v_packet.next_line_id - # defining taus - - # e scattering initialization + cur_electron_density = numba_plasma.electron_density[ v_packet.current_shell_id] tau_electron = calculate_tau_electron(cur_electron_density, @@ -97,7 +68,7 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): distance_trace_line = calculate_distance_line( v_packet.nu, comov_nu, nu_line, numba_model.time_explosion) - if (distance_boundary <= distance_trace_line): + if distance_boundary <= distance_trace_line: break tau_trace_combined += tau_trace_line @@ -113,6 +84,19 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): @njit(**njit_dict) def trace_vpacket(v_packet, numba_model, numba_plasma): + """ + Trace single vpacket. + Parameters + ---------- + v_packet + numba_model + numba_plasma + + Returns + ------- + + """ + tau_trace_combined = 0.0 while True: tau_trace_combined_shell, distance_boundary, delta_shell = trace_vpacket_within_shell( @@ -158,10 +142,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma no_of_vpackets = vpacket_collection.number_of_vpackets - v_packets_nu = np.empty(vpacket_collection.number_of_vpackets) - v_packets_energy = np.empty(vpacket_collection.number_of_vpackets) - - ### TODO theoretical check for r_packet nu within vpackets bins + ### TODO theoretical check for r_packet nu within vpackets bins - is done somewhere else I think if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary mu_min = -np.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) v_packet_on_inner_boundary = False @@ -174,25 +155,30 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma numba_model.time_explosion) for i in range(no_of_vpackets): v_packet_mu = mu_min + i * mu_bin + np.random.random() * mu_bin - if v_packet_on_inner_boundary: + + if v_packet_on_inner_boundary: # The weights are described in K&S 2014 weight = 2 * v_packet_mu / no_of_vpackets else: weight = (1 - mu_min) / (2 * no_of_vpackets) + v_packet_doppler_factor = get_doppler_factor( r_packet.r, v_packet_mu, numba_model.time_explosion) + # transform between r_packet mu and v_packet_mu doppler_factor_ratio = ( r_packet_doppler_factor / v_packet_doppler_factor) + v_packet_nu = r_packet.nu * doppler_factor_ratio v_packet_energy = r_packet.energy * weight * doppler_factor_ratio + v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, v_packet_energy, r_packet.current_shell_id, r_packet.next_line_id) tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) + v_packet.energy *= np.exp(-tau_vpacket) vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy vpacket_collection.idx += 1 - \ No newline at end of file From 5e4d3471f16c6d7b9598d7c2ccd9e74a00e3a774 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 14 Jun 2019 12:34:07 -0400 Subject: [PATCH 039/116] further work on macro_atom --- .../montecarlo_numba/interaction.py | 16 +++++--- .../montecarlo/montecarlo_numba/macro_atom.py | 40 +++++++++++-------- .../montecarlo_numba/single_packet_loop.py | 25 +++++------- 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 93483e4ca48..e9b8933d1d4 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -1,11 +1,11 @@ from enum import IntEnum from numba import njit - from tardis.montecarlo.montecarlo_numba import njit_dict -from tardis.montecarlo.montecarlo_numba.rpacket import get_doppler_factor, \ - get_random_mu +from tardis.montecarlo.montecarlo_numba.rpacket import ( + get_doppler_factor, get_random_mu) +from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom class LineInteractionType(IntEnum): SCATTER = 0 DOWNBRANCH = 1 @@ -61,6 +61,7 @@ def general_scatter(r_packet, time_explosion): """ +@njit(**njit_dict) def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): #increment_j_blue_estimator(packet, storage, distance, line2d_idx); #increment_Edotlu_estimator(packet, storage, distance, line2d_idx); @@ -70,8 +71,10 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): if line_interaction_type == LineInteractionType.SCATTER: line_emission(r_packet, r_packet.next_line_id, time_explosion, numba_plasma) - else: - pass + else: # includes both macro atom and downbranch - encoded in the transition probabilities + emission_line_id = macro_atom(r_packet, numba_plasma) + line_emission(r_packet, emission_line_id, time_explosion, + numba_plasma) """ line_emission() @@ -89,8 +92,11 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): macro_atom(packet, storage, mt_state); """ +@njit(**njit_dict) def line_emission(r_packet, emission_line_id, time_explosion, numba_plasma): + if emission_line_id != r_packet.next_line_id: + pass doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) r_packet.nu = numba_plasma.line_list_nu[ diff --git a/tardis/montecarlo/montecarlo_numba/macro_atom.py b/tardis/montecarlo/montecarlo_numba/macro_atom.py index 474a70e761b..584a20ab01d 100644 --- a/tardis/montecarlo/montecarlo_numba/macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/macro_atom.py @@ -1,6 +1,9 @@ import numpy as np from enum import IntEnum +from numba import njit +from tardis.montecarlo.montecarlo_numba import njit_dict + class MacroAtomError(ValueError): pass @@ -13,48 +16,52 @@ class MacroAtomTransitionType(IntEnum): ADIABATIC_COOLING = -4 - -def macro_atom(r_packet, activation_level, numba_plasma): +@njit(**njit_dict) +def macro_atom(r_packet, numba_plasma): """ Parameters ---------- r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket - activation_level - numba_plasma + numba_plasma: tardis.montecarlo.numba_interface.numba_plasma Returns ------- """ - + activation_level_id = numba_plasma.line2macro_level_upper[ + r_packet.next_line_id] current_transition_type = 0 while current_transition_type >= 0: probability = 0.0 probability_event = np.random.random() - block_start, block_end = numba_plasma.macro_atom_block_references[ - [activation_level, activate_level + 1]] + block_start = numba_plasma.macro_block_references[activation_level_id] + block_end = numba_plasma.macro_block_references[activation_level_id + 1] - current_transition_probabilities = ( - numba_plasma.transition_probabilities[ - block_start:block_end, r_packet.current_shell_id]) + for transition_id in range(block_start, block_end): + + transition_probability = numba_plasma.transition_probabilities[ + transition_id, r_packet.current_shell_id] - for transition_probability in current_transition_probabilities: probability += transition_probability if probability > probability_event: - current_transition_type = transition_type[i] - activate_level = destination_level_id[i] + current_transition_type = numba_plasma.transition_type[ + transition_id] break + else: raise MacroAtomError( 'MacroAtom ran out of the block. This should not happen as the sum ' 'of probabilities is normalized to 1 and the probability_event ' 'should be less than 1') - if - + if current_transition_type == MacroAtomTransitionType.BB_EMISSION: + return numba_plasma.transition_line_id[transition_id] + else: + raise MacroAtomError('MacroAtom currently only allows BB transitions') +""" #void #macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) #{ @@ -103,4 +110,5 @@ def macro_atom(r_packet, activation_level, numba_plasma): fprintf (stderr, "This process for macro-atom deactivation should not exist! (emit = %d)\n", emit); exit(1); } -} \ No newline at end of file +} +""" \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 3a94a1874bd..f7315d95bd8 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -3,8 +3,8 @@ from tardis.montecarlo.montecarlo_numba.rpacket import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, move_packet_across_shell_boundary, move_rpacket) -from tardis.montecarlo.montecarlo_numba.interaction import general_scatter, \ - LineInteractionType +from tardis.montecarlo.montecarlo_numba.interaction import ( + general_scatter, LineInteractionType, line_scatter) from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley @@ -32,7 +32,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ """ - line_interaction_type = LineInteractionType.SCATTER + line_interaction_type = LineInteractionType.MACROATOM doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, numba_model.time_explosion) @@ -48,27 +48,22 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ if interaction_type == InteractionType.BOUNDARY: move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + estimators) move_packet_across_shell_boundary(r_packet, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + estimators) + line_scatter(r_packet, numba_model.time_explosion, + line_interaction_type, numba_plasma) - if line_interaction_type == LineInteractionType.SCATTER: - general_scatter(r_packet, numba_model.time_explosion) - r_packet.next_line_id += 1 - - else: - pass - - - trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + trace_vpacket_volley( + r_packet, vpacket_collection, numba_model, numba_plasma) elif interaction_type == InteractionType.ESCATTERING: move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + estimators) general_scatter(r_packet, numba_model.time_explosion) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) From 7b8c86acdd916b29c4f0dcabf66c4cfd08ca2a55 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 27 Jun 2019 12:59:20 -0400 Subject: [PATCH 040/116] debugged macro_atom - very close now Co-authored-by: Christian Vogl --- .../montecarlo/montecarlo_numba/macro_atom.py | 62 +++---------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/macro_atom.py b/tardis/montecarlo/montecarlo_numba/macro_atom.py index 584a20ab01d..8302223004e 100644 --- a/tardis/montecarlo/montecarlo_numba/macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/macro_atom.py @@ -46,10 +46,13 @@ def macro_atom(r_packet, numba_plasma): transition_id, r_packet.current_shell_id] probability += transition_probability + if probability > probability_event: - current_transition_type = numba_plasma.transition_type[ - transition_id] - break + activation_level_id = numba_plasma.destination_level_id[ + transition_id] + current_transition_type = numba_plasma.transition_type[ + transition_id] + break else: raise MacroAtomError( @@ -60,55 +63,4 @@ def macro_atom(r_packet, numba_plasma): if current_transition_type == MacroAtomTransitionType.BB_EMISSION: return numba_plasma.transition_line_id[transition_id] else: - raise MacroAtomError('MacroAtom currently only allows BB transitions') -""" -#void -#macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) -#{ - int emit = 0, i = 0, offset = -1; - uint64_t activate_level = rpacket_get_macro_atom_activation_level (packet); - while (emit >= 0) - { - double event_random = rk_double (mt_state); - i = storage->macro_block_references[activate_level] - 1; - double p = 0.0; - offset = storage->transition_probabilities_nd * - rpacket_get_current_shell_id (packet); - do - { - ++i; - p += storage->transition_probabilities[offset + i]; - } - while (p <= event_random); - emit = storage->transition_type[i]; - activate_level = storage->destination_level_id[i]; - } - switch (emit) - { - case BB_EMISSION: - line_emission (packet, storage, storage->transition_line_id[i], mt_state); - break; - - case BF_EMISSION: - rpacket_set_current_continuum_id (packet, storage->transition_line_id[i]); - storage->last_line_interaction_out_id[rpacket_get_id (packet)] = - rpacket_get_current_continuum_id (packet); - - continuum_emission (packet, storage, mt_state, sample_nu_free_bound, 3); - break; - - case FF_EMISSION: - continuum_emission (packet, storage, mt_state, sample_nu_free_free, 4); - break; - - case ADIABATIC_COOLING: - storage->last_interaction_type[rpacket_get_id (packet)] = 5; - rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); - break; - - default: - fprintf (stderr, "This process for macro-atom deactivation should not exist! (emit = %d)\n", emit); - exit(1); - } -} -""" \ No newline at end of file + raise MacroAtomError('MacroAtom currently only allows BB transitions') \ No newline at end of file From 2a9ec251720e886c049fe8335fd044cee149bf32 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 25 Nov 2019 22:15:35 -0500 Subject: [PATCH 041/116] add tracking to rpack4ets --- .../montecarlo/montecarlo_numba/__init__.py | 3 ++- tardis/montecarlo/montecarlo_numba/base.py | 19 +++++++++----- .../montecarlo_numba/numba_interface.py | 3 +++ tardis/montecarlo/montecarlo_numba/rpacket.py | 4 ++- .../montecarlo_numba/single_packet_loop.py | 25 +++++++++++++++++-- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index dd918494b6a..41d82521260 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -3,4 +3,5 @@ njit_dict = {'fastmath': True} from tardis.montecarlo.montecarlo_numba.rpacket import RPacket -from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d \ No newline at end of file +from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d +from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 91c57297840..165d9e2c718 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,4 +1,4 @@ -from numba import prange, njit, config, int64 +from numba import prange, njit import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( @@ -27,7 +27,8 @@ def montecarlo_radial1d(model, plasma, runner, no_of_virtual_packets): @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, - estimators, spectrum_frequency, montecarlo_configuration): + estimators, spectrum_frequency, + montecarlo_configuration): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -43,11 +44,13 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] + for i in prange(len(output_nus)): r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], packet_collection.packets_input_nu[i], - packet_collection.packets_input_energy[i]) + packet_collection.packets_input_energy[i], + i) vpacket_collection = VPacketCollection(spectrum_frequency, montecarlo_configuration.number_of_vpackets, 20000) single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection) @@ -62,9 +65,13 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, vpackets_nu = vpacket_collection.nus[:vpacket_collection.idx] vpackets_energy = vpacket_collection.energies[:vpacket_collection.idx] - v_packets_idx = np.floor((vpackets_nu - spectrum_frequency[0]) / delta_nu).astype(np.int64) - for i, idx in enumerate(v_packets_idx): - v_packets_energy_hist[idx] += vpackets_energy[i] + v_packets_idx = np.floor((vpackets_nu - spectrum_frequency[0]) / + delta_nu).astype(np.int64) + for j, idx in enumerate(v_packets_idx): + if ((vpackets_nu[j] < spectrum_frequency[0]) or + (vpackets_nu[j] > spectrum_frequency[-1])): + continue + v_packets_energy_hist[idx] += vpackets_energy[j] packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index de52b771265..f7f68cba2ba 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -148,3 +148,6 @@ def __init__(self, j_estimator, nu_bar_estimator): class MonteCarloConfiguration(object): def __init__(self, number_of_vpackets): self.number_of_vpackets = number_of_vpackets + + +#class TrackRPacket(object): diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/rpacket.py index 6223930c290..7fad4a0e8cc 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/rpacket.py @@ -36,6 +36,7 @@ class InteractionType(IntEnum): ('next_line_id', int64), ('current_shell_id', int64), ('status', int64), + ('index', int64) ] @njit(**njit_dict) @@ -95,13 +96,14 @@ def get_random_mu(): @jitclass(rpacket_spec) class RPacket(object): - def __init__(self, r, mu, nu, energy): + def __init__(self, r, mu, nu, energy, index=0): self.r = r self.mu = mu self.nu = nu self.energy = energy self.current_shell_id = 0 self.status = PacketStatus.IN_PROCESS + self.index = index def initialize_line_id(self, numba_plasma, numba_model): inverse_line_list_nu = numba_plasma.line_list_nu[::-1] diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index f7315d95bd8..338b1b49c3e 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,4 +1,5 @@ from numba import njit +import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, @@ -12,7 +13,8 @@ @njit -def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection): +def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, + vpacket_collection, track_rpackets=False): """ Parameters @@ -31,7 +33,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ and if virtual packets are requested - also updates the vpacket_collection """ - + np.random.seed(r_packet.index) line_interaction_type = LineInteractionType.MACROATOM doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, @@ -41,6 +43,13 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ r_packet.initialize_line_id(numba_plasma, numba_model) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + if track_rpackets: + rpacket_track_nu = [r_packet.nu] + rpacket_track_mu = [r_packet.mu] + rpacket_track_r = [r_packet.r] + rpacket_track_interaction = [InteractionType.BOUNDARY] + rpacket_track_distance = [0.] + while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( @@ -68,3 +77,15 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_ trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + if track_rpackets: + rpacket_track_nu.append(r_packet.nu) + rpacket_track_mu.append(r_packet.mu) + rpacket_track_r.append(r_packet.r) + rpacket_track_interaction.append(interaction_type) + rpacket_track_distance.append(distance) + + + if track_rpackets is True: + return (rpacket_track_nu, rpacket_track_mu, rpacket_track_r, + rpacket_track_interaction, rpacket_track_distance) + From c728696d055b0e8dbd1227cf12fe6870a096d43d Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 27 Nov 2019 12:38:28 -0500 Subject: [PATCH 042/116] make the numba branch compatible with the rest of tardis --- tardis/montecarlo/montecarlo_numba/base.py | 4 +++- tardis/montecarlo/montecarlo_numba/single_packet_loop.py | 7 +++++-- tardis/montecarlo/montecarlo_numba/vpacket.py | 4 ++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 165d9e2c718..e8f32cede38 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -20,7 +20,9 @@ def montecarlo_radial1d(model, plasma, runner, no_of_virtual_packets): numba_plasma = numba_plasma_initialize(plasma) estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator) - v_packets_energy_hist = montecarlo_main_loop(packet_collection, numba_model, numba_plasma, estimators, runner.spectrum_frequency.value, montecarlo_configuration) + v_packets_energy_hist = montecarlo_main_loop( + packet_collection, numba_model, numba_plasma, estimators, + runner.spectrum_frequency.value, montecarlo_configuration) runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 338b1b49c3e..cefb8e1b4bf 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -42,7 +42,9 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet.energy /= doppler_factor r_packet.initialize_line_id(numba_plasma, numba_model) - trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, + numba_plasma) + if track_rpackets: rpacket_track_nu = [r_packet.nu] rpacket_track_mu = [r_packet.mu] @@ -75,7 +77,8 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, estimators) general_scatter(r_packet, numba_model.time_explosion) - trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) + trace_vpacket_volley(r_packet, vpacket_collection, numba_model, + numba_plasma) if track_rpackets: rpacket_track_nu.append(r_packet.nu) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 39464aaa29a..469e3f2c23d 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -139,8 +139,12 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma (r_packet.nu > vpacket_collection.spectrum_frequency[-1])): return + + no_of_vpackets = vpacket_collection.number_of_vpackets + if no_of_vpackets == 0: + return ### TODO theoretical check for r_packet nu within vpackets bins - is done somewhere else I think if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary From 51aed35d1d6123f39b895ed0f788e542de838b44 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 14:51:55 -0500 Subject: [PATCH 043/116] rewire montecarlo with new numba part --- tardis/montecarlo/base.py | 7 +++++- tardis/montecarlo/montecarlo_numba/base.py | 16 +++++++----- .../montecarlo_numba/interaction.py | 10 +++----- .../montecarlo/montecarlo_numba/macro_atom.py | 17 +++++++------ .../montecarlo_numba/numba_interface.py | 25 ++++++++++++++++++- .../montecarlo_numba/single_packet_loop.py | 10 +++++--- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 1b5c96f317a..afafde98a50 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -17,6 +17,8 @@ from tardis.montecarlo.formal_integral import FormalIntegrator from tardis.montecarlo.montecarlo_numba import montecarlo_radial1d +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + configuration_initialize) import numpy as np @@ -222,7 +224,10 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets) - montecarlo_radial1d(model, plasma, self, no_of_virtual_packets) + montecarlo_configuration = configuration_initialize( + self, no_of_virtual_packets) + + montecarlo_radial1d(model, plasma, self, montecarlo_configuration) #montecarlo.montecarlo_radial1d( # model, plasma, self, # virtual_packet_flag=no_of_virtual_packets, diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index e8f32cede38..57d379f4868 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -2,19 +2,20 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( - PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, Estimators, MonteCarloConfiguration) + PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, + Estimators, MonteCarloConfiguration, configuration_initialize) from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( single_packet_loop) from tardis.montecarlo.montecarlo_numba import njit_dict -def montecarlo_radial1d(model, plasma, runner, no_of_virtual_packets): +def montecarlo_radial1d(model, plasma, runner, montecarlo_configuration): packet_collection = PacketCollection( runner.input_nu, runner.input_mu, runner.input_energy, runner._output_nu, runner._output_energy ) - montecarlo_configuration = MonteCarloConfiguration(no_of_virtual_packets) + numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, model.time_explosion.to('s').value) numba_plasma = numba_plasma_initialize(plasma) @@ -40,13 +41,13 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, storage_model : [type] [description] """ + output_nus = np.empty_like(packet_collection.packets_output_nu) output_energies = np.empty_like(packet_collection.packets_output_nu) v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] - for i in prange(len(output_nus)): r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], @@ -54,8 +55,11 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, packet_collection.packets_input_energy[i], i) - vpacket_collection = VPacketCollection(spectrum_frequency, montecarlo_configuration.number_of_vpackets, 20000) - single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection) + vpacket_collection = VPacketCollection( + spectrum_frequency, montecarlo_configuration.number_of_vpackets, + 20000) + single_packet_loop(r_packet, numba_model, numba_plasma, estimators, + vpacket_collection, montecarlo_configuration) output_nus[i] = r_packet.nu diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index e9b8933d1d4..87867372c35 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -1,15 +1,13 @@ -from enum import IntEnum - from numba import njit from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + LineInteractionType) + from tardis.montecarlo.montecarlo_numba.rpacket import ( get_doppler_factor, get_random_mu) from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom -class LineInteractionType(IntEnum): - SCATTER = 0 - DOWNBRANCH = 1 - MACROATOM = 2 + @njit(**njit_dict) def general_scatter(r_packet, time_explosion): diff --git a/tardis/montecarlo/montecarlo_numba/macro_atom.py b/tardis/montecarlo/montecarlo_numba/macro_atom.py index 8302223004e..c9d26740088 100644 --- a/tardis/montecarlo/montecarlo_numba/macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/macro_atom.py @@ -40,6 +40,7 @@ def macro_atom(r_packet, numba_plasma): block_start = numba_plasma.macro_block_references[activation_level_id] block_end = numba_plasma.macro_block_references[activation_level_id + 1] + # looping through the transition probabilities for transition_id in range(block_start, block_end): transition_probability = numba_plasma.transition_probabilities[ @@ -48,17 +49,17 @@ def macro_atom(r_packet, numba_plasma): probability += transition_probability if probability > probability_event: - activation_level_id = numba_plasma.destination_level_id[ - transition_id] - current_transition_type = numba_plasma.transition_type[ - transition_id] - break + activation_level_id = numba_plasma.destination_level_id[ + transition_id] + current_transition_type = numba_plasma.transition_type[ + transition_id] + break else: raise MacroAtomError( - 'MacroAtom ran out of the block. This should not happen as the sum ' - 'of probabilities is normalized to 1 and the probability_event ' - 'should be less than 1') + 'MacroAtom ran out of the block. This should not happen as ' + 'the sum of probabilities is normalized to 1 and ' + 'the probability_event should be less than 1') if current_transition_type == MacroAtomTransitionType.BB_EMISSION: return numba_plasma.transition_line_id[transition_id] diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index f7f68cba2ba..ecb862dcbee 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -1,3 +1,5 @@ +from enum import IntEnum + from numba import float64, int64, jitclass import numpy as np @@ -141,13 +143,34 @@ def __init__(self, j_estimator, nu_bar_estimator): self.nu_bar_estimator = nu_bar_estimator monte_carlo_configuration_spec = [ + ('line_interaction_type', int64), ('number_of_vpackets', int64) ] @jitclass(monte_carlo_configuration_spec) class MonteCarloConfiguration(object): - def __init__(self, number_of_vpackets): + def __init__(self, number_of_vpackets, line_interaction_type): + self.line_interaction_type = line_interaction_type self.number_of_vpackets = number_of_vpackets +def configuration_initialize(runner, number_of_vpackets): + if runner.line_interaction_type == 'macroatom': + line_interaction_type = LineInteractionType.MACROATOM + elif runner.line_interaction_type == 'downbranch': + line_interaction_type = LineInteractionType.DOWNBRANCH + elif runner.line_interaction_type == 'scatter': + line_interaction_type = LineInteractionType.SCATTER + else: + raise ValueError(f'Line interaction type must be one of "macroatom",' + f'"downbranch", or "scatter" but is ' + f'{runner.line_interaction_type}') + + return MonteCarloConfiguration(number_of_vpackets, line_interaction_type) + + #class TrackRPacket(object): +class LineInteractionType(IntEnum): + SCATTER = 0 + DOWNBRANCH = 1 + MACROATOM = 2 \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index cefb8e1b4bf..1f868c59118 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -5,7 +5,9 @@ InteractionType, PacketStatus, get_doppler_factor, trace_packet, move_packet_across_shell_boundary, move_rpacket) from tardis.montecarlo.montecarlo_numba.interaction import ( - general_scatter, LineInteractionType, line_scatter) + general_scatter, line_scatter) +from tardis.montecarlo.montecarlo_numba.numba_interface import \ + LineInteractionType from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley @@ -14,7 +16,8 @@ @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection, track_rpackets=False): + vpacket_collection, montecarlo_configuration, + track_rpackets=False): """ Parameters @@ -34,7 +37,8 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, """ np.random.seed(r_packet.index) - line_interaction_type = LineInteractionType.MACROATOM + + line_interaction_type = montecarlo_configuration.line_interaction_type doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, numba_model.time_explosion) From 7fe7d4fbc35c96ff961e42259f338fd8493d3ac7 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 15:18:26 -0500 Subject: [PATCH 044/116] add temporary_v_packet_bins --- tardis/montecarlo/formal_integral.py | 1 + .../montecarlo/montecarlo_numba/__init__.py | 2 +- tardis/montecarlo/montecarlo_numba/base.py | 4 +-- .../montecarlo_numba/interaction.py | 2 +- .../montecarlo/montecarlo_numba/macro_atom.py | 2 +- .../montecarlo_numba/numba_interface.py | 22 +++++++++---- .../{rpacket.py => r_packet.py} | 2 +- .../montecarlo_numba/single_packet_loop.py | 33 +++++++++---------- tardis/montecarlo/montecarlo_numba/vpacket.py | 2 +- 9 files changed, 38 insertions(+), 32 deletions(-) rename tardis/montecarlo/montecarlo_numba/{rpacket.py => r_packet.py} (99%) diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index ad9463153e9..26049da2149 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -101,6 +101,7 @@ def make_source_function(self): ------- Numpy array containing ( 1 - exp(-tau_ul) ) S_ul ordered by wavelength of the transition u -> l """ + model = self.model plasma = self.plasma runner = self.runner diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index 41d82521260..d7944262a3d 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -2,6 +2,6 @@ binding.set_option("tmp", "-non-global-value-max-name-size=2048") njit_dict = {'fastmath': True} -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket +from tardis.montecarlo.montecarlo_numba.r_packet import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 57d379f4868..a61876695d9 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,6 +1,6 @@ from numba import prange, njit import numpy as np -from tardis.montecarlo.montecarlo_numba.rpacket import RPacket, PacketStatus +from tardis.montecarlo.montecarlo_numba.r_packet import RPacket, PacketStatus from tardis.montecarlo.montecarlo_numba.numba_interface import ( PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, Estimators, MonteCarloConfiguration, configuration_initialize) @@ -57,7 +57,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, vpacket_collection = VPacketCollection( spectrum_frequency, montecarlo_configuration.number_of_vpackets, - 20000) + montecarlo_configuration.temporary_v_packet_bins) single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection, montecarlo_configuration) diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 87867372c35..c62f5733a03 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -4,7 +4,7 @@ LineInteractionType) -from tardis.montecarlo.montecarlo_numba.rpacket import ( +from tardis.montecarlo.montecarlo_numba.r_packet import ( get_doppler_factor, get_random_mu) from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom diff --git a/tardis/montecarlo/montecarlo_numba/macro_atom.py b/tardis/montecarlo/montecarlo_numba/macro_atom.py index c9d26740088..45849952b62 100644 --- a/tardis/montecarlo/montecarlo_numba/macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/macro_atom.py @@ -22,7 +22,7 @@ def macro_atom(r_packet, numba_plasma): Parameters ---------- - r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket numba_plasma: tardis.montecarlo.numba_interface.numba_plasma Returns diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index ecb862dcbee..4aa4f5cfc62 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -121,12 +121,14 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, ('number_of_vpackets', int64) ] + @jitclass(vpacket_collection_spec) class VPacketCollection(object): - def __init__(self, spectrum_frequency, number_of_vpackets, initial_vpacket_bins): + def __init__(self, spectrum_frequency, number_of_vpackets, + temporary_v_packet_bins): self.spectrum_frequency = spectrum_frequency - self.nus = np.empty(initial_vpacket_bins, dtype=np.float64) - self.energies = np.empty(initial_vpacket_bins, dtype=np.float64) + self.nus = np.empty(temporary_v_packet_bins, dtype=np.float64) + self.energies = np.empty(temporary_v_packet_bins, dtype=np.float64) self.number_of_vpackets = number_of_vpackets self.idx = 0 @@ -144,17 +146,22 @@ def __init__(self, j_estimator, nu_bar_estimator): monte_carlo_configuration_spec = [ ('line_interaction_type', int64), - ('number_of_vpackets', int64) + ('number_of_vpackets', int64), + ('temporary_v_packet_bins', int64) ] + @jitclass(monte_carlo_configuration_spec) class MonteCarloConfiguration(object): - def __init__(self, number_of_vpackets, line_interaction_type): + def __init__(self, number_of_vpackets, line_interaction_type, + temporary_v_packet_bins): self.line_interaction_type = line_interaction_type self.number_of_vpackets = number_of_vpackets + self.temporary_v_packet_bins = temporary_v_packet_bins -def configuration_initialize(runner, number_of_vpackets): +def configuration_initialize(runner, number_of_vpackets, + temporary_v_packet_bins=20000): if runner.line_interaction_type == 'macroatom': line_interaction_type = LineInteractionType.MACROATOM elif runner.line_interaction_type == 'downbranch': @@ -166,7 +173,8 @@ def configuration_initialize(runner, number_of_vpackets): f'"downbranch", or "scatter" but is ' f'{runner.line_interaction_type}') - return MonteCarloConfiguration(number_of_vpackets, line_interaction_type) + return MonteCarloConfiguration(number_of_vpackets, line_interaction_type, + temporary_v_packet_bins) #class TrackRPacket(object): diff --git a/tardis/montecarlo/montecarlo_numba/rpacket.py b/tardis/montecarlo/montecarlo_numba/r_packet.py similarity index 99% rename from tardis/montecarlo/montecarlo_numba/rpacket.py rename to tardis/montecarlo/montecarlo_numba/r_packet.py index 7fad4a0e8cc..5a5630e662c 100644 --- a/tardis/montecarlo/montecarlo_numba/rpacket.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -263,7 +263,7 @@ def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): Parameters ---------- - r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket emission_line_id: int numba_plasma time_explosion diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 1f868c59118..15c0d4c18fa 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -1,7 +1,7 @@ from numba import njit import numpy as np -from tardis.montecarlo.montecarlo_numba.rpacket import ( +from tardis.montecarlo.montecarlo_numba.r_packet import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, move_packet_across_shell_boundary, move_rpacket) from tardis.montecarlo.montecarlo_numba.interaction import ( @@ -12,8 +12,6 @@ from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley - - @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection, montecarlo_configuration, @@ -22,7 +20,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, Parameters ---------- - r_packet: tardis.montecarlo.montecarlo_numba.rpacket.RPacket + r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma estimators: tardis.montecarlo.montecarlo_numba.numba_interface.Estimators @@ -34,8 +32,8 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, This function does not return anything but changes the r_packet object and if virtual packets are requested - also updates the vpacket_collection - """ + np.random.seed(r_packet.index) line_interaction_type = montecarlo_configuration.line_interaction_type @@ -50,12 +48,11 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, numba_plasma) if track_rpackets: - rpacket_track_nu = [r_packet.nu] - rpacket_track_mu = [r_packet.mu] - rpacket_track_r = [r_packet.r] - rpacket_track_interaction = [InteractionType.BOUNDARY] - rpacket_track_distance = [0.] - + r_packet_track_nu = [r_packet.nu] + r_packet_track_mu = [r_packet.mu] + r_packet_track_r = [r_packet.r] + r_packet_track_interaction = [InteractionType.BOUNDARY] + r_packet_track_distance = [0.] while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( @@ -85,14 +82,14 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, numba_plasma) if track_rpackets: - rpacket_track_nu.append(r_packet.nu) - rpacket_track_mu.append(r_packet.mu) - rpacket_track_r.append(r_packet.r) - rpacket_track_interaction.append(interaction_type) - rpacket_track_distance.append(distance) + r_packet_track_nu.append(r_packet.nu) + r_packet_track_mu.append(r_packet.mu) + r_packet_track_r.append(r_packet.r) + r_packet_track_interaction.append(interaction_type) + r_packet_track_distance.append(distance) if track_rpackets is True: - return (rpacket_track_nu, rpacket_track_mu, rpacket_track_r, - rpacket_track_interaction, rpacket_track_distance) + return (r_packet_track_nu, r_packet_track_mu, r_packet_track_r, + r_packet_track_interaction, r_packet_track_distance) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 469e3f2c23d..08f78a0ba68 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -5,7 +5,7 @@ import numpy as np -from tardis.montecarlo.montecarlo_numba.rpacket import ( +from tardis.montecarlo.montecarlo_numba.r_packet import ( calculate_distance_boundary, get_doppler_factor, calculate_distance_line, calculate_tau_electron, PacketStatus, move_packet_across_shell_boundary) From e8fcf4da84ef83553ea82cab772187299b718d90 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 16:27:01 -0500 Subject: [PATCH 045/116] document montecarlo part --- tardis/montecarlo/montecarlo_numba/r_packet.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 5a5630e662c..644ea3c8622 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -154,18 +154,30 @@ def trace_packet(r_packet, numba_model, numba_plasma): doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, numba_model.time_explosion) comov_nu = r_packet.nu * doppler_factor - last_line = False + cur_line_id = start_line_id + for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): + # Going through the lines nu_line = numba_plasma.line_list_nu[cur_line_id] + + # Getting the tau for the next line tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, r_packet.current_shell_id] - + + # Adding it to the tau_trace_line_combined tau_trace_line_combined += tau_trace_line + + # Calculating the distance until the current photons co-moving nu + # redshifts to the line frequency distance_trace = calculate_distance_line( r_packet.nu, comov_nu, nu_line, numba_model.time_explosion) + + # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, distance_trace) + + # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron if ((distance_boundary <= distance_trace) and From fc79fe8483dd15ebef0e2163ac712bd5497cdd37 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 17:09:17 -0500 Subject: [PATCH 046/116] recalcuate distance_electron --- tardis/montecarlo/montecarlo_numba/r_packet.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 644ea3c8622..73b43bbdda3 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -180,6 +180,7 @@ def trace_packet(r_packet, numba_model, numba_plasma): # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron + if ((distance_boundary <= distance_trace) and (distance_boundary <= distance_electron)): interaction_type = InteractionType.BOUNDARY # BOUNDARY @@ -199,6 +200,12 @@ def trace_packet(r_packet, numba_model, numba_plasma): r_packet.next_line_id = cur_line_id distance = distance_trace break + + # Recalculating distance_electron using tau_event - + # tau_trace_line_combined + distance_electron = calculate_distance_electron( + cur_electron_density, tau_event - tau_trace_line_combined) + else: # Executed when no break occurs in the for loop if cur_line_id == (len(numba_plasma.line_list_nu) - 1): # Treatment for last line From 6a1d0655169c19a41d382324cf602757ff7b5070 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 19:57:31 -0500 Subject: [PATCH 047/116] add estimators --- tardis/montecarlo/base.py | 31 ++++---- tardis/montecarlo/montecarlo_numba/base.py | 3 +- .../montecarlo_numba/interaction.py | 79 ------------------- .../montecarlo_numba/numba_interface.py | 6 +- .../montecarlo/montecarlo_numba/r_packet.py | 55 +------------ .../montecarlo_numba/single_packet_loop.py | 14 ++-- tardis/simulation/base.py | 2 +- tardis/tests/test_tardis_full.py | 2 +- .../tests/test_tardis_full_formal_integral.py | 2 +- 9 files changed, 36 insertions(+), 158 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index afafde98a50..d6a0b04e94f 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -86,21 +86,22 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, self.optional_hdf_properties.append('spectrum_integrated') - def _initialize_estimator_arrays(self, no_of_shells, tau_sobolev_shape): + def _initialize_estimator_arrays(self, tau_sobolev_shape): """ Initialize the output arrays of the montecarlo simulation. Parameters ---------- - model: ~Radial1DModel + tau_sobolev_shape: tuple + tuple for the tau_sobolev_shape """ # Estimators - self.j_estimator = np.zeros(no_of_shells, dtype=np.float64) - self.nu_bar_estimator = np.zeros(no_of_shells, dtype=np.float64) - self.j_blue_estimator = np.zeros(tau_sobolev_shape) - self.Edotlu_estimator = np.zeros(tau_sobolev_shape) + self.j_estimator = np.zeros(tau_sobolev_shape[1], dtype=np.float64) + self.nu_bar_estimator = np.zeros(tau_sobolev_shape[1], dtype=np.float64) + self.j_b_lu_estimator = np.zeros(tau_sobolev_shape) + self.edot_lu_estimator = np.zeros(tau_sobolev_shape) def _initialize_geometry_arrays(self, model): """ @@ -217,8 +218,10 @@ def run(self, model, plasma, no_of_packets, self) self.time_of_simulation = self.calculate_time_of_simulation(model) self.volume = model.volume - self._initialize_estimator_arrays(self.volume.shape[0], - plasma.tau_sobolevs.shape) + + # Initializing estimator array + self._initialize_estimator_arrays(plasma.tau_sobolevs.shape) + self._initialize_geometry_arrays(model) self._initialize_packets(model.t_inner.value, @@ -236,13 +239,13 @@ def run(self, model, plasma, no_of_packets, # Workaround so that j_blue_estimator is in the right ordering # They are written as an array of dimension (no_of_shells, no_of_lines) # but python expects (no_of_lines, no_of_shells) - self.j_blue_estimator = np.ascontiguousarray( - self.j_blue_estimator.flatten().reshape( - self.j_blue_estimator.shape, order='F') + self.j_b_lu_estimator = np.ascontiguousarray( + self.j_b_lu_estimator.flatten().reshape( + self.j_b_lu_estimator.shape, order='F') ) - self.Edotlu_estimator = np.ascontiguousarray( - self.Edotlu_estimator.flatten().reshape( - self.Edotlu_estimator.shape, order='F') + self.edot_lu_estimator = np.ascontiguousarray( + self.edot_lu_estimator.flatten().reshape( + self.edot_lu_estimator.shape, order='F') ) def legacy_return(self): diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a61876695d9..dcf2d32a827 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -19,7 +19,8 @@ def montecarlo_radial1d(model, plasma, runner, montecarlo_configuration): numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, model.time_explosion.to('s').value) numba_plasma = numba_plasma_initialize(plasma) - estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator) + estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator, + runner.j_b_lu_estimator, runner.edot_lu_estimator) v_packets_energy_hist = montecarlo_main_loop( packet_collection, numba_model, numba_plasma, estimators, diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index c62f5733a03..fda2c077135 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -73,22 +73,6 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): emission_line_id = macro_atom(r_packet, numba_plasma) line_emission(r_packet, emission_line_id, time_explosion, numba_plasma) -""" - line_emission() - - if (storage->line_interaction_id == 0) - { - line_emission(packet, storage, next_line_id, mt_state); - } - else if (storage->line_interaction_id >= 1) - { - rpacket_set_macro_atom_activation_level(packet, - storage-> - rpacket_set_macro_atom_activation_level(packet, - storage->line2macro_level_upper[ - next_line_id]); - macro_atom(packet, storage, mt_state); -""" @njit(**njit_dict) def line_emission(r_packet, emission_line_id, time_explosion, @@ -101,68 +85,5 @@ def line_emission(r_packet, emission_line_id, time_explosion, emission_line_id] / doppler_factor r_packet.next_line_id = emission_line_id + 1 -""" -void -montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state) -{ - uint64_t next_line_id = rpacket_get_next_line_id (packet); - uint64_t line2d_idx = next_line_id + - storage->no_of_lines * rpacket_get_current_shell_id (packet); - if (rpacket_get_virtual_packet (packet) == 0) - { - } - double tau_line = - storage->line_lists_tau_sobolevs[line2d_idx]; - double tau_continuum = rpacket_get_chi_continuum(packet) * distance; - double tau_combined = tau_line + tau_continuum; - //rpacket_set_next_line_id (packet, rpacket_get_next_line_id (packet) + 1); - if (next_line_id + 1 == storage->no_of_lines) - { - rpacket_set_last_line (packet, true); - } - if (rpacket_get_virtual_packet (packet) > 0) - { - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) + tau_line); - rpacket_set_next_line_id (packet, next_line_id + 1); - test_for_close_line (packet, storage); - } - else if (rpacket_get_tau_event (packet) < tau_combined) - { // Line absorption occurs - move_packet (packet, storage, distance); - double old_doppler_factor = rpacket_doppler_factor (packet, storage); - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - storage->last_interaction_in_nu[rpacket_get_id (packet)] = - rpacket_get_nu (packet); - storage->last_line_interaction_in_id[rpacket_get_id (packet)] = - next_line_id; - storage->last_line_interaction_shell_id[rpacket_get_id (packet)] = - rpacket_get_current_shell_id (packet); - storage->last_interaction_type[rpacket_get_id (packet)] = 2; - if (storage->line_interaction_id == 0) - { - line_emission (packet, storage, next_line_id, mt_state); - } - else if (storage->line_interaction_id >= 1) - { - rpacket_set_macro_atom_activation_level (packet, - storage->line2macro_level_upper[next_line_id]); - macro_atom (packet, storage, mt_state); - } - } - else - { // Packet passes line without interacting - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) - tau_line); - rpacket_set_next_line_id (packet, next_line_id + 1); - packet->compute_chi_bf = false; - test_for_close_line (packet, storage); - } -} -""" diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 4aa4f5cfc62..06cb6a275b1 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -136,13 +136,17 @@ def __init__(self, spectrum_frequency, number_of_vpackets, estimators_spec = [ ('j_estimator', float64[:]), ('nu_bar_estimator', float64[:]), + ('edot_lu_estimator', float64[:]), ] @jitclass(estimators_spec) class Estimators(object): - def __init__(self, j_estimator, nu_bar_estimator): + def __init__(self, j_estimator, nu_bar_estimator, j_b_lu_estimator, + edot_lu_estimator): self.j_estimator = j_estimator self.nu_bar_estimator = nu_bar_estimator + self.j_b_lu_estimator = j_b_lu_estimator + self.edot_lu_estimator = edot_lu_estimator monte_carlo_configuration_spec = [ ('line_interaction_type', int64), diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 73b43bbdda3..9cc27d564fc 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -223,7 +223,7 @@ def trace_packet(r_packet, numba_model, numba_plasma): @njit(**njit_dict) -def move_rpacket(r_packet, distance, time_explosion, numba_estimator): +def move_r_packet(r_packet, distance, time_explosion, numba_estimator): """Move packet a distance and recalculate the new angle mu Parameters @@ -240,6 +240,7 @@ def move_rpacket(r_packet, distance, time_explosion, numba_estimator): comov_energy * distance) numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( comov_energy * distance * comov_nu) + #numba_estimator.edot_lu_estimator[] += r = r_packet.r if (distance > 0.0): @@ -294,55 +295,3 @@ def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor r_packet.next_line_id = emission_line_id + 1 - - -""" -void -line_emission (rpacket_t * packet, storage_model_t * storage, int64_t emission_line_id, rk_state *mt_state) -{ - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - storage->last_line_interaction_out_id[rpacket_get_id (packet)] = emission_line_id; - if (storage->cont_status == CONTINUUM_ON) - { - storage->last_interaction_out_type[rpacket_get_id (packet)] = 2; - } - - rpacket_set_nu (packet, - storage->line_list_nu[emission_line_id] * inverse_doppler_factor); - rpacket_set_nu_line (packet, storage->line_list_nu[emission_line_id]); - rpacket_set_next_line_id (packet, emission_line_id + 1); - rpacket_reset_tau_event (packet, mt_state); - - angle_aberration_CMF_to_LF (packet, storage); - - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - bool virtual_close_line = false; - if (!rpacket_get_last_line (packet) && - fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - - rpacket_get_nu_line (packet)) < - (rpacket_get_nu_line (packet)* 1e-7)) - { - virtual_close_line = true; - } - // QUESTIONABLE!!! - bool old_close_line = rpacket_get_close_line (packet); - rpacket_set_close_line (packet, virtual_close_line); - create_vpacket (storage, packet, mt_state); - rpacket_set_close_line (packet, old_close_line); - virtual_close_line = false; - } - test_for_close_line (packet, storage); -} - -void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) -{ - if (!rpacket_get_last_line (packet) && - fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - - rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* - 1e-7)) - { - rpacket_set_close_line (packet, true); - } -} -""" \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 15c0d4c18fa..3be235b752b 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -3,7 +3,7 @@ from tardis.montecarlo.montecarlo_numba.r_packet import ( InteractionType, PacketStatus, get_doppler_factor, trace_packet, - move_packet_across_shell_boundary, move_rpacket) + move_packet_across_shell_boundary, move_r_packet) from tardis.montecarlo.montecarlo_numba.interaction import ( general_scatter, line_scatter) from tardis.montecarlo.montecarlo_numba.numba_interface import \ @@ -59,14 +59,14 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet, numba_model, numba_plasma) if interaction_type == InteractionType.BOUNDARY: - move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + move_r_packet(r_packet, distance, numba_model.time_explosion, + estimators) move_packet_across_shell_boundary(r_packet, delta_shell, len(numba_model.r_inner)) elif interaction_type == InteractionType.LINE: - move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + move_r_packet(r_packet, distance, numba_model.time_explosion, + estimators) line_scatter(r_packet, numba_model.time_explosion, line_interaction_type, numba_plasma) @@ -74,8 +74,8 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet, vpacket_collection, numba_model, numba_plasma) elif interaction_type == InteractionType.ESCATTERING: - move_rpacket(r_packet, distance, numba_model.time_explosion, - estimators) + move_r_packet(r_packet, distance, numba_model.time_explosion, + estimators) general_scatter(r_packet, numba_model.time_explosion) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, diff --git a/tardis/simulation/base.py b/tardis/simulation/base.py index 95cddd93527..8d9d0d30159 100644 --- a/tardis/simulation/base.py +++ b/tardis/simulation/base.py @@ -257,7 +257,7 @@ def advance_state(self): # case it needs some extra kwargs. if 'j_blue_estimator' in self.plasma.outputs_dict: update_properties.update(t_inner=next_t_inner, - j_blue_estimator=self.runner.j_blue_estimator) + j_blue_estimator=self.runner.j_b_lu_estimator) self.plasma.update(**update_properties) diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 682e4645728..4e98249b685 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -52,7 +52,7 @@ def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata('j_blue_estimator').values npt.assert_allclose( - runner.j_blue_estimator, + runner.j_b_lu_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): diff --git a/tardis/tests/test_tardis_full_formal_integral.py b/tardis/tests/test_tardis_full_formal_integral.py index d3de68501c4..3be437b8113 100644 --- a/tardis/tests/test_tardis_full_formal_integral.py +++ b/tardis/tests/test_tardis_full_formal_integral.py @@ -79,7 +79,7 @@ def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata('j_blue_estimator').values npt.assert_allclose( - runner.j_blue_estimator, + runner.j_b_lu_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): From b02f53ca486bd2b0e714e604201268c84894f3fc Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sun, 1 Dec 2019 20:33:15 -0500 Subject: [PATCH 048/116] finish estimators --- .../montecarlo_numba/numba_interface.py | 3 +- .../montecarlo/montecarlo_numba/r_packet.py | 28 +++++++++++++++---- .../montecarlo_numba/single_packet_loop.py | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 06cb6a275b1..56157b27196 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -136,7 +136,8 @@ def __init__(self, spectrum_frequency, number_of_vpackets, estimators_spec = [ ('j_estimator', float64[:]), ('nu_bar_estimator', float64[:]), - ('edot_lu_estimator', float64[:]), + ('j_b_lu_estimator', float64[:, :]), + ('edot_lu_estimator', float64[:, :]) ] @jitclass(estimators_spec) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 9cc27d564fc..adc60af0bd8 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -91,9 +91,6 @@ def get_doppler_factor(r, mu, time_explosion): def get_random_mu(): return 2.0 * np.random.random() - 1.0 - - - @jitclass(rpacket_spec) class RPacket(object): def __init__(self, r, mu, nu, energy, index=0): @@ -114,16 +111,33 @@ def initialize_line_id(self, numba_plasma, numba_model): np.searchsorted(inverse_line_list_nu, comov_nu)) self.next_line_id = next_line_id +@njit(**njit_dict) +def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, + time_explosion): + """ Actual calculation - simplified below + r_interaction = np.sqrt(r_packet.r**2 + distance_trace**2 + + 2 * r_packet.r * distance_trace * r_packet.mu) + mu_interaction = (r_packet.mu * r_packet.r + distance_trace) / r_interaction + doppler_factor = 1.0 - mu_interaction * r_interaction / time_explosion + """ + doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / + (time_explosion * C_SPEED_OF_LIGHT)) + energy = r_packet.energy * doppler_factor + estimators.j_b_lu_estimator[cur_line_id, r_packet.current_shell_id] += ( + energy / r_packet.nu) + estimators.edot_lu_estimator[cur_line_id, r_packet.current_shell_id] += ( + energy) @njit(**njit_dict) -def trace_packet(r_packet, numba_model, numba_plasma): +def trace_packet(r_packet, numba_model, numba_plasma, estimators): """ Parameters ---------- numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma + estimators: tardis.motnecarlo.montecarlo_numba.numba_interface.Estimators Returns ------- @@ -158,6 +172,7 @@ def trace_packet(r_packet, numba_model, numba_plasma): cur_line_id = start_line_id for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): + # Going through the lines nu_line = numba_plasma.line_list_nu[cur_line_id] @@ -180,6 +195,10 @@ def trace_packet(r_packet, numba_model, numba_plasma): # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron + # Updating the J_b_lu and E_dot_lu + update_line_estimators( + estimators, r_packet, cur_line_id, distance_trace, + numba_model.time_explosion) if ((distance_boundary <= distance_trace) and (distance_boundary <= distance_electron)): @@ -240,7 +259,6 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): comov_energy * distance) numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( comov_energy * distance * comov_nu) - #numba_estimator.edot_lu_estimator[] += r = r_packet.r if (distance > 0.0): diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 3be235b752b..4955f306c39 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -56,7 +56,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( - r_packet, numba_model, numba_plasma) + r_packet, numba_model, numba_plasma, estimators=estimators) if interaction_type == InteractionType.BOUNDARY: move_r_packet(r_packet, distance, numba_model.time_explosion, From 4d9dec8b22ec9a4ebbcccc58e26f0fdffcbf290d Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 2 Dec 2019 16:45:40 -0500 Subject: [PATCH 049/116] fix formal integral --- tardis/montecarlo/formal_integral.py | 4 ++-- tardis/montecarlo/montecarlo.pyx | 16 ++++++++++++---- tardis/montecarlo/montecarlo_numba/r_packet.py | 3 ++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index 26049da2149..2d973c44603 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -122,7 +122,7 @@ def make_source_function(self): Edotlu_norm_factor = (1 / (runner.time_of_simulation * model.volume)) exptau = 1 - np.exp(- plasma.tau_sobolevs) - Edotlu = Edotlu_norm_factor * exptau * runner.Edotlu_estimator + Edotlu = Edotlu_norm_factor * exptau * runner.edot_lu_estimator # The following may be achieved by calling the appropriate plasma # functions @@ -131,7 +131,7 @@ def make_source_function(self): model.volume)).to("1/(cm^2 s)").value # Jbluelu should already by in the correct order, i.e. by wavelength of # the transition l->u - Jbluelu = runner.j_blue_estimator * Jbluelu_norm_factor + Jbluelu = runner.j_b_lu_estimator * Jbluelu_norm_factor upper_level_index = atomic_data.lines.index.droplevel('level_number_lower') e_dot_lu = pd.DataFrame(Edotlu, index=upper_level_index) diff --git a/tardis/montecarlo/montecarlo.pyx b/tardis/montecarlo/montecarlo.pyx index e3ac6e42f58..d2d2def18bb 100644 --- a/tardis/montecarlo/montecarlo.pyx +++ b/tardis/montecarlo/montecarlo.pyx @@ -140,10 +140,18 @@ cdef extern from "src/integrator.h": cdef initialize_storage_model(model, plasma, runner, storage_model_t *storage): """ - Initializing the storage struct. + + Parameters + ---------- + model + plasma + runner: tardis.montecarlo.base.MontecarloRunner + storage - """ + Returns + ------- + """ storage.no_of_packets = runner.input_nu.size storage.packet_nus = PyArray_DATA(runner.input_nu) storage.packet_mus = PyArray_DATA(runner.input_mu) @@ -195,10 +203,10 @@ cdef initialize_storage_model(model, plasma, runner, storage_model_t *storage): runner.line_lists_tau_sobolevs ) storage.line_lists_j_blues = PyArray_DATA( - runner.j_blue_estimator) + runner.j_b_lu_estimator) storage.line_lists_Edotlu = PyArray_DATA( - runner.Edotlu_estimator) + runner.edot_lu_estimator) storage.line_interaction_id = runner.get_line_interaction_id( runner.line_interaction_type) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index adc60af0bd8..742a5e0995f 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -118,7 +118,8 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, r_interaction = np.sqrt(r_packet.r**2 + distance_trace**2 + 2 * r_packet.r * distance_trace * r_packet.mu) mu_interaction = (r_packet.mu * r_packet.r + distance_trace) / r_interaction - doppler_factor = 1.0 - mu_interaction * r_interaction / time_explosion + doppler_factor = 1.0 - mu_interaction * r_interaction / + ( time_explosion * C) """ doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / (time_explosion * C_SPEED_OF_LIGHT)) From 0e8b0874fc380ba405e680d03590cf6aa5d9eb07 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 4 Dec 2019 11:14:46 -0500 Subject: [PATCH 050/116] fix estimators Co-authored-by: Christian Vogl Co-authored-by: Marc Williamson --- tardis/montecarlo/base.py | 12 +----------- tardis/montecarlo/montecarlo_numba/r_packet.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index d6a0b04e94f..5665778ae05 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -236,17 +236,7 @@ def run(self, model, plasma, no_of_packets, # virtual_packet_flag=no_of_virtual_packets, # nthreads=nthreads, # last_run=last_run) - # Workaround so that j_blue_estimator is in the right ordering - # They are written as an array of dimension (no_of_shells, no_of_lines) - # but python expects (no_of_lines, no_of_shells) - self.j_b_lu_estimator = np.ascontiguousarray( - self.j_b_lu_estimator.flatten().reshape( - self.j_b_lu_estimator.shape, order='F') - ) - self.edot_lu_estimator = np.ascontiguousarray( - self.edot_lu_estimator.flatten().reshape( - self.edot_lu_estimator.shape, order='F') - ) + def legacy_return(self): return (self.output_nu, self.output_energy, diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 742a5e0995f..6e6f25f433e 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -170,7 +170,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): numba_model.time_explosion) comov_nu = r_packet.nu * doppler_factor - cur_line_id = start_line_id + cur_line_id = start_line_id # initializing varibale for Numba + # - do not remove for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): @@ -196,11 +197,6 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron - # Updating the J_b_lu and E_dot_lu - update_line_estimators( - estimators, r_packet, cur_line_id, distance_trace, - numba_model.time_explosion) - if ((distance_boundary <= distance_trace) and (distance_boundary <= distance_electron)): interaction_type = InteractionType.BOUNDARY # BOUNDARY @@ -215,6 +211,14 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): r_packet.next_line_id = cur_line_id break + # Updating the J_b_lu and E_dot_lu + # This means we are still looking for line interaction and have not + # been kicked out of the path by boundary or electron interaction + + update_line_estimators( + estimators, r_packet, cur_line_id, distance_trace, + numba_model.time_explosion) + if tau_trace_combined > tau_event: interaction_type = InteractionType.LINE # Line r_packet.next_line_id = cur_line_id @@ -227,6 +231,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): cur_electron_density, tau_event - tau_trace_line_combined) else: # Executed when no break occurs in the for loop + # We are beyond the line list now and the only next thing is to see + # if we are interacting with the boundary or electron scattering if cur_line_id == (len(numba_plasma.line_list_nu) - 1): # Treatment for last line cur_line_id += 1 From 7375efe5711b856b261b21ed6388ef9b3606c35d Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 15 Jan 2020 10:25:43 -0800 Subject: [PATCH 051/116] work on full relativity --- .../montecarlo_numba/numba_interface.py | 8 +- .../montecarlo/montecarlo_numba/r_packet.py | 108 +++++++++++++++--- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 56157b27196..82c7631c58e 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -1,6 +1,6 @@ from enum import IntEnum -from numba import float64, int64, jitclass +from numba import float64, int64, jitclass, boolean import numpy as np from tardis import constants as const @@ -152,17 +152,19 @@ def __init__(self, j_estimator, nu_bar_estimator, j_b_lu_estimator, monte_carlo_configuration_spec = [ ('line_interaction_type', int64), ('number_of_vpackets', int64), - ('temporary_v_packet_bins', int64) + ('temporary_v_packet_bins', int64), + ('full_relativity', boolean) ] @jitclass(monte_carlo_configuration_spec) class MonteCarloConfiguration(object): def __init__(self, number_of_vpackets, line_interaction_type, - temporary_v_packet_bins): + temporary_v_packet_bins, full_relativity=False): self.line_interaction_type = line_interaction_type self.number_of_vpackets = number_of_vpackets self.temporary_v_packet_bins = temporary_v_packet_bins + self.full_relativity = full_relativity def configuration_initialize(runner, number_of_vpackets, diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 6e6f25f433e..d9fa949096a 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -3,6 +3,7 @@ from numba import int64, float64 from numba import jitclass, njit + from tardis.montecarlo.montecarlo_numba import njit_dict from tardis import constants as const @@ -62,18 +63,48 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): return distance, delta_shell @njit(**njit_dict) -def calculate_distance_line(nu, comov_nu, nu_line, time_explosion): +def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion, + montecarlo_configuration): + """ + + Parameters + ---------- + r_packet + comov_nu + nu_line + time_explosion + montecarlo_configuration + + Returns + ------- + + """ + + nu = r_packet.nu + if nu_line == 0.0: return MISS_DISTANCE + nu_diff = comov_nu - nu_line if np.abs(nu_diff / comov_nu) < CLOSE_LINE_THRESHOLD: - nu_diff = 0.0 - if nu_diff >= 0: - return (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion - else: + nu_diff = 0.0 + if nu_diff <= 0: print('nu difference is less than 0.0', nu_diff, comov_nu, nu, nu_line, time_explosion) raise MonteCarloException('nu difference is less than 0.0') + if montecarlo_configuration.full_relativity: + nu_r = nu_line / nu + ct = C_SPEED_OF_LIGHT * time_explosion + distance = -r_packet.mu * r_packet.r + ( + ct - nu_r**2 * np.sqrt( + ct**2 - (1 + r_packet**2 * (1 - r_packet.mu**2) * + (1 + 1 / nu_r**2)))) / (1 + nu_r**3) + else: + distance = (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion + + return distance + + @njit(**njit_dict) def calculate_distance_electron(electron_density, tau_event): return tau_event / (electron_density * SIGMA_THOMSON) @@ -113,7 +144,20 @@ def initialize_line_id(self, numba_plasma, numba_model): @njit(**njit_dict) def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, - time_explosion): + time_explosion, montecarlo_configuration): + """ + Function to update the line estimators + + Parameters + ---------- + estimators + r_packet + cur_line_id + distance_trace + time_explosion + + """ + """ Actual calculation - simplified below r_interaction = np.sqrt(r_packet.r**2 + distance_trace**2 + 2 * r_packet.r * distance_trace * r_packet.mu) @@ -121,9 +165,14 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, doppler_factor = 1.0 - mu_interaction * r_interaction / ( time_explosion * C) """ - doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / - (time_explosion * C_SPEED_OF_LIGHT)) - energy = r_packet.energy * doppler_factor + + if not montecarlo_configuration.full_relativity: + doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / + (time_explosion * C_SPEED_OF_LIGHT)) + energy = r_packet.energy * doppler_factor + else: + # accurate to 1 / gamma - according to C. Vogl + energy = r_packet.energy estimators.j_b_lu_estimator[cur_line_id, r_packet.current_shell_id] += ( energy / r_packet.nu) @@ -131,14 +180,16 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, energy) @njit(**njit_dict) -def trace_packet(r_packet, numba_model, numba_plasma, estimators): +def trace_packet(r_packet, numba_model, numba_plasma, estimators, + montecarlo_configuration): """ Parameters ---------- numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma - estimators: tardis.motnecarlo.montecarlo_numba.numba_interface.Estimators + estimators: tardis.montecarlo.montecarlo_numba.numba_interface.Estimators + montecarlo_configuration: tardis.montecarlo.montecarlo_numba.numba_interface.MonteCarloConfiguration Returns ------- @@ -188,7 +239,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency distance_trace = calculate_distance_line( - r_packet.nu, comov_nu, nu_line, numba_model.time_explosion) + r_packet, comov_nu, nu_line, numba_model.time_explosion, + montecarlo_configuration) # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, @@ -217,7 +269,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): update_line_estimators( estimators, r_packet, cur_line_id, distance_trace, - numba_model.time_explosion) + numba_model.time_explosion, montecarlo_configuration) if tau_trace_combined > tau_event: interaction_type = InteractionType.LINE # Line @@ -249,11 +301,19 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): @njit(**njit_dict) -def move_r_packet(r_packet, distance, time_explosion, numba_estimator): +def move_r_packet(r_packet, distance, time_explosion, numba_estimator, + montecarlo_configuration): """Move packet a distance and recalculate the new angle mu Parameters ---------- + + r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket + r_packet objects + time_explosion: float + time since explosion in s + numba_estimator: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaEstimator + Estimators object distance : float distance in cm """ @@ -262,10 +322,18 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) comov_nu = r_packet.nu * doppler_factor comov_energy = r_packet.energy * doppler_factor - numba_estimator.j_estimator[r_packet.current_shell_id] += ( - comov_energy * distance) - numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( - comov_energy * distance * comov_nu) + + if montecarlo_configuration.full_relativity: + numba_estimator.j_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * doppler_factor) + numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * comov_nu * doppler_factor) + + else: + numba_estimator.j_estimator[r_packet.current_shell_id] += ( + comov_energy * distance) + numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * comov_nu) r = r_packet.r if (distance > 0.0): @@ -320,3 +388,7 @@ def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor r_packet.next_line_id = emission_line_id + 1 + +def angle_aberration_CMF_to_LF(r_packet, time_explosion): + beta = r_packet.r / (time_explosion * C_SPEED_OF_LIGHT) + return (r_packet.mu + beta) / (1.0 + beta * r_packet.mu) From df884c40cc53aacb7bd038d56423a65785d48402 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Wed, 13 May 2020 09:49:08 -0400 Subject: [PATCH 052/116] several changes --- tardis/montecarlo/montecarlo_numba/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index dcf2d32a827..a08d5eb735f 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -50,6 +50,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, delta_nu = spectrum_frequency[1] - spectrum_frequency[0] for i in prange(len(output_nus)): + np.random.seed(r_packet.seed) r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], packet_collection.packets_input_nu[i], From feee98472414c1a33485d7a492b0dc702c10658a Mon Sep 17 00:00:00 2001 From: Arjun Savel <35353555+arjunsavel@users.noreply.github.com> Date: Thu, 2 Jul 2020 07:50:28 -0700 Subject: [PATCH 053/116] Debugging relativity on numba_montecarlo (#1136) * removing kwarg from MonteCarloConfiguration, as jitclass does not support them * need to pass enable_full_relativity to configuration_initialize, as removed kwarg in MonteCarloConfiguration * move_r_packet and trace_packet both require montecarlo_configuration arg * r_packet object used in distance calculation in place of r_packet.r value * the full_relativity bool was being passed as a different kwarg * added montecarlo_configuration to args of trace_vpacket_volley and trace_vpacket, so that relativity attribute continues to get passed down * making sure the vpacket funcs that now require montecarlo_configuration get called with this arg * Added random seed; does not work with numba * Changed to numpy random seed * Commented out MonteCarloException block; will test how this impacts output * Need the MonteCarloException block to be run, otherwise large discrepancies between numba branch and master branch * Adding custom seeds to vpackets; few line adjustments * changed from njit to jit to ease debugging * More descriptive montecarloexception, with optional printing beforehand * Changed njit to jit in single_loop to ease debugging * Added single-packet debugging capabilities to MonteCarloRunner. * Removing plotting funcs from montecarlo * Added basic logging capabilities to montecarlo_numba * Generalized logger to *args * Tweaking configuration of logging in module * Logger can now handle kwargs * Renamed logger * Make logging more in line with other TARDIS loggers. * Share global variable DEBUG_MODE across modules with montecarlo init. * No longer calling removed plot_single_packet; also referring to __init__ DEBUG_MODE * Now referring to montecarlo __init__ DEBUG_MODE global * Remove dangling else statement * no longer need plotting in montecarlo_numba base.py * Remove import of function that no longer exists. * No longer holding global variables in montecarlo init * Importing mc_logger for global variables now * Moving log_decorator from base.py * Adding logger to new montecarlo_logger file * Add new logger to, remove print statements from r_packet.py * Ensure that DEBUG_MODE is referenced within the montecarlo_logger file * Allow the function being decorated to take its kwargs. * Apply wraps correctly * Make else block logic more apparent * Get rid of extra blank line * Got rid of a few todos * Add note about why decorator behavior will not change according to config * removing reference to logger in base * Added crude buffer * Increase buffer; check DEBUG_MODE checked during call, not init * Set default buffer in montecarlo logger to stated default * Allow specification of buffer in montecarlo yml * Make sure logger_buffer is passed through to montecarlo logger * Remove todo, question from log_decorator * Adding better docstrings to log_decorator * Add newline at end of montecarlo_logger file * Make logger config happen outside of decorator * Moved print statement to exception; * Added logging to file * No longer profiling calculate_distance_line * Add functionality to profile incomplete packet runs * Rename previous debug mode, as it was not for single packets * Added single packet seed to yml * Propagate single_packet_seed throughout configurations * Reference single packet seed from montecarloconfiguration * Add bool type to single packet seed type * Catch exception better, catch extra random seed * Set whole index, including energies, not just random seed * Change where loop is broken. * Move close_line_threshold check past montecarloexception * Add single-packet debug documentation and script * Add jitclass arg for single packet seed in montecarloconfiguration * Now the default for single_packet_seed cannot be a bool * Added new debug page to developer part of documentation * Now the single_packet_seed has to be not 1, not not False * Pause using log_decorator, remove try/except for nopython * Vpacket jitclass now allows for an int64 index * Exceptions args must be compile-time constants in nopython mode * Allow 0 nu_diff to not throw exception. * v_packets should have the same random seed as their r_packet * Fix MonteCarloException throw * Fix estimator typos * Make sure return is not made before relativity block * Rename esitmators to keep them consistent * Also rename the estimators in montecarlo.pyx * Rename estimators in numba_interface * Clean up estimator typos; change back to njit * Include Doppler factor in energy calculation for full_relativity * Add back not statement * Added relativistic Doppler factor * Add in Doppler factor relativity for interaction * Add angle aberration * add angle aberration to interactions * Update numba version * Remove relativity branching * Logger no longer prints to console if printing to file * Initialize configuration with external module, not jitclass * Remove extra repr of logger.handlers * Ensure the angle aberration is called on vpacket, remove jitclass reference * Convert montecarloconfiguration refs from jitclass to module * Add more references to global module, complete relativistic branching * Pass r_packet.mu to angle aberration calculation * Clarify angle aberration calc, include doppler factor to distance calc * Remove reference to MonteCarloConfiguration * Trying to delete C tests * Remove C montecarlo * Remove references to C module * Alter formal integral tests for python version * Rewrite formal integral in python --- docs/development/debug_numba.rst | 14 + docs/development/index.rst | 1 + tardis/io/schemas/montecarlo.yml | 13 + tardis/montecarlo/__init__.py | 2 +- tardis/montecarlo/base.py | 31 +- tardis/montecarlo/formal_integral.py | 322 +++- tardis/montecarlo/montecarlo.pyx | 378 ----- tardis/montecarlo/montecarlo_configuration.py | 5 + tardis/montecarlo/montecarlo_numba/base.py | 39 +- .../montecarlo_numba/interaction.py | 29 +- .../montecarlo_numba/montecarlo_logger.py | 67 + .../montecarlo_numba/numba_interface.py | 45 +- .../montecarlo/montecarlo_numba/r_packet.py | 223 ++- .../montecarlo_numba/single_packet_loop.py | 74 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 33 +- tardis/montecarlo/setup_package.py | 44 +- tardis/montecarlo/src/abbrev.h | 24 - tardis/montecarlo/src/cmontecarlo.c | 1291 ----------------- tardis/montecarlo/src/cmontecarlo.h | 159 -- tardis/montecarlo/src/integrator.c | 343 ----- tardis/montecarlo/src/integrator.h | 19 - tardis/montecarlo/src/io.h | 32 - tardis/montecarlo/src/omp_helper.h | 7 - tardis/montecarlo/src/randomkit/LICENSE | 20 - tardis/montecarlo/src/randomkit/randomkit.h | 42 - tardis/montecarlo/src/randomkit/rk_isaac.c | 258 ---- tardis/montecarlo/src/randomkit/rk_isaac.h | 142 -- tardis/montecarlo/src/randomkit/rk_mt.c | 342 ----- tardis/montecarlo/src/randomkit/rk_mt.h | 194 --- .../montecarlo/src/randomkit/rk_primitive.c | 520 ------- .../montecarlo/src/randomkit/rk_primitive.h | 54 - tardis/montecarlo/src/randomkit/rk_sobol.c | 991 ------------- tardis/montecarlo/src/randomkit/rk_sobol.h | 173 --- tardis/montecarlo/src/rpacket.c | 55 - tardis/montecarlo/src/rpacket.h | 330 ----- tardis/montecarlo/src/status.h | 38 - tardis/montecarlo/src/storage.h | 102 -- tardis/montecarlo/tests/conftest.py | 7 - .../montecarlo/tests/test_formal_integral.py | 73 +- tardis/montecarlo/tests/test_montecarlo.py | 0 tardis/scripts/debug/run_numba_single.py | 22 + tardis/scripts/debug/run_numba_single.run.xml | 25 + .../scripts/debug/tardis_example_single.yml | 45 + tardis/simulation/base.py | 4 +- tardis/tests/test_tardis_full.py | 2 +- .../tests/test_tardis_full_formal_integral.py | 2 +- tardis_env3.yml | 2 +- 47 files changed, 889 insertions(+), 5749 deletions(-) create mode 100644 docs/development/debug_numba.rst delete mode 100644 tardis/montecarlo/montecarlo.pyx create mode 100644 tardis/montecarlo/montecarlo_configuration.py create mode 100644 tardis/montecarlo/montecarlo_numba/montecarlo_logger.py delete mode 100644 tardis/montecarlo/src/abbrev.h delete mode 100644 tardis/montecarlo/src/cmontecarlo.c delete mode 100644 tardis/montecarlo/src/cmontecarlo.h delete mode 100644 tardis/montecarlo/src/integrator.c delete mode 100644 tardis/montecarlo/src/integrator.h delete mode 100644 tardis/montecarlo/src/io.h delete mode 100644 tardis/montecarlo/src/omp_helper.h delete mode 100644 tardis/montecarlo/src/randomkit/LICENSE delete mode 100644 tardis/montecarlo/src/randomkit/randomkit.h delete mode 100644 tardis/montecarlo/src/randomkit/rk_isaac.c delete mode 100644 tardis/montecarlo/src/randomkit/rk_isaac.h delete mode 100644 tardis/montecarlo/src/randomkit/rk_mt.c delete mode 100644 tardis/montecarlo/src/randomkit/rk_mt.h delete mode 100644 tardis/montecarlo/src/randomkit/rk_primitive.c delete mode 100644 tardis/montecarlo/src/randomkit/rk_primitive.h delete mode 100644 tardis/montecarlo/src/randomkit/rk_sobol.c delete mode 100644 tardis/montecarlo/src/randomkit/rk_sobol.h delete mode 100644 tardis/montecarlo/src/rpacket.c delete mode 100644 tardis/montecarlo/src/rpacket.h delete mode 100644 tardis/montecarlo/src/status.h delete mode 100644 tardis/montecarlo/src/storage.h create mode 100644 tardis/montecarlo/tests/test_montecarlo.py create mode 100644 tardis/scripts/debug/run_numba_single.py create mode 100644 tardis/scripts/debug/run_numba_single.run.xml create mode 100644 tardis/scripts/debug/tardis_example_single.yml diff --git a/docs/development/debug_numba.rst b/docs/development/debug_numba.rst new file mode 100644 index 00000000000..5cd8c31d9e5 --- /dev/null +++ b/docs/development/debug_numba.rst @@ -0,0 +1,14 @@ +************************** +Debugging numba_montecarlo +************************** +To facilitate more in-depth debugging when interfacing with the `montecarlo_numba` +module, we provide a set of debugging configurations. PyCharm debugging +configurations, in addition to related scripts and .yml files, are contained in +`tardis.scripts.debug`. Currently, these include the ability to run TARDIS +in asingle-packet mode, with the packet seed identified at debug time. +`tardis_example_single.yml` is the configuration filethat is used to set up the +single-packet TARDIS run; `run_numba_single.py` is thePython script that runs +this .yml file; `run_numba_single.xml` is the PyCharmdebug configuration file +that can be used in conjunction with the above files. + + diff --git a/docs/development/index.rst b/docs/development/index.rst index e4fe42e68e9..03c4a9b2e2a 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -13,6 +13,7 @@ to the Astropy team for designing it. running_tests issues + debug_numba diff --git a/tardis/io/schemas/montecarlo.yml b/tardis/io/schemas/montecarlo.yml index b0638270e6c..2c32e9598cb 100644 --- a/tardis/io/schemas/montecarlo.yml +++ b/tardis/io/schemas/montecarlo.yml @@ -68,6 +68,19 @@ properties: default: false description: Enables a more complete treatment of relativitic effects. This includes angle aberration as well as use of the fully general Doppler formula. + debug_packets: + type: boolean + default: false + description: Decide whether to go into debugging mode. + logger_buffer: + type: number + default: 1 + description: Provides option to not log every line. + single_packet_seed: + type: + - number + default: -1 + description: If debug_packets is true, this is the seed for the only packet. required: - no_of_packets diff --git a/tardis/montecarlo/__init__.py b/tardis/montecarlo/__init__.py index 222ee95b0b8..d8d3283c243 100644 --- a/tardis/montecarlo/__init__.py +++ b/tardis/montecarlo/__init__.py @@ -1 +1 @@ -from tardis.montecarlo.base import * +from tardis.montecarlo.base import * \ No newline at end of file diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 5665778ae05..507f61aeebb 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -13,10 +13,13 @@ from tardis.util.base import quantity_linspace from tardis.io.util import HDFWriterMixin -from tardis.montecarlo import montecarlo, packet_source as source +from tardis.montecarlo import packet_source as source from tardis.montecarlo.formal_integral import FormalIntegrator +from tardis.montecarlo import montecarlo_configuration as mc_config_module + from tardis.montecarlo.montecarlo_numba import montecarlo_radial1d +from tardis.montecarlo.montecarlo_numba import montecarlo_logger as mc_logger from tardis.montecarlo.montecarlo_numba.numba_interface import ( configuration_initialize) @@ -62,7 +65,8 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, enable_full_relativity, inner_boundary_albedo, line_interaction_type, integrator_settings, v_packet_settings, spectrum_method, - packet_source=None): + packet_source=None, debug_packets=False, + logger_buffer=1, single_packet_seed=None): self.seed = seed if packet_source is None: @@ -77,11 +81,17 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, self.inner_boundary_albedo = inner_boundary_albedo self.enable_full_relativity = enable_full_relativity self.line_interaction_type = line_interaction_type + self.single_packet_seed = single_packet_seed self.integrator_settings = integrator_settings self.v_packet_settings = v_packet_settings self.spectrum_method = spectrum_method self._integrator = None self._spectrum_integrated = None + + # set up logger based on config + mc_logger.DEBUG_MODE = debug_packets + mc_logger.BUFFER = logger_buffer + if self.spectrum_method == 'integrated': self.optional_hdf_properties.append('spectrum_integrated') @@ -100,8 +110,9 @@ def _initialize_estimator_arrays(self, tau_sobolev_shape): # Estimators self.j_estimator = np.zeros(tau_sobolev_shape[1], dtype=np.float64) self.nu_bar_estimator = np.zeros(tau_sobolev_shape[1], dtype=np.float64) - self.j_b_lu_estimator = np.zeros(tau_sobolev_shape) - self.edot_lu_estimator = np.zeros(tau_sobolev_shape) + self.j_blue_estimator = np.zeros(tau_sobolev_shape) + self.Edotlu_estimator = np.zeros(tau_sobolev_shape) + # TODO: this is the wrong attribute naming style. def _initialize_geometry_arrays(self, model): """ @@ -227,10 +238,9 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets) - montecarlo_configuration = configuration_initialize( - self, no_of_virtual_packets) - - montecarlo_radial1d(model, plasma, self, montecarlo_configuration) + montecarlo_configuration = configuration_initialize(self, + no_of_virtual_packets) + montecarlo_radial1d(model, plasma, self) #montecarlo.montecarlo_radial1d( # model, plasma, self, # virtual_packet_flag=no_of_virtual_packets, @@ -450,4 +460,7 @@ def from_config(cls, config, packet_source=None): integrator_settings=config.spectrum.integrated, v_packet_settings=config.spectrum.virtual, spectrum_method=config.spectrum.method, - packet_source=packet_source) + packet_source=packet_source, + debug_packets=config.montecarlo.debug_packets, + logger_buffer=config.montecarlo.logger_buffer, + single_packet_seed=config.montecarlo.single_packet_seed) diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index 2d973c44603..872e0410e2c 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -5,14 +5,30 @@ from scipy.interpolate import interp1d from astropy import units as u from tardis import constants as const +from numba import jitclass, njit -from tardis.montecarlo.montecarlo import formal_integral + +from tardis.montecarlo.montecarlo_numba import njit_dict + +# from tardis.montecarlo.montecarlo import formal_integral from tardis.montecarlo.spectrum import TARDISSpectrum +C_INV = 3.33564e-11 +M_PI = np.arccos(-1) +KB_CGS = 1.3806488e-16 +H_CGS = 6.62606957e-27 class IntegrationError(Exception): pass +# integrator_spec = [ +# ('model', float64), +# ('plasma', float64), +# ('runner', float64), +# ('points', int64) +# ] +# +# @jitclass(integrator_spec) class FormalIntegrator(object): def __init__(self, model, plasma, runner, points=1000): @@ -122,7 +138,7 @@ def make_source_function(self): Edotlu_norm_factor = (1 / (runner.time_of_simulation * model.volume)) exptau = 1 - np.exp(- plasma.tau_sobolevs) - Edotlu = Edotlu_norm_factor * exptau * runner.edot_lu_estimator + Edotlu = Edotlu_norm_factor * exptau * runner.Edotlu_estimator # The following may be achieved by calling the appropriate plasma # functions @@ -131,7 +147,7 @@ def make_source_function(self): model.volume)).to("1/(cm^2 s)").value # Jbluelu should already by in the correct order, i.e. by wavelength of # the transition l->u - Jbluelu = runner.j_b_lu_estimator * Jbluelu_norm_factor + Jbluelu = runner.j_blue_estimator * Jbluelu_norm_factor upper_level_index = atomic_data.lines.index.droplevel('level_number_lower') e_dot_lu = pd.DataFrame(Edotlu, index=upper_level_index) @@ -219,3 +235,303 @@ def interpolate_integrator_quantities(self, att_S_ul, Jredlu, Jredlu = Jredlu.clip(0.) e_dot_u = e_dot_u.clip(0.) return att_S_ul, Jredlu, Jbluelu, e_dot_u + + def formal_integral(self, nu, N): + # TODO: get rid of storage later on + + + res = self.make_source_function() + + + + + + att_S_ul = res[0].flatten(order='F') + Jred_lu = res[1].flatten(order='F') + Jblue_lu = res[2].flatten(order='F') + L = self._formal_integral( + self.model.t_inner.value, + nu, + nu.shape[0], + att_S_ul, + Jred_lu, + Jblue_lu, + N + ) + return np.array(L, np.NPY_DOUBLE, nu.shape[0]) + + + + def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): + # todo: add all the original todos + # Initialize the output which is shared among threads + L = np.zeros(inu_size) + # global read-only values + size_line = self.model.no_of_lines # check + size_shell = self.model.no_of_shells_i # check + size_tau = size_line * size_shell + finished_nus = 0 + + R_ph = self.runner.r_inner_i[0] + R_max = self.runner.r_outer_i[size_shell - 1] + pp = np.zeros(N) # check + exp_tau = np.zeros(size_tau) + # TODO: multiprocessing + offset = 0 + i = 0 + size_z = 0 + idx_nu_start = 0 + direction = 0 + first = 0 + I_nu = np.zeros(N) + shell_id = np.zeros(2 * self.runner.no_of_shells_i) # check + # instantiate more variables here, maybe? + + # prepare exp_tau + for i in range(size_tau): + exp_tau[i] = np.exp(-self.model.line_lists_tau_sobolevs_i[i]) # check + pp = self.calculate_p_values(R_max, N, pp) + + # done with instantiation + # now loop over wavelength in spectrum + for nu_idx in range(inu_size): + nu = inu[nu_idx] + # now loop over discrete values along line + for p_idx in range(1, N): + escat_contrib = 0 + p = pp[p_idx] + + # initialize z intersections for p values + size_z = self.populate_z(p, z, shell_id) # check returns + + # initialize I_nu + if p <= R_ph: + I_nu[p_idx] = intensity_black_body(nu * z[0], iT) + else: + I_nu[p_idx] = 0 + + # find first contributing lines + nu_start = nu * z[0] + nu_end = nu * z[1] + self.line_search(nu_start, size_line, idx_nu_start) + offset = shell_id[0] * size_line + + # start tracking accumulated e-scattering optical depth + zstart = self.model.time_explosion / C_INV * (1. - z[0]) + + # Initialize "pointers" + pline = self.runner.line_list_nu + idx_nu_start; + pexp_tau = exp_tau + offset + idx_nu_start; + patt_S_ul = att_S_ul + offset + idx_nu_start; + pJred_lu = Jred_lu + offset + idx_nu_start; + pJblue_lu = Jblue_lu + offset + idx_nu_start; + + # flag for first contribution to integration on current p-ray + first = 1 + + # loop over all interactions + for i in range(size_z - 1): + escat_op = self.model.electron_densities_i[shell_id[i]] * sigma_thomson # change sigma_thomson + nu_end = nu * z[i + 1] + while pline < self.model.ine_list_nu + size_line: # check ;pline + # increment all pointers simulatenously + pline += 1 + pexp_tau += 1 + patt_S_ul += 1 + pJblue_lu += 1 + + if (pline[0] < nu_end): + break + + # calculate e-scattering optical depth to next resonance point + zend = self.model.time_explosion / C_INV * (1. - pline / nu) # check + + if first == 1: + # first contribution to integration + # NOTE: this treatment of I_nu_b (given + # by boundary conditions) is not in Lucy 1999; + # should be re-examined carefully + escat_contrib += (zend - zstart) * escat_op * ( + pJblue_lu - I_nu[p_idx]); + first = 0; + else: + # Account for e-scattering, c.f. Eqs 27, 28 in Lucy 1999 + Jkkp = 0.5 * (pJred_lu + pJblue_lu); + escat_contrib += (zend - zstart) * escat_op * ( + Jkkp - I_nu[p_idx]) + # this introduces the necessary ffset of one element between + # pJblue_lu and pJred_lu + pJred_lu += 1 + I_nu[p_idx] = I_nu[p_idx] + escat_contrib; + # // Lucy 1999, Eq 26 + I_nu[p_idx] = I_nu[p_idx] * (pexp_tau) + patt_S_ul # check about taking about asterisks beforehand elsewhere + + # // reset e-scattering opacity + escat_contrib = 0 + zstart = zend + # calculate e-scattering optical depth to grid cell boundary + + Jkkp = 0.5 * (pJred_lu + pJblue_lu) + zend = self.model.time_explosion / C_INV * (1. - nu_end / nu) # check + escat_contrib += (zend - zstart) * escat_op * ( + Jkkp - I_nu[p_idx]) + zstart = zend + + if i < size_z - 1: + # advance pointers + direction = shell_id[i+1] - shell_id[i] + pexp_tau += direction * size_line + patt_S_ul += direction * size_line + pJred_lu += direction * size_line + pJblue_lu += direction * size_line + I_nu[p_idx] *= p; + L[nu_idx] = 8 * M_PI * M_PI * trapezoid_integration(I_nu, R_max / N, + N) + # something pragma op atomic + return L + + + # @njit(**njit_dict) + def populate_z(self, p, oz, oshell_id): + """Calculate p line intersections + + This function calculates the intersection points of the p-line with + each shell + + Inputs: + :p: (double) distance of the integration line to the center + :oz: (array of doubles) will be set with z values. the array is truncated + by the value `1`. + :oshell_id: (int64) will be set with the corresponding shell_ids + """ + # abbreviations + r = self.model.r_outer_i + N = self.model.no_of_shells_i # check + print(N) + inv_t = 1/self.model.time_explosion + z = 0 + offset = N + + if p <= self.model.r_inner_i[0]: + # intersect the photosphere + for i in range(N): + oz[i] = 1 - self.calculate_z(r[i], p, inv_t) + oshell_id[i] = i + return N + else: + # no intersection with photosphere + # that means we intersect each shell twice + for i in range(N): + z = self.calculate_z(r[i], p, inv_t) + if z == 0: + continue + if offset == N: + offset = i + # calculate the index in the resulting array + i_low = N - i - 1 # the far intersection with the shell + i_up = N + i - 2 * offset # the nearer intersection with the shell + + # setting the arrays; check return them? + oz[i_low] = 1 + z + oshell_id[i_low] = i + oz[i_up] = 1 - z + oshell_id[i_up] = i + return 2 * (N - offset) + + # @njit(**njit_dict) + def calculate_z(self, r, p, inv_t): + """ Calculate distance to p line + + Calculate half of the length of the p-line inside a shell + of radius r in terms of unit length (c * t_exp). + If shell and p-line do not intersect, return 0. + + Inputs: + :r: (double) radius of the shell + :p: (double) distance of the p-line to the center of the supernova + :inv_t: (double) inverse time_explosio is needed to norm to unit-length + """ + if r > p: + return np.sqrt(r * r - p * p) * C_INV * inv_t + else: + return 0 + + @njit(**njit_dict) + def line_search(self, nu, nu_insert, number_of_lines, result): + """ + Insert a value in to an array of line frequencies + + Inputs: + :nu: (array) line frequencies + :nu_insert: (int) value of nu key + :number_of_lines: (int) number of lines in the line list + + Outputs: + index of the next line ot the red. + If the key value is redder + than the reddest line returns number_of_lines. + """ + # TODO: fix the TARDIS_ERROR_OK + # tardis_error_t ret_val = TARDIS_ERROR_OK # check + imin = 0 + imax = number_of_lines - 1 + if nu_insert > nu[imin]: + result = imin + elif nu_insert < nu[imax]: + result = imax + 1 + else: + ret_val = self.reverse_binary_search(nu, nu_insert, imin, imax, result) + result = result + 1 + return ret_val + + @njit(**njit_dict) + def reverse_binary_search(self, x, x_insert, imin, imax, result): + """Look for a place to insert a value in an inversely sorted float array. + + Inputs: + :x: (array) an inversely (largest to lowest) sorted float array + :x_insert: (value) a value to insert + :imin: (int) lower bound + :imax: (int) upper bound + + Outputs: + index of the next boundary to the left + """ + # ret_val = TARDIS_ERROR_OK # check + if x_insert > x[imin] or x_insert < x[imax]: + ret_val = TARDIS_ERROR_BOUNDS_ERROR # check + else: + imid = (imin + imax) >> 1 + while imax - imin > 2: + if (x[imid] < x_insert): + imax = imid + 1 + else: + imin = imid + imid = (imin + imax) >> 1 + if (imax - imin == 2 and x_insert < x[imin + 1]): + result = imin + 1 + else: + result = imin + return ret_val + +@njit(**njit_dict) +def trapezoid_integration(array, h, N): + # TODO: replace with np.trapz? + result = (array[0] + array[N - 1]) / 2; + for idx in range(1, N-1): + result += array[idx] + return result * h + +@njit +def intensity_black_body(nu, T): + if nu == 0: + return np.nan # to avoid ZeroDivisionError + beta_rad = 1 / (KB_CGS * T) + coefficient = 2 * H_CGS * C_INV * C_INV + return coefficient * nu * nu * nu / (np.exp(H_CGS * nu * beta_rad) - 1) + +@njit(**njit_dict) +def calculate_p_values(R_max, N, opp): + for i in range(N): + opp[i] = R_max / (N - 1) * (i) + return opp \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo.pyx b/tardis/montecarlo/montecarlo.pyx deleted file mode 100644 index d2d2def18bb..00000000000 --- a/tardis/montecarlo/montecarlo.pyx +++ /dev/null @@ -1,378 +0,0 @@ -# cython: profile=False -# cython: boundscheck=False -# cython: wraparound=False -# cython: cdivision=True -# cython: cdivision=True -# cython: language_level=3 - - - -import numpy as np -cimport numpy as np -from numpy cimport PyArray_DATA -from tardis import constants -from astropy import units -from libc.stdlib cimport free - -np.import_array() - - - -ctypedef np.int64_t int_type_t - -cdef extern from "numpy/arrayobject.h": - void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) - -cdef c_array_to_numpy(void *ptr, int dtype, np.npy_intp N): - cdef np.ndarray arr = np.PyArray_SimpleNewFromData(1, &N, dtype, ptr) - PyArray_ENABLEFLAGS(arr, np.NPY_OWNDATA) - return arr - -cdef extern from "src/cmontecarlo.h": - ctypedef enum ContinuumProcessesStatus: - CONTINUUM_OFF = 0 - CONTINUUM_ON = 1 - - cdef int LOG_VPACKETS - - ctypedef struct photo_xsect_1level: - double *nu - double *x_sect - int_type_t no_of_points - - ctypedef struct storage_model_t: - double *packet_nus - double *packet_mus - double *packet_energies - double *output_nus - double *output_energies - double *last_interaction_in_nu - int_type_t *last_line_interaction_in_id - int_type_t *last_line_interaction_out_id - int_type_t *last_line_interaction_shell_id - int_type_t *last_interaction_type - int_type_t *last_interaction_out_type - int_type_t no_of_packets - int_type_t no_of_shells - int_type_t no_of_shells_i - double *r_inner - double *r_outer - double *r_inner_i - double *r_outer_i - double *v_inner - double time_explosion - double inverse_time_explosion - double *electron_densities - double *electron_densities_i - double *inverse_electron_densities - double *line_list_nu - double *line_lists_tau_sobolevs - double *line_lists_tau_sobolevs_i - double *continuum_list_nu - int_type_t line_lists_tau_sobolevs_nd - double *line_lists_j_blues - double *line_lists_Edotlu - int_type_t line_lists_j_blues_nd - int_type_t no_of_lines - int_type_t no_of_edges - int_type_t line_interaction_id - double *transition_probabilities - int_type_t transition_probabilities_nd - int_type_t *line2macro_level_upper - int_type_t *macro_block_references - int_type_t *transition_type - int_type_t *destination_level_id - int_type_t *transition_line_id - double *js - double *nubars - double spectrum_virt_start_nu - double spectrum_virt_end_nu - double spectrum_start_nu - double spectrum_delta_nu - double spectrum_end_nu - double *spectrum_virt_nu - double sigma_thomson - double inverse_sigma_thomson - double inner_boundary_albedo - int_type_t reflective_inner_boundary - photo_xsect_1level ** photo_xsect - double *chi_ff_factor - double *t_electrons - double *l_pop - double *l_pop_r - ContinuumProcessesStatus cont_status - double *virt_packet_nus - double *virt_packet_energies - double *virt_packet_last_interaction_in_nu - int_type_t *virt_packet_last_interaction_type - int_type_t *virt_packet_last_line_interaction_in_id - int_type_t *virt_packet_last_line_interaction_out_id - int_type_t virt_packet_count - int_type_t virt_array_size - int_type_t kpacket2macro_level - int_type_t *cont_edge2macro_level - double *photo_ion_estimator - double *stim_recomb_estimator - int_type_t *photo_ion_estimator_statistics - double *bf_heating_estimator - double *ff_heating_estimator - double *stim_recomb_cooling_estimator - int full_relativity - double survival_probability - double tau_russian - double *tau_bias - int enable_biasing - - void montecarlo_main_loop(storage_model_t * storage, int_type_t virtual_packet_flag, int nthreads, unsigned long seed) - -cdef extern from "src/integrator.h": - double *_formal_integral( - const storage_model_t *storage, - double T, - double *nu, - int_type_t nu_size, - double *att_S_ul, - double *Jred_lu, - double *Jblue_lu, - int N) - - - -cdef initialize_storage_model(model, plasma, runner, storage_model_t *storage): - """ - - Parameters - ---------- - model - plasma - runner: tardis.montecarlo.base.MontecarloRunner - storage - - Returns - ------- - - """ - storage.no_of_packets = runner.input_nu.size - storage.packet_nus = PyArray_DATA(runner.input_nu) - storage.packet_mus = PyArray_DATA(runner.input_mu) - storage.packet_energies = PyArray_DATA(runner.input_energy) - - # Setup of structure - storage.no_of_shells = model.no_of_shells - - - storage.r_inner = PyArray_DATA(runner.r_inner_cgs) - storage.r_outer = PyArray_DATA(runner.r_outer_cgs) - storage.v_inner = PyArray_DATA(runner.v_inner_cgs) - - # Setup the rest - # times - storage.time_explosion = model.time_explosion.to('s').value - storage.inverse_time_explosion = 1.0 / storage.time_explosion - #electron density - storage.electron_densities = PyArray_DATA( - plasma.electron_densities.values) - - runner.inverse_electron_densities = ( - 1.0 / plasma.electron_densities.values) - storage.inverse_electron_densities = PyArray_DATA( - runner.inverse_electron_densities) - # Switch for continuum processes - storage.cont_status = CONTINUUM_OFF - # Continuum data - cdef np.ndarray[double, ndim=1] continuum_list_nu - cdef np.ndarray[double, ndim=1] l_pop - cdef np.ndarray[double, ndim=1] l_pop_r - - if storage.cont_status == CONTINUUM_ON: - continuum_list_nu = np.array([9.0e14, 8.223e14, 6.0e14, 3.5e14, 3.0e14]) # sorted list of threshold frequencies - storage.continuum_list_nu = continuum_list_nu.data - storage.no_of_edges = continuum_list_nu.size - l_pop = np.ones(storage.no_of_shells * continuum_list_nu.size, dtype=np.float64) - storage.l_pop = l_pop.data - l_pop_r = np.ones(storage.no_of_shells * continuum_list_nu.size, dtype=np.float64) - storage.l_pop_r = l_pop_r.data - - # Line lists - storage.no_of_lines = plasma.atomic_data.lines.nu.values.size - storage.line_list_nu = PyArray_DATA(plasma.atomic_data.lines.nu.values) - runner.line_lists_tau_sobolevs = ( - plasma.tau_sobolevs.values.flatten(order='F') - ) - storage.line_lists_tau_sobolevs = PyArray_DATA( - runner.line_lists_tau_sobolevs - ) - storage.line_lists_j_blues = PyArray_DATA( - runner.j_b_lu_estimator) - - storage.line_lists_Edotlu = PyArray_DATA( - runner.edot_lu_estimator) - - storage.line_interaction_id = runner.get_line_interaction_id( - runner.line_interaction_type) - - # macro atom & downbranch - if storage.line_interaction_id >= 1: - runner.transition_probabilities = ( - plasma.transition_probabilities.values.flatten(order='F') - ) - storage.transition_probabilities = PyArray_DATA( - runner.transition_probabilities - ) - storage.transition_probabilities_nd = ( - plasma.transition_probabilities.values.shape[0]) - storage.line2macro_level_upper = PyArray_DATA( - plasma.atomic_data.lines_upper2macro_reference_idx) - storage.macro_block_references = PyArray_DATA( - plasma.atomic_data.macro_atom_references['block_references'].values) - storage.transition_type = PyArray_DATA( - plasma.atomic_data.macro_atom_data['transition_type'].values) - - # Destination level is not needed and/or generated for downbranch - storage.destination_level_id = PyArray_DATA( - plasma.atomic_data.macro_atom_data['destination_level_idx'].values) - storage.transition_line_id = PyArray_DATA( - plasma.atomic_data.macro_atom_data['lines_idx'].values) - - storage.output_nus = PyArray_DATA(runner._output_nu) - storage.output_energies = PyArray_DATA(runner._output_energy) - - storage.last_line_interaction_in_id = PyArray_DATA( - runner.last_line_interaction_in_id) - storage.last_line_interaction_out_id = PyArray_DATA( - runner.last_line_interaction_out_id) - storage.last_line_interaction_shell_id = PyArray_DATA( - runner.last_line_interaction_shell_id) - storage.last_interaction_type = PyArray_DATA( - runner.last_interaction_type) - storage.last_interaction_in_nu = PyArray_DATA( - runner.last_interaction_in_nu) - - storage.js = PyArray_DATA(runner.j_estimator) - storage.nubars = PyArray_DATA(runner.nu_bar_estimator) - - storage.spectrum_start_nu = runner.spectrum_frequency.to('Hz').value.min() - storage.spectrum_end_nu = runner.spectrum_frequency.to('Hz').value.max() - # TODO: Linspace handling for virtual_spectrum_range - storage.spectrum_virt_start_nu = runner.virtual_spectrum_range.stop.to('Hz', units.spectral()).value - storage.spectrum_virt_end_nu = runner.virtual_spectrum_range.start.to('Hz', units.spectral()).value - storage.spectrum_delta_nu = runner.spectrum_frequency.to('Hz').value[1] - runner.spectrum_frequency.to('Hz').value[0] - - storage.spectrum_virt_nu = PyArray_DATA( - runner._montecarlo_virtual_luminosity.value) - - storage.sigma_thomson = runner.sigma_thomson.cgs.value - storage.inverse_sigma_thomson = 1.0 / storage.sigma_thomson - storage.reflective_inner_boundary = runner.enable_reflective_inner_boundary - storage.inner_boundary_albedo = runner.inner_boundary_albedo - storage.full_relativity = runner.enable_full_relativity - - storage.tau_russian = runner.v_packet_settings['tau_russian'] - storage.survival_probability = runner.v_packet_settings['survival_probability'] - storage.enable_biasing = runner.v_packet_settings['enable_biasing'] - - if runner.v_packet_settings['enable_biasing']: - # Calculate the integrated electron scattering optical depth - # at all cell interfaces. - runner.tau_bias = np.zeros(len(runner.r_inner_cgs) + 1) - runner.tau_bias[:-1] = ( - ((runner.r_outer_cgs - runner.r_inner_cgs) * - plasma.electron_densities.values * - runner.sigma_thomson.cgs.value)[::-1].cumsum()[::-1] - ) - storage.tau_bias = PyArray_DATA(runner.tau_bias) - - # Data for continuum implementation - cdef np.ndarray[double, ndim=1] t_electrons = plasma.t_electrons - storage.t_electrons = t_electrons.data - -def montecarlo_radial1d(model, plasma, runner, int_type_t virtual_packet_flag=0, - int nthreads=4,last_run=False): - """ - Parameters - ---------- - model : `tardis.model_radial_oned.ModelRadial1D` - complete model - param photon_packets : PacketSource object - photon packets - - Returns - ------- - output_nus : `numpy.ndarray` - output_energies : `numpy.ndarray` - - TODO - np.ndarray[double, ndim=1] line_list_nu, - np.ndarray[double, ndim=2] tau_lines, - np.ndarray[double, ndim=1] ne, - double packet_energy, - np.ndarray[double, ndim=2] p_transition, - np.ndarray[int_type_t, ndim=1] type_transition, - np.ndarray[int_type_t, ndim=1] target_level_id, - np.ndarray[int_type_t, ndim=1] target_line_id, - np.ndarray[int_type_t, ndim=1] unroll_reference, - np.ndarray[int_type_t, ndim=1] line2level, - int_type_t log_packets, - int_type_t do_scatter - """ - - cdef storage_model_t storage - - initialize_storage_model(model, plasma, runner, &storage) - - montecarlo_main_loop(&storage, virtual_packet_flag, nthreads, runner.seed) - runner.virt_logging = LOG_VPACKETS - if LOG_VPACKETS != 0: - runner.virt_packet_nus = c_array_to_numpy(storage.virt_packet_nus, np.NPY_DOUBLE, storage.virt_packet_count) - runner.virt_packet_energies = c_array_to_numpy(storage.virt_packet_energies, np.NPY_DOUBLE, storage.virt_packet_count) - runner.virt_packet_last_interaction_in_nu = c_array_to_numpy(storage.virt_packet_last_interaction_in_nu, np.NPY_DOUBLE, storage.virt_packet_count) - runner.virt_packet_last_interaction_type = c_array_to_numpy(storage.virt_packet_last_interaction_type, np.NPY_INT64, storage.virt_packet_count) - runner.virt_packet_last_line_interaction_in_id = c_array_to_numpy(storage.virt_packet_last_line_interaction_in_id, np.NPY_INT64, - storage.virt_packet_count) - runner.virt_packet_last_line_interaction_out_id = c_array_to_numpy(storage.virt_packet_last_line_interaction_out_id, np.NPY_INT64, - storage.virt_packet_count) - else: - runner.virt_packet_nus = np.zeros(0) - runner.virt_packet_energies = np.zeros(0) - runner.virt_packet_last_interaction_in_nu = np.zeros(0) - runner.virt_packet_last_interaction_type = np.zeros(0) - runner.virt_packet_last_line_interaction_in_id = np.zeros(0) - runner.virt_packet_last_line_interaction_out_id = np.zeros(0) - - -# This will be a method of the Simulation object -def formal_integral(self, nu, N): - cdef storage_model_t storage - - initialize_storage_model(self.model, self.plasma, self.runner, &storage) - - res = self.make_source_function() - - storage.no_of_shells_i = len(self.runner.r_inner_i) - storage.r_inner_i = PyArray_DATA(self.runner.r_inner_i) - storage.r_outer_i = PyArray_DATA(self.runner.r_outer_i) - - storage.electron_densities_i = PyArray_DATA( - self.runner.electron_densities_integ) - self.runner.line_lists_tau_sobolevs_i = ( - self.runner.tau_sobolevs_integ.flatten(order='F') - ) - storage.line_lists_tau_sobolevs_i = PyArray_DATA( - self.runner.line_lists_tau_sobolevs_i - ) - - att_S_ul = res[0].flatten(order='F') - Jred_lu = res[1].flatten(order='F') - Jblue_lu = res[2].flatten(order='F') - - cdef double *L = _formal_integral( - &storage, - self.model.t_inner.value, - PyArray_DATA(nu), - nu.shape[0], - PyArray_DATA(att_S_ul), - PyArray_DATA(Jred_lu), - PyArray_DATA(Jblue_lu), - N - ) - return c_array_to_numpy(L, np.NPY_DOUBLE, nu.shape[0]) diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py new file mode 100644 index 00000000000..f81b6e9b186 --- /dev/null +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -0,0 +1,5 @@ +full_relativity = True +single_packet_seed = -1 +temporary_v_packet_bins = None +number_of_vpackets = 0 +line_interaction_type = None \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a08d5eb735f..fe5ad56cb22 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -1,16 +1,21 @@ -from numba import prange, njit +from numba import prange, njit, jit +import logging import numpy as np -from tardis.montecarlo.montecarlo_numba.r_packet import RPacket, PacketStatus + +from tardis.montecarlo.montecarlo_numba.r_packet import ( + RPacket, PacketStatus, MonteCarloException) from tardis.montecarlo.montecarlo_numba.numba_interface import ( PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, - Estimators, MonteCarloConfiguration, configuration_initialize) + Estimators, configuration_initialize) + +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( single_packet_loop) from tardis.montecarlo.montecarlo_numba import njit_dict -def montecarlo_radial1d(model, plasma, runner, montecarlo_configuration): +def montecarlo_radial1d(model, plasma, runner): packet_collection = PacketCollection( runner.input_nu, runner.input_mu, runner.input_energy, runner._output_nu, runner._output_energy @@ -20,19 +25,18 @@ def montecarlo_radial1d(model, plasma, runner, montecarlo_configuration): model.time_explosion.to('s').value) numba_plasma = numba_plasma_initialize(plasma) estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator, - runner.j_b_lu_estimator, runner.edot_lu_estimator) + runner.j_blue_estimator, runner.Edotlu_estimator) v_packets_energy_hist = montecarlo_main_loop( packet_collection, numba_model, numba_plasma, estimators, - runner.spectrum_frequency.value, montecarlo_configuration) + runner.spectrum_frequency.value) runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, - estimators, spectrum_frequency, - montecarlo_configuration): + estimators, spectrum_frequency): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -50,18 +54,23 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, delta_nu = spectrum_frequency[1] - spectrum_frequency[0] for i in prange(len(output_nus)): - np.random.seed(r_packet.seed) + if montecarlo_configuration.single_packet_seed != -1: + i = montecarlo_configuration.single_packet_seed r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], packet_collection.packets_input_nu[i], packet_collection.packets_input_energy[i], i) - + + # We want to set the seed correctly per user; otherwise, random. + np.random.seed(i) vpacket_collection = VPacketCollection( spectrum_frequency, montecarlo_configuration.number_of_vpackets, montecarlo_configuration.temporary_v_packet_bins) - single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection, montecarlo_configuration) + loop = single_packet_loop(r_packet, numba_model, numba_plasma, estimators, + vpacket_collection) + # if loop and 'stop' in loop: + # raise MonteCarloException output_nus[i] = r_packet.nu @@ -75,6 +84,9 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_idx = np.floor((vpackets_nu - spectrum_frequency[0]) / delta_nu).astype(np.int64) + # if we're only in a single-packet mode + if montecarlo_configuration.single_packet_seed != -1: + break for j, idx in enumerate(v_packets_idx): if ((vpackets_nu[j] < spectrum_frequency[0]) or (vpackets_nu[j] > spectrum_frequency[-1])): @@ -84,5 +96,4 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] - return v_packets_energy_hist - + return v_packets_energy_hist \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index fda2c077135..482ec7564a6 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -3,9 +3,10 @@ from tardis.montecarlo.montecarlo_numba.numba_interface import ( LineInteractionType) - +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration from tardis.montecarlo.montecarlo_numba.r_packet import ( - get_doppler_factor, get_random_mu) + get_doppler_factor, get_inverse_doppler_factor, get_random_mu, + angle_aberration_CMF_to_LF) from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom @@ -24,14 +25,24 @@ def general_scatter(r_packet, time_explosion): distance : [type] [description] """ - old_doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) + old_doppler_factor = get_doppler_factor( + r_packet.r, + r_packet.mu, + time_explosion) comov_energy = r_packet.energy * old_doppler_factor comov_nu = r_packet.nu * old_doppler_factor r_packet.mu = get_random_mu() - inverse_new_doppler_factor = 1. / get_doppler_factor( + inverse_new_doppler_factor = get_inverse_doppler_factor( r_packet.r, r_packet.mu, time_explosion) r_packet.energy = comov_energy * inverse_new_doppler_factor r_packet.nu = comov_nu * inverse_new_doppler_factor + if montecarlo_configuration.full_relativity: + r_packet.mu = angle_aberration_CMF_to_LF( + r_packet, + time_explosion, + r_packet.mu + ) + """ void @@ -68,7 +79,8 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): # update last_interaction if line_interaction_type == LineInteractionType.SCATTER: - line_emission(r_packet, r_packet.next_line_id, time_explosion, numba_plasma) + line_emission(r_packet, r_packet.next_line_id, + time_explosion, numba_plasma) else: # includes both macro atom and downbranch - encoded in the transition probabilities emission_line_id = macro_atom(r_packet, numba_plasma) line_emission(r_packet, emission_line_id, time_explosion, @@ -84,6 +96,13 @@ def line_emission(r_packet, emission_line_id, time_explosion, r_packet.nu = numba_plasma.line_list_nu[ emission_line_id] / doppler_factor r_packet.next_line_id = emission_line_id + 1 + if montecarlo_configuration.full_relativity: + r_packet.mu = angle_aberration_CMF_to_LF( + r_packet, + time_explosion, + r_packet.mu + ) + diff --git a/tardis/montecarlo/montecarlo_numba/montecarlo_logger.py b/tardis/montecarlo/montecarlo_numba/montecarlo_logger.py new file mode 100644 index 00000000000..d1b5722dbbd --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/montecarlo_logger.py @@ -0,0 +1,67 @@ +import logging +from functools import wraps + +DEBUG_MODE = False +LOG_FILE = 'montecarlo_log.log' +BUFFER = 1 +ticker = 1 + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) +logger.handlers = [] + +if LOG_FILE: + logger.propagate = False + console_handler = logging.FileHandler(LOG_FILE) +else: + console_handler = logging.StreamHandler() + +console_handler.setLevel(logging.DEBUG) +console_formatter = logging.Formatter( + '%(name)s - %(levelname)s - %(message)s') +console_handler.setFormatter(console_formatter) +logger.addHandler(console_handler) + +def log_decorator(func): + """ + Decorator to log functions while in debug mode, i.e., when + `debug_montecarlo` is True in the config. Works for + `@jit'd and `@njit`'d functions, but with a significant speed + penalty. + + TODO: in nopython mode: do I need a context manager? + + Input: + func : (function) function to be logged. + + Output: + wrapper : (function) wrapper to the function being logged. + """ + + # to ensure that calling `help` on the decorated function still works + # @wraps(func) + def wrapper(*args, **kwargs): + """ + Wrapper to the log_decorator. + + When called, it has the side effect of + logging every `BUFFER` input and output to `func`, if `DEBUG_MODE` is + `True`. + + Input: + *args : arguments to be passed to `func`. + **kwargs : keyword arguments to be passed to `func`. + + Output: + result : result of calling `func` on `*args` and `**kwargs`. + """ + result = func(*args, **kwargs) + if DEBUG_MODE: + global ticker + ticker += 1 + if ticker % BUFFER == 0: # without a buffer, performance suffers + logger.debug(f'Func: {func.__name__}. Input: {(args, kwargs)}') + logger.debug(f'Output: {result}.') + return result + + return wrapper diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 82c7631c58e..d52fb377deb 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -5,6 +5,9 @@ from tardis import constants as const +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration + + C_SPEED_OF_LIGHT = const.c.to('cm/s').value @@ -136,52 +139,36 @@ def __init__(self, spectrum_frequency, number_of_vpackets, estimators_spec = [ ('j_estimator', float64[:]), ('nu_bar_estimator', float64[:]), - ('j_b_lu_estimator', float64[:, :]), - ('edot_lu_estimator', float64[:, :]) + ('j_blue_estimator', float64[:, :]), + ('Edotlu_estimator', float64[:, :]) ] @jitclass(estimators_spec) class Estimators(object): - def __init__(self, j_estimator, nu_bar_estimator, j_b_lu_estimator, - edot_lu_estimator): + def __init__(self, j_estimator, nu_bar_estimator, j_blue_estimator, + Edotlu_estimator): self.j_estimator = j_estimator self.nu_bar_estimator = nu_bar_estimator - self.j_b_lu_estimator = j_b_lu_estimator - self.edot_lu_estimator = edot_lu_estimator - -monte_carlo_configuration_spec = [ - ('line_interaction_type', int64), - ('number_of_vpackets', int64), - ('temporary_v_packet_bins', int64), - ('full_relativity', boolean) -] - - -@jitclass(monte_carlo_configuration_spec) -class MonteCarloConfiguration(object): - def __init__(self, number_of_vpackets, line_interaction_type, - temporary_v_packet_bins, full_relativity=False): - self.line_interaction_type = line_interaction_type - self.number_of_vpackets = number_of_vpackets - self.temporary_v_packet_bins = temporary_v_packet_bins - self.full_relativity = full_relativity + self.j_blue_estimator = j_blue_estimator + self.Edotlu_estimator = Edotlu_estimator def configuration_initialize(runner, number_of_vpackets, temporary_v_packet_bins=20000): if runner.line_interaction_type == 'macroatom': - line_interaction_type = LineInteractionType.MACROATOM + montecarlo_configuration.line_interaction_type = LineInteractionType.MACROATOM elif runner.line_interaction_type == 'downbranch': - line_interaction_type = LineInteractionType.DOWNBRANCH + montecarlo_configuration.line_interaction_type = LineInteractionType.DOWNBRANCH elif runner.line_interaction_type == 'scatter': - line_interaction_type = LineInteractionType.SCATTER + montecarlo_configuration.line_interaction_type = LineInteractionType.SCATTER else: raise ValueError(f'Line interaction type must be one of "macroatom",' f'"downbranch", or "scatter" but is ' f'{runner.line_interaction_type}') - - return MonteCarloConfiguration(number_of_vpackets, line_interaction_type, - temporary_v_packet_bins) + montecarlo_configuration.number_of_vpackets = number_of_vpackets + montecarlo_configuration.temporary_v_packet_bins = temporary_v_packet_bins + montecarlo_configuration.full_relativity = runner.enable_full_relativity + montecarlo_configuration.single_packet_seed = runner.single_packet_seed #class TrackRPacket(object): diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index d9fa949096a..70fcb0bed50 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -5,6 +5,8 @@ from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration +from tardis.montecarlo.montecarlo_numba.montecarlo_logger import log_decorator from tardis import constants as const class MonteCarloException(ValueError): @@ -62,9 +64,10 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): return distance, delta_shell + +# @log_decorator @njit(**njit_dict) -def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion, - montecarlo_configuration): +def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion): """ Parameters @@ -84,27 +87,37 @@ def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion, if nu_line == 0.0: return MISS_DISTANCE - nu_diff = comov_nu - nu_line + + # for numerical reasons, if line is too close, we set the distance to 0. if np.abs(nu_diff / comov_nu) < CLOSE_LINE_THRESHOLD: nu_diff = 0.0 - if nu_diff <= 0: - print('nu difference is less than 0.0', nu_diff, comov_nu, nu, nu_line, time_explosion) - raise MonteCarloException('nu difference is less than 0.0') - if montecarlo_configuration.full_relativity: - nu_r = nu_line / nu - ct = C_SPEED_OF_LIGHT * time_explosion - distance = -r_packet.mu * r_packet.r + ( - ct - nu_r**2 * np.sqrt( - ct**2 - (1 + r_packet**2 * (1 - r_packet.mu**2) * - (1 + 1 / nu_r**2)))) / (1 + nu_r**3) - else: + if nu_diff >= 0: distance = (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion + else: + raise MonteCarloException('nu difference is less than 0.0; for more' + ' information, see print statement beforehand') + if montecarlo_configuration.full_relativity: + return calculate_distance_line_full_relativity(nu_line, nu, + time_explosion, + r_packet) return distance +@njit(**njit_dict) +def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, + r_packet): + # distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - (1 + r * r * (1 - mu * mu) * (1 + pow(nu_r, -2))))) / (1 + nu_r * nu_r); + nu_r = nu_line / nu + ct = C_SPEED_OF_LIGHT * time_explosion + distance = -r_packet.mu * r_packet.r + ( + ct - nu_r ** 2 * np.sqrt( + ct ** 2 - (1 + r_packet.r ** 2 * (1 - r_packet.mu ** 2) * + (1 + pow(nu_r, -2))))) / (1 + nu_r ** 2) + return distance + @njit(**njit_dict) def calculate_distance_electron(electron_density, tau_event): return tau_event / (electron_density * SIGMA_THOMSON) @@ -115,9 +128,37 @@ def calculate_tau_electron(electron_density, distance): @njit(**njit_dict) def get_doppler_factor(r, mu, time_explosion): - beta = (r / time_explosion) / C_SPEED_OF_LIGHT + beta = r / (time_explosion * C_SPEED_OF_LIGHT) + if not montecarlo_configuration.full_relativity: + return get_doppler_factor_partial_relativity(mu, beta) + else: + return get_doppler_factor_full_relativity(mu, beta) + +@njit(**njit_dict) +def get_doppler_factor_partial_relativity(mu, beta): return 1.0 - mu * beta +@njit(**njit_dict) +def get_doppler_factor_full_relativity(mu, beta): + return (1.0 - mu * beta) / np.sqrt(1 - beta * beta) + + +@njit(**njit_dict) +def get_inverse_doppler_factor(r, mu, time_explosion): + beta = (r / time_explosion) / C_SPEED_OF_LIGHT + if not montecarlo_configuration.full_relativity: + return get_inverse_doppler_factor_partial_relativity(mu, beta) + else: + return get_inverse_doppler_factor_full_relativity(mu, beta) + +@njit(**njit_dict) +def get_inverse_doppler_factor_partial_relativity(mu, beta): + return 1.0 / (1.0 - mu * beta) + +@njit(**njit_dict) +def get_inverse_doppler_factor_full_relativity(mu, beta): + return (1.0 + mu * beta) / np.sqrt(1 - beta * beta) + @njit(**njit_dict) def get_random_mu(): return 2.0 * np.random.random() - 1.0 @@ -144,7 +185,7 @@ def initialize_line_id(self, numba_plasma, numba_model): @njit(**njit_dict) def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, - time_explosion, montecarlo_configuration): + time_explosion): """ Function to update the line estimators @@ -167,21 +208,30 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, """ if not montecarlo_configuration.full_relativity: - doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / - (time_explosion * C_SPEED_OF_LIGHT)) - energy = r_packet.energy * doppler_factor + energy = calc_packet_energy(r_packet, distance_trace, + time_explosion) else: - # accurate to 1 / gamma - according to C. Vogl - energy = r_packet.energy + energy = calc_packet_energy_full_relativity(r_packet) - estimators.j_b_lu_estimator[cur_line_id, r_packet.current_shell_id] += ( + estimators.j_blue_estimator[cur_line_id, r_packet.current_shell_id] += ( energy / r_packet.nu) - estimators.edot_lu_estimator[cur_line_id, r_packet.current_shell_id] += ( + estimators.Edotlu_estimator[cur_line_id, r_packet.current_shell_id] += ( energy) @njit(**njit_dict) -def trace_packet(r_packet, numba_model, numba_plasma, estimators, - montecarlo_configuration): +def calc_packet_energy_full_relativity(r_packet): + # accurate to 1 / gamma - according to C. Vogl + return r_packet.energy + +@njit(**njit_dict) +def calc_packet_energy(r_packet, distance_trace, time_explosion): + doppler_factor = 1.0 - ((distance_trace + r_packet.mu * r_packet.r) / + (time_explosion * C_SPEED_OF_LIGHT)) + energy = r_packet.energy * doppler_factor + return energy + +@njit(**njit_dict) +def trace_packet(r_packet, numba_model, numba_plasma, estimators): """ Parameters @@ -189,7 +239,6 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel numba_plasma: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaPlasma estimators: tardis.montecarlo.montecarlo_numba.numba_interface.Estimators - montecarlo_configuration: tardis.montecarlo.montecarlo_numba.numba_interface.MonteCarloConfiguration Returns ------- @@ -239,8 +288,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency distance_trace = calculate_distance_line( - r_packet, comov_nu, nu_line, numba_model.time_explosion, - montecarlo_configuration) + r_packet, comov_nu, nu_line, numba_model.time_explosion) # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, @@ -269,7 +317,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, update_line_estimators( estimators, r_packet, cur_line_id, distance_trace, - numba_model.time_explosion, montecarlo_configuration) + numba_model.time_explosion) if tau_trace_combined > tau_event: interaction_type = InteractionType.LINE # Line @@ -301,8 +349,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, @njit(**njit_dict) -def move_r_packet(r_packet, distance, time_explosion, numba_estimator, - montecarlo_configuration): +def move_r_packet(r_packet, distance, time_explosion, numba_estimator): """Move packet a distance and recalculate the new angle mu Parameters @@ -318,23 +365,26 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator, distance in cm """ - - doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) + doppler_factor = get_doppler_factor(r_packet.r, + r_packet.mu, + time_explosion) comov_nu = r_packet.nu * doppler_factor comov_energy = r_packet.energy * doppler_factor if montecarlo_configuration.full_relativity: - numba_estimator.j_estimator[r_packet.current_shell_id] += ( - comov_energy * distance * doppler_factor) - numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( - comov_energy * distance * comov_nu * doppler_factor) - + distance = distance * doppler_factor + set_estimators_full_relativity(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy, + doppler_factor) else: - numba_estimator.j_estimator[r_packet.current_shell_id] += ( - comov_energy * distance) - numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( - comov_energy * distance * comov_nu) - + set_estimators(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy) r = r_packet.r if (distance > 0.0): new_r = np.sqrt(r**2 + distance**2 + @@ -342,6 +392,52 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator, r_packet.mu = (r_packet.mu * r + distance) / new_r r_packet.r = new_r +@njit(**njit_dict) +def set_estimators(r_packet, distance, numba_estimator, comov_nu, comov_energy): + numba_estimator.j_estimator[r_packet.current_shell_id] += ( + comov_energy * distance) + numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * comov_nu) + +@njit(**njit_dict) +def set_estimators_full_relativity(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy, + doppler_factor): + numba_estimator.j_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * doppler_factor) + numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( + comov_energy * distance * comov_nu * doppler_factor) + +@njit(**njit_dict) +def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): + """ + + Parameters + ---------- + r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket + emission_line_id: int + numba_plasma + time_explosion + + Returns + ------- + + """ + doppler_factor = get_doppler_factor(r_packet.r, + r_packet.mu, + time_explosion,) + r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor + r_packet.next_line_id = emission_line_id + 1 + if montecarlo_configuration.full_relativity: + r_packet.mu = angle_aberration_CMF_to_LF( + r_packet, + time_explosion + ) + + @njit(**njit_dict) def move_packet_across_shell_boundary(packet, delta_shell, no_of_shells): @@ -349,12 +445,12 @@ def move_packet_across_shell_boundary(packet, delta_shell, Move packet across shell boundary - realizing if we are still in the simulation or have moved out through the inner boundary or outer boundary and updating packet status. - + Parameters ---------- distance : float distance to move to shell boundary - + delta_shell: int is +1 if moving outward or -1 if moving inward @@ -370,25 +466,24 @@ def move_packet_across_shell_boundary(packet, delta_shell, else: packet.current_shell_id = next_shell_id - -def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): +@njit(**njit_dict) +def angle_aberration_CMF_to_LF(r_packet, time_explosion, mu): """ - - Parameters - ---------- - r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket - emission_line_id: int - numba_plasma - time_explosion - - Returns - ------- - + Converts angle aberration from comoving frame to + laboratory frame. """ - doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) - r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor - r_packet.next_line_id = emission_line_id + 1 + ct = C_SPEED_OF_LIGHT * time_explosion + beta = r_packet.r / (ct) + return (r_packet.mu + beta) / (1.0 + beta * mu) -def angle_aberration_CMF_to_LF(r_packet, time_explosion): - beta = r_packet.r / (time_explosion * C_SPEED_OF_LIGHT) - return (r_packet.mu + beta) / (1.0 + beta * r_packet.mu) +@njit(**njit_dict) +def angle_aberration_LF_to_CMF(r_packet, time_explosion, mu): + """ + + c code: + double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; + return (mu - beta) / (1.0 - beta * mu); + """ + ct = C_SPEED_OF_LIGHT * time_explosion + beta = r_packet.r /(ct) + return (mu - beta) / (1.0 - beta * mu) diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index 4955f306c39..e0c0a4b1996 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -2,20 +2,29 @@ import numpy as np from tardis.montecarlo.montecarlo_numba.r_packet import ( - InteractionType, PacketStatus, get_doppler_factor, trace_packet, - move_packet_across_shell_boundary, move_r_packet) + InteractionType, PacketStatus, get_inverse_doppler_factor, trace_packet, + move_packet_across_shell_boundary, move_r_packet, + MonteCarloException) from tardis.montecarlo.montecarlo_numba.interaction import ( general_scatter, line_scatter) from tardis.montecarlo.montecarlo_numba.numba_interface import \ LineInteractionType +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration from tardis.montecarlo.montecarlo_numba.vpacket import trace_vpacket_volley +from tardis import constants as const +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + +from tardis.montecarlo.montecarlo_numba.montecarlo_logger import log_decorator +from tardis.montecarlo.montecarlo_numba import ( + montecarlo_logger as mc_logger) + +# @log_decorator @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection, montecarlo_configuration, - track_rpackets=False): + vpacket_collection): """ Parameters @@ -33,21 +42,20 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, This function does not return anything but changes the r_packet object and if virtual packets are requested - also updates the vpacket_collection """ - - np.random.seed(r_packet.index) + flag = None line_interaction_type = montecarlo_configuration.line_interaction_type - doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, - numba_model.time_explosion) - r_packet.nu /= doppler_factor - r_packet.energy /= doppler_factor + if montecarlo_configuration.full_relativity: + set_packet_props_full_relativity(r_packet, numba_model) + else: + set_packet_props_partial_relativity(r_packet, numba_model) r_packet.initialize_line_id(numba_plasma, numba_model) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) - if track_rpackets: + if mc_logger.DEBUG_MODE: r_packet_track_nu = [r_packet.nu] r_packet_track_mu = [r_packet.mu] r_packet_track_r = [r_packet.r] @@ -55,8 +63,12 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet_track_distance = [0.] while r_packet.status == PacketStatus.IN_PROCESS: + # try: distance, interaction_type, delta_shell = trace_packet( - r_packet, numba_model, numba_plasma, estimators=estimators) + r_packet, numba_model, numba_plasma, estimators) + # except MonteCarloException: + # flag = 'stop' + # break if interaction_type == InteractionType.BOUNDARY: move_r_packet(r_packet, distance, numba_model.time_explosion, @@ -69,9 +81,13 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, estimators) line_scatter(r_packet, numba_model.time_explosion, line_interaction_type, numba_plasma) - + # try: trace_vpacket_volley( - r_packet, vpacket_collection, numba_model, numba_plasma) + r_packet, vpacket_collection, numba_model, numba_plasma, + ) + # except MonteCarloException: + # flag = 'stop' + # break elif interaction_type == InteractionType.ESCATTERING: move_r_packet(r_packet, distance, numba_model.time_explosion, @@ -80,8 +96,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma) - - if track_rpackets: + if mc_logger.DEBUG_MODE: r_packet_track_nu.append(r_packet.nu) r_packet_track_mu.append(r_packet.mu) r_packet_track_r.append(r_packet.r) @@ -89,7 +104,30 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet_track_distance.append(distance) - if track_rpackets is True: + if mc_logger.DEBUG_MODE: return (r_packet_track_nu, r_packet_track_mu, r_packet_track_r, - r_packet_track_interaction, r_packet_track_distance) + r_packet_track_interaction, r_packet_track_distance, flag) + + # check where else initialize line ID happens! + +@njit +def set_packet_props_partial_relativity(r_packet, numba_model): + inverse_doppler_factor = get_inverse_doppler_factor(r_packet.r, r_packet.mu, + numba_model.time_explosion, + ) + r_packet.nu *= inverse_doppler_factor + r_packet.energy *= inverse_doppler_factor + +@njit +def set_packet_props_full_relativity(r_packet, numba_model): + beta = (r_packet.r / numba_model.time_explosion) / C_SPEED_OF_LIGHT + + inverse_doppler_factor = get_inverse_doppler_factor(r_packet.r, r_packet.mu, + numba_model.time_explosion, + ) + + r_packet.nu *= inverse_doppler_factor + r_packet.energy *= inverse_doppler_factor + r_packet.mu = (r_packet.mu + beta) / (1 + beta * r_packet.mu) + diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 08f78a0ba68..9eb3efaa94b 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -2,12 +2,14 @@ from numba import jitclass, njit, gdb from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration import numpy as np from tardis.montecarlo.montecarlo_numba.r_packet import ( calculate_distance_boundary, get_doppler_factor, calculate_distance_line, - calculate_tau_electron, PacketStatus, move_packet_across_shell_boundary) + calculate_tau_electron, PacketStatus, move_packet_across_shell_boundary, + angle_aberration_LF_to_CMF, angle_aberration_CMF_to_LF) vpacket_spec = [ ('r', float64), @@ -16,12 +18,14 @@ ('energy', float64), ('next_line_id', int64), ('current_shell_id', int64), - ('status', int64) + ('status', int64), + ('index', int64) ] @jitclass(vpacket_spec) class VPacket(object): - def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id): + def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, + index=0): self.r = r self.mu = mu self.nu = nu @@ -29,6 +33,7 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id): self.current_shell_id = current_shell_id self.next_line_id = next_line_id self.status = PacketStatus.IN_PROCESS + self.index = index @@ -66,7 +71,7 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): cur_line_id, v_packet.current_shell_id] distance_trace_line = calculate_distance_line( - v_packet.nu, comov_nu, nu_line, numba_model.time_explosion) + v_packet, comov_nu, nu_line, numba_model.time_explosion) if distance_boundary <= distance_trace_line: break @@ -118,7 +123,8 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): return tau_trace_combined @njit(**njit_dict) -def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma): +def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, + numba_plasma): """ Shoot a volley of vpackets (the vpacket collection specifies how many) from the current position of the rpacket. @@ -140,7 +146,6 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma return - no_of_vpackets = vpacket_collection.number_of_vpackets if no_of_vpackets == 0: @@ -150,6 +155,10 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary mu_min = -np.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) v_packet_on_inner_boundary = False + if montecarlo_configuration.full_relativity: + mu_min = angle_aberration_LF_to_CMF(r_packet, + numba_model.time_explosion, + mu_min) else: v_packet_on_inner_boundary = True mu_min = 0.0 @@ -165,10 +174,18 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma else: weight = (1 - mu_min) / (2 * no_of_vpackets) + # C code: next line, angle_aberration_CMF_to_LF( & virt_packet, storage); + if montecarlo_configuration.full_relativity: + v_packet_mu = angle_aberration_CMF_to_LF( + r_packet, + numba_model.time_explosion, + v_packet_mu + ) v_packet_doppler_factor = get_doppler_factor( r_packet.r, v_packet_mu, numba_model.time_explosion) # transform between r_packet mu and v_packet_mu + doppler_factor_ratio = ( r_packet_doppler_factor / v_packet_doppler_factor) @@ -177,8 +194,8 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, numba_plasma v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, v_packet_energy, r_packet.current_shell_id, - r_packet.next_line_id) - + r_packet.next_line_id, i) + tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) v_packet.energy *= np.exp(-tau_vpacket) diff --git a/tardis/montecarlo/setup_package.py b/tardis/montecarlo/setup_package.py index 44dcc1913f9..2e0a5a107f9 100644 --- a/tardis/montecarlo/setup_package.py +++ b/tardis/montecarlo/setup_package.py @@ -3,7 +3,7 @@ from setuptools import Extension import os from astropy_helpers.distutils_helpers import get_distutils_option -from Cython.Build import cythonize +# from Cython.Build import cythonize import yaml @@ -30,27 +30,27 @@ yaml.dump(vpacket_config, open(vpacket_config_file_path, "w"), default_flow_style=False) -def get_extensions(): - sources = ['tardis/montecarlo/montecarlo.pyx'] - sources += [os.path.relpath(fname) for fname in glob( - os.path.join(os.path.dirname(__file__), 'src', '*.c'))] - sources += [os.path.relpath(fname) for fname in glob( - os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.c'))] - deps = [os.path.relpath(fname) for fname in glob( - os.path.join(os.path.dirname(__file__), 'src', '*.h'))] - deps += [os.path.relpath(fname) for fname in glob( - os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.h'))] - - return cythonize( - Extension('tardis.montecarlo.montecarlo', sources, - include_dirs=['tardis/montecarlo/src', - 'tardis/montecarlo/src/randomkit', - 'numpy'], - depends=deps, - extra_compile_args=compile_args, - extra_link_args=link_args, - define_macros=define_macros) - ) +# def get_extensions(): +# sources = ['tardis/montecarlo/montecarlo.pyx'] +# sources += [os.path.relpath(fname) for fname in glob( +# os.path.join(os.path.dirname(__file__), 'src', '*.c'))] +# sources += [os.path.relpath(fname) for fname in glob( +# os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.c'))] +# deps = [os.path.relpath(fname) for fname in glob( +# os.path.join(os.path.dirname(__file__), 'src', '*.h'))] +# deps += [os.path.relpath(fname) for fname in glob( +# os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.h'))] + + # return cythonize( + # Extension('tardis.montecarlo.montecarlo', sources, + # include_dirs=['tardis/montecarlo/src', + # 'tardis/montecarlo/src/randomkit', + # 'numpy'], + # depends=deps, + # extra_compile_args=compile_args, + # extra_link_args=link_args, + # define_macros=define_macros) + # ) def get_package_data(): diff --git a/tardis/montecarlo/src/abbrev.h b/tardis/montecarlo/src/abbrev.h deleted file mode 100644 index fff7d07036c..00000000000 --- a/tardis/montecarlo/src/abbrev.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ABBREV_H -#define ABBREV_H - -#include - -/** - * @brief safe malloc; checks for NULL and aborts if encountered - */ -static inline void* safe_malloc(size_t size) { - void *mem = malloc(size); - if (mem == NULL && size != 0) abort(); - return mem; -} - -/** - * @brief safe realloc; checks for NULL and aborts if encountered - */ -static inline void* safe_realloc(void *ptr, size_t size) { - void *mem = realloc(ptr, size); - if (mem == NULL && size != 0) abort(); - return mem; -} - -#endif /* ABBREV_H */ diff --git a/tardis/montecarlo/src/cmontecarlo.c b/tardis/montecarlo/src/cmontecarlo.c deleted file mode 100644 index 41441d8d363..00000000000 --- a/tardis/montecarlo/src/cmontecarlo.c +++ /dev/null @@ -1,1291 +0,0 @@ - -#include -#include -#include -#include -#ifdef WITHOPENMP -#include -#endif -#include "io.h" -#include "abbrev.h" -#include "status.h" -#include "rpacket.h" -#include "cmontecarlo.h" - - -/** Look for a place to insert a value in an inversely sorted float array. - * - * @param x an inversely (largest to lowest) sorted float array - * @param x_insert a value to insert - * @param imin lower bound - * @param imax upper bound - * - * @return index of the next boundary to the left - */ -tardis_error_t -reverse_binary_search (const double *x, double x_insert, - int64_t imin, int64_t imax, int64_t * result) -{ - /* - Have in mind that *x points to a reverse sorted array. - That is large values will have small indices and small ones - will have large indices. - */ - tardis_error_t ret_val = TARDIS_ERROR_OK; - if (x_insert > x[imin] || x_insert < x[imax]) - { - ret_val = TARDIS_ERROR_BOUNDS_ERROR; - } - else - { - int imid = (imin + imax) >> 1; - while (imax - imin > 2) - { - if (x[imid] < x_insert) - { - imax = imid + 1; - } - else - { - imin = imid; - } - imid = (imin + imax) >> 1; - } - if (imax - imin == 2 && x_insert < x[imin + 1]) - { - *result = imin + 1; - } - else - { - *result = imin; - } - } - return ret_val; -} - -/** Insert a value in to an array of line frequencies - * - * @param nu array of line frequencies - * @param nu_insert value of nu key - * @param number_of_lines number of lines in the line list - * - * @return index of the next line ot the red. If the key value is redder than the reddest line returns number_of_lines. - */ -tardis_error_t -line_search (const double *nu, double nu_insert, int64_t number_of_lines, - int64_t * result) -{ - tardis_error_t ret_val = TARDIS_ERROR_OK; - int64_t imin = 0; - int64_t imax = number_of_lines - 1; - if (nu_insert > nu[imin]) - { - *result = imin; - } - else if (nu_insert < nu[imax]) - { - *result = imax + 1; - } - else - { - ret_val = reverse_binary_search (nu, nu_insert, imin, imax, result); - *result = *result + 1; - } - return ret_val; -} - -tardis_error_t -binary_search (const double *x, double x_insert, int64_t imin, - int64_t imax, int64_t * result) -{ - /* - Have in mind that *x points to a sorted array. - Like [1,2,3,4,5,...] - */ - int imid; - tardis_error_t ret_val = TARDIS_ERROR_OK; - if (x_insert < x[imin] || x_insert > x[imax]) - { - ret_val = TARDIS_ERROR_BOUNDS_ERROR; - } - else - { - while (imax >= imin) - { - imid = (imin + imax) / 2; - if (x[imid] == x_insert) - { - *result = imid; - break; - } - else if (x[imid] < x_insert) - { - imin = imid + 1; - } - else - { - imax = imid - 1; - } - } - if (imax - imid == 2 && x_insert < x[imin + 1]) - { - *result = imin; - } - else - { - *result = imin; - } - } - return ret_val; -} - -void -angle_aberration_CMF_to_LF (rpacket_t *packet, const storage_model_t *storage) -{ - if (storage->full_relativity) - { - double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; - double mu_0 = rpacket_get_mu (packet); - rpacket_set_mu (packet, (mu_0 + beta) / (1.0 + beta * mu_0)); - } -} - -/** Transform the lab frame direction cosine to the CMF - * - * @param packet - * @param storage - * @param mu lab frame direction cosine - * - * @return CMF direction cosine - */ -double -angle_aberration_LF_to_CMF (rpacket_t *packet, const storage_model_t *storage, double mu) -{ - double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; - return (mu - beta) / (1.0 - beta * mu); -} - -double -rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) -{ - double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; - if (!storage->full_relativity) - { - return 1.0 - rpacket_get_mu (packet) * beta; - } - else - { - return (1.0 - rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); - } -} - -double -rpacket_inverse_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) -{ - double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; - if (!storage->full_relativity) - { - return 1.0 / (1.0 - rpacket_get_mu (packet) * beta); - } - else - { - return (1.0 + rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); - } -} - -double -bf_cross_section (const storage_model_t * storage, int64_t continuum_id, double comov_nu) -{ - double bf_xsect; - double *x_sect = storage->photo_xsect[continuum_id]->x_sect; - double *nu = storage->photo_xsect[continuum_id]->nu; - - switch (storage->bf_treatment) - { - case LIN_INTERPOLATION: - { - int64_t result; - tardis_error_t error = binary_search (nu, comov_nu, 0, - storage->photo_xsect[continuum_id]->no_of_points - 1, &result); - if (error == TARDIS_ERROR_BOUNDS_ERROR) - { - bf_xsect = 0.0; - } - else - { - bf_xsect = x_sect[result-1] + (comov_nu - nu[result-1]) / (nu[result] - nu[result-1]) - * (x_sect[result] - x_sect[result-1]); - } - break; - } - - case HYDROGENIC: - { - double nu_ratio = nu[0] / comov_nu; - bf_xsect = x_sect[0] * nu_ratio * nu_ratio * nu_ratio; - break; - } - - default: - fprintf (stderr, "(%d) is not a valid bound-free cross section treatment.\n", storage->bf_treatment); - exit(1); - } - return bf_xsect; -} - -void calculate_chi_bf (rpacket_t * packet, storage_model_t * storage) -{ - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - - int64_t no_of_continuum_edges = storage->no_of_edges; - int64_t current_continuum_id; - line_search(storage->continuum_list_nu, comov_nu, no_of_continuum_edges, ¤t_continuum_id); - rpacket_set_current_continuum_id (packet, current_continuum_id); - - int64_t shell_id = rpacket_get_current_shell_id (packet); - double T = storage->t_electrons[shell_id]; - double boltzmann_factor = exp (-(H * comov_nu) / (KB * T)); - - double bf_helper = 0; - for(int64_t i = current_continuum_id; i < no_of_continuum_edges; i++) - { - // get the level population for the level ijk in the current shell: - double l_pop = storage->l_pop[shell_id * no_of_continuum_edges + i]; - // get the level population ratio \frac{n_{0,j+1,k}}{n_{i,j,k}} \frac{n_{i,j,k}}{n_{0,j+1,k}}^{*}: - double l_pop_r = storage->l_pop_r[shell_id * no_of_continuum_edges + i]; - double bf_x_sect = bf_cross_section (storage, i, comov_nu); - if (bf_x_sect == 0.0) - { - break; - } - bf_helper += l_pop * bf_x_sect * (1.0 - l_pop_r * boltzmann_factor) * doppler_factor; - - packet->chi_bf_tmp_partial[i] = bf_helper; - } - - rpacket_set_chi_boundfree (packet, bf_helper); -} - -void calculate_chi_ff (rpacket_t * packet, const storage_model_t * storage) -{ - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - int64_t shell_id = rpacket_get_current_shell_id (packet); - double T = storage->t_electrons[shell_id]; - double boltzmann_factor = exp (-(H * comov_nu) / KB / T); - double chi_ff_factor = storage->chi_ff_factor[shell_id]; - - double chi_ff = chi_ff_factor * (1 - boltzmann_factor) * pow (comov_nu, -3); - - rpacket_set_chi_freefree (packet, chi_ff * doppler_factor); -} - -void -compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) -{ - double r = rpacket_get_r (packet); - double mu = rpacket_get_mu (packet); - double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; - double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; - double check, distance; - if (mu > 0.0) - { // direction outward - rpacket_set_next_shell_id (packet, 1); - distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); - } - else - { // going inward - if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) - { // hit inner boundary - rpacket_set_next_shell_id (packet, -1); - distance = - r * mu - sqrt (check); - } - else - { // miss inner boundary - rpacket_set_next_shell_id (packet, 1); - distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); - } - } - rpacket_set_d_boundary (packet, distance); -} - -tardis_error_t -compute_distance2line (rpacket_t * packet, const storage_model_t * storage) -{ - if (!rpacket_get_last_line (packet)) - { - double r = rpacket_get_r (packet); - double mu = rpacket_get_mu (packet); - double nu = rpacket_get_nu (packet); - double nu_line = rpacket_get_nu_line (packet); - double distance, nu_diff; - double ct = storage->time_explosion * C; - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = nu * doppler_factor; - if ( (nu_diff = comov_nu - nu_line) >= 0) - { - if (!storage->full_relativity) - { - distance = (nu_diff / nu) * ct; - } - else - { - double nu_r = nu_line / nu; - distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - - (1 + r * r * (1 - mu * mu) * (1 + pow (nu_r, -2))))) / (1 + nu_r * nu_r); - } - rpacket_set_d_line (packet, distance); - return TARDIS_ERROR_OK; - } - else - { - if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) - { - fprintf (stderr, "last_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) - 1]); - fprintf (stderr, "Last line in line list reached!"); - } - else if (rpacket_get_next_line_id (packet) == 0) - { - fprintf (stderr, "First line in line list!"); - fprintf (stderr, "next_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) + 1]); - } - else - { - fprintf (stderr, "last_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) - 1]); - fprintf (stderr, "next_line = %f\n", - storage-> - line_list_nu[rpacket_get_next_line_id (packet) + 1]); - } - fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); - fprintf (stderr, "comov_nu = %f\n", comov_nu); - fprintf (stderr, "nu_line = %f\n", nu_line); - fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", - (comov_nu - nu_line) / nu_line); - fprintf (stderr, "r = %f\n", r); - fprintf (stderr, "mu = %f\n", mu); - fprintf (stderr, "nu = %f\n", nu); - fprintf (stderr, "doppler_factor = %f\n", doppler_factor); - fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); - return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; - } - } - else - { - rpacket_set_d_line (packet, MISS_DISTANCE); - return TARDIS_ERROR_OK; - } -} - -void -compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) -{ - double chi_continuum, d_continuum; - double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * - storage->sigma_thomson; - if (storage->full_relativity) - { - chi_electron *= rpacket_doppler_factor (packet, storage); - } - - if (storage->cont_status == CONTINUUM_ON) - { - if (packet->compute_chi_bf) - { - calculate_chi_bf (packet, storage); - calculate_chi_ff (packet, storage); - } - else - { - packet->compute_chi_bf=true; - } - chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; - d_continuum = rpacket_get_tau_event (packet) / chi_continuum; - } - else - { - chi_continuum = chi_electron; - d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * - storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); - } - - if (rpacket_get_virtual_packet(packet) > 0) - { - //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet - d_continuum = MISS_DISTANCE; - packet->compute_chi_bf = false; - } - else - { - - // fprintf(stderr, "--------\n"); - // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); - // fprintf(stderr, "chi_electron = %e\n", chi_electron); - // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); - // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); - // fprintf(stderr, "--------\n"); - - //rpacket_set_chi_freefree(packet, chi_freefree); - rpacket_set_chi_electron (packet, chi_electron); - } - rpacket_set_chi_continuum (packet, chi_continuum); - rpacket_set_d_continuum (packet, d_continuum); -} - -void -macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) -{ - int emit = 0, i = 0, offset = -1; - uint64_t activate_level = rpacket_get_macro_atom_activation_level (packet); - while (emit >= 0) - { - double event_random = rk_double (mt_state); - i = storage->macro_block_references[activate_level] - 1; - double p = 0.0; - offset = storage->transition_probabilities_nd * - rpacket_get_current_shell_id (packet); - do - { - ++i; - p += storage->transition_probabilities[offset + i]; - } - while (p <= event_random); - emit = storage->transition_type[i]; - activate_level = storage->destination_level_id[i]; - } - switch (emit) - { - case BB_EMISSION: - line_emission (packet, storage, storage->transition_line_id[i], mt_state); - break; - - case BF_EMISSION: - rpacket_set_current_continuum_id (packet, storage->transition_line_id[i]); - storage->last_line_interaction_out_id[rpacket_get_id (packet)] = - rpacket_get_current_continuum_id (packet); - - continuum_emission (packet, storage, mt_state, sample_nu_free_bound, 3); - break; - - case FF_EMISSION: - continuum_emission (packet, storage, mt_state, sample_nu_free_free, 4); - break; - - case ADIABATIC_COOLING: - storage->last_interaction_type[rpacket_get_id (packet)] = 5; - rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); - break; - - default: - fprintf (stderr, "This process for macro-atom deactivation should not exist! (emit = %d)\n", emit); - exit(1); - } -} - -void -move_packet (rpacket_t * packet, storage_model_t * storage, double distance) -{ - double doppler_factor = rpacket_doppler_factor (packet, storage); - if (distance > 0.0) - { - double r = rpacket_get_r (packet); - double new_r = - sqrt (r * r + distance * distance + - 2.0 * r * distance * rpacket_get_mu (packet)); - rpacket_set_mu (packet, - (rpacket_get_mu (packet) * r + distance) / new_r); - rpacket_set_r (packet, new_r); - if (rpacket_get_virtual_packet (packet) <= 0) - { - double comov_energy = rpacket_get_energy (packet) * doppler_factor; - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - if (storage->full_relativity) - { - distance *= doppler_factor; - } -#ifdef WITHOPENMP -#pragma omp atomic -#endif - storage->js[rpacket_get_current_shell_id (packet)] += - comov_energy * distance; -#ifdef WITHOPENMP -#pragma omp atomic -#endif - storage->nubars[rpacket_get_current_shell_id (packet)] += - comov_energy * distance * comov_nu; - - if (storage->cont_status) - { - increment_continuum_estimators(packet, storage, distance, comov_nu, comov_energy); - } - } - } -} - -void -increment_continuum_estimators (const rpacket_t * packet, storage_model_t * storage, double distance, - double comov_nu, double comov_energy) -{ - int64_t current_continuum_id; - int64_t no_of_continuum_edges = storage->no_of_edges; - int64_t shell_id = rpacket_get_current_shell_id (packet); - line_search(storage->continuum_list_nu, comov_nu, no_of_continuum_edges, ¤t_continuum_id); - double T = storage->t_electrons[shell_id]; - double boltzmann_factor = exp (-(H * comov_nu) / (KB * T)); - - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->ff_heating_estimator[shell_id] += comov_energy * distance * rpacket_get_chi_freefree (packet); - - for(int64_t i = current_continuum_id; i < no_of_continuum_edges; i++) - { - double bf_xsect = bf_cross_section (storage, i, comov_nu); - int64_t photo_ion_idx = i * storage->no_of_shells + shell_id; - double photo_ion_estimator_helper = comov_energy * distance * bf_xsect / comov_nu; - double bf_heating_estimator_helper = - comov_energy * distance * bf_xsect * (1. - storage->continuum_list_nu[i] / comov_nu); - - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->photo_ion_estimator[photo_ion_idx] += photo_ion_estimator_helper; - - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->stim_recomb_estimator[photo_ion_idx] += photo_ion_estimator_helper * boltzmann_factor; - - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->bf_heating_estimator[photo_ion_idx] += bf_heating_estimator_helper; - - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->stim_recomb_cooling_estimator[photo_ion_idx] += bf_heating_estimator_helper * boltzmann_factor; - - if (photo_ion_estimator_helper != 0.0) - { - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->photo_ion_estimator_statistics[photo_ion_idx] += 1; - } - else - { - break; - } - } -} - -double -get_increment_j_blue_estimator_energy (const rpacket_t * packet, - const storage_model_t * storage, - double d_line) -{ - double energy; - if (storage->full_relativity) - { - // Accurate up to a factor 1 / gamma - energy = rpacket_get_energy (packet); - } - else - { - double r = rpacket_get_r (packet); - double r_interaction = sqrt (r * r + d_line * d_line + - 2.0 * r * d_line * rpacket_get_mu (packet)); - double mu_interaction = (rpacket_get_mu (packet) * r + d_line) / r_interaction; - double doppler_factor = 1.0 - mu_interaction * r_interaction * - storage->inverse_time_explosion * INVERSE_C; - energy = rpacket_get_energy (packet) * doppler_factor; - } - return energy; -} - -void -increment_j_blue_estimator (const rpacket_t * packet, storage_model_t * storage, - double d_line, int64_t j_blue_idx) -{ - if (storage->line_lists_j_blues != NULL) - { - double energy = get_increment_j_blue_estimator_energy (packet, storage, - d_line); - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->line_lists_j_blues[j_blue_idx] += - energy / rpacket_get_nu (packet); - } -} - -void -increment_Edotlu_estimator (const rpacket_t * packet, storage_model_t * storage, - double d_line, int64_t line_idx) -{ - if (storage->line_lists_Edotlu != NULL) - { - double energy = get_increment_j_blue_estimator_energy (packet, storage, - d_line); - #ifdef WITHOPENMP - #pragma omp atomic - #endif - storage->line_lists_Edotlu[line_idx] += energy; - } -} - - -int64_t -montecarlo_one_packet (storage_model_t * storage, rpacket_t * packet, - int64_t virtual_mode, rk_state *mt_state) -{ - int64_t reabsorbed=-1; - if (virtual_mode == 0) - { - reabsorbed = montecarlo_one_packet_loop (storage, packet, 0, mt_state); - } - else - { - if ((rpacket_get_nu (packet) > storage->spectrum_virt_start_nu) && (rpacket_get_nu(packet) < storage->spectrum_virt_end_nu)) - { - for (int64_t i = 0; i < rpacket_get_virtual_packet_flag (packet); i++) - { - double weight; - rpacket_t virt_packet = *packet; - double mu_min; - if (rpacket_get_r(&virt_packet) > storage->r_inner[0]) - { - mu_min = - -1.0 * sqrt (1.0 - - (storage->r_inner[0] / rpacket_get_r(&virt_packet)) * - (storage->r_inner[0] / rpacket_get_r(&virt_packet))); - - if (storage->full_relativity) - { - // Need to transform the angular size of the photosphere into the CMF - mu_min = angle_aberration_LF_to_CMF (&virt_packet, storage, mu_min); - } - } - else - { - mu_min = 0.0; - } - double mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag (packet); - rpacket_set_mu(&virt_packet,mu_min + (i + rk_double (mt_state)) * mu_bin); - switch (virtual_mode) - { - case -2: - weight = 1.0 / rpacket_get_virtual_packet_flag (packet); - break; - case -1: - weight = - 2.0 * rpacket_get_mu(&virt_packet) / - rpacket_get_virtual_packet_flag (packet); - break; - case 1: - weight = - (1.0 - - mu_min) / 2.0 / rpacket_get_virtual_packet_flag (packet); - break; - default: - fprintf (stderr, "Something has gone horribly wrong!\n"); - // FIXME MR: we need to somehow signal an error here - // I'm adding an exit() here to inform the compiler about the impossible path - exit(1); - } - angle_aberration_CMF_to_LF (&virt_packet, storage); - double doppler_factor_ratio = - rpacket_doppler_factor (packet, storage) / - rpacket_doppler_factor (&virt_packet, storage); - rpacket_set_energy(&virt_packet, - rpacket_get_energy (packet) * doppler_factor_ratio); - rpacket_set_nu(&virt_packet,rpacket_get_nu (packet) * doppler_factor_ratio); - reabsorbed = montecarlo_one_packet_loop (storage, &virt_packet, 1, mt_state); -#ifdef WITH_VPACKET_LOGGING -#ifdef WITHOPENMP -#pragma omp critical - { -#endif // WITHOPENMP - if (storage->virt_packet_count >= storage->virt_array_size) - { - storage->virt_array_size *= 2; - storage->virt_packet_nus = safe_realloc(storage->virt_packet_nus, sizeof(double) * storage->virt_array_size); - storage->virt_packet_energies = safe_realloc(storage->virt_packet_energies, sizeof(double) * storage->virt_array_size); - storage->virt_packet_last_interaction_in_nu = safe_realloc(storage->virt_packet_last_interaction_in_nu, sizeof(double) * storage->virt_array_size); - storage->virt_packet_last_interaction_type = safe_realloc(storage->virt_packet_last_interaction_type, sizeof(int64_t) * storage->virt_array_size); - storage->virt_packet_last_line_interaction_in_id = safe_realloc(storage->virt_packet_last_line_interaction_in_id, sizeof(int64_t) * storage->virt_array_size); - storage->virt_packet_last_line_interaction_out_id = safe_realloc(storage->virt_packet_last_line_interaction_out_id, sizeof(int64_t) * storage->virt_array_size); - } - storage->virt_packet_nus[storage->virt_packet_count] = rpacket_get_nu(&virt_packet); - storage->virt_packet_energies[storage->virt_packet_count] = rpacket_get_energy(&virt_packet) * weight; - storage->virt_packet_last_interaction_in_nu[storage->virt_packet_count] = storage->last_interaction_in_nu[rpacket_get_id (packet)]; - storage->virt_packet_last_interaction_type[storage->virt_packet_count] = storage->last_interaction_type[rpacket_get_id (packet)]; - storage->virt_packet_last_line_interaction_in_id[storage->virt_packet_count] = storage->last_line_interaction_in_id[rpacket_get_id (packet)]; - storage->virt_packet_last_line_interaction_out_id[storage->virt_packet_count] = storage->last_line_interaction_out_id[rpacket_get_id (packet)]; - storage->virt_packet_count += 1; -#ifdef WITHOPENMP - } -#endif // WITHOPENMP -#endif // WITH_VPACKET_LOGGING - if ((rpacket_get_nu(&virt_packet) < storage->spectrum_end_nu) && - (rpacket_get_nu(&virt_packet) > storage->spectrum_start_nu)) - { -#ifdef WITHOPENMP -#pragma omp critical - { -#endif // WITHOPENMP - int64_t virt_id_nu = - floor ((rpacket_get_nu(&virt_packet) - - storage->spectrum_start_nu) / - storage->spectrum_delta_nu); - storage->spectrum_virt_nu[virt_id_nu] += - rpacket_get_energy(&virt_packet) * weight; -#ifdef WITHOPENMP - } -#endif // WITHOPENMP - } - } - } - else - { - return 1; - } - } - return reabsorbed; -} - -void -move_packet_across_shell_boundary (rpacket_t * packet, - storage_model_t * storage, double distance, rk_state *mt_state) -{ - move_packet (packet, storage, distance); - if (rpacket_get_virtual_packet (packet) > 0) - { - double delta_tau_event = rpacket_get_chi_continuum(packet) * distance; - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) + - delta_tau_event); - packet->compute_chi_bf = true; - } - else - { - rpacket_reset_tau_event (packet, mt_state); - } - if ((rpacket_get_current_shell_id (packet) < storage->no_of_shells - 1 - && rpacket_get_next_shell_id (packet) == 1) - || (rpacket_get_current_shell_id (packet) > 0 - && rpacket_get_next_shell_id (packet) == -1)) - { - rpacket_set_current_shell_id (packet, - rpacket_get_current_shell_id (packet) + - rpacket_get_next_shell_id (packet)); - } - else if (rpacket_get_next_shell_id (packet) == 1) - { - rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); - } - else if ((storage->reflective_inner_boundary == 0) || - (rk_double (mt_state) > storage->inner_boundary_albedo)) - { - rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); - } - else - { - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - double comov_energy = rpacket_get_energy (packet) * doppler_factor; - // TODO: correct - rpacket_set_mu (packet, rk_double (mt_state)); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - montecarlo_one_packet (storage, packet, -2, mt_state); - } - } -} - -void -montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state) -{ - move_packet (packet, storage, distance); - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - double comov_energy = rpacket_get_energy (packet) * doppler_factor; - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - rpacket_reset_tau_event (packet, mt_state); - storage->last_interaction_type[rpacket_get_id (packet)] = 1; - - angle_aberration_CMF_to_LF (packet, storage); - - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - create_vpacket (storage, packet, mt_state); - } -} - -void -montecarlo_bound_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state) -{ - // current position in list of continuum edges -> indicates which bound-free processes are possible - int64_t ccontinuum = rpacket_get_current_continuum_id (packet); - - // Determine in which continuum the bf-absorption occurs - double chi_bf = rpacket_get_chi_boundfree (packet); - double zrand = rk_double (mt_state); - double zrand_x_chibf = zrand * chi_bf; - - while ((ccontinuum < storage->no_of_edges - 1) && (packet->chi_bf_tmp_partial[ccontinuum] <= zrand_x_chibf)) - { - ccontinuum++; - } - rpacket_set_current_continuum_id (packet, ccontinuum); - - /* For consistency reasons the branching between ionization and thermal energy is determined using the - comoving frequency at the initial position instead of the frequency at the point of interaction */ - double comov_nu = rpacket_get_nu (packet) * rpacket_doppler_factor (packet, storage); - - /* Move the packet to the place of absorption, select a direction for re-emission and impose energy conservation - in the co-moving frame. */ - move_packet (packet, storage, distance); - double old_doppler_factor = rpacket_doppler_factor (packet, storage); - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - storage->last_interaction_type[rpacket_get_id (packet)] = 3; // last interaction was a bf-absorption - storage->last_line_interaction_in_id[rpacket_get_id (packet)] = ccontinuum; - - // Convert the rpacket to thermal or ionization energy - zrand = rk_double (mt_state); - int64_t activate_level = (zrand < storage->continuum_list_nu[ccontinuum] / comov_nu) ? - storage->cont_edge2macro_level[ccontinuum] : storage->kpacket2macro_level; - - rpacket_set_macro_atom_activation_level (packet, activate_level); - macro_atom (packet, storage, mt_state); -} - -void -montecarlo_free_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state) -{ - /* Move the packet to the place of absorption, select a direction for re-emission and impose energy conservation - in the co-moving frame. */ - move_packet (packet, storage, distance); - double old_doppler_factor = rpacket_doppler_factor (packet, storage); - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - storage->last_interaction_type[rpacket_get_id (packet)] = 4; // last interaction was a ff-absorption - - // Create a k-packet - rpacket_set_macro_atom_activation_level (packet, storage->kpacket2macro_level); - macro_atom (packet, storage, mt_state); -} - -double -sample_nu_free_free (const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) -{ - int64_t shell_id = rpacket_get_current_shell_id (packet); - double T = storage->t_electrons[shell_id]; - double zrand = rk_double (mt_state); - return -KB * T / H * log(zrand); // Lucy 2003 MC II Eq.41 -} - -double -sample_nu_free_bound (const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) -{ - int64_t continuum_id = rpacket_get_current_continuum_id (packet); - double th_frequency = storage->continuum_list_nu[continuum_id]; - int64_t shell_id = rpacket_get_current_shell_id (packet); - double T = storage->t_electrons[shell_id]; - double zrand = rk_double (mt_state); - return th_frequency * (1 - (KB * T / H / th_frequency * log(zrand))); // Lucy 2003 MC II Eq.26 -} - -void -montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state) -{ - uint64_t next_line_id = rpacket_get_next_line_id (packet); - uint64_t line2d_idx = next_line_id + - storage->no_of_lines * rpacket_get_current_shell_id (packet); - if (rpacket_get_virtual_packet (packet) == 0) - { - increment_j_blue_estimator (packet, storage, distance, line2d_idx); - increment_Edotlu_estimator (packet, storage, distance, line2d_idx); - } - double tau_line = - storage->line_lists_tau_sobolevs[line2d_idx]; - double tau_continuum = rpacket_get_chi_continuum(packet) * distance; - double tau_combined = tau_line + tau_continuum; - //rpacket_set_next_line_id (packet, rpacket_get_next_line_id (packet) + 1); - - if (next_line_id + 1 == storage->no_of_lines) - { - rpacket_set_last_line (packet, true); - } - if (rpacket_get_virtual_packet (packet) > 0) - { - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) + tau_line); - rpacket_set_next_line_id (packet, next_line_id + 1); - test_for_close_line (packet, storage); - } - else if (rpacket_get_tau_event (packet) < tau_combined) - { // Line absorption occurs - move_packet (packet, storage, distance); - double old_doppler_factor = rpacket_doppler_factor (packet, storage); - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - storage->last_interaction_in_nu[rpacket_get_id (packet)] = - rpacket_get_nu (packet); - storage->last_line_interaction_in_id[rpacket_get_id (packet)] = - next_line_id; - storage->last_line_interaction_shell_id[rpacket_get_id (packet)] = - rpacket_get_current_shell_id (packet); - storage->last_interaction_type[rpacket_get_id (packet)] = 2; - if (storage->line_interaction_id == 0) - { - line_emission (packet, storage, next_line_id, mt_state); - } - else if (storage->line_interaction_id >= 1) - { - rpacket_set_macro_atom_activation_level (packet, - storage->line2macro_level_upper[next_line_id]); - macro_atom (packet, storage, mt_state); - } - } - else - { // Packet passes line without interacting - rpacket_set_tau_event (packet, - rpacket_get_tau_event (packet) - tau_line); - rpacket_set_next_line_id (packet, next_line_id + 1); - packet->compute_chi_bf = false; - test_for_close_line (packet, storage); - } -} - -void -line_emission (rpacket_t * packet, storage_model_t * storage, int64_t emission_line_id, rk_state *mt_state) -{ - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - storage->last_line_interaction_out_id[rpacket_get_id (packet)] = emission_line_id; - if (storage->cont_status == CONTINUUM_ON) - { - storage->last_interaction_out_type[rpacket_get_id (packet)] = 2; - } - - rpacket_set_nu (packet, - storage->line_list_nu[emission_line_id] * inverse_doppler_factor); - rpacket_set_nu_line (packet, storage->line_list_nu[emission_line_id]); - rpacket_set_next_line_id (packet, emission_line_id + 1); - rpacket_reset_tau_event (packet, mt_state); - - angle_aberration_CMF_to_LF (packet, storage); - - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - bool virtual_close_line = false; - if (!rpacket_get_last_line (packet) && - fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - - rpacket_get_nu_line (packet)) < - (rpacket_get_nu_line (packet)* 1e-7)) - { - virtual_close_line = true; - } - // QUESTIONABLE!!! - bool old_close_line = rpacket_get_close_line (packet); - rpacket_set_close_line (packet, virtual_close_line); - create_vpacket (storage, packet, mt_state); - rpacket_set_close_line (packet, old_close_line); - virtual_close_line = false; - } - test_for_close_line (packet, storage); -} - -void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) -{ - if (!rpacket_get_last_line (packet) && - fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - - rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* - 1e-7)) - { - rpacket_set_close_line (packet, true); - } -} - -void -continuum_emission (rpacket_t * packet, storage_model_t * storage, rk_state *mt_state, - pt2sample_nu sample_nu_continuum, int64_t emission_type_id) -{ - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - double nu_comov = sample_nu_continuum (packet, storage, mt_state); - rpacket_set_nu (packet, nu_comov * inverse_doppler_factor); - rpacket_reset_tau_event (packet, mt_state); - - storage->last_interaction_out_type[rpacket_get_id (packet)] = emission_type_id; - - // Have to find current position in line list - int64_t current_line_id; - line_search (storage->line_list_nu, nu_comov, storage->no_of_lines, ¤t_line_id); - - bool last_line = (current_line_id == storage->no_of_lines); - rpacket_set_last_line (packet, last_line); - rpacket_set_next_line_id (packet, current_line_id); - - angle_aberration_CMF_to_LF (packet, storage); - - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - create_vpacket (storage, packet, mt_state); - } -} - -static void -montecarlo_compute_distances (rpacket_t * packet, storage_model_t * storage) -{ - // Check if the last line was the same nu as the current line. - if (rpacket_get_close_line (packet)) - { - // If so set the distance to the line to 0.0 - rpacket_set_d_line (packet, 0.0); - // Reset close_line. - rpacket_set_close_line (packet, false); - } - else - { - compute_distance2boundary (packet, storage); - compute_distance2line (packet, storage); - // FIXME MR: return status of compute_distance2line() is ignored - compute_distance2continuum (packet, storage); - } -} - -montecarlo_event_handler_t -get_event_handler (rpacket_t * packet, storage_model_t * storage, - double *distance, rk_state *mt_state) -{ - montecarlo_compute_distances (packet, storage); - double d_boundary = rpacket_get_d_boundary (packet); - double d_continuum = rpacket_get_d_continuum (packet); - double d_line = rpacket_get_d_line (packet); - montecarlo_event_handler_t handler; - if (d_line <= d_boundary && d_line <= d_continuum) - { - *distance = d_line; - handler = &montecarlo_line_scatter; - } - else if (d_boundary <= d_continuum) - { - *distance = d_boundary; - handler = &move_packet_across_shell_boundary; - } - else - { - *distance = d_continuum; - handler = montecarlo_continuum_event_handler (packet, storage, mt_state); - } - return handler; -} - -montecarlo_event_handler_t -montecarlo_continuum_event_handler (rpacket_t * packet, storage_model_t * storage, rk_state *mt_state) -{ - if (storage->cont_status) - { - double zrand_x_chi_cont = rk_double (mt_state) * rpacket_get_chi_continuum (packet); - double chi_th = rpacket_get_chi_electron (packet); - double chi_bf = rpacket_get_chi_boundfree (packet); - - if (zrand_x_chi_cont < chi_th) - { - return &montecarlo_thomson_scatter; - } - else if (zrand_x_chi_cont < chi_th + chi_bf) - { - return &montecarlo_bound_free_scatter; - } - else - { - return &montecarlo_free_free_scatter; - } - } - else - { - return &montecarlo_thomson_scatter; - } -} - -int64_t -montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, - int64_t virtual_packet, rk_state *mt_state) -{ - rpacket_set_tau_event (packet, 0.0); - rpacket_set_nu_line (packet, 0.0); - rpacket_set_virtual_packet (packet, virtual_packet); - rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); - // Initializing tau_event if it's a real packet. - if (virtual_packet == 0) - { - rpacket_reset_tau_event (packet,mt_state); - } - // For a virtual packet tau_event is the sum of all the tau's that the packet passes. - while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) - { - // Check if we are at the end of line list. - if (!rpacket_get_last_line (packet)) - { - rpacket_set_nu_line (packet, - storage-> - line_list_nu[rpacket_get_next_line_id - (packet)]); - } - double distance; - get_event_handler (packet, storage, &distance, mt_state) (packet, storage, - distance, mt_state); - if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) - { - double event_random = rk_double (mt_state); - if (event_random > storage->survival_probability) - { - rpacket_set_energy(packet, 0.0); - rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); - } - else - { - rpacket_set_energy(packet, - rpacket_get_energy (packet) / storage->survival_probability * - exp (-1.0 * rpacket_get_tau_event (packet))); - rpacket_set_tau_event (packet, 0.0); - } - } - } - if (virtual_packet > 0) - { - rpacket_set_energy (packet, - rpacket_get_energy (packet) * exp (-1.0 * - rpacket_get_tau_event - (packet))); - } - return rpacket_get_status (packet) == - TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; -} - -void -montecarlo_main_loop(storage_model_t * storage, int64_t virtual_packet_flag, int nthreads, unsigned long seed) -{ - int64_t finished_packets = 0; - storage->virt_packet_count = 0; -#ifdef WITH_VPACKET_LOGGING - storage->virt_packet_nus = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_energies = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_last_interaction_in_nu = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); - storage->virt_packet_last_interaction_type = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_packet_last_line_interaction_in_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_packet_last_line_interaction_out_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); - storage->virt_array_size = storage->no_of_packets; -#endif // WITH_VPACKET_LOGGING -#ifdef WITHOPENMP - omp_set_dynamic(0); - if (nthreads > 0) - { - omp_set_num_threads(nthreads); - } - -#pragma omp parallel firstprivate(finished_packets) - { - rk_state mt_state; - rk_seed (seed + omp_get_thread_num(), &mt_state); -#pragma omp master - { - fprintf(stderr, "Running with OpenMP - %d threads\n", omp_get_num_threads()); - print_progress(0, storage->no_of_packets); - } -#else - rk_state mt_state; - rk_seed (seed, &mt_state); - fprintf(stderr, "Running without OpenMP\n"); -#endif - int64_t chi_bf_tmp_size = (storage->cont_status) ? storage->no_of_edges : 0; - double *chi_bf_tmp_partial = safe_malloc(sizeof(double) * chi_bf_tmp_size); - - #pragma omp for - for (int64_t packet_index = 0; packet_index < storage->no_of_packets; ++packet_index) - { - int reabsorbed = 0; - rpacket_t packet; - rpacket_set_id(&packet, packet_index); - rpacket_init(&packet, storage, packet_index, virtual_packet_flag, chi_bf_tmp_partial); - if (virtual_packet_flag > 0) - { - reabsorbed = montecarlo_one_packet(storage, &packet, -1, &mt_state); - } - reabsorbed = montecarlo_one_packet(storage, &packet, 0, &mt_state); - storage->output_nus[packet_index] = rpacket_get_nu(&packet); - if (reabsorbed == 1) - { - storage->output_energies[packet_index] = -rpacket_get_energy(&packet); - } - else - { - storage->output_energies[packet_index] = rpacket_get_energy(&packet); - } - if ( ++finished_packets%100 == 0 ) - { -#ifdef WITHOPENMP - // WARNING: This only works with a static sheduler and gives an approximation of progress. - // The alternative would be to have a shared variable but that could potentially decrease performance when using many threads. - if (omp_get_thread_num() == 0 ) - print_progress(finished_packets * omp_get_num_threads(), storage->no_of_packets); -#else - print_progress(finished_packets, storage->no_of_packets); -#endif - } - } - free(chi_bf_tmp_partial); -#ifdef WITHOPENMP - } -#endif - print_progress(storage->no_of_packets, storage->no_of_packets); - fprintf(stderr,"\n"); -} - -void -create_vpacket (storage_model_t * storage, rpacket_t * packet, - rk_state *mt_state) -{ - if (storage->enable_biasing) - { - int64_t shell_id = rpacket_get_current_shell_id(packet); - double tau_bias = (storage->tau_bias[shell_id + 1] + - (storage->tau_bias[shell_id] - storage->tau_bias[shell_id + 1]) * - (storage->r_outer[shell_id] - rpacket_get_r (packet)) / - (storage->r_outer[shell_id] - storage->r_inner[shell_id])); - double vpacket_prob = exp(-tau_bias); - double event_random = rk_double (mt_state); - if (event_random < vpacket_prob) - { - packet->vpacket_weight = 1. / vpacket_prob; - montecarlo_one_packet (storage, packet, 1, mt_state); - } - } - else - { - montecarlo_one_packet (storage, packet, 1, mt_state); - } -} diff --git a/tardis/montecarlo/src/cmontecarlo.h b/tardis/montecarlo/src/cmontecarlo.h deleted file mode 100644 index 767017b7303..00000000000 --- a/tardis/montecarlo/src/cmontecarlo.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef TARDIS_CMONTECARLO_H -#define TARDIS_CMONTECARLO_H - -#include -#include "randomkit/randomkit.h" -#include "rpacket.h" -#include "status.h" -#include "storage.h" - -#ifdef WITH_VPACKET_LOGGING -#define LOG_VPACKETS 1 -#else -#define LOG_VPACKETS 0 -#endif - -typedef void (*montecarlo_event_handler_t) (rpacket_t *packet, - storage_model_t *storage, - double distance, rk_state *mt_state); - -typedef double (*pt2sample_nu) (const rpacket_t *packet, - const storage_model_t *storage, - rk_state *mt_state); - -void initialize_random_kit (unsigned long seed); - -tardis_error_t line_search (const double *nu, double nu_insert, - int64_t number_of_lines, int64_t * result); - -tardis_error_t -reverse_binary_search (const double *x, double x_insert, - int64_t imin, int64_t imax, int64_t * result); - -tardis_error_t -binary_search (const double *x, double x_insert, - int64_t imin, int64_t imax, int64_t * result); - -double rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage); - -double rpacket_inverse_doppler_factor (const rpacket_t *packet, const storage_model_t *storage); - -void -angle_aberration_CMF_to_LF (rpacket_t * packet, const storage_model_t * storage); - -double -angle_aberration_LF_to_CMF (rpacket_t *packet, const storage_model_t *storage, double mu); - -/** Calculate the distance to shell boundary. - * - * @param packet rpacket structure with packet information - * @param storage storage model data - * - * @return distance to shell boundary - */ -void compute_distance2boundary (rpacket_t * packet, - const storage_model_t * storage); - -/** Calculate the distance the packet has to travel until it redshifts to the first spectral line. - * - * @param packet rpacket structure with packet information - * @param storage storage model data - * - * @return distance to the next spectral line - */ -tardis_error_t compute_distance2line (rpacket_t * packet, - const storage_model_t * storage); - -/** Calculate the distance to the next continuum event, which can be a Thomson scattering, bound-free absorption or - free-free transition. - * - * @param packet rpacket structure with packet information - * @param storage storage model data - * - * sets distance to the next continuum event (in centimeters) in packet rpacket structure - */ -void compute_distance2continuum (rpacket_t *packet, storage_model_t *storage); - -void macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); - -void move_packet (rpacket_t * packet, storage_model_t * storage, - double distance); - -void increment_j_blue_estimator (const rpacket_t * packet, - storage_model_t * storage, - double d_line, - int64_t j_blue_idx); - -void increment_Edotlu_estimator (const rpacket_t * packet, - storage_model_t * storage, - double d_line, - int64_t j_blue_idx); - -double get_increment_j_blue_estimator_energy (const rpacket_t * packet, - const storage_model_t * storage, - double d_line); - -void -increment_continuum_estimators (const rpacket_t * packet, storage_model_t * storage, double distance, - double comov_nu, double comov_energy); - -int64_t montecarlo_one_packet (storage_model_t * storage, rpacket_t * packet, - int64_t virtual_mode, rk_state *mt_state); - -int64_t montecarlo_one_packet_loop (storage_model_t * storage, - rpacket_t * packet, - int64_t virtual_packet, rk_state *mt_state); - -void montecarlo_main_loop(storage_model_t * storage, - int64_t virtual_packet_flag, - int nthreads, - unsigned long seed); - -montecarlo_event_handler_t get_event_handler (rpacket_t * packet, storage_model_t * storage, - double *distance, rk_state *mt_state); - -void test_for_close_line(rpacket_t * packet, const storage_model_t * storage); - -/* New handlers for continuum implementation */ - -montecarlo_event_handler_t montecarlo_continuum_event_handler(rpacket_t * packet, storage_model_t * storage, rk_state *mt_state); - -void montecarlo_free_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state); - -void montecarlo_bound_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state); - -double -bf_cross_section(const storage_model_t * storage, int64_t continuum_id, double comov_nu); - -void calculate_chi_bf(rpacket_t * packet, storage_model_t * storage); - -void calculate_chi_ff(rpacket_t * packet, const storage_model_t * storage); - -void -move_packet_across_shell_boundary (rpacket_t * packet, - storage_model_t * storage, double distance, rk_state *mt_state); - -void -montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state); - -void -montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state); - -void -line_emission(rpacket_t * packet, storage_model_t * storage, - int64_t emission_line_id, rk_state *mt_state); - -double sample_nu_free_free(const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); - -double sample_nu_free_bound(const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); - -void continuum_emission(rpacket_t * packet, storage_model_t * storage, rk_state *mt_state, - pt2sample_nu sample_nu_continuum, int64_t emission_type_id); - -void -create_vpacket (storage_model_t * storage, rpacket_t * packet, - rk_state *mt_state); - -#endif // TARDIS_CMONTECARLO_H diff --git a/tardis/montecarlo/src/integrator.c b/tardis/montecarlo/src/integrator.c deleted file mode 100644 index c885257d458..00000000000 --- a/tardis/montecarlo/src/integrator.c +++ /dev/null @@ -1,343 +0,0 @@ -#define _USE_MATH_DEFINES - -#include -#include -#include -#include -#include -#include - -#include "io.h" -#include "storage.h" -#include "integrator.h" -#include "cmontecarlo.h" - -#include "omp_helper.h" - -#define NULEN 0 -#define LINELEN 1 -#define PLEN 2 -#define SHELLEN 3 - -#define C_INV 3.33564e-11 -#define M_PI acos (-1) -#define KB_CGS 1.3806488e-16 -#define H_CGS 6.62606957e-27 - -/** - * Calculate the intensity of a black-body according to the following formula - * .. math:: - * I(\\nu, T) = \\frac{2h\\nu^3}{c^2}\frac{1}{e^{h\\nu \\beta_\\textrm{rad}} - 1} -*/ -double -intensity_black_body (double nu, double T) -{ - double beta_rad = 1 / (KB_CGS * T); - double coefficient = 2 * H_CGS * C_INV * C_INV; - return coefficient * nu * nu * nu / (exp(H_CGS * nu * beta_rad) - 1 ); -} - - -/*! @brief Algorithm to integrate an array using the trapezoid integration rule - * -*/ -double -trapezoid_integration (const double* array, const double h, int N) -{ - double result = (array[0] + array[N-1])/2; - for (int idx = 1; idx < N-1; ++idx) - { - result += array[idx]; - } - return result * h; -} - -/*! @brief Calculate distance to p line - * - * Calculate half of the length of the p-line inside a shell - * of radius r in terms of unit length (c * t_exp). - * If shell and p-line do not intersect, return 0. - * - * @param r radius of the shell - * @param p distance of the p-line to the center of the supernova - * @param inv_t inverse time_explosio is needed to norm to unit-length - * @return half the lenght inside the shell or zero - */ -static inline double -calculate_z(double r, double p, double inv_t) -{ - return (r > p) ? sqrt(r * r - p * p) * C_INV * inv_t : 0; -} - - -/*! - * @brief Calculate p line intersections - * - * This function calculates the intersection points of the p-line with each shell - * - * @param storage (INPUT) A storage model containing the environment - * @param p (INPUT) distance of the integration line to the center - * @param oz (OUTPUT) will be set with z values. The array is truncated by the - * value `1`. - * @param oshell_id (OUTPUT) will be set with the corresponding shell_ids - * @return number of shells intersected by the p-line - */ -int64_t -populate_z(const storage_model_t *storage, const double p, double *oz, int64_t *oshell_id) -{ - - // Abbreviations - double *r = storage->r_outer_i; - const int64_t N = storage->no_of_shells_i; - double inv_t = storage->inverse_time_explosion; - double z = 0; - - int64_t i = 0, offset = N, i_low, i_up; - - if (p <= storage->r_inner_i[0]) - { - // Intersect the photosphere - for(i = 0; i < N; ++i) - { // Loop from inside to outside - oz[i] = 1 - calculate_z(r[i], p, inv_t); - oshell_id[i] = i; - } - return N; - } - else - { - // No intersection with the photosphere - // that means we intersect each shell twice - for(i = 0; i < N; ++i) - { // Loop from inside to outside - z = calculate_z(r[i], p, inv_t); - if (z == 0) - continue; - if (offset == N) - { - offset = i; - } - // Calculate the index in the resulting array - i_low = N - i - 1; // the far intersection with the shell - i_up = N + i - 2 * offset; // the nearer intersection with the shell - - // Setting the arrays - oz[i_low] = 1 + z; - oshell_id[i_low] = i; - oz[i_up] = 1 - z; - oshell_id[i_up] = i; - } - return 2 * (N - offset); - } -} - - -/*! @brief Calculate integration points - * - */ -void -calculate_p_values(double R_max, int64_t N, double *opp) -{ - for(int i = 0; ino_of_lines, - size_shell = storage->no_of_shells_i, - size_tau = size_line * size_shell, - finished_nus = 0; - - double R_ph = storage->r_inner_i[0]; - double R_max = storage->r_outer_i[size_shell - 1]; - double pp[N]; - double *exp_tau = calloc(size_tau, sizeof(double)); -#pragma omp parallel firstprivate(L, exp_tau) - { - -#pragma omp master - { - if (omp_get_num_threads() > 1) { - fprintf(stderr, "Doing the formal integral\nRunning with OpenMP - %d threads\n", omp_get_num_threads()); - } else { - fprintf(stderr, "Doing the formal integral\nRunning without OpenMP\n"); - } - print_progress_fi(0, inu_size); - } - - // Initializing all the thread-local variables - int64_t offset = 0, i = 0, - size_z = 0, - idx_nu_start = 0, - direction = 0, - first = 0; - - double I_nu[N], - //I_nu_b[N], - //I_nu_r[N], - z[2 * storage->no_of_shells_i], - p = 0, - nu_start, - nu_end, - nu, - zstart, - zend, - escat_contrib, - escat_op, - Jkkp; - int64_t shell_id[2 * storage->no_of_shells_i]; - - double *pexp_tau, *patt_S_ul, *pline, *pJred_lu, *pJblue_lu; - - // Prepare exp_tau -#pragma omp for - for (i = 0; i < size_tau; ++i) { - exp_tau[i] = exp( -storage->line_lists_tau_sobolevs_i[i]); - } - calculate_p_values(R_max, N, pp); - // Done with the initialization - - // Loop over wavelengths in spectrum -#pragma omp for - for (int nu_idx = 0; nu_idx < inu_size ; ++nu_idx) - { - nu = inu[nu_idx]; - - // Loop over discrete values along line - for (int p_idx = 1; p_idx < N; ++p_idx) - { - escat_contrib = 0; - p = pp[p_idx]; - - // initialize z intersections for p values - size_z = populate_z(storage, p, z, shell_id); - - // initialize I_nu - if (p <= R_ph) - I_nu[p_idx] = intensity_black_body(nu * z[0], iT); - else - I_nu[p_idx] = 0; - - // Find first contributing line - nu_start = nu * z[0]; - nu_end = nu * z[1]; - line_search( - storage->line_list_nu, - nu_start, - size_line, - &idx_nu_start - ); - offset = shell_id[0] * size_line; - - // start tracking accumulated e-scattering optical depth - zstart = storage->time_explosion / C_INV * (1. - z[0]); - - // Initialize pointers - pline = storage->line_list_nu + idx_nu_start; - pexp_tau = exp_tau + offset + idx_nu_start; - patt_S_ul = att_S_ul + offset + idx_nu_start; - pJred_lu = Jred_lu + offset + idx_nu_start; - pJblue_lu = Jblue_lu + offset + idx_nu_start; - - // flag for first contribution to integration on current p-ray - first = 1; - - // TODO: Ugly loop - // Loop over all intersections - // TODO: replace by number of intersections and remove break - for (i = 0; i < size_z - 1; ++i) - { - escat_op = storage->electron_densities_i[shell_id[i]] * storage->sigma_thomson; - nu_end = nu * z[i+1]; - - // TODO: e-scattering: in principle we also have to check - // that dtau is <<1 (as assumed in Lucy 1999); if not, there - // is the chance that I_nu_b becomes negative - for (;pline < storage->line_list_nu + size_line; - // We have to increment all pointers simultaneously - ++pline, - ++pexp_tau, - ++patt_S_ul, - ++pJblue_lu) - { - if (*pline < nu_end) - { - // next resonance not in current shell - break; - } - - // Calculate e-scattering optical depth to next resonance point - zend = storage->time_explosion / C_INV * (1. - *pline / nu); - - if (first == 1){ - // First contribution to integration - // NOTE: this treatment of I_nu_b (given by boundary - // conditions) is not in Lucy 1999; should be - // re-examined carefully - escat_contrib += (zend - zstart) * escat_op * (*pJblue_lu - I_nu[p_idx]) ; - first = 0; - } - else{ - // Account for e-scattering, c.f. Eqs 27, 28 in Lucy 1999 - Jkkp = 0.5 * (*pJred_lu + *pJblue_lu); - escat_contrib += (zend - zstart) * escat_op * (Jkkp - I_nu[p_idx]) ; - // this introduces the necessary offset of one element between pJblue_lu and pJred_lu - pJred_lu += 1; - } - I_nu[p_idx] = I_nu[p_idx] + escat_contrib; - - // Lucy 1999, Eq 26 - I_nu[p_idx] = I_nu[p_idx] * (*pexp_tau) + *patt_S_ul; - - // reset e-scattering opacity - escat_contrib = 0; - zstart = zend; - } - // Calculate e-scattering optical depth to grid cell boundary - Jkkp = 0.5 * (*pJred_lu + *pJblue_lu); - zend = storage->time_explosion / C_INV * (1. - nu_end / nu); - escat_contrib += (zend - zstart) * escat_op * (Jkkp - I_nu[p_idx]); - zstart = zend; - - if (i < size_z-1){ - // advance pointers - direction = shell_id[i+1] - shell_id[i]; - pexp_tau += direction * size_line; - patt_S_ul += direction * size_line; - pJred_lu += direction * size_line; - pJblue_lu += direction * size_line; - } - } - I_nu[p_idx] *= p; - } - // TODO: change integration to match the calculation of p values - L[nu_idx] = 8 * M_PI * M_PI * trapezoid_integration(I_nu, R_max/N, N); -#pragma omp atomic update - ++finished_nus; - if (finished_nus%10 == 0){ - print_progress_fi(finished_nus, inu_size); - } - } - // Free everything allocated on heap - free(exp_tau); - printf("\n"); - } - return L; -} \ No newline at end of file diff --git a/tardis/montecarlo/src/integrator.h b/tardis/montecarlo/src/integrator.h deleted file mode 100644 index c06e4a286a0..00000000000 --- a/tardis/montecarlo/src/integrator.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TARDIS_INTEGRATOR_H -#define TARDIS_INTEGRATOR_H - -#include "storage.h" - -double -integrate_intensity(const double* I_nu, const double h, int N); - -int64_t -populate_z(const storage_model_t *storage, const double p, double *oz, int64_t *oshell_id); - -void -calculate_p_values(double R_max, int64_t N, double *opp); - -double -*_formal_integral( - const storage_model_t *storage, double T, double *nu, int64_t nu_size, double *att_S_ul, double *Jred_lu, double *Jblue_lu, int N); - -#endif diff --git a/tardis/montecarlo/src/io.h b/tardis/montecarlo/src/io.h deleted file mode 100644 index 122af1e5967..00000000000 --- a/tardis/montecarlo/src/io.h +++ /dev/null @@ -1,32 +0,0 @@ -#define _POSIX_C_SOURCE 1 - -#include -#include -#include - -#define STATUS_FORMAT "\r\033[2K\t[%" PRId64 "%%] Packets(finished/total): %" PRId64 "/%" PRId64 -#define STATUS_FORMAT_FI "\r\033[2K\t[%" PRId64 "%%] Bins(finished/total): %" PRId64 "/%" PRId64 - -static inline void -print_progress (const int64_t current, const int64_t total) -{ - if (isatty(fileno(stderr))) - { - fprintf(stderr, STATUS_FORMAT, - current * 100 / total, - current, - total); - } -} - -static inline void -print_progress_fi (const int64_t current, const int64_t total) -{ - if (isatty(fileno(stderr))) - { - fprintf(stderr, STATUS_FORMAT_FI, - current * 100 / total, - current, - total); - } -} diff --git a/tardis/montecarlo/src/omp_helper.h b/tardis/montecarlo/src/omp_helper.h deleted file mode 100644 index abb9c2acc7c..00000000000 --- a/tardis/montecarlo/src/omp_helper.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifdef WITHOPENMP - #include -#else -int omp_get_num_threads(){ - return 1; -} -#endif diff --git a/tardis/montecarlo/src/randomkit/LICENSE b/tardis/montecarlo/src/randomkit/LICENSE deleted file mode 100644 index c0d13eb1acc..00000000000 --- a/tardis/montecarlo/src/randomkit/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tardis/montecarlo/src/randomkit/randomkit.h b/tardis/montecarlo/src/randomkit/randomkit.h deleted file mode 100644 index d5220d01857..00000000000 --- a/tardis/montecarlo/src/randomkit/randomkit.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Anyone who attempts to generate random numbers by deterministic means is, - * of course, living in a state of sin. - * -- John von Neumann - */ - -/* - * Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* @(#) $Jeannot: randomkit.h,v 1.29 2006/02/19 14:44:14 js Exp $ */ - -#ifndef _RANDOMKIT_ -#define _RANDOMKIT_ - -#include "rk_mt.h" -#include "rk_sobol.h" -#include "rk_primitive.h" -#include "rk_isaac.h" - -#endif /* _RANDOMKIT_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_isaac.c b/tardis/montecarlo/src/randomkit/rk_isaac.c deleted file mode 100644 index 9f52fafae4c..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_isaac.c +++ /dev/null @@ -1,258 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * ISAAC RNG by By Bob Jenkins. Based on Bob Jenkins public domain code. - * - * Original algorithm for the implementation of rk_interval function from - * Richard J. Wagner's implementation of the Mersenne Twister RNG, optimised by - * Magnus Jonsson. - * - * Constants used in the rk_double implementation by Isaku Wada. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -static char const rcsid[] = - "@(#) $Jeannot: rk_isaac.c,v 1.4 2006/02/20 19:13:37 js Exp $"; - -#include "rk_isaac.h" -#include -#include -#include - -/* use the contents of randrsl[0..RK_ISAAC_STATE_LEN-1] as the seed. */ -static void isaac_init(rk_isaac_state *rg); - -void rk_isaac_seed(unsigned long seed, rk_isaac_state *state) -{ - rk_knuth_fill(seed, state->randrsl, RK_ISAAC_STATE_LEN); - isaac_init(state); -} - -rk_error rk_isaac_randomseed(rk_isaac_state *state) -{ - if(rk_devfill(state->randrsl, sizeof(state->randrsl), 1) == RK_NOERR) - { - isaac_init(state); - return RK_NOERR; - } - - rk_isaac_seed(rk_seedfromsystem(), state); - - return RK_ENODEV; -} - -#define ind(mm,x) ((mm)[(x>>2)&(RK_ISAAC_STATE_LEN-1)]) -#define rngstep(mix,a,b,mm,m,m2,r,x) \ -{ \ - x = *m; \ - a = ((a^(mix)) + *(m2++)) & 0xffffffff; \ - *(m++) = y = (ind(mm,x) + a + b) & 0xffffffff; \ - *(r++) = b = (ind(mm,y>>RK_ISAAC_STATE_POW) + x) & 0xffffffff; \ -} - -/* Call rand(rk_isaac_state *r) to retrieve a single 32-bit random value */ -unsigned long rk_isaac_random(rk_isaac_state *state) -{ - if (!state->randcnt--) - { - register unsigned long a,b,x,y,*m,*mm,*m2,*r,*mend; - mm=state->randmem; r=state->randrsl; - a = state->randa; b = (state->randb + (++state->randc)) & 0xffffffff; - for (m = mm, mend = m2 = m+(RK_ISAAC_STATE_LEN/2); m>6 , a, b, mm, m, m2, r, x); - rngstep( a<<2 , a, b, mm, m, m2, r, x); - rngstep( a>>16, a, b, mm, m, m2, r, x); - } - for (m2 = mm; m2>6 , a, b, mm, m, m2, r, x); - rngstep( a<<2 , a, b, mm, m, m2, r, x); - rngstep( a>>16, a, b, mm, m, m2, r, x); - } - state->randb = b; state->randa = a; - state->randcnt=RK_ISAAC_STATE_LEN-1; - } - return state->randrsl[state->randcnt] & 0xFFFFFFFF; -} - -#define mix(a,b,c,d,e,f,g,h) \ -{ \ - a^=b<<11; d+=a; b+=c; \ - b^=(c & 0xFFFFFFFF)>>2; e+=b; c+=d; \ - c^=d<<8; f+=c; d+=e; \ - d^=(e & 0xFFFFFFFF)>>16; g+=d; e+=f; \ - e^=f<<10; h+=e; f+=g; \ - f^=(g & 0xFFFFFFFF)>>4; a+=f; g+=h; \ - g^=h<<8; b+=g; h+=a; \ - h^=(a & 0xFFFFFFFF)>>9; c+=h; a+=b; \ -} - -/* if (flag==1), then use the contents of randrsl[] to initialize mm[]. */ -void isaac_init(rk_isaac_state *rk_isaac_state) -{ - int i; - unsigned long a,b,c,d,e,f,g,h; - unsigned long *m,*r; - rk_isaac_state->randa = rk_isaac_state->randb = rk_isaac_state->randc = 0; - m=rk_isaac_state->randmem; - r=rk_isaac_state->randrsl; - a=b=c=d=e=f=g=h=0x9e3779b9; /* the golden ratio */ - - for (i=0; i<4; ++i) /* scramble it */ - mix(a,b,c,d,e,f,g,h); - - /* initialize using the contents of r[] as the seed */ - for (i=0; irandcnt=0; - rk_isaac_state->has_gauss=0; -} - -long rk_isaac_long(rk_isaac_state *state) -{ - return rk_isaac_ulong(state) >> 1; -} - -unsigned long rk_isaac_ulong(rk_isaac_state *state) -{ -#if ULONG_MAX <= 0xffffffffUL - return rk_isaac_random(state); -#else - /* Assumes 64 bits */ - return (rk_isaac_random(state) << 32) | (rk_isaac_random(state)); -#endif -} - -unsigned long rk_isaac_interval(unsigned long max, rk_isaac_state *state) -{ - unsigned long mask = max, value; - - if (max == 0) return 0; - - /* Smallest bit mask >= max */ - mask |= mask >> 1; - mask |= mask >> 2; - mask |= mask >> 4; - mask |= mask >> 8; - mask |= mask >> 16; -#if ULONG_MAX > 0xffffffffUL - mask |= mask >> 32; -#endif - - /* Search a random value in [0..mask] <= max */ - while ((value = (rk_isaac_ulong(state) & mask)) > max); - - return value; -} - -double rk_isaac_double(rk_isaac_state *state) -{ - /* shifts : 67108864 = 0x4000000, 9007199254740992 = 0x20000000000000 */ - long a = rk_isaac_random(state) >> 5, b = rk_isaac_random(state) >> 6; - return (a * 67108864.0 + b) / 9007199254740992.0; -} - -void rk_isaac_copy(rk_isaac_state *copy, rk_isaac_state *orig) -{ - memcpy(copy, orig, sizeof(rk_isaac_state)); -} - -double rk_isaac_gauss(rk_isaac_state *state) -{ - if (state->has_gauss) - { - state->has_gauss = 0; - return state->gauss; - } - else - { - double f, x1, x2, r2; - do - { - x1 = 2.0*rk_isaac_double(state) - 1.0; - x2 = 2.0*rk_isaac_double(state) - 1.0; - r2 = x1*x1 + x2*x2; - } - while (r2 >= 1.0 || r2 == 0.0); - - f = sqrt(-2.0*log(r2)/r2); /* Box-Muller transform */ - state->has_gauss = 1; - state->gauss = f*x1; /* Keep for next call */ - return f*x2; - } -} - -void rk_isaac_fill(void *buffer, size_t size, rk_isaac_state *state) -{ - unsigned long r; - unsigned char *buf = buffer; - rk_isaac_state tempstate; - - if (size > 0 && state == NULL) - { - rk_isaac_randomseed(&tempstate); - state = &tempstate; - } - - for (; size >= 4; size -= 4) - { - r = rk_isaac_random(state); - *(buf++) = r & 0xFF; - *(buf++) = (r >> 8) & 0xFF; - *(buf++) = (r >> 16) & 0xFF; - *(buf++) = (r >> 24) & 0xFF; - } - - if (!size) return; - - r = rk_isaac_random(state); - - for (; size; r >>= 8, size --) - *(buf++) = (unsigned char)(r & 0xFF); -} - -void rk_seed_isaac(rk_isaac_state *i_state, rk_state *state) -{ - rk_isaac_fill(state->key, sizeof(state->key), i_state); - state->pos = RK_STATE_LEN; - state->has_gauss = 0; -} diff --git a/tardis/montecarlo/src/randomkit/rk_isaac.h b/tardis/montecarlo/src/randomkit/rk_isaac.h deleted file mode 100644 index c8dfdcb451e..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_isaac.h +++ /dev/null @@ -1,142 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * ISAAC RNG by By Bob Jenkins. Based on Bob Jenkins public domain code. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* @(#) $Jeannot: rk_isaac.h,v 1.2 2006/02/19 14:40:26 js Exp $ */ - -/* - * Typical use: - * - * { - * rk_isaac_state state; - * unsigned long seed = 1, random_value; - * - * rk_isaac_seed(seed, &state); // Initialize the RNG - * ... - * random_value = rk_isaac_random(&state); // a random value in [0..RK_MAX] - * } - * - * Instead of rk_isaac_seed, you can use rk_isaac_randomseed which will get a - * random seed from /dev/random (or the clock, if /dev/random is unavailable): - * - * { - * rk_isaac_state state; - * unsigned long random_value; - * - * rk_isaac_randomseed(&state); // Initialize the RNG with a random seed - * ... - * random_value = rk_isaac_random(&state); // a random value in [0..RK_MAX] - * } - */ - -#ifndef _RK_ISAAC_ -#define _RK_ISAAC_ - -#include "rk_mt.h" - -#define RK_ISAAC_STATE_POW 8 -#define RK_ISAAC_STATE_LEN (1< -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -/* Windows */ -#include -#ifndef RK_NO_WINCRYPT -/* Windows crypto */ -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0400 -#endif -#include -#include -#endif -#else -/* Unix */ -#include -#include -#endif - -#include "randomkit.h" - -#ifndef RK_DEV_URANDOM -#define RK_DEV_URANDOM "/dev/urandom" -#endif - -#ifndef RK_DEV_RANDOM -#define RK_DEV_RANDOM "/dev/random" -#endif - -char *rk_strerror[RK_ERR_MAX] = -{ - "no error", - "random device unvavailable" -}; - -/* static functions */ -static unsigned long rk_hash(unsigned long key); - -void rk_knuth_fill(unsigned long seed, unsigned long *key, unsigned long len) -{ - int pos; - seed &= 0xffffffffUL; - - /* Knuth's PRNG as used in the Mersenne Twister reference implementation */ - for (pos=0; pos> 30)) + pos + 1) & 0xffffffffUL; - } -} - -void rk_seed(unsigned long seed, rk_state *state) -{ - rk_knuth_fill(seed, state->key, RK_STATE_LEN); - state->pos = RK_STATE_LEN; - state->has_gauss = 0; -} - -/* Thomas Wang 32 bits integer hash function */ -unsigned long rk_hash(unsigned long key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} - -unsigned long rk_seedfromsystem() -{ -#ifndef _WIN32 - struct timeval tv; -#else - struct _timeb tv; -#endif - -#ifndef _WIN32 - gettimeofday(&tv, NULL); - return rk_hash(getpid()) ^ rk_hash(tv.tv_sec) ^ rk_hash(tv.tv_usec) - ^ rk_hash(clock()); -#else - _ftime(&tv); - return rk_hash(tv.time) ^ rk_hash(tv.millitm) ^ rk_hash(clock()); -#endif -} - -rk_error rk_randomseed(rk_state *state) -{ - if(rk_devfill(state->key, sizeof(state->key), 0) == RK_NOERR) - { - state->key[0] |= 0x80000000UL; /* ensures non-zero key */ - state->pos = RK_STATE_LEN; - state->has_gauss = 0; - return RK_NOERR; - } - - rk_seed(rk_seedfromsystem(), state); - - return RK_ENODEV; -} - -/* Magic Mersenne Twister constants */ -#define N 624 -#define M 397 -#define MATRIX_A 0x9908b0dfUL -#define UPPER_MASK 0x80000000UL -#define LOWER_MASK 0x7fffffffUL - -/* Slightly optimised reference implementation of the Mersenne Twister */ -unsigned long rk_random(rk_state *state) -{ - unsigned long y; - - if (state->pos == RK_STATE_LEN) - { - int i; - - for (i=0;ikey[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); - state->key[i] = state->key[i+M] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); - } - for (;ikey[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); - state->key[i] = state->key[i+(M-N)] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); - } - y = (state->key[N-1] & UPPER_MASK) | (state->key[0] & LOWER_MASK); - state->key[N-1] = state->key[M-1] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); - - state->pos = 0; - } - - y = state->key[state->pos++]; - - /* Tempering */ - y ^= (y >> 11); - y ^= (y << 7) & 0x9d2c5680UL; - y ^= (y << 15) & 0xefc60000UL; - y ^= (y >> 18); - - return y; -} - -long rk_long(rk_state *state) -{ - return rk_ulong(state) >> 1; -} - -unsigned long rk_ulong(rk_state *state) -{ -#if ULONG_MAX <= 0xffffffffUL - return rk_random(state); -#else - /* Assumes 64 bits */ - return (rk_random(state) << 32) | (rk_random(state)); -#endif -} - -unsigned long rk_interval(unsigned long max, rk_state *state) -{ - unsigned long mask = max, value; - - if (max == 0) return 0; - - /* Smallest bit mask >= max */ - mask |= mask >> 1; - mask |= mask >> 2; - mask |= mask >> 4; - mask |= mask >> 8; - mask |= mask >> 16; -#if ULONG_MAX > 0xffffffffUL - mask |= mask >> 32; -#endif - - /* Search a random value in [0..mask] <= max */ - while ((value = (rk_ulong(state) & mask)) > max); - - return value; -} - -double rk_double(rk_state *state) -{ - /* shifts : 67108864 = 0x4000000, 9007199254740992 = 0x20000000000000 */ - long a = rk_random(state) >> 5, b = rk_random(state) >> 6; - return (a * 67108864.0 + b) / 9007199254740992.0; -} - -void rk_copy(rk_state *copy, rk_state *orig) -{ - memcpy(copy, orig, sizeof(rk_state)); -} - -void rk_fill(void *buffer, size_t size, rk_state *state) -{ - unsigned long r; - unsigned char *buf = buffer; - rk_state tempstate; - - if (size > 0 && state == NULL) - { - rk_randomseed(&tempstate); - state = &tempstate; - } - - for (; size >= 4; size -= 4) - { - r = rk_random(state); - *(buf++) = r & 0xFF; - *(buf++) = (r >> 8) & 0xFF; - *(buf++) = (r >> 16) & 0xFF; - *(buf++) = (r >> 24) & 0xFF; - } - - if (!size) return; - - r = rk_random(state); - - for (; size; r >>= 8, size --) - *(buf++) = (unsigned char)(r & 0xFF); -} - -rk_error rk_devfill(void *buffer, size_t size, int strong) -{ -#ifndef _WIN32 - FILE *rfile; - int done; - - if (strong) - rfile = fopen(RK_DEV_RANDOM, "rb"); - else - rfile = fopen(RK_DEV_URANDOM, "rb"); - if (rfile == NULL) - return RK_ENODEV; - done = fread(buffer, size, 1, rfile); - fclose(rfile); - if (done) - return RK_NOERR; -#else - -#ifndef RK_NO_WINCRYPT - HCRYPTPROV hCryptProv; - BOOL done; - - if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT) || !hCryptProv) - return RK_ENODEV; - done = CryptGenRandom(hCryptProv, size, (unsigned char *)buffer); - CryptReleaseContext(hCryptProv, 0); - if (done) - return RK_NOERR; -#endif - -#endif - - return RK_ENODEV; -} - -rk_error rk_altfill(void *buffer, size_t size, int strong, rk_state *state) -{ - rk_error err; - - err = rk_devfill(buffer, size, strong); - if (err) - rk_fill(buffer, size, state); - - return err; -} - -double rk_gauss(rk_state *state) -{ - if (state->has_gauss) - { - state->has_gauss = 0; - return state->gauss; - } - else - { - double f, x1, x2, r2; - do - { - x1 = 2.0*rk_double(state) - 1.0; - x2 = 2.0*rk_double(state) - 1.0; - r2 = x1*x1 + x2*x2; - } - while (r2 >= 1.0 || r2 == 0.0); - - f = sqrt(-2.0*log(r2)/r2); /* Box-Muller transform */ - state->has_gauss = 1; - state->gauss = f*x1; /* Keep for next call */ - return f*x2; - } -} diff --git a/tardis/montecarlo/src/randomkit/rk_mt.h b/tardis/montecarlo/src/randomkit/rk_mt.h deleted file mode 100644 index ff0e22a0879..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_mt.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Anyone who attempts to generate random numbers by deterministic means is, - * of course, living in a state of sin. - * -- John von Neumann - */ - -/* - * Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* @(#) $Jeannot: rk_mt.h,v 1.6 2006/02/20 19:13:37 js Exp $ */ - -/* - * Typical use: - * - * { - * rk_state state; - * unsigned long seed = 1, random_value; - * - * rk_seed(seed, &state); // Initialize the RNG - * ... - * random_value = rk_random(&state); // a random value in [0..RK_MAX] - * } - * - * Instead of rk_seed, you can use rk_randomseed which will get a random seed - * from /dev/urandom (or the clock, if /dev/urandom is unavailable): - * - * { - * rk_state state; - * unsigned long random_value; - * - * rk_randomseed(&state); // Initialize the RNG with a random seed - * ... - * random_value = rk_random(&state); // a random value in [0..RK_MAX] - * } - */ - -/* - * Useful macro: - * RK_DEV_RANDOM: the device used for random seeding. - * defaults to "/dev/urandom" - */ - -#include - -#ifndef _RK_MT_ -#define _RK_MT_ - -#define RK_STATE_LEN 624 - -typedef struct rk_state_ -{ - unsigned long key[RK_STATE_LEN]; - int pos; - int has_gauss; /* !=0: gauss contains a gaussian deviate */ - double gauss; -} -rk_state; - -typedef enum { - RK_NOERR = 0, /* no error */ - RK_ENODEV = 1, /* no RK_DEV_RANDOM device */ - RK_ERR_MAX = 2 -} rk_error; - -/* error strings */ -extern char *rk_strerror[RK_ERR_MAX]; - -/* Maximum generated random value */ -#define RK_MAX 0xFFFFFFFFUL - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Initialize the RNG state using the given seed. - */ -extern void rk_seed(unsigned long seed, rk_state *state); - -/* - * Initialize the RNG state using a random seed. - * Uses /dev/urandom or, when unavailable, the clock (see rk_mt.c). - * Returns RK_NOERR when no errors occurs. - * Returns RK_ENODEV when the use of RK_DEV_RANDOM failed (for example because - * there is no such device). In this case, the RNG was initialized using the - * clock. - */ -extern rk_error rk_randomseed(rk_state *state); - -/* - * Returns a random unsigned long between 0 and RK_MAX inclusive - */ -extern unsigned long rk_random(rk_state *state); - -/* - * Returns a random long between 0 and LONG_MAX inclusive - */ -extern long rk_long(rk_state *state); - -/* - * Returns a random unsigned long between 0 and ULONG_MAX inclusive - */ -extern unsigned long rk_ulong(rk_state *state); - -/* - * Returns a random unsigned long between 0 and max inclusive. - */ -extern unsigned long rk_interval(unsigned long max, rk_state *state); - -/* - * Returns a random double between 0.0 and 1.0, 1.0 excluded. - */ -extern double rk_double(rk_state *state); - -/* - * Copy a random generator state. - */ -extern void rk_copy(rk_state *copy, rk_state *orig); - -/* - * fill the buffer with size random bytes. - * If state == NULL, the random generator is inilialized using rk_randomseed. - * Calling multiple times rk_randomseed should be avoided therefore calling - * multiple times rk_fill with state == NULL should be avoided. - */ -extern void rk_fill(void *buffer, size_t size, rk_state *state); - -/* - * fill the buffer with randombytes from the random device - * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is - * On Unix, if strong is defined, RK_DEV_RANDOM is used. If not, RK_DEV_URANDOM - * is used instead. This parameter has no effect on Windows. - * Warning: on most unixes RK_DEV_RANDOM will wait for enough entropy to answer - * which can take a very long time on quiet systems. - */ -extern rk_error rk_devfill(void *buffer, size_t size, int strong); - -/* - * fill the buffer using rk_devfill if the random device is available and using - * rk_fill if is is not - * parameters have the same meaning as rk_fill and rk_devfill - * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is - */ -extern rk_error rk_altfill(void *buffer, size_t size, int strong, - rk_state *state); - -/* - * return a random gaussian deviate with variance unity and zero mean. - */ -extern double rk_gauss(rk_state *state); - -/* Utility functions */ - -/* - * fill the key vector using Knuth RNG as used in MT reference implementation - * using the provided seed. The key vector length is len. - */ -extern void rk_knuth_fill(unsigned long seed, unsigned long *key, - unsigned long len); - -/* - * return a random unsigned long based upon the system state (clock, pid) - * used as seed when there is no random device. - */ -extern unsigned long rk_seedfromsystem(void); - - -#ifdef __cplusplus -} -#endif - -#endif /* _RK_MT_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_primitive.c b/tardis/montecarlo/src/randomkit/rk_primitive.c deleted file mode 100644 index 25e70c6c7f5..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_primitive.c +++ /dev/null @@ -1,520 +0,0 @@ -/* Random kit 1.6 */ -/* Primitivity test for binary polynomials of low degree */ - -/* - * Copyright (c) 2005-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Methodology inspired by scott duplichan's ppsearch code. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -static char const rcsid[] = - "@(#) $Jeannot: rk_primitive.c,v 1.7 2006/02/19 13:48:34 js Exp $"; - -#include -#include -#include "rk_primitive.h" - -#ifndef LONG_BIT -#if ULONG_MAX <= 0xffffffffUL -#define LONG_BIT 32 -#else -#define LONG_BIT 64 -#endif -#endif - -static unsigned long modmul(unsigned long poly1, unsigned long poly2, - unsigned long modulo, unsigned long mask); -static unsigned long modpow(unsigned long polynomial, unsigned long power, - unsigned long modulo, int degree); - -/* - * For all powers i of two up to 64, list all the number of the form - * (2^i-1)/p for p a prime factor of 2^i-1. - */ -static const unsigned long divisors[][12]={ -/* 2^0-1 */ - {1UL, - 0UL}, -/* 2^1-1 */ - {1UL, - 0UL}, -/* 2^2-1 */ - {1UL, - 0UL}, -/* 2^3-1 */ - {1UL, - 0UL}, -/* 2^4-1 */ - {5UL, - 3UL, - 0UL}, -/* 2^5-1 */ - {1UL, - 0UL}, -/* 2^6-1 */ - {21UL, - 9UL, - 0UL}, -/* 2^7-1 */ - {1UL, - 0UL}, -/* 2^8-1 */ - {85UL, - 51UL, - 15UL, - 0UL}, -/* 2^9-1 */ - {73UL, - 7UL, - 0UL}, -/* 2^10-1 */ - {341UL, - 93UL, - 33UL, - 0UL}, -/* 2^11-1 */ - {89UL, - 23UL, - 0UL}, -/* 2^12-1 */ - {1365UL, - 819UL, - 585UL, - 315UL, - 0UL}, -/* 2^13-1 */ - {1UL, - 0UL}, -/* 2^14-1 */ - {5461UL, - 381UL, - 129UL, - 0UL}, -/* 2^15-1 */ - {4681UL, - 1057UL, - 217UL, - 0UL}, -/* 2^16-1 */ - {21845UL, - 13107UL, - 3855UL, - 255UL, - 0UL}, -/* 2^17-1 */ - {1UL, - 0UL}, -/* 2^18-1 */ - {87381UL, - 37449UL, - 13797UL, - 3591UL, - 0UL}, -/* 2^19-1 */ - {1UL, - 0UL}, -/* 2^20-1 */ - {349525UL, - 209715UL, - 95325UL, - 33825UL, - 25575UL, - 0UL}, -/* 2^21-1 */ - {299593UL, - 16513UL, - 6223UL, - 0UL}, -/* 2^22-1 */ - {1398101UL, - 182361UL, - 47127UL, - 6141UL, - 0UL}, -/* 2^23-1 */ - {178481UL, - 47UL, - 0UL}, -/* 2^24-1 */ - {5592405UL, - 3355443UL, - 2396745UL, - 1290555UL, - 986895UL, - 69615UL, - 0UL}, -/* 2^25-1 */ - {1082401UL, - 55831UL, - 18631UL, - 0UL}, -/* 2^26-1 */ - {22369621UL, - 24573UL, - 8193UL, - 0UL}, -/* 2^27-1 */ - {19173961UL, - 1838599UL, - 511UL, - 0UL}, -/* 2^28-1 */ - {89478485UL, - 53687091UL, - 9256395UL, - 6242685UL, - 2375535UL, - 2113665UL, - 0UL}, -/* 2^29-1 */ - {2304167UL, - 486737UL, - 256999UL, - 0UL}, -/* 2^30-1 */ - {357913941UL, - 153391689UL, - 97612893UL, - 34636833UL, - 7110873UL, - 3243933UL, - 0UL}, -/* 2^31-1 */ - {1UL, - 0UL}, -/* 2^32-1 */ - {1431655765UL, - 858993459UL, - 252645135UL, - 16711935UL, - 65535UL, - 0UL}, -#if LONG_BIT > 32 -/* 2^33-1 */ - {1227133513UL, - 373475417UL, - 96516119UL, - 14329UL, - 0UL}, -/* 2^34-1 */ - {5726623061UL, - 393213UL, - 131073UL, - 0UL}, -/* 2^35-1 */ - {1108378657UL, - 483939977UL, - 270549121UL, - 279527UL, - 0UL}, -/* 2^36-1 */ - {22906492245UL, - 13743895347UL, - 9817068105UL, - 5286113595UL, - 3616814565UL, - 1857283155UL, - 941362695UL, - 630453915UL, - 0UL}, -/* 2^37-1 */ - {616318177UL, - 223UL, - 0UL}, -/* 2^38-1 */ - {91625968981UL, - 1572861UL, - 524289UL, - 0UL}, -/* 2^39-1 */ - {78536544841UL, - 6958934353UL, - 67117057UL, - 4529623UL, - 0UL}, -/* 2^40-1 */ - {366503875925UL, - 219902325555UL, - 99955602525UL, - 64677154575UL, - 35468117025UL, - 26817356775UL, - 17825775UL, - 0UL}, -/* 2^41-1 */ - {164511353UL, - 13367UL, - 0UL}, -/* 2^42-1 */ - {1466015503701UL, - 628292358729UL, - 102280151421UL, - 34630287489UL, - 13050583119UL, - 811597437UL, - 0UL}, -/* 2^43-1 */ - {20408568497UL, - 905040953UL, - 4188889UL, - 0UL}, -/* 2^44-1 */ - {5864062014805UL, - 3518437208883UL, - 764877654105UL, - 197665011735UL, - 44312811195UL, - 25757227005UL, - 8325691455UL, - 0UL}, -/* 2^45-1 */ - {5026338869833UL, - 1134979744801UL, - 481977699847UL, - 233009086681UL, - 55759702201UL, - 1509346321UL, - 0UL}, -/* 2^46-1 */ - {23456248059221UL, - 1497207322929UL, - 394264623UL, - 25165821UL, - 0UL}, -/* 2^47-1 */ - {59862819377UL, - 31184907679UL, - 10610063UL, - 0UL}, -/* 2^48-1 */ - {93824992236885UL, - 56294995342131UL, - 40210710958665UL, - 21651921285435UL, - 16557351571215UL, - 2901803883615UL, - 1167945961455UL, - 1095233372415UL, - 418239192735UL, - 0UL}, -/* 2^49-1 */ - {4432676798593UL, - 127UL, - 0UL}, -/* 2^50-1 */ - {375299968947541UL, - 102354536985693UL, - 36319351833633UL, - 4485656999373UL, - 1873377548823UL, - 625152641223UL, - 277931351973UL, - 0UL}, -/* 2^51-1 */ - {321685687669321UL, - 21862134113449UL, - 1050769861729UL, - 202518195313UL, - 17180000257UL, - 0UL}, -/* 2^52-1 */ - {1501199875790165UL, - 900719925474099UL, - 84973577874915UL, - 28685347945035UL, - 2792064245115UL, - 1649066139645UL, - 549822930945UL, - 0UL}, -/* 2^53-1 */ - {1416003655831UL, - 129728784761UL, - 441650591UL, - 0UL}, -/* 2^54-1 */ - {6004799503160661UL, - 2573485501354569UL, - 948126237341157UL, - 246772582321671UL, - 206561081853UL, - 68585259519UL, - 0UL}, -/* 2^55-1 */ - {1566469435607129UL, - 1162219258676257UL, - 404817944033303UL, - 40895342813807UL, - 11290754314937UL, - 178394823847UL, - 0UL}, -/* 2^56-1 */ - {24019198012642645UL, - 14411518807585587UL, - 4238682002231055UL, - 2484744621997515UL, - 1675758000882045UL, - 637677823344495UL, - 567382630219905UL, - 4563402735UL, - 0UL}, -/* 2^57-1 */ - {20587884010836553UL, - 4451159405623UL, - 274878431233UL, - 118823881393UL, - 0UL}, -/* 2^58-1 */ - {96076792050570581UL, - 4885260612740877UL, - 1237040240994471UL, - 261314937580881UL, - 137975287770087UL, - 95026151247UL, - 0UL}, -/* 2^59-1 */ - {3203431780337UL, - 179951UL, - 0UL}, -/* 2^60-1 */ - {384307168202282325UL, - 230584300921369395UL, - 164703072086692425UL, - 104811045873349725UL, - 88686269585142075UL, - 37191016277640225UL, - 28120036697727975UL, - 18900352534538475UL, - 7635241752363225UL, - 3483146539597725UL, - 872764197279975UL, - 0UL}, -/* 2^61-1 */ - {1UL, - 0UL}, -/* 2^62-1 */ - {1537228672809129301UL, - 6442450941UL, - 2147483649UL, - 0UL}, -/* 2^63-1 */ - {1317624576693539401UL, - 126347562148695559UL, - 72624976668147841UL, - 27369056489183311UL, - 99457304386111UL, - 14197294936951UL, - 0UL}, -/* 2^64-1 */ - {6148914691236517205UL, - 3689348814741910323UL, - 1085102592571150095UL, - 71777214294589695UL, - 28778071877862015UL, - 281470681808895UL, - 2753074036095UL, - 0UL}, -#if LONG_BIT > 64 -#error Factorization of numbers up to 2^LONG_BIT required -#endif -#endif -}; - -/* - * Modular multiply for two binary polynomial - * mask is 1UL << the degree of the modulus. - */ -unsigned long modmul(unsigned long poly1, unsigned long poly2, - unsigned long modulo, unsigned long mask) -{ - unsigned long result = 0; - - for (; poly1; poly1 >>= 1) - { - if (poly1 & 1) - result ^= poly2; - - poly2 <<= 1; - if (poly2 & mask) - poly2 ^= modulo; - } - return result; -} - -/* - * Modular exponentiation for a binary polynomial - * degree is the degree of the modulus. - */ -unsigned long modpow(unsigned long polynomial, unsigned long power, - unsigned long modulo, int degree) -{ - unsigned long result = 1, mask = 1UL << degree; - - for (; power; power >>= 1) - { - if (power & 1) - result = modmul(result, polynomial, modulo, mask); - polynomial = modmul(polynomial, polynomial, modulo, mask); - } - return result; -} - -/* - * Test the primitivity of a polynomial - */ -int rk_isprimitive(unsigned long polynomial) -{ - unsigned long pelement = 2, temp = polynomial >> 1; - int k, degree = 0, weight = 1; - - /* Special case for polynomials of degree < 2 */ - if (polynomial < 4) - return (polynomial == 3) || (polynomial == 1); - - /* A binary primitive polynomial has a constant term */ - if (!(polynomial & 1)) - return 0; - - /* - * A binary primitive polynomial of degree > 1 has an odd number of terms. - * temp ^= temp >> 16; temp ^= temp >> 8; ... would be sligthly faster. - * Compute the degree at the same time. - */ - for (; temp; degree++, temp >>= 1) - weight += temp & 1; - if (!(weight & 1)) - return 0; - - /* - * Check if the period is 2^degree-1. - * Sufficient if 2^degree-1 is prime. - */ - if (modpow(pelement, 1UL << degree, polynomial, degree) != pelement) - return 0; - - if (divisors[degree][0] != 1) - /* Primitivity test */ - for (k = 0; divisors[degree][k]; k++) - if (modpow(pelement, divisors[degree][k], polynomial, degree) == 1) - return 0; - - return 1; -} diff --git a/tardis/montecarlo/src/randomkit/rk_primitive.h b/tardis/montecarlo/src/randomkit/rk_primitive.h deleted file mode 100644 index f7ab374cef5..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_primitive.h +++ /dev/null @@ -1,54 +0,0 @@ -/* Random kit 1.6 */ -/* Primitivity test for binary polynomials of low degree */ - -/* - * Copyright (c) 2005-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Methodology inspired by scott duplichan's ppsearch code. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* @(#) $Jeannot: rk_primitive.h,v 1.6 2006/02/19 13:48:34 js Exp $ */ - -#ifndef _RK_PRIMITIVE_ -#define _RK_PRIMITIVE_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Return 1 if the binary polynomial is primitive. - * - * Note that if p is primitive, the the polynomial obtained by reversing the - * bits of p is also primitive. (see list_primitive.c for an example) - * - * Typical use: - * int test; - * test = rk_isprimitive(3, &divisors); - */ -extern int rk_isprimitive(unsigned long polynomial); - -#ifdef __cplusplus -} -#endif - -#endif /* _RK_PRIMITIVE_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_sobol.c b/tardis/montecarlo/src/randomkit/rk_sobol.c deleted file mode 100644 index c6a016bb033..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_sobol.c +++ /dev/null @@ -1,991 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Original algorithm from Numerical Recipes, 2nd edition, by Press et al. - * The inverse normal cdf formulas are from Peter J. Acklam. - * The initialization directions were found in Ferdinando Ametrano's - * implementation in QuantLib. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -static char const rcsid[] = - "@(#) $Jeannot: rk_sobol.c,v 1.9 2006/02/19 13:48:34 js Exp $"; - -#include -#include -#include -#include "rk_sobol.h" -#include "rk_mt.h" -#include "rk_primitive.h" - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif -#ifndef M_SQRT1_2 -#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ -#endif -#define RK_SOBOL_M_SQRT2PI 2.506628274631000502415 /* sqrt(2*pi) */ - -#ifndef LONG_BIT -#if ULONG_MAX <= 0xffffffffUL -#define LONG_BIT 32 -#else -#define LONG_BIT 64 -#endif -#endif - -char *rk_sobol_strerror[] = -{ - "no error", - "invalid dimension", - "too many numbers generated", - "not enough memory" -}; - -static double inverse_normal(double p); - -/* - * Sobol/Levitan coefficients of the free direction integers as given - * by Bratley, P., Fox, B.L. (1988) - */ - -const unsigned long rk_sobol_SLdirections[] = { - 1, - 1, 1, - 1, 3, 7, - 1, 1, 5, - 1, 3, 1, 1, - 1, 1, 3, 7, - 1, 3, 3, 9, 9, - 1, 3, 7, 13, 3, - 1, 1, 5, 11, 27, - 1, 3, 5, 1, 15, - 1, 1, 7, 3, 29, - 1, 3, 7, 7, 21, - 1, 1, 1, 9, 23, 37, - 1, 3, 3, 5, 19, 33, - 1, 1, 3, 13, 11, 7, - 1, 1, 7, 13, 25, 5, - 1, 3, 5, 11, 7, 11, - 1, 1, 1, 3, 13, 39, - 1, 3, 1, 15, 17, 63, 13, - 1, 1, 5, 5, 1, 27, 33, - 1, 3, 3, 3, 25, 17, 115, - 1, 1, 3, 15, 29, 15, 41, - 1, 3, 1, 7, 3, 23, 79, - 1, 3, 7, 9, 31, 29, 17, - 1, 1, 5, 13, 11, 3, 29, - 1, 3, 1, 9, 5, 21, 119, - 1, 1, 3, 1, 23, 13, 75, - 1, 3, 3, 11, 27, 31, 73, - 1, 1, 7, 7, 19, 25, 105, - 1, 3, 5, 5, 21, 9, 7, - 1, 1, 1, 15, 5, 49, 59, - 1, 1, 1, 1, 1, 33, 65, - 1, 3, 5, 15, 17, 19, 21, - 1, 1, 7, 11, 13, 29, 3, - 1, 3, 7, 5, 7, 11, 113, - 1, 1, 5, 3, 15, 19, 61, - 1, 3, 1, 1, 9, 27, 89, 7, - 1, 1, 3, 7, 31, 15, 45, 23, - 1, 3, 3, 9, 9, 25, 107, 39, - 0 -}; - -/* - * Lemieux coefficients of the free direction integers as given - * in QuantLib by Christiane Lemieux, private communication, September 2004 - */ - -const unsigned long rk_sobol_Ldirections[] = { - 1, - 1, 1, - 1, 3, 7, - 1, 1, 5, - 1, 3, 1, 1, - 1, 1, 3, 7, - 1, 3, 3, 9, 9, - 1, 3, 7, 13, 3, - 1, 1, 5, 11, 27, - 1, 3, 5, 1, 15, - 1, 1, 7, 3, 29, - 1, 3, 7, 7, 21, - 1, 1, 1, 9, 23, 37, - 1, 3, 3, 5, 19, 33, - 1, 1, 3, 13, 11, 7, - 1, 1, 7, 13, 25, 5, - 1, 3, 5, 11, 7, 11, - 1, 1, 1, 3, 13, 39, - 1, 3, 1, 15, 17, 63, 13, - 1, 1, 5, 5, 1, 27, 33, - 1, 3, 3, 3, 25, 17, 115, - 1, 1, 3, 15, 29, 15, 41, - 1, 3, 1, 7, 3, 23, 79, - 1, 3, 7, 9, 31, 29, 17, - 1, 1, 5, 13, 11, 3, 29, - 1, 3, 1, 9, 5, 21, 119, - 1, 1, 3, 1, 23, 13, 75, - 1, 3, 3, 11, 27, 31, 73, - 1, 1, 7, 7, 19, 25, 105, - 1, 3, 5, 5, 21, 9, 7, - 1, 1, 1, 15, 5, 49, 59, - 1, 1, 1, 1, 1, 33, 65, - 1, 3, 5, 15, 17, 19, 21, - 1, 1, 7, 11, 13, 29, 3, - 1, 3, 7, 5, 7, 11, 113, - 1, 1, 5, 3, 15, 19, 61, - 1, 3, 1, 1, 9, 27, 89, 7, - 1, 1, 3, 7, 31, 15, 45, 23, - 1, 3, 3, 9, 9, 25, 107, 39, - 1, 1, 3, 13, 7, 35, 61, 91, - 1, 1, 7, 11, 5, 35, 55, 75, - 1, 3, 5, 5, 11, 23, 29, 139, - 1, 1, 1, 7, 11, 15, 17, 81, - 1, 1, 7, 9, 5, 57, 79, 103, - 1, 1, 7, 13, 19, 5, 5, 185, - 1, 3, 1, 3, 13, 57, 97, 131, - 1, 1, 5, 5, 21, 25, 125, 197, - 1, 3, 3, 9, 31, 11, 103, 201, - 1, 1, 5, 3, 7, 25, 51, 121, - 1, 3, 7, 15, 19, 53, 73, 189, - 1, 1, 1, 15, 19, 55, 27, 183, - 1, 1, 7, 13, 3, 29, 109, 69, - 1, 1, 5, 15, 15, 23, 15, 1, 57, - 1, 3, 1, 3, 23, 55, 43, 143, 397, - 1, 1, 3, 11, 29, 9, 35, 131, 411, - 1, 3, 1, 7, 27, 39, 103, 199, 277, - 1, 3, 7, 3, 19, 55, 127, 67, 449, - 1, 3, 7, 3, 5, 29, 45, 85, 3, - 1, 3, 5, 5, 13, 23, 75, 245, 453, - 1, 3, 1, 15, 21, 47, 3, 77, 165, - 1, 1, 7, 9, 15, 5, 117, 73, 473, - 1, 3, 1, 9, 1, 21, 13, 173, 313, - 1, 1, 7, 3, 11, 45, 63, 77, 49, - 1, 1, 1, 1, 1, 25, 123, 39, 259, - 1, 1, 1, 5, 23, 11, 59, 11, 203, - 1, 3, 3, 15, 21, 1, 73, 71, 421, - 1, 1, 5, 11, 15, 31, 115, 95, 217, - 1, 1, 3, 3, 7, 53, 37, 43, 439, - 1, 1, 1, 1, 27, 53, 69, 159, 321, - 1, 1, 5, 15, 29, 17, 19, 43, 449, - 1, 1, 3, 9, 1, 55, 121, 205, 255, - 1, 1, 3, 11, 9, 47, 107, 11, 417, - 1, 1, 1, 5, 17, 25, 21, 83, 95, - 1, 3, 5, 13, 31, 25, 61, 157, 407, - 1, 1, 7, 9, 25, 33, 41, 35, 17, - 1, 3, 7, 15, 13, 39, 61, 187, 461, - 1, 3, 7, 13, 5, 57, 23, 177, 435, - 1, 1, 3, 15, 11, 27, 115, 5, 337, - 1, 3, 7, 3, 15, 63, 61, 171, 339, - 1, 3, 3, 13, 15, 61, 59, 47, 1, - 1, 1, 5, 15, 13, 5, 39, 83, 329, - 1, 1, 5, 5, 5, 27, 25, 39, 301, - 1, 1, 5, 11, 31, 41, 35, 233, 27, - 1, 3, 5, 15, 7, 37, 119, 171, 419, - 1, 3, 5, 5, 3, 29, 21, 189, 417, - 1, 1, 1, 1, 21, 41, 117, 119, 351, - 1, 1, 3, 1, 7, 27, 87, 19, 213, - 1, 1, 1, 1, 17, 7, 97, 217, 477, - 1, 1, 7, 1, 29, 61, 103, 231, 269, - 1, 1, 7, 13, 9, 27, 107, 207, 311, - 1, 1, 7, 5, 25, 21, 107, 179, 423, - 1, 3, 5, 11, 7, 1, 17, 245, 281, - 1, 3, 5, 9, 1, 5, 53, 59, 125, - 1, 1, 7, 1, 31, 57, 71, 245, 125, - 1, 1, 7, 5, 5, 57, 53, 253, 441, - 1, 3, 1, 13, 19, 35, 119, 235, 381, - 1, 3, 1, 7, 19, 59, 115, 33, 361, - 1, 1, 3, 5, 13, 1, 49, 143, 501, - 1, 1, 3, 5, 1, 63, 101, 85, 189, - 1, 1, 5, 11, 27, 63, 13, 131, 5, - 1, 1, 5, 7, 15, 45, 75, 59, 455, 585, - 1, 3, 1, 3, 7, 7, 111, 23, 119, 959, - 1, 3, 3, 9, 11, 41, 109, 163, 161, 879, - 1, 3, 5, 1, 21, 41, 121, 183, 315, 219, - 1, 1, 3, 9, 15, 3, 9, 223, 441, 929, - 1, 1, 7, 9, 3, 5, 93, 57, 253, 457, - 1, 1, 7, 13, 15, 29, 83, 21, 35, 45, - 1, 1, 3, 7, 13, 61, 119, 219, 85, 505, - 1, 1, 3, 3, 17, 13, 35, 197, 291, 109, - 1, 1, 3, 3, 5, 1, 113, 103, 217, 253, - 1, 1, 7, 1, 15, 39, 63, 223, 17, 9, - 1, 3, 7, 1, 17, 29, 67, 103, 495, 383, - 1, 3, 3, 15, 31, 59, 75, 165, 51, 913, - 1, 3, 7, 9, 5, 27, 79, 219, 233, 37, - 1, 3, 5, 15, 1, 11, 15, 211, 417, 811, - 1, 3, 5, 3, 29, 27, 39, 137, 407, 231, - 1, 1, 3, 5, 29, 43, 125, 135, 109, 67, - 1, 1, 1, 5, 11, 39, 107, 159, 323, 381, - 1, 1, 1, 1, 9, 11, 33, 55, 169, 253, - 1, 3, 5, 5, 11, 53, 63, 101, 251, 897, - 1, 3, 7, 1, 25, 15, 83, 119, 53, 157, - 1, 3, 5, 13, 5, 5, 3, 195, 111, 451, - 1, 3, 1, 15, 11, 1, 19, 11, 307, 777, - 1, 3, 7, 11, 5, 5, 17, 231, 345, 981, - 1, 1, 3, 3, 1, 33, 83, 201, 57, 475, - 1, 3, 7, 7, 17, 13, 35, 175, 499, 809, - 1, 1, 5, 3, 3, 17, 103, 119, 499, 865, - 1, 1, 1, 11, 27, 25, 37, 121, 401, 11, - 1, 1, 1, 11, 9, 25, 25, 241, 403, 3, - 1, 1, 1, 1, 11, 1, 39, 163, 231, 573, - 1, 1, 1, 13, 13, 21, 75, 185, 99, 545, - 1, 1, 1, 15, 3, 63, 69, 11, 173, 315, - 1, 3, 5, 15, 11, 3, 95, 49, 123, 765, - 1, 1, 1, 15, 3, 63, 77, 31, 425, 711, - 1, 1, 7, 15, 1, 37, 119, 145, 489, 583, - 1, 3, 5, 15, 3, 49, 117, 211, 165, 323, - 1, 3, 7, 1, 27, 63, 77, 201, 225, 803, - 1, 1, 1, 11, 23, 35, 67, 21, 469, 357, - 1, 1, 7, 7, 9, 7, 25, 237, 237, 571, - 1, 1, 3, 15, 29, 5, 107, 109, 241, 47, - 1, 3, 5, 11, 27, 63, 29, 13, 203, 675, - 1, 1, 3, 9, 9, 11, 103, 179, 449, 263, - 1, 3, 5, 11, 29, 63, 53, 151, 259, 223, - 1, 1, 3, 7, 9, 25, 5, 197, 237, 163, - 1, 3, 7, 13, 5, 57, 67, 193, 147, 241, - 1, 1, 5, 15, 15, 33, 17, 67, 161, 341, - 1, 1, 3, 13, 17, 43, 21, 197, 441, 985, - 1, 3, 1, 5, 15, 33, 33, 193, 305, 829, - 1, 1, 1, 13, 19, 27, 71, 187, 477, 239, - 1, 1, 1, 9, 9, 17, 41, 177, 229, 983, - 1, 3, 5, 9, 15, 45, 97, 205, 43, 767, - 1, 1, 1, 9, 31, 31, 77, 159, 395, 809, - 1, 3, 3, 3, 29, 19, 73, 123, 165, 307, - 1, 3, 1, 7, 5, 11, 77, 227, 355, 403, - 1, 3, 5, 5, 25, 31, 1, 215, 451, 195, - 1, 3, 7, 15, 29, 37, 101, 241, 17, 633, - 1, 1, 5, 1, 11, 3, 107, 137, 489, 5, - 1, 1, 1, 7, 19, 19, 75, 85, 471, 355, - 1, 1, 3, 3, 9, 13, 113, 167, 13, 27, - 1, 3, 5, 11, 21, 3, 89, 205, 377, 307, - 1, 1, 1, 9, 31, 61, 65, 9, 391, 141, 867, - 1, 1, 1, 9, 19, 19, 61, 227, 241, 55, 161, - 1, 1, 1, 11, 1, 19, 7, 233, 463, 171, 1941, - 1, 1, 5, 7, 25, 13, 103, 75, 19, 1021, 1063, - 1, 1, 1, 15, 17, 17, 79, 63, 391, 403, 1221, - 1, 3, 3, 11, 29, 25, 29, 107, 335, 475, 963, - 1, 3, 5, 1, 31, 33, 49, 43, 155, 9, 1285, - 1, 1, 5, 5, 15, 47, 39, 161, 357, 863, 1039, - 1, 3, 7, 15, 1, 39, 47, 109, 427, 393, 1103, - 1, 1, 1, 9, 9, 29, 121, 233, 157, 99, 701, - 1, 1, 1, 7, 1, 29, 75, 121, 439, 109, 993, - 1, 1, 1, 9, 5, 1, 39, 59, 89, 157, 1865, - 1, 1, 5, 1, 3, 37, 89, 93, 143, 533, 175, - 1, 1, 3, 5, 7, 33, 35, 173, 159, 135, 241, - 1, 1, 1, 15, 17, 37, 79, 131, 43, 891, 229, - 1, 1, 1, 1, 1, 35, 121, 177, 397, 1017, 583, - 1, 1, 3, 15, 31, 21, 43, 67, 467, 923, 1473, - 1, 1, 1, 7, 1, 33, 77, 111, 125, 771, 1975, - 1, 3, 7, 13, 1, 51, 113, 139, 245, 573, 503, - 1, 3, 1, 9, 21, 49, 15, 157, 49, 483, 291, - 1, 1, 1, 1, 29, 35, 17, 65, 403, 485, 1603, - 1, 1, 1, 7, 19, 1, 37, 129, 203, 321, 1809, - 1, 3, 7, 15, 15, 9, 5, 77, 29, 485, 581, - 1, 1, 3, 5, 15, 49, 97, 105, 309, 875, 1581, - 1, 3, 5, 1, 5, 19, 63, 35, 165, 399, 1489, - 1, 3, 5, 3, 23, 5, 79, 137, 115, 599, 1127, - 1, 1, 7, 5, 3, 61, 27, 177, 257, 91, 841, - 1, 1, 3, 5, 9, 31, 91, 209, 409, 661, 159, - 1, 3, 1, 15, 23, 39, 23, 195, 245, 203, 947, - 1, 1, 3, 1, 15, 59, 67, 95, 155, 461, 147, - 1, 3, 7, 5, 23, 25, 87, 11, 51, 449, 1631, - 1, 1, 1, 1, 17, 57, 7, 197, 409, 609, 135, - 1, 1, 1, 9, 1, 61, 115, 113, 495, 895, 1595, - 1, 3, 7, 15, 9, 47, 121, 211, 379, 985, 1755, - 1, 3, 1, 3, 7, 57, 27, 231, 339, 325, 1023, - 1, 1, 1, 1, 19, 63, 63, 239, 31, 643, 373, - 1, 3, 1, 11, 19, 9, 7, 171, 21, 691, 215, - 1, 1, 5, 13, 11, 57, 39, 211, 241, 893, 555, - 1, 1, 7, 5, 29, 21, 45, 59, 509, 223, 491, - 1, 1, 7, 9, 15, 61, 97, 75, 127, 779, 839, - 1, 1, 7, 15, 17, 33, 75, 237, 191, 925, 681, - 1, 3, 5, 7, 27, 57, 123, 111, 101, 371, 1129, - 1, 3, 5, 5, 29, 45, 59, 127, 229, 967, 2027, - 1, 1, 1, 1, 17, 7, 23, 199, 241, 455, 135, - 1, 1, 7, 15, 27, 29, 105, 171, 337, 503, 1817, - 1, 1, 3, 7, 21, 35, 61, 71, 405, 647, 2045, - 1, 1, 1, 1, 1, 15, 65, 167, 501, 79, 737, - 1, 1, 5, 1, 3, 49, 27, 189, 341, 615, 1287, - 1, 1, 1, 9, 1, 7, 31, 159, 503, 327, 1613, - 1, 3, 3, 3, 3, 23, 99, 115, 323, 997, 987, - 1, 1, 1, 9, 19, 33, 93, 247, 509, 453, 891, - 1, 1, 3, 1, 13, 19, 35, 153, 161, 633, 445, - 1, 3, 5, 15, 31, 5, 87, 197, 183, 783, 1823, - 1, 1, 7, 5, 19, 63, 69, 221, 129, 231, 1195, - 1, 1, 5, 5, 13, 23, 19, 231, 245, 917, 379, - 1, 3, 1, 15, 19, 43, 27, 223, 171, 413, 125, - 1, 1, 1, 9, 1, 59, 21, 15, 509, 207, 589, - 1, 3, 5, 3, 19, 31, 113, 19, 23, 733, 499, - 1, 1, 7, 1, 19, 51, 101, 165, 47, 925, 1093, - 1, 3, 3, 9, 15, 21, 43, 243, 237, 461, 1361, - 1, 1, 1, 9, 17, 15, 75, 75, 113, 715, 1419, - 1, 1, 7, 13, 17, 1, 99, 15, 347, 721, 1405, - 1, 1, 7, 15, 7, 27, 23, 183, 39, 59, 571, - 1, 3, 5, 9, 7, 43, 35, 165, 463, 567, 859, - 1, 3, 3, 11, 15, 19, 17, 129, 311, 343, 15, - 1, 1, 1, 15, 31, 59, 63, 39, 347, 359, 105, - 1, 1, 1, 15, 5, 43, 87, 241, 109, 61, 685, - 1, 1, 7, 7, 9, 39, 121, 127, 369, 579, 853, - 1, 1, 1, 1, 17, 15, 15, 95, 325, 627, 299, - 1, 1, 3, 13, 31, 53, 85, 111, 289, 811, 1635, - 1, 3, 7, 1, 19, 29, 75, 185, 153, 573, 653, - 1, 3, 7, 1, 29, 31, 55, 91, 249, 247, 1015, - 1, 3, 5, 7, 1, 49, 113, 139, 257, 127, 307, - 1, 3, 5, 9, 15, 15, 123, 105, 105, 225, 1893, - 1, 3, 3, 1, 15, 5, 105, 249, 73, 709, 1557, - 1, 1, 1, 9, 17, 31, 113, 73, 65, 701, 1439, - 1, 3, 5, 15, 13, 21, 117, 131, 243, 859, 323, - 1, 1, 1, 9, 19, 15, 69, 149, 89, 681, 515, - 1, 1, 1, 5, 29, 13, 21, 97, 301, 27, 967, - 1, 1, 3, 3, 15, 45, 107, 227, 495, 769, 1935, - 1, 1, 1, 11, 5, 27, 41, 173, 261, 703, 1349, - 1, 3, 3, 3, 11, 35, 97, 43, 501, 563, 1331, - 1, 1, 1, 7, 1, 17, 87, 17, 429, 245, 1941, - 1, 1, 7, 15, 29, 13, 1, 175, 425, 233, 797, - 1, 1, 3, 11, 21, 57, 49, 49, 163, 685, 701, - 1, 3, 3, 7, 11, 45, 107, 111, 379, 703, 1403, - 1, 1, 7, 3, 21, 7, 117, 49, 469, 37, 775, - 1, 1, 5, 15, 31, 63, 101, 77, 507, 489, 1955, - 1, 3, 3, 11, 19, 21, 101, 255, 203, 673, 665, - 1, 3, 3, 15, 17, 47, 125, 187, 271, 899, 2003, - 1, 1, 7, 7, 1, 35, 13, 235, 5, 337, 905, - 1, 3, 1, 15, 1, 43, 1, 27, 37, 695, 1429, - 1, 3, 1, 11, 21, 27, 93, 161, 299, 665, 495, - 1, 3, 3, 15, 3, 1, 81, 111, 105, 547, 897, - 1, 3, 5, 1, 3, 53, 97, 253, 401, 827, 1467, - 1, 1, 1, 5, 19, 59, 105, 125, 271, 351, 719, - 1, 3, 5, 13, 7, 11, 91, 41, 441, 759, 1827, - 1, 3, 7, 11, 29, 61, 61, 23, 307, 863, 363, - 1, 1, 7, 1, 15, 35, 29, 133, 415, 473, 1737, - 1, 1, 1, 13, 7, 33, 35, 225, 117, 681, 1545, - 1, 1, 1, 3, 5, 41, 83, 247, 13, 373, 1091, - 1, 3, 1, 13, 25, 61, 71, 217, 233, 313, 547, - 1, 3, 1, 7, 3, 29, 3, 49, 93, 465, 15, - 1, 1, 1, 9, 17, 61, 99, 163, 129, 485, 1087, - 1, 1, 1, 9, 9, 33, 31, 163, 145, 649, 253, - 1, 1, 1, 1, 17, 63, 43, 235, 287, 111, 567, - 1, 3, 5, 13, 29, 7, 11, 69, 153, 127, 449, - 1, 1, 5, 9, 11, 21, 15, 189, 431, 493, 1219, - 1, 1, 1, 15, 19, 5, 47, 91, 399, 293, 1743, - 1, 3, 3, 11, 29, 53, 53, 225, 409, 303, 333, - 1, 1, 1, 15, 31, 31, 21, 81, 147, 287, 1753, - 1, 3, 5, 5, 5, 63, 35, 125, 41, 687, 1793, - 1, 1, 1, 9, 19, 59, 107, 219, 455, 971, 297, - 1, 1, 3, 5, 3, 51, 121, 31, 245, 105, 1311, - 1, 3, 1, 5, 5, 57, 75, 107, 161, 431, 1693, - 1, 3, 1, 3, 19, 53, 27, 31, 191, 565, 1015, - 1, 3, 5, 13, 9, 41, 35, 249, 287, 49, 123, - 1, 1, 5, 7, 27, 17, 21, 3, 151, 885, 1165, - 1, 1, 7, 1, 15, 17, 65, 139, 427, 339, 1171, - 1, 1, 1, 5, 23, 5, 9, 89, 321, 907, 391, - 1, 1, 7, 9, 15, 1, 77, 71, 87, 701, 917, - 1, 1, 7, 1, 17, 37, 115, 127, 469, 779, 1543, - 1, 3, 7, 3, 5, 61, 15, 37, 301, 951, 1437, - 1, 1, 1, 13, 9, 51, 127, 145, 229, 55, 1567, - 1, 3, 7, 15, 19, 47, 53, 153, 295, 47, 1337, - 1, 3, 3, 5, 11, 31, 29, 133, 327, 287, 507, - 1, 1, 7, 7, 25, 31, 37, 199, 25, 927, 1317, - 1, 1, 7, 9, 3, 39, 127, 167, 345, 467, 759, - 1, 1, 1, 1, 31, 21, 15, 101, 293, 787, 1025, - 1, 1, 5, 3, 11, 41, 105, 109, 149, 837, 1813, - 1, 1, 3, 5, 29, 13, 19, 97, 309, 901, 753, - 1, 1, 7, 1, 19, 17, 31, 39, 173, 361, 1177, - 1, 3, 3, 3, 3, 41, 81, 7, 341, 491, 43, - 1, 1, 7, 7, 31, 35, 29, 77, 11, 335, 1275, - 1, 3, 3, 15, 17, 45, 19, 63, 151, 849, 129, - 1, 1, 7, 5, 7, 13, 47, 73, 79, 31, 499, - 1, 3, 1, 11, 1, 41, 59, 151, 247, 115, 1295, - 1, 1, 1, 9, 31, 37, 73, 23, 295, 483, 179, - 1, 3, 1, 15, 13, 63, 81, 27, 169, 825, 2037, - 1, 3, 5, 15, 7, 11, 73, 1, 451, 101, 2039, - 1, 3, 5, 3, 13, 53, 31, 137, 173, 319, 1521, - 1, 3, 1, 3, 29, 1, 73, 227, 377, 337, 1189, - 1, 3, 3, 13, 27, 9, 31, 101, 229, 165, 1983, - 1, 3, 1, 13, 13, 19, 19, 111, 319, 421, 223, - 1, 1, 7, 15, 25, 37, 61, 55, 359, 255, 1955, - 1, 1, 5, 13, 17, 43, 49, 215, 383, 915, 51, - 1, 1, 3, 1, 3, 7, 13, 119, 155, 585, 967, - 1, 3, 1, 13, 1, 63, 125, 21, 103, 287, 457, - 1, 1, 7, 1, 31, 17, 125, 137, 345, 379, 1925, - 1, 1, 3, 5, 5, 25, 119, 153, 455, 271, 2023, - 1, 1, 7, 9, 9, 37, 115, 47, 5, 255, 917, - 1, 3, 5, 3, 31, 21, 75, 203, 489, 593, 1, - 1, 3, 7, 15, 19, 63, 123, 153, 135, 977, 1875, - 1, 1, 1, 1, 5, 59, 31, 25, 127, 209, 745, - 1, 1, 1, 1, 19, 45, 67, 159, 301, 199, 535, - 1, 1, 7, 1, 31, 17, 19, 225, 369, 125, 421, - 1, 3, 3, 11, 7, 59, 115, 197, 459, 469, 1055, - 1, 3, 1, 3, 27, 45, 35, 131, 349, 101, 411, - 1, 3, 7, 11, 9, 3, 67, 145, 299, 253, 1339, - 1, 3, 3, 11, 9, 37, 123, 229, 273, 269, 515, - 1, 3, 7, 15, 11, 25, 75, 5, 367, 217, 951, - 1, 1, 3, 7, 9, 23, 63, 237, 385, 159, 1273, - 1, 1, 5, 11, 23, 5, 55, 193, 109, 865, 663, - 1, 1, 7, 15, 1, 57, 17, 141, 51, 217, 1259, - 1, 1, 3, 3, 15, 7, 89, 233, 71, 329, 203, - 1, 3, 7, 11, 11, 1, 19, 155, 89, 437, 573, - 1, 3, 1, 9, 27, 61, 47, 109, 161, 913, 1681, - 1, 1, 7, 15, 1, 33, 19, 15, 23, 913, 989, - 1, 3, 1, 1, 25, 39, 119, 193, 13, 571, 157, - 1, 1, 7, 13, 9, 55, 59, 147, 361, 935, 515, - 1, 1, 1, 9, 7, 59, 67, 117, 71, 855, 1493, - 1, 3, 1, 3, 13, 19, 57, 141, 305, 275, 1079, - 1, 1, 1, 9, 17, 61, 33, 7, 43, 931, 781, - 1, 1, 3, 1, 11, 17, 21, 97, 295, 277, 1721, - 1, 3, 1, 13, 15, 43, 11, 241, 147, 391, 1641, - 1, 1, 1, 1, 1, 19, 37, 21, 255, 263, 1571, - 1, 1, 3, 3, 23, 59, 89, 17, 475, 303, 757, 543, - 1, 3, 3, 9, 11, 55, 35, 159, 139, 203, 1531, 1825, - 1, 1, 5, 3, 17, 53, 51, 241, 269, 949, 1373, 325, - 1, 3, 7, 7, 5, 29, 91, 149, 239, 193, 1951, 2675, - 1, 3, 5, 1, 27, 33, 69, 11, 51, 371, 833, 2685, - 1, 1, 1, 15, 1, 17, 35, 57, 171, 1007, 449, 367, - 1, 1, 1, 7, 25, 61, 73, 219, 379, 53, 589, 4065, - 1, 3, 5, 13, 21, 29, 45, 19, 163, 169, 147, 597, - 1, 1, 5, 11, 21, 27, 7, 17, 237, 591, 255, 1235, - 1, 1, 7, 7, 17, 41, 69, 237, 397, 173, 1229, 2341, - 1, 1, 3, 1, 1, 33, 125, 47, 11, 783, 1323, 2469, - 1, 3, 1, 11, 3, 39, 35, 133, 153, 55, 1171, 3165, - 1, 1, 5, 11, 27, 23, 103, 245, 375, 753, 477, 2165, - 1, 3, 1, 15, 15, 49, 127, 223, 387, 771, 1719, 1465, - 1, 1, 1, 9, 11, 9, 17, 185, 239, 899, 1273, 3961, - 1, 1, 3, 13, 11, 51, 73, 81, 389, 647, 1767, 1215, - 1, 3, 5, 15, 19, 9, 69, 35, 349, 977, 1603, 1435, - 1, 1, 1, 1, 19, 59, 123, 37, 41, 961, 181, 1275, - 1, 1, 1, 1, 31, 29, 37, 71, 205, 947, 115, 3017, - 1, 1, 7, 15, 5, 37, 101, 169, 221, 245, 687, 195, - 1, 1, 1, 1, 19, 9, 125, 157, 119, 283, 1721, 743, - 1, 1, 7, 3, 1, 7, 61, 71, 119, 257, 1227, 2893, - 1, 3, 3, 3, 25, 41, 25, 225, 31, 57, 925, 2139, - 0 -}; - - -/* - * coefficients of the free direction integers as given in - * "Monte Carlo Methods in Finance", by Peter Jäckel, section 8.3 - */ - -const unsigned long rk_sobol_Jdirections[] = { - 1, - 1, 1, - 1, 3, 7, - 1, 1, 5, - 1, 3, 1, 1, - 1, 1, 3, 7, - 1, 3, 3, 9, 9, - 1, 3, 7, 7, 21, - 1, 1, 5, 11, 27, - 1, 1, 7, 3, 29, - 1, 3, 7, 13, 3, - 1, 3, 5, 1, 15, - 1, 1, 1, 9, 23, 37, - 1, 1, 3, 13, 11, 7, - 1, 3, 3, 5, 19, 33, - 1, 1, 7, 13, 25, 5, - 1, 1, 1, 3, 13, 39, - 1, 3, 5, 11, 7, 11, - 1, 3, 1, 7, 3, 23, 79, - 1, 3, 1, 15, 17, 63, 13, - 1, 3, 3, 3, 25, 17, 115, - 1, 3, 7, 9, 31, 29, 17, - 1, 1, 3, 15, 29, 15, 41, - 1, 3, 1, 9, 5, 21, 119, - 1, 1, 5, 5, 1, 27, 33, - 1, 1, 3, 1, 23, 13, 75, - 1, 1, 7, 7, 19, 25, 105, - 1, 3, 5, 5, 21, 9, 7, - 1, 1, 1, 15, 5, 49, 59, - 1, 3, 5, 15, 17, 19, 21, - 1, 1, 7, 11, 13, 29, 3, - 0 -}; - -/* - * 0 terminated list of primitive polynomials to speed up initialization - * All polynomials up to degree 13 (ie. 1111 polynomials) - */ -static const unsigned long rk_sobol_primitive_polynomials[] = { - 0x1UL, 0x3UL, 0x7UL, 0xBUL, 0xDUL, 0x13UL, 0x19UL, 0x25UL, 0x29UL, - 0x2FUL, 0x37UL, 0x3BUL, 0x3DUL, 0x43UL, 0x5BUL, 0x61UL, 0x67UL, 0x6DUL, - 0x73UL, 0x83UL, 0x89UL, 0x8FUL, 0x91UL, 0x9DUL, 0xA7UL, 0xABUL, 0xB9UL, - 0xBFUL, 0xC1UL, 0xCBUL, 0xD3UL, 0xD5UL, 0xE5UL, 0xEFUL, 0xF1UL, 0xF7UL, - 0xFDUL, 0x11DUL, 0x12BUL, 0x12DUL, 0x14DUL, 0x15FUL, 0x163UL, 0x165UL, - 0x169UL, 0x171UL, 0x187UL, 0x18DUL, 0x1A9UL, 0x1C3UL, 0x1CFUL, 0x1E7UL, - 0x1F5UL, 0x211UL, 0x21BUL, 0x221UL, 0x22DUL, 0x233UL, 0x259UL, 0x25FUL, - 0x269UL, 0x26FUL, 0x277UL, 0x27DUL, 0x287UL, 0x295UL, 0x2A3UL, 0x2A5UL, - 0x2AFUL, 0x2B7UL, 0x2BDUL, 0x2CFUL, 0x2D1UL, 0x2DBUL, 0x2F5UL, 0x2F9UL, - 0x313UL, 0x315UL, 0x31FUL, 0x323UL, 0x331UL, 0x33BUL, 0x34FUL, 0x35BUL, - 0x361UL, 0x36BUL, 0x36DUL, 0x373UL, 0x37FUL, 0x385UL, 0x38FUL, 0x3B5UL, - 0x3B9UL, 0x3C7UL, 0x3CBUL, 0x3CDUL, 0x3D5UL, 0x3D9UL, 0x3E3UL, 0x3E9UL, - 0x3FBUL, 0x409UL, 0x41BUL, 0x427UL, 0x42DUL, 0x465UL, 0x46FUL, 0x481UL, - 0x48BUL, 0x4C5UL, 0x4D7UL, 0x4E7UL, 0x4F3UL, 0x4FFUL, 0x50DUL, 0x519UL, - 0x523UL, 0x531UL, 0x53DUL, 0x543UL, 0x557UL, 0x56BUL, 0x585UL, 0x58FUL, - 0x597UL, 0x5A1UL, 0x5C7UL, 0x5E5UL, 0x5F7UL, 0x5FBUL, 0x613UL, 0x615UL, - 0x625UL, 0x637UL, 0x643UL, 0x64FUL, 0x65BUL, 0x679UL, 0x67FUL, 0x689UL, - 0x6B5UL, 0x6C1UL, 0x6D3UL, 0x6DFUL, 0x6FDUL, 0x717UL, 0x71DUL, 0x721UL, - 0x739UL, 0x747UL, 0x74DUL, 0x755UL, 0x759UL, 0x763UL, 0x77DUL, 0x78DUL, - 0x793UL, 0x7B1UL, 0x7DBUL, 0x7F3UL, 0x7F9UL, 0x805UL, 0x817UL, 0x82BUL, - 0x82DUL, 0x847UL, 0x863UL, 0x865UL, 0x871UL, 0x87BUL, 0x88DUL, 0x895UL, - 0x89FUL, 0x8A9UL, 0x8B1UL, 0x8CFUL, 0x8D1UL, 0x8E1UL, 0x8E7UL, 0x8EBUL, - 0x8F5UL, 0x90DUL, 0x913UL, 0x925UL, 0x929UL, 0x93BUL, 0x93DUL, 0x945UL, - 0x949UL, 0x951UL, 0x95BUL, 0x973UL, 0x975UL, 0x97FUL, 0x983UL, 0x98FUL, - 0x9ABUL, 0x9ADUL, 0x9B9UL, 0x9C7UL, 0x9D9UL, 0x9E5UL, 0x9F7UL, 0xA01UL, - 0xA07UL, 0xA13UL, 0xA15UL, 0xA29UL, 0xA49UL, 0xA61UL, 0xA6DUL, 0xA79UL, - 0xA7FUL, 0xA85UL, 0xA91UL, 0xA9DUL, 0xAA7UL, 0xAABUL, 0xAB3UL, 0xAB5UL, - 0xAD5UL, 0xADFUL, 0xAE9UL, 0xAEFUL, 0xAF1UL, 0xAFBUL, 0xB03UL, 0xB09UL, - 0xB11UL, 0xB33UL, 0xB3FUL, 0xB41UL, 0xB4BUL, 0xB59UL, 0xB5FUL, 0xB65UL, - 0xB6FUL, 0xB7DUL, 0xB87UL, 0xB8BUL, 0xB93UL, 0xB95UL, 0xBAFUL, 0xBB7UL, - 0xBBDUL, 0xBC9UL, 0xBDBUL, 0xBDDUL, 0xBE7UL, 0xBEDUL, 0xC0BUL, 0xC0DUL, - 0xC19UL, 0xC1FUL, 0xC57UL, 0xC61UL, 0xC6BUL, 0xC73UL, 0xC85UL, 0xC89UL, - 0xC97UL, 0xC9BUL, 0xC9DUL, 0xCB3UL, 0xCBFUL, 0xCC7UL, 0xCCDUL, 0xCD3UL, - 0xCD5UL, 0xCE3UL, 0xCE9UL, 0xCF7UL, 0xD03UL, 0xD0FUL, 0xD1DUL, 0xD27UL, - 0xD2DUL, 0xD41UL, 0xD47UL, 0xD55UL, 0xD59UL, 0xD63UL, 0xD6FUL, 0xD71UL, - 0xD93UL, 0xD9FUL, 0xDA9UL, 0xDBBUL, 0xDBDUL, 0xDC9UL, 0xDD7UL, 0xDDBUL, - 0xDE1UL, 0xDE7UL, 0xDF5UL, 0xE05UL, 0xE1DUL, 0xE21UL, 0xE27UL, 0xE2BUL, - 0xE33UL, 0xE39UL, 0xE47UL, 0xE4BUL, 0xE55UL, 0xE5FUL, 0xE71UL, 0xE7BUL, - 0xE7DUL, 0xE81UL, 0xE93UL, 0xE9FUL, 0xEA3UL, 0xEBBUL, 0xECFUL, 0xEDDUL, - 0xEF3UL, 0xEF9UL, 0xF0BUL, 0xF19UL, 0xF31UL, 0xF37UL, 0xF5DUL, 0xF6BUL, - 0xF6DUL, 0xF75UL, 0xF83UL, 0xF91UL, 0xF97UL, 0xF9BUL, 0xFA7UL, 0xFADUL, - 0xFB5UL, 0xFCDUL, 0xFD3UL, 0xFE5UL, 0xFE9UL, 0x1053UL, 0x1069UL, - 0x107BUL, 0x107DUL, 0x1099UL, 0x10D1UL, 0x10EBUL, 0x1107UL, 0x111FUL, - 0x1123UL, 0x113BUL, 0x114FUL, 0x1157UL, 0x1161UL, 0x116BUL, 0x1185UL, - 0x11B3UL, 0x11D9UL, 0x11DFUL, 0x120DUL, 0x1237UL, 0x123DUL, 0x1267UL, - 0x1273UL, 0x127FUL, 0x12B9UL, 0x12C1UL, 0x12CBUL, 0x130FUL, 0x131DUL, - 0x1321UL, 0x1339UL, 0x133FUL, 0x134DUL, 0x1371UL, 0x1399UL, 0x13A3UL, - 0x13A9UL, 0x1407UL, 0x1431UL, 0x1437UL, 0x144FUL, 0x145DUL, 0x1467UL, - 0x1475UL, 0x14A7UL, 0x14ADUL, 0x14D3UL, 0x150FUL, 0x151DUL, 0x154DUL, - 0x1593UL, 0x15C5UL, 0x15D7UL, 0x15DDUL, 0x15EBUL, 0x1609UL, 0x1647UL, - 0x1655UL, 0x1659UL, 0x16A5UL, 0x16BDUL, 0x1715UL, 0x1719UL, 0x1743UL, - 0x1745UL, 0x1775UL, 0x1789UL, 0x17ADUL, 0x17B3UL, 0x17BFUL, 0x17C1UL, - 0x1857UL, 0x185DUL, 0x1891UL, 0x1897UL, 0x18B9UL, 0x18EFUL, 0x191BUL, - 0x1935UL, 0x1941UL, 0x1965UL, 0x197BUL, 0x198BUL, 0x19B1UL, 0x19BDUL, - 0x19C9UL, 0x19CFUL, 0x19E7UL, 0x1A1BUL, 0x1A2BUL, 0x1A33UL, 0x1A69UL, - 0x1A8BUL, 0x1AD1UL, 0x1AE1UL, 0x1AF5UL, 0x1B0BUL, 0x1B13UL, 0x1B1FUL, - 0x1B57UL, 0x1B91UL, 0x1BA7UL, 0x1BBFUL, 0x1BC1UL, 0x1BD3UL, 0x1C05UL, - 0x1C11UL, 0x1C17UL, 0x1C27UL, 0x1C4DUL, 0x1C87UL, 0x1C9FUL, 0x1CA5UL, - 0x1CBBUL, 0x1CC5UL, 0x1CC9UL, 0x1CCFUL, 0x1CF3UL, 0x1D07UL, 0x1D23UL, - 0x1D43UL, 0x1D51UL, 0x1D5BUL, 0x1D75UL, 0x1D85UL, 0x1D89UL, 0x1E15UL, - 0x1E19UL, 0x1E2FUL, 0x1E45UL, 0x1E51UL, 0x1E67UL, 0x1E73UL, 0x1E8FUL, - 0x1EE3UL, 0x1F11UL, 0x1F1BUL, 0x1F27UL, 0x1F71UL, 0x1F99UL, 0x1FBBUL, - 0x1FBDUL, 0x1FC9UL, 0x201BUL, 0x2027UL, 0x2035UL, 0x2053UL, 0x2065UL, - 0x206FUL, 0x208BUL, 0x208DUL, 0x209FUL, 0x20A5UL, 0x20AFUL, 0x20BBUL, - 0x20BDUL, 0x20C3UL, 0x20C9UL, 0x20E1UL, 0x20F3UL, 0x210DUL, 0x2115UL, - 0x2129UL, 0x212FUL, 0x213BUL, 0x2143UL, 0x2167UL, 0x216BUL, 0x2179UL, - 0x2189UL, 0x2197UL, 0x219DUL, 0x21BFUL, 0x21C1UL, 0x21C7UL, 0x21CDUL, - 0x21DFUL, 0x21E3UL, 0x21F1UL, 0x21FBUL, 0x2219UL, 0x2225UL, 0x2237UL, - 0x223DUL, 0x2243UL, 0x225BUL, 0x225DUL, 0x2279UL, 0x227FUL, 0x2289UL, - 0x2297UL, 0x229BUL, 0x22B3UL, 0x22BFUL, 0x22CDUL, 0x22EFUL, 0x22F7UL, - 0x22FBUL, 0x2305UL, 0x2327UL, 0x232BUL, 0x2347UL, 0x2355UL, 0x2359UL, - 0x236FUL, 0x2371UL, 0x237DUL, 0x2387UL, 0x238DUL, 0x2395UL, 0x23A3UL, - 0x23A9UL, 0x23B1UL, 0x23B7UL, 0x23BBUL, 0x23E1UL, 0x23EDUL, 0x23F9UL, - 0x240BUL, 0x2413UL, 0x241FUL, 0x2425UL, 0x2429UL, 0x243DUL, 0x2451UL, - 0x2457UL, 0x2461UL, 0x246DUL, 0x247FUL, 0x2483UL, 0x249BUL, 0x249DUL, - 0x24B5UL, 0x24BFUL, 0x24C1UL, 0x24C7UL, 0x24CBUL, 0x24E3UL, 0x2509UL, - 0x2517UL, 0x251DUL, 0x2521UL, 0x252DUL, 0x2539UL, 0x2553UL, 0x2555UL, - 0x2563UL, 0x2571UL, 0x2577UL, 0x2587UL, 0x258BUL, 0x2595UL, 0x2599UL, - 0x259FUL, 0x25AFUL, 0x25BDUL, 0x25C5UL, 0x25CFUL, 0x25D7UL, 0x25EBUL, - 0x2603UL, 0x2605UL, 0x2611UL, 0x262DUL, 0x263FUL, 0x264BUL, 0x2653UL, - 0x2659UL, 0x2669UL, 0x2677UL, 0x267BUL, 0x2687UL, 0x2693UL, 0x2699UL, - 0x26B1UL, 0x26B7UL, 0x26BDUL, 0x26C3UL, 0x26EBUL, 0x26F5UL, 0x2713UL, - 0x2729UL, 0x273BUL, 0x274FUL, 0x2757UL, 0x275DUL, 0x276BUL, 0x2773UL, - 0x2779UL, 0x2783UL, 0x2791UL, 0x27A1UL, 0x27B9UL, 0x27C7UL, 0x27CBUL, - 0x27DFUL, 0x27EFUL, 0x27F1UL, 0x2807UL, 0x2819UL, 0x281FUL, 0x2823UL, - 0x2831UL, 0x283BUL, 0x283DUL, 0x2845UL, 0x2867UL, 0x2875UL, 0x2885UL, - 0x28ABUL, 0x28ADUL, 0x28BFUL, 0x28CDUL, 0x28D5UL, 0x28DFUL, 0x28E3UL, - 0x28E9UL, 0x28FBUL, 0x2909UL, 0x290FUL, 0x2911UL, 0x291BUL, 0x292BUL, - 0x2935UL, 0x293FUL, 0x2941UL, 0x294BUL, 0x2955UL, 0x2977UL, 0x297DUL, - 0x2981UL, 0x2993UL, 0x299FUL, 0x29AFUL, 0x29B7UL, 0x29BDUL, 0x29C3UL, - 0x29D7UL, 0x29F3UL, 0x29F5UL, 0x2A03UL, 0x2A0FUL, 0x2A1DUL, 0x2A21UL, - 0x2A33UL, 0x2A35UL, 0x2A4DUL, 0x2A69UL, 0x2A6FUL, 0x2A71UL, 0x2A7BUL, - 0x2A7DUL, 0x2AA5UL, 0x2AA9UL, 0x2AB1UL, 0x2AC5UL, 0x2AD7UL, 0x2ADBUL, - 0x2AEBUL, 0x2AF3UL, 0x2B01UL, 0x2B15UL, 0x2B23UL, 0x2B25UL, 0x2B2FUL, - 0x2B37UL, 0x2B43UL, 0x2B49UL, 0x2B6DUL, 0x2B7FUL, 0x2B85UL, 0x2B97UL, - 0x2B9BUL, 0x2BADUL, 0x2BB3UL, 0x2BD9UL, 0x2BE5UL, 0x2BFDUL, 0x2C0FUL, - 0x2C21UL, 0x2C2BUL, 0x2C2DUL, 0x2C3FUL, 0x2C41UL, 0x2C4DUL, 0x2C71UL, - 0x2C8BUL, 0x2C8DUL, 0x2C95UL, 0x2CA3UL, 0x2CAFUL, 0x2CBDUL, 0x2CC5UL, - 0x2CD1UL, 0x2CD7UL, 0x2CE1UL, 0x2CE7UL, 0x2CEBUL, 0x2D0DUL, 0x2D19UL, - 0x2D29UL, 0x2D2FUL, 0x2D37UL, 0x2D3BUL, 0x2D45UL, 0x2D5BUL, 0x2D67UL, - 0x2D75UL, 0x2D89UL, 0x2D8FUL, 0x2DA7UL, 0x2DABUL, 0x2DB5UL, 0x2DE3UL, - 0x2DF1UL, 0x2DFDUL, 0x2E07UL, 0x2E13UL, 0x2E15UL, 0x2E29UL, 0x2E49UL, - 0x2E4FUL, 0x2E5BUL, 0x2E5DUL, 0x2E61UL, 0x2E6BUL, 0x2E8FUL, 0x2E91UL, - 0x2E97UL, 0x2E9DUL, 0x2EABUL, 0x2EB3UL, 0x2EB9UL, 0x2EDFUL, 0x2EFBUL, - 0x2EFDUL, 0x2F05UL, 0x2F09UL, 0x2F11UL, 0x2F17UL, 0x2F3FUL, 0x2F41UL, - 0x2F4BUL, 0x2F4DUL, 0x2F59UL, 0x2F5FUL, 0x2F65UL, 0x2F69UL, 0x2F95UL, - 0x2FA5UL, 0x2FAFUL, 0x2FB1UL, 0x2FCFUL, 0x2FDDUL, 0x2FE7UL, 0x2FEDUL, - 0x2FF5UL, 0x2FFFUL, 0x3007UL, 0x3015UL, 0x3019UL, 0x302FUL, 0x3049UL, - 0x304FUL, 0x3067UL, 0x3079UL, 0x307FUL, 0x3091UL, 0x30A1UL, 0x30B5UL, - 0x30BFUL, 0x30C1UL, 0x30D3UL, 0x30D9UL, 0x30E5UL, 0x30EFUL, 0x3105UL, - 0x310FUL, 0x3135UL, 0x3147UL, 0x314DUL, 0x315FUL, 0x3163UL, 0x3171UL, - 0x317BUL, 0x31A3UL, 0x31A9UL, 0x31B7UL, 0x31C5UL, 0x31C9UL, 0x31DBUL, - 0x31E1UL, 0x31EBUL, 0x31EDUL, 0x31F3UL, 0x31FFUL, 0x3209UL, 0x320FUL, - 0x321DUL, 0x3227UL, 0x3239UL, 0x324BUL, 0x3253UL, 0x3259UL, 0x3265UL, - 0x3281UL, 0x3293UL, 0x3299UL, 0x329FUL, 0x32A9UL, 0x32B7UL, 0x32BBUL, - 0x32C3UL, 0x32D7UL, 0x32DBUL, 0x32E7UL, 0x3307UL, 0x3315UL, 0x332FUL, - 0x3351UL, 0x335DUL, 0x3375UL, 0x3397UL, 0x339BUL, 0x33ABUL, 0x33B9UL, - 0x33C1UL, 0x33C7UL, 0x33D5UL, 0x33E3UL, 0x33E5UL, 0x33F7UL, 0x33FBUL, - 0x3409UL, 0x341BUL, 0x3427UL, 0x3441UL, 0x344DUL, 0x345FUL, 0x3469UL, - 0x3477UL, 0x347BUL, 0x3487UL, 0x3493UL, 0x3499UL, 0x34A5UL, 0x34BDUL, - 0x34C9UL, 0x34DBUL, 0x34E7UL, 0x34F9UL, 0x350DUL, 0x351FUL, 0x3525UL, - 0x3531UL, 0x3537UL, 0x3545UL, 0x354FUL, 0x355DUL, 0x356DUL, 0x3573UL, - 0x357FUL, 0x359DUL, 0x35A1UL, 0x35B9UL, 0x35CDUL, 0x35D5UL, 0x35D9UL, - 0x35E3UL, 0x35E9UL, 0x35EFUL, 0x3601UL, 0x360BUL, 0x361FUL, 0x3625UL, - 0x362FUL, 0x363BUL, 0x3649UL, 0x3651UL, 0x365BUL, 0x3673UL, 0x3675UL, - 0x3691UL, 0x369BUL, 0x369DUL, 0x36ADUL, 0x36CBUL, 0x36D3UL, 0x36D5UL, - 0x36E3UL, 0x36EFUL, 0x3705UL, 0x370FUL, 0x371BUL, 0x3721UL, 0x372DUL, - 0x3739UL, 0x3741UL, 0x3747UL, 0x3753UL, 0x3771UL, 0x3777UL, 0x378BUL, - 0x3795UL, 0x3799UL, 0x37A3UL, 0x37C5UL, 0x37CFUL, 0x37D1UL, 0x37D7UL, - 0x37DDUL, 0x37E1UL, 0x37F3UL, 0x3803UL, 0x3805UL, 0x3817UL, 0x381DUL, - 0x3827UL, 0x3833UL, 0x384BUL, 0x3859UL, 0x3869UL, 0x3871UL, 0x38A3UL, - 0x38B1UL, 0x38BBUL, 0x38C9UL, 0x38CFUL, 0x38E1UL, 0x38F3UL, 0x38F9UL, - 0x3901UL, 0x3907UL, 0x390BUL, 0x3913UL, 0x3931UL, 0x394FUL, 0x3967UL, - 0x396DUL, 0x3983UL, 0x3985UL, 0x3997UL, 0x39A1UL, 0x39A7UL, 0x39ADUL, - 0x39CBUL, 0x39CDUL, 0x39D3UL, 0x39EFUL, 0x39F7UL, 0x39FDUL, 0x3A07UL, - 0x3A29UL, 0x3A2FUL, 0x3A3DUL, 0x3A51UL, 0x3A5DUL, 0x3A61UL, 0x3A67UL, - 0x3A73UL, 0x3A75UL, 0x3A89UL, 0x3AB9UL, 0x3ABFUL, 0x3ACDUL, 0x3AD3UL, - 0x3AD5UL, 0x3ADFUL, 0x3AE5UL, 0x3AE9UL, 0x3AFBUL, 0x3B11UL, 0x3B2BUL, - 0x3B2DUL, 0x3B35UL, 0x3B3FUL, 0x3B53UL, 0x3B59UL, 0x3B63UL, 0x3B65UL, - 0x3B6FUL, 0x3B71UL, 0x3B77UL, 0x3B8BUL, 0x3B99UL, 0x3BA5UL, 0x3BA9UL, - 0x3BB7UL, 0x3BBBUL, 0x3BD1UL, 0x3BE7UL, 0x3BF3UL, 0x3BFFUL, 0x3C0DUL, - 0x3C13UL, 0x3C15UL, 0x3C1FUL, 0x3C23UL, 0x3C25UL, 0x3C3BUL, 0x3C4FUL, - 0x3C5DUL, 0x3C6DUL, 0x3C83UL, 0x3C8FUL, 0x3C9DUL, 0x3CA7UL, 0x3CABUL, - 0x3CB9UL, 0x3CC7UL, 0x3CE9UL, 0x3CFBUL, 0x3CFDUL, 0x3D03UL, 0x3D17UL, - 0x3D1BUL, 0x3D21UL, 0x3D2DUL, 0x3D33UL, 0x3D35UL, 0x3D41UL, 0x3D4DUL, - 0x3D65UL, 0x3D69UL, 0x3D7DUL, 0x3D81UL, 0x3D95UL, 0x3DB1UL, 0x3DB7UL, - 0x3DC3UL, 0x3DD1UL, 0x3DDBUL, 0x3DE7UL, 0x3DEBUL, 0x3DF9UL, 0x3E05UL, - 0x3E09UL, 0x3E0FUL, 0x3E1BUL, 0x3E2BUL, 0x3E3FUL, 0x3E41UL, 0x3E53UL, - 0x3E65UL, 0x3E69UL, 0x3E8BUL, 0x3EA3UL, 0x3EBDUL, 0x3EC5UL, 0x3ED7UL, - 0x3EDDUL, 0x3EE1UL, 0x3EF9UL, 0x3F0DUL, 0x3F19UL, 0x3F1FUL, 0x3F25UL, - 0x3F37UL, 0x3F3DUL, 0x3F43UL, 0x3F45UL, 0x3F49UL, 0x3F51UL, 0x3F57UL, - 0x3F61UL, 0x3F83UL, 0x3F89UL, 0x3F91UL, 0x3FABUL, 0x3FB5UL, 0x3FE3UL, - 0x3FF7UL, 0x3FFDUL, - 0UL -}; - -rk_sobol_error rk_sobol_init(size_t dimension, rk_sobol_state *s, - rk_state *rs_dir, const unsigned long *directions, - const unsigned long *polynomials) -{ - rk_state rs_dir_temp; - int j, l, degree = 0, last_degree = 0, ooord = 0; - size_t k, cdir = 0, cpol = 0; - unsigned long polynomial = 1, rev = 0, last = 0; - - if (dimension == 0) - return RK_SOBOL_EINVAL; - - if (polynomials == NULL) - polynomials = rk_sobol_primitive_polynomials; - - /* Allocate the structure */ - s->direction = NULL; s->numerator = NULL; - s->direction = malloc(sizeof(*(s->direction))*dimension*LONG_BIT); - s->numerator = malloc(sizeof(*(s->numerator))*dimension); - if (!s->direction | !s->numerator) - { - if (!s->direction) free(s->direction); - if (!s->numerator) free(s->numerator); - return RK_SOBOL_ENOMEM; - } - - /* Initialize directions */ - /* Degree 0 */ - for (j = degree; j < LONG_BIT; j++) - s->direction[j*dimension] = 1UL << (LONG_BIT-j-1); - - /* Skip unused first polynomial */ - if (polynomials[cpol]) - cpol++; - - /* Degree >0 */ - for (k = 1; k < dimension; k++) - { - unsigned long temp; - - /* Find a new primitive polynomial */ - if (polynomials[cpol]) - polynomial = polynomials[cpol++]; - else if (rev) - { - /* We are generating polynomials out of order: - use the reverse of the previous polynomial */ - last = polynomial; - polynomial = rev; - rev = 0; - } - else - { - if (last) - { - polynomial = last; - last = 0; - } - - /* Find a new primitive polynomial */ - while(1) - { - if (polynomial == ULONG_MAX) - { - /* Not enough polynomials */ - free(s->direction); - free(s->numerator); - return RK_SOBOL_EINVAL; - } - - polynomial += 2; - - if (ooord) - { - unsigned long copy = polynomial; - /* We are generating polynomials out of order: - check if the reverse was already checked */ - for (rev = 0; copy; copy >>= 1) - rev = (rev << 1) | (copy & 1); - if (ooord && rev < polynomial) - continue; - } - - if (rk_isprimitive(polynomial)) - break; - } - - if (rev == polynomial) - /* We are generating polynomials out of order: - the reverse is not different, discard it */ - rev = 0; - } - - /* Compute the degree */ - for (temp = polynomial >> 1, degree = 0; temp; degree++, temp >>= 1); - - for (j=0; jdirection[j*dimension+k] = m << (LONG_BIT-j-1); - } - - /* Scaled recursion for directions */ - for (j = degree; j < LONG_BIT; j++) - { - unsigned long effdir = s->direction[(j-degree)*dimension+k], - ptemp = polynomial >> 1; - effdir ^= (effdir >> degree); - for (l = degree-1; l >= 1; l--, ptemp >>= 1) - if (ptemp & 1) - effdir ^= s->direction[(j-l)*dimension+k]; - s->direction[j*dimension+k] = effdir; - } - - /* Can we generate polynomials out of order ? */ - if (!ooord && polynomials[cpol] == 0 && degree > last_degree - && (directions == NULL || directions[cdir] == 0)) - ooord = 0; - else - last_degree = degree; - } - - /* Initialize numerator */ - for (k=0; knumerator[k] = 0; - - s->dimension = dimension; - s->gcount = 0; - s->count = 0; - return RK_SOBOL_OK; -} - -void rk_sobol_reinit(rk_sobol_state *s) -{ - size_t k; - - /* Initialize numerator */ - for (k=0; kdimension; k++) - s->numerator[k] = 0; - - s->count = 0; - s->gcount = 0; -} - -void rk_sobol_randomshift(rk_sobol_state *s, rk_state *rs_num) -{ - rk_state rs_num_temp; - size_t k; - - if (rs_num == NULL) - { - rs_num = &rs_num_temp; - rk_randomseed(rs_num); - } - - /* Initialize numerator */ - for (k=0; kdimension; k++) - s->numerator[k] = rk_ulong(rs_num); -} - -rk_sobol_error rk_sobol_copy(rk_sobol_state *copy, rk_sobol_state *orig) -{ - size_t k; - - /* Allocate the structure */ - copy->direction = NULL; copy->numerator = NULL; - copy->direction = malloc(sizeof(*(copy->direction))*orig->dimension*LONG_BIT); - copy->numerator = malloc(sizeof(*(copy->numerator))*orig->dimension); - if (!copy->direction | !copy->numerator) - { - if (!copy->direction) free(copy->direction); - if (!copy->numerator) free(copy->numerator); - return RK_SOBOL_ENOMEM; - } - - /* Initialize numerator */ - for (k=0; kdimension; k++) - copy->numerator[k] = orig->numerator[k]; - for (k=0; k<(orig->dimension*LONG_BIT); k++) - copy->direction[k] = orig->direction[k]; - - copy->count = orig->count; - copy->gcount = orig->gcount; - copy->dimension = orig->dimension; - - return RK_SOBOL_OK; -} - -rk_sobol_error rk_sobol_double(rk_sobol_state *s, double *x) -{ - int j; - size_t k; - unsigned long im; - const double inverse_denominator=1.0/(ULONG_MAX+1.0); - - if (s->count == ULONG_MAX) - j = 0; - else - for (im = s->count, j=0; im & 1; j++, im >>= 1); - s->count++; - - for (k=0; kdimension; k++) - { - s->numerator[k] ^= s->direction[j*s->dimension+k]; - x[k] = s->numerator[k]*inverse_denominator; - } - - if ((s->gcount++) == ULONG_MAX) return RK_SOBOL_EXHAUST; - return RK_SOBOL_OK; -} - -void rk_sobol_setcount(rk_sobol_state *s, unsigned long count) -{ - s->count = count; -} - -void rk_sobol_free(rk_sobol_state *s) -{ - free(s->direction); - free(s->numerator); -} - -double inverse_normal(double p) -{ - double q, t, x; - - /* Peter J. Acklam constants for the rational approximation */ - const double a[6] = - { - -3.969683028665376e+01, 2.209460984245205e+02, - -2.759285104469687e+02, 1.383577518672690e+02, - -3.066479806614716e+01, 2.506628277459239e+00 - }; - const double b[5] = - { - -5.447609879822406e+01, 1.615858368580409e+02, - -1.556989798598866e+02, 6.680131188771972e+01, - -1.328068155288572e+01 - }; - const double c[6] = - { - -7.784894002430293e-03, -3.223964580411365e-01, - -2.400758277161838e+00, -2.549732539343734e+00, - 4.374664141464968e+00, 2.938163982698783e+00 - }; - const double d[4] = - { - 7.784695709041462e-03, 3.224671290700398e-01, - 2.445134137142996e+00, 3.754408661907416e+00 - }; - - if (p <= 0) - return -HUGE_VAL; - else if (p >= 1) - return HUGE_VAL; - - q = p<0.5 ? p : 1-p; - if (q > 0.02425) - { - /* Rational approximation for central region */ - x = q-0.5; - t = x*x; - x = x*(((((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4])*t+a[5]) - /(((((b[0]*t+b[1])*t+b[2])*t+b[3])*t+b[4])*t+1); - } - else - { - /* Rational approximation for tail region */ - t = sqrt(-2*log(q)); - x = (((((c[0]*t+c[1])*t+c[2])*t+c[3])*t+c[4])*t+c[5]) - /((((d[0]*t+d[1])*t+d[2])*t+d[3])*t+1); - } - - /* If we have erfc, improve the precision */ -#ifndef WIN32 - /* Halley's rational method */ - t = (erfc(-x*M_SQRT1_2)/2 - q) * RK_SOBOL_M_SQRT2PI * exp(x*x/2); - x -= t/(1 + x*t/2); -#endif - - return p>0.5 ? -x : x; -} - -rk_sobol_error rk_sobol_gauss(rk_sobol_state *s, double *x) -{ - size_t k; - rk_sobol_error rc = rk_sobol_double(s, x); - - for (k=0; kdimension; k++) - x[k] = inverse_normal(x[k]); - - return rc; -} diff --git a/tardis/montecarlo/src/randomkit/rk_sobol.h b/tardis/montecarlo/src/randomkit/rk_sobol.h deleted file mode 100644 index aca71bebe1e..00000000000 --- a/tardis/montecarlo/src/randomkit/rk_sobol.h +++ /dev/null @@ -1,173 +0,0 @@ -/* Random kit 1.6 */ - -/* - * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* @(#) $Jeannot: rk_sobol.h,v 1.7 2006/02/19 13:48:34 js Exp $ */ - -/* - * Typical use: - * - * int dimension = 2; - * rk_sobol_state s; - * rk_sobol_error rc; - * double x[dimension], y[dimension]; - * - * // Init - * if (rc = rk_sobol_init(dimension, &s, NULL, NULL, NULL)) - * { - * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); - * abort(); - * } - * - * // Draw uniform quasirandom doubles - * if (rc = rk_sobol_double(&s, x)) - * { - * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); - * abort(); - * } - * - * // Draw gaussian quasirandom doubles - * if (rc = rk_sobol_gauss(&s, y)) - * { - * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); - * abort(); - * } - * - * // Free allocated memory - * rk_sobol_free(&s); - */ - - -#ifndef _RK_SOBOL_ -#define _RK_SOBOL_ - -#include "rk_mt.h" - -typedef enum { - RK_SOBOL_OK = 0, /* No error */ - RK_SOBOL_EINVAL = 1, /* Invalid dimension (<= 0 or too large) */ - RK_SOBOL_EXHAUST = 2, /* Too many number generated */ - RK_SOBOL_ENOMEM = 3, /* Not enough memory */ - RK_SOBOL_ERR_MAX = 4 -} rk_sobol_error; - -/* error strings */ -extern char *rk_sobol_strerror[]; - -typedef struct -{ - size_t dimension; - unsigned long *direction; - unsigned long *numerator; - unsigned long count; - unsigned long gcount; -} rk_sobol_state; - -#ifdef __cplusplus -extern "C" { -#endif - -/* Sobol directions initializations (zero terminated lists) */ - -/* - * Sobol/Levitan coefficients of the free direction integers as given - * by Bratley, P., Fox, B.L. (1988) - * Defined up to dimension 40. - */ -extern const unsigned long rk_sobol_SLdirections[]; - -/* - * Lemieux coefficients of the free direction integers as given - * in QuantLib by Christiane Lemieux, private communication, September 2004 - * Defined up to dimension 360. - */ -extern const unsigned long rk_sobol_Ldirections[]; - -/* - * Peter Jäckel coefficients of the free direction integers as given - * in "Monte Carlo Methods in Finance", by Peter Jäckel, section 8.3 - * Defined up to dimension 32. - */ -extern const unsigned long rk_sobol_Jdirections[]; - -/* - * Initialize a sobol quasirandom number generator. - * 1 <= dimension <= the number of primitive polylonimals of degree < LONG_BIT - * If directions == NULL (or more directions than provided are required), - * the directions are picked at random using rs_dir. - * If rs_dir == NULL, it is initialized using rk_randomseed. - * polynomials is a zero terminated list of primitive polynomials to use if - * it is != NULL to speed up initialization for dimension > 1024. - */ -extern rk_sobol_error rk_sobol_init(size_t dimension, rk_sobol_state *s, - rk_state *rs_dir, const unsigned long *directions, - const unsigned long *polynomials); - -/* - * Reinitialize the random generator with same directions. - */ -extern void rk_sobol_reinit(rk_sobol_state *s); - -/* - * You can change the starting rank in the sequence by changing s->count. - */ -extern void rk_sobol_setcount(rk_sobol_state *s, unsigned long count); - -/* - * XOR the numerators at random using rs_num. - * To be used once, after (re-)initialization. - * Useful for randomized quasi monte carlo. - * If rs_num == NULL, it is initialized using rk_randomseed. - */ -extern void rk_sobol_randomshift(rk_sobol_state *s, rk_state *rs_num); - -/* - * Copy a sobol generator. - * Can be used to avoid the time consuming initialization. - */ -extern rk_sobol_error rk_sobol_copy(rk_sobol_state *copy, rk_sobol_state *orig); - -/* - * Free the memory allocated by rk_sobol_init - */ -extern void rk_sobol_free(rk_sobol_state *s); - -/* - * return a vector of dimension quasirandom uniform deviates between 0 and 1 - */ -extern rk_sobol_error rk_sobol_double(rk_sobol_state *s, double *x); - -/* - * return a vector of dimension quasirandom gaussian deviates - * with variance unity and zero mean. - * On Windows, the standard function erfc is missing, which results in - * lower precision (9 digits instead of full precision). - */ -extern rk_sobol_error rk_sobol_gauss(rk_sobol_state *s, double *x); - -#ifdef __cplusplus -} -#endif - -#endif /* _RK_SOBOL_ */ diff --git a/tardis/montecarlo/src/rpacket.c b/tardis/montecarlo/src/rpacket.c deleted file mode 100644 index cb52cddc698..00000000000 --- a/tardis/montecarlo/src/rpacket.c +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include "rpacket.h" -#include "storage.h" - -extern tardis_error_t line_search (const double *nu, double nu_insert, - int64_t number_of_lines, int64_t * result); - -tardis_error_t -rpacket_init (rpacket_t * packet, storage_model_t * storage, int packet_index, - int virtual_packet_flag, double * chi_bf_tmp_partial) -{ - int64_t current_line_id; - tardis_error_t ret_val = TARDIS_ERROR_OK; - double current_nu = storage->packet_nus[packet_index]; - double current_energy = storage->packet_energies[packet_index]; - double current_mu = storage->packet_mus[packet_index]; - double comov_current_nu = current_nu; - int current_shell_id = 0; - double current_r = storage->r_inner[0]; - double beta = current_r * storage->inverse_time_explosion * INVERSE_C; - - if (storage->full_relativity) - { - current_nu = current_nu * (1 + beta * current_mu) / sqrt(1 - beta * beta); - current_energy = current_energy * (1 + beta * current_mu) / sqrt(1 - beta * beta); - current_mu = (current_mu + beta) / (1 + beta * current_mu); - } - else - { - current_nu = current_nu / (1 - beta * current_mu); - current_energy = current_energy / (1 - beta * current_mu); - } - if ((ret_val = - line_search (storage->line_list_nu, comov_current_nu, - storage->no_of_lines, - ¤t_line_id)) != TARDIS_ERROR_OK) - { - return ret_val; - } - bool last_line = (current_line_id == storage->no_of_lines); - rpacket_set_nu (packet, current_nu); - rpacket_set_mu (packet, current_mu); - rpacket_set_energy (packet, current_energy); - rpacket_set_r (packet, current_r); - rpacket_set_current_shell_id (packet, current_shell_id); - rpacket_set_next_line_id (packet, current_line_id); - rpacket_set_last_line (packet, last_line); - rpacket_set_close_line (packet, false); - rpacket_set_virtual_packet_flag (packet, virtual_packet_flag); - packet->chi_bf_tmp_partial = chi_bf_tmp_partial; - packet->compute_chi_bf = true; - packet->vpacket_weight = 1.0; - return ret_val; -} diff --git a/tardis/montecarlo/src/rpacket.h b/tardis/montecarlo/src/rpacket.h deleted file mode 100644 index 2a30546a837..00000000000 --- a/tardis/montecarlo/src/rpacket.h +++ /dev/null @@ -1,330 +0,0 @@ -#ifndef TARDIS_RPACKET_H -#define TARDIS_RPACKET_H - -#include -#include -#include -#include "randomkit/randomkit.h" -#include "status.h" -#include "storage.h" - -#define MISS_DISTANCE 1e99 -#define C 29979245800.0 -#define INVERSE_C 3.33564095198152e-11 -#define H 6.6260755e-27 // erg * s, converted to CGS units from the NIST Constant Index -#define KB 1.3806488e-16 // erg / K converted to CGS units from the NIST Constant Index - -/** - * @brief A photon packet. - */ -typedef struct RPacket -{ - double nu; /**< Frequency of the packet in Hz. */ - double mu; /**< Cosine of the angle of the packet. */ - double energy; /**< Energy of the packet in erg. */ - double r; /**< Distance from center in cm. */ - double tau_event; - double nu_line; - int64_t current_shell_id; /**< ID of the current shell. */ - int64_t next_line_id; /**< The index of the next line that the packet will encounter. */ - /** - * @brief The packet has a nu red-ward of the last line. - * It will not encounter any lines anymore. - */ - int64_t last_line; - /** - * @brief The packet just encountered a line that is very close to the next line. - * The next iteration will automatically make an interaction with the next line - * (avoiding numerical problems). - */ - int64_t close_line; - /** - * @brief packet is a virtual packet and will ignore any d_line or d_electron checks. - * It now whenever a d_line is calculated only adds the tau_line to an - * internal float. - */ - int64_t current_continuum_id; /* Packet can interact with bf-continua with an index equal or bigger than this */ - int64_t virtual_packet_flag; - int64_t virtual_packet; - double d_line; /**< Distance to electron event. */ - double d_electron; /**< Distance to line event. */ - double d_boundary; /**< Distance to shell boundary. */ - double d_cont; /**< Distance to continuum event */ - int64_t next_shell_id; /**< ID of the next shell packet visits. */ - rpacket_status_t status; /**< Packet status (in process, emitted or reabsorbed). */ - int64_t id; - double chi_th; /**< Opacity due to electron scattering */ - double chi_cont; /**< Opacity due to continuum processes */ - double chi_ff; /**< Opacity due to free-free processes */ - double chi_bf; /**< Opacity due to bound-free processes */ - double *chi_bf_tmp_partial; - int64_t macro_atom_activation_level; - bool compute_chi_bf; - double vpacket_weight; -} rpacket_t; - -static inline double rpacket_get_nu (const rpacket_t * packet) -{ - return packet->nu; -} - -static inline void rpacket_set_nu (rpacket_t * packet, double nu) -{ - packet->nu = nu; -} - -static inline double rpacket_get_mu (const rpacket_t * packet) -{ - return packet->mu; -} - -static inline void rpacket_set_mu (rpacket_t * packet, double mu) -{ - packet->mu = mu; -} - -static inline double rpacket_get_energy (const rpacket_t * packet) -{ - return packet->energy; -} - -static inline void rpacket_set_energy (rpacket_t * packet, double energy) -{ - packet->energy = energy; -} - -static inline double rpacket_get_r (const rpacket_t * packet) -{ - return packet->r; -} - -static inline void rpacket_set_r (rpacket_t * packet, double r) -{ - packet->r = r; -} - -static inline double rpacket_get_tau_event (const rpacket_t * packet) -{ - return packet->tau_event; -} - -static inline void rpacket_set_tau_event (rpacket_t * packet, double tau_event) -{ - packet->tau_event = tau_event; -} - -static inline double rpacket_get_nu_line (const rpacket_t * packet) -{ - return packet->nu_line; -} - -static inline void rpacket_set_nu_line (rpacket_t * packet, double nu_line) -{ - packet->nu_line = nu_line; -} - -static inline unsigned int rpacket_get_current_shell_id (const rpacket_t * packet) -{ - return packet->current_shell_id; -} - -static inline void rpacket_set_current_shell_id (rpacket_t * packet, - unsigned int current_shell_id) -{ - packet->current_shell_id = current_shell_id; -} - -static inline unsigned int rpacket_get_next_line_id (const rpacket_t * packet) -{ - return packet->next_line_id; -} - -static inline void rpacket_set_next_line_id (rpacket_t * packet, - unsigned int next_line_id) -{ - packet->next_line_id = next_line_id; -} - -static inline bool rpacket_get_last_line (const rpacket_t * packet) -{ - return packet->last_line; -} - -static inline void rpacket_set_last_line (rpacket_t * packet, bool last_line) -{ - packet->last_line = last_line; -} - -static inline bool rpacket_get_close_line (const rpacket_t * packet) -{ - return packet->close_line; -} - -static inline void rpacket_set_close_line (rpacket_t * packet, bool close_line) -{ - packet->close_line = close_line; -} - -static inline int rpacket_get_virtual_packet_flag (const rpacket_t * packet) -{ - return packet->virtual_packet_flag; -} - -static inline void rpacket_set_virtual_packet_flag (rpacket_t * packet, - int virtual_packet_flag) -{ - packet->virtual_packet_flag = virtual_packet_flag; -} - -static inline int rpacket_get_virtual_packet (const rpacket_t * packet) -{ - return packet->virtual_packet; -} - -static inline void rpacket_set_virtual_packet (rpacket_t * packet, - int virtual_packet) -{ - packet->virtual_packet = virtual_packet; -} - -static inline double rpacket_get_d_boundary (const rpacket_t * packet) -{ - return packet->d_boundary; -} - -static inline void rpacket_set_d_boundary (rpacket_t * packet, double d_boundary) -{ - packet->d_boundary = d_boundary; -} - -static inline double rpacket_get_d_electron (const rpacket_t * packet) -{ - return packet->d_electron; -} - -static inline void rpacket_set_d_electron (rpacket_t * packet, double d_electron) -{ - packet->d_electron = d_electron; -} - -static inline double rpacket_get_d_line (const rpacket_t * packet) -{ - return packet->d_line; -} - -static inline void rpacket_set_d_line (rpacket_t * packet, double d_line) -{ - packet->d_line = d_line; -} - -static inline int rpacket_get_next_shell_id (const rpacket_t * packet) -{ - return packet->next_shell_id; -} - -static inline void rpacket_set_next_shell_id (rpacket_t * packet, int next_shell_id) -{ - packet->next_shell_id = next_shell_id; -} - -static inline rpacket_status_t rpacket_get_status (const rpacket_t * packet) -{ - return packet->status; -} - -static inline void rpacket_set_status (rpacket_t * packet, rpacket_status_t status) -{ - packet->status = status; -} - -static inline int rpacket_get_id (const rpacket_t * packet) -{ - return packet->id; -} - -static inline void rpacket_set_id (rpacket_t * packet, int id) -{ - packet->id = id; -} - -static inline void rpacket_reset_tau_event (rpacket_t * packet, rk_state *mt_state) -{ - rpacket_set_tau_event (packet, -log (rk_double (mt_state))); -} - -tardis_error_t rpacket_init (rpacket_t * packet, storage_model_t * storage, - int packet_index, int virtual_packet_flag, double * chi_bf_tmp_partial); - -/* New getter and setter methods for continuum implementation */ - -static inline void rpacket_set_d_continuum (rpacket_t * packet, double d_continuum) -{ - packet->d_cont = d_continuum; -} - -static inline double rpacket_get_d_continuum (const rpacket_t * packet) -{ - return packet->d_cont; -} - -static inline void rpacket_set_chi_electron (rpacket_t * packet, double chi_electron) -{ - packet->chi_th = chi_electron; -} - -static inline double rpacket_get_chi_electron (const rpacket_t * packet) -{ - return packet->chi_th; -} - -static inline void rpacket_set_chi_continuum (rpacket_t * packet, double chi_continuum) -{ - packet->chi_cont = chi_continuum; -} - -static inline double rpacket_get_chi_continuum (const rpacket_t * packet) -{ - return packet->chi_cont; -} - -static inline void rpacket_set_chi_freefree (rpacket_t * packet, double chi_freefree) -{ - packet->chi_ff = chi_freefree; -} - -static inline double rpacket_get_chi_freefree (const rpacket_t * packet) -{ - return packet->chi_ff; -} - -static inline void rpacket_set_chi_boundfree (rpacket_t * packet, double chi_boundfree) -{ - packet->chi_bf = chi_boundfree; -} - -static inline double rpacket_get_chi_boundfree (const rpacket_t * packet) -{ - return packet->chi_bf; -} - -static inline unsigned int rpacket_get_current_continuum_id (const rpacket_t * packet) -{ - return packet->current_continuum_id; -} - -static inline void rpacket_set_current_continuum_id (rpacket_t * packet, unsigned int current_continuum_id) -{ - packet->current_continuum_id = current_continuum_id; -} - -static inline void rpacket_set_macro_atom_activation_level (rpacket_t * packet, unsigned int activation_level) -{ - packet->macro_atom_activation_level = activation_level; -} - -static inline unsigned int rpacket_get_macro_atom_activation_level (const rpacket_t * packet) -{ - return packet->macro_atom_activation_level; -} - -#endif // TARDIS_RPACKET_H diff --git a/tardis/montecarlo/src/status.h b/tardis/montecarlo/src/status.h deleted file mode 100644 index 17a01720b79..00000000000 --- a/tardis/montecarlo/src/status.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TARDIS_STATUS_H -#define TARDIS_STATUS_H - -typedef enum -{ - TARDIS_ERROR_OK = 0, - TARDIS_ERROR_BOUNDS_ERROR = 1, - TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE = 2 -} tardis_error_t; - -typedef enum -{ - TARDIS_PACKET_STATUS_IN_PROCESS = 0, - TARDIS_PACKET_STATUS_EMITTED = 1, - TARDIS_PACKET_STATUS_REABSORBED = 2 -} rpacket_status_t; - -typedef enum -{ - CONTINUUM_OFF = 0, - CONTINUUM_ON = 1 -} ContinuumProcessesStatus; - -typedef enum -{ - BB_EMISSION = -1, - BF_EMISSION = -2, - FF_EMISSION = -3, - ADIABATIC_COOLING = -4 -} emission_type; - -typedef enum -{ - LIN_INTERPOLATION = 0, - HYDROGENIC = 1 -} bound_free_treatment; - -#endif // TARDIS_STATUS_H diff --git a/tardis/montecarlo/src/storage.h b/tardis/montecarlo/src/storage.h deleted file mode 100644 index a408a9f7bc2..00000000000 --- a/tardis/montecarlo/src/storage.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef TARDIS_STORAGE_H -#define TARDIS_STORAGE_H - -#include - -#include "status.h" - -typedef struct photo_xsect_1level -{ - double * nu; - double * x_sect; - int64_t no_of_points; -} photo_xsect_1level; - -typedef struct StorageModel -{ - double *packet_nus; - double *packet_mus; - double *packet_energies; - double *output_nus; - double *output_energies; - double *last_interaction_in_nu; - int64_t *last_line_interaction_in_id; - int64_t *last_line_interaction_out_id; - int64_t *last_line_interaction_shell_id; - int64_t *last_interaction_type; - int64_t *last_interaction_out_type; - int64_t no_of_packets; - int64_t no_of_shells; - int64_t no_of_shells_i; - double *r_inner; - double *r_outer; - double *r_inner_i; - double *r_outer_i; - double *v_inner; - double time_explosion; - double inverse_time_explosion; - double *electron_densities; - double *electron_densities_i; - double *inverse_electron_densities; - double *line_list_nu; - double *continuum_list_nu; - double *line_lists_tau_sobolevs; - double *line_lists_tau_sobolevs_i; - int64_t line_lists_tau_sobolevs_nd; - double *line_lists_j_blues; - int64_t line_lists_j_blues_nd; - double *line_lists_Edotlu; - int64_t no_of_lines; - int64_t no_of_edges; - int64_t line_interaction_id; - double *transition_probabilities; - int64_t transition_probabilities_nd; - int64_t *line2macro_level_upper; - int64_t *macro_block_references; - int64_t *transition_type; - int64_t *destination_level_id; - int64_t *transition_line_id; - double *js; - double *nubars; - double spectrum_start_nu; - double spectrum_delta_nu; - double spectrum_end_nu; - double spectrum_virt_start_nu; - double spectrum_virt_end_nu; - double *spectrum_virt_nu; - double sigma_thomson; - double inverse_sigma_thomson; - double inner_boundary_albedo; - int64_t reflective_inner_boundary; - int64_t current_packet_id; - photo_xsect_1level **photo_xsect; - double *chi_ff_factor; - double *t_electrons; - double *l_pop; - double *l_pop_r; - ContinuumProcessesStatus cont_status; - bound_free_treatment bf_treatment; - double *virt_packet_nus; - double *virt_packet_energies; - double *virt_packet_last_interaction_in_nu; - int64_t *virt_packet_last_interaction_type; - int64_t *virt_packet_last_line_interaction_in_id; - int64_t *virt_packet_last_line_interaction_out_id; - int64_t virt_packet_count; - int64_t virt_array_size; - int64_t kpacket2macro_level; - int64_t *cont_edge2macro_level; - double *photo_ion_estimator; - double *stim_recomb_estimator; - int64_t *photo_ion_estimator_statistics; - double *bf_heating_estimator; - double *ff_heating_estimator; - double *stim_recomb_cooling_estimator; - int full_relativity; - double survival_probability; - double tau_russian; - double *tau_bias; - int enable_biasing; -} storage_model_t; - -#endif // TARDIS_STORAGE_H diff --git a/tardis/montecarlo/tests/conftest.py b/tardis/montecarlo/tests/conftest.py index 13cca00085e..054f6b455c4 100644 --- a/tardis/montecarlo/tests/conftest.py +++ b/tardis/montecarlo/tests/conftest.py @@ -9,7 +9,6 @@ c_ulong, ) -from tardis.montecarlo import montecarlo from tardis.montecarlo.struct import ( RPacket, StorageModel, RKState, TARDIS_PACKET_STATUS_IN_PROCESS, @@ -18,12 +17,6 @@ ) -# Wrap the shared object containing C methods, which are tested here. -@pytest.fixture(scope='session') -def clib(): - return CDLL(os.path.join(montecarlo.__file__)) - - @pytest.fixture(scope="function") def packet(): """Fixture to return `RPacket` object with default params initialized.""" diff --git a/tardis/montecarlo/tests/test_formal_integral.py b/tardis/montecarlo/tests/test_formal_integral.py index 616de0d2d7d..ccf52665e5c 100644 --- a/tardis/montecarlo/tests/test_formal_integral.py +++ b/tardis/montecarlo/tests/test_formal_integral.py @@ -2,23 +2,16 @@ import numpy as np from tardis import constants as c -import ctypes -from ctypes import ( - c_double, - c_int - ) - from numpy.ctypeslib import ( as_array, as_ctypes, - ndpointer ) import numpy.testing as ntest from tardis.util.base import intensity_black_body -from tardis.montecarlo.struct import StorageModel +import tardis.montecarlo.formal_integral as formal_integral @pytest.mark.parametrize( @@ -29,11 +22,10 @@ (1, 1), ] ) -def test_intensity_black_body(clib, nu, T): - func = clib.intensity_black_body - func.restype = c_double - func.argtypes = [c_double, c_double] +def test_intensity_black_body(nu, T): + func = formal_integral.intensity_black_body actual = func(nu, T) + print(actual, type(actual)) expected = intensity_black_body(nu, T) ntest.assert_almost_equal( actual, @@ -45,16 +37,10 @@ def test_intensity_black_body(clib, nu, T): 'N', (1e2, 1e3, 1e4, 1e5) ) -def test_trapezoid_integration(clib, N): - func = clib.trapezoid_integration - func.restype = c_double +def test_trapezoid_integration(N): + func = formal_integral.trapezoid_integration h = 1. N = int(N) - func.argtypes = [ - ndpointer(c_double), - c_double, - c_int - ] data = np.random.random(N) actual = func(data, h, int(N)) @@ -70,7 +56,7 @@ def test_trapezoid_integration(clib, N): True, reason='static inline functions are not inside the library' ) -def test_calculate_z(clib): +def test_calculate_z(): pass @@ -97,7 +83,7 @@ def calculate_z(r, p): def formal_integral_model(request, model): r = request.param['r'] model.no_of_shells_i = r.shape[0] - 1 - model.inverse_time_explosion = c.c.cgs.value + model.time_explosion = 1/c.c.cgs.value model.r_outer_i.contents = as_ctypes(r[1:]) model.r_inner_i.contents = as_ctypes(r[:-1]) return model @@ -107,20 +93,16 @@ def formal_integral_model(request, model): 'p', [0, 0.5, 1] ) -def test_populate_z_photosphere(clib, formal_integral_model, p): +def test_populate_z_photosphere(formal_integral_model, p): ''' Test the case where p < r[0] That means we 'hit' all shells from inside to outside. ''' - func = clib.populate_z - func.restype = ctypes.c_int64 - func.argtypes = [ - ctypes.POINTER(StorageModel), # storage - c_double, # p - ndpointer(dtype=np.float64), # oz - ndpointer(dtype=np.int64) # oshell_id - ] - + integrator = formal_integral.FormalIntegrator( + formal_integral_model, + None, + None) + func = integrator.populate_z size = formal_integral_model.no_of_shells_i r_inner = as_array(formal_integral_model.r_inner_i, (size,)) r_outer = as_array(formal_integral_model.r_outer_i, (size,)) @@ -130,7 +112,6 @@ def test_populate_z_photosphere(clib, formal_integral_model, p): oshell_id = np.zeros_like(oz, dtype=np.int64) N = func( - formal_integral_model, p, oz, oshell_id @@ -153,18 +134,15 @@ def test_populate_z_photosphere(clib, formal_integral_model, p): 'p', [1e-5, 0.5, 0.99, 1] ) -def test_populate_z_shells(clib, formal_integral_model, p): +def test_populate_z_shells(formal_integral_model, p): ''' Test the case where p > r[0] ''' - func = clib.populate_z - func.restype = ctypes.c_int64 - func.argtypes = [ - ctypes.POINTER(StorageModel), # storage - c_double, # p - ndpointer(dtype=np.float64), # oz - ndpointer(dtype=np.int64) # oshell_id - ] + integrator = formal_integral.FormalIntegrator( + formal_integral_model, + None, + None) + func = integrator.populate_z size = formal_integral_model.no_of_shells_i r_inner = as_array(formal_integral_model.r_inner_i, (size,)) @@ -194,7 +172,6 @@ def test_populate_z_shells(clib, formal_integral_model, p): p) N = func( - formal_integral_model, p, oz, oshell_id @@ -221,14 +198,10 @@ def test_populate_z_shells(clib, formal_integral_model, p): 1000, 10000, ]) -def test_calculate_p_values(clib, N): +def test_calculate_p_values(N): r = 1. - func = clib.calculate_p_values - func.argtypes = [ - c_double, - c_int, - ndpointer(dtype=np.float64) - ] + func = formal_integral.calculate_p_values + expected = r/(N - 1) * np.arange(0, N, dtype=np.float64) actual = np.zeros_like(expected, dtype=np.float64) diff --git a/tardis/montecarlo/tests/test_montecarlo.py b/tardis/montecarlo/tests/test_montecarlo.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tardis/scripts/debug/run_numba_single.py b/tardis/scripts/debug/run_numba_single.py new file mode 100644 index 00000000000..86d0b22f331 --- /dev/null +++ b/tardis/scripts/debug/run_numba_single.py @@ -0,0 +1,22 @@ +from tardis import run_tardis +import numpy as np +from tardis.montecarlo.montecarlo_numba.base import montecarlo_main_loop +import os +import numba +import sys +import yaml + + +SEED = eval(sys.argv[1].split('=')[1])[0] + +yaml_file, params = 'tardis_example_single.yml', None + +with open(yaml_file) as f: + params = yaml.safe_load(f) + +params['montecarlo']['single_packet_seed'] = SEED + +with open(yaml_file, 'w') as f: + yaml.safe_dump(params, f) + +mdl = run_tardis(yaml_file) diff --git a/tardis/scripts/debug/run_numba_single.run.xml b/tardis/scripts/debug/run_numba_single.run.xml new file mode 100644 index 00000000000..987a1e7808a --- /dev/null +++ b/tardis/scripts/debug/run_numba_single.run.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/tardis/scripts/debug/tardis_example_single.yml b/tardis/scripts/debug/tardis_example_single.yml new file mode 100644 index 00000000000..35c17071b18 --- /dev/null +++ b/tardis/scripts/debug/tardis_example_single.yml @@ -0,0 +1,45 @@ +atom_data: kurucz_cd23_chianti_H_He.h5 +model: + abundances: + Si: 1.0 + type: uniform + structure: + density: + type: branch85_w7 + type: specific + velocity: + num: 20 + start: 1.1e4 km/s + stop: 20000 km/s +montecarlo: + convergence_strategy: + damping_constant: 1.0 + fraction: 0.8 + hold_iterations: 3 + t_inner: + damping_constant: 1.0 + threshold: 0.05 + type: damped + debug_packets: true + iterations: 2 + last_no_of_packets: 1000000.0 + logger_buffer: 1 + no_of_packets: 40000.0 + no_of_virtual_packets: 0 + nthreads: 6 + seed: 23111963 + single_packet_seed: 46 +plasma: + disable_electron_scattering: false + excitation: lte + ionization: lte + line_interaction_type: macroatom + radiative_rates_type: dilute-blackbody +spectrum: + num: 10000 + start: 500 angstrom + stop: 20000 angstrom +supernova: + luminosity_requested: 9.44 log_lsun + time_explosion: 10 day +tardis_config_version: v1.0 diff --git a/tardis/simulation/base.py b/tardis/simulation/base.py index 8d9d0d30159..fd3161701bf 100644 --- a/tardis/simulation/base.py +++ b/tardis/simulation/base.py @@ -257,7 +257,7 @@ def advance_state(self): # case it needs some extra kwargs. if 'j_blue_estimator' in self.plasma.outputs_dict: update_properties.update(t_inner=next_t_inner, - j_blue_estimator=self.runner.j_b_lu_estimator) + j_blue_estimator=self.runner.j_blue_estimator) self.plasma.update(**update_properties) @@ -299,7 +299,7 @@ def run(self): self.model.t_rad, self.plasma.electron_densities, self.model.t_inner) - self.iterate(self.last_no_of_packets, self.no_of_virtual_packets, True) + self.iterate(self.last_no_of_packets, self.no_of_virtual_packets, last_run=True) self.reshape_plasma_state_store(self.iterations_executed) diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 4e98249b685..682e4645728 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -52,7 +52,7 @@ def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata('j_blue_estimator').values npt.assert_allclose( - runner.j_b_lu_estimator, + runner.j_blue_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): diff --git a/tardis/tests/test_tardis_full_formal_integral.py b/tardis/tests/test_tardis_full_formal_integral.py index 3be437b8113..d3de68501c4 100644 --- a/tardis/tests/test_tardis_full_formal_integral.py +++ b/tardis/tests/test_tardis_full_formal_integral.py @@ -79,7 +79,7 @@ def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata('j_blue_estimator').values npt.assert_allclose( - runner.j_b_lu_estimator, + runner.j_blue_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): diff --git a/tardis_env3.yml b/tardis_env3.yml index 8f0539b7893..d9e7678fd6e 100644 --- a/tardis_env3.yml +++ b/tardis_env3.yml @@ -11,7 +11,7 @@ dependencies: - pandas=0.24 - astropy=3.2.1 - - numba=0.43 + - numba=0.49.1 - numexpr - Cython=0.29 From 73ba9db2cf3519353888f9f6392ddfcf9588f7d1 Mon Sep 17 00:00:00 2001 From: Arjun Savel <35353555+arjunsavel@users.noreply.github.com> Date: Mon, 10 Aug 2020 12:45:47 -0400 Subject: [PATCH 054/116] Explicitly pass number of vpackets to montecarlo_main_loop (#1244) --- tardis/montecarlo/montecarlo_numba/base.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index fe5ad56cb22..fdcb65a650d 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -27,16 +27,18 @@ def montecarlo_radial1d(model, plasma, runner): estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator, runner.j_blue_estimator, runner.Edotlu_estimator) + number_of_vpackets = montecarlo_configuration.number_of_vpackets + v_packets_energy_hist = montecarlo_main_loop( packet_collection, numba_model, numba_plasma, estimators, - runner.spectrum_frequency.value) - + runner.spectrum_frequency.value, number_of_vpackets) runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, - estimators, spectrum_frequency): + estimators, spectrum_frequency, + number_of_vpackets): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -46,7 +48,6 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, storage_model : [type] [description] """ - output_nus = np.empty_like(packet_collection.packets_output_nu) output_energies = np.empty_like(packet_collection.packets_output_nu) @@ -65,7 +66,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, # We want to set the seed correctly per user; otherwise, random. np.random.seed(i) vpacket_collection = VPacketCollection( - spectrum_frequency, montecarlo_configuration.number_of_vpackets, + spectrum_frequency, number_of_vpackets, montecarlo_configuration.temporary_v_packet_bins) loop = single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection) @@ -85,8 +86,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_idx = np.floor((vpackets_nu - spectrum_frequency[0]) / delta_nu).astype(np.int64) # if we're only in a single-packet mode - if montecarlo_configuration.single_packet_seed != -1: - break + # if montecarlo_configuration.single_packet_seed == -1: + # break for j, idx in enumerate(v_packets_idx): if ((vpackets_nu[j] < spectrum_frequency[0]) or (vpackets_nu[j] > spectrum_frequency[-1])): From 5d6b79011b7ffd0b740d6e40be43e560d3e0438e Mon Sep 17 00:00:00 2001 From: Arjun Savel <35353555+arjunsavel@users.noreply.github.com> Date: Wed, 2 Sep 2020 16:22:40 -0400 Subject: [PATCH 055/116] Final numba PR (#1285) * Add montecarlo_seed value to global module * Add user-input seed to each seed setting * Make sure montecarlo_seed for global module is set during configuration * Include packet_seeds within montecarlo_configuration module * Initialize packet seeds * Set user seed before instantiating *any* random process! * Pass packet seeds to montecarlo_main_loop; use said seeds * Bring rpacket creation outside of if/else * Initialize seeds within initialize_packets * Set mu and nu sampling on a per-seed basis * Use single_packet_seed to index existing packet_seeds array * Perform packet-by-packet creation of trajectories, nu in packet_source * Try initializing xis outside of blackbody function * Remove print statements * Do not concatenate arrays in packet_source running; add njit * Name create_array more explicitly * Make montecarlo seeds attributes of r_packet * Add rationale for size of random array * Clean up documentation of blackbody sampling * Remove extraneous numba import * Remove extra kwarg in basepacket create_packets * Add packet_source docstrings * Justify and change MAX_SEED_VAL * Refactor packet_source so that all packet properties are made in the same func * Attach seed to r_packet * Clean up create_packets func * Clean up docstrings for packet_source * @jitclass BlackBodySimpleSource * Ensure that the iteration is added to the seed each time montecarlorunner is run * jitclass uniform packets * Pass iteration to montecarlorunner * Remove njit from uniform packet energies * Provide a better-motivated upper bound for the seed value * Use random.sample instead of np.random.randint to prevent possible repetitions * Raise more explicit error if too many packets * Set seed of random module before calling random.sample * Address merge error in montecarlo_main_loop * Clarify ValueError for seeds * Do not generate packet properties on a seed-by-seed basis * Remove seeds from create_packets function call * use new random number generator to make packet seeds * Bump numpy version to 1.19.0 * Pass rng to packet source functions for randomness * actually define rng in montecarlo/base * Delete error raised for too many packets * Allow packet seeds to overlap * Remove random module import * Clarify C comparison * Include numba comparison plots * Add Testing TARDIS section to docs * Make sure custom packet source works * Add disabling to yml file * Make changes to mc_config_module when necessary * Add scatter-disabling functionality to global module * Set tau_sobolev to 0 in plasma setting * Add rudimentary last_interaction_type * Address combined > event for tau_trace * reference module for sigma_thomson * Explicitly pass sigma_thomson * Only reset nu for e-scatter * Split thomson_scatter and line_scatter entirely * check doppler factor * Account for interaction type * Do not hardcode the array size anymore * Remove extra commented statments * Remove reference to flag --- docs/development/index.rst | 1 - docs/development/running_tests.rst | 98 ------------------ .../numba_comparison_real_quickstart.png | Bin 0 -> 285069 bytes .../numba_comparison_virtual_quickstart.png | Bin 0 -> 196666 bytes docs/index.rst | 8 ++ docs/running/custom_source.ipynb | 16 +-- tardis/io/schemas/plasma.yml | 5 + tardis/montecarlo/base.py | 40 +++++-- tardis/montecarlo/montecarlo_configuration.py | 8 +- tardis/montecarlo/montecarlo_numba/base.py | 30 ++++-- .../montecarlo_numba/interaction.py | 27 +++-- .../montecarlo_numba/numba_interface.py | 42 +++++--- .../montecarlo/montecarlo_numba/r_packet.py | 42 +++++--- .../montecarlo_numba/single_packet_loop.py | 27 ++--- tardis/montecarlo/montecarlo_numba/vpacket.py | 15 +-- tardis/montecarlo/packet_source.py | 62 +++++------ tardis/simulation/base.py | 5 +- tardis_env3.yml | 32 +++--- 18 files changed, 220 insertions(+), 238 deletions(-) delete mode 100644 docs/development/running_tests.rst create mode 100644 docs/graphics/numba_comparison_real_quickstart.png create mode 100644 docs/graphics/numba_comparison_virtual_quickstart.png diff --git a/docs/development/index.rst b/docs/development/index.rst index 03c4a9b2e2a..56fc47df4e3 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -11,7 +11,6 @@ to the Astropy team for designing it. .. toctree:: :maxdepth: 2 - running_tests issues debug_numba diff --git a/docs/development/running_tests.rst b/docs/development/running_tests.rst deleted file mode 100644 index 8680331da4b..00000000000 --- a/docs/development/running_tests.rst +++ /dev/null @@ -1,98 +0,0 @@ -************* -Running tests -************* - -There are two basic categories of tests unit tests in TARDIS 1) the unit -tests 2) integration tests. Unit tests check the outputs of individual functions -while the integration tests check entire runs for different setups of TARDIS. - -The Unit tests run very quickly and thus are executed after every suggested change -to TARDIS. The Integration tests are much more costly and thus are only executed -every few days on a dedicated server. - -All of them are based on the excellent ``astropy-setup-helpers`` package and -``pytest``. - -Running the unit tests -====================== - -This is very straight forward to run on your own machine. For very simple unit -tests you can run this with: - -.. code-block:: shell - - > python setup.py test - - -Running the more advanced unit tests requires Tardis Reference data that can be -downloaded -(`tardis_refdata `_). - -.. code-block:: shell - - > python setup.py test --args="--tardis-refdata=/path/to/tardis-refdata/" - -Generating Plasma Reference -=========================== - -You can generate Plasma Reference by the following command - -.. code-block:: shell - - > pytest -rs tardis/plasma/tests/test_complete_plasmas.py - --tardis-refdata="/path/to/tardis-refdata/" --generate-reference - -Running the integration tests -============================= - -These tests require reference files against which the results of the various -tardis runs are tested. So you first need to either download the current -reference files (`here `_) -or generate new ones. - -Both of of these require a configuration file for the integration tests: - -.. literalinclude:: integration.yml - :language: yaml - -Inside the atomic data directory there needs to be atomic data for each of -the setups that are provided in the ``test_integration`` folder. -If no references are given the first step is to generate them. -The ``--less-packets`` option is useful for debugging purposes and will just -use very few packets to generate the references and thus make the process much -faster - THIS IS ONLY FOR DEBUGGING PURPOSES. The ``-s`` option ensures that -TARDIS prints out the progress: - -.. code-block:: shell - - > python setup.py test --args="--integration=integration.yml -m integration - --generate-reference --less-packets" - -To run the test after having run the ``--generate-references`` all that is -needed is: - -.. code-block:: shell - - > python setup.py test --args="--integration=integration.yml -m integration - --less-packets" --remote-data - - -Setting up the Dokuwiki report -============================== - -A normal dokuwiki installation is performed on the required server. Before the -connection works one is requires to set the option remote access in the -settings. If this is not done the ``dokuwiki`` python plugin will not connect -with the warning ``DokuWikiError: syntax error: line 1, column 0``. One also -has to enable this for users (``remoteuser`` option) otherwise the error: -``ProtocolError for xmlrpc.php?p=xxxxxx&u=tardistester: 403 Forbidden`` -will appear. - -Another important configuration option is to enable embedded html ``htmlok`` -otherwise it won't show nice html page reports. - -Finally, one has to call the `python setup.py test` with the ``--remote-data`` -option to allow posting to an external dokuwiki server. - - - diff --git a/docs/graphics/numba_comparison_real_quickstart.png b/docs/graphics/numba_comparison_real_quickstart.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ced13fc3a690387b968b45ef0cccd19877827e GIT binary patch literal 285069 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�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 z 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 z zFI@Zt|8gD=cedH4a|!$^c`BVo)R2h1dOIw#Vq$(nN&h|3e52KmaI1cYwjEEM_K2Kw z&rdZbrj9NHXwuTsdyes!YwA~_}t0YcNf1JLo{=T_VzoY&X z!d&*8V{@ukUeK`n{qCQo^r{!MTo#-7i73E0+DV7J#*!KVvMtj9ncTUkit#j(U<}x% zV3yjsXlRC?mp%VYNbDc?Ms7NRa{>25QqmW$L#b(g@6E>BV=a2t3))L+Y3jcfoU2AJ z>`u;|*q~68d^)gxL<;}ovLA4o7gX$h{Yx(7{;KCPYv^{=pZJBGWb^U@mak|n+zgb6 z4;c@W1axoSB1d5zk{nLbD!_sRnps7T^ffH(-bDV7p1>ab0i?IL9FG4bl};-3Toa{4 zz3(&rBjgfKv7oM#?neB%^{ldZ+X}9p;J8T}laCLBmWE3EzF9=7(xTpo&*7EnQiD9d zZ5g+AlA*G@YS723VxhyIbjL>b&WF~Csw8~DDE(=fB6sF8)!Vys-ahVq@|)Gy(ec{s zsTB;?f+V0M`xuQNQC=*{+RI-?=dLI!9;c)?4y01=8;}W53yzoK8Ri8}fiQaZzC%Tq zCvZZvZ#WSFnC5+a7#8ptMKR!}GkFbp+)&DpWBILxRfUds^`foVYd$QQ+t0TZ=wXe$ zzG#G$^$0EV|yG1eWcatQ~t0lY(|(gK*#*m%TsVTnFC9h>0sc zKcTZv$4^O5QE}jGmGI4x7148I#afb9htBHd-GSDO$>YpGtz2Vc`rs!)U~^m*sy20d zGv@`hGD5};te=d)@6(k&1S@tUQ*v_q$LLLUoEF=9cp*=2e8$f1M|Hj2U%uK!Sre88 zmNv3$zwhf@uWi$q8*?(5h<7|UZ9wn632b<|7iWeE)$~L(8lp-j{^J5|Dyt|qY#}|K zKP2fKRFY>_{dfhOcMW*bz8a3GvHWn$(=qHH2*L{Nlz8=D;;uj~uv+3q7EwksXp1*i z8v}S&RV+X9P1yRxHEYO4l+#`8pa~1Spnkp1*mf(WvdWj<8j_@DK6@xw9B48A(Ymr9~yhVoLTz&;Q6-+vPgw%Tawo(k@h z^}QK!0WIoPJlO+zRG`~?iUzpe8nZD1zlUQ^97a9a6zYhG8djf|hnolz!7Si;1u>%} zX8@qQ0IAooo#7n+iyNSWX2zOW_Tsd->B#(iT&XySjmKeI$i$im@z&Jv+LURn$mXNM zKzSqo{KTmsaP?`6GRl19d2IW~?!|c*xoipt4DgqN1c`Mg*}T5b3lb!?mR+!~T3q%w z|8b>OF%3)A?u!ByUNXn zYMP>uB}zwMR-3?D!i88nF-j|PVmS8FzW*LliuOyF!#9FP-r^lAIN@K(i*LAVZ(@g-Y*qvEEU~belPhs`{%-Y{QxCNP zR0x_?;ysx}3ac{(y1;FYuW%s;_E_2i9b^;-TPc~5S2wcMnetZ{L#^aBN`1EdQ{@s5 znr&k8Y0x|77uRhT?%c&UCeuclt_??Y6yZa94ekQFH-u3H>|x+TAOrJ#v3yDG>`Tv| z$v=V6_(U<%Fl0?+CSBN{KK;UqRYxQ4-TYJ>iW*WN;y^i@ ztv9m3vh-a#W@G||FgWLnVMVbJTe@be8-3@~+3W4?xSVqb1eQseDQXVv35sIWC(?VY zb<9D=B0smL0mJ|UEW?LwW$+38Y(NwLsQhPsp!qhb^G7zn2oVVh1c-c70c+)1NeM7j zg1X!IqI_Qhd$_Hd(=4(r_%bZw00;+o8v9#Z53P<`y?=lDoCbF}R1=*kKP;vs%8)#_ z*uY`jj{+VJSneRj>P0e03r+gm*FB}ab~u`6#-Z0TVO**Tr*@pen{h#Dp;rEdgY)HX zptMDy^uGy{yrXbNs+WJPG;@`Gzq~p71IK;4!coK?W82~yZ=SMy+(j_H`_~Jlf;3oQ z%DlTBi1q~MLWi^lbOLu6?oJkM9c1|6*7#aaRfo2X*yoinMiZzHEmvCD89OiDTBE&) zfkG&krhqF5I{gzhB&BH%TZ{KI-j7u{oHldlJ4g`pG@ToH&*(6UQuc5o!s~?%MOXk| zj|fFPW_CTHV7;Im>Eg!CYIVe0uZd0t)Jzlwmvvc7P4lgP^Y{n%{pz zLrE@nMmz!B8`^O1tL6os9{HzdE3`D2PenBUSDjZA+-_FSzjhaef3*R|BTy^6ERTW) zgWn;}B)h=!v<3FSW%T9vb0c*zozO%Rb1U)%{>^QVPv;5mXZY_-HeDDL@@?_(OM?r=^)I@5{*OhKURY>0G>fi#j1ESwnji+*M{}g|>>?JV7p92GwKq zbkrppwa$v75)08KZfrb>2__1BrQX-SUjlDz7xC-in#Q@@j_^W5LqVq5>>(d?WAX@F zUT-0qkeCRPboJ+b7a4KC2>HSUBy+;(`|r;VOZ$lt@54j>fTV+>)RsfUsiAmm{u{nT z|L33)X@Ow9FFqf!EgAgnZMb!|J(gysva6#YcL zgVI3>Wc-7w=I79Aw;kzUcYKt%l)0)p9c$4!iaE`uY+)%DY(0m&cSr^`DH?u9w6jGO zU>$L+VxWKl*Or|XZa17D!)yZIzv>8IkT(e4_gC8NU57XFyXy_MU|NM-PuQ&!AR7!p zhUA~DAgiX2bPke7N7PX}ylGK1Ql*KJ5TE81p)Z~ZAt(QUrK4s4m~6TCZaj68mEjbe zg|ThZm+#UZcpGfXdE1ljmUazm7I99*Bk;Na#Vy!9p27o?7CJhG1(pz68XGgG8J)uW&xSRRal$ zO0x-}XD$7_$&;%^%02%2*gfHKo9~qjwE$-andc9fr9AolA2J8(s*W%N^5iiIiM{~WA<#FgUw>MM@l zSn7%=1LL(vC_a%HOT#`F!5%@AX|jr3YIVeNsaQSOqksHG{7AJ$L?lj>j^12c@PLkT)6S|IlyLZv^8Gc-klflrySAO=vslS z^_@KWx*Sag@oM+7*bmjx`FSyiziAu)j^qAkw2IqFcs%B#2p*Ti2bmY&Le_<0kIKY4kaq_o#F?IFncSqr}X9hPz8W!OalOM76&RSbRhh zusi+1bog<=(Z5)80v?Jifc$~K^VEr}FLw;=0kR@nSlvU*Y%o>Ww&}J~oU$|sg{^Sq~#O^Aa(UBK$rOtk{eP-n_N)88W4N`_Z<^>ST z00J&(O-;x9x}0d8j;~b$bfVU@?ep;Yhxe<`HCr`*8F%=)wh)NP*2(GC2vZfeIGViU+%mR zO;qw@sJW8-QS$qQ9t`I2eNvNg!-A7n`%7*{DJ|y9@daOOYL9NW%BpU!rln5sxZ^;K zRpa}(hlefC)4K2j{c^^D59^)~eg^OB8Qqrkhsl8OdvBG;XLyai5i5vm#Uy7N<4vPy z&a82rv@I%s0ER7s+Ju%!g#bN0s!Sr4=7PBlwO&(5L~X-C9x|;qTx!H`oMU_feiV{= zbm-2nxHtru5Ls>JnW9Jp^~k8CX{C&f7vHpecPmgA(UwsJ>@0AD%vr|#_`ZJm=*&S_ zx}64-WWll2SoUz$uoO0O>a9Pftzk%?Bl(gc7yKnstyOOzL?kL9hZSoT#d!k**X_{S*Huf&1y8&9J; z(%nx(6@$5+^lq_ZPg+c*X4qf^4|u>3zRL(*Ng6h`gbp(Cb^Q0Tpu}FF<|huJK>K&r5mD58NT z=Q%O$Pi6_GXX9((B~1JH3y#03zVMi z2mlEyH&>~Ph>Tp@$QEJgM2p_&J?$s!;J}1MDc`%al= z`OI_L_P_wRdM49}EJezgo0Yin+ zQiuo(OZoH?)V*L%kaD2m(gNa+{_MN7fjk#n-rkl0r-jN#5(x~rd#d}5s`IK`7oT=R zZs#}EbtuOv%v8qOak?`KxL&dNIr(=Im|`LE)9r`bpx|PMJphOFQUF+C^JA(`! z)d+I30)mu-g6!Lb3140jd;d+P6-c@_8X9w@I&Z@+=5ZBWdAZl?ZPWPdiW>6IZrdf7 zrIR!p9jy(LI+>PdI&)CBU$A>GxV5!~&+A6l>b8*J#Qz3eoQ}R=Gas^dG?gS-@8ArJN$_FOlJKfBoQ&6LgORKFydR6ZYYbm!bK9nD_!Obkh`NiYe2IB(1#e-DJ)Bvh`tF`w6}7+n zp*5b)pj+b9#$H3G$kbXJy6#lFH}eiL#fZK@O@LA4z7U78W3jx+`y7GRC!{r&?`W{)(W{t0n$@yv!;Ct+bbF|@nH27^&8CE@eS%aUS|!P0(-!`!ndfwsAR zpR2H6u4q&vc)}{DrY3H!^Eqv^z2P)Wt)#%eSI)xyeYexSOS>Se`S*j*=U=$TeNo=L zhMwR(&sV^b#^oWyD}qTJwfk@11BGA^0KO1VQ40WG8qAUk*L$Q2AWdqbgiOrNK_xyR>K5|rrbJ2C72 zs35KT247#_R8D(hkOUI7rAVddei%GDhZ`v#7)^K#57nME-&bixbH}--yewVvNd4d> z!+Do`GF#uz3@9YKvwd%-B`(mgN|9YHk3SmbH#Mci#l0mZ zBVrW>`yAkQ=T?1C8<}aF-!cWC6q?Zf1Bt=>Up`})hSBMLqWz=Aj`W3E^SQk)%xNpW z)8>KPmz8%?Vp5NFPZ9uszQ0mM@+!$?V5pUx7CBQ%sA4i!S;<>S*MjP2uZC=bf=XtU zZ$07^`?k%3X5)P~BYV=Qte5>t@UTrG_(8zPfAn;XL{)O|9f^!D5OTz+>x;bfM6+Kf zwq0=m;azj_p%?!>Le|alC8LDr{TzyiV>!EI)SG_3)RX0XTW z9vKM-XCjz(b&qF8h0QiLHU<7z;x~dEQBY98P7^4KB=UHu{`VPLm-uang=P7}IN#&* zon{`mun0J9OD;}Nj$`~p-JC7lvXcGvUt-wB^V6Bohkvc7m}PZH08u&!P#6k6K77L} z?`#9D-ehR*XtrU#XrboP=+`rX;%xRF1zRjuq2uv;w*S*YJ%)lLQ_a$Vu|SsO$zozk z1x0`rV)54Ird0*f2;<0Bff%yJ?FsEGXpR!74^P@I!{?PF7Ef7NM{3om_=9S#nV_y|ScTn?eN0OG@KB0sC zwOgXAxdxbyzj$%aUO2%OysdgT6(OGes@v+u3}~{@bs%vDcYH^M_T}o$8h3H|?t$|y ziy{8P&H~%Ht#pdHh<5P3%9r-z8Xzp(eYtM?)}<2)#HjOl(k>=0s7eJVh8((CMCOgg z^z;>~Y-29B2zV&PJPR}wi|~!RK_`ruU`>w!2Ob)j^`e{AHqS!bZQ`}@r?%Ci-o4-Q z_tvAV=X&>b^g>oI+&@vI86F?Umz9-g)P+VG7pN9%SF*^uY2(1wNa-i`{?TdQLQU<_ zP}qQmUVtM+Per#y$3^YsvphD%#a(Q{wHOiz8w_Xc9ISM0-NRU}j;gfb$!8qtR!asd{p~@zDhHL(N|AHV#DLf4SDN975bZm&~i`$1`7?E#M*@EgcKxEgsGg`s&dzx=0fxwedma z3rWq_7w<3z7tqwwRC6OPCYUf}Tb4cG%P3ufaIK|22ur72TMI33jn2^@a74Wr1 z1YYY+Z=Uk>Dv45er-RIoRYj3|6i#M)MD{}TBDLDWl( z2hI=pWWmFL`afE!5^5}!dk0SYGO`u+;YmotNc(*sFs|MedoQrm2`?Ie>S7RM++oV| zn(lHJV!l6F@M>FiTM6X7Fbop)W8vcufi6?aT*y}-YpReG8R;`{Z>GMyx57ngfNJJ% zrmO#eKNB2*?4lSFa%sJ5osSXrk|82V7SqK_boU;ZPw-cb+5Q3@R{B7`2kMInf!sz7 z`V1=Bx|_Hb(oUvq^)>IrVz`Rc#D`c-qzAgBA>+KZrND4xpFUi-`q=cjgsS{j?> zCR3`rc(ML#yOui&H@lUg&}l3>{f-Q%`WU5nY4Z<_*4_HLH=HZl2VUOR{!J;}l=wJd zfb#nF5%7&n7V9ig-g9C6@)acUScd^wu19_~<7cW5q*&YA+2)*u>0FpyJ%O9JIEqY9 ze#3%Kqa9Ta*E&Fl7MzEx_wzQ@ykz+o%;mZ0gCF>wfLV{Ys3;sLYrtr+dc6dbl8}unkn`*_k*Gecha7mSI1)OcjI9`Y`GQmVn|ZvPWqO9ejFH!y!EkK>);2= z+A7C#Fk`U|STw3j1GA10C)@y)SHZU)a0*1jIAh0~fivM6yPb2EQ-#${!&+1L0U!SG zwxLJYXMdB$@HqW#1hfWke(!(k`4J^TKu=E|Cp<5-aa+1eYD38U5so{EYpkL_gHDa0N}8r zBOCB5748;lL-Hg6{mpu|ykw^P%m=K#gnKs{9X^6Lp5TFlBKIF0vF|;XEu=<+4tg^4 zQGP1{dy09*QE*UY(C`d(Ee9SA@u-{mf%!eSCN#uoLH*GN@)2E*wOEl)&>D89pLqlY zBgK0F4evPhZ1RS|_U`721XYjR?{)`%HT^}vtS1qop(_pT5>p26(U4XbUO4P~JNt2Y z1t`Zn(#Qt{)>nMP@AlSz__*CFCz1Q;<)iF<25qf4Mvkh%@uu3_2ErGR1$}H4x~qIF z)qVim`V$YKXa+pPPL*Jpuv)F~wq`>R)izH0g1` z4)sa7*vy22eBi_!@yN5isiNTG1X7l_RRbhjS(VXDY%OjuzZ7*}pIBdhT^ZliD%AH7 zybr#~&rhdaO@1p(0jjScRui5|CQsl(faMF!1IL8PXFBSN#t*6A zBOM?J<2pJ2Y->`uG+hFco~s!khYP=O0|`*w5Re7{sC&Yq&b&lU4Xf&Jg~32V<=#nM zVm-n0LNMKL64Ns0xuhZ*jZUxEeTlpGv4H^r;6&Fb$4Mf3$^dPL`qVWwUk0eNmRR9V zKA@xZ@Al!I>Hb3s<4IUSeIrs4HbGGqs(|L-+g}&Ser@S>-Fvf>{GK8zC^4vcNCvPQ1VQSqnV~9^CeEYF=sW3mj`#({b2I?;pQ@e_X7vO!8~NmL>J2;IhY` zvZ@C>P)rh%=mEK>AzT?-{<5Sr9$c`42beP_K+T%DPkU8+T7OsHad9NF>^$`fq-m89 zz&ef((m{GHn3;TjkAdNLjc@DWFqOH5cy#wei6(**x1pgSG#d#mJOEtg1>m1+960kq zIDF7?Z)X=Fl3rf-Kct%fS>?t&w%t;6ty#$j8*~8}3?RQJl_=Xta+-;>jZ|q6Th_*t64m8o|3F1et;T$S z*zS-Su3ojjWZAe5(dzY*+ec(XaV1E(pDoy6ijb?r@ojzRdpKCQ;E9Y=(?qMEfzmoqmoy3s1?1- zkT2Q_!qxl0PanzIzZ&-ZgXFj~wFPddREfLV(a+20+qxl*vU%USVvXa$h_nnN#AVqmU zW8hwp+EiET(5wQhCX$nss!>7-hC}-Eo9Y+1g2mCv%J{+=u{v^k_k6m+bp|?wsX<;O z`a; zycLrO<)BmAZ*5ASl1c?+JS7n>euZP9v`tbG49wGwj9;o%spTc!HLK|-I>?Ds1!EJ2 zQL$!~$#QGDyse#)9eFheuqst7*)wVTn#&bxB#Z_cr11ZmSRQt@7e3b-}QC)K2Y%}gwE^22^Twmv8vVp z!5oSe4S`_ahSo*Io#Orbgw*QHsp@6HO_GN+H3n3y<#!O_BT_bZ-Y zm%Lba>`uyxpWQmbMqQ3@CTy3^w!*IW|16=EGY`tqSJFSO4OKw@^PbD$6`(FA|Ec7| zBnf$p-wDAP2zp!`_F(+PQTk5z!PRuDqv!VVnYEpi^iTjXo$R&H)NeEBXBJd}=e2Fv zW-jpUraYBOI|D0JMnPd0v?k`D#6(MF7N#Jhq6#t3@e~tY?8DM?kgkI@1u2Q8rp$VGM5O-&s@fvV zrX}6XkMl-V4lY`6_VT0OVqx_FS((P}sbbkQOYy|ISBby6vXW94s5nzS2Cz?-w#F2# z>40G@|KJZX__-M^T@$}l{**Hy1ea5<345$+R;UCPJ5T0#L1H%Rn+T$j43Nd@1PVTc z99F0c44e)FVEx$(kY;};O-26hM7G$IB*?#|kYAGs$7v{cB-%881ucwxvO6BV4n93Z zz5%>TDUho_=_~XoWlSbii>!+MuUXp?M(s)W+2y_xPki^=c z7K-+f2yf1XFW%^MqpJb^(xhl%^zgdvX*o7hf;sn26ub!AKREqOR?BziW8CC9_aF(X z*lI1wj_}&ED;X&oTdR8!(8cFMQT8wD%gOsMGB8(SIdg~Hj!dy?x<*xZH6txdlvy99 zrXpl_r8+~NQ6dR~3%|$|6=v>D=FlDoE@2V=2^-JVZOZS<1Ye4~&A~6;=u>xKn5d|s zDXEjQ&3}vrB8V{2`Txa?JSi8-0-3vn>b{OITGyh%QmIQfb3%po+&vLC+_#R*`^iQr z!StC(3o(j#C<^}qIIE!BP#|iN1M?7RvqEdez3t6Ulh*~87f)3&88+z}s&?~|``)Ky zS-26n5dUvLE=FEL6^iRV>P`~_*I;nJV5?Be?HfNbm%#Odl=Ll-G6Ka@{q?B6_55%L zudy_j*{NK;T4KSF)%kFOgItq2*DLekaZ~A?fCw|cG0P)u?}j5_8pAh@Lh6SySOJ_a z>o`rdbIz(&q`DkMY>J}zA(K{Xg>op%1HCZPT!oOK|m zJ5iyjCg8#x1-&ki9TV0rM99W47YI1NNWU;%zzJy8X9{nf8Nh{ zu!-bZ;eK$kmNjFAs%k)|&+EzaI+QUN+^-{+t) zBcwAt4(yc#?`1#quzu#8m{!$mR3>(sFiCy=B-_Q_la13GThFWhfB@m-}Pck&|12Y-s-vtc)`YPWD z@I`20+Z%2&+d8MdG_^QlqUe)vXm6mZ**fA_U3@4E)YY}6GB(DfrC~1mIqP`@1RSAC zG8-A{f_TI3kJ&*AXQAs5C|4k9IxS+ITg7?!|g*m?b%iM$+UDa!f{=_`~x_#XL<7 z-{%|cNdU1QDM+=fVu`ik{N%asdbJ}a1_MgqhJFmAX3iA{F<|UsLNrupXd&`IQ_M#S z8$nU7T6&h(7XW5)T}yrAlk2^nEN#M^Rt+Y=u*(}=l8|X7y z0ItyK*vo>L1zZ7=X=}q^Ao)ahc&?$Ukni(TW3xXpvBiTTYHK<2-R9nIxtuFqcz9@N zSF7|kZ3P=@#b@&yyTl*Co2;jrOW)hWz~O2DqSl=sbSN6yraD$=w7B5yj!a9q8SN{Zrjc(?P!=JE9hl#Z_lH1|Z=j^^Hy|o?J{(y## z{{I)$a+Bl#Q|?rDG~e$N4F%eg6e>$nvJ8*^SzfEx#hHg47$EkJ7<>~8&O&DGO!yS= zbBF=~3=jwb-zwLt?2i@&d+=>~J*BkNB&M_t^aFNFIs}_r`!O-&7cUQSFbeH(Ecig? zh%WQ(u>8G{l_Sf9z{EA;IZ(qigT>1-l7_4VTKvBq^~c*0#)$XaHEP}&z&{RLE`8{N zkLwqBO4vlhGK|4{dlmwqULxQSeO0q=0GEk2GM?#jx|TWmTGQtFSw}TMLrHijpLI_i zXMCXs#hW~kHi3may1_pqHj?O=zKr#1reJ5>0v{v%bD@Dgp+fn>g2s5671o%U^xDfS zWh2rD=W8VkDg&YI-&(KBpiWER+Kf+2lXi5h9@%0@I{5EH*^HI8Vx)CY&WzD~J#uxD z&0}XSFdwAd)xUy6+de5W6_UyF{>v@!e7nUHC>!3_4OE&#K=>2D_X*CmN>8Oy*W8_{ zadMq&-aLctmhTl z)Wqe*yZ=;qb#;Id#7fME@aDB+rkxv&i>BOEp&WODM@WTbIuO}csaY$TgVYcfR#wZ| zpZI>n4IE6u7#>Y4#y}q@z;%`-a#~MYTBa~M5H;gj&Q2X#GQn)p96MHt4hmh&M}X`0 z@H*IAEg6_09`5YV@CYb(Q26Zf5&ULA`~{i{hIbu3d_7G?GuYTH7HH8qqN5Y=hLw0HTY1m$-h3LBMQ&t^3Mi*#i^KxkDhjX?bvvFl}@FgyNELbCS7rATD8m9>s^7%-ly#u z!#q;o)kQ0?x+=6AH^C+pBF|4WER2ftT7ZB=9F4GV+?JQR_4gNuZqTW}2p}XBmzUS6 zPjHD~z_oDfiTjxnoA!SJpjYVJDzf9Gw|gr@!;)y*BU-gY1-ih5vd}?V^@uh-y7Z@&hC8Z}@4)4tA-8T^ z*vUz2Oc0#%^Nf0k87r_>1Mz&*zia^#CG3*h9d%iW=*ebp^g5;31OXq)*&3&arG^== z)#;{v6dM=$i8(Nu&zFeip-=3@aQ?!g|1(p(@QWkJzM1o~0V`fG=QRb|h`r2WjBF}d zlatv6lLXxiqHLfkeR)28IYJ&A$AE)zg%E>1_ExB)vgi6ZyvF$slDGADZt{14)XBBR z#bd>zoS@U2#%_IkP*(LGmPrZwj@LI|E=Pf)s(L*!DJl5J4~Yumb)JH#YtRYgjQ4NN z{6{sO&YBWMGSI|JT&)9Xs9i#sVgJyO)DUe29ruGnM(+j_nv&K(0t`;5zlH+aOJRTnMiZH}q3N67ajm&_BtOd#P#446O%M1fr6L`!> zNJvNB4vS0ux!ekltu{jv32o1BfJ=L3&ybU{Nb#{7b~xELl#lOycC!9wR;OOEPIRC; zOHo!nJj&-F=6WTL#oQANHPeEX5)u42Nq6c8n-ZBu)U9z{U|rWp@B78!iW(}PLH^38 zyKRVmq_30oD<&=-UbDzm`+c{-KSf_IG<#23&SfIf|?lTrgd8>ozrVs<6K6O5=JMh^>Y0t8XYVB`rThc(f!+f)1GRGD8Ayi#01#m_ zPl|KzO=cB>_k43Zs03eSf5(|27t^O|ULQ-RzV%1x=`1=G}SkO@lQRn`~rj^8QwlF&lyP?J6inHxCaF z*~mOfLc(i94QExaSIEjKFIyRZEARAG8xMp;R^WekK{y#Km#;nEIc2Y$$cIM?3dqYN zwQD`*fGVuC;OjPk4*p0L4ktVakY6wVO)0Q~2|Scs0K`*v5R_m~j-Miu16s7mP=RY; z%A1r5Gg$U-2sH0ncqzV02h}&tpS0^fty=d0B0*9_t%Z1$5C)td{}nRH-{oaNj_@9)TB!Xq`r}Mtj=x@#4nm?tsYFK<)afx30E74B~m~LJA0|bYI<18ryq~ z&L^a#gt_K>qZ{eVQ$R^kHetDx%1ftN^Bn-ZW`%Xe@ehdke}$0-AA|qw&H$W#e}N#7 zl9m=3XfJ(|UBc1}*9NrFJIAolA=`i&ZwjatXzfF-EbbTHllDgt$CD*p-2#d-5t1Bp z4aSJP(5G3IAIJT-{5fj*5!QXawkNwM^Fwx5d?O|-4{ht9#odjSAciK`Kv_9ZwTFVh zU9X>w@9_k%xPAqNb-V7E69?9maC%>S%hb-htL0cr>mOFCJ1-qxS5_c?LdTlo8Q?R1 z(POAlZ;!OKPptCHcIXq&zOpv_c)vqSm3DI!U_xPZIDqdF}>qT1@_A@KMi%)gn7`rrz0Ys;E19*o(_%SBfzXr+U~D)6=EGk0|8 zn3}|x$g-PK;l21nbQ~(+H^L6SE^5w%$(L+><3OLN5Jm?M$Ah+{o1>yLpY08vXe>Aw z)o>g>4rB550c}E88D8mW13+F=_eMwtf@kGz>vB9-tL}E2_}~M9#TLHe_89@$sLXE8 z0d?r09Ptm_dhS_H=xKqn{Uf8Y?GIITp_LX_Yv7u=1L}M{Zl_OGSDvqB3_n6?2X@0I ze5H>$j3z*53@*=LRA-e%;t3Dzp}rv>XCH0_w=`SE>JkdTTs-Z3$%0-YAPD{PFik%c z4=aj8HC(F#3Q)Z^{x1>9Gw==2u@^`)w`noaY7oj+Vli3)yC}G_!FpikewF^u#lpCg+FOXWQ)xGhUG(j%B`9z)P9?T=RVJzyI0^t|=r5MZx8W2huE| zl0GhwH5dzOD_ymf*pLteK-!ghxUSST{7oVMM$xkP69YqK@GaRqg)VBQ$FW|Bcz&+; z&56(R-7Ylj1zL#m-}6HgK7}!$`KW*k1AI9XMG7Qf!5knl^L>F-i%T~nEO~gTz`TCJ z#_TD{6?+#KYkC$Zf@Z0{r$`s*BGy5LYEi$aHBy{Q!~67bI`}z2Nws~A*pPoA=^R}f zRRWJe)Q~$}YUlpS;jNWdEMq zGtU0yxmDc3f}VzS+Q8_5J|E0Czhq`p&f~y)T^;m+f~N2Ge2b_Q9=42Pc30cHc?9@w zZ-J5(Xl-mVkuiYJV{+;Hh1`ZZ-3kop+e?7W*dcQ`>ojyYG3f8%;6ZDB!WR*{M_ znL24p0LZ-H-593cN>&z}ny$j<@6K2f1q)Pe*Sn?|$x}%%xm|EJgSYjY{{cj$&~fi_ zE3WB7Lyk%wrb_+waqA{X!}>d2qMX6y7yuHBoDQa`A4pMulST7&gV}RvM7eg`I3T2Y zUYfl@B`{HsN|snSc@69qaB@!r>LgIN7~!Ac=LbN)$*rqP0Kv&iWm4ls<>e6o*X1}u z-skD)NNEh{Y#b*hcg3)9)3~q^8PKLZi6+7 z<(?5U@Bp)R+#O%PD1aAKl+ftrryxnH`-;&Z*h?}K@rSuBSKuCdA0tucR8oTZ0jY>v z@NK{f-}H6tAy1DPPpo8NRfud^J^b{-!UAx{!YetLiIdHx?AzB#5&ahTo+RT{g<*>yi&C%mJ8TneLWwp8Udsa1Jrk} zUVc|_WnLExJjJL>cZ!jx9oGn*%GHtx9ezIQTpy_x*+en&t1Q&S(AtxFT3V}XeArk&Z@SbY8lts(E z38?>|YYBiEHUE<%yenWI1kY1Y6zH173ydznt@6F9iV-w@b+R*iV&D%1u=v%?c3Hbym*YGbIO!a9&-rNJ`lh zMe~civ{3q!ZiyYazD9`AFO8UeK1`XBL5ryv!Rxkh{)$4btDGui5)u*~x1&8K_2O|2 zf=F@sOrAa}b1sdY&JmQ7r)Hh~bNLoaLsq_A9OQmSB)2 z0ptiyk9&@wQ)DPTY6EI1yZW-PW5gG&z4pJt)PSmtY;nL5jV_FIXC&z(x^;-z*@vRJ z;^jI-884p2))SYOm$eUxK69^?uv0-8t9{1>4)^`hVTbxW=tGah1uRsvZcA)ahs(?9 zK7amv;KQX3Hs2|KHqqXQp%yB5to*%t%hA@}4o#gqa~Ik70PtdvnOkeKg$t||IN)6Z zVowewCQ7hH{@*Xe9dTl+wu3;c91Cg6LWki@?!J1z@K6E9OUF`YV z>~zE8Io6AR3mOyzwnNZ#k=yNfg`@b1U%!}=AaAc0R`a z^e&odd36+#M6V6-2c6t-Q!^l<6BuKjz`r@(o*4olfg$cjS-6QQrc&3~xnfqbCVT2x z(>_J(8oSVsm?*ctQ?P$R8b+SK*v0AR%>G)nIJa{JQs7Vjh^rEQY{B45AON=LMI4EX z3q`gl!*Gg7fPU(*>TF@$q?qy71{O@PACUyc0w78&t<^OL<&}rG_gwMqSyPGNh1Pv9 z<2$S7#g8S*6`0QJzhR0PD1d0u6iB}0wFRuUoYWRlqDhLttQkZ1pOSjHuFOv1*m`eb zPRHQq&oOro+_y^FGSXBY|CnhSZR+iT;Rqk!HFD5s<9(K_dLL_)*{QOks&nTh~^{f!dp8s7xw&6ay|9ad=CC1 zq>B#y>I0=5!HZkPvBUt4k?lDPU+^(^Vav>e{jdRXE5GS;GDhpT64{KqElP_ zWffph#`W(PAbETRVlUb~tEnJV1T_U&Yho0%OiiW3xE0%!1wHX2SwaMr)DVBq0ygL= zAkDHN5zx~w;a@{a6M+aF<$>v%lPs(3Gw1003Sl*&uZ;>jsR}^b^sI^;I_DrMV|}Ee zlTWNb+?s4^<*+Js*zicVAxGc^Mi#!RfssML+e%)Tjk+<{$ujc?2I)$~RGiu(KqpL~ zyLvI9gpAjl2J&Ag4}Fj%0TE-Ig$l>S6b6A_Gr2rV?oAnBPYV{+9grKYS?X6gE|~VJ z_$wOP{>p#qx^JfmGxeZ2s>SnTYPDyKU!U{nbl;h0_{mIDIN5vD)+P(m=^J0Y`e5#k zLa6bp@QaXZqgH94wwyf0>3nCkEA5EQr@gfW7Yu>8nwdec?R)u{PkN(?^A;dR-;|wftT)xY^hlII|fdESW;LaBPS3)9`4-e zeJJ)(Twqf}eaUoa-Y-Qg9K)%c)nDNcD-UjcV*eG>(rwp*1AQ$mS#53YI**vgpgzfQ z=N0Ei=&XeGGkl8Da(o9IBGnFS&7%e_Bmf(D3P^X}hKm#Hg4Uiiq2x&6lQ#X$muwu) zIo-w^fUkF~7GY&wPx{*Mq|^dM10U)^?t`aTSof(#BC2(U1KC4fr!p3_%?Say*qh zLulRBo^bs_ZIwLPZ%TwGkRII2SEYVNxM?)w;;u@l1jpEcA}o{BCWM9(jh z3~CFz%r;-E2uRP*j5ScY=mKF3M((&#hFIX$!OT$bWz*@9`v7sb+1}L4NQYBbqzYKm zr|yH73!fYpZC>`@BUJ0I2xd82h*@JzX!aqH+BIuyvVHZs%iwy>CHByH9q48Tvu)+} z4Qt#&!cGM0e$l>pkigR3w`4!oJhpCCLGsll&vw?)NV;2RVZ)hI1iW6AQh2|8ew?jU zVulT?c$6s7qQc4)t911yJ%QBQ%u?eA^;FH~sht)ntb(t;sXba_x$|XZX+|@TgxjzP zCs#wM(5!p0TM(}y*{?~4qot-iuCM59qR?d~OecAw#e~v~hQLNcq}r1{%Vr9vsc_;B z$z8g(`3DgjPu57$#YDMg&7jX&YB})w$BW4yV@35HrOTuqr$@g$>}-*~*3e#m_ii_U z*OPmF!!=og8%eB67oOMzKkaNaj6{{=l#TnRVOw90o#|}W z9qPmPw*cm@h- zc#5Ju87@@nspT!rf+REkmg|N%)vpX`^!dR`%W(=JzmGD)KO|F0&|U${S+Gq|frFsGPx%F#lJ27(E=d$?@I02V<A)yJjw~S8|pW%ldYt`A*8)w{O2eHFI1q*O>bdDovPeJ+}Njoh3p2 z)qYr>qHP3sI<67_H|tvf@x9KydF$BSJ4ri`I95Ti$6bFA7PI5t$bLu8qca}7c}D z?ZN^^t9IrFSFyCh`0#_oH~$>E51 zf8)9M_Q7`*Qx=Vro|+hM6{q{U0J^~Ex~}|@WJ92oftfk?*Fi9Vy`A|fz4vo|UF*j{ zmei-(mTkL!5`0%zM!pP71-~7-3P4^ki|GO>+|93%9_{b$dcmkR-i3ms6H5DFVdbvJ zgi=V0A~|{aw+4RwEAqk9GpY+RdIO(2DxDa1+TO@h;f2r>#QBMXWL^RRfhu3SFP5Ju z=!JsN*7$%`u7s4;{_PHk9jp7*L#@MibCz#ZDIiM$dwGK7+cn>eYYv%`9-@3i9YFUU z1SHpO{_By+Cqt)Y;|tZ;IVfBl@`OL)M0uu-#PCwOS<6E*e;2Mcpu6bp!5kiGRsiY) ziGSu=ci}1!==aMX37OF+;5N zFXQorapFd+vUhPB12y}`OiWA;ot$f+J<8|i=E<>8Z)qds{!Z~;OEvWKe97~Q3o&?`a%IomOMCavPa$KNzwmZb zN(;CxeR6uK^I8hqTq8SsRWFVr7h8(Pm~R0b#+tx)rz@t<-fP?Q?)MBo2-x34NXEut(_M;^znm}ED4p} zA!em)3Na9Ezs{re)^Q<)K}IV=GuuGZ+G64pc6IQJ42!`wOHnQ6c zm%ti*vyByQ-;E2l0jsK-g*#N1ms)j`(Jk;3{pLP5jM7Pn1>G4I9EVdR*qZcqLFfZayEkHliu9gn!J`4X(r=WU=jtX zxkR@&12RJfq*B>5E34Nh>ypc?hf(bz$L=2((9@XHwT)rZHe_Rw5M=kfm!$B}WveqI zOYLljCC290CO_3Cu3FiP3e(&_B&naza7bx^D<8`jT)lFiLMwet=< zOTZbfz}m0tIH=)S77Nfs!*(_z`Pg6h=1rrQVBe?fD{E_C*tKqbZ_jl4$n*IJ6K30+ zZATFSc3N81=+Kuq?WT_89@0@7j-x=y@ZXg5;QkJ?N*E8igA3sCN_P5s`E3yG1PH4@ z&S7=a7o#SmPBx;@O|;H+1H1)x_4qX?1YB>xq{27i3gtM~Yw4{TNlHU8rpZ zU)W^J?M6pwo6@7BCG_>FV9+Q%l8N3y%zf(8y|R*kLm|m^5Xa9JI(Qw|_==2-Y?TwO_ege>!=F_86nVppmcuY*;wUEZy9<-A5- zufh8DuJQIOw$%BD$Cq*N>0lm9fiy^i>XHgEJ2%G!M3ckw8Mz(khDCwACEz%aIjB|H zNfKodFhVU_`EuW3$=#hSv95Sfs}-Y|tXAdDRb8_QvWb+04U{c3m;vs;x+0*T1}fv@ zI|(A`pgRcOH5V=m?Js_#$z3eKri9+CX2<$RxNJhyjE8rPsJn%EoE z(JMXut`q2Dyo{LNt;FzF)r&2DW}rZ0oB>o~jR1akz=;pUPf$m|uW~f~7LNrD-2B&a zU%e#13-zMA@ED8QDk+v-j%m*eX6VC*H)OG_CN!NPAyBEnwd=Jx<_IYAnO^bCehdgj zqg($T_kK$36`z>{+)8zLmxhUdKYhA(y^m@N<%_u7p${6R)L%GI`En8oq1~0_HVdt zpI3WhWTZ1K`X~m9=V+!~0~MjVhKxQdKflYUrqALI7gxz^%^HC=biSsn=bxM`6ow{v9Ip;$S`Sgvf1$~dL@6E{p|=WDG5TU z=3H-|-QqdIhDyZ1#g)sGGOM|2+#GbVZ8UFGvp1P;GdymFjsz~G?b51am+wV&|{37+HDs2DCX zh*bMrJDXRyYB#a%G3y|htx#B8RORDj7eV!^%!+okC+>xlPRUr&^3k0;cPN7QBG4qS zGhgROV0jvQb`?kr2j^h)0!WD+h&;x{TF?<(l45esQ)foH9RmQW$U<~uYfD*OUA-VN z5=0&D1Gfqhg8d_3q%MwU*1!}-#D#+#D0p~BC%-i*It1pOee|WMn5&Wuw+x}=hDUM> zu{>JFvQ@hq;g1<(w4}5*Lw|%;17L<2j{*S?*oEAod zc_b)A0_?*1w{J1+dfo!H6wGf(z-+}l{3Ws{aAiVwxiSjCnaia8E15m#6_|dFpwf=K z;}DPOSy)100MS?Re9Gghy5>7jrq39%ItElr)m(%?%88nR{Ibt^?7Iq*D~3J1@-rrp zW0LLnu7nVmdpaG*llW}O%gfI}D9eZL4QzqE%Ep#0W7YO23f4lYAbC`;bo8!{T}b-) z#X~mQrh^Ska@-h)eJ6|@_F)%MHcY>pIvjPngg?^R$Js-SVSHiwvuwo8S;A6@_tHro1*y9=spbfV|m5*2W@0V8|`ENrup@Ufm^qj1h9Q|UuBwN z5#r-3$=8DrA97IS4k%46XouWA_A1q8D*zv^_}Psy&naW;O`Mp5IlO0_$WjaGFlgTe zq?$I+6kJuo@V<+u&aA#M`W{JntH&&o#dtvejGb)n$tgeUtgNi8uKOrjnW<{gFl4i?zcme%hK+c-Epd;uCm$hjL*ZCAhgC9<7r;J}jFoy>z2ThBi2=;)ADQYt@u zCG94Kr%fa{q$x8yl^MkZQa&mwGrB7}cOT!JBk%_|3K&T?x3FG8qF@?1Pb)_HY>EEv)_;*#_Bo3v`7(ZM?r^{cjaP#=vuv{K?+^r0jn zDmVw>j4HJr?%CJ^#{(#%9}!HMaqnF#t(bUi_Z_XGb>ge3ymqO@H-Ib8t4w@S`&r$Qa<3vA8;;>3n@`7P@gj4UiBfN*jdCDMonNrMPV0jnqBMeHDrwNkK^ z^}@}7++D^3z7in9IQ+)hVnl~i?JWxR(eZZYJ5 z>GuD`*b`pceX>AO*z4nm@r+de8f0g~7FkBkjd&XgfLU-;V3Ju3kV{po_%H z$KBJr?yL5>RHsE(o{%i0t*C8D%NsSh*QR>*ZY)5SA{b5OYKAn*c%N3roBZekr=~90 zWWS+8l>4}obZ^PIr5r4JpuL=Oat6X3(NI)GCs>qx0%-z$qdF)Co6&zY6zxyVW*=nz zoEFTWqT=#99Th}~#^2Kew_1A8m-#3k?+D2KAQN=ZjobkJW~Hq4bKHBmQcR)*jRSgV zckg}SmU^9OEiL->%a@;GpKD~alq>Q}z-mLN29%_qt(7p{Cx$w0@q5T^dN>*6D~iB! z1UM6POiaNbvN~Fk4x>#W&jVdm^SAPf3JN#Cjpw$q&^{qjXhMiRx?<}i()8%)`1oyN zqI+488be4No6UhTgEA7@2LZe7-p`z>n4^1M=u?^`dV9xE9Ao-Tmvf9j58Dk?IhkDF z>p2(a5K)cyKf95}rmdP7-9BY4a$7hf!G*2c3uXyh>z-p5H9FDNi|^_WTyCjc5qeeg zD!HfR2l+kB@8CxusN&MaJN#a|^K#Hk%9*omg+`bCOa)!7*-7;XI4WWcZ0@2`xjBZ%#KboAnyytVL=#H>J zn&OJjuuiO&xcF>;3ELdC28E82hb2FB)5$g5hYCcjhYLT#u`_Jb@SbIMb`~}R-9+%E zBS=Gml;F3^Qco>h{pI>b+s;P$;D(?Ei9lbL-18jGd@T@H^qO|w$px9>#0KgXAy53Z z83!+6*wt$CHdMvKa(`vcNbDOlFtJ2+4XydB`eax!#p~*8vO{drEqD2wd&W%xY~lvJ zV=aqwXbQq59Ojx|T(W_S!WSQgqN~VuQ@}L>3TDc}CozgizPi#2s|{bwmNHho-ANvx zhqr8KV3>nL37BEel)Gp+I&$)-7EX$rZD!hxyxR}oKTE23C1Mh8!*ll?kX^J3TP(`T z*!LrASKiex?&t5oDh;P!Z>_yrtC_xLMWhWqd<>_%#C;(e?wis!_-uq^*H@^5@0T9qp$QUd%7`>iHKm{Poff|#u9182n|NDLmg1s?Rv%NEw|R z)hs~A&o4V|KR)H_8t7R98t`o%o_OfuRGsrrGZCT=_LW&VFsP|KKUkA!lG@%D#U~0(GM4&=%dGe_%Nz*eoG&Z^shwc^y>j< zhLI1bc#jf6xJFWpkn$}u5zcE#nn{MX9El6&Oq_%_7YJO+g6P2MNO5J1SsOBb{FMc- zSI>WcF>(fCpMIRg@O2nIK;j%hCA9iw!m7*>1QoT*og17F9S2xyaeJLpgVz0+o<;~K zgCLvZx42V0v{&pfh=U=TpP!$u*^6iO^^CVJYc9squs#1c_DbQPTnyde=O_2^@u{J9 zDHIMus%C%^?!8_RRhFm=lw9zul`GxqzyI>YxduTc>fupMs3JcYTrGpEK2be;Jne6X zg{7~U^w3t$)buX>7Sg);dRfPtLOIWzMD^R z*t?3!mdfWMnLlQE3&Tp(?MB2$mJFu%e{p))w14V+-l{iBe}k067uJn_O!h)7jh3ff zKgq_+mmyzdWzUA+od7a62Q zZ^Fatc?921PTuY;ZSD6*%Cke_(@b%*O@*xnwUMFDOOJZS0;N} zR`m@LJ?fhL5>v7>uN?;H3dS3^;eb~IoVFn4%D}^;c|GUuDwrK1kb0Ib+14Tg*c>iE z%q-I1pov)y`~F=#Bs7##+wR^~s+S&FHZBXByK15vev>mVEf$CUrrvqe=8e-Esu&ek zT8Ok*qX59}SBmae@xcHK3Ces90FA3M#||$vuNdPg(syMmXIU7ZigkL~lFeCt+=2*& z6(~M4=w}5|0zjhu16`Y;6LJ{c1%G~jgZHB}mklXsmtIw-lw_~?c~m~ek)8I*`Hui$ z57Mh^nE7&@t3%|JqG?lax%NtBRX3Vo&<%+ksk*U&fs)DeZ?O=uj4xj_S%2_S5ucTA z)!YcJ=m^^D-^;TLb*^%Qc;Ge{&O=d-4*eoXhYdJu;^bd_Gd2WqGN7#U+w>N+N!`_) z`zZKgiyh7*Iy(O^Uy_LAFJ1UuRweE1?g2U)&T5!Rw&gh&a_Q(18;D%4pk1{8c6|DE zCz}2Oz6FM%RW$Y`;vXr;(njql4IS? zMY5Juwk`S&fzd)%R$5wzeSjfG>AZC+3vw8-Ukn8%1DrC$Pe{Y9Vc;P0PLuP26)$j+ z*2i|49k2DH@d?HY2?-rT=CJm$>5qZYbmJ@MwO`#A>jmBX*2wCuY}6z6Ae~WA?26uR zQ~XbB6Ezie&t3vxJRnH^;O)&zAy|p-kWQ=Q4Izzi!>EKt#Y7OI_u>cgpO)l3_}HMtc-vIa_NX>`XTdqI{N5_1p*$WA=cURo5nDZ zaK5qPtd}<@FMCFkNX(N8I<>39+x$NaIXFOK(Hj^Hr{!>D*KEXke1v|Q+vi%QN}91z zQ4(nzQAL*KgoFhCPUg!{2=fB0&#tUY70Ar^Km+#ATLP zIhb5`7wXku-nh}+sz^z6EqLu3EWG-YRThw{_>L5hg<@>uAYjPm_m@}D(bfNmuPLFOfB?8L z#MG=GKYoCj*N}?%IXm6+pNNTOf%STC*u6<_=>R=J%gsIX`Nc{g^nf@$_AJV6MvSM- zz9t;q`*Z6|@QiHKf{`;7$Gx#i)UjPh9<$T74Wc-7gp0tseGa>l_fg6vg=w#Tv@ zRG3tya%}ZhQ+YyRov*bE79qocp7gCR*WR&dm%e$Dr=hp-cBpxZ&eyL)Jjj;rR;01k z-ge%DOzUWXEPXZnhMKRzQrG0kRXF07-Kq%B8bA;=FDGK&Jp-k7MfNP|9#G~30WLw{ z+{ABi-?>xx5HfWyNYGiezWBmqk<+w;@H$rS#29iDA5QuB5Xrc?!zSl}6A>UHD<89g z8KCLO%gsT_>$|dz|4dkT+2`o(_Yo&h=)mu@DW^6WS}XpL9ZgvXy08m)Ikw}K_$4D< ziadJS%H`H%ps0GzK--@!mX~((z0FBE2O&}H|4k{fihjL@} zR!(8Szr259Efn?&52{J0kMO#lXqY+}2C>FuAquiJ2XQn$Ca$LmF{TY=Yr zothOZdgUTvy;Mo!&hrMOux_tB*0#6Vy`5>Aj)t?4%E5J6JC$$PM$mO z8gT1QfwrsINNc*v+4!z{;go2i_6K#_yKt{o=2|0>QUEYeD4MOQ{F1Ra48FCt20`Pl%B6r-;`PrQRXSjonV<@xu2A=-8!ZxRDW${7`^G;( z0kC3-nVFdf`_?UrZ?CAdQJmIbNaD=DD#z7#q*eedMewb+8U;2&Z7?ajOG}FqE-j(1 zJ~%`fLx3TLV{BA3ypHu5oF~J8;FO#6J%NW4%)lVW|6<-t#oR^YLIRnTzGZ(9RuC(j>`RpGrvqJr!0V(w?fD`&qzMv_lu28hZh7n{YNQW)0B+H=^|^` zp52b`tlFwSbpsu!5$c9?d?6XtokbnYN&Ou5FQ$)_l|h2=s!K0eit#;7X#4$Xjzc@l zjZv_$iWw7mkJe%#MFu&HE21EE3hUUO%tBA`URT)SlX+0s$at(@6Zb|l6f}zgUP$SU*Vcw(R$bceLyCywd#V5SnlF#m%>gr!y)-*g0 zH^YNk7ucMt<2V@ZW?2kkg2|t8nLUlUh^_x9t(}}<^0%&Olq*LEM?7#n%eN*;L?V;K zor(LzUELp&nVK_Nsmn&n)bv%_nt)ho0uGfpm+Ou1=y7ado4&nmf0ZS^Hbl2PeL}kG zbISbeGc)Ie;Pjv=)5{ysdA?(Z2IFx+@uJ(LHJ2S{=_~Hh3*|k&`~d3VNk2GrU|JIs@Bsp5`qN;!*1Js@}G_RW`H8qU}j-?p5yqu&!#0MDMaS-0s&f2 zAI8)BxMwB5F+no-9c&eW;*|y+VvFSwJmpxDYsc5T9QL@nG?H@? zv3CeD_bDd(U)fApCYhfjues=UP}{EH=es@kffocYc{EW`SH$tsQ}ZXM$`uxU zsJ@&gOg?umzp*K{0RLn9bB~ishn-;OeOP3jp@b?1*>KF@KjVB*F}eEd4^%B(J0;FI z8^%-RF#G_wWsAu(4Ced9ph512`o7~zKe$ioW1s#UZ$Qs%2*i2ys!gu5D(i>jYiVo~ z5OZB`8~+A2IAPC)U)f<-6J%n4f;sW`y4#qCVKlP00u-WEVz^p(TV3n$jgwJBlcUER#Oc3{zsgwZ5F>>EZ z0|J^&!A7=7LLnP=YT2#sW!1Lx2^QQQ5Y{m+`+c50(BChspnw$C;!($AXj@=3>1C>Y zy-LnezEXgM*kySha=M(^9`p{vt>{y1;mGO3Qvnim8 zcNg7{^szeSG^;G6!X_Ac)y^|8naIePPT?^F<;?KeeeiOobtuSCVN5^*z8lUDhN$EU z(Ajw#h`qaO~N3HDb39zD|(^!TeiE#S8Dhg~53>EO0jEwzZqePpuOmFVAR@eE?z zfeFa`T4|X*`fJaS0W(#$$U~5)2!p3J?3sSW@w~=-rm95|UtW*(HSko1^RH)8$P@e< zao0O1J9F9cEI>^2Rkf(ybK&=|i4p@~1uY=u59pYOi&~SCgIvF(q49M7V$BQ$O9}}# z%>j{F`bLdSN>Wl;us~#AvM=K6g1(g0TMA`moJA_pPlPHlObHxDs3PZk1lYG8N-l1M zYpJOfttH`5w?QHf*yN}FlTB71;p1njmT9VWuFe|`V@nA zYx{Qs;BGpX1vzztY@<(%++FD>cR#m(Mzi!M8((WFMuD`s(iIdN*8)NeyrqEu=Xtte zmmZzhYqXoFSbD?ak&Q1K=G2#OzeK#~Vm-~x%ys}%;^f$hBVMMx>2rjFlZ?YjV{8~M zg`QZ1yZB+=L(gC&A1PdKWh3G+iw1-#GcZ9C+#h^=xhjNQ0J3KlFeaqv7P%ps@o<|t za&a^p=8({q!l-)`d66#HU&pzAF@VfhHE+V1hr^Mb{kY13j*4d@Ak!#8XGu#Eiv=F@ zUCsNfCuWVG@apX*_(#iaNrLwRZk?VX z2dOMy?DrIgCHtiS0aeBimdG#`yVUaexK0V8(oRu4-LkVq%db6QS1FgzhhK7Lpp-!1 zPzl1!uLnCe6~kgEmk8u0lE+e zHgvcNy2Y2jl zz=k6TY+WnM@uV97nI0%Ukw_At)sy1!B zGP1N}5^!D5Flg}R3F|Kf`XSHD{a8>T-xxfWntXbn*PWO0h>-cf@DQ#}z&8LrY&*x~3i+ zJ{QZ&RoemqN2K2t=sn1TSVeG1NOf3NefbfnYGLByX3NyL68aqQjnrwYSjZY_?b*jK zfpr@KZ-S7%>tX`*S}PAe%@Sdt$|k9_#^k-QwaHU54zy97X@XaeF63kF4^#5es~ zqpD9+*)jtg)HHT?4Mg4+vqb|Y0Wfr&fa?G*b(lt89V*;hXox&BGeb6S#$fhG;*u`h zTvZjqw(BC2@z6X)YH=$_;id%W#QgKBoPlU*9fnkJHZz^!cEa#edwN>DC>LjQ;+r0K z9>z4FkWHLQ%JY(<&SSs&{SQ9qcf%pfBqbkb_`B{F!2cljiC@tpMM#)0^v-8< zEp3dfk9qm#G%}_ooTK?;U{}w028T)05fKsPl-sseJMsKlwzxrj#o(^jBd7two_l+y z4%=4*jA?73(R1z4Jc?3))cx<*thD6U?&{Y_Kh?~U0J~Yrdyx>@zzLFLwXl!kw^x+F z^Q!2r_?NvL0k+c%O)9Y}fhh$r$BJA{<93Mt^u<77Q z75|#c=Nmz^iGT3b{`ak5V@h_Y7|=l(Z**jSi;4$VhU(_*Uc!eDcZh4NeD$urz@Owz z(KK(6%hpIO+?FlU1J}M|5`&*#bd^ES-c2bPcw1`&zrZ?7EG*J9(R^-3Vp-EGG*{wk z@@fSZD_<3&&0p8^@A!ZW>2T`2c*{9Bj0uhhq6N|dRv!k=foVOvd3AHP@$>mOr^^Et z5j`gK0TN^huS6--!PeL32HL`Y(oGlZFA3EbxpLl~lAT$xTUk@;5B^G_h`QEh$ARwh zKac6!Ew$GthU1;xXNbc4-`fP=B4fH) ziSdPy&={Onvka)SPeo2_u2n6#3-Y}rHqn`2-j@@YS$`dFGg0~05?9cdeq9$&kkx}1 zbV#M?D-FVEcmJR_YOB?_#DUk1lw`A1II?*A=QVpG1F-@H6mL*6!omxRw*Z=Tb~0Sl zd|=uBJ3@^^FWiC?Y3|@;#k?4!J}BV&70gz@Kd<=grX$5Na$*Ar^o*#XE$e$n8u4Dk zR4TV{DMYErg2$&WXD9#Nr||D)mvGpsR}yaCL>i^j(NUHI09Amx)?d|hzDHmaup0o# zbHfJQQRwmfwLu{dnjH0IpOfH@A!`Ku1ei+nucozwQVoJP=|wzwfr`zG*@MFMGKu?XZJ`KNhcXYmRvS4vl|>DTj&M=!2SMs)YCN-pK$f2aPVj{sp=iPr$!39FC%e%aX%M zBS&@3lS@y5C0^C&2iLO{)M%E%P#AtaM(K!znmTVh-+wGMW~dPiuU!RB9Nz^>&7U0*8O8j zN*AghUwkmBwRUOSo?0&2_Pu=mDyT^%-Ban;aG<3aTQ=1o8ZhGE%b{P~wuCt0_uG|| z6&LG)^2-HwAF)FVk{++Er@R$2wSlCgnZ8)4nVtvYE+>|cWHwm2=da>YHJ|LS(Z+Xv z<7;&~@BN-xZY;m?>zxQstrLT|vPkOgKH7c;J*FQGe7%XLx;coYunsk_jRw=sbp!zc z!BJC_kMB!=%HdQO%00Pr)ChZ{U`ip3OM@ZSer*B})pQwQ417n_+NCE#w zX=&RUVB&CPnqTD=HQY33?@V*_`kG;*gWa?wjJ5D-{pE*9DxvFQc=jDk*$BkCw!p>I z(@XS^x_sHav5DQ&!t8FhQRq8$U-awyP!2Ao3S{>puL=2OY$<`i>OT7|o4T<^_~4$` z^keEqm})fg1#$3V!j)9nrItR`(}*jOhInE&;t;As_?zQ^1b{JFOB zW3EU9=cC#yeHmQg!-C$Kvw_~$s~Y*u%sub5(g_EHB~cqrhKlfpq`w^RbpO_Pv4|S! zWfbhXS0pH8HU%#Jvu~sj{)V4hLClui+ajxS@g$^ zVur-P&uR{%ZEbDo?CC#?MvPUFQC!bR*N6d(A^o-`Nl8*2@7G^OZ<#dJ49~)F=jbr~ zlDdANFCyZP1ELZ5@j*?LLB%H_Pb$AG+|HRF$@_aa-=SX*z4|?fdMxrHc=cA2iM7 zTS}oj8kWI-xs)0{ zv8K_?)4aXuQ%4o=^T)BY(Bj7mw>j(J7Z%hdinlydlD7$&kA(UrJR6<08F|cJ9Adol z(=O0xvfl9%dtI{Dx%8%oRAM>N9AChhca9tOnabxzvq24le}jdim|rz2MG}92W{|-* zva;mjkFG4$)zyupO(0hF@ujg$gy`6BKU|wL%zIYVq^Yl5n-%bpS2WLg{2jX<6|<>n z`|^y+ZR<$?+ty4ln!34N=Ti}E1VCB=kCwW`&;F7cD?aIQ+5`4ZT6U}P}`fE)MR;OMa;y6Zg7CRN|0QtHV-SQ zll_XxjU^s^Uk?c(k8z>jT4k26eEfFk!ou!cf_L?_vzT2;&k5C>h!1(nZc33%mTGB5 z#at8h1&6vLk%qR$;&_GnGy2CGVzy^X%5?acG3UGgEkA8oeql$M!h*2;mSa7}e;%MP z^^Xq>M8|DXt}Ua@qQqR9fq4^`Ccu*-q zf7pXoYNo3RcG`+3IVwp9*KfjSW?XK>V%??MZF#vJ7w-RUIyPCA_-nMZ({Ce0=vV{( zyyB+z)Bk)G;U-9>pr~jBAkF^zVmcW_)s2qw@<8&<+V3%Pj2cCLnZ@`iPMBF)e1{8t zjN=9u*Zm?z-X11+)KR6M3}#P%Q8c=29wU1CCMvO8_UPPl?4~d^s*gA6pP8AjK2-lM zJbA2}gQmt0I6{o3I0|*WiB|Vq`VZ35s1Pf!pZ&>_d9PKBs_>XfX7xwJK~kR4#i7p? zL_oUN_07w}bUb>7lFeHJ&ZL5buZ^XwCrw;oKaBH5*u5ZmIO=pOz8ifaoZ4?cTjbFg zeOScpv%~DS_PoqHiT_^e>%ZDlSwX?j(edUeJ@%g56>SAJBC;9| z&g+-YHx=bol@*jwZ$znL1Fz+;3ryLI%D6```G$l%5W`B!gZ#y7+L5<(zH--obnMiq zu}cT^sorT}Kzx)N{Zw`4*p{4(cXqz@?XkyWnJGR@$=fQ=$KnnXkQ)e*QB`m)`$;+m zSzecASCf&XzWWo-Csz6c8_%5o)*hdXZBjjEt)>J zy;M(pb;XdF3qdm@w7J(^C)}2s$8zw$o*rYD27N6)ZkLtQX!+!%KB5)7{U#M|zzlv) zFQ55jYlRA?i3wb9Vs~7*UX>M5zDOmPt&x}+rBmHog(ISV zg^up+7rm0nB0AUo@A$_ce1DH1E$8YdGCz+`v`-=1K=6LP(GE8c4z7}e7&E!MZh{fp zEJe3n_ZYvk*JDzJc6)R^f4Qdf(;&YoE7hl8=Gt!Dbj8d4%W=7D!z^V z<*`%zqwYFGdO8HgejrJYhe8wQ74`7zgR9mo4_4>ea&vtxc^r+L3BNT6VQlOVi}n3f z`P2JP;)H4J02v17bg+VemwB*?`Ji%<9u~cTd%f!f_LhvfHOpq@9L_jEg?M2oGrE?` zmi%6;24j59-(xNAX+tsqf~V_cHqni_Az z`_Gy%{^PX6SaW?g)h^LgE?5Nu;f)(NWcLTYH>2H*eyoxrxiYDX*T~ljy1BH`?e-Hc z!$uR;&uTz$6M+c3Pij|BwsXRt!(MCn%fxP~p35|y>k;_{&$ln8WqiZLTsl4lk}uX0yj)}B6*=#9oBKgh4DCU$ z-Q=kNxsT!;q|MifTr^F)9m}O+OKc$xg>;VoQHF%E89XyHp7*4*L<)c%!j$Tl5BHQp&)u9T6f^`!qL$14<{% zN@f^Dj<2P;&0ag(4iag;Dtx4zQ}dO0BL2mUyiWa%yq36qH)Kp8HL0BW?jv4i=iOgu zy11f0NQIlBF0TKCn>}6rpV-$t8+3cnw2@%{(~xp&b+!H8VS`Kwji}E+km1=ZNnCyc z2BKmtya943K&1ko#lYC+#!fXq!q?{Y2&u`9VFMo_q*7r1NF{OfmI5sy_DuZNxW6H5 z!oJ%^>c$%yzmvF=e($K8hGqm1A@;xTEwF#v{;b?HX8vzhQC#_S&oAk|E47J@-+fm` z)8z2`2NWjxPAAW;BM$X{kXoCEx!iB5fBN~p2OU+ykePiCzQ+zzW&Qc(`>C}bqqr|I zNAn6hM*ZEH`i6%Y&c~K^iT0>^_yUUWdmVhvwg;ry;&@ScIdQ|~{??z>bb}_5bcb8? zLea}Ehh`Ip-+ij_mM({@62g=Sxi6|{q;kynGXLFzep-8e;OSfo^u$A*IZA;g2bnb7TZ_8i`?~o`IXrtF#5sminI~`nM zMHw9(wFl)06vV;Sl)>+0G?e<1o@S#~LUraRlX_$(pcbHIXYpfxi_&db2A4MNBlpnz z&;DN$*t9J5pR7l!aB_yCe;w6s7T}*g?zt2vrA)2(UtywT8J&B{ivh6|*1d(XJ-ydJ zjkcY}`g{T-Bh+cKi7yiMXkhQOBSd5|C<-rQgF<|Lns!-u$0!Vr%>> zT5oh|xj3uxRNyjb)dG8-(|yxFmJCXWwbZG=+CrQ^5lW&3iT~G!Rab1c;#H1biK zhMAV=+aWj-b}nfVrnqDbI%WlOaWCH48T3=jwZ6g>CA3Sz)%vd>1*%&)#F zpg-}da6*rFAvwEi#hi)A$PrbAiMB+Wcz*ZVKcPX(i=*dQJkm&m)T9$du$^y;_uPkP zY5d(zb*PhF>Yp?pRQtP2XK^c+K7@@AdybdBQd|xogs~AGb>6$cK4aO>`nO0E!^3I= zZXEAYE$hgrJh!+|4{2U{gwpVpwna5=v<#+&%W#DB*Y&GC$3=*<>j=P{eLvis(AoTR z^WP%}FBH+Dm809Ly~jPs=SI=kJ#bl1)@btdVC0G+w@H5N{ZFXd?^h20jj3XI%_)Cv zp#vGjbA(nC=`G{1m)9F#py>9HuA$i3eKN>k-w14v(xGOP;YgYdTU`#(ol$-xHAK<-JQSapZ`v_<-Gtp#K{tXLac#_wm z{_P^bIrZiy&HvYLXxM(;s^vF%%kPUA5&f3mBKR3&+mL9M2!=Oi?}(` zbhEGx)3A-yl$8A6TK_%UHv~a<*>g~Iv8UO3j*q%Xc$f5hs(enSe6D6Z3wvTZvZa}_ z%LZRVLqoCD_y1d!#q)glX#3%9Z#TP1^Gw2uSA3pJ>@2x-&Hetj5~F!T5Oa5J2OF-H z(zg@k%&DV%4B_kKjq7_0afW5WibI1oEs^3>k7x34w&O>m#2Qzcv6yld<+r&_4a36J z<<;rSMNi%T_Fr>wZ*bJk|61_7IR7s$Vcz%dGl3-b!2AxR!hl=mfGYm?E__3f1enry zf9)P-)N8A#!3?uUgC^N|XkH_-m8C2@&=C>pKuMcv!64vVZ?Qp{mMNRFpe6gVCvwDq9Yj&?6AN zBSv}m@7*eo-Y|uWm`;(uM23|&$PR)ix}L22P6~mf+35 zKVINpAw>0&tP05+_IF;Gk2`pftAdsC%v*#M>gb~3n#L?z_?=1*;9D?1dLaWxb3Yv( zCJ1KP+8kTvb`uzckWFT^d0t>5@h|L|VEe zq`ONxq`N_yiTwc@Ee$MJtM?; zMjXX@0(*R<1W%sAQSgWg&f2Zvnz*VznV zg5Mm^M`f0D527;U!P!gT6#Y;3Yn3%+!1~&MRh$|7zvEso$zmlw3f(u2rZkH_w1_?& z{Uc}^eV5ER(+Bkl=X950`bc>|`W{Kt?_qINr@BnaUb{oat|KL%JPQr_yL^yzT=$p3DE zvoLMR%Gv#VwKB;69@fxsVyp^DS1ZD8D#I_7uJ6!t_P;cw_|^(nZ0X|TG*Bp+BDN)G zQ~cE9N#S((clQzahJa8W$dkc8|GRdibaeKDGLh!Gp$h}h8`soXatKz*t-qzW9$9pa zGI#%9uKvGDN=hFnqzK{fT9|=76#VcYgmK6omYd4pJ4?P-ray(W>!&<4BGwXm;#59B z#POmK(}qJqG%ENARUzP1p#~#Bx}d>>X8*e(lT(V>VB?$Fp99UTFD;;^BqN?V=3dHS znd{jPkuDvfE*-@!ErRRV1$lfhte;G7V3d9Q12acQApyiLm_ao(6whQ;2j;lj&^rCL zo4y?%-Y|1Yffrx!XwE$SK^Mnz&Jlt5zvDLf5FxDl`!B@7K^oqp^9EfM* zjSfX7zs8a{xL*hZ2iCj0x*+;&B*^00JF9qgc<9JQ=)CcSaKik@Z+mDkkq92mDPlUp zcu(F%1o`l#asa{CcK+~&1wu9EHR0tAP!9TrCy+d?U8c>}*ZsGq5e#dI@x%|0f1ckL z%9kuJRm!XT9uDutq(Uu!4Jd&206iS%1vPiJ%eTd&P|6VsuabRI;ja&Yp;R)`@^7zD zV=?zk-o$NTTKxD_m?TT$4Sg&o_$)f&*m=ZV<STIYiZ^(_c&hxbGLrDE3+K_ZW;ByIb%gO{)5<|$csw! z)=|12%uJl*0Im(utwe?J=Zv9HjsEs5ejsZ5dGlQYx6q{5gj0$(0ZW|K-YD8utd{cO zYSN!gw;QlZ;%2pG3m=@ud?4AG<~Jft4_gpX?%DA_GHqY?43*5cGsx%?DjoPHZspRR|8PYcD3CUK3%kwqJ^=KkU)f9aMq zAEKw!iE!G3hjmmw^V`%k@e5f~Ho=x+Q}Ve$M< zzYPdJA%DCYHd^Z=F=AQf^g@(edRF#Pw@AD6_-~neMNKt=>3~q4LH#XlwyFmzmGB8o zNsziZIX8IKv>XT!^gao2Lz?76DJeszc6&h}KLUg=%T6A1Z*ZmP+`+vBM?2Yy4VqZBdiR_ zVeI=sln_n~Ai>c!MqK^YFL{o?e%V`yfBWB6?&l;gOjRe)4lsxt$gh3k7d?Ype6{c# zm%x1y49x2iY-y$-Arq1qG{5QmEI~W7m|;x-8&dW26Y1UA&7ECM5 z@PM&>yPkbk2T6zM$CxSa#6fn9qYaE!Kj#;W5W>C9Cx359J%#7wyeI z&e0o`$|M`AT4+j-!Qq1BjV`60>Q?3xQ!kyrKOp@TJd0}dvM1t`g7fmi<@<-Zdr*%C zBi86B*q3=F%+_K&Iaes-y~o*!`M3Ff|8am#MJSb;m^4fvj72;7dj%dn(f}Qt!Fi}j zvpiIvO?jb1+eZ~#*pAOhA^@@tb^(H23@CxGA+m95LYzU`LylvoYRhlbnkPQ>+TvT& z1zb9gy>c8o-pJm@e(5|`n0;m;u>A_b62(z0fcI*tZf+0{{-r2SG+_@WB$)V}myp@$ z-0o+V>%ZKLKiJQwIxs(}GzrajcYU326x`pRt!}0&uP8qm_UjB0+xxr_@F^Rho%eU4 z{<3Lxqd@^6(~td~64f#qCfNXbxqR6s1p<=&GCZ7Unlx}84+9zI%0>QQVcT#2fBtKRsE zG8KRMjlaiikaF%zYgnMOh7p=V?Cv^C0C$Dr#u zIH&Jc*?96TOD4Kvo{SMvdB3+rsV_T|xQO3U`6f7x>z*x_Ak*n9h>$8p-z zQbwEVLNq1nQTE!d?!&5-^3~Cvf(&IEyOH4%dV>WA-J)L6uiw$yv|*eN+GF4mPaMgS zC2pFAx}hx9ZHahRZ3~ng^Qsz#wf8_2#J2k`i)U=vUVK00@KybM?oi+yv8G~6Sq}wk zX+R8eKfoVAlT>s_pY+7J*|!_{0z60KE+G|_D@8tfq$JqPI|h`CC)s~;-l}9N(u0m) zdb{(P3@lAARXv1k(AA+Iy0^FIIB!)dPM?$@Kq}6n%D+9wEfFNh5Y35gYTI*8J$i7g zt?Nl=$iV^C7!0o^%oi0`>ktwYH0H|qIXM|5_gpRY3F@}6%`i&>@f%FYMh<%H)c|<* z+*pIPPMcp|B2XObyiHDfZ=|M@M)rhY`4v&7t}DTugq#67zbf%0@yoT#)^xQFz`WpN zgg*Chz|T-@%&V-EiuI8B3`EJo>B|_wOaXH+LU*E;2}!K=TfG?(UVfmfSw{ z3yggXPg<(pkUx#J`qt60u?1hgV1PRa^kVxnl|A=&H^WP7Ni-2dQ~O7ubQ=K@CGkTN z%Fh*uLC1n(dI0Wa$Wq4rx>@vMH&i{@CIB<$)mo^yc|i1wPG(i@&*)6cAQQK=6s=sV zI!vYz0bn~;%iy3(g~*MeyCs!9!D2bacqb}Zn*k@^%U8AhxqrlJ*Ck~LZj!PnxdORJ zhP5|bPx+3-mbi0h;VI^vCBD?)s?@C{20Er8&$q7ExeiZ-1KG5=S{aQ>l-YVKqOARR zE9T8YyqVbCH|fY?Xo;gX6|Yc8hqDF;;qcqh2OYOsK+VXWu+5*P3HWFCyMyx;KN*m)&{n?CDJ$Tb?I1G0kq2m_Tv+!6i>CRWxd zp|{~zCm&%$bj>GMFQg(=aIvre(wtXWiB0yFt2vJ%NSAP^nHFvxO6C^U($)@JpgdF= zo(8S<$hB8AC%B4#?$e4JJlUeiS8CQwHJNXktDY@sP^I3(pvyH4)HYTXwl%GRg6xFy2sAz}z8LWdKzj4!zO+iJN;#P3ugMyuJ&-vu5}wr|*yE@>PPe)qvM7}ouNTS_`mCI~EBy-nS>KiqipZ*6s+jHV)F(Yj8 z{VUMs90&?MJ`bVkc*PLTFjl=rV@F54_4W1Ur50mFRQm4jQ>g)h$C*`|1GMUo32C=m zuR6O8JQhQNc^EY}0Iecrm5%xW9sI1d{D0YdFh6{1LrTwv=)zo~x<`x&v@W)x6{wIL zokowho$eAG>MBTsGdfXBn?i&btCdYl60)*rV4oG{MTTBdGNU)dkdjgHn=s}>xMlYO*NKHM+;KH01Ebo~> z07rv+Z;X*uLd5Sgp?U{Gwxb)oAa6`lk~Q+6j~>>J&ptxNeK2n&?)Ov{rj9+RCID3I z`6`UW#R3_g58llWo6?&A{X7e#L4OQYJ5Bn0iwrc)uW1vy%+n0Q#{G_5_%G(w-|JcQ zq*&-bX`R4%#xgP}_ir$k;k13Wqlt0r32mqpY#s8KyT4-)`eyL~8zN>Log-qW_~Ape zA9B|y#IU;8m;}**Cn`^KkL2CZC9qqpqVuoBemm_a54~`A5zI-?%uEEtE3d@i=-~X2Q#@9ZrIpCw+IQ4HqCGe(S4!C3f_u| z&$CL2H%!Y!A2_99KrkjKBvbQnZb#PE+`pwK`Dx7re^bEX1 z1XHukc%bB|W~kks#==an6x|RMg{!LPfKjmaaz1Fq-9$#YZm@kr(HJD@s8RM4950Z}mn$LSh{Kc~iT!9c+zbx{Bax(&ia- z8LDdd49DLCzfT#EEutjIA%mEV%x%?820YBJ3Qkj(yAQ&P7v}{hfi%Pn_D$sAJa&O3 z#=4)9Oyq0&en7+oaZ*80}uJ0jy{Z{LV@g;vNdl`F$ z@Oj@KfD`*8@FN-%1uQ^rpzIBdTIGNvQ;u}!kRl#F`lUalNk$BY2 zzu;*!imHR0QUQse`JbQpl3#z>TJdIcCk$_LWR0GYneRAVfG!O@V)^BFHE!^vKzWV* zas<#KzjK~`!T{?_)oHH9jFwq1cnV)b?9j184FD~#JRKbL%~Wv=rFr(&))H^ujuvn; zU`iqVe0lM$>QInhQmIYU^ZRp~r{*#&<1Dfno4!u7 zBbAUqLK(=R&N}N8$?9o3_i}!bN}6gOqtc|@U>YNmaYPw6uq@;gepfo6D zC}y#|kRXph$y47`sVtWPkT2n^Qhr2G%Zv(Ur5YwqYy_UpifYa`WK(gV21|#Rpv0(9 zi~2%=uIy;3Y@{jg$kFxrf`L`#&+>DQFVWI(6`l$s$o_cN@BtA5CW`MeFqii{WccXF zH0XV@a?F9>0?cL80>zZ5o($#;vstK;ZY?xm zeou!1+}^Gp9_t|JpdVCT_1oKRu>nBl#y#HpU`jCDSxpHAx2k)n!ODtb_~PS0S8<~V zuuiE`1mp1&D%*-B#s*40vKS6VY&=>Cc{jL8OOs~4fi5q+jHCbHk+9v$Ho{y{hu9-H z25`D7q4YQ9ZA7WChr!2zuIFHPF_>syb0soGD{l~v96lM?-!F3j0yVdTzky1^oWk7G ztXVjb{6fM`GmE>U&bD4VQE9oX++eLomghOaR2zs0{rvaAHlp9m@2DfOU#WNQ~%*d-Z9<;SzGyO!Ov>X-*WVY?fc1EFNcGh3TEdOPk6 zMGhO#tt*rj9ywifRW%g;`rTHq)n8G8B}n^-2)b9hzkIIzLXKM*7T@50)$>_` z5g7>{)k5nyR*c@DV`>XBPlE_^lHbH0{eraK~oK0+1BE5L*VR5 zO{X8$jm%5(QDTxfgxBTpwLe5@>(gM{H*q<#IOzOfJN&6+!NI}*K=U8bd3Wdl1)v|m z*qo-`zFH+;{)hS))oZ)dMhf7WWB-Sn1i9xYO`Ugk@)q{dadC81agzBeEE(txjIibQ ztwrb`?DxlVE&hR^>t!Jxj!=oZvD0akp8vo@mLJb#9M-(?-`G0W@b?8t{=Emp2CDIAvylQ^le(I%khdLBN3SHlK~ z?6e?qGjK0DLyZQFZd*PJyL?_wPK4^&tkfe`uG!>ceVk>{&%krHit7Yyfc1y0TV^3` z%YMJp4*yfa)$k-voeVq@!Bo;T`J@mvE^euW zdJQ`Gbc^+mbVUWN0z-JpGeX?JV98QAt6Yd69{Nfk8r5qM0#wz!MF#pC^S`eU60>%F zi~KwUlNL!o$iNta2vD+GBR;Tkt9SUR8p%^Vi2{!>$Wu613m_ANp?~^lVSQI&(0t;z z0wn4&E53MGsH^U+E@X+twYI;218CM1fJOo+GZ7BRVqRwqki@en1=`eCpY#{pSwlhs zELw8K!TH&*?u>m@f_QEu$TADY7Rx9r<39a_Y~Q>|>rFfl}9wC!89v8z`C+i8fNR9)ff9&6bTC|%x= z))FJ5KmQM-?BZ@V7j>paSs41G_8h(c?fEMP5KNF*<=Pf~gSkdK^6Tqs4!%^X=Ja1| zFH@RJd|SV>A)5qZg&%+V^aN}fxW9LmJ@z&Q{7q=RM4y0cX`MRPbEHI)GS9#nIQ$V3 z<>aD&NoNvwg1Y`&t)ovqOx{l6Z-JpiWnC{790Hh|VqYy(ry7~+%vhE2!aR3=ihrXH zAA?U%oqSJ%se6H^@Pq#p&lwYJp>%$V%M)~0N+(j?vngivujn?(<(7Jsk$ZWLep$l; z`{-bO(O8^xV^tyobZ`@9+h>6JV<4xdPE*WP`-GwDdpUh9@k~8WLF>h|p=J;m2sI)U z#uaD;1_au-TyKw#JA2}euJPRu<^nBwGCinVxVZTEP+J#c`}_NK-qI)w9r9G@HQ}(g zodmg7vG4*x5U}*BXlaG3FxFclzZV4vIe;TL%HDcjW_qmu+{owghn!|B#i)dvJfp}I z4ynZ6{blIwH)+xJodtZ9&$=*ghv|6JYa+8WIQ z7#vpt93Td;>gqIlg0c}_iJakh#ca#5wkswx4A8F?&FFw$64?a7>OD<@T+tH%yIBMI zwiIpi+k8!Jt-SF?EjWsTnZ?|zY(+Os>o>6~Z>-v;WLT$L_KJz}ai~b(YS?a}v~ZBs zzz;x*vPcRaCYqVZy%cY49%5wEZm&=2I5;?KAUjt2`P1$Jv3n;FC(#JI1S#3jn0~J) zm8NvPA{YFC`aedN^Ot7EPi0a#522cN&Z=ERf;Gr)9RT+(1u4cxJW9*C5nv}Ej)E5# zv&FP-cb8HNx*KN@83f#c$PKzV(+JmTKK`E!J=i;(|7v}$@ivu4aoY=`iB~*aYV}~h zt^POB-b${t)yp7y`Xt+Z-U=O+Bpjm6vav59O3)9jYCRI%aTT-$V$qtp$>A@gR8%sO zl1L!$`ROOAr7Gd5(6SZ6FRt&5qgO#c@wytmK2aD)FvIccwS^%Du(z zI<(Hs6yRT?<{nUSA5pV=QkZlpMynuc^W0O1qVL^7?2V&&ksziO_+2-pO%|ap_m|FC z_W1zDR@_?i)W^a3OWoTY8~knS)+SG#Qp}5p3k6K61pr_GdlRRWgs{PcmZ-TRnU9AG zc09_I2CnnMi>p#UC2VDYz9`6ht442xb@FDuH1W__N|GyW7%XfpB4St7-gddRZ)l{Y zm5Sur)!75oVB6n$8*~4!5v+9qlfx7R5`>5(MVIU^#3>0(9K*6fmyQ?!NboYUy`v+0 zz@u5pS7>Sl!Jm|QE?2rHUxOjk^Q0Rsf5Ou7aqB3^U@Tgnvu%u=1*FIXIDghY*LQlh zIq!Bv9Ju&1G{pdDgunNi#_RJC1B}#^1Bw;JEWujhgEY9^tYa#=OACMOY;-GsS~s8^ zR;!gc0!)4t#3g`q$%y*9&?ShWA6c3lYVf={Y;ppN zs@4Y?KKHityUn?e@=5W@{QUhJD@W<^n`GRB-F*O0k82fP3n+_bgY z+15P)xIHiRXPdnwwq@ZLcBMp=pk&VZZ(Wwl9}>tHT97%2D|f#PtYe;5|N94^H7SSH<6hyE;H7 zFWa>>ueTMR;ak&W{B5^Hc6)9Bz`5naL1;c3k1msQHwsB!em=rufe@y`1(K9U4$jo{ zLgshB9*RmCpeucs0KNdZ_zLMK73syLP|bara=@<`<;o z-^OGs_-+5{sAZoGtR*)VA9$Afg7`+rsKx|kO!USUBzCgTUnDoI4?>o}O36HwlMcp%>r$DTF+Z zdv&tLBuw2t@Dz_Og#ivazydI#*mibybD#j3)Ib3emOr)Y++q>Q%B;}-4NXKo{5XT@ zk5Z}n7w+tgzl$bT2}=v2)3MWy(~~zAGiG4*DI;NITTtQPZ)DP>9F2anwU86=*=-7a z=tVPvhoh>isSKxMk%gA*RA8Nw;a6t>X!`ei58d+mJ_T!B>%3Z~W&0+*NJ18%qIv2HD;O zTz33~7~PxBL>nhLk@{?D6nWJ5!#)3&(;i3wl4>M$X%j!3xTx6Mn3@Wu1WZGtIl+=J+cB4;D+xJtP|U1?=1Uq@zo%l>6#Ag`8?2*; zN?3)NEmqkBNmBH+6@JoSh#UuE>x`jeh+sECVjilR;4_v)>4BhYP|aldx0lzm{XbkE zuPWf$a&qW}c$a)je=}rP-AYm;g@sGu2{H0N+PA7KQo9<(>=b9g>lxKr%D%XmZZs1# zZl%i*^#g%ubWp4{u%rc;{=R>CS5A{;{?HJW6sNWiLu<>xSq6dK?m(J%WmUA7|ysnM)hKu5;pMhf}jI2bZEeiS5{K)6nr zrS9(bLpyD7>7XwTL6om+QXor0F%|EXbh6YS1Ie1t4u4NE@_h5{+w`G zx&(|)2>ec!vfSwR zLeDt*|0+LR`G-<{cF9#&i6YpCpU(mg+V`?`dDO9%jnHEbyLBeEI_Hcx`k&0nRjF89 zC)J&eZv8x^J4l+#?cK|q=HHHXsii6%Q$Z^nVr)KcsISihwfAusXHd8Cn5%=aJ^my0 z)otOD9%f4d^nMB(R@LJ5Av4u`n$|Ql;iELEYk_PnwJbk}Ebm#`Pii+ zJt_}1fU^fmA%jLnf&F#hkhHo(MVmP_hu3encN50@T z7+=8+zYmZk2v_j-=ckY{GNJ~oPplMeO&-$}P#-xmXLFQ~?l`)yEU^2x!mt`MGs*Nn zUTx0ZjoG&yS6!~+qCKc|PmE!YHJizy8V?rvMj^y!VAimv5`&`<-AVmVC?q7F&uE#u z7R~N&V{`>MevO-EWi4^=Qg;xsM6XvnBMd>VvW^(Z%Yq=f=5l;T$SKxxx%6jqXz?c-~KMwW%3o6F_T#Fa}Q9ff`)fnPl6ax?l1NB_?c4w9vX>emlC z7CE7g{8$-^VJR=QwkUe^HHUl2BEWDBuAPgE3(|fwE_%tr8r4WxRG3-zSp_{BI2(?w zi(U+`D`zwnQA$^$SYm%j7N1`E!ph5o4Xc&8%`-3oqr+(SXbCH}LruxwA=}FsD}7T& za$)B9^xgay9)!YomM;_ki0}!78PlRmS0dUs`E+N87fQHxYyJzap#f(Jm{naBC?8MM z|GK(`!%0LF0hsH41t1FSwLE0*>g96saq>MaXO3Ua_M*$RD5ME(`hdC(dXlbNjMtsZ zM)7&Xtp*XivzXE}!~tEQBgx0R$^Gz99Kgg9d;9^50|YUm`qb2($TZcFcY5z@eY?&q zcbbW;`wDgkM@Lsa;-!DE-xX~z;=P$s(OTfDW~I>)tgn1eZb<5fA|a8Ult+AH)j4-* zB7c0mwNtws0ou+%PIXFcpI;b1&J4?RLi?n8`?0!Q zD~I2Qh;^P@@a&K~4*p8p5Sg)Fus-*>E{11-jP>u9JdcJzhqoF0-7L~7Z*Z2k4se!% z+&R2wGUT;XMPb#%wA^yw__pNpr-NkqsPV59{Ax*)u6#w)M?ZiGju@zuCXwCQux`Rs zORL`yBmuTny&(2=?NL}D>l1O2BkFM&?=*8g(I*uT91n>yEeQ^cm27soWYBo%QFWqt zbhulnCS^F|N1zlbqV_`-t=0c`R3<}8^npYcK;n^4_C9)unS4sGU*S*kmV94X_eUCL zr>pl%y~D>RZaLUu>GGv~O^|nfUzue`8mk0$QR`eJCIN#RMCY)nG>p-YuCK9bODfSk-Hb; zMC+gUTe1y|jlpHqkVp(x4<~I|#>B*Ex4N?egE?{09sJ)ve!%0a=)vdazY>u&8AdNO zEycDM`kda)cHlmHHsT$<-SWA|zw>sP|KhuP%}6bl!|h!R*4aW|P&(xc_ap$E19QW`2{62&gITQS>e}Gz1?{!r$1C&4cT5&jvXNCJ zv=c^K5#Nhv%EGF9cN5$>U^f1I%+QZ7GmG11N6o6PDKNNjI79IvWq9-7zX7pW;6ZNp zT7H$5d<+E8^%#{r2zX9{IY6?&k)b#l%%w%?wr8eFIP&7j9!6Fi3!kqjHOLp5sniPx zxaiT*kpQ*JoKSY^5%x%Bm@c6LP(Zln_*ubKEI1t|8F`4x@{cGFL)EH7z868#*rOly z`HrCmo^B`ad>r2gtwhzZ0yMgZTHrHxIn zB7IMb+df`Y-w>ArV`@zeCzemRs)0J|)q{S+9`kSS{z^~39k*N0**KTXbLX+sR;11z zWse8K(wW@I)~Zg-$H%(8Acv8dT}JXh}!!B^JR8IefP0u8Z| zlp5?KvV6iVC*%?`hiMFbjTb=hnjwH8-W382i42e#=dn&sN|7qiha&|XLOOm3e7*|t z!hDUneX9ZSK>DW_Yw8P5Um9nl7NIqYG^`N83gvvSyc(=pV3&Wy4?HSxeW(_LAMTbP z`o=&0<-P?eFx?>emviP-*aF}0u!UDkv2KxW-_8%-Sn%rNGZ>i6zktclNRPTu~F#_M%*^hbHR?wz00k48fYd6WjqkMC7?v50tQ6WSG8Tl{Na-3bdDJHMfU=tG^? z60#fPqfZWK8Hd`TSl%#rozsgrCa`*@7?o zQ^PxOkCPaBJ|iP1e+*w!h{AIV69jrlSD|5cDg!DKLd67++&*YeW0;-e6E;uZVgp>0 zmP_n?hu0}pUv}z`ySv3hdfr#)BxO5*&J?WiVw0P{JUQW@HzDA+m=>_QEZU#dkBiIw zquK_4c>1wpdz%zfjPqF-b=y6Xu#Ie9agg) z@agjEo>&JD1$5_%KL&9WM6m_^vSrgzkqtpd%!QnjiHwO{>d)EoVd3Jghmsg}eZD_u zxIHfw>&ul*Lb)#mh-k$Cx6KF)+@}3zq2HmwGKEaLYg8vVAm9K&UYwQeZ^TO%Zj_6# zOlh2iNFutN@yETiOr>5*_53m_mf zb)M+fu>1b+-@i5?|HxdVW1SnS+p{hI{;E}je@ErZIZY`>r~3FcmP6F}RHUQ`rdYsf zZZ5(GToqJt7D}^*Y=F818i>v8JlF;^po)UjI$SwLp>%=y$19KEz&t!ZzkbZ*xFGljp64bX3*W{=JF%Bm(B?xC-hPI~<|)N>~YzQ%WG zC$^V$|3>vX6Qmm0*LwWh{!z~fd?k4G*_&Im?t)wG^SZ7ds|Gz0o8NLVFi!njJpLFy zHZ~S*%8ndFW9;h6Q~29*5Z&6~uUZ(u4S%F#&1QRJVvReh%OJo2p+s;M0y~!fH6va%u8td)*}0iTwr_Kd2AM_G`6#i1_d6-wr`on zuG+FmSp@37k#wn!UBx*csK08bhMF$K-+EzQ&sY$^nPES#J}`jF_J|rHK+e7Wa}S~Lx~XL6;r3DS@bEAN@ww)+z;2^cYZg$P6@fLH5;v<1aoA*Fl#9qdB zq>gD7s19XifeOXU`%d5|Z)K;oYw|-eO@0U^a|7-2l1iMa(S7pqr!J|PYb%HvnP>AJ z?B@Eq3o7a2!aefgtZiB)YJ5ckxq-h-&k;zabH}qCIQl2ORin1k(z$fMNzBHU zg293rg+8*I$o*MvgoPOaf_z<MFV>h4d>~Ki~yVR z8Hs$gJc|OJrc-y>R>Du6MHf$>dm;+mco3f(jtPJ-c4B@$(;YW^xa~dg#}E+ovB!hl z{a_Ka{JK3y@5No6%c~zu)ZG_$v+Jj^1E)WQaNomUMOKSk^1AX1o?N(e#FEb}$MeY& zex4QA!TWr>!Jzu?UFf62)5_`rvk$bQYp4Gv3E}y_gLJc_jQp=J08H(I<#3CV#BY~8}=BWm4z^cy|)w?3?R=+CFbZ3leB zXJ%)CO>#sqaB%B{Rn5QvJ8d{+d3Rwl(vvo~?(^lR?~3B>*3-<4DMJFq4%jz|c3b3c zW2_cjE{w)yX^4M(3$s4w#ryV^QO(u)8HP1yCIjn=ROHPSX1;#~HU6`p%9lx~RL*r2 zTp6}YQ`5c44uyhVYzB4LWWPH$-5=c(4}P2eFV~{8hDMSdz;EX$94*%-+&zab(&8F^F&xkYrFk34637Sc`?zd=eN9$!@iXZ&-`R23nI983e~Xf@J}(-Hcz+kMPN62 zv+4A9i@u`1su!R@gy$c_dk{*en1IIth$U)^yd;IG1|)GsLck)?BG@Iupv7Li4?4M) zZL;pf7?A7gjeSGWs%*o+rS^h;_TM@?k!kxk_F}pFignxKz^J%9QBnEATg1NO>H`li z?|bk8&owz1O_;JU^z=-fed#~h*VJlTw!Vp9a7}PiduU7;w4OAyV`k-bo(|rxl!AHJA1iDpwq(p z^TT?!!%Ft&Apx-{mwWI00TLe-i>P=d5M#7+f_FaZ&BIHn?7|1d0ywVR^T?>S<{tZA zI;XkOjMVeu#LCzfyvP_8v4uMpz>WNa?|x3CU!yIK>=Xd9ZjD1 zT#clL^Ouw{B9>z0mi)FZGCJlaP{-jm1P?-$^gfc+W3c)L;Q|Eaj2%|0-k}}?tv2-I zeY{iVgj$-`3cR=0qIUinRWUXUtc!C%E7J3bk6kv2;U*;smh(wbARH|FnZ?+00{4dE zOVf0D0rd_bRtG(N4OsmOiI?j4m6D43q=FPBr6MZ~MjaiaH!&(v6581w$(b^pbGi=F zwD|lKv6IMd#=0uxwO}6l2F?@EHGm<9p*T9VmAYYJJxr-QSXabHGW%UaIma3qlVEU_ zAG4KGy6(%5z}GjixySqiUe@k6|NASepHmgw>(Q~Wdg{p`AfV%lI@Em>d?F>x?=M-x zp%ls)_Bi>1th7KuKgy4eeYjt%ybWgj0C5Qj(@6oq5S}E&`62^j!S}~J4=p3V1{h~* z5)jmYr8@N}X$F$5@$n2>?QP{y{bwZnp1_c@W9Pb2!BvcPgLP#Qw!fHp$`A&L;ph^3EI(ZqEo^B$b#=D^~Id*BbrvgTVY*d#G;%YCadJ2Ml+nX09-&GnoBe`@hhvEM5wd0sLT z=9iSc!^6Jb-rlxGHzpF$KXGNSR61;f$!z89s)!i7kL!S3Cb;IADd%9Gf|3gGN6Oa8 z0`b9O6zJp;TF2O?KYR+xl=b>$18bFS$^Xgxb+Y5B=G$5lM;8|tUEJj>;4vWfAE(u- zY{B@v{MW0E_nP>=Gv~obTFNbm0WUUu3XpX-4&cx-Tgjn5KYn<$Flu?}DZ2rg|HxMs z({5*a6Ktz0L2IUc+83J(>!)%bRG6DFyo);kz>9>01VqA>m4l&got_F%yFJQKotxWM zMAre%ZCLFgPFXGTUKv1f5p%@!4cU3Xo}ydX$b!Guw{p&!8(gfPwdR#$laDTUA3JT$ z3q+AW&Fq7+2ZxVpo}Qo?bA7=5K4SLvjtua~6y#!>a0`koK~$iXOleG+Y{X16LMKfk zGEM z*e%6T$Cf<{_m-F}SPfxy`MOx`6RzKmFr zaO`q|Ojx=ae!l3VDN);wQi&30%bE!bU%JF&v&y;iz<6H$25fmLR-_V|EdZkX?=XL! z^1Rd`6Gn-4&%qb4M@?*CAVRiRcSd~qrWO5LlSrK!tL3pg0tt=R4KIA88VPwYuysdR zrGdkj7Jk|8M@=h%+~m+MvM<@Y>ncJ2#(@s#EmYpUd-2x3?pb6S&?uJ!W586CKD}&I zb8%jH3FGZqaXhmn&vOj3#H=Ml&t(JQ_MpAWND}=P5o7RGqB2`INr5%#GM|VbP zaew4?5gsw#o8BSatmRkpXdvb*Y5w^IqP@Mnj4;)B>xqWFuPQaDPrAoW3By{3vEz0( z6o2Cm2dXdcS`I$5vQ27mNjw@PoAqPo=YJstQ6&vDXkYS7%CeMgm3+5=5Skjy9N(N_UxA%eLP zJ>;hf5_2Xsa=VGeH1|~CzZU!$VV;i1l4GOM;hm}2{X-kgsqyaJdoa>~bNRNUq*ewL z{1cx$4!u96;D$0vJs{afZyAdy5z-FOL0=eQLn!nzrk`a(?%U zZ=uo6fPdiRQxAcNXiR$d1QQX zEM!&7oycNPR~V9kM;r`?>2cZUL4rg;<3aRNa|N)x($&3T>+bhCmCs8T*Y}_rs^6mbsQid&U%n9oH5C zfl?n~Dkfg9{Heu2vnT0;X3Tq+^j!GqKge^4K}1D-Hu?FtVFp;wDThfn)RuVRKnMxe zF!Q~LZ*}FQJ4j4Y4t>Yb_0g!QKk5C1J~)Y=Ct<<29H@4Id^t$Ba3Zgvp&`#J7@A{m zY1sufusvSb=YRhaA3UXsMxEgO89Jq9@ro`Bv*B8oUvdnK?k(U*K==L>qQIq^h52i* z6s=ueoHFyTlOYzS-Y-15PN2P>+67+u9~o~2$;&R6=+%?Xb4H#6`TO{G&r-V&!t>(< zGlj;8L3$Y2P2&X+5q+P-hQx1jl?<#pDy{UuLZINCzEL>3gz66$vw4}K0pz(r!pV}l zF#U?Y_vB1OOg5(j2TY!U**DGlRrRh83D?)&!1|aSy92b2urEVPXG%phr4Dai0vALD zQ#6nPqbBa^v89#oG&0h4m=)1DgtT;#S&w;vvrdzd%b;{ZLM|cErD3U%S@bE1Be*-j zlblXCcv?fH!ns;333q?cGtqX_HbJWsaZ7x*sy_u*4Dm*D#d>~|A16K8& z{rxwo6eFCQLQg*#o0&-|fZSGA0&LWtwkLZg*#!Weav58V@pJ)xv36K64p5Y(k8ahc za`!!r8%QFU+Z8I#RBPr_F_Cw zcCB>*&J-d?;r(ZBr_zN#Uo)wvnosV#3Icfh zB^wNA)se*+v*fHkTLZOB{E_*Uadf_tQQB0yCl^Ns@gRZThVKdzbpVJwTYk9r(AG#5 zz}y2@C_rom;}y+1yj9T<0}F^t`{WG-klkn#7`-`PKrsQHM?#oS3~B&{2M2WI25ThD z(>@Z6LUeEdV*=j~U3CIwee1`0{lCH?EVquAj)$R z7J=nL|hmo~fG`^lSP#7hcCGnbz{e`>o2HoZLc8vE}V=W7IO;#0)-iEnLfEeTg~ zPlmtn%H66ug*^Ds_8BS5!+86+nA}%^j~av-O?7oJoq&Ij(CsVzqws2l;#ho^Aers- z_=}%Ukj-8Bz*<@{hxm^qJQ zeK!7kq2h5$$X6SHLzs*ofrg~IQ#*eSS1fX;Y9j{^;I(F7e`p5|t*mN0_gJ18(MVJXs5}siGSfAY4Zt zGi{z(H>@qJzS^an@=r=xF#E2LeS`UOtoDf*D2gD;h4SFb3bJ;Z)&>$2sWF4Xf?Y> z_cuq1bI8^e%)_Xjotum+U_lgi6Vw?>i?u{Ya2vdO!zW_l5C&G1qr2{bg!fzo(1lR? z%{UA%IV{RPn2d8*;(#}72QrmvZ=cpbwTgyM<-_RK_F(Ob?(z)hA-_xq=!xy>sJvRF zN^`jd!yoxencBt6T0!2G#X0PU05Xp2ew!CWJ~!mJs5MN=l*3$ zJpPbBz~0>ay_qZqG+RI?Dke=Um$6jCD3>#I!Sk#y&Q}7*`jQXD%?sCP&Ew8*bjV;I zoB_zwAqJ8nBbG7y1}*OY5F5wBcpbwwO3JwRPS$mld!PJ<`|i=vz1A+_6cZ{(3`~m;!i*QOkf%RuRiN!$ z)&~hn#+bxoOY9HNWQN2Z2L-izye)^>CiJ3mXKha1U58>5T4$%H@4=#|Qo!%n(qj%( zp^^=T^q=+B6i6um$L*V!c-DCjPhiOIY{Sa5O%5_X_I-9X8o_$IX8KKiy)ZI43Ed&& za7?HV|R>*FpP^;*{`Crmp)J(7vxz4|EgPNJO>1`WB^ z1>||4+J@w9sFvd+7`pg}&ZPNU>*(V|kJ}1TQH*0yq@q4%i6!}y?e$n zi=Q@sER!k$<^eLQHo)tKMAN>BiLo*!QDY+d+2sCUYkRxO*Yh#}me@XpmB^`2%e=%| z7E@i!#-h|IZ^TzS3hQ7Gq0)7~4FmSpg~7KoA4yO~Y@?gtH%8ov1+g(qH`K%KW!^jA zUNiny+QyBGdX_ClZe2f|zWGOLhgOl3P?Z>o_Q!u*T`xg_%bjjA-lJIDj=H!Yx?ZC9 zjTS{(Jb+Uo_#R(x1?$*3*=8JzRQL%%?95IiB++9GS$MeLP+30+Y`N zgIU&sHFr+bCM(n@4MB+`BnV<`--$PehlO}za4p)fuEYMG=yz)uwe({6V%Jz4H?lidjSFR?>y@UXg?Se>eN*KT_JlkY5p*_hGDP zzwtG49})sbPZ!{kak4)}PieR95P%D9wB+|OztAc*WYmW!UQIh_Ts4WUZ+M<8Bi$lA zGFMi={x)PB_Sm#Pn|zBgO+iT+(rNbff@bhBJy*>mXjRH|OKUqm|7fnxtxpeMbYZS+ z5Wm{_LZ@Wn47YO3j3PuE1h<=(qtAJFsD}+?vV6Zq8*qOyv~Vuql5-m#1uX!4O4gii zR)E;Ctk8E8jLRK&jR53#D+lEsPS)QB)_bvE!wm-Tn7ITrQ z_8?2OUDYQL=&+sZO^ z@OP!ZA7thVXAHe~@q$%!H^cqy^97fA=~xhP&M`fqbTm7}wyVNZm86RpTvZ_-&-0nq zZoAWWXB6S?zdG|V?JXk02>~nBxSow849Nw@HZDCwcm$bRRe`j4XKaeUl#{?h;-1Yu;!blMLD_F6+6pF+B z@PF9}$H{dF@B%Tq>-^lOGmTk0O#JbH$h)$9x(pRX3kWbpUrWz9iNM-;{<$ByeM@;= z=M!zFOUZrCo$EOlk~b~~cLCiuY%6)E^BE%juU=G>Q3i1c7VoE8?brSv!~!%&@CLNn zmR%%r5gcZdq?5upN|RbsAML=>VRQXMrF3QuMZ^5?~}qURrK!! zb<4`jhfC2_%P znx4KAEp1M63i>vTm*ldaB|6ps#!S>lS9bT%2V5KQ7LyH;Qd;O|wfPxIL5Rw>O}gax z21>-}-dmIjTuqpsc){a2UN|U>!KQA*U75#(D6y!b1!^mX1l1FQ1^~9uwzN`oJu|>W60*)!;#in16{0I%dxFDdpFnJ4G>Ln_>Ny1Jmyn)%MfVFt`e* z$l|eejE$x2uU_febBOxPgvtRN5S)~3&lox2d2&DDM~VKi>Gw;9J7#24P5dzg(mTy~nH0RzwA=FK zNQmEJ2~N>g3JDeC!l+4mz!9cxaQ5d}$G?9E_={NR$#z1kx+>B5n7$Z#k$;MW94*d6 z@YL>lpoz9&T8%zi{O=o=lrsoKRnN*CPR6o7-T8O=rG065YX95irIDdc&x-5kKR;BJ zD2P4@&3uRPT4(#1ul!M)xI4dCjg>Ck=)akA29n54u?jT)Th9q-V=6j=niBAe{Riwx z7pY&PM&{kHw;Boha{is#oTuJRm{k5OecoJ)k`x1rB4J_N@{ovty?zZn+}zr2A(mkV zY8+(kkE;T7*$D06f^S*)Ox-T|M^(~yQA-j=3I^rn=YQ)*RyxllBkvgCMv1fk8X<>JF*X#``XK~2y&(A>#D}%5iKqTVQgQGI8tE$ZtfIfEfLC(qm?RjxPu zlu<)|J(6T+ECdS{w17#MTyJnXxnbz@@t6&niqxIfCH0^w8cP!yFwzsDRZfdLm4cyy z3$`+_5unQ+TVpi0(*w6J*yv)R;A!(6*30|P7GY|9x6th1&YvV53m#-wVG#e{8LK;T79N=oU5*|2j+M zK2@+uNlKfu*1Pm^7T}RF$ZcwUaNtr^33q?n;x&h^_Y)#wI=auTYNi+6npCnE;a$E1 z0h=vhp<&5?YG7(X=B?0M8&ver#S&~D6+b{53-uL`7vbd%URzsx^Iz4Fnf#osCk6Gr zE-132=={Ld45pnL?hjETBP!xM$y9m5X=*^#+(_N(fLY(7_np8936mzYoR?byjXNzV zOE*({eTJ6}E(;Q8OK3wSMPu=3gt?_oszC|amw&q zW7!|7E!ZAdp2)@o&^)wyySa99ah9JhLvVCD4N

!Tg4 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 Qp8*IwUHx3vIVCg!0BD|yF8}}l literal 0 HcmV?d00001 diff --git a/docs/graphics/numba_comparison_virtual_quickstart.png b/docs/graphics/numba_comparison_virtual_quickstart.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5cc3ae3dfa9cc5328dc7c04c023dd67b06862f GIT binary patch literal 196666 zcmcG$2RzmP-#2cgrL;ptX=f$LrlC?qA!Js^NcPO8i5x^^(@>I?y+`5LDY9o-Wv}e< ze}3xwyZ+a8U)O!z_x&H|@#s5r&gXnS@9}y)*PF)`xeFWD?^;hqMYZvww3GrB740|t z$y&P_Kbg%p#y_cqtqx z_PKYtbi7f3bdqFqwqdW1yl$uZmvslcKb1FpQau`AnHsjw@V5D-7#$A|8P6v|=L8y_ z9B!t6x%cL=s(h^i$&ui>YVn0TelwHfnd})0^IDyQ3vaCs_Iq3JZ1Iit z=cDW*o%FY6eWXi{3D~*tD%ty7j|CrP!{vpxP27X3Qc^uDM{v!o`^=>0Zm`W9oJ1{eoPBM{@a3g za)hIV6rZ6{@ppL1jotR2+N^2Cn=dUGqgK{B4fl_S?`dlic=>-z4%s+ zQH=O0QY<8IX3{~L3w7@qF2?Hf*9=yfM(FdD#@!*?H87>rGflej9sJ5P>Cj>}{rFMt zPT}2kOr*z5qphWdW14)<_x29YZ(1+D8fMb;F48S8K1MG-NoJBB6DhaOt${Y2Uo&H>^s(%4ROz50 zdCs(@;8|@4Cz;Q)b~+~D>;}PT$4|SC8y4AC3o{LKj7=zK_aU^&vvE+{W35vjSin=qy+H@7e5=HJ4R`{LB38Rd&uT^ zvv~0hTZnAJD)Ra}F{4y9)R+KrS_4I|*?G;CjjE>c;vvSPVP16EG_m)N6>e&xnx!SV zM!L!$ukXB;`i1)EDzN~YFdx_3FW9~?W>zxQb*L>U^0l}y*zPI~q#o`5*&9ofx@v@4 zStn+c!O^2zBQQE|S@anFyG`WJ8qG|WlHR-mTV1c6vQ=B$M16M`sYTV$rBbQDrX}k! zmFPV|bH|TPyT^o%t#MyaEc@*GOnTd$?sO5n(%p2q415OG>OXAd9nCblmrl`kNxTT;>pN*U?9IwB zxz@X8<&cm>L3?cr!wHIGh>f?QnQZv&H5*nPeLMW@+Di#WgZ>ouriY~W=Qe-Yt!hM$ zF^%6HS@~veZK?jGi-CD$`5QJ$(BM5|p?og&N8L^{bOSW6U01qnM~_ivY1QwYFPtgl zH0ZbI{qUKIzgBHQnZeC~noOHtGX6-TI(2fng30nh+H`;0k8NABj;b2|{&IGc_*CX6 zb|Jqr^c$*gwniJY&g&h?D16MNyREK*&4cFBZ_m@r#Q@*-aUU4uYPZPS2tI$ zdR>d1NVF|gjclTzkn^>Edy`+|&g*RZKR8`&nN|}v$`LkhE#~TFsjVnjv&bKy9cq)n zXtv*^{^G0g83N*BM1*gBEk*U=>S$SaUzBA8<2Db9=xm)iwnv*x@ zatD&gHQkoMp_#1S)_aG;q!;Fx-Mi`EN5`j~RLnbD7%Swr6t?q7+nsn#|2IR%dzf!u zD-${=wQO;sM_sBX@w*|bxbCnQ-$woeTV}dv3!Ht^PR48=Hs<|BHF=Bti<(@>mz&Db z>>_!sn9nHva7Pi})ZIwFKH3}mynDDZ=py{?nOYp~d^+t&dYhbXP!nGF_<;Vkb!H`B zpXF`4{Y-iJoPDw)%@6abJI_)+-Tqi(5aLRmh~c&35aB9~#@nDw4HLw8ggf>6_AdA7P4QB1shUDcm$=Q@(*`2A_vbXa5h zee%IEc?X^{Yw3cHET@-#S|gD~>3(8vG%h@3zQ!NYWLnp1p}utOV!lhSc6!hCXF0v4 zUrf=DZ#NfNGD$N`GNFT8bxIq(-$e@y1wpZ*DkNKW+3yK(u$ie z`qu7`%-(0lCFOUYbhzHkaCkY@xQ14|_PyLimXL!S3TiuPp56OM35veweQI~tAgdo` zal622nNj1a>4gKvSEJ%|d(F5qf3GonmNu>QeAqZ;L{Zh?;uo$PXm(q@JF1q2cJ4Pc zA{&I+G4Wpc%G4clcEaj%i)Eh_*HWi*LyHXE+C#^Og*$J4ee|@^Az*Of^yl)r-mSsY z8czqGI(sFS>pBE*{k|k~Q-V>xYc{!eIj-o97ay(J{%^uZhXVICw3yzDFyIUnT(kU` zBt=mmRb4k64qUWLc!~Y$-FKzmXp6VDG+oj+wbpNH4zy!5W7=g*`L^54XuYo~tmu-* zxyPl?N!6+v%6A8)sBgJYV>0hJkd-y8xawEAl!>?^V{L?C&xiPP47xka_uF*#Je5`H zT5(%=?w&f=!Wo}7^Mk2RZB5yHiGeWdq)ke;@UZY?+r(&Xgy_y=52xlcLhrZVI!brk z#7TMkBPU6oz)u=(Q-e;HJgs$=mN{!2E*`1se`V4#c|rKwsK$Lh-46{1RSgT08YJk& zLX_J$hv)QJUrU%Aqx77RU3i|_Uz`7!gTqDm(mTr=@$IXGs`-vnzVT5UBW!ppt0_UN z4XpWRq-`fx#Ob_Ty&772QXk!Ne${m`+gem@}dMFcGu`inKaNqXP$hdm#8|Xga_MkAo@VooS@SF<_3D}nG!d(~cw(BQq!M=S zn$R?}ytk#Hqa+*{{E2J#mp--L@8xG0*E`4A0M`nQ11KHtC#Ys==PmroQ z>z0kp+VbD2tF@C1tP4Kzop91^2^$sa%1ldt;ZYM^xc7!D-&+xHhnD%V}!1bh-lhJa_k~ zt7kN+4Dt>Rn=Rgw<^4SSxJ7M2WGtJnk6kD~hV53oX5cjaAPcLDtW)I*uMG!>!qHkk zAG*SCsjuwp>Um$CF02&tdq6GZ_hQ@P%B8v@v$nT}RNevyor}!XBN)qt@3HuFuavu) zwRKK&c=!C06zWr9ys*KW?Tb1Vt+Sx_s-d;s>RNf>MG_vT7;o(I&T7EM8UH! zaCmO+_(P4Znn!6i8GL;^StZ@t&2vp9sh!NK*m+|f&TnM(urz$()M73d6;PAfpcj{J z5-%Q^e(+jT@Lp%Rz)fBN*o1#45SP`{z&pn+wiOL~KHxjs8% z*Zs7Utz(>|Duo*!X*R|&tGGBgO7zIWt8c`|UrYBbi}a9S+UCQS;c#|Z{$m(gQ(Y0dw z==*9TITjA*bxgP8>z|tbA(6fX?wleoDM@F3QM1x_^ypYNNagH_OQW@D_BReNF*Np$ zjZ!o0HjkV7hnKpXRvCIanp%I!p-c6{IncYNxU{qr^2*7z{b!`4EG$b7ek zc#_%p4^LXwCmT50ecJl>>ta9d5bkMZs@o<+if<1-sUmVnl4`HPjO;Ljlryr~qzP9b}r#Ra_hl}ouOZ#}>Yr_t^RxVPN6*}eR)nevVFSQKfcL$Y9 zl)CP0XmQVNG5PxHQF{{U!n*UEe+SQHC+Jf{%TD5>_-eI<)U|yw6npiwy`{$mv%qeDb zxH$$M+&K4_kIxNe54bu0!@d0l{ukW*qxPKuVg`;x%+A_=ju4k|bQCKuFW24e?d~pp zFEcCaYK+1GM<*xVLGu8D(66OpIQ~ur%0hyY?zpJz|CR<3wN_$bh`%C` zx8_$}bfw64U&p+uTA6R}uXz?17bi2E>)}23=Q?UtgT2 zWpkRk%CK9+e{?w}CT4M=bEWU48`~?3-U_)^y{e#(Ap(Y%IHxKH7DfyDzg}Q}V^H^2 z>D`rk@2|zyrs&Jt3^nbgPZlMNF}%8XXmDe>wryhl4V*ytl-J%9yx`P3<@c=Zgu@gF6shBEsp zTlSsbykkd+x@q1uj$se?vsuXuP&uS}EarbjM`m_=iwfNxVO^U4z3;?{?Sg`W)1#d= zPj+wy@$1Xw=h;TOEDPt{9o3tg=#}e@mJ9!Z&-C>MYV%YpW*SAfEZdzl?U3!vce0!M zdLhf~JB?}H@14Y#V*+ub;;%(8$4C;&n(&EUc_ddNl*9G%k8kTKYzN zw)F-!u^@4W>9J4ryxT=7>vtSIcKgd7E&E>w&ik-=d3w&*OI>=HrtBl@(^nm;z6C14 z*X5;!N|A>OcgKI6Q%}*mte$l1+(Mnaq;4x43yUGU!%&iAE2iDYxFPO(`h>jX(l;8L zy0@1PY%vLL&9;ucEG=zz6pQ&oz|m{Tf*rQ673c2L(R1BUiC61YTAm+FR4+`>$U0s_ z+M&0CZQ+0wZ5W@1ncMkc+dz8l-n|>0{NlwP zll(b7O!26a^RL>C^Kn5ZRX z*Sep{OMEOUdVrB<%<>8sy}Om2U4F8!I=rIPd$$a>E{2Estd*vQM)CpoKp6#vttb>+ zS2Wq#*~P@f*0)WM7Oco!x$;!;djIEeKFV0xKA&TjeO2VWt(cw@#x1Ge(_2j{Q>uTA zXfp|GhCY0_%FoXa1;p$q?@-zi@1`czTO^XllP4TpT;xaejJ3(SGK)(~lWnHo-d*KV zPbXh}eg13I#ce#A*~JN(IZf%tEKUp4)$jDlFVA`IH0sFB@~@HV8F=}pm?Z34m>G?B zUYakt^b`x2st|i~P|10w{Vu=rqODr$?T^@ko;&m}Hf39LX0%zz+#BH0$b9EnI6xsi-J^+z9XWT9{-=K7ho8z$yG-^g{OQg@J6$Ohj|9g=Bn_M@=i(Y9IGrxi9m@9rfF_coH z*Q)jA(hd=;pQkcR+TUGIIoO(E+F!7;#QR{)roIQ8B+3NuagHq01=OY4jh8Uab^Az$ zZB;uwn|5T~%a<<~hfQ6&hniCy@mFC&=J$X8{OLF|IWwc*nr{5i%}w$3c`vcS)vH%y zKF{dr=%m+taNiyQNqEbatUI&w^ILc9xW-}J%$334l3~h@-gCmRAx^4=H^J4FD%se{u4Cb!@z6^7^d7s3lC=qstsi0c7~5%lvPM!d=~juxOsKHs!3V21 za_ry#a4@kT0xPJtz-7g#ZtI?fer2gA7yh)0o){tVPcA1hKRs^j&U^OmeOXqNU$-rk zo{>i=V6-KTzDxeul|u&)S}ZTl(bWH*oGhxWxWOuN*qX+{lZ|i~5sq-4H;B$=t(MEN&bAm&Re&;Bav=RiYzW$hao>L5PSYTDHs%-)OY zlszSX|5g_*A8j+-GCMnK*6k&@^A2B(Y5v^fqu1VV|B0b{qS1O$RyGPZ=!os(IQDT3 zZgg*{eZk7I=#e8wnk$|?dlnvHso1u(@}yQ}=JR~-WjTDdm{?TFx|M4;ZHl(+|12AH z>|RvV{%HFP-mEVHxI!ek70&!I5X!Ehk@*kQ$MV|@sZNiyhZ;Kop^!?wt5dwN7`lJ` zDxc|eLH%2tHGi)h|HRXCGExOCyUngYJk_)_0*^U9Udqm|SH*<2QshcatD0vofb!=` zVw*k`>FDRH(-^DbV_LBE9Bt__!|qE3&PynhhreXKt=Yy?wr`NTu=Y(<)R>RU;)CCV zjgy~6N58zj;G^NAQsC?~J=|J~S^BAzuT$Mo7x-8zRm>XQBW2D2VOlOJNJ*`i zKbsF=+w=ZAs&RCr^CiriK}W7I=H>;~5ulm*Sl4OgIMt8A{QAk)&E&uHPyf+nm~mqSnXb~udozV?y01t5Z5 zN4^%T%y*-Z3QhAYx)8tpNB+)<+f_jXHt{WT+r)VLWt?Bo*`*qnv!EdEY^D)_uwkTK zA9E`@ih91I-Qf4{H(5^HzEMhMCiwvPj4q=_CAqfe+%-MljQcdMi4c!ZOiZL3+9oe{ zGd|6x>A8ww-Q{1k9zXRxH)#}yB0OI6UonGr!F5jwKw{8(F3&^YsP@H?v zOzP5Q+zd0xM$J&&zj*wF8{tVtJ=M_AupPDtW#&zHOA^iFb z;0t6XzE^Q)x6H|0g(nHcVikbwejXlg;HUoUHTCrYDB3C3gUVR07vEe8U=p>7tNU>a zo46}BG2cRMJO`sJHc__kglR_@%G@(FNv~Z3mxdjC5{-wNl2}D;Hr%~C^Ovxk%}@D4 z@Z~Hu1A<&>{32xZKd+wk|5NAnFLUL%^5^#c+~Fm7n}1$UPW^9DDZKZ8v~zI}17VUb z5#!yZb#d4E9UUFVCMI5?a*NIXe9bCi^-jr~8ST2*W81HeJkgIIKQfVmgv@Wnu*zUH z_*ndPl*@XjfZ5GUci%M|X%hxIxd48I)i~5y5V?a>`I!D^)~(yN$t-5#5oAM7co2lK z?y_K#NqhEINsvShtv^rF^WGfbcO-j|2MA+!d0{jQt2jJeKJ=s)`uy83!vv4RDk4g; z#-LJ+!lP%0DJzOUnAr_3Xblh%2=hdrMw!Hx= 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

Jy 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 literal 0 HcmV?d00001 diff --git a/docs/index.rst b/docs/index.rst index d7646934154..556de41f4ea 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,14 @@ assistance. physics/plasma/index physics/montecarlo/index +.. toctree:: + :maxdepth: 2 + :caption: Testing TARDIS + :hidden: + + testing/automated_tests + testing/numba + .. toctree:: :maxdepth: 2 diff --git a/docs/running/custom_source.ipynb b/docs/running/custom_source.ipynb index 6a2e756d4ad..1516cd0734b 100644 --- a/docs/running/custom_source.ipynb +++ b/docs/running/custom_source.ipynb @@ -52,9 +52,10 @@ " \n", " def __init__(self, seed, truncation_wavelength):\n", " super().__init__(seed)\n", + " self.rng = np.random.default_rng(seed=seed)\n", " self.truncation_wavelength = truncation_wavelength\n", " \n", - " def create_packets(self, T, no_of_packets,\n", + " def create_packets(self, T, no_of_packets, rng,\n", " drawing_sample_size=None):\n", " \"\"\"\n", " Packet source that generates a truncated Blackbody source.\n", @@ -70,11 +71,10 @@ " Only wavelengths higher than the truncation wavelength\n", " will be sampled.\n", " \"\"\"\n", - " \n", - " \n", + "\n", " # Use mus and energies from normal blackbody source.\n", - " mus = self.create_zero_limb_darkening_packet_mus(no_of_packets)\n", - " energies = self.create_uniform_packet_energies(no_of_packets)\n", + " mus = self.create_zero_limb_darkening_packet_mus(no_of_packets, self.rng)\n", + " energies = self.create_uniform_packet_energies(no_of_packets, self.rng)\n", "\n", " # If not specified, draw 2 times as many packets and reject any beyond no_of_packets.\n", " if drawing_sample_size is None:\n", @@ -86,7 +86,7 @@ " \n", " # Draw nus from blackbody distribution and reject based on truncation_frequency.\n", " # If more nus.shape[0] > no_of_packets use only the first no_of_packets.\n", - " nus = self.create_blackbody_packet_nus(T, drawing_sample_size)\n", + " nus = self.create_blackbody_packet_nus(T, drawing_sample_size, self.rng)\n", " nus = nus[nus no_of_packets.\n", " while nus.shape[0] < no_of_packets:\n", " additional_nus = self.create_blackbody_packet_nus(\n", - " T, drawing_sample_size\n", + " T, drawing_sample_size, self.rng\n", " )\n", " mask = additional_nus < truncation_frequency\n", " additional_nus = additional_nus[mask][:no_of_packets]\n", @@ -170,4 +170,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/tardis/io/schemas/plasma.yml b/tardis/io/schemas/plasma.yml index 56a45371cd6..59405000b74 100644 --- a/tardis/io/schemas/plasma.yml +++ b/tardis/io/schemas/plasma.yml @@ -17,6 +17,11 @@ properties: default: false description: disable electron scattering process in montecarlo part - non-physical only for tests + disable_line_scattering: + type: boolean + default: false + description: disable line scattering process in montecarlo part - non-physical + only for tests ionization: type: string enum: diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 507f61aeebb..07c7f100414 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -28,6 +28,12 @@ logger = logging.getLogger(__name__) TARDIS_PATH = TARDIS_PATH[0] +MAX_SEED_VAL = 2**32 - 1 + +# MAX_SEED_VAL must be multiple orders of magnitude larger than no_of_packets; +# otherwise, each packet would not have its own seed. Here, we set the max +# seed val to the maximum allowed by numpy. + class MontecarloRunner(HDFWriterMixin): """ This class is designed as an interface between the Python part and the @@ -61,7 +67,7 @@ class MontecarloRunner(HDFWriterMixin): (const.h / const.k_B)).cgs.value def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, - sigma_thomson, enable_reflective_inner_boundary, + sigma_thomson, disable_electron_scattering, enable_reflective_inner_boundary, enable_full_relativity, inner_boundary_albedo, line_interaction_type, integrator_settings, v_packet_settings, spectrum_method, @@ -74,6 +80,7 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, else: self.packet_source = packet_source # inject different packets + self.disable_electron_scattering = disable_electron_scattering self.spectrum_frequency = spectrum_frequency self.virtual_spectrum_range = virtual_spectrum_range self.sigma_thomson = sigma_thomson @@ -85,6 +92,7 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, self.integrator_settings = integrator_settings self.v_packet_settings = v_packet_settings self.spectrum_method = spectrum_method + self.seed = seed self._integrator = None self._spectrum_integrated = None @@ -127,11 +135,23 @@ def _initialize_geometry_arrays(self, model): self.r_outer_cgs = model.r_outer.to('cm').value self.v_inner_cgs = model.v_inner.to('cm/s').value - def _initialize_packets(self, T, no_of_packets): + def _initialize_packets(self, T, no_of_packets, iteration): + # the iteration is added each time to preserve randomness + # across different simulations with the same temperature, + # for example. We seed the random module instead of the numpy module + # because we call random.sample, which references a different internal + # state than in the numpy.random module. + seed = self.seed + iteration + rng = np.random.default_rng(seed=seed) + seeds = rng.choice(MAX_SEED_VAL, + no_of_packets, + replace=True + ) nus, mus, energies = self.packet_source.create_packets( T, - no_of_packets - ) + no_of_packets, + rng) + mc_config_module.packet_seeds = seeds self.input_nu = nus self.input_mu = mus self.input_energy = energies @@ -206,7 +226,7 @@ def integrator(self): def run(self, model, plasma, no_of_packets, no_of_virtual_packets=0, nthreads=1, - last_run=False): + last_run=False, iteration=0): """ Run the montecarlo calculation @@ -236,7 +256,7 @@ def run(self, model, plasma, no_of_packets, self._initialize_geometry_arrays(model) self._initialize_packets(model.t_inner.value, - no_of_packets) + no_of_packets, iteration) montecarlo_configuration = configuration_initialize(self, no_of_virtual_packets) @@ -439,15 +459,18 @@ def from_config(cls, config, packet_source=None): """ if config.plasma.disable_electron_scattering: logger.warn('Disabling electron scattering - this is not physical') - sigma_thomson = 1e-200 * (u.cm ** 2) + sigma_thomson = 1e-200 + # mc_config_module.disable_electron_scattering = True else: logger.debug("Electron scattering switched on") - sigma_thomson = const.sigma_T.cgs + sigma_thomson = const.sigma_T.to('cm^2').value + # mc_config_module.disable_electron_scattering = False spectrum_frequency = quantity_linspace( config.spectrum.stop.to('Hz', u.spectral()), config.spectrum.start.to('Hz', u.spectral()), num=config.spectrum.num + 1) + mc_config_module.disable_line_scattering = config.plasma.disable_line_scattering return cls(seed=config.montecarlo.seed, spectrum_frequency=spectrum_frequency, @@ -460,6 +483,7 @@ def from_config(cls, config, packet_source=None): integrator_settings=config.spectrum.integrated, v_packet_settings=config.spectrum.virtual, spectrum_method=config.spectrum.method, + disable_electron_scattering=config.plasma.disable_electron_scattering, packet_source=packet_source, debug_packets=config.montecarlo.debug_packets, logger_buffer=config.montecarlo.logger_buffer, diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py index f81b6e9b186..62cceb79deb 100644 --- a/tardis/montecarlo/montecarlo_configuration.py +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -1,5 +1,11 @@ +from tardis import constants as const + full_relativity = True single_packet_seed = -1 temporary_v_packet_bins = None number_of_vpackets = 0 -line_interaction_type = None \ No newline at end of file +montecarlo_seed = 0 +line_interaction_type = None +packet_seeds = [] +disable_electron_scattering = False +disable_line_scattering = False diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index fdcb65a650d..a578fc130f8 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -23,22 +23,26 @@ def montecarlo_radial1d(model, plasma, runner): numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, model.time_explosion.to('s').value) - numba_plasma = numba_plasma_initialize(plasma) + numba_plasma = numba_plasma_initialize(plasma, runner.line_interaction_type) estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator, runner.j_blue_estimator, runner.Edotlu_estimator) + packet_seeds = montecarlo_configuration.packet_seeds number_of_vpackets = montecarlo_configuration.number_of_vpackets - v_packets_energy_hist = montecarlo_main_loop( + v_packets_energy_hist, last_interaction_type = montecarlo_main_loop( packet_collection, numba_model, numba_plasma, estimators, - runner.spectrum_frequency.value, number_of_vpackets) + runner.spectrum_frequency.value, number_of_vpackets, packet_seeds, + runner.sigma_thomson) runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist + runner.last_interaction_type = last_interaction_type @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, estimators, spectrum_frequency, - number_of_vpackets): + number_of_vpackets, packet_seeds, + sigma_thomson): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -49,6 +53,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, [description] """ output_nus = np.empty_like(packet_collection.packets_output_nu) + last_interaction_types = np.ones_like(packet_collection.packets_output_nu) * -1 output_energies = np.empty_like(packet_collection.packets_output_nu) v_packets_energy_hist = np.zeros_like(spectrum_frequency) @@ -56,20 +61,22 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, for i in prange(len(output_nus)): if montecarlo_configuration.single_packet_seed != -1: - i = montecarlo_configuration.single_packet_seed + seed = packet_seeds[montecarlo_configuration.single_packet_seed] + np.random.seed(seed) + else: + seed = packet_seeds[i] + np.random.seed(seed) r_packet = RPacket(numba_model.r_inner[0], packet_collection.packets_input_mu[i], packet_collection.packets_input_nu[i], packet_collection.packets_input_energy[i], + seed, i) - - # We want to set the seed correctly per user; otherwise, random. - np.random.seed(i) vpacket_collection = VPacketCollection( spectrum_frequency, number_of_vpackets, montecarlo_configuration.temporary_v_packet_bins) loop = single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection) + vpacket_collection, sigma_thomson) # if loop and 'stop' in loop: # raise MonteCarloException @@ -77,8 +84,10 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, if r_packet.status == PacketStatus.REABSORBED: output_energies[i] = -r_packet.energy + last_interaction_types[i] = 0 elif r_packet.status == PacketStatus.EMITTED: output_energies[i] = r_packet.energy + last_interaction_types[i] = 1 vpackets_nu = vpacket_collection.nus[:vpacket_collection.idx] vpackets_energy = vpacket_collection.energies[:vpacket_collection.idx] @@ -94,7 +103,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, continue v_packets_energy_hist[idx] += vpackets_energy[j] + # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] - return v_packets_energy_hist \ No newline at end of file + return v_packets_energy_hist, last_interaction_types diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 482ec7564a6..1336d8ef734 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -11,9 +11,9 @@ @njit(**njit_dict) -def general_scatter(r_packet, time_explosion): +def thomson_scatter(r_packet, time_explosion): """ - Thomson as well as line scattering + Thomson scattering — no longer line scattering 2) get the doppler factor at that position with the old angle 3) convert the current energy and nu into the comoving frame with the old mu @@ -29,13 +29,14 @@ def general_scatter(r_packet, time_explosion): r_packet.r, r_packet.mu, time_explosion) - comov_energy = r_packet.energy * old_doppler_factor comov_nu = r_packet.nu * old_doppler_factor + comov_energy = r_packet.energy * old_doppler_factor r_packet.mu = get_random_mu() inverse_new_doppler_factor = get_inverse_doppler_factor( r_packet.r, r_packet.mu, time_explosion) - r_packet.energy = comov_energy * inverse_new_doppler_factor + r_packet.nu = comov_nu * inverse_new_doppler_factor + r_packet.energy = comov_energy * inverse_new_doppler_factor if montecarlo_configuration.full_relativity: r_packet.mu = angle_aberration_CMF_to_LF( r_packet, @@ -75,9 +76,21 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): #increment_j_blue_estimator(packet, storage, distance, line2d_idx); #increment_Edotlu_estimator(packet, storage, distance, line2d_idx); - general_scatter(r_packet, time_explosion) + # do_electron_scatter = False + # general_scatter(r_packet, time_explosion, do_electron_scatter) # update last_interaction + old_doppler_factor = get_doppler_factor(r_packet.r, + r_packet.mu, + time_explosion) + r_packet.mu = get_random_mu() + + inverse_new_doppler_factor = get_inverse_doppler_factor( + r_packet.r, r_packet.mu, time_explosion) + + comov_energy = r_packet.energy * old_doppler_factor + r_packet.energy = comov_energy * inverse_new_doppler_factor + if line_interaction_type == LineInteractionType.SCATTER: line_emission(r_packet, r_packet.next_line_id, time_explosion, numba_plasma) @@ -91,10 +104,10 @@ def line_emission(r_packet, emission_line_id, time_explosion, numba_plasma): if emission_line_id != r_packet.next_line_id: pass - doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, + inverse_doppler_factor = get_inverse_doppler_factor(r_packet.r, r_packet.mu, time_explosion) r_packet.nu = numba_plasma.line_list_nu[ - emission_line_id] / doppler_factor + emission_line_id] * inverse_doppler_factor r_packet.next_line_id = emission_line_id + 1 if montecarlo_configuration.full_relativity: r_packet.mu = angle_aberration_CMF_to_LF( diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index d52fb377deb..11f87377c88 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -71,26 +71,37 @@ def __init__(self, electron_density, line_list_nu, tau_sobolev, self.transition_line_id = transition_line_id -def numba_plasma_initialize(plasma): +def numba_plasma_initialize(plasma, line_interaction_type): electron_densities = plasma.electron_densities.values line_list_nu = plasma.atomic_data.lines.nu.values tau_sobolev = np.ascontiguousarray(plasma.tau_sobolevs.values.copy(), dtype=np.float64) + if montecarlo_configuration.disable_line_scattering: + tau_sobolev *= 0 + + if line_interaction_type == 'scatter': + # to adhere to data types, we must have an array of minimum size 1 + array_size = 1 + transition_probabilities = np.empty((array_size, array_size), dtype=np.float64) # to adhere to data types + line2macro_level_upper = np.empty(array_size, dtype=np.int64) + macro_block_references = np.empty(array_size, dtype=np.int64) + transition_type = np.empty(array_size, dtype=np.int64) + destination_level_id = np.empty(array_size, dtype=np.int64) + transition_line_id = np.empty(array_size, dtype=np.int64) + else: + transition_probabilities = np.ascontiguousarray( + plasma.transition_probabilities.values.copy(), dtype=np.float64) + line2macro_level_upper = plasma.atomic_data.lines_upper2macro_reference_idx + macro_block_references = plasma.atomic_data.macro_atom_references[ + 'block_references'].values + transition_type = plasma.atomic_data.macro_atom_data[ + 'transition_type'].values - transition_probabilities = np.ascontiguousarray( - plasma.transition_probabilities.values.copy(), dtype=np.float64) - line2macro_level_upper = plasma.atomic_data.lines_upper2macro_reference_idx - macro_block_references = plasma.atomic_data.macro_atom_references[ - 'block_references'].values - - transition_type = plasma.atomic_data.macro_atom_data[ - 'transition_type'].values - - # Destination level is not needed and/or generated for downbranch - destination_level_id = plasma.atomic_data.macro_atom_data[ - 'destination_level_idx'].values - transition_line_id = plasma.atomic_data.macro_atom_data[ - 'lines_idx'].values + # Destination level is not needed and/or generated for downbranch + destination_level_id = plasma.atomic_data.macro_atom_data[ + 'destination_level_idx'].values + transition_line_id = plasma.atomic_data.macro_atom_data[ + 'lines_idx'].values return NumbaPlasma(electron_densities, line_list_nu, tau_sobolev, transition_probabilities, line2macro_level_upper, @@ -168,6 +179,7 @@ def configuration_initialize(runner, number_of_vpackets, montecarlo_configuration.number_of_vpackets = number_of_vpackets montecarlo_configuration.temporary_v_packet_bins = temporary_v_packet_bins montecarlo_configuration.full_relativity = runner.enable_full_relativity + montecarlo_configuration.montecarlo_seed = runner.seed montecarlo_configuration.single_packet_seed = runner.single_packet_seed diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 70fcb0bed50..f51b8d6a878 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -9,14 +9,14 @@ from tardis.montecarlo.montecarlo_numba.montecarlo_logger import log_decorator from tardis import constants as const +SIGMA_THOMSON = const.sigma_T.to('cm^2').value + class MonteCarloException(ValueError): pass CLOSE_LINE_THRESHOLD = 1e-7 C_SPEED_OF_LIGHT = const.c.to('cm/s').value MISS_DISTANCE = 1e99 -SIGMA_THOMSON = const.sigma_T.to('cm^2').value -INVERSE_SIGMA_THOMSON = 1 / SIGMA_THOMSON class PacketStatus(IntEnum): @@ -39,6 +39,7 @@ class InteractionType(IntEnum): ('next_line_id', int64), ('current_shell_id', int64), ('status', int64), + ('seed', int64), ('index', int64) ] @@ -119,16 +120,20 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, return distance @njit(**njit_dict) -def calculate_distance_electron(electron_density, tau_event): - return tau_event / (electron_density * SIGMA_THOMSON) +def calculate_distance_electron(electron_density, tau_event, sigma_thomson): + # add full_relativity here + return tau_event / (electron_density * sigma_thomson) @njit(**njit_dict) -def calculate_tau_electron(electron_density, distance): - return electron_density * SIGMA_THOMSON * distance +def calculate_tau_electron(electron_density, distance, sigma_thomson): + return electron_density * sigma_thomson * distance + @njit(**njit_dict) def get_doppler_factor(r, mu, time_explosion): - beta = r / (time_explosion * C_SPEED_OF_LIGHT) + inv_c = 1/C_SPEED_OF_LIGHT + inv_t = 1/time_explosion + beta = r * inv_t * inv_c if not montecarlo_configuration.full_relativity: return get_doppler_factor_partial_relativity(mu, beta) else: @@ -145,7 +150,9 @@ def get_doppler_factor_full_relativity(mu, beta): @njit(**njit_dict) def get_inverse_doppler_factor(r, mu, time_explosion): - beta = (r / time_explosion) / C_SPEED_OF_LIGHT + inv_c = 1 / C_SPEED_OF_LIGHT + inv_t = 1 / time_explosion + beta = r * inv_t * inv_c if not montecarlo_configuration.full_relativity: return get_inverse_doppler_factor_partial_relativity(mu, beta) else: @@ -165,13 +172,14 @@ def get_random_mu(): @jitclass(rpacket_spec) class RPacket(object): - def __init__(self, r, mu, nu, energy, index=0): + def __init__(self, r, mu, nu, energy, seed, index=0): self.r = r self.mu = mu self.nu = nu self.energy = energy self.current_shell_id = 0 self.status = PacketStatus.IN_PROCESS + self.seed = seed self.index = index def initialize_line_id(self, numba_plasma, numba_model): @@ -231,7 +239,7 @@ def calc_packet_energy(r_packet, distance_trace, time_explosion): return energy @njit(**njit_dict) -def trace_packet(r_packet, numba_model, numba_plasma, estimators): +def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson): """ Parameters @@ -263,7 +271,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): cur_electron_density = numba_plasma.electron_density[ r_packet.current_shell_id] distance_electron = calculate_distance_electron( - cur_electron_density, tau_event) + cur_electron_density, tau_event, sigma_thomson) # Calculating doppler factor doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, @@ -292,7 +300,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_trace) + distance_trace, + sigma_thomson) # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron @@ -307,6 +316,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): if ((distance_electron < distance_trace) and (distance_electron < distance_boundary)): interaction_type = InteractionType.ESCATTERING + # print('scattering') distance = distance_electron r_packet.next_line_id = cur_line_id break @@ -319,16 +329,17 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): estimators, r_packet, cur_line_id, distance_trace, numba_model.time_explosion) - if tau_trace_combined > tau_event: + if tau_trace_combined > tau_event \ + and not montecarlo_configuration.disable_line_scattering: interaction_type = InteractionType.LINE # Line r_packet.next_line_id = cur_line_id distance = distance_trace break - # Recalculating distance_electron using tau_event - # tau_trace_line_combined distance_electron = calculate_distance_electron( - cur_electron_density, tau_event - tau_trace_line_combined) + cur_electron_density, tau_event - tau_trace_line_combined, + sigma_thomson) else: # Executed when no break occurs in the for loop # We are beyond the line list now and the only next thing is to see @@ -339,6 +350,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): if distance_electron < distance_boundary: distance = distance_electron interaction_type = InteractionType.ESCATTERING + # print('scattering') else: distance = distance_boundary interaction_type = InteractionType.BOUNDARY diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index e0c0a4b1996..e7a13affdd1 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -6,7 +6,7 @@ move_packet_across_shell_boundary, move_r_packet, MonteCarloException) from tardis.montecarlo.montecarlo_numba.interaction import ( - general_scatter, line_scatter) + thomson_scatter, line_scatter) from tardis.montecarlo.montecarlo_numba.numba_interface import \ LineInteractionType from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration @@ -24,7 +24,7 @@ # @log_decorator @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection): + vpacket_collection, sigma_thomson): """ Parameters @@ -42,7 +42,6 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, This function does not return anything but changes the r_packet object and if virtual packets are requested - also updates the vpacket_collection """ - flag = None line_interaction_type = montecarlo_configuration.line_interaction_type @@ -53,7 +52,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet.initialize_line_id(numba_plasma, numba_model) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma) + numba_plasma, sigma_thomson) if mc_logger.DEBUG_MODE: r_packet_track_nu = [r_packet.nu] @@ -63,12 +62,9 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet_track_distance = [0.] while r_packet.status == PacketStatus.IN_PROCESS: - # try: distance, interaction_type, delta_shell = trace_packet( - r_packet, numba_model, numba_plasma, estimators) - # except MonteCarloException: - # flag = 'stop' - # break + r_packet, numba_model, numba_plasma, estimators, sigma_thomson) + if interaction_type == InteractionType.BOUNDARY: move_r_packet(r_packet, distance, numba_model.time_explosion, @@ -81,21 +77,18 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, estimators) line_scatter(r_packet, numba_model.time_explosion, line_interaction_type, numba_plasma) - # try: trace_vpacket_volley( r_packet, vpacket_collection, numba_model, numba_plasma, - ) - # except MonteCarloException: - # flag = 'stop' - # break + sigma_thomson) + elif interaction_type == InteractionType.ESCATTERING: move_r_packet(r_packet, distance, numba_model.time_explosion, estimators) - general_scatter(r_packet, numba_model.time_explosion) + thomson_scatter(r_packet, numba_model.time_explosion) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma) + numba_plasma, sigma_thomson) if mc_logger.DEBUG_MODE: r_packet_track_nu.append(r_packet.nu) r_packet_track_mu.append(r_packet.mu) @@ -106,7 +99,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, if mc_logger.DEBUG_MODE: return (r_packet_track_nu, r_packet_track_mu, r_packet_track_r, - r_packet_track_interaction, r_packet_track_distance, flag) + r_packet_track_interaction, r_packet_track_distance) # check where else initialize line ID happens! diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 9eb3efaa94b..b9fce146153 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -38,7 +38,8 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, @njit(**njit_dict) -def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): +def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, + sigma_thomson): r_inner = numba_model.r_inner[v_packet.current_shell_id] r_outer = numba_model.r_outer[v_packet.current_shell_id] @@ -53,7 +54,8 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): cur_electron_density = numba_plasma.electron_density[ v_packet.current_shell_id] tau_electron = calculate_tau_electron(cur_electron_density, - distance_boundary) + distance_boundary, + sigma_thomson) tau_trace_combined = tau_electron # Calculating doppler factor @@ -88,7 +90,7 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): return tau_trace_combined, distance_boundary, delta_shell @njit(**njit_dict) -def trace_vpacket(v_packet, numba_model, numba_plasma): +def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): """ Trace single vpacket. Parameters @@ -105,7 +107,7 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): tau_trace_combined = 0.0 while True: tau_trace_combined_shell, distance_boundary, delta_shell = trace_vpacket_within_shell( - v_packet, numba_model, numba_plasma + v_packet, numba_model, numba_plasma, sigma_thomson ) tau_trace_combined += tau_trace_combined_shell if tau_trace_combined > 10: @@ -124,7 +126,7 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): @njit(**njit_dict) def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma): + numba_plasma, sigma_thomson): """ Shoot a volley of vpackets (the vpacket collection specifies how many) from the current position of the rpacket. @@ -196,7 +198,8 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet_energy, r_packet.current_shell_id, r_packet.next_line_id, i) - tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) + tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma, + sigma_thomson) v_packet.energy *= np.exp(-tau_vpacket) diff --git a/tardis/montecarlo/packet_source.py b/tardis/montecarlo/packet_source.py index eaed2ddd5d4..75d7f8d51c9 100644 --- a/tardis/montecarlo/packet_source.py +++ b/tardis/montecarlo/packet_source.py @@ -10,37 +10,36 @@ class BasePacketSource(abc.ABC): def __init__(self, seed): self.seed = seed np.random.seed(seed) - + @abc.abstractmethod def create_packets(self, seed=None, **kwargs): pass @staticmethod - def create_zero_limb_darkening_packet_mus(no_of_packets): + def create_zero_limb_darkening_packet_mus(no_of_packets, rng): """ Create zero-limb-darkening packet :math:`\mu` distributed according to :math:`\\mu=\\sqrt{z}, z \isin [0, 1]` - + Parameters ---------- no_of_packets : int number of packets to be created """ - return np.sqrt(np.random.random(no_of_packets)) + return np.sqrt(rng.random(no_of_packets)) @staticmethod - def create_uniform_packet_energies(no_of_packets): + def create_uniform_packet_energies(no_of_packets, rng): """ - Uniformly distribute energy in arbitrary units where the ensemble of - packets has energy of 1. + Uniformly distribute energy in arbitrary units where the ensemble of + packets has energy of 1. - Parameters ---------- no_of_packets : int number of packets - + Returns ------- : numpy.ndarray @@ -48,33 +47,26 @@ def create_uniform_packet_energies(no_of_packets): """ return np.ones(no_of_packets) / no_of_packets - @staticmethod - def create_blackbody_packet_nus(T, no_of_packets, l_samples=1000): + def create_blackbody_packet_nus(T, no_of_packets, rng, l_samples=1000): """ - - Create packet :math:`\\nu` distributed using the algorithm described in - Bjorkman & Wood 2001 (page 4) which references + Create packet :math:`\\nu` distributed using the algorithm described in + Bjorkman & Wood 2001 (page 4) which references Carter & Cashwell 1975: - First, generate a uniform random number, :math:`\\xi_0 \\in [0, 1]` and - determine the minimum value of + determine the minimum value of :math:`l, l_{\\rm min}`, that satisfies the condition - .. math:: \\sum_{i=1}^{l} i^{-4} \\ge {{\\pi^4}\\over{90}} m_0 \\; . - - Next obtain four additional uniform random numbers (in the range 0 - to 1) :math:`\\xi_1, \\xi_2, \\xi_3, {\\rm and } \\xi_4`. - + + Next obtain four additional uniform random numbers (in the range 0 + to 1) :math:`\\xi_1, \\xi_2, \\xi_3, {\\rm and } \\xi_4`. + Finally, the packet frequency is given by - + .. math:: x = -\\ln{(\\xi_1\\xi_2\\xi_3\\xi_4)}/l_{\\rm min}\\; . - - where :math:`x=h\\nu/kT` - Parameters ---------- T : float @@ -82,19 +74,17 @@ def create_blackbody_packet_nus(T, no_of_packets, l_samples=1000): no_of_packets: int l_samples: int number of l_samples needed in the algorithm - Returns ------- - : numpy.ndarray array of frequencies """ l_samples = l_samples - l_array = np.cumsum(np.arange(1, l_samples, dtype=np.float64)**-4) - l_coef = np.pi**4 / 90.0 + l_array = np.cumsum(np.arange(1, l_samples, dtype=np.float64) ** -4) + l_coef = np.pi ** 4 / 90.0 - xis = np.random.random((5, no_of_packets)) - l = l_array.searchsorted(xis[0]*l_coef) + 1. + xis = rng.random((5, no_of_packets)) + l = l_array.searchsorted(xis[0] * l_coef) + 1. xis_prod = np.prod(xis[1:], 0) x = ne.evaluate('-log(xis_prod)/l') @@ -103,13 +93,13 @@ def create_blackbody_packet_nus(T, no_of_packets, l_samples=1000): class BlackBodySimpleSource(BasePacketSource): """ - Simple packet source that generates Blackbody packets for the Montecarlo + Simple packet source that generates Blackbody packets for the Montecarlo part. """ - def create_packets(self, T, no_of_packets): - nus = self.create_blackbody_packet_nus(T, no_of_packets) - mus = self.create_zero_limb_darkening_packet_mus(no_of_packets) - energies = self.create_uniform_packet_energies(no_of_packets) + def create_packets(self, T, no_of_packets, rng): + nus = self.create_blackbody_packet_nus(T, no_of_packets, rng) + mus = self.create_zero_limb_darkening_packet_mus(no_of_packets, rng) + energies = self.create_uniform_packet_energies(no_of_packets, rng) return nus, mus, energies \ No newline at end of file diff --git a/tardis/simulation/base.py b/tardis/simulation/base.py index fd3161701bf..2e31ccc4a8e 100644 --- a/tardis/simulation/base.py +++ b/tardis/simulation/base.py @@ -10,6 +10,8 @@ from tardis.plasma.standard_plasmas import assemble_plasma from tardis.io.util import HDFWriterMixin from tardis.io.config_reader import ConfigurationError +from tardis.montecarlo import montecarlo_configuration as mc_config_module + # Adding logging support logger = logging.getLogger(__name__) @@ -268,7 +270,8 @@ def iterate(self, no_of_packets, no_of_virtual_packets=0, last_run=False): self.iterations_executed + 1, self.iterations)) self.runner.run(self.model, self.plasma, no_of_packets, no_of_virtual_packets=no_of_virtual_packets, - nthreads=self.nthreads, last_run=last_run) + nthreads=self.nthreads, last_run=last_run, + iteration=self.iterations_executed) output_energy = self.runner.output_energy if np.sum(output_energy < 0) == len(output_energy): logger.critical("No r-packet escaped through the outer boundary.") diff --git a/tardis_env3.yml b/tardis_env3.yml index d9e7678fd6e..29e2b5cf261 100644 --- a/tardis_env3.yml +++ b/tardis_env3.yml @@ -6,15 +6,13 @@ channels: dependencies: - python=3 - pip - - numpy=1.15 - - scipy=1.1 - - pandas=0.24 - - astropy=3.2.1 - - - numba=0.49.1 + - numpy=1.19.0 + - scipy=1.5 + - pandas=1.0 + - astropy=3 + - numba=0.50 - numexpr - Cython=0.29 - - compilers # Plasma @@ -23,7 +21,7 @@ dependencies: # I/O - pyyaml - jsonschema - - pyne + - pyne=0.7 - pytables - h5py - requests @@ -37,6 +35,9 @@ dependencies: - pygraphviz - pyside2 + # widgets + - qgrid + # Documentation - sphinx - nbconvert @@ -50,21 +51,22 @@ dependencies: - sphinx_rtd_theme - recommonmark - #Test/Coverage requirements + # Test/Coverage requirements - git-lfs - - pytest=4 + - pytest=5 - pytest-html - - requests + - pytest-cov - coverage + - requests - docopt - - pytest-cov - - codecov + + # Code quality + - black - pip: # Documentation - sphinxcontrib-tikz - - dokuwiki - dot2tex - sphinx-jsonschema - - git+https://github.com/Naereen/dot2tex.git + - git+https://github.com/Naereen/dot2tex.git \ No newline at end of file From 7deb107d7be2fddc4ec47341253143ff8c024407 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 4 Sep 2020 09:38:18 +0000 Subject: [PATCH 056/116] modify virtual packet spawn range --- tardis/io/schemas/montecarlo.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tardis/io/schemas/montecarlo.yml b/tardis/io/schemas/montecarlo.yml index 2c32e9598cb..dba7afb59e0 100644 --- a/tardis/io/schemas/montecarlo.yml +++ b/tardis/io/schemas/montecarlo.yml @@ -33,21 +33,17 @@ properties: multipleOf: 1.0 default: 0 description: Setting the number of virtual packets for the last iteration. - virtual_spectrum_range: + virtual_spectrum_spawn_range: type: object default: {} properties: start: type: quantity - default: 50 angstrom + default: 0 angstrom stop: type: quantity - default: 250000 angstrom - num: - type: number - multipleOf: 1.0 - default: 1000000 - description: Limits of virtual packet spectrum (giving maximum and minimum packet + default: inf angstrom + description: Limits of virtual packet spawn spectrum (giving maximum and minimum packet frequency) enable_reflective_inner_boundary: type: boolean From 8e1aebe9d54bcbda9d3d5e411246f1ae7009a9f4 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 4 Sep 2020 09:59:14 +0000 Subject: [PATCH 057/116] use spawn range --- tardis/montecarlo/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 07c7f100414..b2c4d59550f 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -66,7 +66,7 @@ class MontecarloRunner(HDFWriterMixin): t_rad_estimator_constant = ((np.pi**4 / (15 * 24 * zeta(5, 1))) * (const.h / const.k_B)).cgs.value - def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, + def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, sigma_thomson, disable_electron_scattering, enable_reflective_inner_boundary, enable_full_relativity, inner_boundary_albedo, line_interaction_type, integrator_settings, @@ -82,7 +82,7 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_range, # inject different packets self.disable_electron_scattering = disable_electron_scattering self.spectrum_frequency = spectrum_frequency - self.virtual_spectrum_range = virtual_spectrum_range + self.virtual_spectrum_spawn_range = virtual_spectrum_spawn_range self.sigma_thomson = sigma_thomson self.enable_reflective_inner_boundary = enable_reflective_inner_boundary self.inner_boundary_albedo = inner_boundary_albedo @@ -474,7 +474,7 @@ def from_config(cls, config, packet_source=None): return cls(seed=config.montecarlo.seed, spectrum_frequency=spectrum_frequency, - virtual_spectrum_range=config.montecarlo.virtual_spectrum_range, + virtual_spectrum_spawn_range=config.montecarlo.virtual_spectrum_spawn_range, sigma_thomson=sigma_thomson, enable_reflective_inner_boundary=config.montecarlo.enable_reflective_inner_boundary, inner_boundary_albedo=config.montecarlo.inner_boundary_albedo, From e7a92e824c87f0068491b177ae7a6a8481d7892e Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 4 Sep 2020 10:14:36 +0000 Subject: [PATCH 058/116] Co-authored-by: cvogl@mpa-garching.mpg.de --- tardis/montecarlo/montecarlo_numba/numba_interface.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 11f87377c88..454b2301c40 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -129,6 +129,8 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, vpacket_collection_spec = [ ('spectrum_frequency', float64[:]), + ('v_packet_spawn_start_frequency', float64), + ('v_packet_spawn_end_frequency', float64), ('nus', float64[:]), ('energies', float64[:]), ('idx', int64), @@ -138,9 +140,14 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, @jitclass(vpacket_collection_spec) class VPacketCollection(object): - def __init__(self, spectrum_frequency, number_of_vpackets, + def __init__(self, spectrum_frequency, + v_packet_spawn_start_frequency, + v_packet_spawn_end_frequency, + number_of_vpackets, temporary_v_packet_bins): self.spectrum_frequency = spectrum_frequency + self.v_packet_spawn_start_frequency = v_packet_spawn_start_frequency + self.v_packet_spawn_end_frequency = v_packet_spawn_end_frequency self.nus = np.empty(temporary_v_packet_bins, dtype=np.float64) self.energies = np.empty(temporary_v_packet_bins, dtype=np.float64) self.number_of_vpackets = number_of_vpackets From 67788d4d9485b2f368cc3192501f21362229064f Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 4 Sep 2020 16:18:48 +0200 Subject: [PATCH 059/116] add some virtual packet spawn Co-authored-by: Christian Vogl Co-authored-by: Andrew Fullard --- tardis/io/schemas/montecarlo.yml | 2 +- tardis/montecarlo/montecarlo_numba/base.py | 5 ++++- tardis/montecarlo/montecarlo_numba/numba_interface.py | 7 +++++++ tardis/montecarlo/montecarlo_numba/vpacket.py | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tardis/io/schemas/montecarlo.yml b/tardis/io/schemas/montecarlo.yml index dba7afb59e0..1bcaafc48e5 100644 --- a/tardis/io/schemas/montecarlo.yml +++ b/tardis/io/schemas/montecarlo.yml @@ -40,7 +40,7 @@ properties: start: type: quantity default: 0 angstrom - stop: + end: type: quantity default: inf angstrom description: Limits of virtual packet spawn spectrum (giving maximum and minimum packet diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index a578fc130f8..424165342ec 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -73,7 +73,10 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, seed, i) vpacket_collection = VPacketCollection( - spectrum_frequency, number_of_vpackets, + spectrum_frequency, + montecarlo_configuration.v_packet_spawn_start_frequency, + montecarlo_configuration.v_packet_spawn_end_frequency, + number_of_vpackets, montecarlo_configuration.temporary_v_packet_bins) loop = single_packet_loop(r_packet, numba_model, numba_plasma, estimators, vpacket_collection, sigma_thomson) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 454b2301c40..21e5ba7c144 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -3,6 +3,7 @@ from numba import float64, int64, jitclass, boolean import numpy as np +from astropy import units as u from tardis import constants as const from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration @@ -188,6 +189,12 @@ def configuration_initialize(runner, number_of_vpackets, montecarlo_configuration.full_relativity = runner.enable_full_relativity montecarlo_configuration.montecarlo_seed = runner.seed montecarlo_configuration.single_packet_seed = runner.single_packet_seed + montecarlo_configuration.v_packet_spawn_start_frequency = runner.virtual_spectrum_spawn_range.end.to( + u.Hz, equivalencies=u.spectral() + ).value + montecarlo_configuration.v_packet_spawn_end_frequency = runner.virtual_spectrum_spawn_range.start.to( + u.Hz, equivalencies=u.spectral() + ).value #class TrackRPacket(object): diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index b9fce146153..8ca25ca812d 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -143,8 +143,8 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, [description] """ - if ((r_packet.nu < vpacket_collection.spectrum_frequency[0]) or - (r_packet.nu > vpacket_collection.spectrum_frequency[-1])): + if ((r_packet.nu < vpacket_collection.v_packet_spawn_start_frequency) or + (r_packet.nu > vpacket_collection.v_packet_spawn_end_frequency)): return From 5eb5433867d0f0127deb6bd1ae6d7d4051c4fee3 Mon Sep 17 00:00:00 2001 From: Christian Vogl Date: Fri, 4 Sep 2020 17:12:39 +0200 Subject: [PATCH 060/116] Reformat with black --- asv/benchmarks/benchmarks.py | 32 +- .../plasma_plots/lte_ionization_balance.py | 70 +- docs/physics/pyplot/plot_mu_in_out_packet.py | 15 +- tardis/__init__.py | 9 +- tardis/_astropy_init.py | 65 +- tardis/analysis.py | 285 +++-- tardis/base.py | 12 +- tardis/conftest.py | 92 +- tardis/constants.py | 2 +- tardis/gui/datahandler.py | 167 +-- tardis/gui/interface.py | 37 +- tardis/gui/tests/test_gui.py | 23 +- tardis/gui/widgets.py | 1013 ++++++++++------- tardis/io/__init__.py | 8 +- tardis/io/atom_data/__init__.py | 2 +- tardis/io/atom_data/atom_web_download.py | 19 +- tardis/io/atom_data/base.py | 365 +++--- tardis/io/atom_data/util.py | 32 +- tardis/io/config_internal.py | 49 +- tardis/io/config_reader.py | 135 ++- tardis/io/config_validator.py | 45 +- tardis/io/decay.py | 46 +- tardis/io/model_reader.py | 284 +++-- tardis/io/parsers/__init__.py | 7 +- tardis/io/parsers/blondin_toymodel.py | 141 ++- tardis/io/parsers/csvy.py | 22 +- tardis/io/parsers/stella.py | 14 +- tardis/io/setup_package.py | 24 +- tardis/io/tests/test_HDFWriter.py | 138 ++- tardis/io/tests/test_ascii_readers.py | 23 +- tardis/io/tests/test_atomic.py | 26 +- tardis/io/tests/test_config_reader.py | 64 +- .../io/tests/test_configuration_namespace.py | 49 +- tardis/io/tests/test_csvy_reader.py | 27 +- tardis/io/tests/test_decay.py | 25 +- tardis/io/tests/test_model_reader.py | 98 +- tardis/io/util.py | 107 +- tardis/plasma/base.py | 197 ++-- tardis/plasma/exceptions.py | 16 +- tardis/plasma/properties/atomic.py | 174 +-- tardis/plasma/properties/base.py | 71 +- .../plasma/properties/continuum_processes.py | 39 +- tardis/plasma/properties/general.py | 89 +- tardis/plasma/properties/ion_population.py | 451 +++++--- tardis/plasma/properties/j_blues.py | 67 +- tardis/plasma/properties/level_population.py | 61 +- tardis/plasma/properties/nlte.py | 305 +++-- .../plasma/properties/partition_function.py | 254 +++-- tardis/plasma/properties/plasma_input.py | 70 +- tardis/plasma/properties/properties.py | 2 +- .../plasma/properties/property_collections.py | 116 +- .../plasma/properties/radiative_properties.py | 319 ++++-- tardis/plasma/setup_package.py | 48 +- tardis/plasma/standard_plasmas.py | 176 +-- tardis/plasma/tests/test_complete_plasmas.py | 192 ++-- tardis/plasma/tests/test_hdf_plasma.py | 84 +- tardis/plasma/tests/test_plasma_vboundary.py | 52 +- tardis/plasma/tests/test_plasmas_full.py | 65 +- .../tests/test_tardis_model_density_config.py | 12 +- tardis/scripts/cmfgen2tardis.py | 88 +- tardis/scripts/debug/run_numba_single.py | 8 +- tardis/simulation/__init__.py | 2 +- tardis/simulation/base.py | 398 ++++--- tardis/simulation/setup_package.py | 5 +- tardis/simulation/tests/test_simulation.py | 136 ++- tardis/stats/base.py | 6 +- tardis/tests/fixtures/atom_data.py | 18 +- tardis/tests/integration_tests/conftest.py | 106 +- .../tests/integration_tests/plot_helpers.py | 50 +- tardis/tests/integration_tests/report.py | 69 +- tardis/tests/integration_tests/runner.py | 62 +- .../integration_tests/test_integration.py | 219 ++-- tardis/tests/setup_package.py | 14 +- tardis/tests/test_montecarlo.py | 1 - tardis/tests/test_tardis_full.py | 67 +- .../tests/test_tardis_full_formal_integral.py | 80 +- tardis/tests/test_util.py | 260 +++-- tardis/util/__init__.py | 2 - tardis/util/base.py | 229 ++-- tardis/util/colored_logger.py | 44 +- 80 files changed, 5115 insertions(+), 3181 deletions(-) diff --git a/asv/benchmarks/benchmarks.py b/asv/benchmarks/benchmarks.py index 423f6b96d49..1e828a7cd27 100644 --- a/asv/benchmarks/benchmarks.py +++ b/asv/benchmarks/benchmarks.py @@ -6,17 +6,21 @@ LINE_SIZE = 10000000 + class TimeSuite: """ An example benchmark that times the performance of various kinds of iterating over dictionaries in Python. """ + def setup(self): self.line = np.arange(LINE_SIZE, 1, -1).astype(np.float64) def time_binarysearch(self): for _ in range(LINE_SIZE): - montecarlo.binary_search_wrapper(self.line, np.random.random() * LINE_SIZE, 0, LINE_SIZE - 1) + montecarlo.binary_search_wrapper( + self.line, np.random.random() * LINE_SIZE, 0, LINE_SIZE - 1 + ) def time_compute_distance2outer(self): for _ in range(1000000): @@ -25,7 +29,7 @@ def time_compute_distance2outer(self): montecarlo.compute_distance2outer_wrapper(0.3, 1.0, 1.0) montecarlo.compute_distance2outer_wrapper(0.3, -1.0, 1.0) montecarlo.compute_distance2outer_wrapper(0.5, 0.0, 1.0) - + def time_compute_distance2inner(self): for _ in range(1000000): montecarlo.compute_distance2inner_wrapper(1.5, -1.0, 1.0) @@ -34,8 +38,28 @@ def time_compute_distance2inner(self): def time_compute_distance2line(self): for _ in range(1000000): - montecarlo.compute_distance2line_wrapper(2.20866912e+15, -0.251699059004, 1.05581082105e+15, 1.06020910733e+15, 1693440.0, 5.90513983371e-07, 1.0602263591e+15, 1.06011723237e+15, 2) - montecarlo.compute_distance2line_wrapper(2.23434667994e+15, -0.291130548401, 1.05581082105e+15, 1.06733618121e+15, 1693440.0, 5.90513983371e-07, 1.06738407486e+15, 1.06732933961e+15, 3) + montecarlo.compute_distance2line_wrapper( + 2.20866912e15, + -0.251699059004, + 1.05581082105e15, + 1.06020910733e15, + 1693440.0, + 5.90513983371e-07, + 1.0602263591e15, + 1.06011723237e15, + 2, + ) + montecarlo.compute_distance2line_wrapper( + 2.23434667994e15, + -0.291130548401, + 1.05581082105e15, + 1.06733618121e15, + 1693440.0, + 5.90513983371e-07, + 1.06738407486e15, + 1.06732933961e15, + 3, + ) def time_compute_distance2electron(self): for _ in range(1000000): diff --git a/docs/physics/plasma/plasma_plots/lte_ionization_balance.py b/docs/physics/plasma/plasma_plots/lte_ionization_balance.py index 219b9d8245b..097730ca7b2 100644 --- a/docs/physics/plasma/plasma_plots/lte_ionization_balance.py +++ b/docs/physics/plasma/plasma_plots/lte_ionization_balance.py @@ -7,7 +7,7 @@ import numpy as np import pandas as pd -#Making 2 Figures for ionization balance and level populations +# Making 2 Figures for ionization balance and level populations plt.figure(1).clf() ax1 = plt.figure(1).add_subplot(111) @@ -16,73 +16,87 @@ ax2 = plt.figure(2).add_subplot(111) # expanding the tilde to the users directory -#atom_fname = os.path.join(os.path.dirname(base.__file__), 'data', 'atom_data.h5') +# atom_fname = os.path.join(os.path.dirname(base.__file__), 'data', 'atom_data.h5') # reading in the HDF5 File atom_data = AtomData.from_hdf(atom_fname) -#The atom_data needs to be prepared to create indices. The Class needs to know which atomic numbers are needed for the -#calculation and what line interaction is needed (for "downbranch" and "macroatom" the code creates special tables) -atom_data.prepare_atom_data([14], 'scatter') +# The atom_data needs to be prepared to create indices. The Class needs to know which atomic numbers are needed for the +# calculation and what line interaction is needed (for "downbranch" and "macroatom" the code creates special tables) +atom_data.prepare_atom_data([14], "scatter") -#Initializing the NebularPlasma class using the from_abundance class method. -#This classmethod is normally only needed to test individual plasma classes -#Usually the plasma class just gets the number densities from the model class +# Initializing the NebularPlasma class using the from_abundance class method. +# This classmethod is normally only needed to test individual plasma classes +# Usually the plasma class just gets the number densities from the model class assert True, ( - 'This script needs a proper rewrite and should use the new' - '"assemble_plasma" function.') + "This script needs a proper rewrite and should use the new" + '"assemble_plasma" function.' +) # TODO: Uncomment and fix the next line # lte_plasma = assemble_plasma({'Si':1.0}, 1e-14*u.g/u.cm**3, atom_data, 10*u.day) lte_plasma = None lte_plasma.update_radiationfield([10000], [1.0]) -#Initializing a dataframe to store the ion populations and level populations for the different temperatures +# Initializing a dataframe to store the ion populations and level populations for the different temperatures ion_number_densities = pd.DataFrame(index=lte_plasma.ion_populations.index) -level_populations = pd.DataFrame(index=lte_plasma.level_populations.ix[14, 1].index) +level_populations = pd.DataFrame( + index=lte_plasma.level_populations.ix[14, 1].index +) t_rads = np.linspace(2000, 20000, 100) -#Calculating the different ion populations and level populuatios for the given temperatures +# Calculating the different ion populations and level populuatios for the given temperatures for t_rad in t_rads: lte_plasma.update_radiationfield([t_rad], ws=[1.0]) - #getting total si number density + # getting total si number density si_number_density = lte_plasma.number_densities.get_value(14, 0) - #Normalizing the ion populations + # Normalizing the ion populations ion_density = lte_plasma.ion_populations / si_number_density ion_number_densities[t_rad] = ion_density - #normalizing the level_populations for Si II - current_level_population = lte_plasma.level_populations[0].ix[14, 1] / lte_plasma.ion_populations.get_value((14, 1), 0) + # normalizing the level_populations for Si II + current_level_population = lte_plasma.level_populations[0].ix[ + 14, 1 + ] / lte_plasma.ion_populations.get_value((14, 1), 0) - #normalizing with statistical weight + # normalizing with statistical weight current_level_population /= atom_data.levels.ix[14, 1].g level_populations[t_rad] = current_level_population -ion_colors = ['b', 'g', 'r', 'k'] +ion_colors = ["b", "g", "r", "k"] for ion_number in [0, 1, 2, 3]: current_ion_density = ion_number_densities.ix[14, ion_number] - ax1.plot(current_ion_density.index, current_ion_density.values, '%s-' % ion_colors[ion_number], - label='Si %s W=1.0' % tardis.util.base.int_to_roman(ion_number + 1).upper()) + ax1.plot( + current_ion_density.index, + current_ion_density.values, + "%s-" % ion_colors[ion_number], + label="Si %s W=1.0" + % tardis.util.base.int_to_roman(ion_number + 1).upper(), + ) -#only plotting every 5th radiation temperature +# only plotting every 5th radiation temperature t_rad_normalizer = colors.Normalize(vmin=2000, vmax=20000) t_rad_color_map = plt.cm.ScalarMappable(norm=t_rad_normalizer, cmap=plt.cm.jet) for t_rad in t_rads[::5]: - ax2.plot(level_populations[t_rad].index, level_populations[t_rad].values, color=t_rad_color_map.to_rgba(t_rad)) + ax2.plot( + level_populations[t_rad].index, + level_populations[t_rad].values, + color=t_rad_color_map.to_rgba(t_rad), + ) ax2.semilogy() t_rad_color_map.set_array(t_rads) cb = plt.figure(2).colorbar(t_rad_color_map) -ax1.set_xlabel('T [K]') -ax1.set_ylabel('Number Density Fraction') +ax1.set_xlabel("T [K]") +ax1.set_ylabel("Number Density Fraction") ax1.legend() -ax2.set_xlabel('Level Number for Si II') -ax2.set_ylabel('Number Density Fraction') -cb.set_label('T [K]') +ax2.set_xlabel("Level Number for Si II") +ax2.set_ylabel("Number Density Fraction") +cb.set_label("T [K]") plt.show() diff --git a/docs/physics/pyplot/plot_mu_in_out_packet.py b/docs/physics/pyplot/plot_mu_in_out_packet.py index d58250c8fb0..69505088359 100644 --- a/docs/physics/pyplot/plot_mu_in_out_packet.py +++ b/docs/physics/pyplot/plot_mu_in_out_packet.py @@ -1,12 +1,13 @@ from pylab import * from astropy import units as u, constants as const + x, y = x, y = mgrid[1:1000, 1:1000] -mu_in = x / 500. - 1 -mu_out = y / 500. - 1 +mu_in = x / 500.0 - 1 +mu_out = y / 500.0 - 1 v = 1.1e4 * u.km / u.s -doppler_fac = (1-mu_in * v/const.c)/(1-mu_out * v/const.c) -xlabel('$\mu_{\\rm in}$') -ylabel('$\mu_{\\rm out}$') -imshow(np.rot90(doppler_fac), extent=[-1, 1, -1, 1], cmap='bwr') +doppler_fac = (1 - mu_in * v / const.c) / (1 - mu_out * v / const.c) +xlabel("$\mu_{\\rm in}$") +ylabel("$\mu_{\\rm out}$") +imshow(np.rot90(doppler_fac), extent=[-1, 1, -1, 1], cmap="bwr") colorbar() -show() \ No newline at end of file +show() diff --git a/tardis/__init__.py b/tardis/__init__.py index 77b6a16437b..152b22a141d 100644 --- a/tardis/__init__.py +++ b/tardis/__init__.py @@ -6,21 +6,24 @@ import pyne.data from tardis.util.colored_logger import ColoredFormatter, formatter_message + # Affiliated packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * + # ---------------------------------------------------------------------------- from tardis.base import run_tardis from tardis.io.util import yaml_load_config_file as yaml_load -warnings.filterwarnings('ignore', category=pyne.data.QAWarning) + +warnings.filterwarnings("ignore", category=pyne.data.QAWarning) FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s] %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)" COLOR_FORMAT = formatter_message(FORMAT, True) logging.captureWarnings(True) -logger = logging.getLogger('tardis') +logger = logging.getLogger("tardis") logger.setLevel(logging.INFO) console_handler = logging.StreamHandler(sys.stdout) @@ -28,4 +31,4 @@ console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) -logging.getLogger('py.warnings').addHandler(console_handler) +logging.getLogger("py.warnings").addHandler(console_handler) diff --git a/tardis/_astropy_init.py b/tardis/_astropy_init.py index 3d761295658..7a4e25060e2 100644 --- a/tardis/_astropy_init.py +++ b/tardis/_astropy_init.py @@ -1,12 +1,13 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -__all__ = ['__version__', '__githash__', 'test'] +__all__ = ["__version__", "__githash__", "test"] # this indicates whether or not we are in the package's setup.py try: _ASTROPY_SETUP_ except NameError: from sys import version_info + if version_info[0] >= 3: import builtins else: @@ -16,21 +17,34 @@ try: from .version import version as __version__ except ImportError: - __version__ = '' + __version__ = "" try: from .version import githash as __githash__ except ImportError: - __githash__ = '' + __githash__ = "" # set up the test command def _get_test_runner(): import os from astropy.tests.helper import TestRunner + return TestRunner(os.path.dirname(__file__)) -def test(package=None, test_path=None, args=None, plugins=None, - verbose=False, pastebin=None, remote_data=False, pep8=False, - pdb=False, coverage=False, open_files=False, **kwargs): + +def test( + package=None, + test_path=None, + args=None, + plugins=None, + verbose=False, + pastebin=None, + remote_data=False, + pep8=False, + pdb=False, + coverage=False, + open_files=False, + **kwargs, +): """ Run the tests using `py.test `__. A proper set of arguments is constructed and passed to `pytest.main`_. @@ -105,10 +119,20 @@ def test(package=None, test_path=None, args=None, plugins=None, """ test_runner = _get_test_runner() return test_runner.run_tests( - package=package, test_path=test_path, args=args, - plugins=plugins, verbose=verbose, pastebin=pastebin, - remote_data=remote_data, pep8=pep8, pdb=pdb, - coverage=coverage, open_files=open_files, **kwargs) + package=package, + test_path=test_path, + args=args, + plugins=plugins, + verbose=verbose, + pastebin=pastebin, + remote_data=remote_data, + pep8=pep8, + pdb=pdb, + coverage=coverage, + open_files=open_files, + **kwargs, + ) + if not _ASTROPY_SETUP_: import os @@ -118,21 +142,30 @@ def test(package=None, test_path=None, args=None, plugins=None, # add these here so we only need to cleanup the namespace at the end config_dir = None - if not os.environ.get('ASTROPY_SKIP_CONFIG_UPDATE', False): + if not os.environ.get("ASTROPY_SKIP_CONFIG_UPDATE", False): config_dir = os.path.dirname(__file__) config_template = os.path.join(config_dir, __package__ + ".cfg") if os.path.isfile(config_template): try: config.configuration.update_default_config( - __package__, config_dir, version=__version__) + __package__, config_dir, version=__version__ + ) except TypeError as orig_error: try: config.configuration.update_default_config( - __package__, config_dir) + __package__, config_dir + ) except config.configuration.ConfigurationDefaultMissingError as e: - wmsg = (e.args[0] + " Cannot install default profile. If you are " - "importing from source, this is expected.") - warn(config.configuration.ConfigurationDefaultMissingWarning(wmsg)) + wmsg = ( + e.args[0] + + " Cannot install default profile. If you are " + "importing from source, this is expected." + ) + warn( + config.configuration.ConfigurationDefaultMissingWarning( + wmsg + ) + ) del e except: raise orig_error diff --git a/tardis/analysis.py b/tardis/analysis.py index 8118eca7ce0..89a8781079f 100644 --- a/tardis/analysis.py +++ b/tardis/analysis.py @@ -1,4 +1,4 @@ -#codes to for analyse the model. +# codes to for analyse the model. import re import os @@ -10,25 +10,36 @@ class LastLineInteraction(object): - @classmethod def from_model(cls, model): - return cls(model.runner.last_line_interaction_in_id, - model.runner.last_line_interaction_out_id, - model.runner.last_line_interaction_shell_id, - model.runner.output_nu, model.plasma.atomic_data.lines) - - def __init__(self, last_line_interaction_in_id, - last_line_interaction_out_id, last_line_interaction_shell_id, - output_nu, lines, packet_filter_mode='packet_nu'): + return cls( + model.runner.last_line_interaction_in_id, + model.runner.last_line_interaction_out_id, + model.runner.last_line_interaction_shell_id, + model.runner.output_nu, + model.plasma.atomic_data.lines, + ) + + def __init__( + self, + last_line_interaction_in_id, + last_line_interaction_out_id, + last_line_interaction_shell_id, + output_nu, + lines, + packet_filter_mode="packet_nu", + ): # mask out packets which did not perform a line interaction # TODO mask out packets which do not escape to observer? mask = last_line_interaction_out_id != -1 self.last_line_interaction_in_id = last_line_interaction_in_id[mask] self.last_line_interaction_out_id = last_line_interaction_out_id[mask] - self.last_line_interaction_shell_id = last_line_interaction_shell_id[mask] + self.last_line_interaction_shell_id = last_line_interaction_shell_id[ + mask + ] self.last_line_interaction_angstrom = output_nu.to( - u.Angstrom, equivalencies=u.spectral())[mask] + u.Angstrom, equivalencies=u.spectral() + )[mask] self.lines = lines self._wavelength_start = 0 * u.angstrom @@ -38,27 +49,25 @@ def __init__(self, last_line_interaction_in_id, self.packet_filter_mode = packet_filter_mode self.update_last_interaction_filter() - - @property def wavelength_start(self): - return self._wavelength_start.to('angstrom') + return self._wavelength_start.to("angstrom") @wavelength_start.setter def wavelength_start(self, value): if not isinstance(value, u.Quantity): - raise ValueError('needs to be a Quantity') + raise ValueError("needs to be a Quantity") self._wavelength_start = value self.update_last_interaction_filter() @property def wavelength_end(self): - return self._wavelength_end.to('angstrom') + return self._wavelength_end.to("angstrom") @wavelength_end.setter def wavelength_end(self, value): if not isinstance(value, u.Quantity): - raise ValueError('needs to be a Quantity') + raise ValueError("needs to be a Quantity") self._wavelength_end = value self.update_last_interaction_filter() @@ -81,126 +90,150 @@ def ion_number(self, value): self.update_last_interaction_filter() def update_last_interaction_filter(self): - if self.packet_filter_mode == 'packet_nu': + if self.packet_filter_mode == "packet_nu": packet_filter = ( - (self.last_line_interaction_angstrom > - self.wavelength_start) & - (self.last_line_interaction_angstrom < - self.wavelength_end)) - elif self.packet_filter_mode == 'line_in_nu': - line_in_nu = ( - self.lines.wavelength.iloc[ - self.last_line_interaction_in_id].values) + self.last_line_interaction_angstrom > self.wavelength_start + ) & (self.last_line_interaction_angstrom < self.wavelength_end) + elif self.packet_filter_mode == "line_in_nu": + line_in_nu = self.lines.wavelength.iloc[ + self.last_line_interaction_in_id + ].values packet_filter = ( - (line_in_nu > self.wavelength_start.to(u.angstrom).value) & - (line_in_nu < self.wavelength_end.to(u.angstrom).value)) - + line_in_nu > self.wavelength_start.to(u.angstrom).value + ) & (line_in_nu < self.wavelength_end.to(u.angstrom).value) self.last_line_in = self.lines.iloc[ - self.last_line_interaction_in_id[packet_filter]] + self.last_line_interaction_in_id[packet_filter] + ] self.last_line_out = self.lines.iloc[ - self.last_line_interaction_out_id[packet_filter]] + self.last_line_interaction_out_id[packet_filter] + ] if self.atomic_number is not None: self.last_line_in = self.last_line_in.xs( - self.atomic_number, level='atomic_number', drop_level=False) + self.atomic_number, level="atomic_number", drop_level=False + ) self.last_line_out = self.last_line_out.xs( - self.atomic_number, level='atomic_number', drop_level=False) + self.atomic_number, level="atomic_number", drop_level=False + ) if self.ion_number is not None: self.last_line_in = self.last_line_in.xs( - self.ion_number, level='ion_number', drop_level=False) + self.ion_number, level="ion_number", drop_level=False + ) self.last_line_out = self.last_line_out.xs( - self.ion_number, level='ion_number', drop_level=False) + self.ion_number, level="ion_number", drop_level=False + ) last_line_in_count = self.last_line_in.line_id.value_counts() last_line_out_count = self.last_line_out.line_id.value_counts() self.last_line_in_table = self.last_line_in.reset_index()[ - [ - 'wavelength', 'atomic_number', 'ion_number', - 'level_number_lower', 'level_number_upper']] - self.last_line_in_table['count'] = last_line_in_count - self.last_line_in_table.sort_values(by='count', ascending=False, - inplace=True) + [ + "wavelength", + "atomic_number", + "ion_number", + "level_number_lower", + "level_number_upper", + ] + ] + self.last_line_in_table["count"] = last_line_in_count + self.last_line_in_table.sort_values( + by="count", ascending=False, inplace=True + ) self.last_line_out_table = self.last_line_out.reset_index()[ - [ - 'wavelength', 'atomic_number', 'ion_number', - 'level_number_lower', 'level_number_upper']] - self.last_line_out_table['count'] = last_line_out_count - self.last_line_out_table.sort_values(by='count', ascending=False, - inplace=True) + [ + "wavelength", + "atomic_number", + "ion_number", + "level_number_lower", + "level_number_upper", + ] + ] + self.last_line_out_table["count"] = last_line_out_count + self.last_line_out_table.sort_values( + by="count", ascending=False, inplace=True + ) def plot_wave_in_out(self, fig, do_clf=True, plot_resonance=True): if do_clf: fig.clf() ax = fig.add_subplot(111) - wave_in = self.last_line_list_in['wavelength'] - wave_out = self.last_line_list_out['wavelength'] + wave_in = self.last_line_list_in["wavelength"] + wave_out = self.last_line_list_out["wavelength"] if plot_resonance: min_wave = np.min([wave_in.min(), wave_out.min()]) max_wave = np.max([wave_in.max(), wave_out.max()]) - ax.plot([min_wave, max_wave], [min_wave, max_wave], 'b-') + ax.plot([min_wave, max_wave], [min_wave, max_wave], "b-") - ax.plot(wave_in, wave_out, 'b.', picker=True) - ax.set_xlabel('Last interaction Wave in') - ax.set_ylabel('Last interaction Wave out') + ax.plot(wave_in, wave_out, "b.", picker=True) + ax.set_xlabel("Last interaction Wave in") + ax.set_ylabel("Last interaction Wave out") def onpick(event): print("-" * 80) - print("Line_in (%d/%d):\n%s" % ( - len(event.ind), self.current_no_packets, - self.last_line_list_in.ix[event.ind])) + print( + "Line_in (%d/%d):\n%s" + % ( + len(event.ind), + self.current_no_packets, + self.last_line_list_in.ix[event.ind], + ) + ) print("\n\n") - print("Line_out (%d/%d):\n%s" % ( - len(event.ind), self.current_no_packets, - self.last_line_list_in.ix[event.ind])) + print( + "Line_out (%d/%d):\n%s" + % ( + len(event.ind), + self.current_no_packets, + self.last_line_list_in.ix[event.ind], + ) + ) print("^" * 80) def onpress(event): pass - fig.canvas.mpl_connect('pick_event', onpick) - fig.canvas.mpl_connect('on_press', onpress) + fig.canvas.mpl_connect("pick_event", onpick) + fig.canvas.mpl_connect("on_press", onpress) class TARDISHistory(object): """ Records the history of the model """ + def __init__(self, hdf5_fname, iterations=None): self.hdf5_fname = hdf5_fname if iterations is None: iterations = [] - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") for key in hdf_store.keys(): - if key.split('/')[1] == 'atom_data': + if key.split("/")[1] == "atom_data": continue iterations.append( - int(re.match(r'model(\d+)', key.split('/')[1]).groups()[0])) + int(re.match(r"model(\d+)", key.split("/")[1]).groups()[0]) + ) self.iterations = np.sort(np.unique(iterations)) hdf_store.close() else: - self.iterations=iterations + self.iterations = iterations self.levels = None self.lines = None - - def load_atom_data(self): if self.levels is None or self.lines is None: - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') - self.levels = hdf_store['atom_data/levels'] - self.lines = hdf_store['atom_data/lines'] + hdf_store = pd.HDFStore(self.hdf5_fname, "r") + self.levels = hdf_store["atom_data/levels"] + self.lines = hdf_store["atom_data/lines"] hdf_store.close() - def load_t_inner(self, iterations=None): t_inners = [] - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") if iterations is None: iterations = self.iterations @@ -210,7 +243,9 @@ def load_t_inner(self, iterations=None): iterations = self.iterations[iterations] for iter in iterations: - t_inners.append(hdf_store['model%03d/configuration' %iter].ix['t_inner']) + t_inners.append( + hdf_store["model%03d/configuration" % iter].ix["t_inner"] + ) hdf_store.close() t_inners = np.array(t_inners) @@ -218,7 +253,7 @@ def load_t_inner(self, iterations=None): def load_t_rads(self, iterations=None): t_rads_dict = {} - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") if iterations is None: iterations = self.iterations @@ -227,10 +262,9 @@ def load_t_rads(self, iterations=None): else: iterations = self.iterations[iterations] - for iter in iterations: - current_iter = 'iter%03d' % iter - t_rads_dict[current_iter] = hdf_store['model%03d/t_rads' % iter] + current_iter = "iter%03d" % iter + t_rads_dict[current_iter] = hdf_store["model%03d/t_rads" % iter] t_rads = pd.DataFrame(t_rads_dict) hdf_store.close() @@ -238,7 +272,7 @@ def load_t_rads(self, iterations=None): def load_ws(self, iterations=None): ws_dict = {} - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") if iterations is None: iterations = self.iterations @@ -248,8 +282,8 @@ def load_ws(self, iterations=None): iterations = self.iterations[iterations] for iter in iterations: - current_iter = 'iter{:03d}'.format(iter) - ws_dict[current_iter] = hdf_store['model{:03d}/ws'.format(iter)] + current_iter = "iter{:03d}".format(iter) + ws_dict[current_iter] = hdf_store["model{:03d}/ws".format(iter)] hdf_store.close() @@ -257,7 +291,7 @@ def load_ws(self, iterations=None): def load_level_populations(self, iterations=None): level_populations_dict = {} - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") is_scalar = False if iterations is None: iterations = self.iterations @@ -268,9 +302,10 @@ def load_level_populations(self, iterations=None): iterations = self.iterations[iterations] for iter in iterations: - current_iter = 'iter%03d' % iter + current_iter = "iter%03d" % iter level_populations_dict[current_iter] = hdf_store[ - 'model{:03d}/level_populations'.format(iter)] + "model{:03d}/level_populations".format(iter) + ] hdf_store.close() if is_scalar: @@ -280,7 +315,7 @@ def load_level_populations(self, iterations=None): def load_jblues(self, iterations=None): jblues_dict = {} - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") is_scalar = False if iterations is None: iterations = self.iterations @@ -291,9 +326,10 @@ def load_jblues(self, iterations=None): iterations = self.iterations[iterations] for iter in iterations: - current_iter = 'iter{:03d}'.format(iter) + current_iter = "iter{:03d}".format(iter) jblues_dict[current_iter] = hdf_store[ - 'model{:03d}/j_blues'.format(iter)] + "model{:03d}/j_blues".format(iter) + ] hdf_store.close() if is_scalar: @@ -301,10 +337,9 @@ def load_jblues(self, iterations=None): else: return pd.Panel(jblues_dict) - def load_ion_populations(self, iterations=None): ion_populations_dict = {} - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + hdf_store = pd.HDFStore(self.hdf5_fname, "r") is_scalar = False if iterations is None: @@ -316,9 +351,10 @@ def load_ion_populations(self, iterations=None): iterations = self.iterations[iterations] for iter in iterations: - current_iter = 'iter{:03d}'.format(iter) + current_iter = "iter{:03d}".format(iter) ion_populations_dict[current_iter] = hdf_store[ - 'model{:03d}/ion_populations'.format(iter)] + "model{:03d}/ion_populations".format(iter) + ] hdf_store.close() if is_scalar: @@ -326,37 +362,47 @@ def load_ion_populations(self, iterations=None): else: return pd.Panel(ion_populations_dict) - def load_spectrum(self, iteration, spectrum_keyword='luminosity_density'): - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') + def load_spectrum(self, iteration, spectrum_keyword="luminosity_density"): + hdf_store = pd.HDFStore(self.hdf5_fname, "r") - spectrum = hdf_store['model%03d/%s' % (self.iterations[iteration], spectrum_keyword)] + spectrum = hdf_store[ + "model%03d/%s" % (self.iterations[iteration], spectrum_keyword) + ] hdf_store.close() return spectrum def calculate_relative_lte_level_populations(self, species, iteration=-1): self.load_atom_data() t_rads = self.load_t_rads(iteration) - beta_rads = 1 / (constants.k_B.cgs.value * t_rads.values[:,0]) + beta_rads = 1 / (constants.k_B.cgs.value * t_rads.values[:, 0]) species_levels = self.levels.ix[species] relative_lte_level_populations = ( - (species_levels.g.values[np.newaxis].T / - float(species_levels.g.loc[0])) * - np.exp(-beta_rads * species_levels.energy.values[np.newaxis].T)) + species_levels.g.values[np.newaxis].T + / float(species_levels.g.loc[0]) + ) * np.exp(-beta_rads * species_levels.energy.values[np.newaxis].T) - return pd.DataFrame(relative_lte_level_populations, index=species_levels.index) + return pd.DataFrame( + relative_lte_level_populations, index=species_levels.index + ) def calculate_departure_coefficients(self, species, iteration=-1): self.load_atom_data() t_rads = self.load_t_rads(iteration) - beta_rads = 1 / (constants.k_B.cgs.value * t_rads.values[:,0]) + beta_rads = 1 / (constants.k_B.cgs.value * t_rads.values[:, 0]) species_levels = self.levels.ix[species] - species_level_populations = self.load_level_populations(iteration).ix[species] - departure_coefficient = ((species_level_populations.values * species_levels.g.ix[0]) / - (species_level_populations.ix[0].values * species_levels.g.values[np.newaxis].T)) \ - * np.exp(beta_rads * species_levels.energy.values[np.newaxis].T) + species_level_populations = self.load_level_populations(iteration).ix[ + species + ] + departure_coefficient = ( + (species_level_populations.values * species_levels.g.ix[0]) + / ( + species_level_populations.ix[0].values + * species_levels.g.values[np.newaxis].T + ) + ) * np.exp(beta_rads * species_levels.energy.values[np.newaxis].T) return pd.DataFrame(departure_coefficient, index=species_levels.index) @@ -364,15 +410,28 @@ def get_last_line_interaction(self, iteration=-1): iteration = self.iterations[iteration] self.load_atom_data() - hdf_store = pd.HDFStore(self.hdf5_fname, 'r') - model_string = 'model'+('%03d' % iteration) + '/%s' - last_line_interaction_in_id = hdf_store[model_string % 'last_line_interaction_in_id'].values - last_line_interaction_out_id = hdf_store[model_string % 'last_line_interaction_out_id'].values - last_line_interaction_shell_id = hdf_store[model_string % 'last_line_interaction_shell_id'].values + hdf_store = pd.HDFStore(self.hdf5_fname, "r") + model_string = "model" + ("%03d" % iteration) + "/%s" + last_line_interaction_in_id = hdf_store[ + model_string % "last_line_interaction_in_id" + ].values + last_line_interaction_out_id = hdf_store[ + model_string % "last_line_interaction_out_id" + ].values + last_line_interaction_shell_id = hdf_store[ + model_string % "last_line_interaction_shell_id" + ].values try: - montecarlo_nu = hdf_store[model_string % 'montecarlo_nus_path'].values + montecarlo_nu = hdf_store[ + model_string % "montecarlo_nus_path" + ].values except KeyError: - montecarlo_nu = hdf_store[model_string % 'montecarlo_nus'].values + montecarlo_nu = hdf_store[model_string % "montecarlo_nus"].values hdf_store.close() - return LastLineInteraction(last_line_interaction_in_id, last_line_interaction_out_id, last_line_interaction_shell_id, - montecarlo_nu, self.lines) + return LastLineInteraction( + last_line_interaction_in_id, + last_line_interaction_out_id, + last_line_interaction_shell_id, + montecarlo_nu, + self.lines, + ) diff --git a/tardis/base.py b/tardis/base.py index 1969240bc5b..deec06a782b 100644 --- a/tardis/base.py +++ b/tardis/base.py @@ -1,7 +1,9 @@ # functions that are important for the general usage of TARDIS -def run_tardis(config, atom_data=None, packet_source=None, - simulation_callbacks=[]): + +def run_tardis( + config, atom_data=None, packet_source=None, simulation_callbacks=[] +): """ This function is one of the core functions to run TARDIS from a given config object. @@ -35,9 +37,9 @@ def run_tardis(config, atom_data=None, packet_source=None, except TypeError: tardis_config = Configuration.from_config_dict(config) - simulation = Simulation.from_config(tardis_config, - packet_source=packet_source, - atom_data=atom_data) + simulation = Simulation.from_config( + tardis_config, packet_source=packet_source, atom_data=atom_data + ) for cb in simulation_callbacks: simulation.add_callback(cb) diff --git a/tardis/conftest.py b/tardis/conftest.py index 3028372a815..76541c5cefc 100644 --- a/tardis/conftest.py +++ b/tardis/conftest.py @@ -6,7 +6,8 @@ # test infrastructure. from astropy.version import version as astropy_version -if astropy_version < '3.0': + +if astropy_version < "3.0": # With older versions of Astropy, we actually need to import the pytest # plugins themselves in order to make them discoverable by pytest. from astropy.tests.pytest_plugins import * @@ -15,7 +16,10 @@ # automatically made available when Astropy is installed. This means it's # not necessary to import them here, but we still need to import global # variables that are used for configuration. - from astropy.tests.plugins.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS + from astropy.tests.plugins.display import ( + PYTEST_HEADER_MODULES, + TESTED_VERSIONS, + ) from astropy.tests.helper import enable_deprecations_as_exceptions @@ -82,15 +86,15 @@ # the tests. Making it pass for KeyError is essential in some cases when # the package uses other astropy affiliated packages. try: - PYTEST_HEADER_MODULES['Numpy'] = 'numpy' - PYTEST_HEADER_MODULES['Scipy'] = 'scipy' - PYTEST_HEADER_MODULES['Pandas'] = 'pandas' - PYTEST_HEADER_MODULES['Astropy'] = 'astropy' - PYTEST_HEADER_MODULES['Yaml'] = 'yaml' - PYTEST_HEADER_MODULES['Cython'] = 'cython' - PYTEST_HEADER_MODULES['h5py'] = 'h5py' - PYTEST_HEADER_MODULES['Matplotlib'] = 'matplotlib' - PYTEST_HEADER_MODULES['Ipython'] = 'IPython' + PYTEST_HEADER_MODULES["Numpy"] = "numpy" + PYTEST_HEADER_MODULES["Scipy"] = "scipy" + PYTEST_HEADER_MODULES["Pandas"] = "pandas" + PYTEST_HEADER_MODULES["Astropy"] = "astropy" + PYTEST_HEADER_MODULES["Yaml"] = "yaml" + PYTEST_HEADER_MODULES["Cython"] = "cython" + PYTEST_HEADER_MODULES["h5py"] = "h5py" + PYTEST_HEADER_MODULES["Matplotlib"] = "matplotlib" + PYTEST_HEADER_MODULES["Ipython"] = "IPython" # del PYTEST_HEADER_MODULES['h5py'] except (NameError, KeyError): # NameError is needed to support Astropy < 1.0 pass @@ -105,40 +109,50 @@ try: from .version import version except ImportError: - version = 'dev' + version = "dev" try: packagename = os.path.basename(os.path.dirname(__file__)) TESTED_VERSIONS[packagename] = version -except NameError: # Needed to support Astropy <= 1.0.0 +except NameError: # Needed to support Astropy <= 1.0.0 pass - # ------------------------------------------------------------------------- # Initialization # ------------------------------------------------------------------------- def pytest_addoption(parser): - parser.addoption("--tardis-refdata", default=None, - help="Path to Tardis Reference Folder") - parser.addoption("--integration-tests", - dest="integration-tests", default=None, - help="path to configuration file for integration tests") - parser.addoption("--generate-reference", - action="store_true", default=False, - help="generate reference data instead of testing") - parser.addoption("--less-packets", - action="store_true", default=False, - help="Run integration tests with less packets.") + parser.addoption( + "--tardis-refdata", default=None, help="Path to Tardis Reference Folder" + ) + parser.addoption( + "--integration-tests", + dest="integration-tests", + default=None, + help="path to configuration file for integration tests", + ) + parser.addoption( + "--generate-reference", + action="store_true", + default=False, + help="generate reference data instead of testing", + ) + parser.addoption( + "--less-packets", + action="store_true", + default=False, + help="Run integration tests with less packets.", + ) + # ------------------------------------------------------------------------- # project specific fixtures # ------------------------------------------------------------------------- -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def generate_reference(): option = pytest.config.getvalue("generate_reference") if option is None: @@ -151,32 +165,32 @@ def generate_reference(): def tardis_ref_path(): tardis_ref_path = pytest.config.getvalue("tardis_refdata") if tardis_ref_path is None: - pytest.skip('--tardis-refdata was not specified') + pytest.skip("--tardis-refdata was not specified") else: return os.path.expandvars(os.path.expanduser(tardis_ref_path)) + from tardis.tests.fixtures.atom_data import * + @pytest.yield_fixture(scope="session") def tardis_ref_data(tardis_ref_path, generate_reference): if generate_reference: - mode = 'w' + mode = "w" else: - mode = 'r' + mode = "r" with pd.HDFStore( - os.path.join( - tardis_ref_path, - 'unit_test_data.h5'), - mode=mode - ) as store: + os.path.join(tardis_ref_path, "unit_test_data.h5"), mode=mode + ) as store: yield store - @pytest.fixture def tardis_config_verysimple(): return yaml_load_config_file( - 'tardis/io/tests/data/tardis_configv1_verysimple.yml') + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) + ### # HDF Fixtures @@ -185,14 +199,14 @@ def tardis_config_verysimple(): @pytest.fixture(scope="session") def hdf_file_path(tmpdir_factory): - path = tmpdir_factory.mktemp('hdf_buffer').join('test.hdf') + path = tmpdir_factory.mktemp("hdf_buffer").join("test.hdf") return str(path) @pytest.fixture(scope="session") def config_verysimple(): - filename = 'tardis_configv1_verysimple.yml' - path = os.path.abspath(os.path.join('tardis/io/tests/data/', filename)) + filename = "tardis_configv1_verysimple.yml" + path = os.path.abspath(os.path.join("tardis/io/tests/data/", filename)) config = Configuration.from_yaml(path) return config diff --git a/tardis/constants.py b/tardis/constants.py index 269d599b5fc..c4926adee17 100644 --- a/tardis/constants.py +++ b/tardis/constants.py @@ -1 +1 @@ -from astropy.constants.astropyconst13 import * \ No newline at end of file +from astropy.constants.astropyconst13 import * diff --git a/tardis/gui/datahandler.py b/tardis/gui/datahandler.py index b7bc9a803a2..4e9798a2761 100644 --- a/tardis/gui/datahandler.py +++ b/tardis/gui/datahandler.py @@ -6,30 +6,33 @@ import matplotlib.pylab as plt -if os.environ.get('QT_API', None)=='pyqt': +if os.environ.get("QT_API", None) == "pyqt": from PyQt5 import QtGui, QtCore, QtWidgets -elif os.environ.get('QT_API', None)=='pyside': +elif os.environ.get("QT_API", None) == "pyside": from PySide2 import QtGui, QtCore, QtWidgets else: - raise ImportError('QT_API was not set! Please exit the IPython console\n' - ' and at the bash prompt use : \n\n export QT_API=pyside \n or\n' - ' export QT_API=pyqt \n\n For more information refer to user guide.') + raise ImportError( + "QT_API was not set! Please exit the IPython console\n" + " and at the bash prompt use : \n\n export QT_API=pyside \n or\n" + " export QT_API=pyqt \n\n For more information refer to user guide." + ) import yaml from tardis import run_tardis from tardis.gui.widgets import MatplotlibWidget, ModelViewer, ShellInfo from tardis.gui.widgets import LineInfo, LineInteractionTables -if (parse_version(matplotlib.__version__) >= parse_version('1.4')): - matplotlib.style.use('fivethirtyeight') +if parse_version(matplotlib.__version__) >= parse_version("1.4"): + matplotlib.style.use("fivethirtyeight") else: print("Please upgrade matplotlib to a version >=1.4 for best results!") -matplotlib.rcParams['font.family'] = 'serif' -matplotlib.rcParams['font.size'] = 10.0 -matplotlib.rcParams['lines.linewidth'] = 1.0 -matplotlib.rcParams['axes.formatter.use_mathtext'] = True -matplotlib.rcParams['axes.edgecolor'] = matplotlib.rcParams['grid.color'] -matplotlib.rcParams['axes.linewidth'] = matplotlib.rcParams['grid.linewidth'] +matplotlib.rcParams["font.family"] = "serif" +matplotlib.rcParams["font.size"] = 10.0 +matplotlib.rcParams["lines.linewidth"] = 1.0 +matplotlib.rcParams["axes.formatter.use_mathtext"] = True +matplotlib.rcParams["axes.edgecolor"] = matplotlib.rcParams["grid.color"] +matplotlib.rcParams["axes.linewidth"] = matplotlib.rcParams["grid.linewidth"] + class Node(object): """Object that serves as the nodes in the TreeModel. @@ -93,8 +96,8 @@ def __init__(self, data, parent=None): self.parent = parent self.children = [] self.data = data - self.siblings = {} #For 'type' fields. Will store the nodes to - #enable disable on selection + self.siblings = {} # For 'type' fields. Will store the nodes to + # enable disable on selection def append_child(self, child): """Add a child to this node.""" @@ -159,6 +162,7 @@ def set_data(self, column, value): return True + class TreeModel(QtCore.QAbstractItemModel): """The class that defines the tree for ConfigEditor. @@ -174,6 +178,7 @@ class TreeModel(QtCore.QAbstractItemModel): nodes that have values that can be set from a list. """ + def __init__(self, dictionary, parent=None): """Create a tree of tardis configuration dictionary. @@ -192,7 +197,7 @@ def __init__(self, dictionary, parent=None): self.typenodes = [] self.dict_to_tree(dictionary, self.root) - #mandatory functions for subclasses + # mandatory functions for subclasses def columnCount(self, index): """Return the number of columns in the node pointed to by the given model index. @@ -222,15 +227,19 @@ def flags(self, index): return QtCore.Qt.NoItemFlags node = index.internalPointer() - if ((node.get_parent() in self.disabledNodes) or - (node in self.disabledNodes)): + if (node.get_parent() in self.disabledNodes) or ( + node in self.disabledNodes + ): return QtCore.Qt.NoItemFlags - if node.num_children()==0: - return (QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | - QtCore.Qt.ItemIsSelectable) + if node.num_children() == 0: + return ( + QtCore.Qt.ItemIsEditable + | QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable + ) - return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable def getItem(self, index): """Returns the node to which the model index is pointing. If the @@ -249,8 +258,10 @@ def headerData(self, section, orientation, role): be needed for QTreeView. """ - if (orientation == QtCore.Qt.Horizontal and - role == QtCore.Qt.DisplayRole): + if ( + orientation == QtCore.Qt.Horizontal + and role == QtCore.Qt.DisplayRole + ): return self.root.get_data(section) return None @@ -283,8 +294,9 @@ def insertRows(self, position, rows, parent=QtCore.QModelIndex()): """Insert rows in the tree model.""" parentItem = self.getItem(parent) self.beginInsertRows(parent, position, position + rows - 1) - success = parentItem.insertChildren(position, rows, - self.rootItem.columnCount()) + success = parentItem.insertChildren( + position, rows, self.rootItem.columnCount() + ) self.endInsertRows() return success @@ -327,8 +339,9 @@ def setData(self, index, value, role=QtCore.Qt.EditRole): self.dataChanged.emit(index, index) return result - def setHeaderData(self, section, orientation, value, - role=QtCore.Qt.EditRole): + def setHeaderData( + self, section, orientation, value, role=QtCore.Qt.EditRole + ): """Change header data. Unused in columnview.""" if role != QtCore.Qt.EditRole or orientation != QtCore.Qt.Horizontal: return False @@ -338,8 +351,8 @@ def setHeaderData(self, section, orientation, value, self.headerDataChanged.emit(orientation, section, section) return result - - #Custom functions + + # Custom functions def dict_to_tree(self, dictionary, root): """Create the tree and append siblings to nodes that need them. @@ -351,11 +364,11 @@ def dict_to_tree(self, dictionary, root): The root node of the tree. """ - #Construct tree with all nodes + # Construct tree with all nodes self.tree_from_node(dictionary, root) - #Append siblings to type nodes - for node in self.typenodes: #For every type node + # Append siblings to type nodes + for node in self.typenodes: # For every type node parent = node.get_parent() sibsdict = {} for i in range(parent.num_children()): @@ -363,8 +376,8 @@ def dict_to_tree(self, dictionary, root): typesleaf = node.get_child(0) for i in range(typesleaf.num_columns()): - sibstrings = typesleaf.get_data(i).split('|_:_|') - + sibstrings = typesleaf.get_data(i).split("|_:_|") + typesleaf.set_data(i, sibstrings[0]) sibslist = [] for j in range(1, len(sibstrings)): @@ -372,15 +385,14 @@ def dict_to_tree(self, dictionary, root): sibslist.append(sibsdict[sibstrings[j]]) typesleaf.siblings[sibstrings[0]] = sibslist - - #Then append siblings of current selection for all type nodes to - #disabled nodes - for i in range(1,typesleaf.num_columns()): + + # Then append siblings of current selection for all type nodes to + # disabled nodes + for i in range(1, typesleaf.num_columns()): key = typesleaf.get_data(i) for nd in typesleaf.siblings[key]: self.disabledNodes.append(nd) - def tree_from_node(self, dictionary, root): """Convert dictionary to tree. Called by dict_to_tree.""" for key in dictionary: @@ -390,15 +402,15 @@ def tree_from_node(self, dictionary, root): self.tree_from_node(dictionary[key], child) elif isinstance(dictionary[key], list): if isinstance(dictionary[key][1], list): - leaf = Node(dictionary[key][1]) + leaf = Node(dictionary[key][1]) else: leaf = Node([dictionary[key][1]]) child.append_child(leaf) - if key == 'type': + if key == "type": self.typenodes.append(child) - def dict_from_node(self, node): + def dict_from_node(self, node): """Take a node and convert the whole subtree rooted at it into a dictionary. @@ -412,7 +424,7 @@ def dict_from_node(self, node): else: dictionary[nd.get_data(0)] = self.dict_from_node(nd) return dictionary - elif len(children)==1: + elif len(children) == 1: return children[0].get_data(0) @@ -421,21 +433,24 @@ class TreeDelegate(QtWidgets.QStyledItemDelegate): TreeModel. """ + def __init__(self, parent=None): """Call the constructor of the superclass.""" QtWidgets.QStyledItemDelegate.__init__(self, parent) - - #Mandatory methods for subclassing + + # Mandatory methods for subclassing def createEditor(self, parent, option, index): """Create a lineEdit or combobox depending on the type of node.""" node = index.internalPointer() - if node.num_columns()>1: + if node.num_columns() > 1: combobox = QtGui.QComboBox(parent) - combobox.addItems([node.get_data(i) for i in range(node.num_columns())]) + combobox.addItems( + [node.get_data(i) for i in range(node.num_columns())] + ) combobox.setEditable(False) return combobox else: - editor = QtWidgets.QLineEdit(parent) + editor = QtWidgets.QLineEdit(parent) editor.setText(str(node.get_data(0))) editor.returnPressed.connect(self.close_and_commit) return editor @@ -448,13 +463,13 @@ def setModelData(self, editor, model, index): """ node = index.internalPointer() - if node.num_columns() > 1 and node.get_parent().get_data(0) != 'type': + if node.num_columns() > 1 and node.get_parent().get_data(0) != "type": selectedIndex = editor.currentIndex() firstItem = node.get_data(0) node.setData(0, str(editor.currentText())) node.setData(selectedIndex, str(firstItem)) - elif node.num_columns() > 1 and node.get_parent().get_data(0) == 'type': + elif node.num_columns() > 1 and node.get_parent().get_data(0) == "type": selectedIndex = editor.currentIndex() firstItem = node.get_data(0) node.setData(0, str(editor.currentText())) @@ -468,26 +483,37 @@ def setModelData(self, editor, model, index): for nd in itemsToEnable: if nd in model.disabledNodes: - model.disabledNodes.remove(nd) + model.disabledNodes.remove(nd) - elif isinstance(editor, QtWidgets.QLineEdit): + elif isinstance(editor, QtWidgets.QLineEdit): node.setData(0, str(editor.text())) else: - QtWidgets.QStyledItemDelegate.setModelData(self, editor, model, index) - - #Custom methods + QtWidgets.QStyledItemDelegate.setModelData( + self, editor, model, index + ) + + # Custom methods def close_and_commit(self): """Saver for the line edits.""" editor = self.sender() if isinstance(editor, QtWidgets.QLineEdit): self.commitData.emit(editor) - self.closeEditor.emit(editor, QtWidgets.QAbstractItemDelegate.NoHint) + self.closeEditor.emit( + editor, QtWidgets.QAbstractItemDelegate.NoHint + ) + class SimpleTableModel(QtCore.QAbstractTableModel): """Create a table data structure for the table widgets.""" - - def __init__(self, headerdata=None, iterate_header=(0, 0), - index_info=None, parent=None, *args): + + def __init__( + self, + headerdata=None, + iterate_header=(0, 0), + index_info=None, + parent=None, + *args, + ): """Call constructor of the QAbstractTableModel and set parameters given by user. """ @@ -496,8 +522,8 @@ def __init__(self, headerdata=None, iterate_header=(0, 0), self.arraydata = [] self.iterate_header = iterate_header self.index_info = index_info - - #Implementing methods mandatory for subclassing QAbstractTableModel + + # Implementing methods mandatory for subclassing QAbstractTableModel def rowCount(self, parent=QtCore.QModelIndex()): """Return number of rows.""" return len(self.arraydata[0]) @@ -518,7 +544,10 @@ def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): return self.headerdata[0][0] + str(section + 1) else: return self.headerdata[0][section] - elif orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole: + elif ( + orientation == QtCore.Qt.Horizontal + and role == QtCore.Qt.DisplayRole + ): if self.iterate_header[1] == 1: return self.headerdata[1][0] + str(section + 1) elif self.iterate_header[1] == 2: @@ -534,7 +563,7 @@ def data(self, index, role=QtCore.Qt.DisplayRole): return None elif role != QtCore.Qt.DisplayRole: return None - return (self.arraydata[index.column()][index.row()]) + return self.arraydata[index.column()][index.row()] def setData(self, index, value, role=QtCore.Qt.EditRole): """Change the data in the model for specified index and role @@ -544,19 +573,21 @@ def setData(self, index, value, role=QtCore.Qt.EditRole): elif role != QtCore.Qt.EditRole: return False self.arraydata[index.column()][index.row()] = value - - self.dataChanged=QtCore.Signal(QtGui.QModelIndex(),QtGui.QModelIndex()) + + self.dataChanged = QtCore.Signal( + QtGui.QModelIndex(), QtGui.QModelIndex() + ) self.dataChanged.emit(index, index) return True - #Methods used to inderact with the SimpleTableModel + # Methods used to inderact with the SimpleTableModel def update_table(self): """Update table to set all the new data.""" for r in range(self.rowCount()): for c in range(self.columnCount()): index = self.createIndex(r, c) self.setData(index, self.arraydata[c][r]) - + def add_data(self, datain): """Add data to the model.""" self.arraydata.append(datain) diff --git a/tardis/gui/interface.py b/tardis/gui/interface.py index a79d0cb555c..099cee8a9ed 100644 --- a/tardis/gui/interface.py +++ b/tardis/gui/interface.py @@ -1,24 +1,30 @@ import os -if os.environ.get('QT_API', None)=='pyqt': + +if os.environ.get("QT_API", None) == "pyqt": from PyQt5 import QtCore, QtWidgets -elif os.environ.get('QT_API', None)=='pyside': - from PySide2 import QtCore,QtWidgets +elif os.environ.get("QT_API", None) == "pyside": + from PySide2 import QtCore, QtWidgets else: - raise ImportError('QT_API was not set! Please exit the IPython console\n' - ' and at the bash prompt use : \n\n export QT_API=pyside \n or\n' - ' export QT_API=pyqt \n\n For more information refer to user guide.') + raise ImportError( + "QT_API was not set! Please exit the IPython console\n" + " and at the bash prompt use : \n\n export QT_API=pyside \n or\n" + " export QT_API=pyqt \n\n For more information refer to user guide." + ) import sys + try: from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 from IPython.lib.guisupport import is_event_loop_running_qt4 - importFailed = False + + importFailed = False except ImportError: importFailed = True -from tardis.gui.widgets import Tardis +from tardis.gui.widgets import Tardis from tardis.gui.datahandler import SimpleTableModel from tardis import run_tardis - + + def show(model): """Take an instance of tardis model and display it. @@ -47,14 +53,15 @@ def show(model): else: start_event_loop_qt4(app) - #If the IPython console is being used, this will evaluate to true. - #In that case the window created will be garbage collected unless a - #reference to it is maintained after this function exits. So the win is - #returned. + # If the IPython console is being used, this will evaluate to true. + # In that case the window created will be garbage collected unless a + # reference to it is maintained after this function exits. So the win is + # returned. if is_event_loop_running_qt4(app): return win -if __name__=='__main__': + +if __name__ == "__main__": """When this module is executed as script, take arguments, calculate model and call the show function. @@ -62,4 +69,4 @@ def show(model): yamlfile = sys.argv[1] atomfile = sys.argv[2] mdl = run_tardis(yamlfile, atomfile) - show(mdl) + show(mdl) diff --git a/tardis/gui/tests/test_gui.py b/tardis/gui/tests/test_gui.py index 7aca7b0e8b2..208ec22ac4d 100644 --- a/tardis/gui/tests/test_gui.py +++ b/tardis/gui/tests/test_gui.py @@ -4,32 +4,30 @@ from tardis.simulation import Simulation import astropy.units as u from tardis.gui import interface -from tardis.gui.widgets import Tardis +from tardis.gui.widgets import Tardis from tardis.gui.datahandler import SimpleTableModel from PyQt5 import QtWidgets - - - -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def refdata(tardis_ref_data): def get_ref_data(key): - return tardis_ref_data[os.path.join( - 'test_simulation', key)] + return tardis_ref_data[os.path.join("test_simulation", key)] + return get_ref_data -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def config(): return Configuration.from_yaml( - 'tardis/io/tests/data/tardis_configv1_verysimple.yml') + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def simulation_one_loop( - atomic_data_fname, config, - tardis_ref_data, generate_reference): + atomic_data_fname, config, tardis_ref_data, generate_reference +): config.atom_data = atomic_data_fname config.montecarlo.iterations = 2 config.montecarlo.no_of_packets = int(4e4) @@ -40,6 +38,7 @@ def simulation_one_loop( return simulation + def test_gui(simulation_one_loop): simulation = simulation_one_loop app = QtWidgets.QApplication([]) diff --git a/tardis/gui/widgets.py b/tardis/gui/widgets.py index bde5acb7110..bfc9275a7c2 100644 --- a/tardis/gui/widgets.py +++ b/tardis/gui/widgets.py @@ -2,20 +2,24 @@ import tardis.util.base -if os.environ.get('QT_API', None)=='pyqt': +if os.environ.get("QT_API", None) == "pyqt": from PyQt5 import QtGui, QtCore, QtWidgets -elif os.environ.get('QT_API', None)=='pyside': - from PySide2 import QtGui, QtCore,QtWidgets +elif os.environ.get("QT_API", None) == "pyside": + from PySide2 import QtGui, QtCore, QtWidgets else: - raise ImportError('QT_API was not set! Please exit the IPython console\n' - ' and at the bash prompt use : \n\n export QT_API=pyside \n or\n' - ' export QT_API=pyqt \n\n For more information refer to user guide.') + raise ImportError( + "QT_API was not set! Please exit the IPython console\n" + " and at the bash prompt use : \n\n export QT_API=pyside \n or\n" + " export QT_API=pyqt \n\n For more information refer to user guide." + ) import matplotlib from matplotlib.figure import * import matplotlib.gridspec as gridspec from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4 import NavigationToolbar2QT as NavigationToolbar +from matplotlib.backends.backend_qt4 import ( + NavigationToolbar2QT as NavigationToolbar, +) from matplotlib import colors from matplotlib.patches import Circle import matplotlib.pylab as plt @@ -24,6 +28,7 @@ import tardis from tardis import analysis, util + class MatplotlibWidget(FigureCanvas): """Canvas to draw graphs on.""" @@ -35,67 +40,80 @@ def __init__(self, tablecreator, parent, fig=None): self.tablecreator = tablecreator self.parent = parent - self.figure = Figure()#(frameon=False,facecolor=(1,1,1)) + self.figure = Figure() # (frameon=False,facecolor=(1,1,1)) self.cid = {} - if fig != 'model': + if fig != "model": self.ax = self.figure.add_subplot(111) else: self.gs = gridspec.GridSpec(2, 1, height_ratios=[1, 3]) self.ax1 = self.figure.add_subplot(self.gs[0]) - self.ax2 = self.figure.add_subplot(self.gs[1])#, aspect='equal') + self.ax2 = self.figure.add_subplot(self.gs[1]) # , aspect='equal') self.cb = None self.span = None super(MatplotlibWidget, self).__init__(self.figure) - super(MatplotlibWidget, self).setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) + super(MatplotlibWidget, self).setSizePolicy( + QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding + ) super(MatplotlibWidget, self).updateGeometry() - if fig != 'model': + if fig != "model": self.toolbar = NavigationToolbar(self, parent) - self.cid[0] = self.figure.canvas.mpl_connect('pick_event', - self.on_span_pick) + self.cid[0] = self.figure.canvas.mpl_connect( + "pick_event", self.on_span_pick + ) else: - self.cid[0] = self.figure.canvas.mpl_connect('pick_event', - self.on_shell_pick) + self.cid[0] = self.figure.canvas.mpl_connect( + "pick_event", self.on_shell_pick + ) def show_line_info(self): """Show line info for span selected region.""" - self.parent.line_info.append(LineInfo(self.parent, self.span.xy[0][0], - self.span.xy[2][0], self.tablecreator)) + self.parent.line_info.append( + LineInfo( + self.parent, + self.span.xy[0][0], + self.span.xy[2][0], + self.tablecreator, + ) + ) def show_span(self, garbage=0, left=5000, right=10000): """Hide/Show/Change the buttons that show line info in spectrum plot widget. """ - if self.parent.spectrum_span_button.text() == 'Show Wavelength Range': + if self.parent.spectrum_span_button.text() == "Show Wavelength Range": if not self.span: - self.span = self.ax.axvspan(left, right, color='r', alpha=0.3, - picker=self.span_picker) + self.span = self.ax.axvspan( + left, right, color="r", alpha=0.3, picker=self.span_picker + ) else: self.span.set_visible(True) self.parent.spectrum_line_info_button.show() - self.parent.spectrum_span_button.setText('Hide Wavelength Range') + self.parent.spectrum_span_button.setText("Hide Wavelength Range") else: self.span.set_visible(False) self.parent.spectrum_line_info_button.hide() - self.parent.spectrum_span_button.setText('Show Wavelength Range') + self.parent.spectrum_span_button.setText("Show Wavelength Range") self.draw() def on_span_pick(self, event): """Callback to 'pick'(grab with mouse) the span selector tool.""" self.figure.canvas.mpl_disconnect(self.cid[0]) - self.span.set_edgecolor('m') + self.span.set_edgecolor("m") self.span.set_linewidth(5) self.draw() - if event.edge == 'left': - self.cid[1] = self.figure.canvas.mpl_connect('motion_notify_event', - self.on_span_left_motion) - elif event.edge == 'right': - self.cid[1] = self.figure.canvas.mpl_connect('motion_notify_event', - self.on_span_right_motion) - self.cid[2] = self.figure.canvas.mpl_connect('button_press_event', - self.on_span_resized) + if event.edge == "left": + self.cid[1] = self.figure.canvas.mpl_connect( + "motion_notify_event", self.on_span_left_motion + ) + elif event.edge == "right": + self.cid[1] = self.figure.canvas.mpl_connect( + "motion_notify_event", self.on_span_right_motion + ) + self.cid[2] = self.figure.canvas.mpl_connect( + "button_press_event", self.on_span_resized + ) def on_span_left_motion(self, mouseevent): """Update data of span selector tool on left movement of mouse and @@ -122,9 +140,10 @@ def on_span_resized(self, mouseevent): """Redraw the red rectangle to currently selected span.""" self.figure.canvas.mpl_disconnect(self.cid[1]) self.figure.canvas.mpl_disconnect(self.cid[2]) - self.cid[0] = self.figure.canvas.mpl_connect('pick_event', - self.on_span_pick) - self.span.set_edgecolor('r') + self.cid[0] = self.figure.canvas.mpl_connect( + "pick_event", self.on_span_pick + ) + self.span.set_edgecolor("r") self.span.set_linewidth(1) self.draw() @@ -137,9 +156,9 @@ def highlight_shell(self, index): self.parent.tableview.selectRow(index) for i in range(len(self.parent.shells)): if i != index and i != index + 1: - self.parent.shells[i].set_edgecolor('k') + self.parent.shells[i].set_edgecolor("k") else: - self.parent.shells[i].set_edgecolor('w') + self.parent.shells[i].set_edgecolor("w") self.draw() def shell_picker(self, shell, mouseevent): @@ -158,33 +177,39 @@ def span_picker(self, span, mouseevent, tolerance=5): """ left = float(span.xy[0][0]) right = float(span.xy[2][0]) - tolerance = span.axes.transData.inverted().transform((tolerance, 0) - )[0] - span.axes.transData.inverted().transform((0, 0))[0] - event_attributes = {'edge': None} + tolerance = ( + span.axes.transData.inverted().transform((tolerance, 0))[0] + - span.axes.transData.inverted().transform((0, 0))[0] + ) + event_attributes = {"edge": None} if mouseevent.xdata is None: return False, event_attributes if left - tolerance <= mouseevent.xdata <= left + tolerance: - event_attributes['edge'] = 'left' + event_attributes["edge"] = "left" return True, event_attributes elif right - tolerance <= mouseevent.xdata <= right + tolerance: - event_attributes['edge'] = 'right' + event_attributes["edge"] = "right" return True, event_attributes return False, event_attributes + class Shell(matplotlib.patches.Wedge): """A data holder to store measurements of shells that will be drawn in the plot. """ + def __init__(self, index, center, r_inner, r_outer, **kwargs): - super(Shell, self).__init__(center, r_outer, 0, 90, - width=r_outer - r_inner, **kwargs) + super(Shell, self).__init__( + center, r_outer, 0, 90, width=r_outer - r_inner, **kwargs + ) self.index = index self.center = center self.r_outer = r_outer self.r_inner = r_inner self.width = r_outer - r_inner + class ConfigEditor(QtWidgets.QWidget): """The configuration editor widget. @@ -206,120 +231,151 @@ def __init__(self, yamlconfigfile, parent=None): """ super(ConfigEditor, self).__init__(parent) - #Configurations from the input and template + # Configurations from the input and template configDict = yaml.load(open(yamlconfigfile), Loader=yaml.CLoader) - templatedictionary ={'tardis_config_version':[True, 'v1.0'], - 'supernova':{ 'luminosity_requested':[True, '1 solLum'], - 'time_explosion':[True, None], - 'distance':[False, None], - 'luminosity_wavelength_start':[False, '0 angstrom'], - 'luminosity_wavelength_end':[False, 'inf angstrom'], - }, - 'atom_data':[True,'File Browser'], - 'plasma':{ 'initial_t_inner':[False, '-1K'], - 'initial_t_rad':[False,'10000K'], - 'disable_electron_scattering':[False, False], - 'ionization':[True, None], - 'excitation':[True, None], - 'radiative_rates_type':[True, None], - 'line_interaction_type':[True, None], - 'w_epsilon':[False, 1e-10], - 'delta_treatment':[False, None], - 'nlte':{ 'species':[False, []], - 'coronal_approximation':[False, False], - 'classical_nebular':[False, False] - } - }, - 'model':{ 'structure':{'type':[True, ['file|_:_|filename|_:_|' - 'filetype|_:_|v_inner_boundary|_:_|v_outer_boundary', - 'specific|_:_|velocity|_:_|density']], - 'filename':[True, None], - 'filetype':[True, None], - 'v_inner_boundary':[False, '0 km/s'], - 'v_outer_boundary':[False, 'inf km/s'], - 'velocity':[True, None], - 'density':{ 'type':[True, ['branch85_w7|_:_|w7_time_0' - '|_:_|w7_time_0|_:_|w7_time_0', - 'exponential|_:_|time_0|_:_|rho_0|_:_|' - 'v_0','power_law|_:_|time_0|_:_|rho_0' - '|_:_|v_0|_:_|exponent','uniform|_:_|value']], - 'w7_time_0':[False, '0.000231481 day'], - 'w7_rho_0':[False, '3e29 g/cm^3'], - 'w7_v_0': [False, '1 km/s'], - 'time_0':[True, None], - 'rho_0':[True, None], - 'v_0': [True, None], - 'exponent': [True, None], - 'value':[True, None] - } - }, - 'abundances':{ 'type':[True, ['file|_:_|filetype|_:_|' - 'filename', 'uniform']], - 'filename':[True, None], - 'filetype':[False, None] - } + templatedictionary = { + "tardis_config_version": [True, "v1.0"], + "supernova": { + "luminosity_requested": [True, "1 solLum"], + "time_explosion": [True, None], + "distance": [False, None], + "luminosity_wavelength_start": [False, "0 angstrom"], + "luminosity_wavelength_end": [False, "inf angstrom"], + }, + "atom_data": [True, "File Browser"], + "plasma": { + "initial_t_inner": [False, "-1K"], + "initial_t_rad": [False, "10000K"], + "disable_electron_scattering": [False, False], + "ionization": [True, None], + "excitation": [True, None], + "radiative_rates_type": [True, None], + "line_interaction_type": [True, None], + "w_epsilon": [False, 1e-10], + "delta_treatment": [False, None], + "nlte": { + "species": [False, []], + "coronal_approximation": [False, False], + "classical_nebular": [False, False], + }, + }, + "model": { + "structure": { + "type": [ + True, + [ + "file|_:_|filename|_:_|" + "filetype|_:_|v_inner_boundary|_:_|v_outer_boundary", + "specific|_:_|velocity|_:_|density", + ], + ], + "filename": [True, None], + "filetype": [True, None], + "v_inner_boundary": [False, "0 km/s"], + "v_outer_boundary": [False, "inf km/s"], + "velocity": [True, None], + "density": { + "type": [ + True, + [ + "branch85_w7|_:_|w7_time_0" + "|_:_|w7_time_0|_:_|w7_time_0", + "exponential|_:_|time_0|_:_|rho_0|_:_|" "v_0", + "power_law|_:_|time_0|_:_|rho_0" + "|_:_|v_0|_:_|exponent", + "uniform|_:_|value", + ], + ], + "w7_time_0": [False, "0.000231481 day"], + "w7_rho_0": [False, "3e29 g/cm^3"], + "w7_v_0": [False, "1 km/s"], + "time_0": [True, None], + "rho_0": [True, None], + "v_0": [True, None], + "exponent": [True, None], + "value": [True, None], + }, + }, + "abundances": { + "type": [ + True, + ["file|_:_|filetype|_:_|" "filename", "uniform"], + ], + "filename": [True, None], + "filetype": [False, None], + }, + }, + "montecarlo": { + "seed": [False, 23111963], + "no_of_packets": [True, None], + "iterations": [True, None], + "black_body_sampling": { + "start": "1 angstrom", + "stop": "1000000 angstrom", + "num": "1.e+6", + }, + "last_no_of_packets": [False, -1], + "no_of_virtual_packets": [False, 0], + "enable_reflective_inner_boundary": [False, False], + "inner_boundary_albedo": [False, 0.0], + "convergence_strategy": { + "type": [ + True, + [ + "damped|_:_|damping_constant|_:_|t_inner|_:_|" + "t_rad|_:_|w|_:_|lock_t_inner_cycles|_:_|" + "t_inner_update_exponent", + "specific|_:_|threshold" + "|_:_|fraction|_:_|hold_iterations|_:_|t_inner" + "|_:_|t_rad|_:_|w|_:_|lock_t_inner_cycles|_:_|" + "damping_constant|_:_|t_inner_update_exponent", + ], + ], + "t_inner_update_exponent": [False, -0.5], + "lock_t_inner_cycles": [False, 1], + "hold_iterations": [True, 3], + "fraction": [True, 0.8], + "damping_constant": [False, 0.5], + "threshold": [True, None], + "t_inner": { + "damping_constant": [False, 0.5], + "threshold": [False, None], + }, + "t_rad": { + "damping_constant": [False, 0.5], + "threshold": [True, None], + }, + "w": { + "damping_constant": [False, 0.5], + "threshold": [True, None], }, - 'montecarlo':{'seed':[False, 23111963], - 'no_of_packets':[True, None], - 'iterations':[True, None], - 'black_body_sampling':{ - 'start': '1 angstrom', - 'stop': '1000000 angstrom', - 'num': '1.e+6', - }, - 'last_no_of_packets':[False, -1], - 'no_of_virtual_packets':[False, 0], - 'enable_reflective_inner_boundary':[False, False], - 'inner_boundary_albedo':[False, 0.0], - 'convergence_strategy':{ 'type':[True, - ['damped|_:_|damping_constant|_:_|t_inner|_:_|' - 't_rad|_:_|w|_:_|lock_t_inner_cycles|_:_|' - 't_inner_update_exponent','specific|_:_|threshold' - '|_:_|fraction|_:_|hold_iterations|_:_|t_inner' - '|_:_|t_rad|_:_|w|_:_|lock_t_inner_cycles|_:_|' - 'damping_constant|_:_|t_inner_update_exponent']], - 't_inner_update_exponent':[False, -0.5], - 'lock_t_inner_cycles':[False, 1], - 'hold_iterations':[True, 3], - 'fraction':[True, 0.8], - 'damping_constant':[False, 0.5], - 'threshold':[True, None], - 't_inner':{ 'damping_constant':[False, 0.5], - 'threshold': [False, None] - }, - 't_rad':{'damping_constant':[False, 0.5], - 'threshold':[True, None] - }, - 'w':{'damping_constant': [False, 0.5], - 'threshold': [True, None] - } - } - }, - 'spectrum':[True, None] - } + }, + }, + "spectrum": [True, None], + } self.match_dicts(configDict, templatedictionary) self.layout = QtWidgets.QVBoxLayout() - #Make tree + # Make tree self.trmodel = TreeModel(templatedictionary) self.colView = QtWidgets.QColumnView() self.colView.setModel(self.trmodel) - #Five columns of width 256 each can be visible at once - self.colView.setFixedWidth(256*5) + # Five columns of width 256 each can be visible at once + self.colView.setFixedWidth(256 * 5) self.colView.setItemDelegate(TreeDelegate(self)) self.layout.addWidget(self.colView) - #Recalculate button - button = QtWidgets.QPushButton('Recalculate') + # Recalculate button + button = QtWidgets.QPushButton("Recalculate") button.setFixedWidth(90) self.layout.addWidget(button) button.clicked.connect(self.recalculate) - #Finally put them all in + # Finally put them all in self.setLayout(self.layout) - def match_dicts(self, dict1, dict2): #dict1<=dict2 + def match_dicts(self, dict1, dict2): # dict1<=dict2 """Compare and combine two dictionaries. If there are new keys in `dict1` then they are appended to `dict2`. @@ -357,13 +413,14 @@ def match_dicts(self, dict1, dict2): #dict1<=dict2 elif isinstance(dict2[key], list): if isinstance(dict2[key][1], list): - #options = dict2[key][1] #This is passed by reference. - #So copy the list manually. - options = [dict2[key][1][i] for i in range( - len(dict2[key][1]))] + # options = dict2[key][1] #This is passed by reference. + # So copy the list manually. + options = [ + dict2[key][1][i] for i in range(len(dict2[key][1])) + ] for i in range(len(options)): - options[i] = options[i].split('|_:_|')[0] + options[i] = options[i].split("|_:_|")[0] optionselected = dict1[key] @@ -374,13 +431,14 @@ def match_dicts(self, dict1, dict2): #dict1<=dict2 dict2[key][1][0] = dict2[key][1][indexofselected] dict2[key][1][indexofselected] = temp - else: - print('The selected and available options') + print("The selected and available options") print(optionselected) print(options) - raise IOError("An invalid option was" - " provided in the input file") + raise IOError( + "An invalid option was" + " provided in the input file" + ) else: dict2[key] = dict1[key] @@ -394,6 +452,7 @@ def recalculate(self): """ pass + class ModelViewer(QtWidgets.QWidget): """The widget that holds all the plots and tables that visualize the data in the tardis model. This is also appended to the stacked @@ -405,49 +464,55 @@ def __init__(self, tablecreator, parent=None): """Create all widgets that are children of ModelViewer.""" QtWidgets.QWidget.__init__(self, parent) - #Data structures + # Data structures self.model = None self.shell_info = {} self.line_info = [] - #functions + # functions self.createTable = tablecreator - #Shells widget + # Shells widget self.shellWidget = self.make_shell_widget() - #Spectrum widget + # Spectrum widget self.spectrumWidget = self.make_spectrum_widget() - #Plot tab widget + # Plot tab widget self.plotTabWidget = QtWidgets.QTabWidget() - self.plotTabWidget.addTab(self.shellWidget,"&Shells") + self.plotTabWidget.addTab(self.shellWidget, "&Shells") self.plotTabWidget.addTab(self.spectrumWidget, "S&pectrum") - #Table widget - self.tablemodel = self.createTable([['Shell: '], ["Rad. temp", "Ws", "V"]], - (1, 0)) + # Table widget + self.tablemodel = self.createTable( + [["Shell: "], ["Rad. temp", "Ws", "V"]], (1, 0) + ) self.tableview = QtWidgets.QTableView() self.tableview.setMinimumWidth(200) - self.sectionClicked=QtCore.Signal(int) - self.tableview.verticalHeader().sectionClicked.connect(self.graph.highlight_shell) + self.sectionClicked = QtCore.Signal(int) + self.tableview.verticalHeader().sectionClicked.connect( + self.graph.highlight_shell + ) - self.sectionDoubleClicked=QtCore.Signal(int) - self.tableview.verticalHeader().sectionDoubleClicked.connect(self.on_header_double_clicked) + self.sectionDoubleClicked = QtCore.Signal(int) + self.tableview.verticalHeader().sectionDoubleClicked.connect( + self.on_header_double_clicked + ) - #Label for text output + # Label for text output self.outputLabel = QtWidgets.QLabel() - self.outputLabel.setFrameStyle(QtWidgets.QFrame.StyledPanel | - QtWidgets.QFrame.Sunken) + self.outputLabel.setFrameStyle( + QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Sunken + ) self.outputLabel.setStyleSheet("QLabel{background-color:white;}") - #Group boxes + # Group boxes graphsBox = QtWidgets.QGroupBox("Visualized results") textsBox = QtWidgets.QGroupBox("Model parameters") tableBox = QtWidgets.QGroupBox("Tabulated results") - #For textbox + # For textbox textlayout = QtWidgets.QHBoxLayout() textlayout.addWidget(self.outputLabel) @@ -474,19 +539,20 @@ def fill_output_label(self): quick user access. """ - labeltext = 'Iterations requested: {}
Iterations executed: {}
\ + labeltext = "Iterations requested: {}
Iterations executed: {}
\ Model converged : {}
Simulation Time : {} s
\ Inner Temperature : {} K
Number of packets : {}
\ - Inner Luminosity : {}'\ - .format(self.model.iterations, - self.model.iterations_executed, - 'True' if - self.model.converged else - 'False', - self.model.runner.time_of_simulation.value, - self.model.model.t_inner.value, - self.model.last_no_of_packets, - self.model.runner.calculate_luminosity_inner(self.model.model)) + Inner Luminosity : {}".format( + self.model.iterations, + self.model.iterations_executed, + 'True' + if self.model.converged + else 'False', + self.model.runner.time_of_simulation.value, + self.model.model.t_inner.value, + self.model.last_no_of_packets, + self.model.runner.calculate_luminosity_inner(self.model.model), + ) self.outputLabel.setText(labeltext) def make_shell_widget(self): @@ -494,19 +560,21 @@ def make_shell_widget(self): container widget. Return the container widget. """ - #Widgets for plot of shells - self.graph = MatplotlibWidget(self.createTable, self, 'model') - self.graph_label = QtWidgets.QLabel('Select Property:') + # Widgets for plot of shells + self.graph = MatplotlibWidget(self.createTable, self, "model") + self.graph_label = QtWidgets.QLabel("Select Property:") self.graph_button = QtWidgets.QToolButton() - self.graph_button.setText('Rad. temp') + self.graph_button.setText("Rad. temp") self.graph_button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.graph_button.setMenu(QtWidgets.QMenu(self.graph_button)) - self.graph_button.menu().addAction('Rad. temp').triggered.connect( - self.change_graph_to_t_rads) - self.graph_button.menu().addAction('Ws').triggered.connect( - self.change_graph_to_ws) - - #Layouts: bottom up + self.graph_button.menu().addAction("Rad. temp").triggered.connect( + self.change_graph_to_t_rads + ) + self.graph_button.menu().addAction("Ws").triggered.connect( + self.change_graph_to_ws + ) + + # Layouts: bottom up self.graph_subsublayout = QtWidgets.QHBoxLayout() self.graph_subsublayout.addWidget(self.graph_label) self.graph_subsublayout.addWidget(self.graph_button) @@ -525,20 +593,26 @@ def make_spectrum_widget(self): """ self.spectrum = MatplotlibWidget(self.createTable, self) - self.spectrum_label = QtWidgets.QLabel('Select Spectrum:') + self.spectrum_label = QtWidgets.QLabel("Select Spectrum:") self.spectrum_button = QtWidgets.QToolButton() - self.spectrum_button.setText('spec_flux_angstrom') + self.spectrum_button.setText("spec_flux_angstrom") self.spectrum_button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.spectrum_button.setMenu(QtWidgets.QMenu(self.spectrum_button)) - self.spectrum_button.menu().addAction('spec_flux_angstrom' - ).triggered.connect(self.change_spectrum_to_spec_flux_angstrom) - self.spectrum_button.menu().addAction('spec_virtual_flux_angstrom' - ).triggered.connect(self.change_spectrum_to_spec_virtual_flux_angstrom) - self.spectrum_span_button = QtWidgets.QPushButton('Show Wavelength Range') + self.spectrum_button.menu().addAction( + "spec_flux_angstrom" + ).triggered.connect(self.change_spectrum_to_spec_flux_angstrom) + self.spectrum_button.menu().addAction( + "spec_virtual_flux_angstrom" + ).triggered.connect(self.change_spectrum_to_spec_virtual_flux_angstrom) + self.spectrum_span_button = QtWidgets.QPushButton( + "Show Wavelength Range" + ) self.spectrum_span_button.clicked.connect(self.spectrum.show_span) - self.spectrum_line_info_button = QtWidgets.QPushButton('Show Line Info') + self.spectrum_line_info_button = QtWidgets.QPushButton("Show Line Info") self.spectrum_line_info_button.hide() - self.spectrum_line_info_button.clicked.connect(self.spectrum.show_line_info) + self.spectrum_line_info_button.clicked.connect( + self.spectrum.show_line_info + ) self.spectrum_subsublayout = QtWidgets.QHBoxLayout() self.spectrum_subsublayout.addWidget(self.spectrum_span_button) @@ -563,10 +637,10 @@ def update_data(self, model=None): for index in self.shell_info.keys(): self.shell_info[index].update_tables() self.plot_model() - if self.graph_button.text == 'Ws': + if self.graph_button.text == "Ws": self.change_graph_to_ws() self.plot_spectrum() - if self.spectrum_button.text == 'spec_virtual_flux_angstrom': + if self.spectrum_button.text == "spec_virtual_flux_angstrom": self.change_spectrum_to_spec_virtual_flux_angstrom() self.show() @@ -582,24 +656,28 @@ def change_spectrum_to_spec_virtual_flux_angstrom(self): """Change the spectrum data to the virtual spectrum.""" if self.model.runner.spectrum_virtual.luminosity_density_lambda is None: luminosity_density_lambda = np.zeros_like( - self.model.runner.spectrum_virtual.wavelength) + self.model.runner.spectrum_virtual.wavelength + ) else: - luminosity_density_lambda = \ - self.model.runner.spectrum_virtual.luminosity_density_lambda.value + luminosity_density_lambda = ( + self.model.runner.spectrum_virtual.luminosity_density_lambda.value + ) - self.change_spectrum(luminosity_density_lambda, 'spec_flux_angstrom') + self.change_spectrum(luminosity_density_lambda, "spec_flux_angstrom") def change_spectrum_to_spec_flux_angstrom(self): """Change spectrum data back from virtual spectrum. (See the method above).""" if self.model.runner.spectrum.luminosity_density_lambda is None: luminosity_density_lambda = np.zeros_like( - self.model.runner.spectrum.wavelength) + self.model.runner.spectrum.wavelength + ) else: - luminosity_density_lambda = \ - self.model.runner.spectrum.luminosity_density_lambda.value + luminosity_density_lambda = ( + self.model.runner.spectrum.luminosity_density_lambda.value + ) - self.change_spectrum(luminosity_density_lambda, 'spec_flux_angstrom') + self.change_spectrum(luminosity_density_lambda, "spec_flux_angstrom") def change_spectrum(self, data, name): """Replot the spectrum plot using the data provided. Called @@ -615,27 +693,29 @@ def change_spectrum(self, data, name): def plot_spectrum(self): """Plot the spectrum and add labels to the graph.""" self.spectrum.ax.clear() - self.spectrum.ax.set_title('Spectrum') - self.spectrum.ax.set_xlabel('Wavelength (A)') - self.spectrum.ax.set_ylabel('Intensity') + self.spectrum.ax.set_title("Spectrum") + self.spectrum.ax.set_xlabel("Wavelength (A)") + self.spectrum.ax.set_ylabel("Intensity") wavelength = self.model.runner.spectrum.wavelength.value if self.model.runner.spectrum.luminosity_density_lambda is None: luminosity_density_lambda = np.zeros_like(wavelength) else: - luminosity_density_lambda =\ - self.model.runner.spectrum.luminosity_density_lambda.value + luminosity_density_lambda = ( + self.model.runner.spectrum.luminosity_density_lambda.value + ) - self.spectrum.dataplot = self.spectrum.ax.plot(wavelength, - luminosity_density_lambda, label='b') + self.spectrum.dataplot = self.spectrum.ax.plot( + wavelength, luminosity_density_lambda, label="b" + ) self.spectrum.draw() def change_graph_to_ws(self): """Change the shell plot to show dilution factor.""" - self.change_graph(self.model.model.w, 'Ws', '') + self.change_graph(self.model.model.w, "Ws", "") def change_graph_to_t_rads(self): """Change the graph back to radiation Temperature.""" - self.change_graph(self.model.model.t_rad.value, 't_rad', '(K)') + self.change_graph(self.model.model.t_rad.value, "t_rad", "(K)") def change_graph(self, data, name, unit): """Called to change the shell plot by the two methods above.""" @@ -643,15 +723,15 @@ def change_graph(self, data, name, unit): self.graph.dataplot[0].set_ydata(data) self.graph.ax1.relim() self.graph.ax1.autoscale() - self.graph.ax1.set_title(name + ' vs Shell') - self.graph.ax1.set_ylabel(name + ' ' + unit) + self.graph.ax1.set_title(name + " vs Shell") + self.graph.ax1.set_ylabel(name + " " + unit) normalizer = colors.Normalize(vmin=data.min(), vmax=data.max()) color_map = plt.cm.ScalarMappable(norm=normalizer, cmap=plt.cm.jet) color_map.set_array(data) self.graph.cb.set_clim(vmin=data.min(), vmax=data.max()) self.graph.cb.update_normal(color_map) - if unit == '(K)': - unit = 'T (K)' + if unit == "(K)": + unit = "T (K)" self.graph.cb.set_label(unit) for i, item in enumerate(data): self.shells[i].set_facecolor(color_map.to_rgba(item)) @@ -663,52 +743,76 @@ def plot_model(self): """ self.graph.ax1.clear() - self.graph.ax1.set_title('Rad. Temp vs Shell') - self.graph.ax1.set_xlabel('Shell Number') - self.graph.ax1.set_ylabel('Rad. Temp (K)') + self.graph.ax1.set_title("Rad. Temp vs Shell") + self.graph.ax1.set_xlabel("Shell Number") + self.graph.ax1.set_ylabel("Rad. Temp (K)") self.graph.ax1.yaxis.get_major_formatter().set_powerlimits((0, 1)) self.graph.dataplot = self.graph.ax1.plot( - range(len(self.model.model.t_rad.value)), self.model.model.t_rad.value) + range(len(self.model.model.t_rad.value)), + self.model.model.t_rad.value, + ) self.graph.ax2.clear() - self.graph.ax2.set_title('Shell View') + self.graph.ax2.set_title("Shell View") self.graph.ax2.set_xticklabels([]) self.graph.ax2.set_yticklabels([]) self.graph.ax2.grid = True self.shells = [] - t_rad_normalizer = colors.Normalize(vmin=self.model.model.t_rad.value.min(), - vmax=self.model.model.t_rad.value.max()) - t_rad_color_map = plt.cm.ScalarMappable(norm=t_rad_normalizer, - cmap=plt.cm.jet) + t_rad_normalizer = colors.Normalize( + vmin=self.model.model.t_rad.value.min(), + vmax=self.model.model.t_rad.value.max(), + ) + t_rad_color_map = plt.cm.ScalarMappable( + norm=t_rad_normalizer, cmap=plt.cm.jet + ) t_rad_color_map.set_array(self.model.model.t_rad.value) if self.graph.cb: - self.graph.cb.set_clim(vmin=self.model.model.t_rad.value.min(), - vmax=self.model.model.t_rad.value.max()) + self.graph.cb.set_clim( + vmin=self.model.model.t_rad.value.min(), + vmax=self.model.model.t_rad.value.max(), + ) self.graph.cb.update_normal(t_rad_color_map) else: self.graph.cb = self.graph.figure.colorbar(t_rad_color_map) - self.graph.cb.set_label('T (K)') - self.graph.normalizing_factor = 0.2 * ( - self.model.model.r_outer.value[-1] - - self.model.model.r_inner.value[0]) / ( - self.model.model.r_inner.value[0]) - - #self.graph.normalizing_factor = 8e-16 + self.graph.cb.set_label("T (K)") + self.graph.normalizing_factor = ( + 0.2 + * ( + self.model.model.r_outer.value[-1] + - self.model.model.r_inner.value[0] + ) + / (self.model.model.r_inner.value[0]) + ) + + # self.graph.normalizing_factor = 8e-16 for i, t_rad in enumerate(self.model.model.t_rad.value): - r_inner = (self.model.model.r_inner.value[i] * - self.graph.normalizing_factor) - r_outer = (self.model.model.r_outer.value[i] * - self.graph.normalizing_factor) - self.shells.append(Shell(i, (0,0), r_inner, r_outer, - facecolor=t_rad_color_map.to_rgba(t_rad), - picker=self.graph.shell_picker)) + r_inner = ( + self.model.model.r_inner.value[i] + * self.graph.normalizing_factor + ) + r_outer = ( + self.model.model.r_outer.value[i] + * self.graph.normalizing_factor + ) + self.shells.append( + Shell( + i, + (0, 0), + r_inner, + r_outer, + facecolor=t_rad_color_map.to_rgba(t_rad), + picker=self.graph.shell_picker, + ) + ) self.graph.ax2.add_patch(self.shells[i]) - self.graph.ax2.set_xlim(0, - self.model.model.r_outer.value[-1] * - self.graph.normalizing_factor) - self.graph.ax2.set_ylim(0, - self.model.model.r_outer.value[-1] * - self.graph.normalizing_factor) + self.graph.ax2.set_xlim( + 0, + self.model.model.r_outer.value[-1] * self.graph.normalizing_factor, + ) + self.graph.ax2.set_ylim( + 0, + self.model.model.r_outer.value[-1] * self.graph.normalizing_factor, + ) self.graph.figure.tight_layout() self.graph.draw() @@ -716,6 +820,7 @@ def on_header_double_clicked(self, index): """Callback to get counts for different Z from table.""" self.shell_info[index] = ShellInfo(index, self.createTable, self) + class ShellInfo(QtWidgets.QDialog): """Dialog to display Shell abundances.""" @@ -727,19 +832,21 @@ def __init__(self, index, tablecreator, parent=None): self.parent = parent self.shell_index = index self.setGeometry(400, 150, 200, 400) - self.setWindowTitle('Shell %d Abundances' % (self.shell_index + 1)) + self.setWindowTitle("Shell %d Abundances" % (self.shell_index + 1)) self.atomstable = QtWidgets.QTableView() self.ionstable = QtWidgets.QTableView() self.levelstable = QtWidgets.QTableView() - self.sectionClicked=QtCore.Signal(int) - self.atomstable.verticalHeader().sectionClicked.connect(self.on_atom_header_double_clicked) - - - self.table1_data = self.parent.model.plasma.abundance[ - self.shell_index] - self.atomsdata = self.createTable([['Z = '], ['Count (Shell %d)' % ( - self.shell_index + 1)]], iterate_header=(2, 0), - index_info=self.table1_data.index.values.tolist()) + self.sectionClicked = QtCore.Signal(int) + self.atomstable.verticalHeader().sectionClicked.connect( + self.on_atom_header_double_clicked + ) + + self.table1_data = self.parent.model.plasma.abundance[self.shell_index] + self.atomsdata = self.createTable( + [["Z = "], ["Count (Shell %d)" % (self.shell_index + 1)]], + iterate_header=(2, 0), + index_info=self.table1_data.index.values.tolist(), + ) self.ionsdata = None self.levelsdata = None self.atomsdata.add_data(self.table1_data.values.tolist()) @@ -759,22 +866,30 @@ def on_atom_header_double_clicked(self, index): ion populations.""" self.current_atom_index = self.table1_data.index.values.tolist()[index] self.table2_data = self.parent.model.plasma.ion_number_density[ - self.shell_index].ix[self.current_atom_index] - self.ionsdata = self.createTable([['Ion: '], - ['Count (Z = %d)' % self.current_atom_index]], + self.shell_index + ].ix[self.current_atom_index] + self.ionsdata = self.createTable( + [["Ion: "], ["Count (Z = %d)" % self.current_atom_index]], iterate_header=(2, 0), - index_info=self.table2_data.index.values.tolist()) + index_info=self.table2_data.index.values.tolist(), + ) normalized_data = [] for item in self.table2_data.values: - normalized_data.append(float(item / - self.parent.model.plasma.number_density[self.shell_index] - .ix[self.current_atom_index])) - + normalized_data.append( + float( + item + / self.parent.model.plasma.number_density[ + self.shell_index + ].ix[self.current_atom_index] + ) + ) self.ionsdata.add_data(normalized_data) self.ionstable.setModel(self.ionsdata) - self.sectionClicked=QtCore.Signal(int) - self.ionstable.verticalHeader().sectionClicked.connect(self.on_ion_header_double_clicked) + self.sectionClicked = QtCore.Signal(int) + self.ionstable.verticalHeader().sectionClicked.connect( + self.on_ion_header_double_clicked + ) self.levelstable.hide() self.ionstable.setColumnWidth(0, 120) self.ionstable.show() @@ -785,15 +900,18 @@ def on_ion_header_double_clicked(self, index): """Called on double click of ion headers to show level populations.""" self.current_ion_index = self.table2_data.index.values.tolist()[index] self.table3_data = self.parent.model.plasma.level_number_density[ - self.shell_index].ix[self.current_atom_index, self.current_ion_index] - self.levelsdata = self.createTable([['Level: '], - ['Count (Ion %d)' % self.current_ion_index]], + self.shell_index + ].ix[self.current_atom_index, self.current_ion_index] + self.levelsdata = self.createTable( + [["Level: "], ["Count (Ion %d)" % self.current_ion_index]], iterate_header=(2, 0), - index_info=self.table3_data.index.values.tolist()) + index_info=self.table3_data.index.values.tolist(), + ) normalized_data = [] for item in self.table3_data.values.tolist(): - normalized_data.append(float(item / - self.table2_data.ix[self.current_ion_index])) + normalized_data.append( + float(item / self.table2_data.ix[self.current_ion_index]) + ) self.levelsdata.add_data(normalized_data) self.levelstable.setModel(self.levelsdata) self.levelstable.setColumnWidth(0, 120) @@ -804,8 +922,9 @@ def on_ion_header_double_clicked(self, index): def update_tables(self): """Update table data for shell info viewer.""" self.table1_data = self.parent.model.plasma.number_density[ - self.shell_index] - self.atomsdata.index_info=self.table1_data.index.values.tolist() + self.shell_index + ] + self.atomsdata.index_info = self.table1_data.index.values.tolist() self.atomsdata.arraydata = [] self.atomsdata.add_data(self.table1_data.values.tolist()) self.atomsdata.update_table() @@ -814,8 +933,10 @@ def update_tables(self): self.setGeometry(400, 150, 200, 400) self.show() + class LineInfo(QtWidgets.QDialog): """Dialog to show the line info used by spectrum widget.""" + def __init__(self, parent, wavelength_start, wavelength_end, tablecreator): """Create the dialog and set data in it from the model. Show widget.""" @@ -823,28 +944,47 @@ def __init__(self, parent, wavelength_start, wavelength_end, tablecreator): self.createTable = tablecreator self.parent = parent self.setGeometry(180 + len(self.parent.line_info) * 20, 150, 250, 400) - self.setWindowTitle('Line Interaction: %.2f - %.2f (A) ' % ( - wavelength_start, wavelength_end,)) + self.setWindowTitle( + "Line Interaction: %.2f - %.2f (A) " + % (wavelength_start, wavelength_end,) + ) self.layout = QtWidgets.QVBoxLayout() packet_nu_line_interaction = analysis.LastLineInteraction.from_model( - self.parent.model) - packet_nu_line_interaction.packet_filter_mode = 'packet_nu' - packet_nu_line_interaction.wavelength_start = wavelength_start * u.angstrom + self.parent.model + ) + packet_nu_line_interaction.packet_filter_mode = "packet_nu" + packet_nu_line_interaction.wavelength_start = ( + wavelength_start * u.angstrom + ) packet_nu_line_interaction.wavelength_end = wavelength_end * u.angstrom line_in_nu_line_interaction = analysis.LastLineInteraction.from_model( - self.parent.model) - line_in_nu_line_interaction.packet_filter_mode = 'line_in_nu' - line_in_nu_line_interaction.wavelength_start = wavelength_start * u.angstrom + self.parent.model + ) + line_in_nu_line_interaction.packet_filter_mode = "line_in_nu" + line_in_nu_line_interaction.wavelength_start = ( + wavelength_start * u.angstrom + ) line_in_nu_line_interaction.wavelength_end = wavelength_end * u.angstrom - - self.layout.addWidget(LineInteractionTables(packet_nu_line_interaction, - self.parent.model.plasma.atomic_data.atom_data, self.parent.model.plasma.lines, 'filtered by frequency of packet', - self.createTable)) - self.layout.addWidget(LineInteractionTables(line_in_nu_line_interaction, - self.parent.model.plasma.atomic_data.atom_data, self.parent.model.plasma.lines, - 'filtered by frequency of line interaction', self.createTable)) + self.layout.addWidget( + LineInteractionTables( + packet_nu_line_interaction, + self.parent.model.plasma.atomic_data.atom_data, + self.parent.model.plasma.lines, + "filtered by frequency of packet", + self.createTable, + ) + ) + self.layout.addWidget( + LineInteractionTables( + line_in_nu_line_interaction, + self.parent.model.plasma.atomic_data.atom_data, + self.parent.model.plasma.lines, + "filtered by frequency of line interaction", + self.createTable, + ) + ) self.setLayout(self.layout) self.show() @@ -856,35 +996,51 @@ def get_data(self, wavelength_start, wavelength_end): """ self.wavelength_start = wavelength_start * u.angstrom self.wavelength_end = wavelength_end * u.angstrom - last_line_in_ids, last_line_out_ids = analysis.get_last_line_interaction( - self.wavelength_start, self.wavelength_end, self.parent.model) + ( + last_line_in_ids, + last_line_out_ids, + ) = analysis.get_last_line_interaction( + self.wavelength_start, self.wavelength_end, self.parent.model + ) self.last_line_in, self.last_line_out = ( self.parent.model.atom_data.lines.ix[last_line_in_ids], - self.parent.model.atom_data.lines.ix[last_line_out_ids]) - self.grouped_lines_in, self.grouped_lines_out = (self.last_line_in.groupby( - ['atomic_number', 'ion_number']), - self.last_line_out.groupby(['atomic_number', 'ion_number'])) - self.ions_in, self.ions_out = (self.grouped_lines_in.groups.keys(), - self.grouped_lines_out.groups.keys()) + self.parent.model.atom_data.lines.ix[last_line_out_ids], + ) + self.grouped_lines_in, self.grouped_lines_out = ( + self.last_line_in.groupby(["atomic_number", "ion_number"]), + self.last_line_out.groupby(["atomic_number", "ion_number"]), + ) + self.ions_in, self.ions_out = ( + self.grouped_lines_in.groups.keys(), + self.grouped_lines_out.groups.keys(), + ) self.ions_in.sort() self.ions_out.sort() self.header_list = [] - self.ion_table = (self.grouped_lines_in.wavelength.count().astype(float) / - self.grouped_lines_in.wavelength.count().sum()).values.tolist() + self.ion_table = ( + self.grouped_lines_in.wavelength.count().astype(float) + / self.grouped_lines_in.wavelength.count().sum() + ).values.tolist() for z, ion in self.ions_in: - self.header_list.append('Z = %d: Ion %d' % (z, ion)) + self.header_list.append("Z = %d: Ion %d" % (z, ion)) def get_transition_table(self, lines, atom, ion): """Called by the two methods below to get transition table for given lines, atom and ions. """ - grouped = lines.groupby(['atomic_number', 'ion_number']) - transitions_with_duplicates = lines.ix[grouped.groups[(atom, ion)] - ].groupby(['level_number_lower', 'level_number_upper']).groups - transitions = lines.ix[grouped.groups[(atom, ion)] - ].drop_duplicates().groupby(['level_number_lower', - 'level_number_upper']).groups + grouped = lines.groupby(["atomic_number", "ion_number"]) + transitions_with_duplicates = ( + lines.ix[grouped.groups[(atom, ion)]] + .groupby(["level_number_lower", "level_number_upper"]) + .groups + ) + transitions = ( + lines.ix[grouped.groups[(atom, ion)]] + .drop_duplicates() + .groupby(["level_number_lower", "level_number_upper"]) + .groups + ) transitions_count = [] transitions_parsed = [] for item in transitions.values(): @@ -898,8 +1054,16 @@ def get_transition_table(self, lines, atom, ion): for index in range(len(transitions_count)): transitions_count[index] /= float(s) for key, value in transitions.items(): - transitions_parsed.append("%d-%d (%.2f A)" % (key[0], key[1], - self.parent.model.atom_data.lines.ix[value[0]]['wavelength'])) + transitions_parsed.append( + "%d-%d (%.2f A)" + % ( + key[0], + key[1], + self.parent.model.atom_data.lines.ix[value[0]][ + "wavelength" + ], + ) + ) return transitions_parsed, transitions_count def on_atom_clicked(self, index): @@ -907,16 +1071,24 @@ def on_atom_clicked(self, index): dialog created by the spectrum widget. """ - self.transitionsin_parsed, self.transitionsin_count = ( - self.get_transition_table(self.last_line_in, - self.ions_in[index][0], self.ions_in[index][1])) - self.transitionsout_parsed, self.transitionsout_count = ( - self.get_transition_table(self.last_line_out, - self.ions_out[index][0], self.ions_out[index][1])) - self.transitionsindata = self.createTable([self.transitionsin_parsed, - ['Lines In']]) - self.transitionsoutdata = self.createTable([self.transitionsout_parsed, - ['Lines Out']]) + ( + self.transitionsin_parsed, + self.transitionsin_count, + ) = self.get_transition_table( + self.last_line_in, self.ions_in[index][0], self.ions_in[index][1] + ) + ( + self.transitionsout_parsed, + self.transitionsout_count, + ) = self.get_transition_table( + self.last_line_out, self.ions_out[index][0], self.ions_out[index][1] + ) + self.transitionsindata = self.createTable( + [self.transitionsin_parsed, ["Lines In"]] + ) + self.transitionsoutdata = self.createTable( + [self.transitionsout_parsed, ["Lines Out"]] + ) self.transitionsindata.add_data(self.transitionsin_count) self.transitionsoutdata.add_data(self.transitionsout_count) self.transitionsintable.setModel(self.transitionsindata) @@ -931,16 +1103,24 @@ def on_atom_clicked2(self, index): dialog created by the spectrum widget. """ - self.transitionsin_parsed, self.transitionsin_count = ( - self.get_transition_table(self.last_line_in, self.ions_in[index][0], - self.ions_in[index][1])) - self.transitionsout_parsed, self.transitionsout_count = ( - self.get_transition_table(self.last_line_out, - self.ions_out[index][0], self.ions_out[index][1])) - self.transitionsindata = self.createTable([self.transitionsin_parsed, - ['Lines In']]) - self.transitionsoutdata = self.createTable([self.transitionsout_parsed, - ['Lines Out']]) + ( + self.transitionsin_parsed, + self.transitionsin_count, + ) = self.get_transition_table( + self.last_line_in, self.ions_in[index][0], self.ions_in[index][1] + ) + ( + self.transitionsout_parsed, + self.transitionsout_count, + ) = self.get_transition_table( + self.last_line_out, self.ions_out[index][0], self.ions_out[index][1] + ) + self.transitionsindata = self.createTable( + [self.transitionsin_parsed, ["Lines In"]] + ) + self.transitionsoutdata = self.createTable( + [self.transitionsout_parsed, ["Lines Out"]] + ) self.transitionsindata.add_data(self.transitionsin_count) self.transitionsoutdata.add_data(self.transitionsout_count) self.transitionsintable2.setModel(self.transitionsindata) @@ -950,14 +1130,21 @@ def on_atom_clicked2(self, index): self.setGeometry(180 + len(self.parent.line_info) * 20, 150, 750, 400) self.show() + class LineInteractionTables(QtWidgets.QWidget): """Widget to hold the line interaction tables used by LineInfo which in turn is used by spectrum widget. """ - def __init__(self, line_interaction_analysis, atom_data, lines_data, description, - tablecreator): + def __init__( + self, + line_interaction_analysis, + atom_data, + lines_data, + description, + tablecreator, + ): """Create the widget and set data.""" super(LineInteractionTables, self).__init__() self.createTable = tablecreator @@ -967,17 +1154,26 @@ def __init__(self, line_interaction_analysis, atom_data, lines_data, description self.layout = QtWidgets.QHBoxLayout() self.line_interaction_analysis = line_interaction_analysis self.atom_data = atom_data - self.lines_data = lines_data.reset_index().set_index('line_id') - line_interaction_species_group = \ - line_interaction_analysis.last_line_in.groupby(['atomic_number', - 'ion_number']) + self.lines_data = lines_data.reset_index().set_index("line_id") + line_interaction_species_group = line_interaction_analysis.last_line_in.groupby( + ["atomic_number", "ion_number"] + ) self.species_selected = sorted( - line_interaction_species_group.groups.keys()) - species_symbols = [tardis.util.base.species_tuple_to_string(item) for item in self.species_selected] - species_table_model = self.createTable([species_symbols, ['Species']]) + line_interaction_species_group.groups.keys() + ) + species_symbols = [ + tardis.util.base.species_tuple_to_string(item) + for item in self.species_selected + ] + species_table_model = self.createTable([species_symbols, ["Species"]]) species_abundances = ( - line_interaction_species_group.wavelength.count().astype(float) / - line_interaction_analysis.last_line_in.wavelength.count()).astype(float).tolist() + ( + line_interaction_species_group.wavelength.count().astype(float) + / line_interaction_analysis.last_line_in.wavelength.count() + ) + .astype(float) + .tolist() + ) species_abundances = list(map(float, species_abundances)) species_table_model.add_data(species_abundances) self.species_table.setModel(species_table_model) @@ -985,8 +1181,10 @@ def __init__(self, line_interaction_analysis, atom_data, lines_data, description line_interaction_species_group.wavelength.count() self.layout.addWidget(self.text_description) self.layout.addWidget(self.species_table) - self.sectionClicked=QtCore.Signal(int) - self.species_table.verticalHeader().sectionClicked.connect(self.on_species_clicked) + self.sectionClicked = QtCore.Signal(int) + self.species_table.verticalHeader().sectionClicked.connect( + self.on_species_clicked + ) self.layout.addWidget(self.transitions_table) self.setLayout(self.layout) @@ -999,41 +1197,54 @@ def on_species_clicked(self, index): last_line_out = self.line_interaction_analysis.last_line_out current_last_line_in = last_line_in.xs( - key=(current_species[0], current_species[1]), - level=['atomic_number', 'ion_number'], - drop_level=False).reset_index() + key=(current_species[0], current_species[1]), + level=["atomic_number", "ion_number"], + drop_level=False, + ).reset_index() current_last_line_out = last_line_out.xs( - key=(current_species[0], current_species[1]), - level=['atomic_number', 'ion_number'], - drop_level=False).reset_index() - - current_last_line_in['line_id_out'] = current_last_line_out.line_id + key=(current_species[0], current_species[1]), + level=["atomic_number", "ion_number"], + drop_level=False, + ).reset_index() + current_last_line_in["line_id_out"] = current_last_line_out.line_id last_line_in_string = [] last_line_count = [] grouped_line_interactions = current_last_line_in.groupby( - ['line_id', 'line_id_out']) - exc_deexc_string = 'exc. %d-%d (%.2f A) de-exc. %d-%d (%.2f A)' - - for line_id, row in grouped_line_interactions.wavelength.count().iteritems(): + ["line_id", "line_id_out"] + ) + exc_deexc_string = "exc. %d-%d (%.2f A) de-exc. %d-%d (%.2f A)" + + for ( + line_id, + row, + ) in grouped_line_interactions.wavelength.count().iteritems(): current_line_in = self.lines_data.loc[line_id[0]] current_line_out = self.lines_data.loc[line_id[1]] - last_line_in_string.append(exc_deexc_string % ( - current_line_in['level_number_lower'], - current_line_in['level_number_upper'], - current_line_in['wavelength'], - current_line_out['level_number_upper'], - current_line_out['level_number_lower'], - current_line_out['wavelength'])) + last_line_in_string.append( + exc_deexc_string + % ( + current_line_in["level_number_lower"], + current_line_in["level_number_upper"], + current_line_in["wavelength"], + current_line_out["level_number_upper"], + current_line_out["level_number_lower"], + current_line_out["wavelength"], + ) + ) last_line_count.append(int(row)) - - last_line_in_model = self.createTable([last_line_in_string, [ - 'Num. pkts %d' % current_last_line_in.wavelength.count()]]) + last_line_in_model = self.createTable( + [ + last_line_in_string, + ["Num. pkts %d" % current_last_line_in.wavelength.count()], + ] + ) last_line_in_model.add_data(last_line_count) self.transitions_table.setModel(last_line_in_model) + class Tardis(QtWidgets.QMainWindow): """Create the top level window for the GUI and wait for call to display data. @@ -1065,8 +1276,8 @@ def __init__(self, tablemodel, config=None, atom_data=None, parent=None): """ - #assumes that qt has already been initialized by starting IPython - #with the flag "--pylab=qt" + # assumes that qt has already been initialized by starting IPython + # with the flag "--pylab=qt" # app = QtCore.QCoreApplication.instance() # if app is None: # app = QtGui.QApplication([]) @@ -1078,48 +1289,52 @@ def __init__(self, tablemodel, config=None, atom_data=None, parent=None): QtWidgets.QMainWindow.__init__(self, parent) - #path to icons folder - self.path = os.path.join(tardis.__path__[0],'gui','images') + # path to icons folder + self.path = os.path.join(tardis.__path__[0], "gui", "images") - #Check if configuration file was provided - self.mode = 'passive' + # Check if configuration file was provided + self.mode = "passive" if config is not None: - self.mode = 'active' + self.mode = "active" - #Statusbar + # Statusbar statusbr = self.statusBar() lblstr = 'Calculation did not converge' self.successLabel = QtWidgets.QLabel(lblstr) - self.successLabel.setFrameStyle(QtWidgets.QFrame.StyledPanel | - QtWidgets.QFrame.Sunken) + self.successLabel.setFrameStyle( + QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Sunken + ) statusbr.addPermanentWidget(self.successLabel) - self.modeLabel = QtWidgets.QLabel('Passive mode') + self.modeLabel = QtWidgets.QLabel("Passive mode") statusbr.addPermanentWidget(self.modeLabel) statusbr.showMessage(self.mode, 5000) statusbr.showMessage("Ready", 5000) - #Actions + # Actions quitAction = QtWidgets.QAction("&Quit", self) - quitAction.setIcon(QtGui.QIcon(os.path.join(self.path, - 'closeicon.png'))) + quitAction.setIcon( + QtGui.QIcon(os.path.join(self.path, "closeicon.png")) + ) quitAction.triggered.connect(self.close) self.viewMdv = QtWidgets.QAction("View &Model", self) - self.viewMdv.setIcon(QtGui.QIcon(os.path.join(self.path, - 'mdvswitch.png'))) + self.viewMdv.setIcon( + QtGui.QIcon(os.path.join(self.path, "mdvswitch.png")) + ) self.viewMdv.setCheckable(True) self.viewMdv.setChecked(True) self.viewMdv.setEnabled(False) self.viewMdv.triggered.connect(self.switch_to_mdv) self.viewForm = QtWidgets.QAction("&Edit Model", self) - self.viewForm.setIcon(QtGui.QIcon(os.path.join(self.path, - 'formswitch.png'))) + self.viewForm.setIcon( + QtGui.QIcon(os.path.join(self.path, "formswitch.png")) + ) self.viewForm.setCheckable(True) self.viewForm.setEnabled(False) self.viewForm.triggered.connect(self.switch_to_form) - #Menubar + # Menubar self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(quitAction) self.viewMenu = self.menuBar().addMenu("&View") @@ -1127,7 +1342,7 @@ def __init__(self, tablemodel, config=None, atom_data=None, parent=None): self.viewMenu.addAction(self.viewForm) self.helpMenu = self.menuBar().addMenu("&Help") - #Toolbar + # Toolbar fileToolbar = self.addToolBar("File") fileToolbar.setObjectName("FileToolBar") fileToolbar.addAction(quitAction) @@ -1137,14 +1352,14 @@ def __init__(self, tablemodel, config=None, atom_data=None, parent=None): viewToolbar.addAction(self.viewMdv) viewToolbar.addAction(self.viewForm) - #Central Widget + # Central Widget self.stackedWidget = QtWidgets.QStackedWidget() self.mdv = ModelViewer(tablemodel) self.stackedWidget.addWidget(self.mdv) - #In case of active mode - if self.mode == 'active': - #Disabled currently + # In case of active mode + if self.mode == "active": + # Disabled currently # self.formWidget = ConfigEditor(config) # #scrollarea # scrollarea = QtGui.QScrollArea() @@ -1154,8 +1369,10 @@ def __init__(self, tablemodel, config=None, atom_data=None, parent=None): # self.viewMdv.setEnabled(True) # model = run_tardis(config, atom_data) # self.show_model(model) - raise TemporarilyUnavaliable("The active mode is under" - "development. Please use the passive mode for now.") + raise TemporarilyUnavaliable( + "The active mode is under" + "development. Please use the passive mode for now." + ) self.setCentralWidget(self.stackedWidget) @@ -1172,8 +1389,8 @@ def show_model(self, model=None): self.mdv.change_model(model) if model.converged: self.successLabel.setText('converged') - if self.mode == 'active': - self.modeLabel.setText('Active Mode') + if self.mode == "active": + self.modeLabel.setText("Active Mode") self.mdv.fill_output_label() self.mdv.tableview.setModel(self.mdv.tablemodel) @@ -1181,7 +1398,6 @@ def show_model(self, model=None): self.mdv.plot_spectrum() self.showMaximized() - def switch_to_mdv(self): """Switch the cental stacked widget to show the modelviewer.""" self.stackedWidget.setCurrentIndex(0) @@ -1192,6 +1408,7 @@ def switch_to_form(self): self.stackedWidget.setCurrentIndex(1) self.viewMdv.setChecked(False) + class TemporarilyUnavaliable(Exception): """Exception raised when creation of active mode of tardis is attempted.""" diff --git a/tardis/io/__init__.py b/tardis/io/__init__.py index 8034d6d0cc0..ca4d671e64b 100644 --- a/tardis/io/__init__.py +++ b/tardis/io/__init__.py @@ -1,4 +1,8 @@ -#readin model_data -from tardis.io.model_reader import read_simple_ascii_density, read_simple_ascii_abundances, read_density_file +# readin model_data +from tardis.io.model_reader import ( + read_simple_ascii_density, + read_simple_ascii_abundances, + read_density_file, +) from tardis.io.config_internal import get_internal_configuration, get_data_dir diff --git a/tardis/io/atom_data/__init__.py b/tardis/io/atom_data/__init__.py index 10a3e57fd9b..5fd5f11b1ce 100644 --- a/tardis/io/atom_data/__init__.py +++ b/tardis/io/atom_data/__init__.py @@ -1,2 +1,2 @@ from tardis.io.atom_data.base import AtomData -from tardis.io.atom_data.atom_web_download import download_atom_data \ No newline at end of file +from tardis.io.atom_data.atom_web_download import download_atom_data diff --git a/tardis/io/atom_data/atom_web_download.py b/tardis/io/atom_data/atom_web_download.py index 3abd24d884a..fdc60e55328 100644 --- a/tardis/io/atom_data/atom_web_download.py +++ b/tardis/io/atom_data/atom_web_download.py @@ -7,6 +7,7 @@ logger = logging.getLogger(__name__) + def get_atomic_repo_config(): """ Get the repo configuration dictionary for the atomic data @@ -17,7 +18,7 @@ def get_atomic_repo_config(): """ - atomic_repo_fname = get_internal_data_path('atomic_data_repo.yml') + atomic_repo_fname = get_internal_data_path("atomic_data_repo.yml") return yaml.load(open(atomic_repo_fname), Loader=yaml.CLoader) @@ -38,11 +39,15 @@ def download_atom_data(atomic_data_name=None): atomic_repo = get_atomic_repo_config() if atomic_data_name is None: - atomic_data_name = atomic_repo['default'] + atomic_data_name = atomic_repo["default"] if atomic_data_name not in atomic_repo: - raise ValueError('Atomic Data name {0} not known'.format(atomic_data_name)) - dst_dir = os.path.join(get_data_dir(), '{0}.h5'.format(atomic_data_name)) - src_url = atomic_repo[atomic_data_name]['url'] - logger.info('Downloading atomic data from {0} to {1}'.format(src_url, dst_dir)) - download_from_url(src_url, dst_dir) \ No newline at end of file + raise ValueError( + "Atomic Data name {0} not known".format(atomic_data_name) + ) + dst_dir = os.path.join(get_data_dir(), "{0}.h5".format(atomic_data_name)) + src_url = atomic_repo[atomic_data_name]["url"] + logger.info( + "Downloading atomic data from {0} to {1}".format(src_url, dst_dir) + ) + download_from_url(src_url, dst_dir) diff --git a/tardis/io/atom_data/base.py b/tardis/io/atom_data/base.py index 8202ec8fcb4..26841c1175e 100644 --- a/tardis/io/atom_data/base.py +++ b/tardis/io/atom_data/base.py @@ -12,6 +12,7 @@ from astropy.units import Quantity from tardis.io.atom_data.util import resolve_atom_data_fname + class AtomDataNotPreparedError(Exception): pass @@ -117,23 +118,25 @@ class AtomData(object): """ hdf_names = [ - "atom_data", - "ionization_data", - "levels", - "lines", - "macro_atom_data", - "macro_atom_references", - "zeta_data", - "collision_data", - "collision_data_temperatures", - "synpp_refs", - "photoionization_data" + "atom_data", + "ionization_data", + "levels", + "lines", + "macro_atom_data", + "macro_atom_references", + "zeta_data", + "collision_data", + "collision_data_temperatures", + "synpp_refs", + "photoionization_data", ] # List of tuples of the related dataframes. # Either all or none of the related dataframes must be given - related_groups = [("macro_atom_data_all", "macro_atom_references_all"), - ("collision_data", "collision_data_temperatures")] + related_groups = [ + ("macro_atom_data_all", "macro_atom_references_all"), + ("collision_data", "collision_data_temperatures"), + ] @classmethod def from_hdf(cls, fname=None): @@ -153,7 +156,7 @@ def from_hdf(cls, fname=None): fname = resolve_atom_data_fname(fname) - with pd.HDFStore(fname, 'r') as store: + with pd.HDFStore(fname, "r") as store: for name in cls.hdf_names: try: dataframes[name] = store[name] @@ -163,17 +166,17 @@ def from_hdf(cls, fname=None): atom_data = cls(**dataframes) try: - atom_data.uuid1 = store.root._v_attrs['uuid1'].decode('ascii') + atom_data.uuid1 = store.root._v_attrs["uuid1"].decode("ascii") except KeyError: atom_data.uuid1 = None try: - atom_data.md5 = store.root._v_attrs['md5'].decode('ascii') + atom_data.md5 = store.root._v_attrs["md5"].decode("ascii") except KeyError: atom_data.md5 = None try: - atom_data.version = store.root._v_attrs['database_version'] + atom_data.version = store.root._v_attrs["database_version"] except KeyError: atom_data.version = None @@ -181,18 +184,32 @@ def from_hdf(cls, fname=None): logger.info( "Read Atom Data with UUID={0} and MD5={1}.".format( - atom_data.uuid1, atom_data.md5)) + atom_data.uuid1, atom_data.md5 + ) + ) if nonavailable: - logger.info("Non provided atomic data: {0}".format( - ", ".join(nonavailable))) + logger.info( + "Non provided atomic data: {0}".format( + ", ".join(nonavailable) + ) + ) return atom_data - def __init__(self, atom_data, ionization_data, levels=None, lines=None, - macro_atom_data=None, macro_atom_references=None, - zeta_data=None, collision_data=None, - collision_data_temperatures=None, synpp_refs=None, - photoionization_data=None): + def __init__( + self, + atom_data, + ionization_data, + levels=None, + lines=None, + macro_atom_data=None, + macro_atom_references=None, + zeta_data=None, + collision_data=None, + collision_data_temperatures=None, + synpp_refs=None, + photoionization_data=None, + ): self.prepared = False @@ -205,7 +222,8 @@ def __init__(self, atom_data, ionization_data, levels=None, lines=None, # the value of constants.u is used in all cases) if u.u.cgs == const.u.cgs: atom_data.loc[:, "mass"] = Quantity( - atom_data["mass"].values, "u").cgs + atom_data["mass"].values, "u" + ).cgs else: atom_data.loc[:, "mass"] = atom_data["mass"].values * const.u.cgs @@ -214,11 +232,12 @@ def __init__(self, atom_data, ionization_data, levels=None, lines=None, ionization_data[:] = Quantity(ionization_data[:], "eV").cgs # Convert energy to CGS - levels.loc[:, "energy"] = Quantity(levels["energy"].values, 'eV').cgs + levels.loc[:, "energy"] = Quantity(levels["energy"].values, "eV").cgs # Create a new columns with wavelengths in the CGS units - lines.loc[:, 'wavelength_cm'] = Quantity( - lines['wavelength'], 'angstrom').cgs + lines.loc[:, "wavelength_cm"] = Quantity( + lines["wavelength"], "angstrom" + ).cgs # SET ATTRIBUTES @@ -243,31 +262,33 @@ def __init__(self, atom_data, ionization_data, levels=None, lines=None, self._check_related() self.symbol2atomic_number = OrderedDict( - zip(self.atom_data['symbol'].values, self.atom_data.index)) + zip(self.atom_data["symbol"].values, self.atom_data.index) + ) self.atomic_number2symbol = OrderedDict( - zip(self.atom_data.index, self.atom_data['symbol'])) + zip(self.atom_data.index, self.atom_data["symbol"]) + ) def _check_related(self): """ Check that either all or none of the related dataframes are given. """ for group in self.related_groups: - check_list = [ - name for name in group if getattr(self, name) is None] + check_list = [name for name in group if getattr(self, name) is None] if len(check_list) != 0 and len(check_list) != len(group): raise AtomDataMissingError( "The following dataframes from the related group [{0}] " "were not given: {1}".format( - ", ".join(group), - ", ".join(check_list) - ) + ", ".join(group), ", ".join(check_list) ) + ) def prepare_atom_data( - self, selected_atomic_numbers, - line_interaction_type='scatter', - nlte_species=[]): + self, + selected_atomic_numbers, + line_interaction_type="scatter", + nlte_species=[], + ): """ Prepares the atom data to set the lines, levels and if requested macro atom data. This function mainly cuts the `levels` and `lines` by @@ -295,105 +316,155 @@ def prepare_atom_data( self.nlte_species = nlte_species self.levels = self.levels[ - self.levels.index.isin( - self.selected_atomic_numbers, - level='atomic_number')] + self.levels.index.isin( + self.selected_atomic_numbers, level="atomic_number" + ) + ] self.levels_index = pd.Series( - np.arange(len(self.levels), dtype=int), - index=self.levels.index) + np.arange(len(self.levels), dtype=int), index=self.levels.index + ) # cutting levels_lines self.lines = self.lines[ - self.lines.index.isin( - self.selected_atomic_numbers, - level='atomic_number')] + self.lines.index.isin( + self.selected_atomic_numbers, level="atomic_number" + ) + ] - self.lines.sort_values(by='wavelength', inplace=True) + self.lines.sort_values(by="wavelength", inplace=True) self.lines_index = pd.Series( - np.arange(len(self.lines), dtype=int), - index=self.lines.set_index('line_id').index) + np.arange(len(self.lines), dtype=int), + index=self.lines.set_index("line_id").index, + ) - tmp_lines_lower2level_idx = self.lines.index.droplevel('level_number_upper') + tmp_lines_lower2level_idx = self.lines.index.droplevel( + "level_number_upper" + ) self.lines_lower2level_idx = ( - self.levels_index.loc[tmp_lines_lower2level_idx]. - astype(np.int64).values) + self.levels_index.loc[tmp_lines_lower2level_idx] + .astype(np.int64) + .values + ) - tmp_lines_upper2level_idx = self.lines.index.droplevel('level_number_lower') + tmp_lines_upper2level_idx = self.lines.index.droplevel( + "level_number_lower" + ) self.lines_upper2level_idx = ( - self.levels_index.loc[tmp_lines_upper2level_idx]. - astype(np.int64).values) + self.levels_index.loc[tmp_lines_upper2level_idx] + .astype(np.int64) + .values + ) if ( - self.macro_atom_data_all is not None and - not line_interaction_type == 'scatter'): + self.macro_atom_data_all is not None + and not line_interaction_type == "scatter" + ): self.macro_atom_data = self.macro_atom_data_all.loc[ - self.macro_atom_data_all['atomic_number'].isin(self.selected_atomic_numbers) + self.macro_atom_data_all["atomic_number"].isin( + self.selected_atomic_numbers + ) ].copy() self.macro_atom_references = self.macro_atom_references_all[ self.macro_atom_references_all.index.isin( - self.selected_atomic_numbers, - level='atomic_number') + self.selected_atomic_numbers, level="atomic_number" + ) ].copy() - if line_interaction_type == 'downbranch': + if line_interaction_type == "downbranch": self.macro_atom_data = self.macro_atom_data.loc[ - self.macro_atom_data['transition_type'] == -1 + self.macro_atom_data["transition_type"] == -1 ] self.macro_atom_references = self.macro_atom_references.loc[ - self.macro_atom_references['count_down'] > 0 + self.macro_atom_references["count_down"] > 0 ] - self.macro_atom_references.loc[:, 'count_total'] = self.macro_atom_references['count_down'] - self.macro_atom_references.loc[:, 'block_references'] = np.hstack( - (0, np.cumsum(self.macro_atom_references['count_down'].values[:-1])) + self.macro_atom_references.loc[ + :, "count_total" + ] = self.macro_atom_references["count_down"] + self.macro_atom_references.loc[ + :, "block_references" + ] = np.hstack( + ( + 0, + np.cumsum( + self.macro_atom_references["count_down"].values[:-1] + ), + ) ) - elif line_interaction_type == 'macroatom': - self.macro_atom_references.loc[:, 'block_references'] = np.hstack( - (0, np.cumsum(self.macro_atom_references['count_total'].values[:-1])) + elif line_interaction_type == "macroatom": + self.macro_atom_references.loc[ + :, "block_references" + ] = np.hstack( + ( + 0, + np.cumsum( + self.macro_atom_references["count_total"].values[ + :-1 + ] + ), + ) ) - self.macro_atom_references.loc[:, "references_idx"] = np.arange(len(self.macro_atom_references)) + self.macro_atom_references.loc[:, "references_idx"] = np.arange( + len(self.macro_atom_references) + ) self.macro_atom_data.loc[:, "lines_idx"] = self.lines_index.loc[ - self.macro_atom_data['transition_line_id'] + self.macro_atom_data["transition_line_id"] ].values - self.lines_upper2macro_reference_idx = self.macro_atom_references.loc[ - tmp_lines_upper2level_idx, 'references_idx' - ].astype(np.int64).values + self.lines_upper2macro_reference_idx = ( + self.macro_atom_references.loc[ + tmp_lines_upper2level_idx, "references_idx" + ] + .astype(np.int64) + .values + ) - if line_interaction_type == 'macroatom': + if line_interaction_type == "macroatom": # Sets all - tmp_macro_destination_level_idx = pd.MultiIndex.from_arrays([ - self.macro_atom_data['atomic_number'], - self.macro_atom_data['ion_number'], - self.macro_atom_data['destination_level_number'] - ]) - - tmp_macro_source_level_idx = pd.MultiIndex.from_arrays([ - self.macro_atom_data['atomic_number'], - self.macro_atom_data['ion_number'], - self.macro_atom_data['source_level_number'] - ]) - - self.macro_atom_data.loc[:, 'destination_level_idx'] = self.macro_atom_references.loc[ - tmp_macro_destination_level_idx, "references_idx" - ].astype(np.int64).values - - self.macro_atom_data.loc[:, 'source_level_idx'] = self.macro_atom_references.loc[ - tmp_macro_source_level_idx, "references_idx" - ].astype(np.int64).values - - elif line_interaction_type == 'downbranch': + tmp_macro_destination_level_idx = pd.MultiIndex.from_arrays( + [ + self.macro_atom_data["atomic_number"], + self.macro_atom_data["ion_number"], + self.macro_atom_data["destination_level_number"], + ] + ) + + tmp_macro_source_level_idx = pd.MultiIndex.from_arrays( + [ + self.macro_atom_data["atomic_number"], + self.macro_atom_data["ion_number"], + self.macro_atom_data["source_level_number"], + ] + ) + + self.macro_atom_data.loc[:, "destination_level_idx"] = ( + self.macro_atom_references.loc[ + tmp_macro_destination_level_idx, "references_idx" + ] + .astype(np.int64) + .values + ) + + self.macro_atom_data.loc[:, "source_level_idx"] = ( + self.macro_atom_references.loc[ + tmp_macro_source_level_idx, "references_idx" + ] + .astype(np.int64) + .values + ) + + elif line_interaction_type == "downbranch": # Sets all the destination levels to -1 to indicate that they # are not used in downbranch calculations - self.macro_atom_data.loc[:, 'destination_level_idx'] = -1 + self.macro_atom_data.loc[:, "destination_level_idx"] = -1 self.nlte_data = NLTEData(self, nlte_species) @@ -402,15 +473,14 @@ def _check_selected_atomic_numbers(self): available_atomic_numbers = np.unique( self.ionization_data.index.get_level_values(0) ) - atomic_number_check = np.isin(selected_atomic_numbers, - available_atomic_numbers) + atomic_number_check = np.isin( + selected_atomic_numbers, available_atomic_numbers + ) if not all(atomic_number_check): missing_atom_mask = np.logical_not(atomic_number_check) missing_atomic_numbers = selected_atomic_numbers[missing_atom_mask] - missing_numbers_str = ','.join( - missing_atomic_numbers.astype('str') - ) + missing_numbers_str = ",".join(missing_atomic_numbers.astype("str")) msg = "For atomic numbers {} there is no atomic data.".format( missing_numbers_str ) @@ -418,8 +488,11 @@ def _check_selected_atomic_numbers(self): def __repr__(self): return "".format( - self.uuid1, self.md5, self.lines.line_id.count(), - self.levels.energy.count()) + self.uuid1, + self.md5, + self.lines.line_id.count(), + self.levels.energy.count(), + ) class NLTEData(object): @@ -429,7 +502,7 @@ def __init__(self, atom_data, nlte_species): self.nlte_species = nlte_species if nlte_species: - logger.info('Preparing the NLTE data') + logger.info("Preparing the NLTE data") self._init_indices() if atom_data.collision_data is not None: self._create_collision_coefficient_matrix() @@ -444,12 +517,16 @@ def _init_indices(self): for species in self.nlte_species: lines_idx = np.where( - (self.lines.atomic_number == species[0]) & - (self.lines.ion_number == species[1]) - ) + (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) + 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) 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] @@ -459,55 +536,69 @@ def _create_collision_coefficient_matrix(self): self.C_ul_interpolator = {} self.delta_E_matrices = {} self.g_ratio_matrices = {} - collision_group = self.atom_data.collision_data.groupby(level=['atomic_number', 'ion_number']) + collision_group = self.atom_data.collision_data.groupby( + level=["atomic_number", "ion_number"] + ) for species in self.nlte_species: no_of_levels = self.atom_data.levels.loc[species].energy.count() C_ul_matrix = np.zeros( - ( - no_of_levels, - no_of_levels, - len(self.atom_data.collision_data_temperatures)) - ) + ( + 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)) for ( + ( atomic_number, ion_number, level_number_lower, - level_number_upper), line in ( - collision_group.get_group(species).iterrows()): - # line.columns : delta_e, g_ratio, temperatures ... - 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'] + level_number_upper, + ), + line, + ) in collision_group.get_group(species).iterrows(): + # line.columns : delta_e, g_ratio, temperatures ... + 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.atom_data.collision_data_temperatures, - C_ul_matrix) + 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 def get_collision_matrix(self, species, t_electrons): - ''' + """ Creat collision matrix by interpolating the C_ul values for the desired temperatures. - ''' + """ c_ul_matrix = self.C_ul_interpolator[species](t_electrons) no_of_levels = c_ul_matrix.shape[0] 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 + # 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].reshape( - (no_of_levels, no_of_levels, 1)) / - t_electrons.reshape( - (1, 1, t_electrons.shape[0])) - ) * - self.g_ratio_matrices[species].reshape( - (no_of_levels, no_of_levels, 1)) + c_ul_matrix + * np.exp( + -self.delta_E_matrices[species].reshape( + (no_of_levels, no_of_levels, 1) ) + / t_electrons.reshape((1, 1, t_electrons.shape[0])) + ) + * self.g_ratio_matrices[species].reshape( + (no_of_levels, no_of_levels, 1) + ) + ) return c_ul_matrix + c_lu_matrix.transpose(1, 0, 2) diff --git a/tardis/io/atom_data/util.py b/tardis/io/atom_data/util.py index d5f1fde1939..2e07d9b859e 100644 --- a/tardis/io/atom_data/util.py +++ b/tardis/io/atom_data/util.py @@ -2,10 +2,14 @@ import logging from tardis.io.config_internal import get_data_dir -from tardis.io.atom_data.atom_web_download import get_atomic_repo_config, download_atom_data +from tardis.io.atom_data.atom_web_download import ( + get_atomic_repo_config, + download_atom_data, +) logger = logging.getLogger(__name__) + def resolve_atom_data_fname(fname): """ Check where if atom data HDF file is available on disk, can be downloaded or does not exist @@ -26,17 +30,23 @@ def resolve_atom_data_fname(fname): fpath = os.path.join(os.path.join(get_data_dir(), fname)) if os.path.exists(fpath): - logger.info('Atom Data {0} not found in local path. Exists in TARDIS Data repo {1}'.format(fname, fpath)) + logger.info( + "Atom Data {0} not found in local path. Exists in TARDIS Data repo {1}".format( + fname, fpath + ) + ) return fpath - atom_data_name = fname.replace('.h5', '') + atom_data_name = fname.replace(".h5", "") atom_repo_config = get_atomic_repo_config() if atom_data_name in atom_repo_config: - raise IOError('Atom Data {0} not found in path or in TARDIS data repo - it is available as download:\n' - 'from tardis.io.atom_data import download_atom_data\n' - 'download_atom_data(\'{1}\')'.format(fname, atom_data_name)) - - raise IOError('Atom Data {0} is not found in current path or in TARDIS data repo. {1} is also not a standard known' - 'TARDIS atom dataset.'.format(fname, atom_data_name)) - - + raise IOError( + "Atom Data {0} not found in path or in TARDIS data repo - it is available as download:\n" + "from tardis.io.atom_data import download_atom_data\n" + "download_atom_data('{1}')".format(fname, atom_data_name) + ) + + raise IOError( + "Atom Data {0} is not found in current path or in TARDIS data repo. {1} is also not a standard known" + "TARDIS atom dataset.".format(fname, atom_data_name) + ) diff --git a/tardis/io/config_internal.py b/tardis/io/config_internal.py index 0963696fd4d..30ff8a1567e 100644 --- a/tardis/io/config_internal.py +++ b/tardis/io/config_internal.py @@ -5,40 +5,57 @@ from astropy.config import get_config_dir TARDIS_PATH = TARDIS_PATH[0] -DEFAULT_CONFIG_PATH = os.path.join(TARDIS_PATH, 'data', 'default_tardis_internal_config.yml') -DEFAULT_DATA_DIR = os.path.join(os.path.expanduser('~'), 'Downloads', 'tardis-data') +DEFAULT_CONFIG_PATH = os.path.join( + TARDIS_PATH, "data", "default_tardis_internal_config.yml" +) +DEFAULT_DATA_DIR = os.path.join( + os.path.expanduser("~"), "Downloads", "tardis-data" +) logger = logging.getLogger(__name__) + def get_internal_configuration(): - config_fpath = os.path.join(get_config_dir(), 'tardis_internal_config.yml') + config_fpath = os.path.join(get_config_dir(), "tardis_internal_config.yml") if not os.path.exists(config_fpath): - logger.warning("Configuration File {0} does not exist - creating new one from default".format(config_fpath)) + logger.warning( + "Configuration File {0} does not exist - creating new one from default".format( + config_fpath + ) + ) shutil.copy(DEFAULT_CONFIG_PATH, config_fpath) with open(config_fpath) as config_fh: return yaml.load(config_fh, Loader=yaml.CLoader) - def get_data_dir(): config = get_internal_configuration() - data_dir = config.get('data_dir', None) + data_dir = config.get("data_dir", None) if data_dir is None: - config_fpath = os.path.join(get_config_dir(), 'tardis_internal_config.yml') - logging.critical('\n{line_stars}\n\nTARDIS will download different kinds of data (e.g. atomic) to its data directory {default_data_dir}\n\n' - 'TARDIS DATA DIRECTORY not specified in {config_file}:\n\n' - 'ASSUMING DEFAULT DATA DIRECTORY {default_data_dir}\n ' - 'YOU CAN CHANGE THIS AT ANY TIME IN {config_file} \n\n' - '{line_stars} \n\n'.format(line_stars='*'*80, config_file=config_fpath, - default_data_dir=DEFAULT_DATA_DIR)) + config_fpath = os.path.join( + get_config_dir(), "tardis_internal_config.yml" + ) + logging.critical( + "\n{line_stars}\n\nTARDIS will download different kinds of data (e.g. atomic) to its data directory {default_data_dir}\n\n" + "TARDIS DATA DIRECTORY not specified in {config_file}:\n\n" + "ASSUMING DEFAULT DATA DIRECTORY {default_data_dir}\n " + "YOU CAN CHANGE THIS AT ANY TIME IN {config_file} \n\n" + "{line_stars} \n\n".format( + line_stars="*" * 80, + config_file=config_fpath, + default_data_dir=DEFAULT_DATA_DIR, + ) + ) if not os.path.exists(DEFAULT_DATA_DIR): os.makedirs(DEFAULT_DATA_DIR) - config['data_dir'] = DEFAULT_DATA_DIR - yaml.dump(config, open(config_fpath, 'w'), default_flow_style=False) + config["data_dir"] = DEFAULT_DATA_DIR + yaml.dump(config, open(config_fpath, "w"), default_flow_style=False) data_dir = DEFAULT_DATA_DIR if not os.path.exists(data_dir): - raise IOError('Data directory specified in {0} does not exist'.format(data_dir)) + raise IOError( + "Data directory specified in {0} does not exist".format(data_dir) + ) return data_dir diff --git a/tardis/io/config_reader.py b/tardis/io/config_reader.py index 5927194d93d..50415dddb23 100644 --- a/tardis/io/config_reader.py +++ b/tardis/io/config_reader.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -data_dir = os.path.abspath(os.path.join(tardis.__path__[0], 'data')) +data_dir = os.path.abspath(os.path.join(tardis.__path__[0], "data")) class ConfigurationError(ValueError): @@ -31,17 +31,20 @@ def parse_convergence_section(convergence_section_dict): dictionary """ - convergence_parameters = ['damping_constant', 'threshold'] + convergence_parameters = ["damping_constant", "threshold"] - for convergence_variable in ['t_inner', 't_rad', 'w']: + for convergence_variable in ["t_inner", "t_rad", "w"]: if convergence_variable not in convergence_section_dict: convergence_section_dict[convergence_variable] = {} - convergence_variable_section = convergence_section_dict[convergence_variable] + convergence_variable_section = convergence_section_dict[ + convergence_variable + ] for param in convergence_parameters: if convergence_variable_section.get(param, None) is None: if param in convergence_section_dict: - convergence_section_dict[convergence_variable][param] = ( - convergence_section_dict[param]) + convergence_section_dict[convergence_variable][ + param + ] = convergence_section_dict[param] return convergence_section_dict @@ -79,10 +82,9 @@ def from_yaml(cls, fname): try: yaml_dict = yaml_load_file(fname) except IOError as e: - logger.critical('No config file named: %s', fname) + logger.critical("No config file named: %s", fname) raise e - return cls.from_config_dict(yaml_dict) @classmethod @@ -115,30 +117,32 @@ def __init__(self, value=None): for key in value: self.__setitem__(key, value[key]) else: - raise (TypeError, 'expected dict') + raise (TypeError, "expected dict") - if hasattr(self, 'csvy_model') and hasattr(self, 'model'): - raise ValueError('Cannot specify both model and csvy_model in main config file.') - if hasattr(self, 'csvy_model'): + if hasattr(self, "csvy_model") and hasattr(self, "model"): + raise ValueError( + "Cannot specify both model and csvy_model in main config file." + ) + if hasattr(self, "csvy_model"): model = dict() - csvy_model_path = os.path.join(self.config_dirname,self.csvy_model) + csvy_model_path = os.path.join(self.config_dirname, self.csvy_model) csvy_yml = load_yaml_from_csvy(csvy_model_path) - if 'v_inner_boundary' in csvy_yml: - model['v_inner_boundary'] = csvy_yml['v_inner_boundary'] - if 'v_outer_boundary' in csvy_yml: - model['v_outer_boundary'] = csvy_yml['v_outer_boundary'] + if "v_inner_boundary" in csvy_yml: + model["v_inner_boundary"] = csvy_yml["v_inner_boundary"] + if "v_outer_boundary" in csvy_yml: + model["v_outer_boundary"] = csvy_yml["v_outer_boundary"] - self.__setitem__('model',model) + self.__setitem__("model", model) for key in self.model: self.model.__setitem__(key, self.model[key]) - def __setitem__(self, key, value): - if isinstance(value, dict) and not isinstance(value, - ConfigurationNameSpace): + if isinstance(value, dict) and not isinstance( + value, ConfigurationNameSpace + ): value = ConfigurationNameSpace(value) - if key in self and hasattr(self[key], 'unit'): + if key in self and hasattr(self[key], "unit"): value = u.Quantity(value, self[key].unit) dict.__setitem__(self, key, value) @@ -167,23 +171,26 @@ def get_config_item(self, config_item_string): config_item_string: ~str string of shape 'section1.sectionb.param1' """ - config_item_path = config_item_string.split('.') + config_item_path = config_item_string.split(".") if len(config_item_path) == 1: config_item = config_item_path[0] - if config_item.startswith('item'): + if config_item.startswith("item"): return self[config_item_path[0]] else: return self[config_item] - elif len(config_item_path) == 2 and\ - config_item_path[1].startswith('item'): + elif len(config_item_path) == 2 and config_item_path[1].startswith( + "item" + ): return self[config_item_path[0]][ - int(config_item_path[1].replace('item', ''))] + int(config_item_path[1].replace("item", "")) + ] else: return self[config_item_path[0]].get_config_item( - '.'.join(config_item_path[1:])) + ".".join(config_item_path[1:]) + ) def set_config_item(self, config_item_string, value): """ @@ -199,24 +206,28 @@ def set_config_item(self, config_item_string, value): value to set the parameter with it """ - config_item_path = config_item_string.split('.') + config_item_path = config_item_string.split(".") if len(config_item_path) == 1: self[config_item_path[0]] = value - elif len(config_item_path) == 2 and \ - config_item_path[1].startswith('item'): + elif len(config_item_path) == 2 and config_item_path[1].startswith( + "item" + ): current_value = self[config_item_path[0]][ - int(config_item_path[1].replace('item', ''))] - if hasattr(current_value, 'unit'): + int(config_item_path[1].replace("item", "")) + ] + if hasattr(current_value, "unit"): self[config_item_path[0]][ - int(config_item_path[1].replace('item', ''))] =\ - u.Quantity(value, current_value.unit) + int(config_item_path[1].replace("item", "")) + ] = u.Quantity(value, current_value.unit) else: self[config_item_path[0]][ - int(config_item_path[1].replace('item', ''))] = value + int(config_item_path[1].replace("item", "")) + ] = value else: self[config_item_path[0]].set_config_item( - '.'.join(config_item_path[1:]), value) + ".".join(config_item_path[1:]), value + ) def deepcopy(self): return ConfigurationNameSpace(copy.deepcopy(dict(self))) @@ -230,24 +241,25 @@ class Configuration(ConfigurationNameSpace): @classmethod def from_yaml(cls, fname, *args, **kwargs): try: - yaml_dict = yaml_load_file(fname, - loader=kwargs.pop('loader', YAMLLoader)) + yaml_dict = yaml_load_file( + fname, loader=kwargs.pop("loader", YAMLLoader) + ) except IOError as e: - logger.critical('No config file named: %s', fname) + logger.critical("No config file named: %s", fname) raise e - tardis_config_version = yaml_dict.get('tardis_config_version', None) - if tardis_config_version != 'v1.0': + tardis_config_version = yaml_dict.get("tardis_config_version", None) + if tardis_config_version != "v1.0": raise ConfigurationError( - 'Currently only tardis_config_version v1.0 supported') + "Currently only tardis_config_version v1.0 supported" + ) - kwargs['config_dirname'] = os.path.dirname(fname) + kwargs["config_dirname"] = os.path.dirname(fname) - return cls.from_config_dict( - yaml_dict, *args, **kwargs) + return cls.from_config_dict(yaml_dict, *args, **kwargs) @classmethod - def from_config_dict(cls, config_dict, validate=True, config_dirname=''): + def from_config_dict(cls, config_dict, validate=True, config_dirname=""): """ Validating and subsequently parsing a config file. @@ -272,24 +284,28 @@ def from_config_dict(cls, config_dict, validate=True, config_dirname=''): else: validated_config_dict = config_dict - validated_config_dict['config_dirname'] = config_dirname + validated_config_dict["config_dirname"] = config_dirname - montecarlo_section = validated_config_dict['montecarlo'] - if montecarlo_section['convergence_strategy']['type'] == "damped": - montecarlo_section['convergence_strategy'] = ( - parse_convergence_section( - montecarlo_section['convergence_strategy'])) - elif montecarlo_section['convergence_strategy']['type'] == "custom": + montecarlo_section = validated_config_dict["montecarlo"] + if montecarlo_section["convergence_strategy"]["type"] == "damped": + montecarlo_section[ + "convergence_strategy" + ] = parse_convergence_section( + montecarlo_section["convergence_strategy"] + ) + elif montecarlo_section["convergence_strategy"]["type"] == "custom": raise NotImplementedError( 'convergence_strategy is set to "custom"; ' - 'you need to implement your specific convergence treatment') + "you need to implement your specific convergence treatment" + ) else: - raise ValueError('convergence_strategy is not "damped" ' - 'or "custom"') + raise ValueError( + 'convergence_strategy is not "damped" ' 'or "custom"' + ) - enable_full_relativity = montecarlo_section['enable_full_relativity'] + enable_full_relativity = montecarlo_section["enable_full_relativity"] spectrum_integrated = ( - validated_config_dict['spectrum']['method'] == 'integrated' + validated_config_dict["spectrum"]["method"] == "integrated" ) if enable_full_relativity and spectrum_integrated: raise NotImplementedError( @@ -301,6 +317,5 @@ def from_config_dict(cls, config_dict, validate=True, config_dirname=''): return cls(validated_config_dict) - def __init__(self, config_dict): super(Configuration, self).__init__(config_dict) diff --git a/tardis/io/config_validator.py b/tardis/io/config_validator.py index fa1584e1755..8b23aefc8c2 100644 --- a/tardis/io/config_validator.py +++ b/tardis/io/config_validator.py @@ -6,8 +6,8 @@ from tardis.io.util import YAMLLoader base_dir = os.path.abspath(os.path.dirname(__file__)) -schema_dir = os.path.join(base_dir, 'schemas') -config_schema_file = os.path.join(schema_dir, 'base.yml') +schema_dir = os.path.join(base_dir, "schemas") +config_schema_file = os.path.join(schema_dir, "base.yml") def extend_with_default(validator_class): @@ -26,56 +26,55 @@ def extend_with_default(validator_class): The extended `jsonschema.IValidator` """ - validate_properties = validator_class.VALIDATORS['properties'] + validate_properties = validator_class.VALIDATORS["properties"] def set_defaults(validator, properties, instance, schema): # This validator also checks if default values # are of the correct type and properly sets default # values on schemas that use the oneOf keyword if not list( - validate_properties(validator, properties, instance, schema)): + validate_properties(validator, properties, instance, schema) + ): for property, subschema in properties.items(): - if 'default' in subschema: - instance.setdefault(property, subschema['default']) + if "default" in subschema: + instance.setdefault(property, subschema["default"]) for error in validate_properties( - validator, properties, instance, schema, + validator, properties, instance, schema, ): yield error - return validators.extend( - validator_class, {'properties': set_defaults}, - ) + return validators.extend(validator_class, {"properties": set_defaults},) DefaultDraft4Validator = extend_with_default(Draft4Validator) def _yaml_handler(path): - if not path.startswith('file://'): - raise Exception('Not a file URL: {}'.format(path)) - with open(path[len('file://'):]) as f: + if not path.startswith("file://"): + raise Exception("Not a file URL: {}".format(path)) + with open(path[len("file://") :]) as f: return yaml.load(f, Loader=YAMLLoader) -def validate_dict(config_dict, schemapath=config_schema_file, - validator=DefaultDraft4Validator): +def validate_dict( + config_dict, schemapath=config_schema_file, validator=DefaultDraft4Validator +): with open(schemapath) as f: schema = yaml.load(f, Loader=YAMLLoader) schemaurl = "file://" + schemapath - handlers = {'file': _yaml_handler} + handlers = {"file": _yaml_handler} resolver = RefResolver(schemaurl, schema, handlers=handlers) validated_dict = deepcopy(config_dict) - validator(schema=schema, - types={'quantity': (Quantity,)}, - resolver=resolver - ).validate(validated_dict) + validator( + schema=schema, types={"quantity": (Quantity,)}, resolver=resolver + ).validate(validated_dict) return validated_dict -def validate_yaml(configpath, schemapath=config_schema_file, - validator=DefaultDraft4Validator): +def validate_yaml( + configpath, schemapath=config_schema_file, validator=DefaultDraft4Validator +): with open(configpath) as f: config = yaml.load(f, Loader=YAMLLoader) return validate_dict(config, schemapath, validator) - diff --git a/tardis/io/decay.py b/tardis/io/decay.py index 570fd7e96e2..76ed37a450e 100644 --- a/tardis/io/decay.py +++ b/tardis/io/decay.py @@ -2,14 +2,15 @@ from pyne import nucname, material from astropy import units as u + class IsotopeAbundances(pd.DataFrame): _metadata = ["time_0"] def __init__(self, *args, **kwargs): - if 'time_0' in kwargs: - time_0 = kwargs['time_0'] - kwargs.pop('time_0') + if "time_0" in kwargs: + time_0 = kwargs["time_0"] + kwargs.pop("time_0") else: time_0 = 0 * u.d super(IsotopeAbundances, self).__init__(*args, **kwargs) @@ -22,8 +23,9 @@ def _constructor(self): def _update_material(self): self.comp_dicts = [dict() for i in range(len(self.columns))] for (atomic_number, mass_number), abundances in self.iterrows(): - nuclear_symbol = '%s%d'.format(nucname.name(atomic_number), - mass_number) + nuclear_symbol = "%s%d".format( + nucname.name(atomic_number), mass_number + ) for i in range(len(self.columns)): self.comp_dicts[i][nuclear_symbol] = abundances[i] @@ -31,14 +33,17 @@ def _update_material(self): def from_materials(cls, materials): multi_index_tuples = set([]) for material in materials: - multi_index_tuples.update([cls.id_to_tuple(key) - for key in material.keys()]) + multi_index_tuples.update( + [cls.id_to_tuple(key) for key in material.keys()] + ) index = pd.MultiIndex.from_tuples( - multi_index_tuples, names=['atomic_number', 'mass_number']) - + multi_index_tuples, names=["atomic_number", "mass_number"] + ) - abundances = pd.DataFrame(data=0.0, index=index, columns=range(len(materials))) + abundances = pd.DataFrame( + data=0.0, index=index, columns=range(len(materials)) + ) for i, material in enumerate(materials): for key, value in material.items(): @@ -46,14 +51,10 @@ def from_materials(cls, materials): return cls(abundances) - - - @staticmethod def id_to_tuple(atomic_id): return nucname.znum(atomic_id), nucname.anum(atomic_id) - def to_materials(self): """ Convert DataFrame to a list of materials interpreting the MultiIndex as @@ -68,14 +69,13 @@ def to_materials(self): comp_dicts = [dict() for i in range(len(self.columns))] for (atomic_number, mass_number), abundances in self.iterrows(): - nuclear_symbol = '{0:s}{1:d}'.format(nucname.name(atomic_number), - mass_number) + nuclear_symbol = "{0:s}{1:d}".format( + nucname.name(atomic_number), mass_number + ) for i in range(len(self.columns)): comp_dicts[i][nuclear_symbol] = abundances[i] return [material.Material(comp_dict) for comp_dict in comp_dicts] - - def decay(self, t): """ Decay the Model @@ -91,13 +91,15 @@ def decay(self, t): """ materials = self.to_materials() - t_second = u.Quantity(t, u.day).to(u.s).value - self.time_0.to(u.s).value + t_second = ( + u.Quantity(t, u.day).to(u.s).value - self.time_0.to(u.s).value + ) decayed_materials = [item.decay(t_second) for item in materials] for i in range(len(materials)): materials[i].update(decayed_materials[i]) df = IsotopeAbundances.from_materials(materials) df.sort_index(inplace=True) - return df + return df def as_atoms(self): """ @@ -107,7 +109,7 @@ def as_atoms(self): : merged isotope abundances """ - return self.groupby('atomic_number').sum() + return self.groupby("atomic_number").sum() def merge(self, other, normalize=True): """ @@ -124,7 +126,7 @@ def merge(self, other, normalize=True): """ isotope_abundance = self.as_atoms() isotope_abundance = isotope_abundance.fillna(0.0) - #Merge abundance and isotope dataframe + # Merge abundance and isotope dataframe modified_df = isotope_abundance.add(other, fill_value=0) if normalize: diff --git a/tardis/io/model_reader.py b/tardis/io/model_reader.py index 4812e1236fc..d8060e2de67 100644 --- a/tardis/io/model_reader.py +++ b/tardis/io/model_reader.py @@ -1,4 +1,4 @@ -#reading different model files +# reading different model files import warnings import numpy as np @@ -8,6 +8,7 @@ from pyne import nucname import logging + # Adding logging support logger = logging.getLogger(__name__) @@ -43,37 +44,62 @@ def read_density_file(filename, filetype): the array containing the densities """ - file_parsers = {'artis': read_artis_density, - 'simple_ascii': read_simple_ascii_density, - 'cmfgen_model': read_cmfgen_density} + file_parsers = { + "artis": read_artis_density, + "simple_ascii": read_simple_ascii_density, + "cmfgen_model": read_cmfgen_density, + } electron_densities = None temperature = None - if filetype == 'cmfgen_model': - (time_of_model, velocity, - unscaled_mean_densities, electron_densities, temperature) = read_cmfgen_density(filename) + if filetype == "cmfgen_model": + ( + time_of_model, + velocity, + unscaled_mean_densities, + electron_densities, + temperature, + ) = read_cmfgen_density(filename) else: - (time_of_model, velocity, - unscaled_mean_densities) = file_parsers[filetype](filename) + (time_of_model, velocity, unscaled_mean_densities) = file_parsers[ + filetype + ](filename) v_inner = velocity[:-1] v_outer = velocity[1:] invalid_volume_mask = (v_outer - v_inner) <= 0 if invalid_volume_mask.sum() > 0: - message = "\n".join(["cell {0:d}: v_inner {1:s}, v_outer " - "{2:s}".format(i, v_inner_i, v_outer_i) for i, - v_inner_i, v_outer_i in - zip(np.arange(len(v_outer))[invalid_volume_mask], - v_inner[invalid_volume_mask], - v_outer[invalid_volume_mask])]) - raise ConfigurationError("Invalid volume of following cell(s):\n" - "{:s}".format(message)) - - return time_of_model, velocity, unscaled_mean_densities, electron_densities, temperature - -def read_abundances_file(abundance_filename, abundance_filetype, - inner_boundary_index=None, outer_boundary_index=None): + message = "\n".join( + [ + "cell {0:d}: v_inner {1:s}, v_outer " + "{2:s}".format(i, v_inner_i, v_outer_i) + for i, v_inner_i, v_outer_i in zip( + np.arange(len(v_outer))[invalid_volume_mask], + v_inner[invalid_volume_mask], + v_outer[invalid_volume_mask], + ) + ] + ) + raise ConfigurationError( + "Invalid volume of following cell(s):\n" "{:s}".format(message) + ) + + return ( + time_of_model, + velocity, + unscaled_mean_densities, + electron_densities, + temperature, + ) + + +def read_abundances_file( + abundance_filename, + abundance_filetype, + inner_boundary_index=None, + outer_boundary_index=None, +): """ read different density file formats @@ -95,25 +121,29 @@ def read_abundances_file(abundance_filename, abundance_filetype, """ - file_parsers = {'simple_ascii': read_simple_ascii_abundances, - 'artis': read_simple_ascii_abundances, - 'cmfgen_model': read_cmfgen_composition, - 'custom_composition': read_csv_composition} + file_parsers = { + "simple_ascii": read_simple_ascii_abundances, + "artis": read_simple_ascii_abundances, + "cmfgen_model": read_cmfgen_composition, + "custom_composition": read_csv_composition, + } isotope_abundance = pd.DataFrame() if abundance_filetype in ["cmfgen_model", "custom_composition"]: index, abundances, isotope_abundance = file_parsers[abundance_filetype]( - abundance_filename) + abundance_filename + ) else: - index, abundances = file_parsers[abundance_filetype]( - abundance_filename) + index, abundances = file_parsers[abundance_filetype](abundance_filename) if outer_boundary_index is not None: outer_boundary_index_m1 = outer_boundary_index - 1 else: outer_boundary_index_m1 = None index = index[inner_boundary_index:outer_boundary_index] - abundances = abundances.loc[:, slice(inner_boundary_index, outer_boundary_index_m1)] + abundances = abundances.loc[ + :, slice(inner_boundary_index, outer_boundary_index_m1) + ] abundances.columns = np.arange(len(abundances.columns)) return index, abundances, isotope_abundance @@ -131,37 +161,45 @@ def read_uniform_abundances(abundances_section, no_of_shells): abundance: ~pandas.DataFrame isotope_abundance: ~pandas.DataFrame """ - abundance = pd.DataFrame(columns=np.arange(no_of_shells), - index=pd.Index(np.arange(1, 120), - name='atomic_number'), - dtype=np.float64) + abundance = pd.DataFrame( + columns=np.arange(no_of_shells), + index=pd.Index(np.arange(1, 120), name="atomic_number"), + dtype=np.float64, + ) isotope_index = pd.MultiIndex( - [[]] * 2, [[]] * 2, names=['atomic_number', 'mass_number']) - isotope_abundance = pd.DataFrame(columns=np.arange(no_of_shells), - index=isotope_index, - dtype=np.float64) + [[]] * 2, [[]] * 2, names=["atomic_number", "mass_number"] + ) + isotope_abundance = pd.DataFrame( + columns=np.arange(no_of_shells), index=isotope_index, dtype=np.float64 + ) for element_symbol_string in abundances_section: - if element_symbol_string == 'type': + if element_symbol_string == "type": continue try: if element_symbol_string in nucname.name_zz: z = nucname.name_zz[element_symbol_string] abundance.loc[z] = float( - abundances_section[element_symbol_string]) + abundances_section[element_symbol_string] + ) else: mass_no = nucname.anum(element_symbol_string) z = nucname.znum(element_symbol_string) isotope_abundance.loc[(z, mass_no), :] = float( - abundances_section[element_symbol_string]) + abundances_section[element_symbol_string] + ) except RuntimeError as err: raise RuntimeError( - "Abundances are not defined properly in config file : {}".format(err.args)) + "Abundances are not defined properly in config file : {}".format( + err.args + ) + ) return abundance, isotope_abundance + def read_simple_ascii_density(fname): """ Reading a density file of the following structure (example; lines starting with a hash will be ignored): @@ -192,14 +230,18 @@ def read_simple_ascii_density(fname): time_of_model_string = fh.readline().strip() time_of_model = parse_quantity(time_of_model_string) - data = recfromtxt(fname, skip_header=1, - names=('index', 'velocity', 'density'), - dtype=(int, float, float)) - velocity = (data['velocity'] * u.km / u.s).to('cm/s') - mean_density = (data['density'] * u.Unit('g/cm^3'))[1:] + data = recfromtxt( + fname, + skip_header=1, + names=("index", "velocity", "density"), + dtype=(int, float, float), + ) + velocity = (data["velocity"] * u.km / u.s).to("cm/s") + mean_density = (data["density"] * u.Unit("g/cm^3"))[1:] return time_of_model, velocity, mean_density + def read_artis_density(fname): """ Reading a density file of the following structure (example; lines starting with a hash will be ignored): @@ -231,18 +273,31 @@ def read_artis_density(fname): if i == 0: no_of_shells = np.int64(line.strip()) elif i == 1: - time_of_model = u.Quantity(float(line.strip()), 'day').to('s') + time_of_model = u.Quantity(float(line.strip()), "day").to("s") elif i == 2: break - artis_model_columns = ['index', 'velocities', 'mean_densities_0', 'ni56_fraction', 'co56_fraction', 'fe52_fraction', - 'cr48_fraction'] - artis_model = recfromtxt(fname, skip_header=2, usecols=(0, 1, 2, 4, 5, 6, 7), unpack=True, - dtype=[(item, np.float64) for item in artis_model_columns]) - - - velocity = u.Quantity(artis_model['velocities'], 'km/s').to('cm/s') - mean_density = u.Quantity(10 ** artis_model['mean_densities_0'], 'g/cm^3')[1:] + artis_model_columns = [ + "index", + "velocities", + "mean_densities_0", + "ni56_fraction", + "co56_fraction", + "fe52_fraction", + "cr48_fraction", + ] + artis_model = recfromtxt( + fname, + skip_header=2, + usecols=(0, 1, 2, 4, 5, 6, 7), + unpack=True, + dtype=[(item, np.float64) for item in artis_model_columns], + ) + + velocity = u.Quantity(artis_model["velocities"], "km/s").to("cm/s") + mean_density = u.Quantity(10 ** artis_model["mean_densities_0"], "g/cm^3")[ + 1: + ] return time_of_model, velocity, mean_density @@ -282,26 +337,35 @@ def read_cmfgen_density(fname): temperature: ~np.ndarray """ - warnings.warn("The current CMFGEN model parser is deprecated", - DeprecationWarning) + warnings.warn( + "The current CMFGEN model parser is deprecated", DeprecationWarning + ) - df = pd.read_csv(fname, comment='#', delimiter=r'\s+', skiprows=[0, 2]) + df = pd.read_csv(fname, comment="#", delimiter=r"\s+", skiprows=[0, 2]) with open(fname) as fh: for row_index, line in enumerate(fh): if row_index == 0: - time_of_model_string = line.strip().replace('t0:', '') + time_of_model_string = line.strip().replace("t0:", "") time_of_model = parse_quantity(time_of_model_string) elif row_index == 2: quantities = line.split() - velocity = u.Quantity(df['velocity'].values, quantities[1]).to('cm/s') - temperature = u.Quantity(df['temperature'].values, quantities[2])[1:] - mean_density = u.Quantity(df['densities'].values, quantities[3])[1:] + velocity = u.Quantity(df["velocity"].values, quantities[1]).to("cm/s") + temperature = u.Quantity(df["temperature"].values, quantities[2])[1:] + mean_density = u.Quantity(df["densities"].values, quantities[3])[1:] electron_densities = u.Quantity( - df['electron_densities'].values, quantities[4])[1:] + df["electron_densities"].values, quantities[4] + )[1:] + + return ( + time_of_model, + velocity, + mean_density, + electron_densities, + temperature, + ) - return time_of_model, velocity, mean_density, electron_densities, temperature def read_simple_ascii_abundances(fname): """ @@ -327,13 +391,15 @@ def read_simple_ascii_abundances(fname): """ data = np.loadtxt(fname) - index = data[1:,0].astype(int) - abundances = pd.DataFrame(data[1:,1:].transpose(), index=np.arange(1, data.shape[1])) + index = data[1:, 0].astype(int) + abundances = pd.DataFrame( + data[1:, 1:].transpose(), index=np.arange(1, data.shape[1]) + ) return index, abundances -def read_cmfgen_composition(fname, delimiter=r'\s+'): +def read_cmfgen_composition(fname, delimiter=r"\s+"): """Read composition from a CMFGEN model file The CMFGEN file format contains information about the ejecta state in the @@ -346,14 +412,16 @@ def read_cmfgen_composition(fname, delimiter=r'\s+'): filename of the csv file """ - warnings.warn("The current CMFGEN model parser is deprecated", - DeprecationWarning) + warnings.warn( + "The current CMFGEN model parser is deprecated", DeprecationWarning + ) - return read_csv_isotope_abundances(fname, delimiter=delimiter, - skip_columns=4, skip_rows=[0, 2, 3]) + return read_csv_isotope_abundances( + fname, delimiter=delimiter, skip_columns=4, skip_rows=[0, 2, 3] + ) -def read_csv_composition(fname, delimiter=r'\s+'): +def read_csv_composition(fname, delimiter=r"\s+"): """Read composition from a simple CSV file The CSV file can contain specific isotopes or elemental abundances in the @@ -369,12 +437,14 @@ def read_csv_composition(fname, delimiter=r'\s+'): filename of the csv file """ - return read_csv_isotope_abundances(fname, delimiter=delimiter, - skip_columns=0, skip_rows=[1]) + return read_csv_isotope_abundances( + fname, delimiter=delimiter, skip_columns=0, skip_rows=[1] + ) -def read_csv_isotope_abundances(fname, delimiter=r'\s+', skip_columns=0, - skip_rows=[1]): +def read_csv_isotope_abundances( + fname, delimiter=r"\s+", skip_columns=0, skip_rows=[1] +): """ A generic parser for a TARDIS composition stored as a CSV file @@ -413,20 +483,23 @@ def read_csv_isotope_abundances(fname, delimiter=r'\s+', skip_columns=0, isotope_abundance: ~pandas.MultiIndex """ - df = pd.read_csv(fname, comment='#', - sep=delimiter, skiprows=skip_rows, index_col=0) + df = pd.read_csv( + fname, comment="#", sep=delimiter, skiprows=skip_rows, index_col=0 + ) df = df.transpose() - abundance = pd.DataFrame(columns=np.arange(df.shape[1]), - index=pd.Index([], - name='atomic_number'), - dtype=np.float64) + abundance = pd.DataFrame( + columns=np.arange(df.shape[1]), + index=pd.Index([], name="atomic_number"), + dtype=np.float64, + ) isotope_index = pd.MultiIndex( - [[]] * 2, [[]] * 2, names=['atomic_number', 'mass_number']) - isotope_abundance = pd.DataFrame(columns=np.arange(df.shape[1]), - index=isotope_index, - dtype=np.float64) + [[]] * 2, [[]] * 2, names=["atomic_number", "mass_number"] + ) + isotope_abundance = pd.DataFrame( + columns=np.arange(df.shape[1]), index=isotope_index, dtype=np.float64 + ) for element_symbol_string in df.index[skip_columns:]: if element_symbol_string in nucname.name_zz: @@ -435,11 +508,13 @@ def read_csv_isotope_abundances(fname, delimiter=r'\s+', skip_columns=0, else: z = nucname.znum(element_symbol_string) mass_no = nucname.anum(element_symbol_string) - isotope_abundance.loc[( - z, mass_no), :] = df.loc[element_symbol_string].tolist() + isotope_abundance.loc[(z, mass_no), :] = df.loc[ + element_symbol_string + ].tolist() return abundance.index, abundance, isotope_abundance + def parse_csv_abundances(csvy_data): """ A parser for the csv data part of a csvy model file. This function filters out columns that are not abundances. @@ -457,21 +532,27 @@ def parse_csv_abundances(csvy_data): isotope_abundance : ~pandas.MultiIndex """ - abundance_col_names = [name for name in csvy_data.columns if nucname.iselement(name) or nucname.isnuclide(name)] + abundance_col_names = [ + name + for name in csvy_data.columns + if nucname.iselement(name) or nucname.isnuclide(name) + ] df = csvy_data.loc[:, abundance_col_names] - + df = df.transpose() - abundance = pd.DataFrame(columns=np.arange(df.shape[1]), - index=pd.Index([], - name='atomic_number'), - dtype=np.float64) + abundance = pd.DataFrame( + columns=np.arange(df.shape[1]), + index=pd.Index([], name="atomic_number"), + dtype=np.float64, + ) isotope_index = pd.MultiIndex( - [[]] * 2, [[]] * 2, names=['atomic_number', 'mass_number']) - isotope_abundance = pd.DataFrame(columns=np.arange(df.shape[1]), - index=isotope_index, - dtype=np.float64) + [[]] * 2, [[]] * 2, names=["atomic_number", "mass_number"] + ) + isotope_abundance = pd.DataFrame( + columns=np.arange(df.shape[1]), index=isotope_index, dtype=np.float64 + ) for element_symbol_string in df.index[0:]: if element_symbol_string in nucname.name_zz: @@ -480,7 +561,8 @@ def parse_csv_abundances(csvy_data): else: z = nucname.znum(element_symbol_string) mass_no = nucname.anum(element_symbol_string) - isotope_abundance.loc[( - z, mass_no), :] = df.loc[element_symbol_string].tolist() + isotope_abundance.loc[(z, mass_no), :] = df.loc[ + element_symbol_string + ].tolist() return abundance.index, abundance, isotope_abundance diff --git a/tardis/io/parsers/__init__.py b/tardis/io/parsers/__init__.py index a5bdddb0a28..ef3825cde24 100644 --- a/tardis/io/parsers/__init__.py +++ b/tardis/io/parsers/__init__.py @@ -1,3 +1,4 @@ -from tardis.io.parsers.blondin_toymodel import (read_blondin_toymodel, - convert_blondin_toymodel) - +from tardis.io.parsers.blondin_toymodel import ( + read_blondin_toymodel, + convert_blondin_toymodel, +) diff --git a/tardis/io/parsers/blondin_toymodel.py b/tardis/io/parsers/blondin_toymodel.py index b6dece9f488..de9ff79cb03 100644 --- a/tardis/io/parsers/blondin_toymodel.py +++ b/tardis/io/parsers/blondin_toymodel.py @@ -9,8 +9,8 @@ from tardis.util.base import parse_quantity -PATTERN_REMOVE_BRACKET = re.compile(r'\[.+\]') -T0_PATTERN = re.compile('tend = (.+)\n') +PATTERN_REMOVE_BRACKET = re.compile(r"\[.+\]") +T0_PATTERN = re.compile("tend = (.+)\n") def read_blondin_toymodel(fname): @@ -32,64 +32,95 @@ def read_blondin_toymodel(fname): blondin_csv: pandas.DataFrame DataFrame containing the csv part of the toymodel """ - with open(fname, 'r') as fh: + with open(fname, "r") as fh: for line in fh: if line.startswith("#idx"): break else: raise ValueError( - 'File {0} does not conform to Toy Model format as it does ' - 'not contain #idx') - columns = [PATTERN_REMOVE_BRACKET.sub('', item) for item in - line[1:].split()] - - raw_blondin_csv = pd.read_csv(fname, delim_whitespace=True, comment='#', - header=None, names=columns) - raw_blondin_csv.set_index('idx', inplace=True) - - blondin_csv = raw_blondin_csv.loc[:, - ['vel', 'dens', 'temp', 'X_56Ni0', 'X_Ti', 'X_Ca', 'X_S', - 'X_Si', 'X_O', 'X_C']] - rename_col_dict = {'vel': 'velocity', 'dens': 'density', - 'temp': 't_electron'} + "File {0} does not conform to Toy Model format as it does " + "not contain #idx" + ) + columns = [ + PATTERN_REMOVE_BRACKET.sub("", item) for item in line[1:].split() + ] + + raw_blondin_csv = pd.read_csv( + fname, delim_whitespace=True, comment="#", header=None, names=columns + ) + raw_blondin_csv.set_index("idx", inplace=True) + + blondin_csv = raw_blondin_csv.loc[ + :, + [ + "vel", + "dens", + "temp", + "X_56Ni0", + "X_Ti", + "X_Ca", + "X_S", + "X_Si", + "X_O", + "X_C", + ], + ] + rename_col_dict = { + "vel": "velocity", + "dens": "density", + "temp": "t_electron", + } rename_col_dict.update({item: item[2:] for item in blondin_csv.columns[3:]}) - rename_col_dict['X_56Ni0'] = 'Ni56' + rename_col_dict["X_56Ni0"] = "Ni56" blondin_csv.rename(columns=rename_col_dict, inplace=True) blondin_csv.iloc[:, 3:] = blondin_csv.iloc[:, 3:].divide( - blondin_csv.iloc[:, 3:].sum(axis=1), axis=0) + blondin_csv.iloc[:, 3:].sum(axis=1), axis=0 + ) # changing velocities to outer boundary - new_velocities = 0.5 * (blondin_csv.velocity.iloc[ - :-1].values + blondin_csv.velocity.iloc[1:].values) + new_velocities = 0.5 * ( + blondin_csv.velocity.iloc[:-1].values + + blondin_csv.velocity.iloc[1:].values + ) new_velocities = np.hstack( - (new_velocities, [2 * new_velocities[-1] - new_velocities[-2]])) - blondin_csv['velocity'] = new_velocities + (new_velocities, [2 * new_velocities[-1] - new_velocities[-2]]) + ) + blondin_csv["velocity"] = new_velocities - with open(fname, 'r') as fh: + with open(fname, "r") as fh: t0_string = T0_PATTERN.findall(fh.read())[0] - t0 = parse_quantity(t0_string.replace('DAYS', 'day')) + t0 = parse_quantity(t0_string.replace("DAYS", "day")) blondin_dict = {} - blondin_dict['model_density_time_0'] = str(t0) - blondin_dict['description'] = 'Converted {0} to csvy format'.format(fname) - blondin_dict['tardis_model_config_version'] = 'v1.0' - blondin_dict_fields = [dict(name='velocity', unit='km/s', - desc='velocities of shell outer bounderies.')] + blondin_dict["model_density_time_0"] = str(t0) + blondin_dict["description"] = "Converted {0} to csvy format".format(fname) + blondin_dict["tardis_model_config_version"] = "v1.0" + blondin_dict_fields = [ + dict( + name="velocity", + unit="km/s", + desc="velocities of shell outer bounderies.", + ) + ] blondin_dict_fields.append( - dict(name='density', unit='g/cm^3', desc='mean density of shell.')) + dict(name="density", unit="g/cm^3", desc="mean density of shell.") + ) blondin_dict_fields.append( - dict(name='t_electron', unit='K', desc='electron temperature.')) + dict(name="t_electron", unit="K", desc="electron temperature.") + ) for abund in blondin_csv.columns[3:]: blondin_dict_fields.append( - dict(name=abund, desc='Fraction {0} abundance'.format(abund))) - blondin_dict['datatype'] = {'fields': blondin_dict_fields} + dict(name=abund, desc="Fraction {0} abundance".format(abund)) + ) + blondin_dict["datatype"] = {"fields": blondin_dict_fields} return blondin_dict, blondin_csv -def convert_blondin_toymodel(in_fname, out_fname, v_inner, v_outer, - conversion_t_electron_rad=None): +def convert_blondin_toymodel( + in_fname, out_fname, v_inner, v_outer, conversion_t_electron_rad=None +): """ Parameters @@ -109,25 +140,29 @@ def convert_blondin_toymodel(in_fname, out_fname, v_inner, v_outer, outer boundary velocity. If float will be interpreted as km/s """ blondin_dict, blondin_csv = read_blondin_toymodel(in_fname) - blondin_dict['v_inner_boundary'] = str(u.Quantity(v_inner, u.km / u.s)) - blondin_dict['v_outer_boundary'] = str(u.Quantity(v_outer, u.km / u.s)) + blondin_dict["v_inner_boundary"] = str(u.Quantity(v_inner, u.km / u.s)) + blondin_dict["v_outer_boundary"] = str(u.Quantity(v_outer, u.km / u.s)) if conversion_t_electron_rad is not None: - blondin_dict['datatype']['fields'].append({ - 'desc': - 'converted radiation temperature ' - 'using multiplicative factor={0}'.format( - conversion_t_electron_rad), - 'name': 't_rad', 'unit': 'K'}) - - blondin_csv['t_rad'] = (conversion_t_electron_rad * - blondin_csv.t_electron) - - - csvy_file = '---\n{0}\n---\n{1}'.format( + blondin_dict["datatype"]["fields"].append( + { + "desc": "converted radiation temperature " + "using multiplicative factor={0}".format( + conversion_t_electron_rad + ), + "name": "t_rad", + "unit": "K", + } + ) + + blondin_csv["t_rad"] = ( + conversion_t_electron_rad * blondin_csv.t_electron + ) + + csvy_file = "---\n{0}\n---\n{1}".format( yaml.dump(blondin_dict, default_flow_style=False), - blondin_csv.to_csv(index=False)) + blondin_csv.to_csv(index=False), + ) - with open(out_fname, 'w') as fh: + with open(out_fname, "w") as fh: fh.write(csvy_file) - diff --git a/tardis/io/parsers/csvy.py b/tardis/io/parsers/csvy.py index f0e23e81552..fcb5c175019 100644 --- a/tardis/io/parsers/csvy.py +++ b/tardis/io/parsers/csvy.py @@ -2,7 +2,9 @@ import pandas as pd from tardis.io.util import YAMLLoader -YAML_DELIMITER = '---' +YAML_DELIMITER = "---" + + def load_csvy(fname): """ Parameters @@ -24,14 +26,16 @@ def load_csvy(fname): yaml_end_ind = -1 for i, line in enumerate(fh): if i == 0: - assert line.strip() == YAML_DELIMITER, 'First line of csvy file is not \'---\'' + assert ( + line.strip() == YAML_DELIMITER + ), "First line of csvy file is not '---'" yaml_lines.append(line) if i > 0 and line.strip() == YAML_DELIMITER: yaml_end_ind = i break else: - raise ValueError('End %s not found'%(YAML_DELIMITER)) - yaml_dict = yaml.load(''.join(yaml_lines[1:-1]), YAMLLoader) + raise ValueError("End %s not found" % (YAML_DELIMITER)) + yaml_dict = yaml.load("".join(yaml_lines[1:-1]), YAMLLoader) try: data = pd.read_csv(fname, skiprows=yaml_end_ind + 1) except pd.io.common.EmptyDataError as e: @@ -39,6 +43,7 @@ def load_csvy(fname): return yaml_dict, data + def load_yaml_from_csvy(fname): """ Parameters @@ -57,16 +62,19 @@ def load_yaml_from_csvy(fname): yaml_end_ind = -1 for i, line in enumerate(fh): if i == 0: - assert line.strip() == YAML_DELIMITER, 'First line of csvy file is not \'---\'' + assert ( + line.strip() == YAML_DELIMITER + ), "First line of csvy file is not '---'" yaml_lines.append(line) if i > 0 and line.strip() == YAML_DELIMITER: yaml_end_ind = i break else: - raise ValueError('End %s not found'%(YAML_DELIMITER)) - yaml_dict = yaml.load(''.join(yaml_lines[1:-1]), YAMLLoader) + raise ValueError("End %s not found" % (YAML_DELIMITER)) + yaml_dict = yaml.load("".join(yaml_lines[1:-1]), YAMLLoader) return yaml_dict + def load_csv_from_csvy(fname): """ Parameters diff --git a/tardis/io/parsers/stella.py b/tardis/io/parsers/stella.py index 8d0119bf730..b102e4c1897 100644 --- a/tardis/io/parsers/stella.py +++ b/tardis/io/parsers/stella.py @@ -3,14 +3,18 @@ from astropy import units as u import numpy as np + def read_stella_data(filename): with open(filename) as fh: col = fh.readlines()[5] - col_names = re.split(r'\s{3,}', col.strip()) - col_names = [re.sub(r'\s\(.+\)', '', col_name).replace(' ', '_') for - col_name in col_names] - data = pd.read_csv(filename, skiprows=7, delim_whitespace=True, - names = col_names) + col_names = re.split(r"\s{3,}", col.strip()) + col_names = [ + re.sub(r"\s\(.+\)", "", col_name).replace(" ", "_") + for col_name in col_names + ] + data = pd.read_csv( + filename, skiprows=7, delim_whitespace=True, names=col_names + ) # drop last row of data data = data.iloc[0:-1] diff --git a/tardis/io/setup_package.py b/tardis/io/setup_package.py index 4c9cbe860ba..c12c8607825 100644 --- a/tardis/io/setup_package.py +++ b/tardis/io/setup_package.py @@ -1,6 +1,24 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst + def get_package_data(): - return {'tardis.io.tests': ['data/*.dat', 'data/*.yml', 'data/*.csv', 'data/*.csvy'], - 'tardis.model.tests' : ['data/*.dat', 'data/*.yml', 'data/*.csv', 'data/*.csvy'], - 'tardis.plasma.tests' : ['data/*.dat', 'data/*.yml', 'data/*.csv', 'data/*.txt']} + return { + "tardis.io.tests": [ + "data/*.dat", + "data/*.yml", + "data/*.csv", + "data/*.csvy", + ], + "tardis.model.tests": [ + "data/*.dat", + "data/*.yml", + "data/*.csv", + "data/*.csvy", + ], + "tardis.plasma.tests": [ + "data/*.dat", + "data/*.yml", + "data/*.csv", + "data/*.txt", + ], + } diff --git a/tardis/io/tests/test_HDFWriter.py b/tardis/io/tests/test_HDFWriter.py index e072e873ab1..eafd59228de 100644 --- a/tardis/io/tests/test_HDFWriter.py +++ b/tardis/io/tests/test_HDFWriter.py @@ -11,123 +11,155 @@ from tardis.io.util import HDFWriterMixin -#Test Cases +# Test Cases + +# DataFrame +# None +# Numpy Arrays +# Strings +# Numeric Values +# Pandas Series Object +# MultiIndex Object +# Quantity Objects with - Numeric Values, Numpy Arrays, DataFrame, Pandas Series, None objects -#DataFrame -#None -#Numpy Arrays -#Strings -#Numeric Values -#Pandas Series Object -#MultiIndex Object -#Quantity Objects with - Numeric Values, Numpy Arrays, DataFrame, Pandas Series, None objects class MockHDF(HDFWriterMixin): - hdf_properties = ['property'] + hdf_properties = ["property"] def __init__(self, property): self.property = property -simple_objects = [1.5, 'random_string', 4.2e7] + +simple_objects = [1.5, "random_string", 4.2e7] + @pytest.mark.parametrize("attr", simple_objects) def test_simple_write(tmpdir, attr): - fname = str(tmpdir.mkdir('data').join('test.hdf')) + fname = str(tmpdir.mkdir("data").join("test.hdf")) actual = MockHDF(attr) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/scalars')['property'] + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/scalars")["property"] assert actual.property == expected -mock_df = pd.DataFrame({'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']), - 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}) -complex_objects = [np.array([4.0e14, 2, 2e14, 27.5]), - pd.Series([1., 2., 3.]), mock_df] + +mock_df = pd.DataFrame( + { + "one": pd.Series([1.0, 2.0, 3.0], index=["a", "b", "c"]), + "two": pd.Series([1.0, 2.0, 3.0, 4.0], index=["a", "b", "c", "d"]), + } +) +complex_objects = [ + np.array([4.0e14, 2, 2e14, 27.5]), + pd.Series([1.0, 2.0, 3.0]), + mock_df, +] + @pytest.mark.parametrize("attr", complex_objects) def test_complex_obj_write(tmpdir, attr): - fname = str(tmpdir.mkdir('data').join('test.hdf')) + fname = str(tmpdir.mkdir("data").join("test.hdf")) actual = MockHDF(attr) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/property').values + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/property").values assert_array_almost_equal(actual.property, expected) -arr = np.array([['L1', 'L1', 'L2', 'L2', 'L3', 'L3', 'L4', 'L4'], - ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]) + +arr = np.array( + [ + ["L1", "L1", "L2", "L2", "L3", "L3", "L4", "L4"], + ["one", "two", "one", "two", "one", "two", "one", "two"], + ] +) mock_multiIndex = pd.MultiIndex.from_arrays(arr.transpose()) + def test_MultiIndex_write(tmpdir): - fname = str(tmpdir.mkdir('data').join('test.hdf')) + fname = str(tmpdir.mkdir("data").join("test.hdf")) actual = MockHDF(mock_multiIndex) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/property') + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/property") expected = pd.MultiIndex.from_tuples(expected.unstack().values) pdt.assert_almost_equal(actual.property, expected) -#Test Quantity Objects + +# Test Quantity Objects quantity_objects = [np.array([4.0e14, 2, 2e14, 27.5]), mock_df] + @pytest.mark.parametrize("attr", quantity_objects) def test_quantity_objects_write(tmpdir, attr): - fname = str(tmpdir.mkdir('data').join('test.hdf')) - attr_quantity = u.Quantity(attr, 'g/cm**3') + fname = str(tmpdir.mkdir("data").join("test.hdf")) + attr_quantity = u.Quantity(attr, "g/cm**3") actual = MockHDF(attr_quantity) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/property') + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/property") assert_array_almost_equal(actual.property.cgs.value, expected) + scalar_quantity_objects = [1.5, 4.2e7] + @pytest.mark.parametrize("attr", scalar_quantity_objects) def test_scalar_quantity_objects_write(tmpdir, attr): - fname = str(tmpdir.mkdir('data').join('test.hdf')) - attr_quantity = u.Quantity(attr, 'g/cm**3') + fname = str(tmpdir.mkdir("data").join("test.hdf")) + attr_quantity = u.Quantity(attr, "g/cm**3") actual = MockHDF(attr_quantity) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/scalars/')['property'] + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/scalars/")["property"] assert_array_almost_equal(actual.property.cgs.value, expected) + def test_none_write(tmpdir): - fname = str(tmpdir.mkdir('data').join('test.hdf')) + fname = str(tmpdir.mkdir("data").join("test.hdf")) actual = MockHDF(None) - actual.to_hdf(fname, path='test') - expected = pd.read_hdf(fname, key='/test/mock_hdf/scalars/')['property'] - if expected == 'none': + actual.to_hdf(fname, path="test") + expected = pd.read_hdf(fname, key="/test/mock_hdf/scalars/")["property"] + if expected == "none": expected = None assert actual.property == expected + # Test class_properties parameter (like homologous_density is a class # instance/object inside Model class) + class MockClass(HDFWriterMixin): - hdf_properties = ['property', 'nested_object'] + hdf_properties = ["property", "nested_object"] def __init__(self, property, nested_object): self.property = property self.nested_object = nested_object + @pytest.mark.parametrize("attr", quantity_objects) def test_objects_write(tmpdir, attr): - fname = str(tmpdir.mkdir('data').join('test.hdf')) + fname = str(tmpdir.mkdir("data").join("test.hdf")) nested_object = MockHDF(np.array([4.0e14, 2, 2e14, 27.5])) - attr_quantity = u.Quantity(attr, 'g/cm**3') + attr_quantity = u.Quantity(attr, "g/cm**3") actual = MockClass(attr_quantity, nested_object) - actual.to_hdf(fname, path='test') - expected_property = pd.read_hdf(fname, key='/test/mock_class/property') + actual.to_hdf(fname, path="test") + expected_property = pd.read_hdf(fname, key="/test/mock_class/property") assert_array_almost_equal(actual.property.cgs.value, expected_property) nested_property = pd.read_hdf( - fname, key='/test/mock_class/nested_object/property') - assert_array_almost_equal( - actual.nested_object.property, nested_property) + fname, key="/test/mock_class/nested_object/property" + ) + assert_array_almost_equal(actual.nested_object.property, nested_property) def test_snake_case(): - assert MockHDF.convert_to_snake_case( - "HomologousDensity") == "homologous_density" + assert ( + MockHDF.convert_to_snake_case("HomologousDensity") + == "homologous_density" + ) assert MockHDF.convert_to_snake_case("TARDISSpectrum") == "tardis_spectrum" assert MockHDF.convert_to_snake_case("BasePlasma") == "base_plasma" assert MockHDF.convert_to_snake_case("LTEPlasma") == "lte_plasma" - assert MockHDF.convert_to_snake_case( - "MonteCarloRunner") == "monte_carlo_runner" - assert MockHDF.convert_to_snake_case( - "homologous_density") == "homologous_density" + assert ( + MockHDF.convert_to_snake_case("MonteCarloRunner") + == "monte_carlo_runner" + ) + assert ( + MockHDF.convert_to_snake_case("homologous_density") + == "homologous_density" + ) diff --git a/tardis/io/tests/test_ascii_readers.py b/tardis/io/tests/test_ascii_readers.py index 0e420291dbe..12f7519e9ca 100644 --- a/tardis/io/tests/test_ascii_readers.py +++ b/tardis/io/tests/test_ascii_readers.py @@ -13,29 +13,36 @@ def data_path(filename): data_dir = os.path.dirname(__file__) - return os.path.join(data_dir, 'data', filename) + return os.path.join(data_dir, "data", filename) def test_simple_ascii_density_reader_time(): - time_model, velocity, density = io.read_simple_ascii_density(data_path('tardis_simple_ascii_density_test.dat')) + time_model, velocity, density = io.read_simple_ascii_density( + data_path("tardis_simple_ascii_density_test.dat") + ) - assert time_model.unit.physical_type == 'time' + assert time_model.unit.physical_type == "time" npt.assert_almost_equal(time_model.to(u.day).value, 1.0) + def test_simple_ascii_density_reader_data(): - time_model, velocity, density = io.read_simple_ascii_density(data_path('tardis_simple_ascii_density_test.dat')) - assert velocity.unit == u.Unit('cm/s') + time_model, velocity, density = io.read_simple_ascii_density( + data_path("tardis_simple_ascii_density_test.dat") + ) + assert velocity.unit == u.Unit("cm/s") - npt.assert_allclose(velocity[3].value, 1.3e4*1e5) + npt.assert_allclose(velocity[3].value, 1.3e4 * 1e5) def test_simple_ascii_abundance_reader(): - index, abundances = io.read_simple_ascii_abundances(data_path('artis_abundances.dat')) + index, abundances = io.read_simple_ascii_abundances( + data_path("artis_abundances.dat") + ) npt.assert_almost_equal(abundances.loc[1, 0], 1.542953e-08) npt.assert_almost_equal(abundances.loc[14, 54], 0.21864420000000001) def test_ascii_reader_invalid_volumes(): with pytest.raises(io.model_reader.ConfigurationError): - io.read_density_file(data_path('invalid_artis_model.dat'), 'artis') + io.read_density_file(data_path("invalid_artis_model.dat"), "artis") diff --git a/tardis/io/tests/test_atomic.py b/tardis/io/tests/test_atomic.py index 75f77ded901..70de086d9b0 100644 --- a/tardis/io/tests/test_atomic.py +++ b/tardis/io/tests/test_atomic.py @@ -26,37 +26,37 @@ def lines(kurucz_atomic_data): def test_atom_data_basic_atom_data(basic_atom_data): - assert basic_atom_data.loc[2, 'symbol'] == 'He' + assert basic_atom_data.loc[2, "symbol"] == "He" assert_quantity_allclose( - basic_atom_data.at[2, 'mass'] * u.Unit('g'), - 4.002602 * const.u.cgs - ) + basic_atom_data.at[2, "mass"] * u.Unit("g"), 4.002602 * const.u.cgs + ) def test_atom_data_ionization_data(ionization_data): assert_quantity_allclose( - ionization_data.loc[(2, 1)] * u.Unit('erg'), - 24.587387936 * u.Unit('eV') + ionization_data.loc[(2, 1)] * u.Unit("erg"), 24.587387936 * u.Unit("eV") ) def test_atom_data_levels(levels): assert_quantity_allclose( - u.Quantity(levels.at[(2, 0, 2), 'energy'], u.Unit('erg')).to(u.Unit('cm-1'), equivalencies=u.spectral()), - 166277.542 * u.Unit('cm-1') + u.Quantity(levels.at[(2, 0, 2), "energy"], u.Unit("erg")).to( + u.Unit("cm-1"), equivalencies=u.spectral() + ), + 166277.542 * u.Unit("cm-1"), ) def test_atom_data_lines(lines): assert_quantity_allclose( - lines.at[(2, 0, 0, 6), 'wavelength_cm'] * u.Unit('cm'), - 584.335 * u.Unit('Angstrom') + lines.at[(2, 0, 0, 6), "wavelength_cm"] * u.Unit("cm"), + 584.335 * u.Unit("Angstrom"), ) def test_atomic_reprepare(kurucz_atomic_data): kurucz_atomic_data.prepare_atom_data([14, 20]) lines = kurucz_atomic_data.lines.reset_index() - assert lines['atomic_number'].isin([14, 20]).all() - assert len(lines.loc[lines['atomic_number'] == 14]) > 0 - assert len(lines.loc[lines['atomic_number'] == 20]) > 0 + assert lines["atomic_number"].isin([14, 20]).all() + assert len(lines.loc[lines["atomic_number"] == 14]) > 0 + assert len(lines.loc[lines["atomic_number"] == 20]) > 0 diff --git a/tardis/io/tests/test_config_reader.py b/tardis/io/tests/test_config_reader.py index 70034c4f493..7ebd13514e7 100644 --- a/tardis/io/tests/test_config_reader.py +++ b/tardis/io/tests/test_config_reader.py @@ -10,42 +10,50 @@ def data_path(filename): data_dir = os.path.dirname(__file__) - return os.path.abspath(os.path.join(data_dir, 'data', filename)) + return os.path.abspath(os.path.join(data_dir, "data", filename)) def test_convergence_section_parser(): - test_convergence_section = {'type': 'damped', - 'lock_t_inner_cyles': 1, - 't_inner_update_exponent': -0.5, - 'damping_constant': 0.5, - 'threshold': 0.05, - 'fraction': 0.8, - 'hold_iterations': 3, - 't_rad': {'damping_constant': 1.0}} + test_convergence_section = { + "type": "damped", + "lock_t_inner_cyles": 1, + "t_inner_update_exponent": -0.5, + "damping_constant": 0.5, + "threshold": 0.05, + "fraction": 0.8, + "hold_iterations": 3, + "t_rad": {"damping_constant": 1.0}, + } parsed_convergence_section = config_reader.parse_convergence_section( - test_convergence_section) + test_convergence_section + ) - assert_almost_equal(parsed_convergence_section['t_rad']['damping_constant'], - 1.0) + assert_almost_equal( + parsed_convergence_section["t_rad"]["damping_constant"], 1.0 + ) - assert_almost_equal(parsed_convergence_section['w']['damping_constant'], - 0.5) + assert_almost_equal( + parsed_convergence_section["w"]["damping_constant"], 0.5 + ) def test_from_config_dict(tardis_config_verysimple): - conf = Configuration.from_config_dict(tardis_config_verysimple, - validate=True, - config_dirname='test') - assert conf.config_dirname == 'test' - assert_almost_equal(conf.spectrum.start.value, - tardis_config_verysimple['spectrum']['start'].value) - assert_almost_equal(conf.spectrum.stop.value, - tardis_config_verysimple['spectrum']['stop'].value) - - tardis_config_verysimple['spectrum']['start'] = 'Invalid' + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) + assert conf.config_dirname == "test" + assert_almost_equal( + conf.spectrum.start.value, + tardis_config_verysimple["spectrum"]["start"].value, + ) + assert_almost_equal( + conf.spectrum.stop.value, + tardis_config_verysimple["spectrum"]["stop"].value, + ) + + tardis_config_verysimple["spectrum"]["start"] = "Invalid" with pytest.raises(ValidationError): - conf = Configuration.from_config_dict(tardis_config_verysimple, - validate=True, - config_dirname='test') - + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) diff --git a/tardis/io/tests/test_configuration_namespace.py b/tardis/io/tests/test_configuration_namespace.py index 6101eb5cf19..a44e17ec123 100644 --- a/tardis/io/tests/test_configuration_namespace.py +++ b/tardis/io/tests/test_configuration_namespace.py @@ -5,75 +5,75 @@ from numpy.testing import assert_almost_equal -simple_config_dict = {'a' : {'b' : {'param1' : 1, 'param2': [0, 1, 2 * u.km], - 'param3' : 4.0 * u.km}}} +simple_config_dict = { + "a": {"b": {"param1": 1, "param2": [0, 1, 2 * u.km], "param3": 4.0 * u.km}} +} + def data_path(filename): data_dir = os.path.dirname(__file__) - return os.path.join(data_dir, 'data', filename) - + return os.path.join(data_dir, "data", filename) def test_simple_configuration_namespace(): config_ns = ConfigurationNameSpace(simple_config_dict) assert config_ns.a.b.param1 == 1 config_ns.a.b.param1 = 2 - assert (config_ns['a']['b']['param1'] - == 2) + assert config_ns["a"]["b"]["param1"] == 2 - config_ns['a']['b']['param1'] = 3 + config_ns["a"]["b"]["param1"] = 3 assert config_ns.a.b.param1 == 3 + def test_quantity_configuration_namespace(): config_ns = ConfigurationNameSpace(simple_config_dict) config_ns.a.b.param3 = 3 - assert_almost_equal(config_ns['a']['b']['param3'].to(u.km).value, 3) - + assert_almost_equal(config_ns["a"]["b"]["param3"].to(u.km).value, 3) config_ns.a.b.param3 = 5000 * u.m - assert_almost_equal(config_ns['a']['b']['param3'].to(u.km).value, 5) - + assert_almost_equal(config_ns["a"]["b"]["param3"].to(u.km).value, 5) def test_access_with_config_item_string(): config_ns = ConfigurationNameSpace(simple_config_dict) - assert config_ns.get_config_item('a.b.param1') == 1 + assert config_ns.get_config_item("a.b.param1") == 1 - config_ns.set_config_item('a.b.param1', 2) + config_ns.set_config_item("a.b.param1", 2) assert config_ns.a.b.param1 == 2 + def test_set_with_config_item_string_quantity(): config_ns = ConfigurationNameSpace(simple_config_dict) - config_ns.set_config_item('a.b.param3', 2) + config_ns.set_config_item("a.b.param3", 2) assert_almost_equal(config_ns.a.b.param3.to(u.km).value, 2) def test_get_with_config_item_string_item_access(): config_ns = ConfigurationNameSpace(simple_config_dict) - item = config_ns.get_config_item('a.b.param2.item0') + item = config_ns.get_config_item("a.b.param2.item0") assert item == 0 - item = config_ns.get_config_item('a.b.param2.item1') + item = config_ns.get_config_item("a.b.param2.item1") assert item == 1 + def test_set_with_config_item_string_item_access(): config_ns = ConfigurationNameSpace(simple_config_dict) - config_ns.set_config_item('a.b.param2.item0', 2) + config_ns.set_config_item("a.b.param2.item0", 2) - item = config_ns.get_config_item('a.b.param2.item0') + item = config_ns.get_config_item("a.b.param2.item0") assert item == 2 + def test_set_with_config_item_string_item_access_quantity(): config_ns = ConfigurationNameSpace(simple_config_dict) - config_ns.set_config_item('a.b.param2.item2', 7 ) - - item = config_ns.get_config_item('a.b.param2.item2') - - assert_almost_equal(item.to(u.km).value ,7) + config_ns.set_config_item("a.b.param2.item2", 7) + item = config_ns.get_config_item("a.b.param2.item2") + assert_almost_equal(item.to(u.km).value, 7) def test_config_namespace_copy(): @@ -82,5 +82,6 @@ def test_config_namespace_copy(): config_ns2.a.b.param1 = 2 assert config_ns2.a.b.param1 != config_ns.a.b.param1 + def test_config_namespace_quantity_set(): - data_path('paper1_tardis_configv1.yml') \ No newline at end of file + data_path("paper1_tardis_configv1.yml") diff --git a/tardis/io/tests/test_csvy_reader.py b/tardis/io/tests/test_csvy_reader.py index e858f4ef538..7bb6e8762f3 100644 --- a/tardis/io/tests/test_csvy_reader.py +++ b/tardis/io/tests/test_csvy_reader.py @@ -8,38 +8,49 @@ import numpy.testing as npt -DATA_PATH = os.path.join(tardis.__path__[0], 'io', 'tests', 'data') +DATA_PATH = os.path.join(tardis.__path__[0], "io", "tests", "data") + @pytest.fixture def csvy_full_fname(): - return os.path.join(DATA_PATH, 'csvy_full.csvy') + return os.path.join(DATA_PATH, "csvy_full.csvy") + @pytest.fixture def csvy_nocsv_fname(): - return os.path.join(DATA_PATH, 'csvy_nocsv.csvy') + return os.path.join(DATA_PATH, "csvy_nocsv.csvy") + @pytest.fixture def csvy_missing_fname(): - return os.path.join(DATA_PATH, 'csvy_missing.csvy') + return os.path.join(DATA_PATH, "csvy_missing.csvy") + def test_csvy_finds_csv_first_line(csvy_full_fname): yaml_dict, csv = csvy.load_csvy(csvy_full_fname) - npt.assert_almost_equal(csv['velocity'][0],10000) + npt.assert_almost_equal(csv["velocity"][0], 10000) + def test_csv_colnames_equiv_datatype_fields(csvy_full_fname): yaml_dict, csv = csvy.load_csvy(csvy_full_fname) - datatype_names = [od['name'] for od in yaml_dict['datatype']['fields']] + datatype_names = [od["name"] for od in yaml_dict["datatype"]["fields"]] for key in csv.columns: assert key in datatype_names for name in datatype_names: assert name in csv.columns + def test_csvy_nocsv_data_is_none(csvy_nocsv_fname): yaml_dict, csv = csvy.load_csvy(csvy_nocsv_fname) assert csv is None + def test_missing_required_property(csvy_missing_fname): yaml_dict, csv = csvy.load_csvy(csvy_missing_fname) with pytest.raises(Exception): - vy = validate_dict(yaml_dict, schemapath=os.path.join(tardis.__path__[0], 'io', 'schemas', - 'csvy_model.yml')) + vy = validate_dict( + yaml_dict, + schemapath=os.path.join( + tardis.__path__[0], "io", "schemas", "csvy_model.yml" + ), + ) diff --git a/tardis/io/tests/test_decay.py b/tardis/io/tests/test_decay.py index 09c5abf4601..42856c47b78 100644 --- a/tardis/io/tests/test_decay.py +++ b/tardis/io/tests/test_decay.py @@ -4,12 +4,15 @@ from tardis.io.decay import IsotopeAbundances from numpy.testing import assert_almost_equal + @pytest.fixture def simple_abundance_model(): - index = pd.MultiIndex.from_tuples([(28, 56)], - names=['atomic_number', 'mass_number']) + index = pd.MultiIndex.from_tuples( + [(28, 56)], names=["atomic_number", "mass_number"] + ) return IsotopeAbundances([[1.0, 1.0]], index=index) + def test_simple_decay(simple_abundance_model): decayed_abundance = simple_abundance_model.decay(100) assert_almost_equal(decayed_abundance.loc[26, 56][0], 0.55752) @@ -19,18 +22,26 @@ def test_simple_decay(simple_abundance_model): assert_almost_equal(decayed_abundance.loc[28, 56][0], 1.1086e-05) assert_almost_equal(decayed_abundance.loc[28, 56][1], 1.1086e-05) + @pytest.fixture def raw_abundance_simple(): abundances = pd.DataFrame([[0.2, 0.2], [0.1, 0.1]], index=[28, 30]) - abundances.index.rename('atomic_number', inplace=True) + abundances.index.rename("atomic_number", inplace=True) return abundances + def test_abundance_merge(simple_abundance_model, raw_abundance_simple): decayed_df = simple_abundance_model.decay(100) isotope_df = decayed_df.as_atoms() combined_df = decayed_df.merge(raw_abundance_simple, normalize=False) - - assert_almost_equal(combined_df.loc[28][0], raw_abundance_simple.loc[28][0] + isotope_df.loc[28][0]) - assert_almost_equal(combined_df.loc[28][1], raw_abundance_simple.loc[28][1] + isotope_df.loc[28][1]) + + assert_almost_equal( + combined_df.loc[28][0], + raw_abundance_simple.loc[28][0] + isotope_df.loc[28][0], + ) + assert_almost_equal( + combined_df.loc[28][1], + raw_abundance_simple.loc[28][1] + isotope_df.loc[28][1], + ) assert_almost_equal(combined_df.loc[30][1], raw_abundance_simple.loc[30][1]) - assert_almost_equal(combined_df.loc[26][0], isotope_df.loc[26][0]) \ No newline at end of file + assert_almost_equal(combined_df.loc[26][0], isotope_df.loc[26][0]) diff --git a/tardis/io/tests/test_model_reader.py b/tardis/io/tests/test_model_reader.py index aa0b646716d..60a110b1a91 100644 --- a/tardis/io/tests/test_model_reader.py +++ b/tardis/io/tests/test_model_reader.py @@ -7,91 +7,119 @@ import tardis from tardis.io.config_reader import Configuration from tardis.io.model_reader import ( - read_artis_density, read_simple_ascii_abundances, read_csv_composition, read_uniform_abundances, read_cmfgen_density, read_cmfgen_composition) + read_artis_density, + read_simple_ascii_abundances, + read_csv_composition, + read_uniform_abundances, + read_cmfgen_density, + read_cmfgen_composition, +) + +data_path = os.path.join(tardis.__path__[0], "io", "tests", "data") -data_path = os.path.join(tardis.__path__[0], 'io', 'tests', 'data') @pytest.fixture def artis_density_fname(): - return os.path.join(data_path, 'artis_model.dat') + return os.path.join(data_path, "artis_model.dat") + @pytest.fixture def artis_abundances_fname(): - return os.path.join(data_path, 'artis_abundances.dat') + return os.path.join(data_path, "artis_abundances.dat") + @pytest.fixture def cmfgen_fname(): - return os.path.join(data_path, 'cmfgen_model.csv') + return os.path.join(data_path, "cmfgen_model.csv") + @pytest.fixture def csv_composition_fname(): - return os.path.join(data_path, 'csv_composition.csv') + return os.path.join(data_path, "csv_composition.csv") @pytest.fixture def isotope_uniform_abundance(): config_path = os.path.join( - data_path, 'tardis_configv1_isotope_uniabund.yml') + data_path, "tardis_configv1_isotope_uniabund.yml" + ) config = Configuration.from_yaml(config_path) return config.model.abundances + def test_simple_read_artis_density(artis_density_fname): - time_of_model, velocity, mean_density = read_artis_density(artis_density_fname) + time_of_model, velocity, mean_density = read_artis_density( + artis_density_fname + ) assert np.isclose(0.00114661 * u.day, time_of_model, atol=1e-7 * u.day) - assert np.isclose(mean_density[23], 0.2250048 * u.g / u.cm**3, atol=1.e-6 - * u.g / u.cm**3) + assert np.isclose( + mean_density[23], + 0.2250048 * u.g / u.cm ** 3, + atol=1.0e-6 * u.g / u.cm ** 3, + ) assert len(mean_density) == 69 assert len(velocity) == len(mean_density) + 1 + # Artis files are currently read with read ascii files function def test_read_simple_ascii_abundances(artis_abundances_fname): index, abundances = read_simple_ascii_abundances(artis_abundances_fname) assert len(abundances.columns) == 69 - assert np.isclose(abundances[23].loc[2], 2.672351e-08 , atol=1.e-12) + assert np.isclose(abundances[23].loc[2], 2.672351e-08, atol=1.0e-12) def test_read_simple_isotope_abundances(csv_composition_fname): index, abundances, isotope_abundance = read_csv_composition( - csv_composition_fname) - assert np.isclose(abundances.loc[6, 8], 0.5, atol=1.e-12) - assert np.isclose(abundances.loc[12, 5], 0.8, atol=1.e-12) - assert np.isclose(abundances.loc[14, 1], 0.1, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 56), 0], 0.4, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 58), 2], 0.7, atol=1.e-12) + csv_composition_fname + ) + assert np.isclose(abundances.loc[6, 8], 0.5, atol=1.0e-12) + assert np.isclose(abundances.loc[12, 5], 0.8, atol=1.0e-12) + assert np.isclose(abundances.loc[14, 1], 0.1, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 56), 0], 0.4, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 58), 2], 0.7, atol=1.0e-12) assert abundances.shape == (4, 10) assert isotope_abundance.shape == (2, 10) def test_read_cmfgen_isotope_abundances(cmfgen_fname): - index, abundances, isotope_abundance = read_cmfgen_composition( - cmfgen_fname) - assert np.isclose(abundances.loc[6, 8], 0.5, atol=1.e-12) - assert np.isclose(abundances.loc[12, 5], 0.8, atol=1.e-12) - assert np.isclose(abundances.loc[14, 1], 0.3, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 56), 0], 0.5, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 58), 1], 0.7, atol=1.e-12) + index, abundances, isotope_abundance = read_cmfgen_composition(cmfgen_fname) + assert np.isclose(abundances.loc[6, 8], 0.5, atol=1.0e-12) + assert np.isclose(abundances.loc[12, 5], 0.8, atol=1.0e-12) + assert np.isclose(abundances.loc[14, 1], 0.3, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 56), 0], 0.5, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 58), 1], 0.7, atol=1.0e-12) assert abundances.shape == (4, 9) assert isotope_abundance.shape == (2, 9) def test_read_uniform_abundances(isotope_uniform_abundance): abundances, isotope_abundance = read_uniform_abundances( - isotope_uniform_abundance, 20) - assert np.isclose(abundances.loc[8, 2], 0.19, atol=1.e-12) - assert np.isclose(abundances.loc[20, 5], 0.03, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 56), 15], 0.05, atol=1.e-12) - assert np.isclose(isotope_abundance.loc[(28, 58), 2], 0.05, atol=1.e-12) + isotope_uniform_abundance, 20 + ) + assert np.isclose(abundances.loc[8, 2], 0.19, atol=1.0e-12) + assert np.isclose(abundances.loc[20, 5], 0.03, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 56), 15], 0.05, atol=1.0e-12) + assert np.isclose(isotope_abundance.loc[(28, 58), 2], 0.05, atol=1.0e-12) def test_simple_read_cmfgen_density(cmfgen_fname): - time_of_model, velocity, mean_density, electron_densities, temperature = read_cmfgen_density( - cmfgen_fname) + ( + time_of_model, + velocity, + mean_density, + electron_densities, + temperature, + ) = read_cmfgen_density(cmfgen_fname) assert np.isclose(0.976 * u.day, time_of_model, atol=1e-7 * u.day) - assert np.isclose(mean_density[4], 4.2539537e-09 * u.g / u.cm**3, atol=1.e-6 - * u.g / u.cm**3) - assert np.isclose(electron_densities[5], 2.6e+14 * u.cm**-3, atol=1.e-6 - * u.cm**-3) + assert np.isclose( + mean_density[4], + 4.2539537e-09 * u.g / u.cm ** 3, + atol=1.0e-6 * u.g / u.cm ** 3, + ) + assert np.isclose( + electron_densities[5], 2.6e14 * u.cm ** -3, atol=1.0e-6 * u.cm ** -3 + ) assert len(mean_density) == 9 assert len(velocity) == len(mean_density) + 1 diff --git a/tardis/io/util.py b/tardis/io/util.py index 97e28613981..ef5c8723e66 100644 --- a/tardis/io/util.py +++ b/tardis/io/util.py @@ -1,4 +1,4 @@ -#Utility functions for the IO part of TARDIS +# Utility functions for the IO part of TARDIS import os import re @@ -21,7 +21,6 @@ logger = logging.getLogger(__name__) - def get_internal_data_path(fname): """ Get internal data path of TARDIS @@ -32,7 +31,8 @@ def get_internal_data_path(fname): internal data path of TARDIS """ - return os.path.join(TARDIS_PATH[0], 'data', fname) + return os.path.join(TARDIS_PATH[0], "data", fname) + def quantity_from_str(text): """ @@ -47,9 +47,9 @@ def quantity_from_str(text): """ value_str, unit_str = text.split(None, 1) value = float(value_str) - if unit_str.strip() == 'log_lsun': + if unit_str.strip() == "log_lsun": value = 10 ** (value + np.log10(constants.L_sun.cgs.value)) - unit_str = 'erg/s' + unit_str = "erg/s" unit = u.Unit(unit_str) if unit == u.L_sun: @@ -65,6 +65,7 @@ class MockRegexPattern(object): Note: This is usually a lot slower than regex matching. """ + def __init__(self, target_type): self.type = target_type @@ -115,13 +116,18 @@ def construct_quantity(self, node): def mapping_constructor(self, node): return OrderedDict(self.construct_pairs(node)) -YAMLLoader.add_constructor(u'!quantity', YAMLLoader.construct_quantity) -YAMLLoader.add_implicit_resolver(u'!quantity', - MockRegexPattern(quantity_from_str), None) -YAMLLoader.add_implicit_resolver(u'tag:yaml.org,2002:float', - MockRegexPattern(float), None) -YAMLLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, - YAMLLoader.mapping_constructor) + +YAMLLoader.add_constructor("!quantity", YAMLLoader.construct_quantity) +YAMLLoader.add_implicit_resolver( + "!quantity", MockRegexPattern(quantity_from_str), None +) +YAMLLoader.add_implicit_resolver( + "tag:yaml.org,2002:float", MockRegexPattern(float), None +) +YAMLLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + YAMLLoader.mapping_constructor, +) def yaml_load_file(filename, loader=yaml.Loader): @@ -154,7 +160,11 @@ def traverse_configs(base, other, func, *args): if isinstance(base, collections.Mapping): for k in base: traverse_configs(base[k], other[k], func, *args) - elif isinstance(base, collections.Iterable) and not isinstance(base, basestring) and not hasattr(base, 'shape'): + elif ( + isinstance(base, collections.Iterable) + and not isinstance(base, basestring) + and not hasattr(base, "shape") + ): for val1, val2 in zip(base, other): traverse_configs(val1, val2, func, *args) else: @@ -164,7 +174,7 @@ def traverse_configs(base, other, func, *args): def assert_equality(item1, item2): assert type(item1) is type(item2) try: - if hasattr(item1, 'unit'): + if hasattr(item1, "unit"): assert item1.unit == item2.unit assert np.allclose(item1, item2, atol=0.0) except (ValueError, TypeError): @@ -188,7 +198,7 @@ def __new__(cls, *args, **kwargs): return instance @staticmethod - def to_hdf_util(path_or_buf, path, elements, complevel=9, complib='blosc'): + def to_hdf_util(path_or_buf, path, elements, complevel=9, complib="blosc"): """ A function to uniformly store TARDIS data to an HDF file. @@ -216,13 +226,9 @@ def to_hdf_util(path_or_buf, path, elements, complevel=9, complib='blosc'): we_opened = False try: - buf = pd.HDFStore( - path_or_buf, - complevel=complevel, - complib=complib - ) + buf = pd.HDFStore(path_or_buf, complevel=complevel, complib=complib) except TypeError as e: # Already a HDFStore - if e.message == 'Expected bytes, got HDFStore': + if e.message == "Expected bytes, got HDFStore": buf = path_or_buf else: raise e @@ -236,23 +242,20 @@ def to_hdf_util(path_or_buf, path, elements, complevel=9, complib='blosc'): scalars = {} for key, value in elements.items(): if value is None: - value = 'none' - if hasattr(value, 'cgs'): + value = "none" + if hasattr(value, "cgs"): value = value.cgs.value if np.isscalar(value): scalars[key] = value - elif hasattr(value, 'shape'): + elif hasattr(value, "shape"): if value.ndim == 1: # This try,except block is only for model.plasma.levels try: - pd.Series(value).to_hdf(buf, - os.path.join(path, key)) + pd.Series(value).to_hdf(buf, os.path.join(path, key)) except NotImplementedError: - pd.DataFrame(value).to_hdf(buf, - os.path.join(path, key)) + pd.DataFrame(value).to_hdf(buf, os.path.join(path, key)) else: - pd.DataFrame(value).to_hdf( - buf, os.path.join(path, key)) + pd.DataFrame(value).to_hdf(buf, os.path.join(path, key)) else: try: value.to_hdf(buf, path, name=key) @@ -264,12 +267,12 @@ def to_hdf_util(path_or_buf, path, elements, complevel=9, complib='blosc'): scalars_series = pd.Series(scalars) # Unfortunately, with to_hdf we cannot append, so merge beforehand - scalars_path = os.path.join(path, 'scalars') + scalars_path = os.path.join(path, "scalars") try: scalars_series = buf[scalars_path].append(scalars_series) except KeyError: # no scalars in HDFStore pass - scalars_series.to_hdf(buf, os.path.join(path, 'scalars')) + scalars_series.to_hdf(buf, os.path.join(path, "scalars")) if we_opened: buf.close() @@ -284,10 +287,10 @@ def full_hdf_properties(self): @staticmethod def convert_to_snake_case(s): - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", s) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() - def to_hdf(self, file_path, path='', name=None): + def to_hdf(self, file_path, path="", name=None): """ Parameters ---------- @@ -314,27 +317,29 @@ def to_hdf(self, file_path, path='', name=None): class PlasmaWriterMixin(HDFWriterMixin): - def get_properties(self): data = {} if self.collection: - properties = [name for name in self.plasma_properties - if isinstance(name, tuple(self.collection))] + properties = [ + name + for name in self.plasma_properties + if isinstance(name, tuple(self.collection)) + ] else: properties = self.plasma_properties for prop in properties: for output in prop.outputs: data[output] = getattr(prop, output) - data['atom_data_uuid'] = self.atomic_data.uuid1 - if 'atomic_data' in data: - data.pop('atomic_data') - if 'nlte_data' in data: + data["atom_data_uuid"] = self.atomic_data.uuid1 + if "atomic_data" in data: + data.pop("atomic_data") + if "nlte_data" in data: logger.warning("nlte_data can't be saved") - data.pop('nlte_data') + data.pop("nlte_data") return data - def to_hdf(self, file_path, path='', name=None, collection=None): - ''' + def to_hdf(self, file_path, path="", name=None, collection=None): + """ Parameters ---------- file_path: str @@ -355,7 +360,7 @@ def to_hdf(self, file_path, path='', name=None, collection=None): Returns ------- - ''' + """ self.collection = collection super(PlasmaWriterMixin, self).to_hdf(file_path, path, name) @@ -376,10 +381,14 @@ def download_from_url(url, dst): return file_size header = {"Range": "bytes=%s-%s" % (first_byte, file_size)} pbar = tqdm( - total=file_size, initial=first_byte, - unit='B', unit_scale=True, desc=url.split('/')[-1]) + total=file_size, + initial=first_byte, + unit="B", + unit_scale=True, + desc=url.split("/")[-1], + ) req = requests.get(url, headers=header, stream=True) - with(open(dst, 'ab')) as f: + with (open(dst, "ab")) as f: for chunk in req.iter_content(chunk_size=1024): if chunk: f.write(chunk) diff --git a/tardis/plasma/base.py b/tardis/plasma/base.py index b90f3ab2ddd..56816bfb4a2 100644 --- a/tardis/plasma/base.py +++ b/tardis/plasma/base.py @@ -13,17 +13,20 @@ logger = logging.getLogger(__name__) + class BasePlasma(PlasmaWriterMixin): outputs_dict = {} - hdf_name = 'plasma' + hdf_name = "plasma" + def __init__(self, plasma_properties, property_kwargs=None, **kwargs): self.outputs_dict = {} self.input_properties = [] - self.plasma_properties = self._init_properties(plasma_properties, - property_kwargs, **kwargs) + self.plasma_properties = self._init_properties( + plasma_properties, property_kwargs, **kwargs + ) self._build_graph() -# self.write_to_tex('Plasma_Graph') + # self.write_to_tex('Plasma_Graph') self.update(**kwargs) def __getattr__(self, item): @@ -33,23 +36,24 @@ def __getattr__(self, item): super(BasePlasma, self).__getattribute__(item) def __setattr__(self, key, value): - if key != 'module_dict' and key in self.outputs_dict: - raise AttributeError('Plasma inputs can only be updated using ' - 'the \'update\' method') + if key != "module_dict" and key in self.outputs_dict: + raise AttributeError( + "Plasma inputs can only be updated using " "the 'update' method" + ) else: super(BasePlasma, self).__setattr__(key, value) def __dir__(self): - attrs = [item for item in self.__dict__ - if not item.startswith('_')] - attrs += [item for item in self.__class__.__dict__ - if not item.startswith('_')] + attrs = [item for item in self.__dict__ if not item.startswith("_")] + attrs += [ + item for item in self.__class__.__dict__ if not item.startswith("_") + ] attrs += self.outputs_dict.keys() return attrs @property def plasma_properties_dict(self): - return {item.name:item for item in self.plasma_properties} + return {item.name: item for item in self.plasma_properties} def get_value(self, item): return getattr(self.outputs_dict[item], item) @@ -64,36 +68,48 @@ def _build_graph(self): self.graph = nx.DiGraph() ## Adding all nodes - self.graph.add_nodes_from([(plasma_property.name, {}) - for plasma_property - in self.plasma_properties]) - - #Flagging all input modules - self.input_properties = [item for item in self.plasma_properties - if not hasattr(item, 'inputs')] + self.graph.add_nodes_from( + [ + (plasma_property.name, {}) + for plasma_property in self.plasma_properties + ] + ) + + # Flagging all input modules + self.input_properties = [ + item + for item in self.plasma_properties + if not hasattr(item, "inputs") + ] for plasma_property in self.plasma_properties: - #Skipping any module that is an input module + # Skipping any module that is an input module if plasma_property in self.input_properties: continue for input in plasma_property.inputs: if input not in self.outputs_dict: - raise PlasmaMissingModule('Module {0} requires input ' - '{1} which has not been added' - ' to this plasma'.format( - plasma_property.name, input)) + raise PlasmaMissingModule( + "Module {0} requires input " + "{1} which has not been added" + " to this plasma".format(plasma_property.name, input) + ) try: position = self.outputs_dict[input].outputs.index(input) label = self.outputs_dict[input].latex_name[position] - label = '$' + label + '$' - label = label.replace('\\', '\\\\') + label = "$" + label + "$" + label = label.replace("\\", "\\\\") except: - label = input.replace('_', '-') - self.graph.add_edge(self.outputs_dict[input].name, - plasma_property.name, label = label) - - def _init_properties(self, plasma_properties, property_kwargs=None, **kwargs): + label = input.replace("_", "-") + self.graph.add_edge( + self.outputs_dict[input].name, + plasma_property.name, + label=label, + ) + + def _init_properties( + self, plasma_properties, property_kwargs=None, **kwargs + ): """ Builds a dictionary with the plasma module names as keys @@ -120,25 +136,31 @@ def _init_properties(self, plasma_properties, property_kwargs=None, **kwargs): if issubclass(plasma_property, PreviousIterationProperty): current_property_object = plasma_property( - **property_kwargs.get(plasma_property, {})) + **property_kwargs.get(plasma_property, {}) + ) current_property_object.set_initial_value(kwargs) self.previous_iteration_properties.append( - current_property_object) + current_property_object + ) elif issubclass(plasma_property, Input): if not set(kwargs.keys()).issuperset(plasma_property.outputs): - missing_input_values = (set(plasma_property.outputs) - - set(kwargs.keys())) - raise NotInitializedModule('Input {0} required for ' - 'plasma but not given when ' - 'instantiating the ' - 'plasma'.format( - missing_input_values)) + missing_input_values = set(plasma_property.outputs) - set( + kwargs.keys() + ) + raise NotInitializedModule( + "Input {0} required for " + "plasma but not given when " + "instantiating the " + "plasma".format(missing_input_values) + ) current_property_object = plasma_property( - **property_kwargs.get(plasma_property, {})) + **property_kwargs.get(plasma_property, {}) + ) else: current_property_object = plasma_property( - self, **property_kwargs.get(plasma_property, {})) + self, **property_kwargs.get(plasma_property, {}) + ) for output in plasma_property.outputs: self.outputs_dict[output] = current_property_object plasma_property_objects.append(current_property_object) @@ -148,13 +170,16 @@ def store_previous_properties(self): for property in self.previous_iteration_properties: p = property.outputs[0] self.outputs_dict[p].set_value( - self.get_value(re.sub(r'^previous_', '', p))) + self.get_value(re.sub(r"^previous_", "", p)) + ) def update(self, **kwargs): for key in kwargs: if key not in self.outputs_dict: - raise PlasmaMissingModule('Trying to update property {0}' - ' that is unavailable'.format(key)) + raise PlasmaMissingModule( + "Trying to update property {0}" + " that is unavailable".format(key) + ) self.outputs_dict[key].set_value(kwargs[key]) for module_name in self._resolve_update_list(kwargs.keys()): @@ -192,35 +217,38 @@ def _resolve_update_list(self, changed_properties): descendants_ob = list(set(descendants_ob)) sort_order = list(nx.topological_sort(self.graph)) - descendants_ob.sort(key=lambda val: sort_order.index(val) ) + descendants_ob.sort(key=lambda val: sort_order.index(val)) - logger.debug('Updating modules in the following order: {}'.format( - '->'.join(descendants_ob))) + logger.debug( + "Updating modules in the following order: {}".format( + "->".join(descendants_ob) + ) + ) return descendants_ob def write_to_dot(self, fname, latex_label=True): -# self._update_module_type_str() + # self._update_module_type_str() try: import pygraphviz except: - logger.warn('pygraphviz missing. Plasma graph will not be ' - 'generated.') + logger.warn( + "pygraphviz missing. Plasma graph will not be " "generated." + ) return print_graph = self.graph.copy() print_graph = self.remove_hidden_properties(print_graph) for node in print_graph: - print_graph.node[str(node)]['label'] = node - if hasattr(self.plasma_properties_dict[node], - 'latex_formula'): + print_graph.node[str(node)]["label"] = node + if hasattr(self.plasma_properties_dict[node], "latex_formula"): formulae = self.plasma_properties_dict[node].latex_formula for output in range(0, len(formulae)): formula = formulae[output] - label = formula.replace('\\', '\\\\') - print_graph.node[str(node)]['label']+='\\n$' - print_graph.node[str(node)]['label']+=label - print_graph.node[str(node)]['label']+='$' + label = formula.replace("\\", "\\\\") + print_graph.node[str(node)]["label"] += "\\n$" + print_graph.node[str(node)]["label"] += label + print_graph.node[str(node)]["label"] += "$" nx.drawing.nx_agraph.write_dot(print_graph, fname) @@ -228,8 +256,9 @@ def write_to_tex(self, fname_graph): try: import dot2tex except: - logger.warn('dot2tex missing. Plasma graph will not be ' - 'generated.') + logger.warn( + "dot2tex missing. Plasma graph will not be " "generated." + ) return temp_fname = tempfile.NamedTemporaryFile().name @@ -238,37 +267,47 @@ def write_to_tex(self, fname_graph): dot_string = open(temp_fname).read() - open(fname_graph, 'w').write(dot2tex.dot2tex(dot_string, - texmode='raw')) + open(fname_graph, "w").write(dot2tex.dot2tex(dot_string, texmode="raw")) - for line in fileinput.input(fname_graph, inplace = 1): - print(line.replace(r'\documentclass{article}', - r'\documentclass[class=minimal,border=20pt]{standalone}'), - end='') + for line in fileinput.input(fname_graph, inplace=1): + print( + line.replace( + r"\documentclass{article}", + r"\documentclass[class=minimal,border=20pt]{standalone}", + ), + end="", + ) - for line in fileinput.input(fname_graph, inplace = 1): - print(line.replace(r'\enlargethispage{100cm}', ''), end='') + for line in fileinput.input(fname_graph, inplace=1): + print(line.replace(r"\enlargethispage{100cm}", ""), end="") def remove_hidden_properties(self, print_graph): for item in self.plasma_properties_dict.values(): module = self.plasma_properties_dict[item.name].__class__ - if (issubclass(module, HiddenPlasmaProperty)): + if issubclass(module, HiddenPlasmaProperty): output = module.outputs[0] for value in self.plasma_properties_dict.keys(): if output in getattr( - self.plasma_properties_dict[value], 'inputs', []): + self.plasma_properties_dict[value], "inputs", [] + ): for input in self.plasma_properties_dict[ - item.name].inputs: + item.name + ].inputs: try: position = self.outputs_dict[ - input].outputs.index(input) - label = self.outputs_dict[ - input].latex_name[position] - label = '$' + label + '$' - label = label.replace('\\', '\\\\') + input + ].outputs.index(input) + label = self.outputs_dict[input].latex_name[ + position + ] + label = "$" + label + "$" + label = label.replace("\\", "\\\\") except: - label = input.replace('_', '-') - self.graph.add_edge(self.outputs_dict[input].name, - value, label = label) + label = input.replace("_", "-") + self.graph.add_edge( + self.outputs_dict[input].name, + value, + label=label, + ) print_graph.remove_node(str(item.name)) return print_graph diff --git a/tardis/plasma/exceptions.py b/tardis/plasma/exceptions.py index 70108c0b308..897ce4cf658 100644 --- a/tardis/plasma/exceptions.py +++ b/tardis/plasma/exceptions.py @@ -1,25 +1,33 @@ class PlasmaException(Exception): pass + class IncompleteAtomicData(PlasmaException): def __init__(self, atomic_data_name): - message = ('The current plasma calculation requires {0}, ' - 'which is not provided by the given atomic data'.format( - atomic_data_name)) + message = ( + "The current plasma calculation requires {0}, " + "which is not provided by the given atomic data".format( + atomic_data_name + ) + ) super(PlasmaException, self).__init__(message) class PlasmaMissingModule(PlasmaException): pass + class PlasmaIsolatedModule(PlasmaException): pass + class NotInitializedModule(PlasmaException): pass + class PlasmaIonizationError(PlasmaException): pass + class PlasmaConfigError(PlasmaException): - pass \ No newline at end of file + pass diff --git a/tardis/plasma/properties/atomic.py b/tardis/plasma/properties/atomic.py index 503daba3856..3e1c7014a7d 100644 --- a/tardis/plasma/properties/atomic.py +++ b/tardis/plasma/properties/atomic.py @@ -4,15 +4,27 @@ import pandas as pd from collections import Counter as counter -from tardis.plasma.properties.base import (ProcessingPlasmaProperty, - HiddenPlasmaProperty, BaseAtomicDataProperty) +from tardis.plasma.properties.base import ( + ProcessingPlasmaProperty, + HiddenPlasmaProperty, + BaseAtomicDataProperty, +) from tardis.plasma.exceptions import IncompleteAtomicData logger = logging.getLogger(__name__) -__all__ = ['Levels', 'Lines', 'LinesLowerLevelIndex', 'LinesUpperLevelIndex', - 'AtomicMass', 'IonizationData', 'ZetaData', 'NLTEData', - 'PhotoIonizationData'] +__all__ = [ + "Levels", + "Lines", + "LinesLowerLevelIndex", + "LinesUpperLevelIndex", + "AtomicMass", + "IonizationData", + "ZetaData", + "NLTEData", + "PhotoIonizationData", +] + class Levels(BaseAtomicDataProperty): """ @@ -27,9 +39,14 @@ class Levels(BaseAtomicDataProperty): g : Pandas DataFrame (index=levels), dtype float Statistical weights of atomic levels """ - outputs = ('levels', 'excitation_energy', 'metastability', 'g') - latex_name = ('\\textrm{levels}', '\\epsilon_{\\textrm{k}}', '\\textrm{metastability}', - 'g') + + outputs = ("levels", "excitation_energy", "metastability", "g") + latex_name = ( + "\\textrm{levels}", + "\\epsilon_{\\textrm{k}}", + "\\textrm{metastability}", + "g", + ) def _filter_atomic_property(self, levels, selected_atoms): return levels @@ -38,8 +55,13 @@ def _filter_atomic_property(self, levels, selected_atoms): def _set_index(self, levels): # levels = levels.set_index(['atomic_number', 'ion_number', # 'level_number']) - return (levels.index, levels['energy'], levels['metastable'], - levels['g']) + return ( + levels.index, + levels["energy"], + levels["metastable"], + levels["g"], + ) + class Lines(BaseAtomicDataProperty): """ @@ -55,8 +77,9 @@ class Lines(BaseAtomicDataProperty): wavelength_cm: Pandas DataFrame (index=line_id), dtype float Line wavelengths in cm """ -# Would like for lines to just be the line_id values - outputs = ('lines', 'nu', 'f_lu', 'wavelength_cm') + + # Would like for lines to just be the line_id values + outputs = ("lines", "nu", "f_lu", "wavelength_cm") def _filter_atomic_property(self, lines, selected_atoms): # return lines[lines.atomic_number.isin(selected_atoms)] @@ -64,7 +87,7 @@ def _filter_atomic_property(self, lines, selected_atoms): def _set_index(self, lines): # lines.set_index('line_id', inplace=True) - return lines, lines['nu'], lines['f_lu'], lines['wavelength_cm'] + return lines, lines["nu"], lines["f_lu"], lines["wavelength_cm"] class PhotoIonizationData(ProcessingPlasmaProperty): @@ -86,23 +109,27 @@ class PhotoIonizationData(ProcessingPlasmaProperty): Atomic, ion and level numbers for which photoionization data exists. """ - outputs = ('photo_ion_cross_sections', 'photo_ion_block_references', - 'photo_ion_index') - latex_name = ('\\xi_{\\textrm{i}}(\\nu)', '', '') + + outputs = ( + "photo_ion_cross_sections", + "photo_ion_block_references", + "photo_ion_index", + ) + latex_name = ("\\xi_{\\textrm{i}}(\\nu)", "", "") def calculate(self, atomic_data, continuum_interaction_species): photoionization_data = atomic_data.photoionization_data.set_index( - ['atomic_number', 'ion_number', 'level_number'] + ["atomic_number", "ion_number", "level_number"] ) selected_species_idx = pd.IndexSlice[ - continuum_interaction_species.get_level_values('atomic_number'), - continuum_interaction_species.get_level_values('ion_number'), - slice(None) + continuum_interaction_species.get_level_values("atomic_number"), + continuum_interaction_species.get_level_values("ion_number"), + slice(None), ] photoionization_data = photoionization_data.loc[selected_species_idx] - phot_nus = photoionization_data['nu'] + phot_nus = photoionization_data["nu"] block_references = np.hstack( - [[0], phot_nus.groupby(level=[0,1,2]).count().values.cumsum()] + [[0], phot_nus.groupby(level=[0, 1, 2]).count().values.cumsum()] ) photo_ion_index = photoionization_data.index.unique() return photoionization_data, block_references, photo_ion_index @@ -114,25 +141,31 @@ class LinesLowerLevelIndex(HiddenPlasmaProperty): lines_lower_level_index : One-dimensional Numpy Array, dtype int Levels data for lower levels of particular lines """ - outputs = ('lines_lower_level_index',) + + outputs = ("lines_lower_level_index",) + def calculate(self, levels, lines): - levels_index = pd.Series(np.arange(len(levels), dtype=np.int64), - index=levels) - lines_index = lines.index.droplevel('level_number_upper') + levels_index = pd.Series( + np.arange(len(levels), dtype=np.int64), index=levels + ) + lines_index = lines.index.droplevel("level_number_upper") return np.array(levels_index.loc[lines_index]) + class LinesUpperLevelIndex(HiddenPlasmaProperty): """ Attributes: lines_upper_level_index : One-dimensional Numpy Array, dtype int Levels data for upper levels of particular lines """ - outputs = ('lines_upper_level_index',) + + outputs = ("lines_upper_level_index",) def calculate(self, levels, lines): - levels_index = pd.Series(np.arange(len(levels), dtype=np.int64), - index=levels) - lines_index = lines.index.droplevel('level_number_lower') + levels_index = pd.Series( + np.arange(len(levels), dtype=np.int64), index=levels + ) + lines_index = lines.index.droplevel("level_number_lower") return np.array(levels_index.loc[lines_index]) @@ -142,44 +175,44 @@ class AtomicMass(ProcessingPlasmaProperty): atomic_mass : Pandas Series Atomic masses of the elements used. Indexed by atomic number. """ - outputs = ('atomic_mass',) + + outputs = ("atomic_mass",) def calculate(self, atomic_data, selected_atoms): if getattr(self, self.outputs[0]) is not None: - return getattr(self, self.outputs[0]), + return (getattr(self, self.outputs[0]),) else: return atomic_data.atom_data.loc[selected_atoms].mass + class IonizationData(BaseAtomicDataProperty): """ Attributes: ionization_data : Pandas Series holding ionization energies Indexed by atomic number, ion number. """ - outputs = ('ionization_data',) + + outputs = ("ionization_data",) def _filter_atomic_property(self, ionization_data, selected_atoms): - mask = ionization_data.index.isin( - selected_atoms, - level='atomic_number' - ) + mask = ionization_data.index.isin(selected_atoms, level="atomic_number") ionization_data = ionization_data[mask] - counts = ionization_data.groupby( - level='atomic_number').count() + counts = ionization_data.groupby(level="atomic_number").count() if np.alltrue(counts.index == counts): return ionization_data else: raise IncompleteAtomicData( - 'ionization data for the ion ({}, {})'.format( - str(counts.index[counts.index != counts]), - str(counts[counts.index != counts]) - ) - ) + "ionization data for the ion ({}, {})".format( + str(counts.index[counts.index != counts]), + str(counts[counts.index != counts]), + ) + ) def _set_index(self, ionization_data): return ionization_data + class ZetaData(BaseAtomicDataProperty): """ Attributes: @@ -189,11 +222,12 @@ class ZetaData(BaseAtomicDataProperty): The zeta value represents the fraction of recombination events from the ionized state that go directly to the ground state. """ - outputs = ('zeta_data',) + + outputs = ("zeta_data",) def _filter_atomic_property(self, zeta_data, selected_atoms): - zeta_data['atomic_number'] = zeta_data.index.labels[0] + 1 - zeta_data['ion_number'] = zeta_data.index.labels[1] + 1 + zeta_data["atomic_number"] = zeta_data.index.labels[0] + 1 + zeta_data["ion_number"] = zeta_data.index.labels[1] + 1 zeta_data = zeta_data[zeta_data.atomic_number.isin(selected_atoms)] zeta_data_check = counter(zeta_data.atomic_number.values) keys = np.array(list(zeta_data_check.keys())) @@ -201,36 +235,47 @@ def _filter_atomic_property(self, zeta_data, selected_atoms): if np.alltrue(keys + 1 == values): return zeta_data else: -# raise IncompleteAtomicData('zeta data') -# This currently replaces missing zeta data with 1, which is necessary with -# the present atomic data. Will replace with the error above when I have -# complete atomic data. + # raise IncompleteAtomicData('zeta data') + # This currently replaces missing zeta data with 1, which is necessary with + # the present atomic data. Will replace with the error above when I have + # complete atomic data. missing_ions = [] updated_index = [] for atom in selected_atoms: for ion in range(1, atom + 2): if (atom, ion) not in zeta_data.index: - missing_ions.append((atom,ion)) + missing_ions.append((atom, ion)) updated_index.append([atom, ion]) - logger.warn('Zeta_data missing - replaced with 1s. Missing ions: {}'.format(missing_ions)) + logger.warn( + "Zeta_data missing - replaced with 1s. Missing ions: {}".format( + missing_ions + ) + ) updated_index = np.array(updated_index) - updated_dataframe = pd.DataFrame(index=pd.MultiIndex.from_arrays( - updated_index.transpose().astype(int)), - columns=zeta_data.columns) + updated_dataframe = pd.DataFrame( + index=pd.MultiIndex.from_arrays( + updated_index.transpose().astype(int) + ), + columns=zeta_data.columns, + ) for value in range(len(zeta_data)): - updated_dataframe.loc[zeta_data.atomic_number.values[value], - zeta_data.ion_number.values[value]] = \ - zeta_data.loc[zeta_data.atomic_number.values[value], - zeta_data.ion_number.values[value]] + updated_dataframe.loc[ + zeta_data.atomic_number.values[value], + zeta_data.ion_number.values[value], + ] = zeta_data.loc[ + zeta_data.atomic_number.values[value], + zeta_data.ion_number.values[value], + ] updated_dataframe = updated_dataframe.astype(float) updated_index = pd.DataFrame(updated_index) - updated_dataframe['atomic_number'] = np.array(updated_index[0]) - updated_dataframe['ion_number'] = np.array(updated_index[1]) + updated_dataframe["atomic_number"] = np.array(updated_index[0]) + updated_dataframe["ion_number"] = np.array(updated_index[1]) updated_dataframe.fillna(1.0, inplace=True) return updated_dataframe def _set_index(self, zeta_data): - return zeta_data.set_index(['atomic_number', 'ion_number']) + return zeta_data.set_index(["atomic_number", "ion_number"]) + class NLTEData(ProcessingPlasmaProperty): """ @@ -238,7 +283,8 @@ class NLTEData(ProcessingPlasmaProperty): nlte_data : #Finish later (need atomic dataset with NLTE data). """ - outputs = ('nlte_data',) + + outputs = ("nlte_data",) def calculate(self, atomic_data): if getattr(self, self.outputs[0]) is not None: diff --git a/tardis/plasma/properties/base.py b/tardis/plasma/properties/base.py index e38dd2f5b5f..3a8138e1014 100644 --- a/tardis/plasma/properties/base.py +++ b/tardis/plasma/properties/base.py @@ -5,14 +5,22 @@ import pandas as pd -__all__ = ['BasePlasmaProperty', 'BaseAtomicDataProperty', - 'HiddenPlasmaProperty', 'Input', 'ArrayInput', 'DataFrameInput', - 'ProcessingPlasmaProperty', 'PreviousIterationProperty'] +__all__ = [ + "BasePlasmaProperty", + "BaseAtomicDataProperty", + "HiddenPlasmaProperty", + "Input", + "ArrayInput", + "DataFrameInput", + "ProcessingPlasmaProperty", + "PreviousIterationProperty", +] logger = logging.getLogger(__name__) import os + class BasePlasmaProperty(object, metaclass=ABCMeta): """ Attributes @@ -45,25 +53,19 @@ def get_latex_label(self): \textbf{{Formula}} {formula} {description} """ - outputs = self.outputs.replace('_', r'\_') - latex_name = getattr(self, 'latex_name', '') - if latex_name != '': - complete_name = '{0} [{1}]'.format(latex_name, self.latex_name) + outputs = self.outputs.replace("_", r"\_") + latex_name = getattr(self, "latex_name", "") + if latex_name != "": + complete_name = "{0} [{1}]".format(latex_name, self.latex_name) else: complete_name = latex_name latex_label = latex_template.format( - name=complete_name, - formula=getattr( - self, - 'latex_formula', '--'), - description=getattr( - self, - 'latex_description', - '')) - return latex_label.replace('\\', r'\\') - - + name=complete_name, + formula=getattr(self, "latex_formula", "--"), + description=getattr(self, "latex_description", ""), + ) + return latex_label.replace("\\", r"\\") class ProcessingPlasmaProperty(BasePlasmaProperty, metaclass=ABCMeta): @@ -85,10 +87,11 @@ def _update_inputs(self): `calculate`-function and makes the plasma routines easily programmable. """ calculate_call_signature = self.calculate.__code__.co_varnames[ - :self.calculate.__code__.co_argcount] + : self.calculate.__code__.co_argcount + ] self.inputs = [ - item for item in calculate_call_signature - if item != 'self'] + item for item in calculate_call_signature if item != "self" + ] def _get_input_values(self): return (self.plasma_parent.get_value(item) for item in self.inputs) @@ -101,8 +104,9 @@ def update(self): :return: """ if len(self.outputs) == 1: - setattr(self, self.outputs[0], self.calculate( - *self._get_input_values())) + setattr( + self, self.outputs[0], self.calculate(*self._get_input_values()) + ) else: new_values = self.calculate(*self._get_input_values()) for i, output in enumerate(self.outputs): @@ -110,8 +114,10 @@ def update(self): @abstractmethod def calculate(self, *args, **kwargs): - raise NotImplementedError('This method needs to be implemented by ' - 'processing plasma modules') + raise NotImplementedError( + "This method needs to be implemented by " + "processing plasma modules" + ) class HiddenPlasmaProperty(ProcessingPlasmaProperty, metaclass=ABCMeta): @@ -131,7 +137,7 @@ class BaseAtomicDataProperty(ProcessingPlasmaProperty, metaclass=ABCMeta): the simulation. """ - inputs = ['atomic_data', 'selected_atoms'] + inputs = ["atomic_data", "selected_atoms"] def __init__(self, plasma_parent): @@ -140,11 +146,11 @@ def __init__(self, plasma_parent): @abstractmethod def _set_index(self, raw_atomic_property): - raise NotImplementedError('Needs to be implemented in subclasses') + raise NotImplementedError("Needs to be implemented in subclasses") @abstractmethod def _filter_atomic_property(self, raw_atomic_property): - raise NotImplementedError('Needs to be implemented in subclasses') + raise NotImplementedError("Needs to be implemented in subclasses") def calculate(self, atomic_data, selected_atoms): @@ -153,9 +159,10 @@ def calculate(self, atomic_data, selected_atoms): else: raw_atomic_property = getattr(atomic_data, self.outputs[0]) return self._set_index( - self._filter_atomic_property( - raw_atomic_property, selected_atoms) - ) + self._filter_atomic_property( + raw_atomic_property, selected_atoms + ) + ) class Input(BasePlasmaProperty): @@ -163,6 +170,7 @@ class Input(BasePlasmaProperty): The plasma property class for properties that are input directly from model and not calculated within the plasma module, e.g. t_rad. """ + def _set_output_value(self, output, value): setattr(self, output, value) @@ -188,6 +196,7 @@ class PreviousIterationProperty(BasePlasmaProperty): calculations. Given a sufficient number of iterations, the values should converge successfully on the correct solution. """ + def _set_initial_value(self, value): self.set_value(value) diff --git a/tardis/plasma/properties/continuum_processes.py b/tardis/plasma/properties/continuum_processes.py index 5de3f3825e0..5c53d02617f 100644 --- a/tardis/plasma/properties/continuum_processes.py +++ b/tardis/plasma/properties/continuum_processes.py @@ -8,11 +8,11 @@ from tardis.plasma.properties.base import ProcessingPlasmaProperty -__all__ = ['SpontRecombRateCoeff'] +__all__ = ["SpontRecombRateCoeff"] logger = logging.getLogger(__name__) -njit_dict = {'fastmath': False, 'parallel': False} +njit_dict = {"fastmath": False, "parallel": False} @njit(**njit_dict) @@ -73,20 +73,31 @@ class SpontRecombRateCoeff(ProcessingPlasmaProperty): alpha_sp : Pandas DataFrame, dtype float The rate coefficient for spontaneous recombination. """ - outputs = ('alpha_sp',) - latex_name = ('\\alpha^{\\textrm{sp}}',) - def calculate(self, photo_ion_cross_sections, t_electrons, - photo_ion_block_references, photo_ion_index, phi_ik): - x_sect = photo_ion_cross_sections['x_sect'].values - nu = photo_ion_cross_sections['nu'].values - - alpha_sp = (8 * np.pi * x_sect * nu ** 2 / (const.c.cgs.value) ** 2) + outputs = ("alpha_sp",) + latex_name = ("\\alpha^{\\textrm{sp}}",) + + def calculate( + self, + photo_ion_cross_sections, + t_electrons, + photo_ion_block_references, + photo_ion_index, + phi_ik, + ): + x_sect = photo_ion_cross_sections["x_sect"].values + nu = photo_ion_cross_sections["nu"].values + + alpha_sp = 8 * np.pi * x_sect * nu ** 2 / (const.c.cgs.value) ** 2 alpha_sp = alpha_sp[:, np.newaxis] - boltzmann_factor = np.exp(-nu[np.newaxis].T / t_electrons * - (const.h.cgs.value / const.k_B.cgs.value)) + boltzmann_factor = np.exp( + -nu[np.newaxis].T + / t_electrons + * (const.h.cgs.value / const.k_B.cgs.value) + ) alpha_sp = alpha_sp * boltzmann_factor - alpha_sp = integrate_array_by_blocks(alpha_sp, nu, - photo_ion_block_references) + alpha_sp = integrate_array_by_blocks( + alpha_sp, nu, photo_ion_block_references + ) alpha_sp = pd.DataFrame(alpha_sp, index=photo_ion_index) return alpha_sp * phi_ik diff --git a/tardis/plasma/properties/general.py b/tardis/plasma/properties/general.py index cd8717a8d71..0669073ba4a 100644 --- a/tardis/plasma/properties/general.py +++ b/tardis/plasma/properties/general.py @@ -9,9 +9,18 @@ logger = logging.getLogger(__name__) -__all__ = ['BetaRadiation', 'GElectron', 'NumberDensity', 'SelectedAtoms', - 'ElectronTemperature', 'BetaElectron', 'LuminosityInner', - 'TimeSimulation', 'ThermalGElectron'] +__all__ = [ + "BetaRadiation", + "GElectron", + "NumberDensity", + "SelectedAtoms", + "ElectronTemperature", + "BetaElectron", + "LuminosityInner", + "TimeSimulation", + "ThermalGElectron", +] + class BetaRadiation(ProcessingPlasmaProperty): """ @@ -19,9 +28,10 @@ class BetaRadiation(ProcessingPlasmaProperty): ---------- beta_rad : Numpy Array, dtype float """ - outputs = ('beta_rad',) - latex_name = ('\\beta_{\\textrm{rad}}',) - latex_formula = ('\\dfrac{1}{k_{B} T_{\\textrm{rad}}}',) + + outputs = ("beta_rad",) + latex_name = ("\\beta_{\\textrm{rad}}",) + latex_formula = ("\\dfrac{1}{k_{B} T_{\\textrm{rad}}}",) def __init__(self, plasma_parent): super(BetaRadiation, self).__init__(plasma_parent) @@ -30,20 +40,26 @@ def __init__(self, plasma_parent): def calculate(self, t_rad): return 1 / (self.k_B_cgs * t_rad) + class GElectron(ProcessingPlasmaProperty): """ Attributes ---------- g_electron : Numpy Array, dtype float """ - outputs = ('g_electron',) - latex_name = ('g_{\\textrm{electron}}',) - latex_formula = ('\\Big(\\dfrac{2\\pi m_{e}/\ - \\beta_{\\textrm{rad}}}{h^2}\\Big)^{3/2}',) + + outputs = ("g_electron",) + latex_name = ("g_{\\textrm{electron}}",) + latex_formula = ( + "\\Big(\\dfrac{2\\pi m_{e}/\ + \\beta_{\\textrm{rad}}}{h^2}\\Big)^{3/2}", + ) def calculate(self, beta_rad): - return ((2 * np.pi * const.m_e.cgs.value / beta_rad) / - (const.h.cgs.value ** 2)) ** 1.5 + return ( + (2 * np.pi * const.m_e.cgs.value / beta_rad) + / (const.h.cgs.value ** 2) + ) ** 1.5 class ThermalGElectron(GElectron): @@ -52,10 +68,13 @@ class ThermalGElectron(GElectron): ---------- thermal_g_electron : Numpy Array, dtype float """ - outputs = ('thermal_g_electron',) - latex_name = ('g_{\\textrm{electron_thermal}}',) - latex_formula = ('\\Big(\\dfrac{2\\pi m_{e}/\ - \\beta_{\\textrm{electron}}}{h^2}\\Big)^{3/2}',) + + outputs = ("thermal_g_electron",) + latex_name = ("g_{\\textrm{electron_thermal}}",) + latex_formula = ( + "\\Big(\\dfrac{2\\pi m_{e}/\ + \\beta_{\\textrm{electron}}}{h^2}\\Big)^{3/2}", + ) def calculate(self, beta_electron): return super(ThermalGElectron, self).calculate(beta_electron) @@ -68,14 +87,16 @@ class NumberDensity(ProcessingPlasmaProperty): number_density : Pandas DataFrame, dtype float Indexed by atomic number, columns corresponding to zones """ - outputs = ('number_density',) - latex_name = ('N_{i}',) + + outputs = ("number_density",) + latex_name = ("N_{i}",) @staticmethod def calculate(atomic_mass, abundance, density): - number_densities = (abundance * density) + number_densities = abundance * density return number_densities.div(atomic_mass.loc[abundance.index], axis=0) + class SelectedAtoms(ProcessingPlasmaProperty): """ Attributes @@ -83,33 +104,38 @@ class SelectedAtoms(ProcessingPlasmaProperty): selected_atoms : Pandas Int64Index, dtype int Atomic numbers of elements required for particular simulation """ - outputs = ('selected_atoms',) + + outputs = ("selected_atoms",) def calculate(self, abundance): return abundance.index + class ElectronTemperature(ProcessingPlasmaProperty): """ Attributes ---------- t_electron : Numpy Array, dtype float """ - outputs = ('t_electrons',) - latex_name = ('T_{\\textrm{electron}}',) - latex_formula = ('\\textrm{const.}\\times T_{\\textrm{rad}}',) + + outputs = ("t_electrons",) + latex_name = ("T_{\\textrm{electron}}",) + latex_formula = ("\\textrm{const.}\\times T_{\\textrm{rad}}",) def calculate(self, t_rad, link_t_rad_t_electron): return t_rad * link_t_rad_t_electron + class BetaElectron(ProcessingPlasmaProperty): """ Attributes ---------- beta_electron : Numpy Array, dtype float """ - outputs = ('beta_electron',) - latex_name = ('\\beta_{\\textrm{electron}}',) - latex_formula = ('\\frac{1}{K_{B} T_{\\textrm{electron}}}',) + + outputs = ("beta_electron",) + latex_name = ("\\beta_{\\textrm{electron}}",) + latex_formula = ("\\frac{1}{K_{B} T_{\\textrm{electron}}}",) def __init__(self, plasma_parent): super(BetaElectron, self).__init__(plasma_parent) @@ -118,16 +144,19 @@ def __init__(self, plasma_parent): def calculate(self, t_electrons): return 1 / (self.k_B_cgs * t_electrons) + class LuminosityInner(ProcessingPlasmaProperty): - outputs = ('luminosity_inner',) + outputs = ("luminosity_inner",) @staticmethod def calculate(r_inner, t_inner): - return (4 * np.pi * const.sigma_sb.cgs * r_inner[0] ** 2 - * t_inner ** 4).to('erg/s') + return ( + 4 * np.pi * const.sigma_sb.cgs * r_inner[0] ** 2 * t_inner ** 4 + ).to("erg/s") + class TimeSimulation(ProcessingPlasmaProperty): - outputs = ('time_simulation',) + outputs = ("time_simulation",) @staticmethod def calculate(luminosity_inner): diff --git a/tardis/plasma/properties/ion_population.py b/tardis/plasma/properties/ion_population.py index 92ef72747e9..31bdcaf6a6f 100644 --- a/tardis/plasma/properties/ion_population.py +++ b/tardis/plasma/properties/ion_population.py @@ -15,15 +15,22 @@ logger = logging.getLogger(__name__) -__all__ = ['PhiSahaNebular', 'PhiSahaLTE', 'RadiationFieldCorrection', - 'IonNumberDensity', 'IonNumberDensityHeNLTE', 'SahaFactor', - 'ThermalPhiSahaLTE'] +__all__ = [ + "PhiSahaNebular", + "PhiSahaLTE", + "RadiationFieldCorrection", + "IonNumberDensity", + "IonNumberDensityHeNLTE", + "SahaFactor", + "ThermalPhiSahaLTE", +] def calculate_block_ids_from_dataframe(dataframe): - block_start_id = np.where(np.diff( - dataframe.index.get_level_values(0)) != 0.0)[0] + 1 - return np.hstack(([0], block_start_id, [len(dataframe)])) + block_start_id = ( + np.where(np.diff(dataframe.index.get_level_values(0)) != 0.0)[0] + 1 + ) + return np.hstack(([0], block_start_id, [len(dataframe)])) class PhiSahaLTE(ProcessingPlasmaProperty): @@ -33,12 +40,15 @@ class PhiSahaLTE(ProcessingPlasmaProperty): Used for LTE ionization (at the radiation temperature). Indexed by atomic number, ion number. Columns are zones. """ - outputs = ('phi',) - latex_name = ('\\Phi',) - latex_formula = ('\\dfrac{2Z_{i,j+1}}{Z_{i,j}}\\Big(\ + + outputs = ("phi",) + latex_name = ("\\Phi",) + latex_formula = ( + "\\dfrac{2Z_{i,j+1}}{Z_{i,j}}\\Big(\ \\dfrac{2\\pi m_{e}/\\beta_{\\textrm{rad}}}{h^2}\ \\Big)^{3/2}e^{\\dfrac{-\\chi_{i,j}}{kT_{\ - \\textrm{rad}}}}',) + \\textrm{rad}}}}", + ) broadcast_ionization_energy = None @@ -46,9 +56,12 @@ class PhiSahaLTE(ProcessingPlasmaProperty): def calculate(g_electron, beta_rad, partition_function, ionization_data): phis = np.empty( - (partition_function.shape[0] - - partition_function.index.get_level_values(0).unique().size, - partition_function.shape[1])) + ( + partition_function.shape[0] + - partition_function.index.get_level_values(0).unique().size, + partition_function.shape[1], + ) + ) block_ids = calculate_block_ids_from_dataframe(partition_function) @@ -56,15 +69,19 @@ def calculate(g_electron, beta_rad, partition_function, ionization_data): end_id = block_ids[i + 1] current_block = partition_function.values[start_id:end_id] current_phis = current_block[1:] / current_block[:-1] - phis[start_id - i:end_id - i - 1] = current_phis + phis[start_id - i : end_id - i - 1] = current_phis - broadcast_ionization_energy = ( - ionization_data[partition_function.index].dropna()) + broadcast_ionization_energy = ionization_data[ + partition_function.index + ].dropna() phi_index = broadcast_ionization_energy.index broadcast_ionization_energy = broadcast_ionization_energy.values - phi_coefficient = (2 * g_electron * np.exp( - np.outer(broadcast_ionization_energy, -beta_rad))) + phi_coefficient = ( + 2 + * g_electron + * np.exp(np.outer(broadcast_ionization_energy, -beta_rad)) + ) return pd.DataFrame(phis * phi_coefficient, index=phi_index) @@ -80,19 +97,28 @@ class ThermalPhiSahaLTE(PhiSahaLTE): Used for LTE ionization (at the electron temperature). Indexed by atomic number, ion number. Columns are zones. """ - outputs = ('thermal_phi_lte',) - latex_name = ('\\Phi^{*}(T_\\mathrm{e})',) - latex_formula = ('\\dfrac{2Z_{i,j+1}}{Z_{i,j}}\\Big(\ + + outputs = ("thermal_phi_lte",) + latex_name = ("\\Phi^{*}(T_\\mathrm{e})",) + latex_formula = ( + "\\dfrac{2Z_{i,j+1}}{Z_{i,j}}\\Big(\ \\dfrac{2\\pi m_{e}/\\beta_{\\textrm{electron}}}{h^2}\ \\Big)^{3/2}e^{\\dfrac{-\\chi_{i,j}}{kT_{\ - \\textrm{electron}}}}',) + \\textrm{electron}}}}", + ) @staticmethod - def calculate(thermal_g_electron, beta_electron, - thermal_lte_partition_function, ionization_data): + def calculate( + thermal_g_electron, + beta_electron, + thermal_lte_partition_function, + ionization_data, + ): return super(ThermalPhiSahaLTE, ThermalPhiSahaLTE).calculate( - thermal_g_electron, beta_electron, thermal_lte_partition_function, - ionization_data + thermal_g_electron, + beta_electron, + thermal_lte_partition_function, + ionization_data, ) @@ -102,39 +128,63 @@ class PhiSahaNebular(ProcessingPlasmaProperty): phi : Pandas DataFrame, dtype float Used for nebular ionization. Indexed by atomic number, ion number. Columns are zones. """ - outputs = ('phi',) - latex_name = ('\\Phi',) - latex_formula = ('W(\\delta\\zeta_{i,j}+W(1-\\zeta_{i,j}))\\left(\ + + outputs = ("phi",) + latex_name = ("\\Phi",) + latex_formula = ( + "W(\\delta\\zeta_{i,j}+W(1-\\zeta_{i,j}))\\left(\ \\dfrac{T_{\\textrm{electron}}}{T_{\\textrm{rad}}}\ - \\right)^{1/2}',) + \\right)^{1/2}", + ) + @staticmethod - def calculate(t_rad, w, zeta_data, t_electrons, delta, - g_electron, beta_rad, partition_function, ionization_data): - phi_lte = PhiSahaLTE.calculate(g_electron, beta_rad, - partition_function, ionization_data) + def calculate( + t_rad, + w, + zeta_data, + t_electrons, + delta, + g_electron, + beta_rad, + partition_function, + ionization_data, + ): + phi_lte = PhiSahaLTE.calculate( + g_electron, beta_rad, partition_function, ionization_data + ) zeta = PhiSahaNebular.get_zeta_values(zeta_data, phi_lte.index, t_rad) - phis = phi_lte * w * ((zeta * delta) + w * (1 - zeta)) * \ - (t_electrons/t_rad) ** .5 + phis = ( + phi_lte + * w + * ((zeta * delta) + w * (1 - zeta)) + * (t_electrons / t_rad) ** 0.5 + ) return phis @staticmethod def get_zeta_values(zeta_data, ion_index, t_rad): zeta_t_rad = zeta_data.columns.values.astype(np.float64) zeta_values = zeta_data.loc[ion_index].values.astype(np.float64) - zeta = interpolate.interp1d(zeta_t_rad, zeta_values, bounds_error=False, - fill_value=np.nan)(t_rad) + zeta = interpolate.interp1d( + zeta_t_rad, zeta_values, bounds_error=False, fill_value=np.nan + )(t_rad) zeta = zeta.astype(float) if np.any(np.isnan(zeta)): - warnings.warn('t_rads outside of zeta factor interpolation' - ' zeta_min={0:.2f} zeta_max={1:.2f} ' - '- replacing with 1s'.format( - zeta_data.columns.values.min(), zeta_data.columns.values.max(), - t_rad)) + warnings.warn( + "t_rads outside of zeta factor interpolation" + " zeta_min={0:.2f} zeta_max={1:.2f} " + "- replacing with 1s".format( + zeta_data.columns.values.min(), + zeta_data.columns.values.max(), + t_rad, + ) + ) zeta[np.isnan(zeta)] = 1.0 return zeta + class RadiationFieldCorrection(ProcessingPlasmaProperty): """ Attributes: @@ -144,11 +194,17 @@ class RadiationFieldCorrection(ProcessingPlasmaProperty): Ca II, which is good for type Ia supernovae. For type II supernovae, (1, 1) should be used. Indexed by atomic number, ion number. The columns are zones. """ - outputs = ('delta',) - latex_name = ('\\delta',) - def __init__(self, plasma_parent=None, departure_coefficient=None, - chi_0_species=(20,2), delta_treatment=None): + outputs = ("delta",) + latex_name = ("\\delta",) + + def __init__( + self, + plasma_parent=None, + departure_coefficient=None, + chi_0_species=(20, 2), + delta_treatment=None, + ): super(RadiationFieldCorrection, self).__init__(plasma_parent) self.departure_coefficient = departure_coefficient self.delta_treatment = delta_treatment @@ -160,36 +216,48 @@ def _set_chi_0(self, ionization_data): else: self.chi_0 = ionization_data.loc[self.chi_0_species] - def calculate(self, w, ionization_data, beta_rad, t_electrons, t_rad, - beta_electron): - if getattr(self, 'chi_0', None) is None: + def calculate( + self, w, ionization_data, beta_rad, t_electrons, t_rad, beta_electron + ): + if getattr(self, "chi_0", None) is None: self._set_chi_0(ionization_data) if self.delta_treatment is None: if self.departure_coefficient is None: - departure_coefficient = 1. / w + departure_coefficient = 1.0 / w else: departure_coefficient = self.departure_coefficient - radiation_field_correction = -np.ones((len(ionization_data), len( - beta_rad))) - less_than_chi_0 = ( - ionization_data < self.chi_0).values - factor_a = (t_electrons / (departure_coefficient * w * t_rad)) - radiation_field_correction[~less_than_chi_0] = factor_a * \ - np.exp(np.outer(ionization_data.values[ - ~less_than_chi_0], beta_rad - beta_electron)) - radiation_field_correction[less_than_chi_0] = 1 - np.exp(np.outer( - ionization_data.values[less_than_chi_0], - beta_rad) - beta_rad * self.chi_0) + radiation_field_correction = -np.ones( + (len(ionization_data), len(beta_rad)) + ) + less_than_chi_0 = (ionization_data < self.chi_0).values + factor_a = t_electrons / (departure_coefficient * w * t_rad) + radiation_field_correction[~less_than_chi_0] = factor_a * np.exp( + np.outer( + ionization_data.values[~less_than_chi_0], + beta_rad - beta_electron, + ) + ) + radiation_field_correction[less_than_chi_0] = 1 - np.exp( + np.outer(ionization_data.values[less_than_chi_0], beta_rad) + - beta_rad * self.chi_0 + ) radiation_field_correction[less_than_chi_0] += factor_a * np.exp( - np.outer(ionization_data.values[ - less_than_chi_0],beta_rad) - self.chi_0 * beta_electron) + np.outer(ionization_data.values[less_than_chi_0], beta_rad) + - self.chi_0 * beta_electron + ) else: - radiation_field_correction = np.ones((len(ionization_data), - len(beta_rad))) * self.delta_treatment - delta = pd.DataFrame(radiation_field_correction, - columns=np.arange(len(t_rad)), index=ionization_data.index) + radiation_field_correction = ( + np.ones((len(ionization_data), len(beta_rad))) + * self.delta_treatment + ) + delta = pd.DataFrame( + radiation_field_correction, + columns=np.arange(len(t_rad)), + index=ionization_data.index, + ) return delta + class IonNumberDensity(ProcessingPlasmaProperty): """ Attributes: @@ -206,19 +274,30 @@ class IonNumberDensity(ProcessingPlasmaProperty): value, a new guess for the value of the electron density is chosen and the process is repeated. """ - outputs = ('ion_number_density', 'electron_densities') - latex_name = ('N_{i,j}','n_{e}',) - def __init__(self, plasma_parent, ion_zero_threshold=1e-20, electron_densities=None): + outputs = ("ion_number_density", "electron_densities") + latex_name = ( + "N_{i,j}", + "n_{e}", + ) + + def __init__( + self, plasma_parent, ion_zero_threshold=1e-20, electron_densities=None + ): super(IonNumberDensity, self).__init__(plasma_parent) self.ion_zero_threshold = ion_zero_threshold self.block_ids = None self._electron_densities = electron_densities @staticmethod - def calculate_with_n_electron(phi, partition_function, - number_density, n_electron, block_ids, - ion_zero_threshold): + def calculate_with_n_electron( + phi, + partition_function, + number_density, + n_electron, + block_ids, + ion_zero_threshold, + ): if block_ids is None: block_ids = IonNumberDensity._calculate_block_ids(phi) @@ -231,18 +310,22 @@ def calculate_with_n_electron(phi, partition_function, current_phis = phi_electron[start_id:end_id] phis_product = np.cumprod(current_phis, 0) - tmp_ion_populations = np.empty((current_phis.shape[0] + 1, - current_phis.shape[1])) - tmp_ion_populations[0] = (number_density.values[i] / - (1 + np.sum(phis_product, axis=0))) + tmp_ion_populations = np.empty( + (current_phis.shape[0] + 1, current_phis.shape[1]) + ) + tmp_ion_populations[0] = number_density.values[i] / ( + 1 + np.sum(phis_product, axis=0) + ) tmp_ion_populations[1:] = tmp_ion_populations[0] * phis_product - ion_populations[start_id + i:end_id + 1 + i] = tmp_ion_populations + ion_populations[start_id + i : end_id + 1 + i] = tmp_ion_populations ion_populations[ion_populations < ion_zero_threshold] = 0.0 - return pd.DataFrame(data = ion_populations, - index=partition_function.index), block_ids + return ( + pd.DataFrame(data=ion_populations, index=partition_function.index), + block_ids, + ) @staticmethod def _calculate_block_ids(phi): @@ -255,35 +338,56 @@ def calculate(self, phi, partition_function, number_density): n_electron_iterations = 0 while True: - ion_number_density, self.block_ids = \ - self.calculate_with_n_electron( - phi, partition_function, number_density, n_electron, - self.block_ids, self.ion_zero_threshold) - ion_numbers = ion_number_density.index.get_level_values(1).values + ( + ion_number_density, + self.block_ids, + ) = self.calculate_with_n_electron( + phi, + partition_function, + number_density, + n_electron, + self.block_ids, + self.ion_zero_threshold, + ) + ion_numbers = ion_number_density.index.get_level_values( + 1 + ).values ion_numbers = ion_numbers.reshape((ion_numbers.shape[0], 1)) new_n_electron = (ion_number_density.values * ion_numbers).sum( - axis=0) + axis=0 + ) if np.any(np.isnan(new_n_electron)): - raise PlasmaIonizationError('n_electron just turned "nan" -' - ' aborting') + raise PlasmaIonizationError( + 'n_electron just turned "nan" -' " aborting" + ) n_electron_iterations += 1 if n_electron_iterations > 100: - logger.warn('n_electron iterations above 100 ({0}) -' - ' something is probably wrong'.format( - n_electron_iterations)) - if np.all(np.abs(new_n_electron - n_electron) - / n_electron < n_e_convergence_threshold): + logger.warn( + "n_electron iterations above 100 ({0}) -" + " something is probably wrong".format( + n_electron_iterations + ) + ) + if np.all( + np.abs(new_n_electron - n_electron) / n_electron + < n_e_convergence_threshold + ): break n_electron = 0.5 * (new_n_electron + n_electron) else: n_electron = self._electron_densities - ion_number_density, self.block_ids = \ - self.calculate_with_n_electron( - phi, partition_function, number_density, n_electron, - self.block_ids, self.ion_zero_threshold) + ion_number_density, self.block_ids = self.calculate_with_n_electron( + phi, + partition_function, + number_density, + n_electron, + self.block_ids, + self.ion_zero_threshold, + ) return ion_number_density, n_electron + class IonNumberDensityHeNLTE(ProcessingPlasmaProperty): """ Attributes: @@ -300,80 +404,124 @@ class IonNumberDensityHeNLTE(ProcessingPlasmaProperty): value, a new guess for the value of the electron density is chosen and the process is repeated. """ - outputs = ('ion_number_density', 'electron_densities', - 'helium_population_updated') - latex_name = ('N_{i,j}','n_{e}',) - def __init__(self, plasma_parent, ion_zero_threshold=1e-20, electron_densities=None): + outputs = ( + "ion_number_density", + "electron_densities", + "helium_population_updated", + ) + latex_name = ( + "N_{i,j}", + "n_{e}", + ) + + def __init__( + self, plasma_parent, ion_zero_threshold=1e-20, electron_densities=None + ): super(IonNumberDensityHeNLTE, self).__init__(plasma_parent) self.ion_zero_threshold = ion_zero_threshold self.block_ids = None self._electron_densities = electron_densities - def update_he_population(self, helium_population, n_electron, - number_density): + def update_he_population( + self, helium_population, n_electron, number_density + ): helium_population_updated = helium_population.copy() he_one_population = helium_population_updated.loc[0].mul(n_electron) he_three_population = helium_population_updated.loc[2].mul( - 1./n_electron) + 1.0 / n_electron + ) helium_population_updated.loc[0].update(he_one_population) helium_population_updated.loc[2].update(he_three_population) unnormalised = helium_population_updated.sum() - normalised = helium_population_updated.mul(number_density.loc[2] / - unnormalised) + normalised = helium_population_updated.mul( + number_density.loc[2] / unnormalised + ) helium_population_updated.update(normalised) return helium_population_updated - def calculate(self, phi, partition_function, number_density, - helium_population): + def calculate( + self, phi, partition_function, number_density, helium_population + ): if self._electron_densities is None: n_e_convergence_threshold = 0.05 n_electron = number_density.sum(axis=0) n_electron_iterations = 0 while True: - ion_number_density, self.block_ids = \ - IonNumberDensity.calculate_with_n_electron( - phi, partition_function, number_density, n_electron, - self.block_ids, self.ion_zero_threshold) + ( + ion_number_density, + self.block_ids, + ) = IonNumberDensity.calculate_with_n_electron( + phi, + partition_function, + number_density, + n_electron, + self.block_ids, + self.ion_zero_threshold, + ) helium_population_updated = self.update_he_population( - helium_population, n_electron, number_density) - ion_number_density.loc[2, 0].update(helium_population_updated.loc[ - 0].sum(axis=0)) - ion_number_density.loc[2, 1].update(helium_population_updated.loc[ - 1].sum(axis=0)) - ion_number_density.loc[2, 2].update(helium_population_updated.loc[ - 2, 0]) - ion_numbers = ion_number_density.index.get_level_values(1).values + helium_population, n_electron, number_density + ) + ion_number_density.loc[2, 0].update( + helium_population_updated.loc[0].sum(axis=0) + ) + ion_number_density.loc[2, 1].update( + helium_population_updated.loc[1].sum(axis=0) + ) + ion_number_density.loc[2, 2].update( + helium_population_updated.loc[2, 0] + ) + ion_numbers = ion_number_density.index.get_level_values( + 1 + ).values ion_numbers = ion_numbers.reshape((ion_numbers.shape[0], 1)) new_n_electron = (ion_number_density.values * ion_numbers).sum( - axis=0) + axis=0 + ) if np.any(np.isnan(new_n_electron)): - raise PlasmaIonizationError('n_electron just turned "nan" -' - ' aborting') + raise PlasmaIonizationError( + 'n_electron just turned "nan" -' " aborting" + ) n_electron_iterations += 1 if n_electron_iterations > 100: - logger.warn('n_electron iterations above 100 ({0}) -' - ' something is probably wrong'.format( - n_electron_iterations)) - if np.all(np.abs(new_n_electron - n_electron) - / n_electron < n_e_convergence_threshold): + logger.warn( + "n_electron iterations above 100 ({0}) -" + " something is probably wrong".format( + n_electron_iterations + ) + ) + if np.all( + np.abs(new_n_electron - n_electron) / n_electron + < n_e_convergence_threshold + ): break n_electron = 0.5 * (new_n_electron + n_electron) else: n_electron = self._electron_densities - ion_number_density, self.block_ids = \ - IonNumberDensity.calculate_with_n_electron( - phi, partition_function, number_density, n_electron, - self.block_ids, self.ion_zero_threshold) + ( + ion_number_density, + self.block_ids, + ) = IonNumberDensity.calculate_with_n_electron( + phi, + partition_function, + number_density, + n_electron, + self.block_ids, + self.ion_zero_threshold, + ) helium_population_updated = self.update_he_population( - helium_population, n_electron, number_density) - ion_number_density.loc[2, 0].update(helium_population_updated.loc[ - 0].sum(axis=0)) - ion_number_density.loc[2, 1].update(helium_population_updated.loc[ - 1].sum(axis=0)) - ion_number_density.loc[2, 2].update(helium_population_updated.loc[ - 2, 0]) + helium_population, n_electron, number_density + ) + ion_number_density.loc[2, 0].update( + helium_population_updated.loc[0].sum(axis=0) + ) + ion_number_density.loc[2, 1].update( + helium_population_updated.loc[1].sum(axis=0) + ) + ion_number_density.loc[2, 2].update( + helium_population_updated.loc[2, 0] + ) return ion_number_density, n_electron, helium_population_updated @@ -388,27 +536,34 @@ class SahaFactor(ProcessingPlasmaProperty): Indexed by atom number, ion number, level number. Columns are zones. """ - outputs = ('phi_ik',) - latex_name = ('\\Phi_{i,\\kappa}',) - def calculate(self, thermal_phi_lte, thermal_lte_level_boltzmann_factor, - thermal_lte_partition_function): + outputs = ("phi_ik",) + latex_name = ("\\Phi_{i,\\kappa}",) + + def calculate( + self, + thermal_phi_lte, + thermal_lte_level_boltzmann_factor, + thermal_lte_partition_function, + ): boltzmann_factor = self._prepare_boltzmann_factor( thermal_lte_level_boltzmann_factor ) phi_saha_index = get_ion_multi_index(boltzmann_factor.index) - partition_function_index = get_ion_multi_index(boltzmann_factor.index, - next_higher=False) + partition_function_index = get_ion_multi_index( + boltzmann_factor.index, next_higher=False + ) phi_saha = thermal_phi_lte.loc[phi_saha_index].values # Replace zero values in phi_saha to avoid zero division in Saha factor phi_saha[phi_saha == 0.0] = sys.float_info.min partition_function = thermal_lte_partition_function.loc[ - partition_function_index].values + partition_function_index + ].values return boltzmann_factor / (phi_saha * partition_function) @staticmethod def _prepare_boltzmann_factor(boltzmann_factor): atomic_number = boltzmann_factor.index.get_level_values(0) ion_number = boltzmann_factor.index.get_level_values(1) - selected_ions_mask = (atomic_number != ion_number) + selected_ions_mask = atomic_number != ion_number return boltzmann_factor[selected_ions_mask] diff --git a/tardis/plasma/properties/j_blues.py b/tardis/plasma/properties/j_blues.py index b85fe5b551e..3e28302fa98 100644 --- a/tardis/plasma/properties/j_blues.py +++ b/tardis/plasma/properties/j_blues.py @@ -2,81 +2,92 @@ import pandas as pd from tardis import constants as const -from tardis.plasma.properties.base import (ProcessingPlasmaProperty, - DataFrameInput) +from tardis.plasma.properties.base import ( + ProcessingPlasmaProperty, + DataFrameInput, +) from tardis.util.base import intensity_black_body class JBluesBlackBody(ProcessingPlasmaProperty): - ''' + """ Attributes ---------- lte_j_blues : Pandas DataFrame, dtype float J_blue values as calculated in LTE. - ''' - outputs = ('j_blues',) - latex_name = ('J^{b}_{lu(LTE)}') + """ + + outputs = ("j_blues",) + latex_name = "J^{b}_{lu(LTE)}" @staticmethod def calculate(lines, nu, t_rad): j_blues = intensity_black_body(nu.values[np.newaxis].T, t_rad) - j_blues = pd.DataFrame(j_blues, index=lines.index, - columns=np.arange(len(t_rad))) + j_blues = pd.DataFrame( + j_blues, index=lines.index, columns=np.arange(len(t_rad)) + ) return j_blues class JBluesDiluteBlackBody(ProcessingPlasmaProperty): - outputs = ('j_blues',) - latex_name = ('J_{\\textrm{blue}}') + outputs = ("j_blues",) + latex_name = "J_{\\textrm{blue}}" @staticmethod def calculate(lines, nu, t_rad, w): j_blues = w * intensity_black_body(nu.values[np.newaxis].T, t_rad) - j_blues = pd.DataFrame(j_blues, index=lines.index, - columns=np.arange(len(t_rad))) + j_blues = pd.DataFrame( + j_blues, index=lines.index, columns=np.arange(len(t_rad)) + ) return j_blues class JBluesDetailed(ProcessingPlasmaProperty): - outputs = ('j_blues',) - latex_name = ('J_{\\textrm{blue}}') + outputs = ("j_blues",) + latex_name = "J_{\\textrm{blue}}" def __init__(self, plasma_parent, w_epsilon): super(JBluesDetailed, self).__init__(plasma_parent) self.w_epsilon = w_epsilon - def calculate(self, lines, nu, t_rad, w, j_blues_norm_factor, - j_blue_estimator): + def calculate( + self, lines, nu, t_rad, w, j_blues_norm_factor, j_blue_estimator + ): # Used for initialization if len(j_blue_estimator) == 0: return JBluesDiluteBlackBody.calculate(lines, nu, t_rad, w) else: j_blues = pd.DataFrame( - j_blue_estimator * - j_blues_norm_factor.value, + j_blue_estimator * j_blues_norm_factor.value, index=lines.index, - columns=np.arange(len(t_rad))) + columns=np.arange(len(t_rad)), + ) for i in range(len(t_rad)): zero_j_blues = j_blues[i] == 0.0 j_blues[i][zero_j_blues] = ( - self.w_epsilon * - intensity_black_body(nu[zero_j_blues].values, - t_rad[i])) + self.w_epsilon + * intensity_black_body(nu[zero_j_blues].values, t_rad[i]) + ) return j_blues class JBluesNormFactor(ProcessingPlasmaProperty): - outputs = ('j_blues_norm_factor',) - latex = ('\\frac{c time_\\textrm{simulation}}}{4 \\pi ' - 'time_\\textrm{simulation} volume}') + outputs = ("j_blues_norm_factor",) + latex = ( + "\\frac{c time_\\textrm{simulation}}}{4 \\pi " + "time_\\textrm{simulation} volume}" + ) @staticmethod def calculate(time_explosion, time_simulation, volume): - return (const.c.cgs * time_explosion / - (4 * np.pi * time_simulation * volume)) + return ( + const.c.cgs + * time_explosion + / (4 * np.pi * time_simulation * volume) + ) class JBluesEstimator(DataFrameInput): - outputs = ('j_blue_estimator',) + outputs = ("j_blue_estimator",) diff --git a/tardis/plasma/properties/level_population.py b/tardis/plasma/properties/level_population.py index a35a25d3192..6b8dd2aef31 100644 --- a/tardis/plasma/properties/level_population.py +++ b/tardis/plasma/properties/level_population.py @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) -__all__ = ['LevelNumberDensity', 'LevelNumberDensityHeNLTE'] +__all__ = ["LevelNumberDensity", "LevelNumberDensityHeNLTE"] class LevelNumberDensity(ProcessingPlasmaProperty): @@ -18,9 +18,10 @@ class LevelNumberDensity(ProcessingPlasmaProperty): Index atom number, ion number, level number. Columns are zones. """ - outputs = ('level_number_density',) - latex_name = ('N_{i,j,k}',) - latex_formula = ('N_{i,j}\\dfrac{bf_{i,j,k}}{Z_{i,j}}',) + + outputs = ("level_number_density",) + latex_name = ("N_{i,j,k}",) + latex_formula = ("N_{i,j}\\dfrac{bf_{i,j,k}}{Z_{i,j}}",) def __init__(self, plasma_parent): super(LevelNumberDensity, self).__init__(plasma_parent) @@ -28,13 +29,19 @@ def __init__(self, plasma_parent): self.initialize_indices = True def _initialize_indices(self, levels, partition_function): - indexer = pd.Series(np.arange(partition_function.shape[0]), - index=partition_function.index) + indexer = pd.Series( + np.arange(partition_function.shape[0]), + index=partition_function.index, + ) self._ion2level_idx = indexer.loc[levels.droplevel(2)].values def _calculate_dilute_lte( - self, level_boltzmann_factor, ion_number_density, - levels, partition_function): + self, + level_boltzmann_factor, + ion_number_density, + levels, + partition_function, + ): """ Calculate the level populations from the level_boltzmann_factor, ion_number_density and partition_function @@ -43,15 +50,20 @@ def _calculate_dilute_lte( self._initialize_indices(levels, partition_function) self.initialize_indices = False partition_function_broadcast = partition_function.values[ - self._ion2level_idx] - level_population_fraction = (level_boltzmann_factor.values / - partition_function_broadcast) + self._ion2level_idx + ] + level_population_fraction = ( + level_boltzmann_factor.values / partition_function_broadcast + ) ion_number_density_broadcast = ion_number_density.values[ - self._ion2level_idx] - level_number_density = (level_population_fraction * - ion_number_density_broadcast) - return pd.DataFrame(level_number_density, - index=level_boltzmann_factor.index) + self._ion2level_idx + ] + level_number_density = ( + level_population_fraction * ion_number_density_broadcast + ) + return pd.DataFrame( + level_number_density, index=level_boltzmann_factor.index + ) calculate = _calculate_dilute_lte @@ -65,17 +77,24 @@ class LevelNumberDensityHeNLTE(LevelNumberDensity): """ def calculate( - self, level_boltzmann_factor, - ion_number_density, levels, partition_function, - helium_population_updated): + self, + level_boltzmann_factor, + ion_number_density, + levels, + partition_function, + helium_population_updated, + ): """ If one of the two helium NLTE methods is used, this updates the helium level populations to the appropriate values. """ level_number_density = self._calculate_dilute_lte( - level_boltzmann_factor, ion_number_density, levels, - partition_function) + level_boltzmann_factor, + ion_number_density, + levels, + partition_function, + ) if helium_population_updated is not None: level_number_density.loc[2].update(helium_population_updated) return level_number_density diff --git a/tardis/plasma/properties/nlte.py b/tardis/plasma/properties/nlte.py index e319e5bd8d6..68c65e8b3ed 100644 --- a/tardis/plasma/properties/nlte.py +++ b/tardis/plasma/properties/nlte.py @@ -4,128 +4,194 @@ import numpy as np import pandas as pd -from tardis.plasma.properties.base import (PreviousIterationProperty, - ProcessingPlasmaProperty) +from tardis.plasma.properties.base import ( + PreviousIterationProperty, + ProcessingPlasmaProperty, +) from tardis.plasma.properties.ion_population import PhiSahaNebular -__all__ = ['PreviousElectronDensities', 'PreviousBetaSobolev', - 'HeliumNLTE', 'HeliumNumericalNLTE'] +__all__ = [ + "PreviousElectronDensities", + "PreviousBetaSobolev", + "HeliumNLTE", + "HeliumNumericalNLTE", +] logger = logging.getLogger(__name__) + class PreviousElectronDensities(PreviousIterationProperty): """ Attributes ---------- previous_electron_densities : The values for the electron densities converged upon in the previous iteration. """ - outputs = ('previous_electron_densities',) + + outputs = ("previous_electron_densities",) def set_initial_value(self, kwargs): - initial_value = pd.Series( - 1000000.0, - index=kwargs['abundance'].columns, - ) + initial_value = pd.Series(1000000.0, index=kwargs["abundance"].columns,) self._set_initial_value(initial_value) + class PreviousBetaSobolev(PreviousIterationProperty): """ Attributes ---------- previous_beta_sobolev : The beta sobolev values converged upon in the previous iteration. """ - outputs = ('previous_beta_sobolev',) + + outputs = ("previous_beta_sobolev",) def set_initial_value(self, kwargs): initial_value = pd.DataFrame( - 1., - index=kwargs['atomic_data'].lines.index, - columns=kwargs['abundance'].columns, - ) + 1.0, + index=kwargs["atomic_data"].lines.index, + columns=kwargs["abundance"].columns, + ) self._set_initial_value(initial_value) + class HeliumNLTE(ProcessingPlasmaProperty): - outputs = ('helium_population',) + outputs = ("helium_population",) @staticmethod - def calculate(level_boltzmann_factor, - ionization_data, beta_rad, g, g_electron, w, t_rad, t_electrons, - delta, zeta_data, number_density, partition_function): + def calculate( + level_boltzmann_factor, + ionization_data, + beta_rad, + g, + g_electron, + w, + t_rad, + t_electrons, + delta, + zeta_data, + number_density, + partition_function, + ): """ Updates all of the helium level populations according to the helium NLTE recomb approximation. """ helium_population = level_boltzmann_factor.loc[2].copy() # He I excited states - he_one_population = HeliumNLTE.calculate_helium_one(g_electron, beta_rad, - ionization_data, level_boltzmann_factor, g, w) + he_one_population = HeliumNLTE.calculate_helium_one( + g_electron, beta_rad, ionization_data, level_boltzmann_factor, g, w + ) helium_population.loc[0].update(he_one_population) - #He I ground state + # He I ground state helium_population.loc[0, 0] = 0.0 - #He II excited states + # He II excited states he_two_population = level_boltzmann_factor.loc[2, 1].mul( - (g.loc[2, 1, 0]**(-1.0))) + (g.loc[2, 1, 0] ** (-1.0)) + ) helium_population.loc[1].update(he_two_population) - #He II ground state + # He II ground state helium_population.loc[1, 0] = 1.0 - #He III states - helium_population.loc[2, 0] = HeliumNLTE.calculate_helium_three(t_rad, w, - zeta_data, t_electrons, delta, g_electron, beta_rad, - ionization_data, g) -# unnormalised = helium_population.sum() -# normalised = helium_population.mul(number_density.ix[2] / -# unnormalised) -# helium_population.update(normalised) + # He III states + helium_population.loc[2, 0] = HeliumNLTE.calculate_helium_three( + t_rad, + w, + zeta_data, + t_electrons, + delta, + g_electron, + beta_rad, + ionization_data, + g, + ) + # unnormalised = helium_population.sum() + # normalised = helium_population.mul(number_density.ix[2] / + # unnormalised) + # helium_population.update(normalised) return helium_population @staticmethod - def calculate_helium_one(g_electron, beta_rad, ionization_data, - level_boltzmann_factor, g, w): + def calculate_helium_one( + g_electron, beta_rad, ionization_data, level_boltzmann_factor, g, w + ): """ Calculates the He I level population values, in equilibrium with the He II ground state. """ - return level_boltzmann_factor.loc[2,0] * (1./(2*g.loc[2, 1, 0])) * \ - (1/g_electron) * (1/(w**2.)) * np.exp( - ionization_data.loc[2,1] * beta_rad) + return ( + level_boltzmann_factor.loc[2, 0] + * (1.0 / (2 * g.loc[2, 1, 0])) + * (1 / g_electron) + * (1 / (w ** 2.0)) + * np.exp(ionization_data.loc[2, 1] * beta_rad) + ) @staticmethod - def calculate_helium_three(t_rad, w, zeta_data, t_electrons, delta, - g_electron, beta_rad, ionization_data, g): + def calculate_helium_three( + t_rad, + w, + zeta_data, + t_electrons, + delta, + g_electron, + beta_rad, + ionization_data, + g, + ): """ Calculates the He III level population values. """ zeta = PhiSahaNebular.get_zeta_values(zeta_data, 2, t_rad)[1] - he_three_population = 2 * \ - (float(g.loc[2, 2, 0]) / g.loc[2, 1, 0]) * g_electron * \ - np.exp(-ionization_data.loc[2, 2] * beta_rad) \ - * w * (delta.loc[2, 2] * zeta + w * (1. - zeta)) * \ - (t_electrons / t_rad) ** 0.5 + he_three_population = ( + 2 + * (float(g.loc[2, 2, 0]) / g.loc[2, 1, 0]) + * g_electron + * np.exp(-ionization_data.loc[2, 2] * beta_rad) + * w + * (delta.loc[2, 2] * zeta + w * (1.0 - zeta)) + * (t_electrons / t_rad) ** 0.5 + ) return he_three_population + class HeliumNumericalNLTE(ProcessingPlasmaProperty): - ''' + """ IMPORTANT: This particular property requires a specific numerical NLTE solver and a specific atomic dataset (neither of which are distributed with Tardis) to work. - ''' - outputs = ('helium_population',) + """ + + outputs = ("helium_population",) + def __init__(self, plasma_parent, heating_rate_data_file): super(HeliumNumericalNLTE, self).__init__(plasma_parent) self._g_upper = None self._g_lower = None - self.heating_rate_data = np.loadtxt( - heating_rate_data_file, unpack=True) - - def calculate(self, ion_number_density, electron_densities, t_electrons, w, - lines, j_blues, levels, level_boltzmann_factor, t_rad, - zeta_data, g_electron, delta, partition_function, ionization_data, - beta_rad, g, time_explosion): - logger.info('Performing numerical NLTE He calculations.') - if len(j_blues)==0: + self.heating_rate_data = np.loadtxt(heating_rate_data_file, unpack=True) + + def calculate( + self, + ion_number_density, + electron_densities, + t_electrons, + w, + lines, + j_blues, + levels, + level_boltzmann_factor, + t_rad, + zeta_data, + g_electron, + delta, + partition_function, + ionization_data, + beta_rad, + g, + time_explosion, + ): + logger.info("Performing numerical NLTE He calculations.") + if len(j_blues) == 0: return None - #Outputting data required by SH module + # Outputting data required by SH module for zone, _ in enumerate(electron_densities): - with open('He_NLTE_Files/shellconditions_{}.txt'.format(zone), - 'w') as output_file: + with open( + "He_NLTE_Files/shellconditions_{}.txt".format(zone), "w" + ) as output_file: output_file.write(ion_number_density.loc[2].sum()[zone]) output_file.write(electron_densities[zone]) output_file.write(t_electrons[zone]) @@ -137,74 +203,109 @@ def calculate(self, ion_number_density, electron_densities, t_electrons, w, output_file.write(self.plasma_parent.v_outer[zone]) for zone, _ in enumerate(electron_densities): - with open('He_NLTE_Files/abundances_{}.txt'.format(zone), 'w') as \ - output_file: - for element in range(1,31): + with open( + "He_NLTE_Files/abundances_{}.txt".format(zone), "w" + ) as output_file: + for element in range(1, 31): try: - number_density = ion_number_density[zone].loc[ - element].sum() + number_density = ( + ion_number_density[zone].loc[element].sum() + ) except: number_density = 0.0 output_file.write(number_density) - helium_lines = lines[lines['atomic_number']==2] - helium_lines = helium_lines[helium_lines['ion_number']==0] + helium_lines = lines[lines["atomic_number"] == 2] + helium_lines = helium_lines[helium_lines["ion_number"] == 0] for zone, _ in enumerate(electron_densities): - with open('He_NLTE_Files/discradfield_{}.txt'.format(zone), 'w') \ - as output_file: + with open( + "He_NLTE_Files/discradfield_{}.txt".format(zone), "w" + ) as output_file: j_blues = pd.DataFrame(j_blues, index=lines.index) helium_j_blues = j_blues[zone].loc[helium_lines.index] for value in helium_lines.index: - if (helium_lines.level_number_lower.loc[value]<35): + if helium_lines.level_number_lower.loc[value] < 35: output_file.write( - int(helium_lines.level_number_lower.loc[value]+1), - int(helium_lines.level_number_upper.loc[value]+1), - j_blues[zone].loc[value]) - #Running numerical simulations + int(helium_lines.level_number_lower.loc[value] + 1), + int(helium_lines.level_number_upper.loc[value] + 1), + j_blues[zone].loc[value], + ) + # Running numerical simulations for zone, _ in enumerate(electron_densities): - os.rename('He_NLTE_Files/abundances{}.txt'.format(zone), - 'He_NLTE_Files/abundances_current.txt') - os.rename('He_NLTE_Files/shellconditions{}.txt'.format(zone), - 'He_NLTE_Files/shellconditions_current.txt') - os.rename('He_NLTE_Files/discradfield{}.txt'.format(zone), - 'He_NLTE_Files/discradfield_current.txt') + os.rename( + "He_NLTE_Files/abundances{}.txt".format(zone), + "He_NLTE_Files/abundances_current.txt", + ) + os.rename( + "He_NLTE_Files/shellconditions{}.txt".format(zone), + "He_NLTE_Files/shellconditions_current.txt", + ) + os.rename( + "He_NLTE_Files/discradfield{}.txt".format(zone), + "He_NLTE_Files/discradfield_current.txt", + ) os.system("nlte-solver-module/bin/nlte_solvertest >/dev/null") - os.rename('He_NLTE_Files/abundances_current.txt', - 'He_NLTE_Files/abundances{}.txt'.format(zone)) - os.rename('He_NLTE_Files/shellconditions_current.txt', - 'He_NLTE_Files/shellconditions{}.txt'.format(zone)) - os.rename('He_NLTE_Files/discradfield_current.txt', - 'He_NLTE_Files/discradfield{}.txt'.format(zone)) - os.rename('debug_occs.dat', 'He_NLTE_Files/occs{}.txt'.format(zone)) - #Reading in populations from files + os.rename( + "He_NLTE_Files/abundances_current.txt", + "He_NLTE_Files/abundances{}.txt".format(zone), + ) + os.rename( + "He_NLTE_Files/shellconditions_current.txt", + "He_NLTE_Files/shellconditions{}.txt".format(zone), + ) + os.rename( + "He_NLTE_Files/discradfield_current.txt", + "He_NLTE_Files/discradfield{}.txt".format(zone), + ) + os.rename("debug_occs.dat", "He_NLTE_Files/occs{}.txt".format(zone)) + # Reading in populations from files helium_population = level_boltzmann_factor.loc[2].copy() for zone, _ in enumerate(electron_densities): - with open('He_NLTE_Files/discradfield{}.txt'.format(zone), 'r') as \ - read_file: + with open( + "He_NLTE_Files/discradfield{}.txt".format(zone), "r" + ) as read_file: for level in range(0, 35): level_population = read_file.readline() level_population = float(level_population) helium_population[zone].loc[0, level] = level_population - helium_population[zone].loc[1, 0] = float( - read_file.readline()) - #Performing He LTE level populations (upper two energy levels, - #He II excited states, He III) - he_one_population = HeliumNLTE.calculate_helium_one(g_electron, - beta_rad, partition_function, ionization_data, - level_boltzmann_factor, electron_densities, g, w, t_rad, - t_electrons) + helium_population[zone].loc[1, 0] = float(read_file.readline()) + # Performing He LTE level populations (upper two energy levels, + # He II excited states, He III) + he_one_population = HeliumNLTE.calculate_helium_one( + g_electron, + beta_rad, + partition_function, + ionization_data, + level_boltzmann_factor, + electron_densities, + g, + w, + t_rad, + t_electrons, + ) helium_population.loc[0, 35].update(he_one_population.loc[35]) helium_population.loc[0, 36].update(he_one_population.loc[36]) he_two_population = level_boltzmann_factor.loc[2, 1, 1:].mul( - (g.loc[2, 1, 0] ** (-1)) * helium_population.loc[s1, 0]) + (g.loc[2, 1, 0] ** (-1)) * helium_population.loc[s1, 0] + ) helium_population.loc[1, 1:].update(he_two_population) helium_population.loc[2, 0] = HeliumNLTE.calculate_helium_three( - t_rad, w, zeta_data, t_electrons, delta, g_electron, beta_rad, - partition_function, ionization_data, electron_densities) + t_rad, + w, + zeta_data, + t_electrons, + delta, + g_electron, + beta_rad, + partition_function, + ionization_data, + electron_densities, + ) unnormalised = helium_population.sum() - normalised = helium_population.mul(ion_number_density.loc[2].sum() - / unnormalised) + normalised = helium_population.mul( + ion_number_density.loc[2].sum() / unnormalised + ) helium_population.update(normalised) return helium_population diff --git a/tardis/plasma/properties/partition_function.py b/tardis/plasma/properties/partition_function.py index 0b31d3c6966..1e584a2ed36 100644 --- a/tardis/plasma/properties/partition_function.py +++ b/tardis/plasma/properties/partition_function.py @@ -9,10 +9,15 @@ logger = logging.getLogger(__name__) -__all__ = ['LevelBoltzmannFactorLTE', 'LevelBoltzmannFactorDiluteLTE', - 'LevelBoltzmannFactorNoNLTE', 'LevelBoltzmannFactorNLTE', - 'PartitionFunction', 'ThermalLevelBoltzmannFactorLTE', - 'ThermalLTEPartitionFunction'] +__all__ = [ + "LevelBoltzmannFactorLTE", + "LevelBoltzmannFactorDiluteLTE", + "LevelBoltzmannFactorNoNLTE", + "LevelBoltzmannFactorNLTE", + "PartitionFunction", + "ThermalLevelBoltzmannFactorLTE", + "ThermalLTEPartitionFunction", +] class LevelBoltzmannFactorLTE(ProcessingPlasmaProperty): @@ -26,20 +31,24 @@ class LevelBoltzmannFactorLTE(ProcessingPlasmaProperty): Columns corresponding to zones. Does not consider NLTE. """ - outputs = ('general_level_boltzmann_factor',) - latex_name = ('bf_{i,j,k}',) - latex_formula = ('g_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ - \\textrm{B}}T_{\\textrm{rad}}}}',) + + outputs = ("general_level_boltzmann_factor",) + latex_name = ("bf_{i,j,k}",) + latex_formula = ( + "g_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ + \\textrm{B}}T_{\\textrm{rad}}}}", + ) @staticmethod def calculate(excitation_energy, g, beta_rad, levels): exponential = np.exp(np.outer(excitation_energy.values, -beta_rad)) - level_boltzmann_factor_array = (g.values[np.newaxis].T * - exponential) - level_boltzmann_factor = pd.DataFrame(level_boltzmann_factor_array, - index=levels, - columns=np.arange(len(beta_rad)), - dtype=np.float64) + level_boltzmann_factor_array = g.values[np.newaxis].T * exponential + level_boltzmann_factor = pd.DataFrame( + level_boltzmann_factor_array, + index=levels, + columns=np.arange(len(beta_rad)), + dtype=np.float64, + ) return level_boltzmann_factor @@ -54,17 +63,19 @@ class ThermalLevelBoltzmannFactorLTE(LevelBoltzmannFactorLTE): by atomic number, ion number, level number. Columns corresponding to zones. """ - outputs = ('thermal_lte_level_boltzmann_factor',) - latex_name = ('bf_{i,j,k}^{\\textrm{LTE}}(T_e)',) - latex_formula = ('g_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ - \\textrm{B}}T_{\\textrm{electron}}}}',) + + outputs = ("thermal_lte_level_boltzmann_factor",) + latex_name = ("bf_{i,j,k}^{\\textrm{LTE}}(T_e)",) + latex_formula = ( + "g_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ + \\textrm{B}}T_{\\textrm{electron}}}}", + ) @staticmethod def calculate(excitation_energy, g, beta_electron, levels): - return super(ThermalLevelBoltzmannFactorLTE, - ThermalLevelBoltzmannFactorLTE).calculate( - excitation_energy, g, beta_electron, levels - ) + return super( + ThermalLevelBoltzmannFactorLTE, ThermalLevelBoltzmannFactorLTE + ).calculate(excitation_energy, g, beta_electron, levels) class LevelBoltzmannFactorDiluteLTE(ProcessingPlasmaProperty): @@ -79,16 +90,20 @@ class LevelBoltzmannFactorDiluteLTE(ProcessingPlasmaProperty): multiplied by an additional factor W. Does not consider NLTE. """ - outputs = ('general_level_boltzmann_factor',) - latex_name = ('bf_{i,j,k}',) - latex_formula = ('Wg_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ - \\textrm{B}}T_{\\textrm{rad}}}}',) + + outputs = ("general_level_boltzmann_factor",) + latex_name = ("bf_{i,j,k}",) + latex_formula = ( + "Wg_{i,j,k}e^{\\dfrac{-\\epsilon_{i,j,k}}{k_{\ + \\textrm{B}}T_{\\textrm{rad}}}}", + ) def calculate( - self, levels, g, excitation_energy, beta_rad, w, - metastability): + self, levels, g, excitation_energy, beta_rad, w, metastability + ): level_boltzmann_factor = LevelBoltzmannFactorLTE.calculate( - excitation_energy, g, beta_rad, levels) + excitation_energy, g, beta_rad, levels + ) level_boltzmann_factor[~metastability] *= w return level_boltzmann_factor @@ -101,7 +116,8 @@ class LevelBoltzmannFactorNoNLTE(ProcessingPlasmaProperty): Returns general_level_boltzmann_factor as this property is included if NLTE is not used. """ - outputs = ('level_boltzmann_factor',) + + outputs = ("level_boltzmann_factor",) @staticmethod def calculate(general_level_boltzmann_factor): @@ -116,25 +132,29 @@ class LevelBoltzmannFactorNLTE(ProcessingPlasmaProperty): Returns general_level_boltzmann_factor but updated for those species treated in NLTE. """ - outputs = ('level_boltzmann_factor',) + + outputs = ("level_boltzmann_factor",) def calculate(self): raise AttributeError( - 'This attribute is not defined on the parent class.' - 'Please use one of the subclasses.') + "This attribute is not defined on the parent class." + "Please use one of the subclasses." + ) @staticmethod def from_config(nlte_conf): if nlte_conf.classical_nebular and not nlte_conf.coronal_approximation: return LevelBoltzmannFactorNLTEClassic elif ( - nlte_conf.coronal_approximation and - not nlte_conf.classical_nebular): + nlte_conf.coronal_approximation and not nlte_conf.classical_nebular + ): return LevelBoltzmannFactorNLTECoronal elif nlte_conf.coronal_approximation and nlte_conf.classical_nebular: - raise PlasmaConfigError('Both coronal approximation and ' - 'classical nebular specified in the ' - 'config.') + raise PlasmaConfigError( + "Both coronal approximation and " + "classical nebular specified in the " + "config." + ) else: return LevelBoltzmannFactorNLTEGeneral @@ -148,19 +168,26 @@ def __init__(self, plasma_parent): self._update_inputs() def _main_nlte_calculation( - self, atomic_data, nlte_data, t_electrons, - j_blues, beta_sobolevs, general_level_boltzmann_factor, - previous_electron_densities, g): + self, + atomic_data, + nlte_data, + t_electrons, + j_blues, + beta_sobolevs, + general_level_boltzmann_factor, + previous_electron_densities, + g, + ): """ The core of the NLTE calculation, used with all possible config. options. """ for species in nlte_data.nlte_species: - logger.info('Calculating rates for species %s', species) + logger.info("Calculating rates for species %s", species) number_of_levels = atomic_data.levels.energy.loc[species].count() lnl = nlte_data.lines_level_number_lower[species] lnu = nlte_data.lines_level_number_upper[species] - lines_index, = nlte_data.lines_idx[species] + (lines_index,) = nlte_data.lines_idx[species] try: j_blues_filtered = j_blues.iloc[lines_index] @@ -176,27 +203,33 @@ def _main_nlte_calculation( 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, len(t_electrons)), - dtype=np.float64) + (number_of_levels, number_of_levels, len(t_electrons)), + dtype=np.float64, + ) r_ul_matrix_reshaped = r_ul_matrix.reshape( - (number_of_levels**2, len(t_electrons))) - r_ul_matrix_reshaped[r_ul_index] = A_uls[np.newaxis].T + \ - B_uls[np.newaxis].T * j_blues_filtered + (number_of_levels ** 2, len(t_electrons)) + ) + r_ul_matrix_reshaped[r_ul_index] = ( + A_uls[np.newaxis].T + B_uls[np.newaxis].T * j_blues_filtered + ) r_ul_matrix_reshaped[r_ul_index] *= beta_sobolevs_filtered r_lu_matrix = np.zeros_like(r_ul_matrix) r_lu_matrix_reshaped = r_lu_matrix.reshape( - (number_of_levels**2, len(t_electrons))) - r_lu_matrix_reshaped[r_lu_index] = B_lus[np.newaxis].T * \ - j_blues_filtered * beta_sobolevs_filtered + (number_of_levels ** 2, len(t_electrons)) + ) + r_lu_matrix_reshaped[r_lu_index] = ( + B_lus[np.newaxis].T * j_blues_filtered * beta_sobolevs_filtered + ) if atomic_data.collision_data is None: collision_matrix = np.zeros_like(r_ul_matrix) else: if previous_electron_densities is None: collision_matrix = np.zeros_like(r_ul_matrix) else: - collision_matrix = nlte_data.get_collision_matrix( - species, t_electrons - ) * previous_electron_densities.values + collision_matrix = ( + nlte_data.get_collision_matrix(species, t_electrons) + * previous_electron_densities.values + ) rates_matrix = r_lu_matrix + r_ul_matrix + collision_matrix for i in range(number_of_levels): rates_matrix[i, i] = -rates_matrix[:, i].sum(axis=0) @@ -206,23 +239,35 @@ def _main_nlte_calculation( for i in range(len(t_electrons)): try: level_boltzmann_factor = np.linalg.solve( - rates_matrix[:, :, i], x) + rates_matrix[:, :, i], x + ) except LinAlgError as e: - if e.message == 'Singular matrix': + if e.message == "Singular matrix": raise ValueError( - 'SingularMatrixError during solving of the ' - 'rate matrix. Does the atomic data contain ' - 'collision data?') + "SingularMatrixError during solving of the " + "rate matrix. Does the atomic data contain " + "collision data?" + ) else: raise e - general_level_boltzmann_factor[i].ix[species] = \ - level_boltzmann_factor * g.loc[species][0] / level_boltzmann_factor[0] + general_level_boltzmann_factor[i].ix[species] = ( + level_boltzmann_factor + * g.loc[species][0] + / level_boltzmann_factor[0] + ) return general_level_boltzmann_factor def _calculate_classical_nebular( - self, t_electrons, lines, atomic_data, - nlte_data, general_level_boltzmann_factor, j_blues, - previous_electron_densities, g): + self, + t_electrons, + lines, + atomic_data, + nlte_data, + general_level_boltzmann_factor, + j_blues, + previous_electron_densities, + g, + ): """ Performs NLTE calculations using the classical nebular treatment. All beta sobolev values taken as 1. @@ -230,19 +275,27 @@ def _calculate_classical_nebular( beta_sobolevs = 1.0 general_level_boltzmann_factor = self._main_nlte_calculation( - atomic_data, - nlte_data, - t_electrons, - j_blues, - beta_sobolevs, - general_level_boltzmann_factor, - previous_electron_densities, g) + atomic_data, + nlte_data, + t_electrons, + j_blues, + beta_sobolevs, + general_level_boltzmann_factor, + previous_electron_densities, + g, + ) return general_level_boltzmann_factor def _calculate_coronal_approximation( - self, t_electrons, lines, atomic_data, - nlte_data, general_level_boltzmann_factor, - previous_electron_densities, g): + self, + t_electrons, + lines, + atomic_data, + nlte_data, + general_level_boltzmann_factor, + previous_electron_densities, + g, + ): """ Performs NLTE calculations using the coronal approximation. All beta sobolev values taken as 1 and j_blues taken as 0. @@ -250,15 +303,29 @@ def _calculate_coronal_approximation( beta_sobolevs = 1.0 j_blues = 0.0 general_level_boltzmann_factor = self._main_nlte_calculation( - atomic_data, nlte_data, t_electrons, j_blues, - beta_sobolevs, general_level_boltzmann_factor, - previous_electron_densities, g) + atomic_data, + nlte_data, + t_electrons, + j_blues, + beta_sobolevs, + general_level_boltzmann_factor, + previous_electron_densities, + g, + ) return general_level_boltzmann_factor def _calculate_general( - self, t_electrons, lines, atomic_data, nlte_data, - general_level_boltzmann_factor, j_blues, - previous_beta_sobolev, previous_electron_densities, g): + self, + t_electrons, + lines, + atomic_data, + nlte_data, + general_level_boltzmann_factor, + j_blues, + previous_beta_sobolev, + previous_electron_densities, + g, + ): """ Full NLTE calculation without approximations. """ @@ -268,9 +335,15 @@ def _calculate_general( beta_sobolevs = previous_beta_sobolev general_level_boltzmann_factor = self._main_nlte_calculation( - atomic_data, nlte_data, t_electrons, j_blues, - beta_sobolevs, general_level_boltzmann_factor, - previous_electron_densities, g) + atomic_data, + nlte_data, + t_electrons, + j_blues, + beta_sobolevs, + general_level_boltzmann_factor, + previous_electron_densities, + g, + ) return general_level_boltzmann_factor @@ -294,13 +367,15 @@ class PartitionFunction(ProcessingPlasmaProperty): Indexed by atomic number, ion number. Columns are zones. """ - outputs = ('partition_function',) - latex_name = ('Z_{i,j}',) - latex_formula = ('\\sum_{k}bf_{i,j,k}',) + + outputs = ("partition_function",) + latex_name = ("Z_{i,j}",) + latex_formula = ("\\sum_{k}bf_{i,j,k}",) def calculate(self, level_boltzmann_factor): return level_boltzmann_factor.groupby( - level=['atomic_number', 'ion_number']).sum() + level=["atomic_number", "ion_number"] + ).sum() class ThermalLTEPartitionFunction(PartitionFunction): @@ -311,8 +386,9 @@ class ThermalLTEPartitionFunction(PartitionFunction): Indexed by atomic number, ion number. Columns are zones. """ - outputs = ('thermal_lte_partition_function',) - latex_name = ('Z_{i,j}(T_\\mathrm{e}',) + + outputs = ("thermal_lte_partition_function",) + latex_name = ("Z_{i,j}(T_\\mathrm{e}",) def calculate(self, thermal_lte_level_boltzmann_factor): return super(ThermalLTEPartitionFunction, self).calculate( diff --git a/tardis/plasma/properties/plasma_input.py b/tardis/plasma/properties/plasma_input.py index a9e0cb90f55..b5d6c55bd85 100644 --- a/tardis/plasma/properties/plasma_input.py +++ b/tardis/plasma/properties/plasma_input.py @@ -1,9 +1,20 @@ -from tardis.plasma.properties.base import (Input, ArrayInput, DataFrameInput) - -__all__ = ['TRadiative', 'DilutionFactor', 'AtomicData', 'Abundance', 'Density', - 'TimeExplosion', 'JBlueEstimator', 'LinkTRadTElectron', - 'HeliumTreatment', 'RInner', 'TInner', 'Volume', - 'ContinuumInteractionSpecies'] +from tardis.plasma.properties.base import Input, ArrayInput, DataFrameInput + +__all__ = [ + "TRadiative", + "DilutionFactor", + "AtomicData", + "Abundance", + "Density", + "TimeExplosion", + "JBlueEstimator", + "LinkTRadTElectron", + "HeliumTreatment", + "RInner", + "TInner", + "Volume", + "ContinuumInteractionSpecies", +] class TRadiative(ArrayInput): @@ -12,8 +23,9 @@ class TRadiative(ArrayInput): ---------- t_rad : Numpy Array, dtype float """ - outputs = ('t_rad',) - latex_name = ('T_{\\textrm{rad}}',) + + outputs = ("t_rad",) + latex_name = ("T_{\\textrm{rad}}",) class DilutionFactor(ArrayInput): @@ -24,8 +36,9 @@ class DilutionFactor(ArrayInput): Factor used in nebular ionisation / dilute excitation calculations to account for the dilution of the radiation field. """ - outputs = ('w',) - latex_name = ('W',) + + outputs = ("w",) + latex_name = ("W",) class AtomicData(Input): @@ -34,7 +47,8 @@ class AtomicData(Input): ---------- atomic_data : Object """ - outputs = ('atomic_data',) + + outputs = ("atomic_data",) class Abundance(Input): @@ -44,7 +58,8 @@ class Abundance(Input): abundance : Numpy array, dtype float Fractional abundance of elements """ - outputs = ('abundance',) + + outputs = ("abundance",) class Density(ArrayInput): @@ -54,8 +69,9 @@ class Density(ArrayInput): density : Numpy array, dtype float Total density values """ - outputs = ('density',) - latex_name = ('\\rho',) + + outputs = ("density",) + latex_name = ("\\rho",) class TimeExplosion(Input): @@ -65,8 +81,9 @@ class TimeExplosion(Input): time_explosion : Float Time since explosion in seconds """ - outputs = ('time_explosion',) - latex_name = ('t_{\\textrm{exp}}',) + + outputs = ("time_explosion",) + latex_name = ("t_{\\textrm{exp}}",) class JBlueEstimator(ArrayInput): @@ -75,8 +92,9 @@ class JBlueEstimator(ArrayInput): ---------- j_blue_estimators : Numpy array """ - outputs = ('j_blue_estimators',) - latex_name = ('J_{\\textrm{blue-estimator}}',) + + outputs = ("j_blue_estimators",) + latex_name = ("J_{\\textrm{blue-estimator}}",) class LinkTRadTElectron(Input): @@ -87,24 +105,25 @@ class LinkTRadTElectron(Input): Value used for estimate of electron temperature. Default is 0.9. """ - outputs = ('link_t_rad_t_electron',) - latex_name = ('T_{\\textrm{electron}}/T_{\\textrm{rad}}',) + + outputs = ("link_t_rad_t_electron",) + latex_name = ("T_{\\textrm{electron}}/T_{\\textrm{rad}}",) class HeliumTreatment(Input): - outputs = ('helium_treatment',) + outputs = ("helium_treatment",) class RInner(Input): - outputs = ('r_inner',) + outputs = ("r_inner",) class TInner(Input): - outputs = ('t_inner',) + outputs = ("t_inner",) class Volume(Input): - outputs = ('volume',) + outputs = ("volume",) class ContinuumInteractionSpecies(Input): @@ -115,4 +134,5 @@ class ContinuumInteractionSpecies(Input): Atomic and ion numbers of elements for which continuum interactions (radiative/collisional ionization and recombination) are treated """ - outputs = ('continuum_interaction_species',) + + outputs = ("continuum_interaction_species",) diff --git a/tardis/plasma/properties/properties.py b/tardis/plasma/properties/properties.py index 43e6f024367..835093088bd 100644 --- a/tardis/plasma/properties/properties.py +++ b/tardis/plasma/properties/properties.py @@ -1,3 +1,3 @@ #### Importing properties from other modules ######## -###################################################### \ No newline at end of file +###################################################### diff --git a/tardis/plasma/properties/property_collections.py b/tardis/plasma/properties/property_collections.py index 3f943270b3f..4b8eb7fa608 100644 --- a/tardis/plasma/properties/property_collections.py +++ b/tardis/plasma/properties/property_collections.py @@ -1,43 +1,93 @@ from tardis.plasma.properties import * + class PlasmaPropertyCollection(list): pass -basic_inputs = PlasmaPropertyCollection([TRadiative, Abundance, Density, - TimeExplosion, AtomicData, DilutionFactor, LinkTRadTElectron, - HeliumTreatment, ContinuumInteractionSpecies]) -basic_properties = PlasmaPropertyCollection([BetaRadiation, - Levels, Lines, AtomicMass, PartitionFunction, - GElectron, IonizationData, NumberDensity, LinesLowerLevelIndex, - LinesUpperLevelIndex, TauSobolev, - StimulatedEmissionFactor, SelectedAtoms, ElectronTemperature]) + +basic_inputs = PlasmaPropertyCollection( + [ + TRadiative, + Abundance, + Density, + TimeExplosion, + AtomicData, + DilutionFactor, + LinkTRadTElectron, + HeliumTreatment, + ContinuumInteractionSpecies, + ] +) +basic_properties = PlasmaPropertyCollection( + [ + BetaRadiation, + Levels, + Lines, + AtomicMass, + PartitionFunction, + GElectron, + IonizationData, + NumberDensity, + LinesLowerLevelIndex, + LinesUpperLevelIndex, + TauSobolev, + StimulatedEmissionFactor, + SelectedAtoms, + ElectronTemperature, + ] +) lte_ionization_properties = PlasmaPropertyCollection([PhiSahaLTE]) lte_excitation_properties = PlasmaPropertyCollection([LevelBoltzmannFactorLTE]) -macro_atom_properties = PlasmaPropertyCollection([BetaSobolev, - TransitionProbabilities]) -nebular_ionization_properties = PlasmaPropertyCollection([PhiSahaNebular, - ZetaData, BetaElectron, RadiationFieldCorrection]) -dilute_lte_excitation_properties = PlasmaPropertyCollection([ - LevelBoltzmannFactorDiluteLTE]) +macro_atom_properties = PlasmaPropertyCollection( + [BetaSobolev, TransitionProbabilities] +) +nebular_ionization_properties = PlasmaPropertyCollection( + [PhiSahaNebular, ZetaData, BetaElectron, RadiationFieldCorrection] +) +dilute_lte_excitation_properties = PlasmaPropertyCollection( + [LevelBoltzmannFactorDiluteLTE] +) non_nlte_properties = PlasmaPropertyCollection([LevelBoltzmannFactorNoNLTE]) -nlte_properties = PlasmaPropertyCollection([ - LevelBoltzmannFactorNLTE, NLTEData, PreviousElectronDensities, - PreviousBetaSobolev, BetaSobolev]) -helium_nlte_properties = PlasmaPropertyCollection([HeliumNLTE, - RadiationFieldCorrection, ZetaData, - BetaElectron, LevelNumberDensityHeNLTE, IonNumberDensityHeNLTE]) -helium_lte_properties = PlasmaPropertyCollection([LevelNumberDensity, - IonNumberDensity]) -helium_numerical_nlte_properties = PlasmaPropertyCollection([ - HeliumNumericalNLTE]) -detailed_j_blues_inputs = PlasmaPropertyCollection([JBluesEstimator, RInner, - TInner, Volume]) -detailed_j_blues_properties = PlasmaPropertyCollection([JBluesDetailed, - JBluesNormFactor, - LuminosityInner, - TimeSimulation]) +nlte_properties = PlasmaPropertyCollection( + [ + LevelBoltzmannFactorNLTE, + NLTEData, + PreviousElectronDensities, + PreviousBetaSobolev, + BetaSobolev, + ] +) +helium_nlte_properties = PlasmaPropertyCollection( + [ + HeliumNLTE, + RadiationFieldCorrection, + ZetaData, + BetaElectron, + LevelNumberDensityHeNLTE, + IonNumberDensityHeNLTE, + ] +) +helium_lte_properties = PlasmaPropertyCollection( + [LevelNumberDensity, IonNumberDensity] +) +helium_numerical_nlte_properties = PlasmaPropertyCollection( + [HeliumNumericalNLTE] +) +detailed_j_blues_inputs = PlasmaPropertyCollection( + [JBluesEstimator, RInner, TInner, Volume] +) +detailed_j_blues_properties = PlasmaPropertyCollection( + [JBluesDetailed, JBluesNormFactor, LuminosityInner, TimeSimulation] +) continuum_interaction_properties = PlasmaPropertyCollection( - [PhotoIonizationData, SpontRecombRateCoeff, - ThermalLevelBoltzmannFactorLTE, ThermalLTEPartitionFunction, BetaElectron, - ThermalGElectron, ThermalPhiSahaLTE, SahaFactor] + [ + PhotoIonizationData, + SpontRecombRateCoeff, + ThermalLevelBoltzmannFactorLTE, + ThermalLTEPartitionFunction, + BetaElectron, + ThermalGElectron, + ThermalPhiSahaLTE, + SahaFactor, + ] ) diff --git a/tardis/plasma/properties/radiative_properties.py b/tardis/plasma/properties/radiative_properties.py index 3d26f8cd9ec..21af5713a98 100644 --- a/tardis/plasma/properties/radiative_properties.py +++ b/tardis/plasma/properties/radiative_properties.py @@ -12,8 +12,13 @@ logger = logging.getLogger(__name__) -__all__ = ['StimulatedEmissionFactor', 'TauSobolev', 'BetaSobolev', - 'TransitionProbabilities'] +__all__ = [ + "StimulatedEmissionFactor", + "TauSobolev", + "BetaSobolev", + "TransitionProbabilities", +] + class StimulatedEmissionFactor(ProcessingPlasmaProperty): """ @@ -22,8 +27,9 @@ class StimulatedEmissionFactor(ProcessingPlasmaProperty): stimulated_emission_factor : Numpy Array, dtype float Indexed by lines, columns as zones. """ - outputs = ('stimulated_emission_factor',) - latex_formula = ('1-\\dfrac{g_{lower}n_{upper}}{g_{upper}n_{lower}}',) + + outputs = ("stimulated_emission_factor",) + latex_formula = ("1-\\dfrac{g_{lower}n_{upper}}{g_{upper}n_{lower}}",) def __init__(self, plasma_parent=None, nlte_species=None): super(StimulatedEmissionFactor, self).__init__(plasma_parent) @@ -33,52 +39,74 @@ def __init__(self, plasma_parent=None, nlte_species=None): def get_g_lower(self, g, lines_lower_level_index): if self._g_lower is None: - g_lower = np.array(g.iloc[lines_lower_level_index], - dtype=np.float64) + g_lower = np.array( + g.iloc[lines_lower_level_index], dtype=np.float64 + ) self._g_lower = g_lower[np.newaxis].T return self._g_lower def get_g_upper(self, g, lines_upper_level_index): if self._g_upper is None: - g_upper = np.array(g.iloc[lines_upper_level_index], - dtype=np.float64) + g_upper = np.array( + g.iloc[lines_upper_level_index], dtype=np.float64 + ) self._g_upper = g_upper[np.newaxis].T return self._g_upper def get_metastable_upper(self, metastability, lines_upper_level_index): - if getattr(self, '_meta_stable_upper', None) is None: + if getattr(self, "_meta_stable_upper", None) is None: self._meta_stable_upper = metastability.values[ - lines_upper_level_index][np.newaxis].T + lines_upper_level_index + ][np.newaxis].T return self._meta_stable_upper - def calculate(self, g, level_number_density, lines_lower_level_index, - lines_upper_level_index, metastability, lines): - n_lower = level_number_density.values.take(lines_lower_level_index, - axis=0, mode='raise') - n_upper = level_number_density.values.take(lines_upper_level_index, - axis=0, mode='raise') + def calculate( + self, + g, + level_number_density, + lines_lower_level_index, + lines_upper_level_index, + metastability, + lines, + ): + n_lower = level_number_density.values.take( + lines_lower_level_index, axis=0, mode="raise" + ) + n_upper = level_number_density.values.take( + lines_upper_level_index, axis=0, mode="raise" + ) g_lower = self.get_g_lower(g, lines_lower_level_index) g_upper = self.get_g_upper(g, lines_upper_level_index) - meta_stable_upper = self.get_metastable_upper(metastability, - lines_upper_level_index) + meta_stable_upper = self.get_metastable_upper( + metastability, lines_upper_level_index + ) - stimulated_emission_factor = ne.evaluate('1 - ((g_lower * n_upper) / ' - '(g_upper * n_lower))') + stimulated_emission_factor = ne.evaluate( + "1 - ((g_lower * n_upper) / " "(g_upper * n_lower))" + ) stimulated_emission_factor[n_lower == 0.0] = 0.0 - stimulated_emission_factor[np.isneginf(stimulated_emission_factor)]\ - = 0.0 - stimulated_emission_factor[meta_stable_upper & - (stimulated_emission_factor < 0)] = 0.0 + stimulated_emission_factor[ + np.isneginf(stimulated_emission_factor) + ] = 0.0 + stimulated_emission_factor[ + meta_stable_upper & (stimulated_emission_factor < 0) + ] = 0.0 if self.nlte_species: - nlte_lines_mask = lines.reset_index().apply( - lambda row: - (row.atomic_number, row.ion_number) in self.nlte_species, - axis=1 - ).values - stimulated_emission_factor[(stimulated_emission_factor < 0) & - nlte_lines_mask[np.newaxis].T] = 0.0 + nlte_lines_mask = ( + lines.reset_index() + .apply( + lambda row: (row.atomic_number, row.ion_number) + in self.nlte_species, + axis=1, + ) + .values + ) + stimulated_emission_factor[ + (stimulated_emission_factor < 0) & nlte_lines_mask[np.newaxis].T + ] = 0.0 return stimulated_emission_factor + class TauSobolev(ProcessingPlasmaProperty): """ Attributes @@ -87,35 +115,65 @@ class TauSobolev(ProcessingPlasmaProperty): Sobolev optical depth for each line. Indexed by line. Columns as zones. """ - outputs = ('tau_sobolevs',) - latex_name = ('\\tau_{\\textrm{sobolev}}',) - latex_formula = ('\\dfrac{\\pi e^{2}}{m_{e} c}f_{lu}\\lambda t_{exp}\ - n_{lower} \\Big(1-\\dfrac{g_{lower}n_{upper}}{g_{upper}n_{lower}}\\Big)',) + + outputs = ("tau_sobolevs",) + latex_name = ("\\tau_{\\textrm{sobolev}}",) + latex_formula = ( + "\\dfrac{\\pi e^{2}}{m_{e} c}f_{lu}\\lambda t_{exp}\ + n_{lower} \\Big(1-\\dfrac{g_{lower}n_{upper}}{g_{upper}n_{lower}}\\Big)", + ) def __init__(self, plasma_parent): super(TauSobolev, self).__init__(plasma_parent) - self.sobolev_coefficient = (((np.pi * const.e.gauss ** 2) / - (const.m_e.cgs * const.c.cgs)) - * u.cm * u.s / u.cm**3).to(1).value - - def calculate(self, lines, level_number_density, lines_lower_level_index, - time_explosion, stimulated_emission_factor, j_blues, - f_lu, wavelength_cm): + self.sobolev_coefficient = ( + ( + ((np.pi * const.e.gauss ** 2) / (const.m_e.cgs * const.c.cgs)) + * u.cm + * u.s + / u.cm ** 3 + ) + .to(1) + .value + ) + + def calculate( + self, + lines, + level_number_density, + lines_lower_level_index, + time_explosion, + stimulated_emission_factor, + j_blues, + f_lu, + wavelength_cm, + ): f_lu = f_lu.values[np.newaxis].T wavelength = wavelength_cm.values[np.newaxis].T - n_lower = level_number_density.values.take(lines_lower_level_index, - axis=0, mode='raise') - tau_sobolevs = (self.sobolev_coefficient * f_lu * wavelength * - time_explosion * n_lower * stimulated_emission_factor) - - if (np.any(np.isnan(tau_sobolevs)) or - np.any(np.isinf(np.abs(tau_sobolevs)))): + n_lower = level_number_density.values.take( + lines_lower_level_index, axis=0, mode="raise" + ) + tau_sobolevs = ( + self.sobolev_coefficient + * f_lu + * wavelength + * time_explosion + * n_lower + * stimulated_emission_factor + ) + + if np.any(np.isnan(tau_sobolevs)) or np.any( + np.isinf(np.abs(tau_sobolevs)) + ): raise ValueError( - 'Some tau_sobolevs are nan, inf, -inf in tau_sobolevs.' - ' Something went wrong!') + "Some tau_sobolevs are nan, inf, -inf in tau_sobolevs." + " Something went wrong!" + ) - return pd.DataFrame(tau_sobolevs, index=lines.index, - columns=np.array(level_number_density.columns)) + return pd.DataFrame( + tau_sobolevs, + index=lines.index, + columns=np.array(level_number_density.columns), + ) class BetaSobolev(ProcessingPlasmaProperty): @@ -124,23 +182,23 @@ class BetaSobolev(ProcessingPlasmaProperty): ---------- beta_sobolev : Numpy Array, dtype float """ - outputs = ('beta_sobolev',) - latex_name = ('\\beta_{\\textrm{sobolev}}',) + + outputs = ("beta_sobolev",) + latex_name = ("\\beta_{\\textrm{sobolev}}",) def calculate(self, tau_sobolevs): - if getattr(self, 'beta_sobolev', None) is None: - initial = 0. + if getattr(self, "beta_sobolev", None) is None: + initial = 0.0 else: initial = self.beta_sobolev beta_sobolev = pd.DataFrame( - initial, - index=tau_sobolevs.index, - columns=tau_sobolevs.columns - ) + initial, index=tau_sobolevs.index, columns=tau_sobolevs.columns + ) - self.calculate_beta_sobolev(tau_sobolevs.values.ravel(), - beta_sobolev.values.ravel()) + self.calculate_beta_sobolev( + tau_sobolevs.values.ravel(), beta_sobolev.values.ravel() + ) return beta_sobolev @staticmethod @@ -148,12 +206,13 @@ def calculate(self, tau_sobolevs): def calculate_beta_sobolev(tau_sobolevs, beta_sobolevs): for i in prange(len(tau_sobolevs)): if tau_sobolevs[i] > 1e3: - beta_sobolevs[i] = tau_sobolevs[i]**-1 + beta_sobolevs[i] = tau_sobolevs[i] ** -1 elif tau_sobolevs[i] < 1e-4: beta_sobolevs[i] = 1 - 0.5 * tau_sobolevs[i] else: beta_sobolevs[i] = (1 - np.exp(-tau_sobolevs[i])) / ( - tau_sobolevs[i]) + tau_sobolevs[i] + ) return beta_sobolevs @@ -163,15 +222,22 @@ class TransitionProbabilities(ProcessingPlasmaProperty): ---------- transition_probabilities : Pandas DataFrame, dtype float """ - outputs = ('transition_probabilities',) + + outputs = ("transition_probabilities",) def __init__(self, plasma_parent): super(TransitionProbabilities, self).__init__(plasma_parent) self.initialize = True - def calculate(self, atomic_data, beta_sobolev, j_blues, - stimulated_emission_factor, tau_sobolevs): - #I wonder why? + def calculate( + self, + atomic_data, + beta_sobolev, + j_blues, + stimulated_emission_factor, + tau_sobolevs, + ): + # I wonder why? # Not sure who wrote this but the answer is that when the plasma is # first initialised (before the first iteration, without temperature # values etc.) there are no j_blues values so this just prevents @@ -180,82 +246,107 @@ def calculate(self, atomic_data, beta_sobolev, j_blues, return None macro_atom_data = self._get_macro_atom_data(atomic_data) if self.initialize: - self.initialize_macro_atom_transition_type_filters(atomic_data, - macro_atom_data) - self.transition_probability_coef = ( - self._get_transition_probability_coefs(macro_atom_data)) + self.initialize_macro_atom_transition_type_filters( + atomic_data, macro_atom_data + ) + self.transition_probability_coef = self._get_transition_probability_coefs( + macro_atom_data + ) self.initialize = False transition_probabilities = self._calculate_transition_probability( - macro_atom_data, - beta_sobolev, - j_blues, - stimulated_emission_factor) - transition_probabilities = pd.DataFrame(transition_probabilities, + macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor + ) + transition_probabilities = pd.DataFrame( + transition_probabilities, index=macro_atom_data.transition_line_id, - columns=tau_sobolevs.columns) + columns=tau_sobolevs.columns, + ) return transition_probabilities - def _calculate_transition_probability(self, macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor): - transition_probabilities = np.empty((self.transition_probability_coef.shape[0], beta_sobolev.shape[1])) - #trans_old = self.calculate_transition_probabilities(macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor) + def _calculate_transition_probability( + self, macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor + ): + transition_probabilities = np.empty( + (self.transition_probability_coef.shape[0], beta_sobolev.shape[1]) + ) + # trans_old = self.calculate_transition_probabilities(macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor) transition_type = macro_atom_data.transition_type.values lines_idx = macro_atom_data.lines_idx.values tpos = macro_atom_data.transition_probability.values macro_atom.calculate_transition_probabilities( - tpos, - beta_sobolev.values, - j_blues.values, - stimulated_emission_factor, - transition_type, - lines_idx, - self.block_references, - transition_probabilities) + tpos, + beta_sobolev.values, + j_blues.values, + stimulated_emission_factor, + transition_type, + lines_idx, + self.block_references, + transition_probabilities, + ) return transition_probabilities - def calculate_transition_probabilities(self, macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor): - transition_probabilities = self.prepare_transition_probabilities(macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor) + def calculate_transition_probabilities( + self, macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor + ): + transition_probabilities = self.prepare_transition_probabilities( + macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor + ) return transition_probabilities - def initialize_macro_atom_transition_type_filters(self, atomic_data, - macro_atom_data): - self.transition_up_filter = (macro_atom_data.transition_type.values - == 1) + def initialize_macro_atom_transition_type_filters( + self, atomic_data, macro_atom_data + ): + self.transition_up_filter = macro_atom_data.transition_type.values == 1 self.transition_up_line_filter = macro_atom_data.lines_idx.values[ - self.transition_up_filter] - self.block_references = np.hstack(( - atomic_data.macro_atom_references.block_references, - len(macro_atom_data))) + self.transition_up_filter + ] + self.block_references = np.hstack( + ( + atomic_data.macro_atom_references.block_references, + len(macro_atom_data), + ) + ) @staticmethod def _get_transition_probability_coefs(macro_atom_data): return macro_atom_data.transition_probability.values[np.newaxis].T - def prepare_transition_probabilities(self, macro_atom_data, beta_sobolev, - j_blues, stimulated_emission_factor): + def prepare_transition_probabilities( + self, macro_atom_data, beta_sobolev, j_blues, stimulated_emission_factor + ): current_beta_sobolev = beta_sobolev.values.take( - macro_atom_data.lines_idx.values, axis=0, mode='raise') - transition_probabilities = self.transition_probability_coef * current_beta_sobolev - j_blues = j_blues.take(self.transition_up_line_filter, axis=0, - mode='raise') + macro_atom_data.lines_idx.values, axis=0, mode="raise" + ) + transition_probabilities = ( + self.transition_probability_coef * current_beta_sobolev + ) + j_blues = j_blues.take( + self.transition_up_line_filter, axis=0, mode="raise" + ) macro_stimulated_emission = stimulated_emission_factor.take( - self.transition_up_line_filter, axis=0, mode='raise') - transition_probabilities[self.transition_up_filter] *= (j_blues * macro_stimulated_emission) + self.transition_up_line_filter, axis=0, mode="raise" + ) + transition_probabilities[self.transition_up_filter] *= ( + j_blues * macro_stimulated_emission + ) return transition_probabilities def _normalize_transition_probabilities(self, transition_probabilities): macro_atom.normalize_transition_probabilities( - transition_probabilities, self.block_references) + transition_probabilities, self.block_references + ) def _new_normalize_transition_probabilities(self, transition_probabilites): for i, start_id in enumerate(self.block_references[:-1]): end_id = self.block_references[i + 1] block = transition_probabilites[start_id:end_id] transition_probabilites[start_id:end_id] *= 1 / ne.evaluate( - 'sum(block, 0)') + "sum(block, 0)" + ) @staticmethod def _get_macro_atom_data(atomic_data): - try: - return atomic_data.macro_atom_data - except: - return atomic_data.macro_atom_data_all + try: + return atomic_data.macro_atom_data + except: + return atomic_data.macro_atom_data_all diff --git a/tardis/plasma/setup_package.py b/tardis/plasma/setup_package.py index a36ef1659a3..0a33ad7fbb9 100644 --- a/tardis/plasma/setup_package.py +++ b/tardis/plasma/setup_package.py @@ -3,23 +3,47 @@ from astropy_helpers.distutils_helpers import get_distutils_option import numpy as np + def get_package_data(): - return {'tardis.plasma.tests':['data/*.dat', 'data/*.txt', 'data/*.yml', 'data/*.h5', 'data/*.dot', 'data/*.tex']} + return { + "tardis.plasma.tests": [ + "data/*.dat", + "data/*.txt", + "data/*.yml", + "data/*.h5", + "data/*.dot", + "data/*.tex", + ] + } + -if get_distutils_option('with_openmp', ['build', 'install', 'develop']) is not None: - compile_args = ['-fopenmp', '-W', '-Wall', '-Wmissing-prototypes', '-std=c99'] - link_args = ['-fopenmp'] - define_macros = [('WITHOPENMP', None)] +if ( + get_distutils_option("with_openmp", ["build", "install", "develop"]) + is not None +): + compile_args = [ + "-fopenmp", + "-W", + "-Wall", + "-Wmissing-prototypes", + "-std=c99", + ] + link_args = ["-fopenmp"] + define_macros = [("WITHOPENMP", None)] else: - compile_args = ['-W', '-Wall', '-Wmissing-prototypes', '-std=c99'] + compile_args = ["-W", "-Wall", "-Wmissing-prototypes", "-std=c99"] link_args = [] define_macros = [] def get_extensions(): - sources = ['tardis/plasma/properties/util/macro_atom.pyx'] - return [Extension('tardis.plasma.properties.util.macro_atom', sources, - include_dirs=['numpy'], - extra_compile_args=compile_args, - extra_link_args=link_args)] - + sources = ["tardis/plasma/properties/util/macro_atom.pyx"] + return [ + Extension( + "tardis.plasma.properties.util.macro_atom", + sources, + include_dirs=["numpy"], + extra_compile_args=compile_args, + extra_link_args=link_args, + ) + ] diff --git a/tardis/plasma/standard_plasmas.py b/tardis/plasma/standard_plasmas.py index 08a3cae1070..e6efb300577 100644 --- a/tardis/plasma/standard_plasmas.py +++ b/tardis/plasma/standard_plasmas.py @@ -8,24 +8,35 @@ from tardis.io.config_reader import ConfigurationError from tardis.util.base import species_string_to_tuple from tardis.plasma import BasePlasma -from tardis.plasma.properties.property_collections import (basic_inputs, - basic_properties, lte_excitation_properties, lte_ionization_properties, - macro_atom_properties, dilute_lte_excitation_properties, - nebular_ionization_properties, non_nlte_properties, - nlte_properties, helium_nlte_properties, helium_numerical_nlte_properties, - helium_lte_properties, detailed_j_blues_properties, - detailed_j_blues_inputs, continuum_interaction_properties) +from tardis.plasma.properties.property_collections import ( + basic_inputs, + basic_properties, + lte_excitation_properties, + lte_ionization_properties, + macro_atom_properties, + dilute_lte_excitation_properties, + nebular_ionization_properties, + non_nlte_properties, + nlte_properties, + helium_nlte_properties, + helium_numerical_nlte_properties, + helium_lte_properties, + detailed_j_blues_properties, + detailed_j_blues_inputs, + continuum_interaction_properties, +) from tardis.plasma.exceptions import PlasmaConfigError from tardis.plasma.properties import ( - LevelBoltzmannFactorNLTE, - JBluesBlackBody, - JBluesDiluteBlackBody, - JBluesDetailed, - RadiationFieldCorrection, - StimulatedEmissionFactor, - HeliumNumericalNLTE, - IonNumberDensity) + LevelBoltzmannFactorNLTE, + JBluesBlackBody, + JBluesDiluteBlackBody, + JBluesDetailed, + RadiationFieldCorrection, + StimulatedEmissionFactor, + HeliumNumericalNLTE, + IonNumberDensity, +) logger = logging.getLogger(__name__) @@ -49,133 +60,156 @@ def assemble_plasma(config, model, atom_data=None): """ # Convert the nlte species list to a proper format. - nlte_species = [species_string_to_tuple(s) for s in - config.plasma.nlte.species] + nlte_species = [ + species_string_to_tuple(s) for s in config.plasma.nlte.species + ] # Convert the continuum interaction species list to a proper format. continuum_interaction_species = [ - species_string_to_tuple(s) for s in - config.plasma.continuum_interaction.species + species_string_to_tuple(s) + for s in config.plasma.continuum_interaction.species ] continuum_interaction_species = pd.MultiIndex.from_tuples( - continuum_interaction_species, names=['atomic_number', 'ion_number'] + continuum_interaction_species, names=["atomic_number", "ion_number"] ) if atom_data is None: - if 'atom_data' in config: + if "atom_data" in config: if os.path.isabs(config.atom_data): atom_data_fname = config.atom_data else: - atom_data_fname = os.path.join(config.config_dirname, - config.atom_data) + atom_data_fname = os.path.join( + config.config_dirname, config.atom_data + ) else: - raise ValueError('No atom_data option found in the configuration.') + raise ValueError("No atom_data option found in the configuration.") - logger.info('Reading Atomic Data from %s', atom_data_fname) + logger.info("Reading Atomic Data from %s", atom_data_fname) try: atom_data = AtomData.from_hdf(atom_data_fname) except TypeError as e: - print(e, 'Error might be from the use of an old-format of the atomic database, \n' - 'please see https://github.com/tardis-sn/tardis-refdata/tree/master/atom_data' - ',for the most recent version.') + print( + e, + "Error might be from the use of an old-format of the atomic database, \n" + "please see https://github.com/tardis-sn/tardis-refdata/tree/master/atom_data" + ",for the most recent version.", + ) raise atom_data.prepare_atom_data( model.abundance.index, line_interaction_type=config.plasma.line_interaction_type, - nlte_species=nlte_species) + nlte_species=nlte_species, + ) # Check if continuum interaction species are in selected_atoms continuum_atoms = continuum_interaction_species.get_level_values( - 'atomic_number' + "atomic_number" ) continuum_atoms_in_selected_atoms = np.all( continuum_atoms.isin(atom_data.selected_atomic_numbers) ) if not continuum_atoms_in_selected_atoms: - raise ConfigurationError('Not all continuum interaction species ' - 'belong to atoms that have been specified ' - 'in the configuration.') - - kwargs = dict(t_rad=model.t_radiative, abundance=model.abundance, - density=model.density, atomic_data=atom_data, - time_explosion=model.time_explosion, - w=model.dilution_factor, link_t_rad_t_electron=0.9, - continuum_interaction_species=continuum_interaction_species) + raise ConfigurationError( + "Not all continuum interaction species " + "belong to atoms that have been specified " + "in the configuration." + ) + + kwargs = dict( + t_rad=model.t_radiative, + abundance=model.abundance, + density=model.density, + atomic_data=atom_data, + time_explosion=model.time_explosion, + w=model.dilution_factor, + link_t_rad_t_electron=0.9, + continuum_interaction_species=continuum_interaction_species, + ) plasma_modules = basic_inputs + basic_properties property_kwargs = {} if config.plasma.continuum_interaction.species: plasma_modules += continuum_interaction_properties - if config.plasma.radiative_rates_type == 'blackbody': + if config.plasma.radiative_rates_type == "blackbody": plasma_modules.append(JBluesBlackBody) - elif config.plasma.radiative_rates_type == 'dilute-blackbody': + elif config.plasma.radiative_rates_type == "dilute-blackbody": plasma_modules.append(JBluesDiluteBlackBody) - elif config.plasma.radiative_rates_type == 'detailed': + elif config.plasma.radiative_rates_type == "detailed": plasma_modules += detailed_j_blues_properties + detailed_j_blues_inputs - kwargs.update(r_inner=model.r_inner, - t_inner=model.t_inner, - volume=model.volume, - j_blue_estimator=None) - property_kwargs[JBluesDetailed] = {'w_epsilon': config.plasma.w_epsilon} + kwargs.update( + r_inner=model.r_inner, + t_inner=model.t_inner, + volume=model.volume, + j_blue_estimator=None, + ) + property_kwargs[JBluesDetailed] = {"w_epsilon": config.plasma.w_epsilon} else: - raise ValueError('radiative_rates_type type unknown - %s', - config.plasma.radiative_rates_type) + raise ValueError( + "radiative_rates_type type unknown - %s", + config.plasma.radiative_rates_type, + ) - if config.plasma.excitation == 'lte': + if config.plasma.excitation == "lte": plasma_modules += lte_excitation_properties - elif config.plasma.excitation == 'dilute-lte': + elif config.plasma.excitation == "dilute-lte": plasma_modules += dilute_lte_excitation_properties - if config.plasma.ionization == 'lte': + if config.plasma.ionization == "lte": plasma_modules += lte_ionization_properties - elif config.plasma.ionization == 'nebular': + elif config.plasma.ionization == "nebular": plasma_modules += nebular_ionization_properties if nlte_species: plasma_modules += nlte_properties nlte_conf = config.plasma.nlte - plasma_modules.append( - LevelBoltzmannFactorNLTE.from_config(nlte_conf) - ) + plasma_modules.append(LevelBoltzmannFactorNLTE.from_config(nlte_conf)) property_kwargs[StimulatedEmissionFactor] = dict( - nlte_species=nlte_species) + nlte_species=nlte_species + ) else: plasma_modules += non_nlte_properties - if config.plasma.line_interaction_type in ('downbranch', 'macroatom'): + if config.plasma.line_interaction_type in ("downbranch", "macroatom"): plasma_modules += macro_atom_properties - if 'delta_treatment' in config.plasma: + if "delta_treatment" in config.plasma: property_kwargs[RadiationFieldCorrection] = dict( - delta_treatment=config.plasma.delta_treatment) + delta_treatment=config.plasma.delta_treatment + ) - if config.plasma.helium_treatment == 'recomb-nlte': + if config.plasma.helium_treatment == "recomb-nlte": plasma_modules += helium_nlte_properties - elif config.plasma.helium_treatment == 'numerical-nlte': + elif config.plasma.helium_treatment == "numerical-nlte": plasma_modules += helium_numerical_nlte_properties # TODO: See issue #633 - if config.plasma.heating_rate_data_file in ['none', None]: - raise PlasmaConfigError('Heating rate data file not specified') + if config.plasma.heating_rate_data_file in ["none", None]: + raise PlasmaConfigError("Heating rate data file not specified") else: property_kwargs[HeliumNumericalNLTE] = dict( - heating_rate_data_file=config.plasma.heating_rate_data_file) + heating_rate_data_file=config.plasma.heating_rate_data_file + ) else: plasma_modules += helium_lte_properties if model._electron_densities: electron_densities = pd.Series(model._electron_densities.cgs.value) - if config.plasma.helium_treatment == 'numerical-nlte': + if config.plasma.helium_treatment == "numerical-nlte": property_kwargs[IonNumberDensityHeNLTE] = dict( - electron_densities=electron_densities) + electron_densities=electron_densities + ) else: property_kwargs[IonNumberDensity] = dict( - electron_densities=electron_densities) + electron_densities=electron_densities + ) - kwargs['helium_treatment'] = config.plasma.helium_treatment + kwargs["helium_treatment"] = config.plasma.helium_treatment - plasma = BasePlasma(plasma_properties=plasma_modules, - property_kwargs=property_kwargs, **kwargs) + plasma = BasePlasma( + plasma_properties=plasma_modules, + property_kwargs=property_kwargs, + **kwargs, + ) return plasma diff --git a/tardis/plasma/tests/test_complete_plasmas.py b/tardis/plasma/tests/test_complete_plasmas.py index ffbc25bc160..fc967fa13e6 100644 --- a/tardis/plasma/tests/test_complete_plasmas.py +++ b/tardis/plasma/tests/test_complete_plasmas.py @@ -9,124 +9,156 @@ from tardis.simulation import Simulation ionization = [ - {'ionization': 'nebular'}, - {'ionization': 'lte'}, + {"ionization": "nebular"}, + {"ionization": "lte"}, ] -excitation = [ - {'excitation': 'lte'}, - {'excitation': 'dilute-lte'} -] +excitation = [{"excitation": "lte"}, {"excitation": "dilute-lte"}] radiative_rates_type = [ - {'radiative_rates_type': 'detailed', 'w_epsilon': 1.0e-10}, - {'radiative_rates_type': 'detailed'}, - {'radiative_rates_type': 'blackbody'}, - {'radiative_rates_type': 'dilute-blackbody'} + {"radiative_rates_type": "detailed", "w_epsilon": 1.0e-10}, + {"radiative_rates_type": "detailed"}, + {"radiative_rates_type": "blackbody"}, + {"radiative_rates_type": "dilute-blackbody"}, ] line_interaction_type = [ - {'line_interaction_type': 'scatter'}, - {'line_interaction_type': 'macroatom'}, - {'line_interaction_type': 'downbranch'} + {"line_interaction_type": "scatter"}, + {"line_interaction_type": "macroatom"}, + {"line_interaction_type": "downbranch"}, ] disable_electron_scattering = [ - {'disable_electron_scattering': True}, - {'disable_electron_scattering': False} + {"disable_electron_scattering": True}, + {"disable_electron_scattering": False}, ] nlte = [ - {'nlte': {'species': ['He I'], 'coronal_approximation': True}}, - {'nlte': {'species': ['He I'], 'classical_nebular': True}}, - {'nlte': {'species': ['He I']}} + {"nlte": {"species": ["He I"], "coronal_approximation": True}}, + {"nlte": {"species": ["He I"], "classical_nebular": True}}, + {"nlte": {"species": ["He I"]}}, ] -initial_t_inner = [ - {'initial_t_inner': '10000 K'} -] +initial_t_inner = [{"initial_t_inner": "10000 K"}] -initial_t_rad = [ - {'initial_t_rad': '10000 K'} -] +initial_t_rad = [{"initial_t_rad": "10000 K"}] helium_treatment = [ - {'helium_treatment': 'recomb-nlte'}, - {'helium_treatment': 'recomb-nlte', 'delta_treatment': 0.5} + {"helium_treatment": "recomb-nlte"}, + {"helium_treatment": "recomb-nlte", "delta_treatment": 0.5}, ] config_list = ( - ionization + excitation + radiative_rates_type + - line_interaction_type + disable_electron_scattering + nlte + - initial_t_inner + initial_t_rad + helium_treatment) + ionization + + excitation + + radiative_rates_type + + line_interaction_type + + disable_electron_scattering + + nlte + + initial_t_inner + + initial_t_rad + + helium_treatment +) def idfn(fixture_value): - ''' + """ This function creates a string from a dictionary. We use it to obtain a readable name for the config fixture. - ''' - return str('-'.join([ - '{}:{}'.format(k, v) for k, v in fixture_value.items()])) + """ + return str( + "-".join(["{}:{}".format(k, v) for k, v in fixture_value.items()]) + ) class TestPlasma(object): - general_properties = ['beta_rad', 'g_electron', 'selected_atoms', - 'number_density', 't_electrons', 'w', 't_rad', 'beta_electron'] - partiton_properties = ['level_boltzmann_factor', 'partition_function'] - atomic_properties = ['excitation_energy', 'lines', 'lines_lower_level_index', - 'lines_upper_level_index', 'atomic_mass', 'ionization_data', - 'nu', 'wavelength_cm', 'f_lu', 'metastability'] - ion_population_properties = ['delta', 'previous_electron_densities', - 'phi', 'ion_number_density', 'electron_densities'] - level_population_properties = ['level_number_density'] - radiative_properties = ['stimulated_emission_factor', 'previous_beta_sobolev', - 'tau_sobolevs', 'beta_sobolev', 'transition_probabilities'] - j_blues_properties = ['j_blues', 'j_blues_norm_factor', 'j_blue_estimator'] - input_properties = ['volume', 'r_inner'] - helium_nlte_properties = ['helium_population', 'helium_population_updated'] + general_properties = [ + "beta_rad", + "g_electron", + "selected_atoms", + "number_density", + "t_electrons", + "w", + "t_rad", + "beta_electron", + ] + partiton_properties = ["level_boltzmann_factor", "partition_function"] + atomic_properties = [ + "excitation_energy", + "lines", + "lines_lower_level_index", + "lines_upper_level_index", + "atomic_mass", + "ionization_data", + "nu", + "wavelength_cm", + "f_lu", + "metastability", + ] + ion_population_properties = [ + "delta", + "previous_electron_densities", + "phi", + "ion_number_density", + "electron_densities", + ] + level_population_properties = ["level_number_density"] + radiative_properties = [ + "stimulated_emission_factor", + "previous_beta_sobolev", + "tau_sobolevs", + "beta_sobolev", + "transition_probabilities", + ] + j_blues_properties = ["j_blues", "j_blues_norm_factor", "j_blue_estimator"] + input_properties = ["volume", "r_inner"] + helium_nlte_properties = ["helium_population", "helium_population_updated"] combined_properties = ( - general_properties + partiton_properties + - atomic_properties + ion_population_properties + - level_population_properties + radiative_properties + - j_blues_properties + input_properties + helium_nlte_properties) - - scalars_properties = ['time_explosion', 'link_t_rad_t_electron'] + general_properties + + partiton_properties + + atomic_properties + + ion_population_properties + + level_population_properties + + radiative_properties + + j_blues_properties + + input_properties + + helium_nlte_properties + ) + + scalars_properties = ["time_explosion", "link_t_rad_t_electron"] @pytest.fixture(scope="class") def chianti_he_db_fpath(self, tardis_ref_path): - return os.path.abspath(os.path.join( - tardis_ref_path, 'atom_data', 'chianti_He.h5')) - - @pytest.fixture( - scope="class", - params=config_list, - ids=idfn - ) + return os.path.abspath( + os.path.join(tardis_ref_path, "atom_data", "chianti_He.h5") + ) + + @pytest.fixture(scope="class", params=config_list, ids=idfn) def config(self, request): config_path = os.path.join( - 'tardis', 'plasma', 'tests', 'data', 'plasma_base_test_config.yml') + "tardis", "plasma", "tests", "data", "plasma_base_test_config.yml" + ) config = Configuration.from_yaml(config_path) - hash_string = '' + hash_string = "" for prop, value in request.param.items(): - hash_string = '_'.join((hash_string, prop)) - if prop == 'nlte': + hash_string = "_".join((hash_string, prop)) + if prop == "nlte": for nlte_prop, nlte_value in request.param[prop].items(): config.plasma.nlte[nlte_prop] = nlte_value - if nlte_prop != 'species': - hash_string = '_'.join((hash_string, nlte_prop)) + if nlte_prop != "species": + hash_string = "_".join((hash_string, nlte_prop)) else: config.plasma[prop] = value - hash_string = '_'.join((hash_string, str(value))) + hash_string = "_".join((hash_string, str(value))) hash_string = os.path.join("plasma_unittest", hash_string) - setattr(config.plasma, 'save_path', hash_string) + setattr(config.plasma, "save_path", hash_string) return config @pytest.fixture(scope="class") def plasma(self, chianti_he_db_fpath, config, tardis_ref_data): - config['atom_data'] = chianti_he_db_fpath + config["atom_data"] = chianti_he_db_fpath sim = Simulation.from_config(config) if pytest.config.getvalue("--generate-reference"): sim.plasma.to_hdf(tardis_ref_data, path=config.plasma.save_path) @@ -141,7 +173,7 @@ def test_plasma_properties(self, plasma, tardis_ref_data, config, attr): actual = pd.Series(actual) else: actual = pd.DataFrame(actual) - key = os.path.join(config.plasma.save_path, 'plasma', attr) + key = os.path.join(config.plasma.save_path, "plasma", attr) expected = tardis_ref_data[key] pdt.assert_almost_equal(actual, expected) else: @@ -149,32 +181,28 @@ def test_plasma_properties(self, plasma, tardis_ref_data, config, attr): def test_levels(self, plasma, tardis_ref_data, config): actual = pd.DataFrame(plasma.levels) - key = os.path.join( - config.plasma.save_path, 'plasma', 'levels') + key = os.path.join(config.plasma.save_path, "plasma", "levels") expected = tardis_ref_data[key] pdt.assert_almost_equal(actual, expected) @pytest.mark.parametrize("attr", scalars_properties) def test_scalars_properties(self, plasma, tardis_ref_data, config, attr): actual = getattr(plasma, attr) - if hasattr(actual, 'cgs'): + if hasattr(actual, "cgs"): actual = actual.cgs.value - key = os.path.join( - config.plasma.save_path, 'plasma', 'scalars') + key = os.path.join(config.plasma.save_path, "plasma", "scalars") expected = tardis_ref_data[key][attr] pdt.assert_almost_equal(actual, expected) def test_helium_treatment(self, plasma, tardis_ref_data, config): actual = plasma.helium_treatment - key = os.path.join( - config.plasma.save_path, 'plasma', 'scalars') - expected = tardis_ref_data[key]['helium_treatment'] + key = os.path.join(config.plasma.save_path, "plasma", "scalars") + expected = tardis_ref_data[key]["helium_treatment"] assert actual == expected def test_zeta_data(self, plasma, tardis_ref_data, config): - if hasattr(plasma, 'zeta_data'): + if hasattr(plasma, "zeta_data"): actual = plasma.zeta_data - key = os.path.join( - config.plasma.save_path, 'plasma', 'zeta_data') + key = os.path.join(config.plasma.save_path, "plasma", "zeta_data") expected = tardis_ref_data[key] assert_almost_equal(actual, expected.values) diff --git a/tardis/plasma/tests/test_hdf_plasma.py b/tardis/plasma/tests/test_hdf_plasma.py index 1237339f2ed..fdacbeaff5e 100644 --- a/tardis/plasma/tests/test_hdf_plasma.py +++ b/tardis/plasma/tests/test_hdf_plasma.py @@ -9,78 +9,112 @@ # Save and Load ### + @pytest.fixture(scope="module", autouse=True) def to_hdf_buffer(hdf_file_path, simulation_verysimple): simulation_verysimple.plasma.to_hdf(hdf_file_path) -plasma_properties_list = ['number_density', 'beta_rad', 'general_level_boltzmann_factor', 'level_boltzmann_factor', - 'stimulated_emission_factor', 't_electrons', 'wavelength_cm', 'lines_lower_level_index', - 'ionization_data', 'density', 'atomic_mass', 'level_number_density', 'lines_upper_level_index', - 'nu', 'beta_sobolev', 'transition_probabilities', 'phi', - 'electron_densities', 't_rad', 'selected_atoms', 'ion_number_density', 'partition_function', - 'abundance', 'g_electron', 'g', 'lines', 'f_lu', 'tau_sobolevs', 'j_blues', - 'metastability', 'w', 'excitation_energy'] +plasma_properties_list = [ + "number_density", + "beta_rad", + "general_level_boltzmann_factor", + "level_boltzmann_factor", + "stimulated_emission_factor", + "t_electrons", + "wavelength_cm", + "lines_lower_level_index", + "ionization_data", + "density", + "atomic_mass", + "level_number_density", + "lines_upper_level_index", + "nu", + "beta_sobolev", + "transition_probabilities", + "phi", + "electron_densities", + "t_rad", + "selected_atoms", + "ion_number_density", + "partition_function", + "abundance", + "g_electron", + "g", + "lines", + "f_lu", + "tau_sobolevs", + "j_blues", + "metastability", + "w", + "excitation_energy", +] @pytest.mark.parametrize("attr", plasma_properties_list) def test_hdf_plasma(hdf_file_path, simulation_verysimple, attr): if hasattr(simulation_verysimple.plasma, attr): actual = getattr(simulation_verysimple.plasma, attr) - if hasattr(actual, 'cgs'): + if hasattr(actual, "cgs"): actual = actual.cgs.value - path = os.path.join('plasma', attr) + path = os.path.join("plasma", attr) expected = pd.read_hdf(hdf_file_path, path) assert_almost_equal(actual, expected.values) def test_hdf_levels(hdf_file_path, simulation_verysimple): - actual = getattr(simulation_verysimple.plasma, 'levels') - if hasattr(actual, 'cgs'): + actual = getattr(simulation_verysimple.plasma, "levels") + if hasattr(actual, "cgs"): actual = actual.cgs.value - path = os.path.join('plasma', 'levels') + path = os.path.join("plasma", "levels") expected = pd.read_hdf(hdf_file_path, path) pdt.assert_almost_equal(pd.DataFrame(actual), expected) -scalars_list = ['time_explosion', 'link_t_rad_t_electron'] +scalars_list = ["time_explosion", "link_t_rad_t_electron"] @pytest.mark.parametrize("attr", scalars_list) def test_hdf_scalars(hdf_file_path, simulation_verysimple, attr): actual = getattr(simulation_verysimple.plasma, attr) - if hasattr(actual, 'cgs'): + if hasattr(actual, "cgs"): actual = actual.cgs.value - path = os.path.join('plasma', 'scalars') + path = os.path.join("plasma", "scalars") expected = pd.read_hdf(hdf_file_path, path)[attr] assert_almost_equal(actual, expected) def test_hdf_helium_treatment(hdf_file_path, simulation_verysimple): - actual = getattr(simulation_verysimple.plasma, 'helium_treatment') - path = os.path.join('plasma', 'scalars') - expected = pd.read_hdf(hdf_file_path, path)['helium_treatment'] + actual = getattr(simulation_verysimple.plasma, "helium_treatment") + path = os.path.join("plasma", "scalars") + expected = pd.read_hdf(hdf_file_path, path)["helium_treatment"] assert actual == expected def test_atomic_data_uuid(hdf_file_path, simulation_verysimple): - actual = getattr(simulation_verysimple.plasma.atomic_data, 'uuid1') - path = os.path.join('plasma', 'scalars') - expected = pd.read_hdf(hdf_file_path, path)['atom_data_uuid'] + actual = getattr(simulation_verysimple.plasma.atomic_data, "uuid1") + path = os.path.join("plasma", "scalars") + expected = pd.read_hdf(hdf_file_path, path)["atom_data_uuid"] assert actual == expected + @pytest.fixture(scope="module", autouse=True) def to_hdf_collection_buffer(hdf_file_path, simulation_verysimple): simulation_verysimple.plasma.to_hdf( - hdf_file_path, name='collection', collection=property_collections.basic_inputs) + hdf_file_path, + name="collection", + collection=property_collections.basic_inputs, + ) + + +collection_properties = ["t_rad", "w", "density"] -collection_properties = ['t_rad', 'w', 'density'] @pytest.mark.parametrize("attr", collection_properties) def test_collection(hdf_file_path, simulation_verysimple, attr): actual = getattr(simulation_verysimple.plasma, attr) - if hasattr(actual, 'cgs'): + if hasattr(actual, "cgs"): actual = actual.cgs.value - path = os.path.join('collection', attr) + path = os.path.join("collection", attr) expected = pd.read_hdf(hdf_file_path, path) assert_almost_equal(actual, expected.values) diff --git a/tardis/plasma/tests/test_plasma_vboundary.py b/tardis/plasma/tests/test_plasma_vboundary.py index cba7649ae6a..b57e4c84dcb 100644 --- a/tardis/plasma/tests/test_plasma_vboundary.py +++ b/tardis/plasma/tests/test_plasma_vboundary.py @@ -8,30 +8,44 @@ from tardis.simulation import Simulation +DATA_PATH = os.path.join(tardis.__path__[0], "plasma", "tests", "data") -DATA_PATH = os.path.join(tardis.__path__[0], 'plasma', 'tests', 'data') @pytest.fixture def config_init_trad_fname(): - return os.path.join(DATA_PATH, 'config_init_trad.yml') + return os.path.join(DATA_PATH, "config_init_trad.yml") -@pytest.mark.parametrize("v_inner_boundary, v_outer_boundary", - [(3350, 3650), - (2900, 3750), - (2900, 3850), - (2900, 3900), - (2950, 3750), - (2950, 3850), - (2950, 3900), - (3050, 3750), - (3050, 3850), - (3050, 3900), - (3150, 3750), - (3150, 3850), - (3150, 3900)]) -def test_plasma_vboundary(config_init_trad_fname, v_inner_boundary, v_outer_boundary, atomic_data_fname): + +@pytest.mark.parametrize( + "v_inner_boundary, v_outer_boundary", + [ + (3350, 3650), + (2900, 3750), + (2900, 3850), + (2900, 3900), + (2950, 3750), + (2950, 3850), + (2950, 3900), + (3050, 3750), + (3050, 3850), + (3050, 3900), + (3150, 3750), + (3150, 3850), + (3150, 3900), + ], +) +def test_plasma_vboundary( + config_init_trad_fname, + v_inner_boundary, + v_outer_boundary, + atomic_data_fname, +): tardis_config = Configuration.from_yaml(config_init_trad_fname) tardis_config.atom_data = atomic_data_fname - tardis_config.model.structure.v_inner_boundary = v_inner_boundary * u.km / u.s - tardis_config.model.structure.v_outer_boundary = v_outer_boundary * u.km / u.s + tardis_config.model.structure.v_inner_boundary = ( + v_inner_boundary * u.km / u.s + ) + tardis_config.model.structure.v_outer_boundary = ( + v_outer_boundary * u.km / u.s + ) simulation = Simulation.from_config(tardis_config) diff --git a/tardis/plasma/tests/test_plasmas_full.py b/tardis/plasma/tests/test_plasmas_full.py index 6bf79bd0e09..ec7d299b80e 100644 --- a/tardis/plasma/tests/test_plasmas_full.py +++ b/tardis/plasma/tests/test_plasmas_full.py @@ -10,33 +10,33 @@ from tardis.io.config_reader import Configuration config_files = { - 'lte': 'tardis/plasma/tests/data/plasma_test_config_lte.yml', - 'nlte': 'tardis/plasma/tests/data/plasma_test_config_nlte.yml', - } + "lte": "tardis/plasma/tests/data/plasma_test_config_lte.yml", + "nlte": "tardis/plasma/tests/data/plasma_test_config_nlte.yml", +} -class TestPlasmas(): +class TestPlasmas: - name = 'plasma_full/' + name = "plasma_full/" - @pytest.fixture(scope='class') + @pytest.fixture(scope="class") def refdata(self, tardis_ref_data): def get_ref_data(key): - return tardis_ref_data[os.path.join( - self.name, self._test_name, key)] + return tardis_ref_data[ + os.path.join(self.name, self._test_name, key) + ] + return get_ref_data @pytest.fixture( - scope="class", - params=config_files.items(), - ids=config_files.keys() - ) + scope="class", params=config_files.items(), ids=config_files.keys() + ) def simulation( - self, request, atomic_data_fname, - generate_reference, tardis_ref_data): + self, request, atomic_data_fname, generate_reference, tardis_ref_data + ): name = request.param[0] config = Configuration.from_yaml(request.param[1]) - config['atom_data'] = atomic_data_fname + config["atom_data"] = atomic_data_fname simulation = Simulation.from_config(config) simulation.run() self._test_name = name @@ -45,33 +45,24 @@ def simulation( return simulation else: simulation.plasma.hdf_properties = [ - 'level_number_density', - ] - simulation.model.hdf_properties = [ - 't_radiative' - ] + "level_number_density", + ] + simulation.model.hdf_properties = ["t_radiative"] simulation.plasma.to_hdf( - tardis_ref_data, - self.name, - self._test_name) - simulation.model.to_hdf( - tardis_ref_data, - self.name, - self._test_name) - pytest.skip( - 'Reference data was generated during this run.') + tardis_ref_data, self.name, self._test_name + ) + simulation.model.to_hdf(tardis_ref_data, self.name, self._test_name) + pytest.skip("Reference data was generated during this run.") return simulation def test_levels(self, simulation, refdata): - new_levels = simulation.plasma.get_value('level_number_density') + new_levels = simulation.plasma.get_value("level_number_density") - old_levels = refdata('level_number_density') - pdt.assert_almost_equal( - new_levels, old_levels) + old_levels = refdata("level_number_density") + pdt.assert_almost_equal(new_levels, old_levels) def test_trads(self, simulation, refdata): - new_t_rads = pd.Series(simulation.model.t_rad.to('K').value) + new_t_rads = pd.Series(simulation.model.t_rad.to("K").value) - old_t_rads = refdata('t_radiative') - pdt.assert_almost_equal( - new_t_rads, old_t_rads) + old_t_rads = refdata("t_radiative") + pdt.assert_almost_equal(new_t_rads, old_t_rads) diff --git a/tardis/plasma/tests/test_tardis_model_density_config.py b/tardis/plasma/tests/test_tardis_model_density_config.py index 22d0a1130b5..6ac84e1db7b 100644 --- a/tardis/plasma/tests/test_tardis_model_density_config.py +++ b/tardis/plasma/tests/test_tardis_model_density_config.py @@ -5,12 +5,12 @@ from tardis.plasma.standard_plasmas import assemble_plasma from numpy.testing import assert_almost_equal -data_path = os.path.join('tardis', 'io', 'tests', 'data') +data_path = os.path.join("tardis", "io", "tests", "data") @pytest.fixture def tardis_model_density_config(): - filename = 'tardis_configv1_tardis_model_format.yml' + filename = "tardis_configv1_tardis_model_format.yml" return Configuration.from_yaml(os.path.join(data_path, filename)) @@ -21,12 +21,14 @@ def raw_model(tardis_model_density_config): @pytest.fixture() def raw_plasma(tardis_model_density_config, raw_model, kurucz_atomic_data): - return assemble_plasma(tardis_model_density_config, raw_model, kurucz_atomic_data) + return assemble_plasma( + tardis_model_density_config, raw_model, kurucz_atomic_data + ) def test_electron_densities(raw_plasma): - assert_almost_equal(raw_plasma.electron_densities[8], 2.72e+14) - assert_almost_equal(raw_plasma.electron_densities[3], 2.6e+14) + assert_almost_equal(raw_plasma.electron_densities[8], 2.72e14) + assert_almost_equal(raw_plasma.electron_densities[3], 2.6e14) def test_t_rad(raw_plasma): diff --git a/tardis/scripts/cmfgen2tardis.py b/tardis/scripts/cmfgen2tardis.py index cef4ee18809..69546c1f0a1 100644 --- a/tardis/scripts/cmfgen2tardis.py +++ b/tardis/scripts/cmfgen2tardis.py @@ -13,7 +13,7 @@ def get_atomic_number(element): index = -1 for atomic_no, row in atomic_dataset.atom_data.iterrows(): - if element in row['name']: + if element in row["name"]: index = atomic_no break return index @@ -39,71 +39,87 @@ def extract_file_block(f): def convert_format(file_path): quantities_row = [] - prop_list = ['Velocity', 'Density', 'Electron density', 'Temperature'] - with open(file_path, 'r') as f: + prop_list = ["Velocity", "Density", "Electron density", "Temperature"] + with open(file_path, "r") as f: for line in f: - items = line.replace('(', '').replace(')', '').split() + items = line.replace("(", "").replace(")", "").split() n = len(items) - if 'data points' in line: - abundances_df = pd.DataFrame(columns=np.arange(int(items[n - 1])), - index=pd.Index([], - name='element'), - dtype=np.float64) + if "data points" in line: + abundances_df = pd.DataFrame( + columns=np.arange(int(items[n - 1])), + index=pd.Index([], name="element"), + dtype=np.float64, + ) if any(prop in line for prop in prop_list): - quantities_row.append(items[n - 1].replace('gm', 'g')) - if 'Time' in line: + quantities_row.append(items[n - 1].replace("gm", "g")) + if "Time" in line: time_of_model = float(items[n - 1]) - if 'Velocity' in line: + if "Velocity" in line: velocity = extract_file_block(f) - if 'Density' in line: + if "Density" in line: density = extract_file_block(f) - if 'Electron density' in line: + if "Electron density" in line: electron_density = extract_file_block(f) - if 'Temperature' in line: + if "Temperature" in line: temperature = extract_file_block(f) - if 'mass fraction\n' in line: - element_string = items[0] - atomic_no = get_atomic_number(element_string.capitalize()) - element_symbol = atomic_dataset.atom_data.loc[atomic_no]['symbol'] + if "mass fraction\n" in line: + element_string = items[0] + atomic_no = get_atomic_number(element_string.capitalize()) + element_symbol = atomic_dataset.atom_data.loc[atomic_no][ + "symbol" + ] - #Its a Isotope - if n == 4: - element_symbol += items[1] + # Its a Isotope + if n == 4: + element_symbol += items[1] - abundances = extract_file_block(f) - abundances_df.loc[element_symbol] = abundances + abundances = extract_file_block(f) + abundances_df.loc[element_symbol] = abundances density_df = pd.DataFrame.from_records( - [velocity, temperature * 10**4, density, electron_density]).transpose() - density_df.columns = ['velocity', 'temperature', - 'densities', 'electron_densities'] + [velocity, temperature * 10 ** 4, density, electron_density] + ).transpose() + density_df.columns = [ + "velocity", + "temperature", + "densities", + "electron_densities", + ] quantities_row += abundances_df.shape[0] * [1] - return abundances_df.transpose(), density_df, time_of_model, quantities_row + return ( + abundances_df.transpose(), + density_df, + time_of_model, + quantities_row, + ) def parse_file(args): abundances_df, density_df, time_of_model, quantities_row = convert_format( - args.input_path) + args.input_path + ) filename = os.path.splitext(os.path.basename(args.input_path))[0] - save_fname = '.'.join((filename, 'csv')) + save_fname = ".".join((filename, "csv")) resultant_df = pd.concat([density_df, abundances_df], axis=1) resultant_df.columns = pd.MultiIndex.from_tuples( - zip(resultant_df.columns, quantities_row)) + zip(resultant_df.columns, quantities_row) + ) save_file_path = os.path.join(args.output_path, save_fname) - with open(save_file_path, 'w') as f: - f.write(" ".join(('t0:', str(time_of_model), 'day'))) + with open(save_file_path, "w") as f: + f.write(" ".join(("t0:", str(time_of_model), "day"))) f.write("\n") - resultant_df.to_csv(save_file_path, index=False, sep=' ', mode='a') + resultant_df.to_csv(save_file_path, index=False, sep=" ", mode="a") def main(): parser = argparse.ArgumentParser() - parser.add_argument('input_path', help='Path to a CMFGEN file') + parser.add_argument("input_path", help="Path to a CMFGEN file") parser.add_argument( - 'output_path', help='Path to store converted TARDIS format files') + "output_path", help="Path to store converted TARDIS format files" + ) args = parser.parse_args() parse_file(args) diff --git a/tardis/scripts/debug/run_numba_single.py b/tardis/scripts/debug/run_numba_single.py index 86d0b22f331..1ab234b282c 100644 --- a/tardis/scripts/debug/run_numba_single.py +++ b/tardis/scripts/debug/run_numba_single.py @@ -7,16 +7,16 @@ import yaml -SEED = eval(sys.argv[1].split('=')[1])[0] +SEED = eval(sys.argv[1].split("=")[1])[0] -yaml_file, params = 'tardis_example_single.yml', None +yaml_file, params = "tardis_example_single.yml", None with open(yaml_file) as f: params = yaml.safe_load(f) -params['montecarlo']['single_packet_seed'] = SEED +params["montecarlo"]["single_packet_seed"] = SEED -with open(yaml_file, 'w') as f: +with open(yaml_file, "w") as f: yaml.safe_dump(params, f) mdl = run_tardis(yaml_file) diff --git a/tardis/simulation/__init__.py b/tardis/simulation/__init__.py index 074cd702f18..8c88a5ab087 100644 --- a/tardis/simulation/__init__.py +++ b/tardis/simulation/__init__.py @@ -1 +1 @@ -from tardis.simulation.base import Simulation \ No newline at end of file +from tardis.simulation.base import Simulation diff --git a/tardis/simulation/base.py b/tardis/simulation/base.py index 2e31ccc4a8e..23ddfda50b2 100644 --- a/tardis/simulation/base.py +++ b/tardis/simulation/base.py @@ -25,14 +25,14 @@ class PlasmaStateStorerMixin(object): the electron density in each cell is provided. Additionally, the temperature at the inner boundary is saved. """ + def __init__(self, iterations, no_of_shells): - self.iterations_w = np.zeros( - (iterations, no_of_shells)) - self.iterations_t_rad = np.zeros( - (iterations, no_of_shells)) * u.K + self.iterations_w = np.zeros((iterations, no_of_shells)) + self.iterations_t_rad = np.zeros((iterations, no_of_shells)) * u.K self.iterations_electron_densities = np.zeros( - (iterations, no_of_shells)) + (iterations, no_of_shells) + ) self.iterations_t_inner = np.zeros(iterations) * u.K def store_plasma_state(self, i, w, t_rad, electron_densities, t_inner): @@ -54,8 +54,7 @@ def store_plasma_state(self, i, w, t_rad, electron_densities, t_inner): """ self.iterations_w[i, :] = w self.iterations_t_rad[i, :] = t_rad - self.iterations_electron_densities[i, :] = \ - electron_densities.values + self.iterations_electron_densities[i, :] = electron_densities.values self.iterations_t_inner[i] = t_inner def reshape_plasma_state_store(self, executed_iterations): @@ -67,13 +66,16 @@ def reshape_plasma_state_store(self, executed_iterations): executed_iterations : int iteration index, i.e. number of iterations executed minus one! """ - self.iterations_w = self.iterations_w[:executed_iterations+1, :] - self.iterations_t_rad = \ - self.iterations_t_rad[:executed_iterations+1, :] - self.iterations_electron_densities = \ - self.iterations_electron_densities[:executed_iterations+1, :] - self.iterations_t_inner = \ - self.iterations_t_inner[:executed_iterations+1] + self.iterations_w = self.iterations_w[: executed_iterations + 1, :] + self.iterations_t_rad = self.iterations_t_rad[ + : executed_iterations + 1, : + ] + self.iterations_electron_densities = self.iterations_electron_densities[ + : executed_iterations + 1, : + ] + self.iterations_t_inner = self.iterations_t_inner[ + : executed_iterations + 1 + ] class Simulation(PlasmaStateStorerMixin, HDFWriterMixin): @@ -99,16 +101,33 @@ class Simulation(PlasmaStateStorerMixin, HDFWriterMixin): .. note:: TARDIS must be built with OpenMP support in order for `nthreads` to have effect. """ - hdf_properties = ['model', 'plasma', 'runner', 'iterations_w', - 'iterations_t_rad', 'iterations_electron_densities', - 'iterations_t_inner'] - hdf_name = 'simulation' - def __init__(self, iterations, model, plasma, runner, - no_of_packets, no_of_virtual_packets, luminosity_nu_start, - luminosity_nu_end, last_no_of_packets, - luminosity_requested, convergence_strategy, - nthreads): + hdf_properties = [ + "model", + "plasma", + "runner", + "iterations_w", + "iterations_t_rad", + "iterations_electron_densities", + "iterations_t_inner", + ] + hdf_name = "simulation" + + def __init__( + self, + iterations, + model, + plasma, + runner, + no_of_packets, + no_of_virtual_packets, + luminosity_nu_start, + luminosity_nu_end, + last_no_of_packets, + luminosity_requested, + convergence_strategy, + nthreads, + ): super(Simulation, self).__init__(iterations, model.no_of_shells) @@ -125,32 +144,35 @@ def __init__(self, iterations, model, plasma, runner, self.luminosity_nu_end = luminosity_nu_end self.luminosity_requested = luminosity_requested self.nthreads = nthreads - if convergence_strategy.type in ('damped'): + if convergence_strategy.type in ("damped"): self.convergence_strategy = convergence_strategy self.converged = False self.consecutive_converges_count = 0 - elif convergence_strategy.type in ('custom'): + elif convergence_strategy.type in ("custom"): raise NotImplementedError( - 'Convergence strategy type is custom; ' - 'you need to implement your specific treatment!' + "Convergence strategy type is custom; " + "you need to implement your specific treatment!" ) else: raise ValueError( - 'Convergence strategy type is ' - 'not damped or custom ' - '- input is {0}'.format(convergence_strategy.type)) + "Convergence strategy type is " + "not damped or custom " + "- input is {0}".format(convergence_strategy.type) + ) self._callbacks = OrderedDict() self._cb_next_id = 0 - def estimate_t_inner(self, input_t_inner, luminosity_requested, - t_inner_update_exponent=-0.5): + def estimate_t_inner( + self, input_t_inner, luminosity_requested, t_inner_update_exponent=-0.5 + ): emitted_luminosity = self.runner.calculate_emitted_luminosity( - self.luminosity_nu_start, - self.luminosity_nu_end) + self.luminosity_nu_start, self.luminosity_nu_end + ) luminosity_ratios = ( - (emitted_luminosity / luminosity_requested).to(1).value) + (emitted_luminosity / luminosity_requested).to(1).value + ) return input_t_inner * luminosity_ratios ** t_inner_update_exponent @@ -160,42 +182,53 @@ def damped_converge(value, estimated_value, damping_factor): # as a method return value + damping_factor * (estimated_value - value) - def _get_convergence_status(self, t_rad, w, t_inner, estimated_t_rad, - estimated_w, estimated_t_inner): + def _get_convergence_status( + self, t_rad, w, t_inner, estimated_t_rad, estimated_w, estimated_t_inner + ): # FIXME: Move the convergence checking in its own class. no_of_shells = self.model.no_of_shells - convergence_t_rad = (abs(t_rad - estimated_t_rad) / - estimated_t_rad).value - convergence_w = (abs(w - estimated_w) / estimated_w) - convergence_t_inner = (abs(t_inner - estimated_t_inner) / - estimated_t_inner).value + convergence_t_rad = ( + abs(t_rad - estimated_t_rad) / estimated_t_rad + ).value + convergence_w = abs(w - estimated_w) / estimated_w + convergence_t_inner = ( + abs(t_inner - estimated_t_inner) / estimated_t_inner + ).value fraction_t_rad_converged = ( np.count_nonzero( - convergence_t_rad < self.convergence_strategy.t_rad.threshold) - / no_of_shells) + convergence_t_rad < self.convergence_strategy.t_rad.threshold + ) + / no_of_shells + ) t_rad_converged = ( - fraction_t_rad_converged > self.convergence_strategy.fraction) + fraction_t_rad_converged > self.convergence_strategy.fraction + ) fraction_w_converged = ( np.count_nonzero( - convergence_w < self.convergence_strategy.w.threshold) - / no_of_shells) + convergence_w < self.convergence_strategy.w.threshold + ) + / no_of_shells + ) - w_converged = ( - fraction_w_converged > self.convergence_strategy.fraction) + w_converged = fraction_w_converged > self.convergence_strategy.fraction t_inner_converged = ( - convergence_t_inner < self.convergence_strategy.t_inner.threshold) + convergence_t_inner < self.convergence_strategy.t_inner.threshold + ) if np.all([t_rad_converged, w_converged, t_inner_converged]): hold_iterations = self.convergence_strategy.hold_iterations self.consecutive_converges_count += 1 - logger.info("Iteration converged {0:d}/{1:d} consecutive " - "times.".format(self.consecutive_converges_count, - hold_iterations + 1)) + logger.info( + "Iteration converged {0:d}/{1:d} consecutive " + "times.".format( + self.consecutive_converges_count, hold_iterations + 1 + ) + ) # If an iteration has converged, require hold_iterations more # iterations to converge before we conclude that the Simulation # is converged. @@ -214,36 +247,56 @@ def advance_state(self): ------- converged : ~bool """ - estimated_t_rad, estimated_w = ( - self.runner.calculate_radiationfield_properties()) + ( + estimated_t_rad, + estimated_w, + ) = self.runner.calculate_radiationfield_properties() estimated_t_inner = self.estimate_t_inner( - self.model.t_inner, self.luminosity_requested, - t_inner_update_exponent=self.convergence_strategy.t_inner_update_exponent) - - converged = self._get_convergence_status(self.model.t_rad, - self.model.w, - self.model.t_inner, - estimated_t_rad, - estimated_w, - estimated_t_inner) + self.model.t_inner, + self.luminosity_requested, + t_inner_update_exponent=self.convergence_strategy.t_inner_update_exponent, + ) + + converged = self._get_convergence_status( + self.model.t_rad, + self.model.w, + self.model.t_inner, + estimated_t_rad, + estimated_w, + estimated_t_inner, + ) # calculate_next_plasma_state equivalent # FIXME: Should convergence strategy have its own class? next_t_rad = self.damped_converge( - self.model.t_rad, estimated_t_rad, - self.convergence_strategy.t_rad.damping_constant) + self.model.t_rad, + estimated_t_rad, + self.convergence_strategy.t_rad.damping_constant, + ) next_w = self.damped_converge( - self.model.w, estimated_w, self.convergence_strategy.w.damping_constant) - if (self.iterations_executed + 1) % self.convergence_strategy.lock_t_inner_cycles == 0: + self.model.w, + estimated_w, + self.convergence_strategy.w.damping_constant, + ) + if ( + self.iterations_executed + 1 + ) % self.convergence_strategy.lock_t_inner_cycles == 0: next_t_inner = self.damped_converge( - self.model.t_inner, estimated_t_inner, - self.convergence_strategy.t_inner.damping_constant) + self.model.t_inner, + estimated_t_inner, + self.convergence_strategy.t_inner.damping_constant, + ) else: next_t_inner = self.model.t_inner - self.log_plasma_state(self.model.t_rad, self.model.w, - self.model.t_inner, next_t_rad, next_w, - next_t_inner) + self.log_plasma_state( + self.model.t_rad, + self.model.w, + self.model.t_inner, + next_t_rad, + next_w, + next_t_inner, + ) self.model.t_rad = next_t_rad self.model.w = next_w self.model.t_inner = next_t_inner @@ -251,46 +304,60 @@ def advance_state(self): # model.calculate_j_blues() equivalent # model.update_plasmas() equivalent # Bad test to see if this is a nlte run - if 'nlte_data' in self.plasma.outputs_dict: + if "nlte_data" in self.plasma.outputs_dict: self.plasma.store_previous_properties() update_properties = dict(t_rad=self.model.t_rad, w=self.model.w) # A check to see if the plasma is set with JBluesDetailed, in which # case it needs some extra kwargs. - if 'j_blue_estimator' in self.plasma.outputs_dict: - update_properties.update(t_inner=next_t_inner, - j_blue_estimator=self.runner.j_blue_estimator) + if "j_blue_estimator" in self.plasma.outputs_dict: + update_properties.update( + t_inner=next_t_inner, + j_blue_estimator=self.runner.j_blue_estimator, + ) self.plasma.update(**update_properties) return converged def iterate(self, no_of_packets, no_of_virtual_packets=0, last_run=False): - logger.info('Starting iteration {0:d}/{1:d}'.format( - self.iterations_executed + 1, self.iterations)) - self.runner.run(self.model, self.plasma, no_of_packets, - no_of_virtual_packets=no_of_virtual_packets, - nthreads=self.nthreads, last_run=last_run, - iteration=self.iterations_executed) + logger.info( + "Starting iteration {0:d}/{1:d}".format( + self.iterations_executed + 1, self.iterations + ) + ) + self.runner.run( + self.model, + self.plasma, + no_of_packets, + no_of_virtual_packets=no_of_virtual_packets, + nthreads=self.nthreads, + last_run=last_run, + iteration=self.iterations_executed, + ) output_energy = self.runner.output_energy if np.sum(output_energy < 0) == len(output_energy): logger.critical("No r-packet escaped through the outer boundary.") emitted_luminosity = self.runner.calculate_emitted_luminosity( - self.luminosity_nu_start, self.luminosity_nu_end) + self.luminosity_nu_start, self.luminosity_nu_end + ) reabsorbed_luminosity = self.runner.calculate_reabsorbed_luminosity( - self.luminosity_nu_start, self.luminosity_nu_end) - self.log_run_results(emitted_luminosity, - reabsorbed_luminosity) + self.luminosity_nu_start, self.luminosity_nu_end + ) + self.log_run_results(emitted_luminosity, reabsorbed_luminosity) self.iterations_executed += 1 def run(self): start_time = time.time() - while self.iterations_executed < self.iterations-1: - self.store_plasma_state(self.iterations_executed, self.model.w, - self.model.t_rad, - self.plasma.electron_densities, - self.model.t_inner) + while self.iterations_executed < self.iterations - 1: + self.store_plasma_state( + self.iterations_executed, + self.model.w, + self.model.t_rad, + self.plasma.electron_densities, + self.model.t_inner, + ) self.iterate(self.no_of_packets) self.converged = self.advance_state() self._call_back() @@ -298,22 +365,37 @@ def run(self): if self.convergence_strategy.stop_if_converged: break # Last iteration - self.store_plasma_state(self.iterations_executed, self.model.w, - self.model.t_rad, - self.plasma.electron_densities, - self.model.t_inner) - self.iterate(self.last_no_of_packets, self.no_of_virtual_packets, last_run=True) + self.store_plasma_state( + self.iterations_executed, + self.model.w, + self.model.t_rad, + self.plasma.electron_densities, + self.model.t_inner, + ) + self.iterate( + self.last_no_of_packets, self.no_of_virtual_packets, last_run=True + ) self.reshape_plasma_state_store(self.iterations_executed) - logger.info("Simulation finished in {0:d} iterations " - "and took {1:.2f} s".format( - self.iterations_executed, time.time() - start_time)) + logger.info( + "Simulation finished in {0:d} iterations " + "and took {1:.2f} s".format( + self.iterations_executed, time.time() - start_time + ) + ) self._call_back() - - def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, - next_t_inner, log_sampling=5): + def log_plasma_state( + self, + t_rad, + w, + t_inner, + next_t_rad, + next_w, + next_t_inner, + log_sampling=5, + ): """ Logging the change of the plasma state @@ -335,31 +417,40 @@ def log_plasma_state(self, t_rad, w, t_inner, next_t_rad, next_w, """ - plasma_state_log = pd.DataFrame(index=np.arange(len(t_rad)), - columns=['t_rad', 'next_t_rad', - 'w', 'next_w']) - plasma_state_log['t_rad'] = t_rad - plasma_state_log['next_t_rad'] = next_t_rad - plasma_state_log['w'] = w - plasma_state_log['next_w'] = next_w + plasma_state_log = pd.DataFrame( + index=np.arange(len(t_rad)), + columns=["t_rad", "next_t_rad", "w", "next_w"], + ) + plasma_state_log["t_rad"] = t_rad + plasma_state_log["next_t_rad"] = next_t_rad + plasma_state_log["w"] = w + plasma_state_log["next_w"] = next_w - plasma_state_log.index.name = 'Shell' + plasma_state_log.index.name = "Shell" plasma_state_log = str(plasma_state_log[::log_sampling]) - plasma_state_log = ''.join(['\t%s\n' % item for item in - plasma_state_log.split('\n')]) + plasma_state_log = "".join( + ["\t%s\n" % item for item in plasma_state_log.split("\n")] + ) - logger.info('Plasma stratification:\n%s\n', plasma_state_log) - logger.info('t_inner {0:.3f} -- next t_inner {1:.3f}'.format( - t_inner, next_t_inner)) + logger.info("Plasma stratification:\n%s\n", plasma_state_log) + logger.info( + "t_inner {0:.3f} -- next t_inner {1:.3f}".format( + t_inner, next_t_inner + ) + ) def log_run_results(self, emitted_luminosity, absorbed_luminosity): - logger.info("Luminosity emitted = {0:.5e} " - "Luminosity absorbed = {1:.5e} " - "Luminosity requested = {2:.5e}".format( - emitted_luminosity, absorbed_luminosity, - self.luminosity_requested)) + logger.info( + "Luminosity emitted = {0:.5e} " + "Luminosity absorbed = {1:.5e} " + "Luminosity requested = {2:.5e}".format( + emitted_luminosity, + absorbed_luminosity, + self.luminosity_requested, + ) + ) def _call_back(self): for cb, args in self._callbacks.values(): @@ -431,54 +522,59 @@ def from_config(cls, config, packet_source=None, **kwargs): """ # Allow overriding some config structures. This is useful in some # unit tests, and could be extended in all the from_config classmethods. - if 'model' in kwargs: - model = kwargs['model'] + if "model" in kwargs: + model = kwargs["model"] else: - if hasattr(config, 'csvy_model'): + if hasattr(config, "csvy_model"): model = Radial1DModel.from_csvy(config) else: model = Radial1DModel.from_config(config) - if 'plasma' in kwargs: - plasma = kwargs['plasma'] + if "plasma" in kwargs: + plasma = kwargs["plasma"] else: - plasma = assemble_plasma(config, model, - atom_data=kwargs.get('atom_data', None)) - if 'runner' in kwargs: + plasma = assemble_plasma( + config, model, atom_data=kwargs.get("atom_data", None) + ) + if "runner" in kwargs: if packet_source is not None: raise ConfigurationError( - 'Cannot specify packet_source and runner at the same time.' + "Cannot specify packet_source and runner at the same time." ) - runner = kwargs['runner'] + runner = kwargs["runner"] else: - runner = MontecarloRunner.from_config(config, - packet_source=packet_source) + runner = MontecarloRunner.from_config( + config, packet_source=packet_source + ) luminosity_nu_start = config.supernova.luminosity_wavelength_end.to( - u.Hz, u.spectral()) + u.Hz, u.spectral() + ) if u.isclose( - config.supernova.luminosity_wavelength_start, 0 * u.angstrom): + config.supernova.luminosity_wavelength_start, 0 * u.angstrom + ): luminosity_nu_end = np.inf * u.Hz else: luminosity_nu_end = ( - const.c / - config.supernova.luminosity_wavelength_start).to(u.Hz) + const.c / config.supernova.luminosity_wavelength_start + ).to(u.Hz) last_no_of_packets = config.montecarlo.last_no_of_packets if last_no_of_packets is None or last_no_of_packets < 0: - last_no_of_packets = config.montecarlo.no_of_packets + last_no_of_packets = config.montecarlo.no_of_packets last_no_of_packets = int(last_no_of_packets) - return cls(iterations=config.montecarlo.iterations, - model=model, - plasma=plasma, - runner=runner, - no_of_packets=int(config.montecarlo.no_of_packets), - no_of_virtual_packets=int( - config.montecarlo.no_of_virtual_packets), - luminosity_nu_start=luminosity_nu_start, - luminosity_nu_end=luminosity_nu_end, - last_no_of_packets=last_no_of_packets, - luminosity_requested=config.supernova.luminosity_requested.cgs, - convergence_strategy=config.montecarlo.convergence_strategy, - nthreads=config.montecarlo.nthreads) + return cls( + iterations=config.montecarlo.iterations, + model=model, + plasma=plasma, + runner=runner, + no_of_packets=int(config.montecarlo.no_of_packets), + no_of_virtual_packets=int(config.montecarlo.no_of_virtual_packets), + luminosity_nu_start=luminosity_nu_start, + luminosity_nu_end=luminosity_nu_end, + last_no_of_packets=last_no_of_packets, + luminosity_requested=config.supernova.luminosity_requested.cgs, + convergence_strategy=config.montecarlo.convergence_strategy, + nthreads=config.montecarlo.nthreads, + ) diff --git a/tardis/simulation/setup_package.py b/tardis/simulation/setup_package.py index fbaa4b0ee0e..10b3b9f8880 100644 --- a/tardis/simulation/setup_package.py +++ b/tardis/simulation/setup_package.py @@ -3,7 +3,6 @@ from astropy_helpers.distutils_helpers import get_distutils_option import numpy as np -def get_package_data(): - return {'tardis.simulation.tests':['data/*.h5']} - +def get_package_data(): + return {"tardis.simulation.tests": ["data/*.h5"]} diff --git a/tardis/simulation/tests/test_simulation.py b/tardis/simulation/tests/test_simulation.py index e9eff0b0574..2f963287285 100644 --- a/tardis/simulation/tests/test_simulation.py +++ b/tardis/simulation/tests/test_simulation.py @@ -10,24 +10,25 @@ import astropy.units as u -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def refdata(tardis_ref_data): def get_ref_data(key): - return tardis_ref_data[os.path.join( - 'test_simulation', key)] + return tardis_ref_data[os.path.join("test_simulation", key)] + return get_ref_data -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def config(): return Configuration.from_yaml( - 'tardis/io/tests/data/tardis_configv1_verysimple.yml') + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def simulation_one_loop( - atomic_data_fname, config, - tardis_ref_data, generate_reference): + atomic_data_fname, config, tardis_ref_data, generate_reference +): config.atom_data = atomic_data_fname config.montecarlo.iterations = 2 config.montecarlo.no_of_packets = int(4e4) @@ -40,77 +41,64 @@ def simulation_one_loop( return simulation else: simulation.hdf_properties = [ - 'iterations_w', - 'iterations_t_rad', - 'iterations_electron_densities', - 'iterations_t_inner', + "iterations_w", + "iterations_t_rad", + "iterations_electron_densities", + "iterations_t_inner", ] - simulation.model.hdf_properties = [ - 't_radiative', - 'dilution_factor' - ] + simulation.model.hdf_properties = ["t_radiative", "dilution_factor"] simulation.runner.hdf_properties = [ - 'j_estimator', - 'nu_bar_estimator', - 'output_nu', - 'output_energy' - ] - simulation.to_hdf( - tardis_ref_data, - '', - 'test_simulation' - ) - simulation.model.to_hdf( - tardis_ref_data, - '', - 'test_simulation') - simulation.runner.to_hdf( - tardis_ref_data, - '', - 'test_simulation') - pytest.skip( - 'Reference data was generated during this run.') - - -@pytest.mark.parametrize('name', [ - 'nu_bar_estimator', 'j_estimator', 't_radiative', 'dilution_factor', - 'output_nu', 'output_energy' - ]) -def test_plasma_estimates( - simulation_one_loop, refdata, name): + "j_estimator", + "nu_bar_estimator", + "output_nu", + "output_energy", + ] + simulation.to_hdf(tardis_ref_data, "", "test_simulation") + simulation.model.to_hdf(tardis_ref_data, "", "test_simulation") + simulation.runner.to_hdf(tardis_ref_data, "", "test_simulation") + pytest.skip("Reference data was generated during this run.") + + +@pytest.mark.parametrize( + "name", + [ + "nu_bar_estimator", + "j_estimator", + "t_radiative", + "dilution_factor", + "output_nu", + "output_energy", + ], +) +def test_plasma_estimates(simulation_one_loop, refdata, name): try: - actual = getattr( - simulation_one_loop.runner, name) + actual = getattr(simulation_one_loop.runner, name) except AttributeError: - actual = getattr( - simulation_one_loop.model, name) + actual = getattr(simulation_one_loop.model, name) actual = pd.Series(actual) - pdt.assert_almost_equal( - actual, - refdata(name) - ) + pdt.assert_almost_equal(actual, refdata(name)) -@pytest.mark.parametrize('name', [ - 'iterations_w', 'iterations_t_rad', - 'iterations_electron_densities', 'iterations_t_inner' - ]) -def test_plasma_state_iterations( - simulation_one_loop, refdata, name): - actual = getattr( - simulation_one_loop, name) +@pytest.mark.parametrize( + "name", + [ + "iterations_w", + "iterations_t_rad", + "iterations_electron_densities", + "iterations_t_inner", + ], +) +def test_plasma_state_iterations(simulation_one_loop, refdata, name): + actual = getattr(simulation_one_loop, name) try: actual = pd.Series(actual) except Exception: actual = pd.DataFrame(actual) - pdt.assert_almost_equal( - actual, - refdata(name) - ) + pdt.assert_almost_equal(actual, refdata(name)) @pytest.fixture(scope="module") @@ -121,8 +109,9 @@ def simulation_without_loop(atomic_data_fname, config): return Simulation.from_config(config) -def test_plasma_state_storer_store(atomic_data_fname, config, - simulation_without_loop): +def test_plasma_state_storer_store( + atomic_data_fname, config, simulation_without_loop +): simulation = simulation_without_loop @@ -131,18 +120,21 @@ def test_plasma_state_storer_store(atomic_data_fname, config, electron_densities_test = pd.Series(np.linspace(1e7, 1e6, 20)) t_inner_test = 12500 * u.K - simulation.store_plasma_state(1, w_test, t_rad_test, - electron_densities_test, t_inner_test) + simulation.store_plasma_state( + 1, w_test, t_rad_test, electron_densities_test, t_inner_test + ) np.testing.assert_allclose(simulation.iterations_w[1, :], w_test) np.testing.assert_allclose(simulation.iterations_t_rad[1, :], t_rad_test) - np.testing.assert_allclose(simulation.iterations_electron_densities[1, :], - electron_densities_test) + np.testing.assert_allclose( + simulation.iterations_electron_densities[1, :], electron_densities_test + ) np.testing.assert_allclose(simulation.iterations_t_inner[1], t_inner_test) -def test_plasma_state_storer_reshape(atomic_data_fname, config, - simulation_without_loop): +def test_plasma_state_storer_reshape( + atomic_data_fname, config, simulation_without_loop +): simulation = simulation_without_loop simulation.reshape_plasma_state_store(0) diff --git a/tardis/stats/base.py b/tardis/stats/base.py index a0628a3f70b..e0ea57f684e 100644 --- a/tardis/stats/base.py +++ b/tardis/stats/base.py @@ -1,11 +1,14 @@ import numpy as np + def get_trivial_poisson_uncertainty(model): """ """ emitted_nu = model.montecarlo_nu[model.montecarlo_luminosity >= 0] - emitted_luminosity = model.montecarlo_luminosity[model.montecarlo_luminosity >= 0] + emitted_luminosity = model.montecarlo_luminosity[ + model.montecarlo_luminosity >= 0 + ] freq_bins = model.tardis_config.spectrum.frequency.value bin_counts = np.histogram(emitted_nu, bins=freq_bins)[0] @@ -13,4 +16,3 @@ def get_trivial_poisson_uncertainty(model): uncertainty = np.sqrt(bin_counts) * np.mean(emitted_luminosity) return uncertainty / (freq_bins[1] - freq_bins[0]) - diff --git a/tardis/tests/fixtures/atom_data.py b/tardis/tests/fixtures/atom_data.py index 5851300f504..c0b88f199c3 100644 --- a/tardis/tests/fixtures/atom_data.py +++ b/tardis/tests/fixtures/atom_data.py @@ -5,15 +5,19 @@ from tardis.io.atom_data.base import AtomData -DEFAULT_ATOM_DATA_UUID = '864f1753714343c41f99cb065710cace' +DEFAULT_ATOM_DATA_UUID = "864f1753714343c41f99cb065710cace" + @pytest.fixture(scope="session") def atomic_data_fname(tardis_ref_path): atomic_data_fname = os.path.join( - tardis_ref_path, 'atom_data', 'kurucz_cd23_chianti_H_He.h5') + tardis_ref_path, "atom_data", "kurucz_cd23_chianti_H_He.h5" + ) - atom_data_missing_str = ("{0} atomic datafiles " - "does not seem to exist".format(atomic_data_fname)) + atom_data_missing_str = ( + "{0} atomic datafiles " + "does not seem to exist".format(atomic_data_fname) + ) if not os.path.exists(atomic_data_fname): pytest.exit(atom_data_missing_str) @@ -27,8 +31,10 @@ def atomic_dataset(atomic_data_fname): if atomic_data.md5 != DEFAULT_ATOM_DATA_UUID: pytest.skip( - 'Need default Kurucz atomic dataset (md5="{}"'.format( - DEFAULT_ATOM_DATA_UUID)) + 'Need default Kurucz atomic dataset (md5="{}"'.format( + DEFAULT_ATOM_DATA_UUID + ) + ) else: return atomic_data diff --git a/tardis/tests/integration_tests/conftest.py b/tardis/tests/integration_tests/conftest.py index 29ee6e51a2c..b18b7ee251d 100644 --- a/tardis/tests/integration_tests/conftest.py +++ b/tardis/tests/integration_tests/conftest.py @@ -6,7 +6,10 @@ from tardis import __githash__ as tardis_githash from tardis.tests.integration_tests.report import DokuReport -from tardis.tests.integration_tests.plot_helpers import LocalPlotSaver, RemotePlotSaver +from tardis.tests.integration_tests.plot_helpers import ( + LocalPlotSaver, + RemotePlotSaver, +) def pytest_configure(config): @@ -16,33 +19,44 @@ def pytest_configure(config): os.path.expanduser(integration_tests_configpath) ) config.integration_tests_config = yaml.load( - open(integration_tests_configpath), Loader=yaml.CLoader) + open(integration_tests_configpath), Loader=yaml.CLoader + ) if not config.getoption("--generate-reference"): # Used by DokuReport class to show build environment details in report. config._environment = [] # prevent opening dokupath on slave nodes (xdist) - if not hasattr(config, 'slaveinput'): + if not hasattr(config, "slaveinput"): config.dokureport = DokuReport( - config.integration_tests_config['report']) + config.integration_tests_config["report"] + ) config.pluginmanager.register(config.dokureport) def pytest_unconfigure(config): # Unregister only if it was registered in pytest_configure - if (config.getvalue("integration-tests") and not - config.getoption("--generate-reference")): + if config.getvalue("integration-tests") and not config.getoption( + "--generate-reference" + ): config.pluginmanager.unregister(config.dokureport) def pytest_terminal_summary(terminalreporter): - if (terminalreporter.config.getoption("--generate-reference") and - terminalreporter.config.getvalue("integration-tests")): + if terminalreporter.config.getoption( + "--generate-reference" + ) and terminalreporter.config.getvalue("integration-tests"): # TODO: Add a check whether generation was successful or not. - terminalreporter.write_sep("-", "Generated reference data: {0}".format(os.path.join( - terminalreporter.config.integration_tests_config['reference'], - tardis_githash[:7] - ))) + terminalreporter.write_sep( + "-", + "Generated reference data: {0}".format( + os.path.join( + terminalreporter.config.integration_tests_config[ + "reference" + ], + tardis_githash[:7], + ) + ), + ) @pytest.mark.hookwrapper @@ -60,50 +74,64 @@ def pytest_runtest_makereport(item, call): @pytest.fixture(scope="function") def plot_object(request): integration_tests_config = request.config.integration_tests_config - report_save_mode = integration_tests_config['report']['save_mode'] + report_save_mode = integration_tests_config["report"]["save_mode"] if report_save_mode == "remote": return RemotePlotSaver(request, request.config.dokureport.dokuwiki_url) else: - return LocalPlotSaver(request, os.path.join( - request.config.dokureport.report_dirpath, "assets") + return LocalPlotSaver( + request, + os.path.join(request.config.dokureport.report_dirpath, "assets"), ) -@pytest.fixture(scope="class", params=[ - path for path in glob.glob(os.path.join( - os.path.dirname(os.path.realpath(__file__)), "*")) if os.path.isdir(path) -]) +@pytest.fixture( + scope="class", + params=[ + path + for path in glob.glob( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "*") + ) + if os.path.isdir(path) + ], +) def data_path(request): integration_tests_config = request.config.integration_tests_config hdf_filename = "{0}.h5".format(os.path.basename(request.param)) - if (request.config.getoption("--generate-reference") ): - ref_path = os.path.join(os.path.expandvars(os.path.expanduser( - integration_tests_config['reference'])), tardis_githash[:7] + if request.config.getoption("--generate-reference"): + ref_path = os.path.join( + os.path.expandvars( + os.path.expanduser(integration_tests_config["reference"]) + ), + tardis_githash[:7], ) else: - ref_path = os.path.join(os.path.expandvars( - os.path.expanduser(integration_tests_config['reference'])), hdf_filename + ref_path = os.path.join( + os.path.expandvars( + os.path.expanduser(integration_tests_config["reference"]) + ), + hdf_filename, ) path = { - 'config_dirpath': request.param, - 'reference_path': ref_path, - 'setup_name': hdf_filename[:-3], + "config_dirpath": request.param, + "reference_path": ref_path, + "setup_name": hdf_filename[:-3], # Temporary hack for providing atom data per individual setup. # This url has all the atom data files hosted, for downloading. -# 'atom_data_url': integration_tests_config['atom_data']['atom_data_url'] + # 'atom_data_url': integration_tests_config['atom_data']['atom_data_url'] } # For providing atom data per individual setup. Atom data can be fetched # from a local directory or a remote url. - path['atom_data_path'] = os.path.expandvars(os.path.expanduser( - integration_tests_config['atom_data_path'] - )) - - if (request.config.getoption("--generate-reference") and not - os.path.exists(path['reference_path'])): - os.makedirs(path['reference_path']) + path["atom_data_path"] = os.path.expandvars( + os.path.expanduser(integration_tests_config["atom_data_path"]) + ) + + if request.config.getoption("--generate-reference") and not os.path.exists( + path["reference_path"] + ): + os.makedirs(path["reference_path"]) return path @@ -122,10 +150,12 @@ def reference(request, data_path): return else: try: - reference = pd.HDFStore(data_path['reference_path'], 'r') + reference = pd.HDFStore(data_path["reference_path"], "r") except IOError: - raise IOError('Reference file {0} does not exist and is needed' - ' for the tests'.format(data_path['reference_path'])) + raise IOError( + "Reference file {0} does not exist and is needed" + " for the tests".format(data_path["reference_path"]) + ) else: return reference diff --git a/tardis/tests/integration_tests/plot_helpers.py b/tardis/tests/integration_tests/plot_helpers.py index 69120bba449..d92f066c584 100644 --- a/tardis/tests/integration_tests/plot_helpers.py +++ b/tardis/tests/integration_tests/plot_helpers.py @@ -46,11 +46,21 @@ def save(self, plot, filepath, report): axes = plot.axes[0] if report.passed: - axes.text(0.8, 0.8, 'passed', transform=axes.transAxes, - bbox={'facecolor': 'green', 'alpha': 0.5, 'pad': 10}) + axes.text( + 0.8, + 0.8, + "passed", + transform=axes.transAxes, + bbox={"facecolor": "green", "alpha": 0.5, "pad": 10}, + ) else: - axes.text(0.8, 0.8, 'failed', transform=axes.transAxes, - bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 10}) + axes.text( + 0.8, + 0.8, + "failed", + transform=axes.transAxes, + bbox={"facecolor": "red", "alpha": 0.5, "pad": 10}, + ) plot.savefig(filepath) @@ -76,7 +86,9 @@ def get_extras(self): class RemotePlotSaver(BasePlotSaver): def __init__(self, request, dokuwiki_url): - super(RemotePlotSaver, self).__init__(request, dokuwiki_url=dokuwiki_url) + super(RemotePlotSaver, self).__init__( + request, dokuwiki_url=dokuwiki_url + ) def upload(self, report): """Upload content of ``self._plots`` to ``self.dokuwiki_url``. @@ -93,14 +105,16 @@ def upload(self, report): self.request.config.dokureport.doku_conn.medias.add( "reports:{0}:{1}.png".format(tardis_githash[:7], name), - plot_file.name + plot_file.name, ) - self.plot_html.append(extras.html( - thumbnail_html_remote.format( - dokuwiki_url=self.dokuwiki_url, - githash=tardis_githash[:7], - name=name) + self.plot_html.append( + extras.html( + thumbnail_html_remote.format( + dokuwiki_url=self.dokuwiki_url, + githash=tardis_githash[:7], + name=name, + ) ) ) plot_file.close() @@ -117,7 +131,9 @@ def upload(self, report): class LocalPlotSaver(BasePlotSaver): def __init__(self, request, assets_dirpath): - super(LocalPlotSaver, self).__init__(request, assets_dirpath=assets_dirpath) + super(LocalPlotSaver, self).__init__( + request, assets_dirpath=assets_dirpath + ) def upload(self, report): """Save content of ``self._plots`` to ``self.assets_dirpath``. @@ -129,10 +145,12 @@ def upload(self, report): """ for plot, name in self._plots: - self.save(plot, os.path.join( - self.assets_dirpath, "{0}.png".format(name)), report + self.save( + plot, + os.path.join(self.assets_dirpath, "{0}.png".format(name)), + report, ) - self.plot_html.append(extras.html( - thumbnail_html_local.format(name=name)) + self.plot_html.append( + extras.html(thumbnail_html_local.format(name=name)) ) diff --git a/tardis/tests/integration_tests/report.py b/tardis/tests/integration_tests/report.py index 2edb443774c..fc5acc51a04 100644 --- a/tardis/tests/integration_tests/report.py +++ b/tardis/tests/integration_tests/report.py @@ -46,7 +46,6 @@ class DokuReport(HTMLReport): - def __init__(self, report_config): """ Initialization of a DokuReport object and registration as a plugin @@ -54,10 +53,11 @@ def __init__(self, report_config): password of dokuwiki is passed through `dokuwiki_details`. """ # This will be either "remote" or "local". - self.save_mode = report_config['save_mode'] + self.save_mode = report_config["save_mode"] if self.save_mode == "remote": import dokuwiki + # Base class accepts a file path to save the report, but we pass an # empty string as it is redundant for this use case. super(DokuReport, self).__init__( @@ -65,33 +65,37 @@ def __init__(self, report_config): ) # Upload the report on a dokuwiki instance. - dokuwiki_details = report_config['dokuwiki'] + dokuwiki_details = report_config["dokuwiki"] try: self.doku_conn = dokuwiki.DokuWiki( - url=dokuwiki_details['url'], - user=dokuwiki_details['username'], - password=dokuwiki_details['password']) + url=dokuwiki_details["url"], + user=dokuwiki_details["username"], + password=dokuwiki_details["password"], + ) except (TypeError, gaierror, dokuwiki.DokuWikiError) as e: raise e self.doku_conn = None self.dokuwiki_url = "" else: - self.dokuwiki_url = dokuwiki_details['url'] + self.dokuwiki_url = dokuwiki_details["url"] else: # Save the html report file locally. self.report_dirpath = os.path.join( - os.path.expandvars(os.path.expanduser(report_config['reportpath'])), - tardis_githash[:7] + os.path.expandvars( + os.path.expanduser(report_config["reportpath"]) + ), + tardis_githash[:7], ) if os.path.exists(self.report_dirpath): shutil.rmtree(self.report_dirpath) os.makedirs(self.report_dirpath) - os.makedirs(os.path.join(self.report_dirpath, 'assets')) + os.makedirs(os.path.join(self.report_dirpath, "assets")) super(DokuReport, self).__init__( logfile=os.path.join(self.report_dirpath, "report.html"), - self_contained=False, has_rerun=False + self_contained=False, + has_rerun=False, ) self.suite_start_time = time.time() @@ -114,10 +118,10 @@ def _generate_report(self, session): # Quick hack for preventing log to be placed in narrow left out space report_content = report_content.replace( - u'class="log"', u'class="log" style="clear: both"' + 'class="log"', 'class="log" style="clear: both"' ) # It was displayed raw on wiki pages, but not needed. - report_content = report_content.replace(u'', u'') + report_content = report_content.replace("", "") return report_content def _save_report(self, report_content): @@ -128,16 +132,19 @@ def _save_report(self, report_content): if self.save_mode == "remote": # Upload the report content to wiki try: - self.doku_conn.pages.set("reports:{0}".format( - tardis_githash[:7]), report_content) + self.doku_conn.pages.set( + "reports:{0}".format(tardis_githash[:7]), report_content + ) except (gaierror, TypeError): pass else: # Save the file locally at "self.logfile" path - with open(self.logfile, 'w') as f: + with open(self.logfile, "w") as f: f.write(report_content) - with open(os.path.join(self.report_dirpath, 'assets', 'style.css'), 'w') as f: + with open( + os.path.join(self.report_dirpath, "assets", "style.css"), "w" + ) as f: f.write(self.style_css) def _wiki_overview_entry(self): @@ -150,7 +157,9 @@ def _wiki_overview_entry(self): else: status = "Errored" - suite_start_datetime = datetime.datetime.utcfromtimestamp(self.suite_start_time) + suite_start_datetime = datetime.datetime.utcfromtimestamp( + self.suite_start_time + ) # Fetch commit message from github. gh_request = requests.get( @@ -160,7 +169,7 @@ def _wiki_overview_entry(self): ) gh_commit_data = json.loads(gh_request.content) # Pick only first line of commit message - gh_commit_message = gh_commit_data['message'].split('\n')[0] + gh_commit_message = gh_commit_data["message"].split("\n")[0] # Truncate long commit messages if len(gh_commit_message) > 60: @@ -173,13 +182,15 @@ def _wiki_overview_entry(self): tardis_githash, gh_commit_message ) # Append start time - row += "{0} | ".format(suite_start_datetime.strftime('%d %b %H:%M:%S')) + row += "{0} | ".format( + suite_start_datetime.strftime("%d %b %H:%M:%S") + ) # Append time elapsed row += "{0:.2f} sec | ".format(self.suite_time_delta) # Append status row += "{0} |\n".format(status) try: - self.doku_conn.pages.append('/', row) + self.doku_conn.pages.append("/", row) except (gaierror, TypeError): pass @@ -204,7 +215,8 @@ def pytest_terminal_summary(self, terminalreporter): if self.save_mode == "remote": try: uploaded_report = self.doku_conn.pages.get( - "reports:{0}".format(tardis_githash[:7])) + "reports:{0}".format(tardis_githash[:7]) + ) except (gaierror, TypeError): uploaded_report = "" @@ -213,12 +225,17 @@ def pytest_terminal_summary(self, terminalreporter): "-", "Successfully uploaded report to Dokuwiki" ) terminalreporter.write_sep( - "-", "URL: {0}doku.php?id=reports:{1}".format( - self.dokuwiki_url, tardis_githash[:7]) + "-", + "URL: {0}doku.php?id=reports:{1}".format( + self.dokuwiki_url, tardis_githash[:7] + ), ) else: terminalreporter.write_sep( - "-", "Connection not established, upload failed.") + "-", "Connection not established, upload failed." + ) else: if os.path.exists(self.logfile): - super(DokuReport, self).pytest_terminal_summary(terminalreporter) + super(DokuReport, self).pytest_terminal_summary( + terminalreporter + ) diff --git a/tardis/tests/integration_tests/runner.py b/tardis/tests/integration_tests/runner.py index ea9392f10fc..51d7f820385 100644 --- a/tardis/tests/integration_tests/runner.py +++ b/tardis/tests/integration_tests/runner.py @@ -13,29 +13,44 @@ logger = logging.getLogger(__name__) parser = argparse.ArgumentParser(description="Run slow integration tests") -parser.add_argument("--integration-tests", dest="yaml_filepath", - help="Path to YAML config file for integration tests.") -parser.add_argument("--tardis-refdata", dest="tardis_refdata", - help="Path to Tardis Reference Data.") -parser.add_argument("--less-packets", action="store_true", default=False, - help="Run integration tests with less packets.") +parser.add_argument( + "--integration-tests", + dest="yaml_filepath", + help="Path to YAML config file for integration tests.", +) +parser.add_argument( + "--tardis-refdata", + dest="tardis_refdata", + help="Path to Tardis Reference Data.", +) +parser.add_argument( + "--less-packets", + action="store_true", + default=False, + help="Run integration tests with less packets.", +) def run_tests(): args = parser.parse_args() - integration_tests_config = yaml.load(open(args.yaml_filepath), Loader=yaml.CLoader) + integration_tests_config = yaml.load( + open(args.yaml_filepath), Loader=yaml.CLoader + ) doku_conn = dokuwiki.DokuWiki( - url=integration_tests_config['dokuwiki']['url'], - user=integration_tests_config['dokuwiki']['username'], - password=integration_tests_config['dokuwiki']['password'] + url=integration_tests_config["dokuwiki"]["url"], + user=integration_tests_config["dokuwiki"]["username"], + password=integration_tests_config["dokuwiki"]["password"], ) less_packets = "--less-packets" if args.less_packets else "" test_command = [ - "python", "setup.py", "test", - "--test-path=tardis/tests/integration_tests/test_integration.py", "--args", + "python", + "setup.py", + "test", + "--test-path=tardis/tests/integration_tests/test_integration.py", + "--args", "--capture=no --integration-tests={0} --tardis-refdata={1} --remote-data " - "{2}".format(args.yaml_filepath, args.tardis_refdata, less_packets) + "{2}".format(args.yaml_filepath, args.tardis_refdata, less_packets), ] subprocess.call(test_command) @@ -45,7 +60,7 @@ def run_tests(): "https://api.github.com/repos/tardis-sn/tardis/branches/master" ) gh_master_head_data = json.loads(gh_request.content) - gh_tardis_githash = gh_master_head_data['commit']['sha'][:7] + gh_tardis_githash = gh_master_head_data["commit"]["sha"][:7] # Check whether a report of this githash is uploaded on dokuwiki. # If not, then this is a new commit and tests should be executed. @@ -56,13 +71,20 @@ def run_tests(): # If dokuwiki returns empty string, then it means that report has not # been created yet. if len(dokuwiki_report) == 0: - subprocess.call([ - "git", "pull", "https://www.github.com/tardis-sn/tardis", "master" - ]) + subprocess.call( + [ + "git", + "pull", + "https://www.github.com/tardis-sn/tardis", + "master", + ] + ) subprocess.call(test_command) else: checked = datetime.datetime.now() - logger.info("Up-to-date. Checked on {0} {1}".format( - checked.strftime("%d-%b-%Y"), checked.strftime("%H:%M:%S") - )) + logger.info( + "Up-to-date. Checked on {0} {1}".format( + checked.strftime("%d-%b-%Y"), checked.strftime("%H:%M:%S") + ) + ) time.sleep(600) diff --git a/tardis/tests/integration_tests/test_integration.py b/tardis/tests/integration_tests/test_integration.py index 82c1d0407bc..f67116a7fe2 100644 --- a/tardis/tests/integration_tests/test_integration.py +++ b/tardis/tests/integration_tests/test_integration.py @@ -3,6 +3,7 @@ import yaml import pytest import matplotlib + matplotlib.use("Agg") import matplotlib.pyplot as plt from numpy.testing import assert_allclose @@ -12,45 +13,50 @@ from tardis.io.config_reader import Configuration quantity_comparison = [ - ('/simulation/runner/last_line_interaction_in_id', - 'runner.last_line_interaction_in_id'), - ('/simulation/runner/last_line_interaction_out_id', - 'runner.last_line_interaction_out_id'), - ('/simulation/runner/last_line_interaction_shell_id', - 'runner.last_line_interaction_shell_id'), - ('/simulation/plasma/j_blues', - 'plasma.j_blues'), - ('/simulation/plasma/j_blue_estimator', - 'plasma.j_blue_estimator'), - ('/simulation/runner/packet_luminosity', - 'runner.packet_luminosity.cgs.value'), - ('/simulation/runner/montecarlo_virtual_luminosity', - 'runner.montecarlo_virtual_luminosity.cgs.value'), - ('/simulation/runner/output_nu', - 'runner.output_nu.cgs.value'), - ('/simulation/plasma/ion_number_density', - 'plasma.ion_number_density'), - ('/simulation/plasma/level_number_density', - 'plasma.level_number_density'), - ('/simulation/plasma/electron_densities', - 'plasma.electron_densities'), - ('/simulation/plasma/tau_sobolevs', - 'plasma.tau_sobolevs'), - ('/simulation/plasma/transition_probabilities', - 'plasma.transition_probabilities'), - ('/simulation/model/t_radiative', - 'model.t_radiative.cgs.value'), - ('/simulation/model/w', - 'model.w'), - ('/simulation/runner/j_estimator', - 'runner.j_estimator'), - ('/simulation/runner/nu_bar_estimator', - 'runner.nu_bar_estimator'), - ('/simulation/plasma/j_blues_norm_factor', - 'plasma.j_blues_norm_factor.cgs.value'), - ('/simulation/plasma/luminosity_inner', - 'plasma.luminosity_inner.cgs.value'), - ] + ( + "/simulation/runner/last_line_interaction_in_id", + "runner.last_line_interaction_in_id", + ), + ( + "/simulation/runner/last_line_interaction_out_id", + "runner.last_line_interaction_out_id", + ), + ( + "/simulation/runner/last_line_interaction_shell_id", + "runner.last_line_interaction_shell_id", + ), + ("/simulation/plasma/j_blues", "plasma.j_blues"), + ("/simulation/plasma/j_blue_estimator", "plasma.j_blue_estimator"), + ( + "/simulation/runner/packet_luminosity", + "runner.packet_luminosity.cgs.value", + ), + ( + "/simulation/runner/montecarlo_virtual_luminosity", + "runner.montecarlo_virtual_luminosity.cgs.value", + ), + ("/simulation/runner/output_nu", "runner.output_nu.cgs.value"), + ("/simulation/plasma/ion_number_density", "plasma.ion_number_density"), + ("/simulation/plasma/level_number_density", "plasma.level_number_density"), + ("/simulation/plasma/electron_densities", "plasma.electron_densities"), + ("/simulation/plasma/tau_sobolevs", "plasma.tau_sobolevs"), + ( + "/simulation/plasma/transition_probabilities", + "plasma.transition_probabilities", + ), + ("/simulation/model/t_radiative", "model.t_radiative.cgs.value"), + ("/simulation/model/w", "model.w"), + ("/simulation/runner/j_estimator", "runner.j_estimator"), + ("/simulation/runner/nu_bar_estimator", "runner.nu_bar_estimator"), + ( + "/simulation/plasma/j_blues_norm_factor", + "plasma.j_blues_norm_factor.cgs.value", + ), + ( + "/simulation/plasma/luminosity_inner", + "plasma.luminosity_inner.cgs.value", + ), +] @pytest.fixture(params=quantity_comparison) @@ -58,8 +64,10 @@ def model_quantities(request): return request.param -@pytest.mark.skipif(not pytest.config.getvalue("integration-tests"), - reason="integration tests are not included in this run") +@pytest.mark.skipif( + not pytest.config.getvalue("integration-tests"), + reason="integration tests are not included in this run", +) @pytest.mark.integration class TestIntegration(object): """Slow integration test for various setups present in subdirectories of @@ -74,22 +82,25 @@ def setup(self, request, reference, data_path, pytestconfig): a single run of integration test. """ # Get capture manager - capmanager = pytestconfig.pluginmanager.getplugin('capturemanager') + capmanager = pytestconfig.pluginmanager.getplugin("capturemanager") # The last component in dirpath can be extracted as name of setup. - self.name = data_path['setup_name'] + self.name = data_path["setup_name"] - self.config_file = os.path.join(data_path['config_dirpath'], "config.yml") + self.config_file = os.path.join( + data_path["config_dirpath"], "config.yml" + ) # A quick hack to use atom data per setup. Atom data is ingested from # local HDF or downloaded and cached from a url, depending on data_path # keys. - atom_data_name = yaml.load( - open(self.config_file), Loader=yaml.CLoader)['atom_data'] + atom_data_name = yaml.load(open(self.config_file), Loader=yaml.CLoader)[ + "atom_data" + ] # Get the path to HDF file: atom_data_filepath = os.path.join( - data_path['atom_data_path'], atom_data_name + data_path["atom_data_path"], atom_data_name ) # Load atom data file separately, pass it for forming tardis config. @@ -106,20 +117,20 @@ def setup(self, request, reference, data_path, pytestconfig): # Check whether current run is with less packets. if request.config.getoption("--less-packets"): - less_packets = request.config.integration_tests_config['less_packets'] - tardis_config['montecarlo']['no_of_packets'] = ( - less_packets['no_of_packets'] - ) - tardis_config['montecarlo']['last_no_of_packets'] = ( - less_packets['last_no_of_packets'] - ) - - - + less_packets = request.config.integration_tests_config[ + "less_packets" + ] + tardis_config["montecarlo"]["no_of_packets"] = less_packets[ + "no_of_packets" + ] + tardis_config["montecarlo"]["last_no_of_packets"] = less_packets[ + "last_no_of_packets" + ] # We now do a run with prepared config and get the simulation object. - self.result = Simulation.from_config(tardis_config, - atom_data=self.atom_data) + self.result = Simulation.from_config( + tardis_config, atom_data=self.atom_data + ) capmanager.suspend_global_capture(True) # If current test run is just for collecting reference data, store the @@ -129,16 +140,19 @@ def setup(self, request, reference, data_path, pytestconfig): self.result.run() if request.config.getoption("--generate-reference"): ref_data_path = os.path.join( - data_path['reference_path'], "{0}.h5".format(self.name) + data_path["reference_path"], "{0}.h5".format(self.name) ) if os.path.exists(ref_data_path): pytest.skip( - 'Reference data {0} does exist and tests will not ' - 'proceed generating new data'.format(ref_data_path)) + "Reference data {0} does exist and tests will not " + "proceed generating new data".format(ref_data_path) + ) self.result.to_hdf(file_path=ref_data_path) - pytest.skip("Reference data saved at {0}".format( - data_path['reference_path'] - )) + pytest.skip( + "Reference data saved at {0}".format( + data_path["reference_path"] + ) + ) capmanager.resume_global_capture() # Get the reference data through the fixture. @@ -147,10 +161,11 @@ def setup(self, request, reference, data_path, pytestconfig): def test_model_quantities(self, model_quantities): reference_quantity_name, tardis_quantity_name = model_quantities if reference_quantity_name not in self.reference: - pytest.skip('{0} not calculated in this run'.format( - reference_quantity_name)) + pytest.skip( + "{0} not calculated in this run".format(reference_quantity_name) + ) reference_quantity = self.reference[reference_quantity_name] - tardis_quantity = eval('self.result.' + tardis_quantity_name) + tardis_quantity = eval("self.result." + tardis_quantity_name) assert_allclose(tardis_quantity, reference_quantity) def plot_t_rad(self): @@ -162,17 +177,28 @@ def plot_t_rad(self): ax.set_ylabel("t_rad") result_line = ax.plot( - self.result.model.t_rad.cgs, color="blue", marker=".", label="Result" + self.result.model.t_rad.cgs, + color="blue", + marker=".", + label="Result", ) reference_line = ax.plot( - self.reference['/simulation/model/t_rad'], - color="green", marker=".", label="Reference" + self.reference["/simulation/model/t_rad"], + color="green", + marker=".", + label="Reference", ) error_ax = ax.twinx() error_line = error_ax.plot( - (1 - self.result.model.t_rad.cgs.value / self.reference['/simulation/model/t_rad']), - color="red", marker=".", label="Rel. Error" + ( + 1 + - self.result.model.t_rad.cgs.value + / self.reference["/simulation/model/t_rad"] + ), + color="red", + marker=".", + label="Rel. Error", ) error_ax.set_ylabel("Relative error (1 - result / reference)") @@ -182,21 +208,25 @@ def plot_t_rad(self): ax.legend(lines, labels, loc="lower left") return figure - def test_spectrum(self, plot_object): plot_object.add(self.plot_spectrum(), "{0}_spectrum".format(self.name)) assert_allclose( - self.reference['/simulation/runner/spectrum/luminosity_density_nu'], - self.result.runner.spectrum.luminosity_density_nu.cgs.value) + self.reference["/simulation/runner/spectrum/luminosity_density_nu"], + self.result.runner.spectrum.luminosity_density_nu.cgs.value, + ) assert_allclose( - self.reference['/simulation/runner/spectrum/wavelength'], - self.result.runner.spectrum.wavelength.cgs.value) + self.reference["/simulation/runner/spectrum/wavelength"], + self.result.runner.spectrum.wavelength.cgs.value, + ) assert_allclose( - self.reference['/simulation/runner/spectrum/luminosity_density_lambda'], - self.result.runner.spectrum.luminosity_density_lambda.cgs.value) + self.reference[ + "/simulation/runner/spectrum/luminosity_density_lambda" + ], + self.result.runner.spectrum.luminosity_density_lambda.cgs.value, + ) def plot_spectrum(self): @@ -209,29 +239,32 @@ def plot_spectrum(self): spectrum_ax.set_ylabel("Flux [cgs]") deviation = 1 - ( - self.result.runner.spectrum.luminosity_density_lambda.cgs.value / - self.reference[ - '/simulation/runner/spectrum/luminosity_density_lambda'] - + self.result.runner.spectrum.luminosity_density_lambda.cgs.value + / self.reference[ + "/simulation/runner/spectrum/luminosity_density_lambda" + ] ) - spectrum_ax.plot( - self.reference['/simulation/runner/spectrum/wavelength'], + self.reference["/simulation/runner/spectrum/wavelength"], self.reference[ - '/simulation/runner/spectrum/luminosity_density_lambda'], - color="black" + "/simulation/runner/spectrum/luminosity_density_lambda" + ], + color="black", ) spectrum_ax.plot( - self.reference['/simulation/runner/spectrum/wavelength'], + self.reference["/simulation/runner/spectrum/wavelength"], self.result.runner.spectrum.luminosity_density_lambda.cgs.value, - color="red" + color="red", ) spectrum_ax.set_xticks([]) deviation_ax = plt.subplot(gs[1]) - deviation_ax.plot(self.reference['/simulation/runner/spectrum/wavelength'], - deviation, color='black') + deviation_ax.plot( + self.reference["/simulation/runner/spectrum/wavelength"], + deviation, + color="black", + ) deviation_ax.set_xlabel("Wavelength [Angstrom]") - return plt.gcf() \ No newline at end of file + return plt.gcf() diff --git a/tardis/tests/setup_package.py b/tardis/tests/setup_package.py index 29a9bf65331..113c3f617f9 100644 --- a/tardis/tests/setup_package.py +++ b/tardis/tests/setup_package.py @@ -1,6 +1,12 @@ def get_package_data(): return { - _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'data/*.h5', - 'data/*.dat', 'data/*.npy', - 'integration_tests/*/*.yml', - 'integration_tests/*/*.dat']} + _ASTROPY_PACKAGE_NAME_ + + ".tests": [ + "coveragerc", + "data/*.h5", + "data/*.dat", + "data/*.npy", + "integration_tests/*/*.yml", + "integration_tests/*/*.dat", + ] + } diff --git a/tardis/tests/test_montecarlo.py b/tardis/tests/test_montecarlo.py index 65830b57497..81d31fbabcd 100644 --- a/tardis/tests/test_montecarlo.py +++ b/tardis/tests/test_montecarlo.py @@ -55,4 +55,3 @@ # def test_compute_distance2electron(): # assert montecarlo.compute_distance2electron_wrapper(0.0, 0.0, 2.0, 2.0) == 4.0 - diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 682e4645728..825909e165c 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -9,19 +9,19 @@ from tardis.io.config_reader import Configuration -class TestRunnerSimple(): +class TestRunnerSimple: """ Very simple run """ - name = 'test_runner_simple' + + name = "test_runner_simple" @pytest.fixture(scope="class") - def runner( - self, atomic_data_fname, - tardis_ref_data, generate_reference): + def runner(self, atomic_data_fname, tardis_ref_data, generate_reference): config = Configuration.from_yaml( - 'tardis/io/tests/data/tardis_configv1_verysimple.yml') - config['atom_data'] = atomic_data_fname + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) + config["atom_data"] = atomic_data_fname simulation = Simulation.from_config(config) simulation.run() @@ -30,45 +30,36 @@ def runner( return simulation.runner else: simulation.runner.hdf_properties = [ - 'j_blue_estimator', - 'spectrum', - 'spectrum_virtual' - ] - simulation.runner.to_hdf( - tardis_ref_data, - '', - self.name) - pytest.skip( - 'Reference data was generated during this run.') - - @pytest.fixture(scope='class') + "j_blue_estimator", + "spectrum", + "spectrum_virtual", + ] + simulation.runner.to_hdf(tardis_ref_data, "", self.name) + pytest.skip("Reference data was generated during this run.") + + @pytest.fixture(scope="class") def refdata(self, tardis_ref_data): def get_ref_data(key): - return tardis_ref_data[os.path.join( - self.name, key)] + return tardis_ref_data[os.path.join(self.name, key)] + return get_ref_data def test_j_blue_estimators(self, runner, refdata): - j_blue_estimator = refdata('j_blue_estimator').values + j_blue_estimator = refdata("j_blue_estimator").values - npt.assert_allclose( - runner.j_blue_estimator, - j_blue_estimator) + npt.assert_allclose(runner.j_blue_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): - luminosity = u.Quantity(refdata('spectrum/luminosity'), 'erg /s') + luminosity = u.Quantity(refdata("spectrum/luminosity"), "erg /s") - assert_quantity_allclose( - runner.spectrum.luminosity, - luminosity) + assert_quantity_allclose(runner.spectrum.luminosity, luminosity) def test_virtual_spectrum(self, runner, refdata): luminosity = u.Quantity( - refdata('spectrum_virtual/luminosity'), 'erg /s') + refdata("spectrum_virtual/luminosity"), "erg /s" + ) - assert_quantity_allclose( - runner.spectrum_virtual.luminosity, - luminosity) + assert_quantity_allclose(runner.spectrum_virtual.luminosity, luminosity) def test_runner_properties(self, runner): """Tests whether a number of runner attributes exist and also verifies @@ -81,14 +72,16 @@ def test_runner_properties(self, runner): virt_type = np.ndarray - props_required_by_modeltohdf5 = dict([ + props_required_by_modeltohdf5 = dict( + [ ("virt_packet_last_interaction_type", virt_type), ("virt_packet_last_line_interaction_in_id", virt_type), ("virt_packet_last_line_interaction_out_id", virt_type), ("virt_packet_last_interaction_in_nu", virt_type), ("virt_packet_nus", virt_type), ("virt_packet_energies", virt_type), - ]) + ] + ) required_props = props_required_by_modeltohdf5.copy() @@ -96,5 +89,5 @@ def test_runner_properties(self, runner): actual = getattr(runner, prop) assert type(actual) == prop_type, ( "wrong type of attribute '{}':" - "expected {}, found {}".format( - prop, prop_type, type(actual))) + "expected {}, found {}".format(prop, prop_type, type(actual)) + ) diff --git a/tardis/tests/test_tardis_full_formal_integral.py b/tardis/tests/test_tardis_full_formal_integral.py index d3de68501c4..53a7343e7ea 100644 --- a/tardis/tests/test_tardis_full_formal_integral.py +++ b/tardis/tests/test_tardis_full_formal_integral.py @@ -9,18 +9,19 @@ from tardis.io.config_reader import Configuration import astropy -config_line_modes = ['downbranch', 'macroatom'] +config_line_modes = ["downbranch", "macroatom"] interpolate_shells = [-1, 30] -@pytest.fixture(scope='module', params=config_line_modes) +@pytest.fixture(scope="module", params=config_line_modes) def base_config(request): config = Configuration.from_yaml( - 'tardis/io/tests/data/tardis_configv1_verysimple.yml') + "tardis/io/tests/data/tardis_configv1_verysimple.yml" + ) config["plasma"]["line_interaction_type"] = request.param - config["montecarlo"]["no_of_packets"] = 4.0e+4 - config["montecarlo"]["last_no_of_packets"] = 1.0e+5 + config["montecarlo"]["no_of_packets"] = 4.0e4 + config["montecarlo"]["last_no_of_packets"] = 1.0e5 config["montecarlo"]["no_of_virtual_packets"] = 0 config["spectrum"]["method"] = "integrated" config["spectrum"]["integrated"]["points"] = 200 @@ -28,27 +29,31 @@ def base_config(request): return config -@pytest.fixture(scope='module', params=interpolate_shells) + +@pytest.fixture(scope="module", params=interpolate_shells) def config(base_config, request): base_config["spectrum"]["integrated"]["interpolate_shells"] = request.param return base_config -class TestRunnerSimpleFormalInegral(): + +class TestRunnerSimpleFormalInegral: """ Very simple run with the formal integral spectral synthesis method """ - _name = 'test_runner_simple_integral' + + _name = "test_runner_simple_integral" @pytest.fixture(scope="class") def runner( - self, config, atomic_data_fname, - tardis_ref_data, generate_reference): + self, config, atomic_data_fname, tardis_ref_data, generate_reference + ): config.atom_data = atomic_data_fname - self.name = (self._name + - "_{:s}".format(config.plasma.line_interaction_type)) + self.name = self._name + "_{:s}".format( + config.plasma.line_interaction_type + ) if config.spectrum.integrated.interpolate_shells > 0: - self.name += '_interp' + self.name += "_interp" simulation = Simulation.from_config(config) simulation.run() @@ -57,43 +62,40 @@ def runner( return simulation.runner else: simulation.runner.hdf_properties = [ - 'j_blue_estimator', - 'spectrum', - 'spectrum_integrated' - ] - simulation.runner.to_hdf( - tardis_ref_data, - '', - self.name) - pytest.skip( - 'Reference data was generated during this run.') - - @pytest.fixture(scope='class') + "j_blue_estimator", + "spectrum", + "spectrum_integrated", + ] + simulation.runner.to_hdf(tardis_ref_data, "", self.name) + pytest.skip("Reference data was generated during this run.") + + @pytest.fixture(scope="class") def refdata(self, tardis_ref_data): def get_ref_data(key): - return tardis_ref_data[os.path.join( - self.name, key)] + return tardis_ref_data[os.path.join(self.name, key)] + return get_ref_data def test_j_blue_estimators(self, runner, refdata): - j_blue_estimator = refdata('j_blue_estimator').values + j_blue_estimator = refdata("j_blue_estimator").values - npt.assert_allclose( - runner.j_blue_estimator, - j_blue_estimator) + npt.assert_allclose(runner.j_blue_estimator, j_blue_estimator) def test_spectrum(self, runner, refdata): - luminosity = u.Quantity(refdata('spectrum/luminosity'), 'erg /s') + luminosity = u.Quantity(refdata("spectrum/luminosity"), "erg /s") - assert_quantity_allclose( - runner.spectrum.luminosity, - luminosity) + assert_quantity_allclose(runner.spectrum.luminosity, luminosity) def test_spectrum_integrated(self, runner, refdata): luminosity = u.Quantity( - refdata('spectrum_integrated/luminosity'), 'erg /s') + refdata("spectrum_integrated/luminosity"), "erg /s" + ) - print("actual, desired: ", luminosity, runner.spectrum_integrated.luminosity) - assert_quantity_allclose( + print( + "actual, desired: ", + luminosity, runner.spectrum_integrated.luminosity, - luminosity) + ) + assert_quantity_allclose( + runner.spectrum_integrated.luminosity, luminosity + ) diff --git a/tardis/tests/test_util.py b/tardis/tests/test_util.py index 0e6bbd0d44f..820bd6ef5dd 100644 --- a/tardis/tests/test_util.py +++ b/tardis/tests/test_util.py @@ -5,45 +5,72 @@ from astropy import units as u from io import StringIO -from tardis.util.base import MalformedSpeciesError, MalformedElementSymbolError, MalformedQuantityError, int_to_roman, \ - roman_to_int, calculate_luminosity, create_synpp_yaml, intensity_black_body, \ - species_tuple_to_string, species_string_to_tuple, parse_quantity, element_symbol2atomic_number, \ - atomic_number2element_symbol, reformat_element_symbol, quantity_linspace, convert_abundances_format - -data_path = os.path.join('tardis', 'io', 'tests', 'data') +from tardis.util.base import ( + MalformedSpeciesError, + MalformedElementSymbolError, + MalformedQuantityError, + int_to_roman, + roman_to_int, + calculate_luminosity, + create_synpp_yaml, + intensity_black_body, + species_tuple_to_string, + species_string_to_tuple, + parse_quantity, + element_symbol2atomic_number, + atomic_number2element_symbol, + reformat_element_symbol, + quantity_linspace, + convert_abundances_format, +) + +data_path = os.path.join("tardis", "io", "tests", "data") @pytest.fixture def artis_abundances_fname(): - return os.path.join(data_path, 'artis_abundances.dat') + return os.path.join(data_path, "artis_abundances.dat") + def test_malformed_species_error(): - malformed_species_error = MalformedSpeciesError('He') - assert malformed_species_error.malformed_element_symbol == 'He' - assert str(malformed_species_error) == 'Expecting a species notation (e.g. "Si 2", "Si II", "Fe IV") - supplied He' + malformed_species_error = MalformedSpeciesError("He") + assert malformed_species_error.malformed_element_symbol == "He" + assert ( + str(malformed_species_error) + == 'Expecting a species notation (e.g. "Si 2", "Si II", "Fe IV") - supplied He' + ) def test_malformed_elements_symbol_error(): - malformed_elements_symbol_error = MalformedElementSymbolError('Hx') - assert malformed_elements_symbol_error.malformed_element_symbol == 'Hx' - assert str(malformed_elements_symbol_error) == 'Expecting an atomic symbol (e.g. Fe) - supplied Hx' + malformed_elements_symbol_error = MalformedElementSymbolError("Hx") + assert malformed_elements_symbol_error.malformed_element_symbol == "Hx" + assert ( + str(malformed_elements_symbol_error) + == "Expecting an atomic symbol (e.g. Fe) - supplied Hx" + ) def test_malformed_quantity_error(): - malformed_quantity_error = MalformedQuantityError('abcd') - assert malformed_quantity_error.malformed_quantity_string == 'abcd' - assert str(malformed_quantity_error) == 'Expecting a quantity string(e.g. "5 km/s") for keyword - supplied abcd' - - -@pytest.mark.parametrize(['test_input', 'expected_result'], [ - (1, 'I'), - (5, 'V'), - (19, 'XIX'), - (556, 'DLVI'), - (1400, 'MCD'), - (1999, 'MCMXCIX'), - (3000, 'MMM') -]) + malformed_quantity_error = MalformedQuantityError("abcd") + assert malformed_quantity_error.malformed_quantity_string == "abcd" + assert ( + str(malformed_quantity_error) + == 'Expecting a quantity string(e.g. "5 km/s") for keyword - supplied abcd' + ) + + +@pytest.mark.parametrize( + ["test_input", "expected_result"], + [ + (1, "I"), + (5, "V"), + (19, "XIX"), + (556, "DLVI"), + (1400, "MCD"), + (1999, "MCMXCIX"), + (3000, "MMM"), + ], +) def test_int_to_roman(test_input, expected_result): assert int_to_roman(test_input) == expected_result @@ -51,15 +78,18 @@ def test_int_to_roman(test_input, expected_result): int_to_roman(1.5) -@pytest.mark.parametrize(['test_input', 'expected_result'], [ - ('I', 1), - ('V', 5), - ('XIX', 19), - ('DLVI', 556), - ('MCD', 1400), - ('MCMXCIX', 1999), - ('MMM', 3000) -]) +@pytest.mark.parametrize( + ["test_input", "expected_result"], + [ + ("I", 1), + ("V", 5), + ("XIX", 19), + ("DLVI", 556), + ("MCD", 1400), + ("MCMXCIX", 1999), + ("MMM", 3000), + ], +) def test_roman_to_int(test_input, expected_result): assert roman_to_int(test_input) == expected_result @@ -67,117 +97,147 @@ def test_roman_to_int(test_input, expected_result): roman_to_int(1) - -@pytest.mark.parametrize(['string_io', 'distance', 'result'], [ - (StringIO(u'4000 1e-21\n4500 3e-21\n5000 5e-21'), '100 km', (0.0037699111843077517, 4000.0, 5000.0)), - (StringIO(u'7600 2.4e-19\n7800 1.6e-19\n8100 9.1e-20'), '500 km', (2.439446695512474, 7600.0, 8100.0)) -]) +@pytest.mark.parametrize( + ["string_io", "distance", "result"], + [ + ( + StringIO("4000 1e-21\n4500 3e-21\n5000 5e-21"), + "100 km", + (0.0037699111843077517, 4000.0, 5000.0), + ), + ( + StringIO("7600 2.4e-19\n7800 1.6e-19\n8100 9.1e-20"), + "500 km", + (2.439446695512474, 7600.0, 8100.0), + ), + ], +) def test_calculate_luminosity(string_io, distance, result): assert calculate_luminosity(string_io, distance) == result -@pytest.mark.parametrize(['nu', 't', 'i'], [ - (10**6, 1000, 3.072357852080765e-22), - (10**6, 300, 9.21707305730458e-23), - (10**8, 1000, 6.1562660718558254e-24), - (10**8, 300, 1.846869480674048e-24), -]) +@pytest.mark.parametrize( + ["nu", "t", "i"], + [ + (10 ** 6, 1000, 3.072357852080765e-22), + (10 ** 6, 300, 9.21707305730458e-23), + (10 ** 8, 1000, 6.1562660718558254e-24), + (10 ** 8, 300, 1.846869480674048e-24), + ], +) def test_intensity_black_body(nu, t, i): assert np.isclose(intensity_black_body(nu, t), i) - -@pytest.mark.parametrize(['species_tuple', 'roman_numerals', 'species_string'], [ - ((14, 1), True, 'Si II'), - ((14, 1), False, 'Si 1'), - ((14, 3), True, 'Si IV'), - ((14, 3), False, 'Si 3'), - ((14, 8), True, 'Si IX'), - ((14, 8), False, 'Si 8'), -]) +@pytest.mark.parametrize( + ["species_tuple", "roman_numerals", "species_string"], + [ + ((14, 1), True, "Si II"), + ((14, 1), False, "Si 1"), + ((14, 3), True, "Si IV"), + ((14, 3), False, "Si 3"), + ((14, 8), True, "Si IX"), + ((14, 8), False, "Si 8"), + ], +) def test_species_tuple_to_string(species_tuple, roman_numerals, species_string): - assert species_tuple_to_string(species_tuple, roman_numerals=roman_numerals) == species_string + assert ( + species_tuple_to_string(species_tuple, roman_numerals=roman_numerals) + == species_string + ) -@pytest.mark.parametrize(['species_string', 'species_tuple'], [ - ('si ii', (14, 1)), - ('si 2', (14, 1)), - ('si ix', (14, 8)), -]) +@pytest.mark.parametrize( + ["species_string", "species_tuple"], + [("si ii", (14, 1)), ("si 2", (14, 1)), ("si ix", (14, 8)),], +) def test_species_string_to_tuple(species_string, species_tuple): assert species_string_to_tuple(species_string) == species_tuple with pytest.raises(MalformedSpeciesError): - species_string_to_tuple('II') + species_string_to_tuple("II") with pytest.raises(MalformedSpeciesError): - species_string_to_tuple('He Si') + species_string_to_tuple("He Si") with pytest.raises(ValueError): - species_string_to_tuple('He IX') + species_string_to_tuple("He IX") def test_parse_quantity(): - q1 = parse_quantity('5 km/s') - assert q1.value == 5. - assert q1.unit == u.Unit('km/s') + q1 = parse_quantity("5 km/s") + assert q1.value == 5.0 + assert q1.unit == u.Unit("km/s") with pytest.raises(MalformedQuantityError): parse_quantity(5) with pytest.raises(MalformedQuantityError): - parse_quantity('abcd') + parse_quantity("abcd") with pytest.raises(MalformedQuantityError): - parse_quantity('a abcd') + parse_quantity("a abcd") with pytest.raises(MalformedQuantityError): - parse_quantity('5 abcd') + parse_quantity("5 abcd") -@pytest.mark.parametrize(['element_symbol', 'atomic_number'], [ - ('sI', 14), - ('ca', 20), - ('Fe', 26) -]) +@pytest.mark.parametrize( + ["element_symbol", "atomic_number"], [("sI", 14), ("ca", 20), ("Fe", 26)] +) def test_element_symbol2atomic_number(element_symbol, atomic_number): assert element_symbol2atomic_number(element_symbol) == atomic_number with pytest.raises(MalformedElementSymbolError): - element_symbol2atomic_number('Hx') + element_symbol2atomic_number("Hx") def test_atomic_number2element_symbol(): - assert atomic_number2element_symbol(14) == 'Si' - - -@pytest.mark.parametrize(['unformatted_element_string', 'formatted_element_string'], [ - ('si', 'Si'), - ('sI', 'Si'), - ('Si', 'Si'), - ('c', 'C'), - ('C', 'C'), -]) -def test_reformat_element_symbol(unformatted_element_string, formatted_element_string): - assert reformat_element_symbol(unformatted_element_string) == formatted_element_string - - -@pytest.mark.parametrize(['start', 'stop', 'num', 'expected'], [ - (u.Quantity(1, 'km/s'), u.Quantity(5, 'km/s'), 5, u.Quantity(np.array([1., 2., 3., 4., 5.]), 'km/s')), - (u.Quantity(0.5, 'eV'), u.Quantity(0.6, 'eV'), 3, u.Quantity(np.array([0.5, 0.55, 0.6]), 'eV')) -]) + assert atomic_number2element_symbol(14) == "Si" + + +@pytest.mark.parametrize( + ["unformatted_element_string", "formatted_element_string"], + [("si", "Si"), ("sI", "Si"), ("Si", "Si"), ("c", "C"), ("C", "C"),], +) +def test_reformat_element_symbol( + unformatted_element_string, formatted_element_string +): + assert ( + reformat_element_symbol(unformatted_element_string) + == formatted_element_string + ) + + +@pytest.mark.parametrize( + ["start", "stop", "num", "expected"], + [ + ( + u.Quantity(1, "km/s"), + u.Quantity(5, "km/s"), + 5, + u.Quantity(np.array([1.0, 2.0, 3.0, 4.0, 5.0]), "km/s"), + ), + ( + u.Quantity(0.5, "eV"), + u.Quantity(0.6, "eV"), + 3, + u.Quantity(np.array([0.5, 0.55, 0.6]), "eV"), + ), + ], +) def test_quantity_linspace(start, stop, num, expected): obtained = quantity_linspace(start, stop, num) assert obtained.unit == expected.unit assert obtained.value.all() == expected.value.all() with pytest.raises(ValueError): - quantity_linspace(u.Quantity(0.5, 'eV'), '0.6 eV', 3) + quantity_linspace(u.Quantity(0.5, "eV"), "0.6 eV", 3) def test_convert_abundances_format(artis_abundances_fname): abundances = convert_abundances_format(artis_abundances_fname) - assert np.isclose(abundances.loc[3, 'O'], 1.240199e-08, atol=1.e-12) - assert np.isclose(abundances.loc[1, 'Co'], 2.306023e-05, atol=1.e-12) - assert np.isclose(abundances.loc[69, 'Ni'], 1.029928e-17, atol=1.e-12) - assert np.isclose(abundances.loc[2, 'C'], 4.425876e-09, atol=1.e-12) + assert np.isclose(abundances.loc[3, "O"], 1.240199e-08, atol=1.0e-12) + assert np.isclose(abundances.loc[1, "Co"], 2.306023e-05, atol=1.0e-12) + assert np.isclose(abundances.loc[69, "Ni"], 1.029928e-17, atol=1.0e-12) + assert np.isclose(abundances.loc[2, "C"], 4.425876e-09, atol=1.0e-12) diff --git a/tardis/util/__init__.py b/tardis/util/__init__.py index 0d1b60fa94c..6dd4694b3ef 100644 --- a/tardis/util/__init__.py +++ b/tardis/util/__init__.py @@ -1,3 +1 @@ # Utilities for TARDIS - - diff --git a/tardis/util/base.py b/tardis/util/base.py index 7777cf756e1..9f6bca3f3ab 100644 --- a/tardis/util/base.py +++ b/tardis/util/base.py @@ -23,52 +23,66 @@ logger = logging.getLogger(__name__) tardis_dir = os.path.realpath(tardis.__path__[0]) -ATOMIC_SYMBOLS_DATA = pd.read_csv(get_internal_data_path('atomic_symbols.dat'), delim_whitespace=True, - names=['atomic_number', 'symbol']).set_index('atomic_number').squeeze() +ATOMIC_SYMBOLS_DATA = ( + pd.read_csv( + get_internal_data_path("atomic_symbols.dat"), + delim_whitespace=True, + names=["atomic_number", "symbol"], + ) + .set_index("atomic_number") + .squeeze() +) ATOMIC_NUMBER2SYMBOL = OrderedDict(ATOMIC_SYMBOLS_DATA.to_dict()) -SYMBOL2ATOMIC_NUMBER = OrderedDict((y, x) for x, y in ATOMIC_NUMBER2SYMBOL.items()) +SYMBOL2ATOMIC_NUMBER = OrderedDict( + (y, x) for x, y in ATOMIC_NUMBER2SYMBOL.items() +) -synpp_default_yaml_fname = get_internal_data_path('synpp_default.yaml') +synpp_default_yaml_fname = get_internal_data_path("synpp_default.yaml") -NUMERAL_MAP = tuple(zip( - (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), - ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I') -)) +NUMERAL_MAP = tuple( + zip( + (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), + ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"), + ) +) + class MalformedError(Exception): pass class MalformedSpeciesError(MalformedError): - def __init__(self, malformed_element_symbol): self.malformed_element_symbol = malformed_element_symbol def __str__(self): - return ('Expecting a species notation (e.g. "Si 2", "Si II", "Fe IV") ' - '- supplied {0}'.format(self.malformed_element_symbol)) + return ( + 'Expecting a species notation (e.g. "Si 2", "Si II", "Fe IV") ' + "- supplied {0}".format(self.malformed_element_symbol) + ) class MalformedElementSymbolError(MalformedError): - def __init__(self, malformed_element_symbol): self.malformed_element_symbol = malformed_element_symbol def __str__(self): - return ('Expecting an atomic symbol (e.g. Fe) - supplied {0}').format( - self.malformed_element_symbol) + return ("Expecting an atomic symbol (e.g. Fe) - supplied {0}").format( + self.malformed_element_symbol + ) class MalformedQuantityError(MalformedError): - def __init__(self, malformed_quantity_string): self.malformed_quantity_string = malformed_quantity_string def __str__(self): - return ('Expecting a quantity string(e.g. "5 km/s") for keyword ' - '- supplied {0}').format(self.malformed_quantity_string) + return ( + 'Expecting a quantity string(e.g. "5 km/s") for keyword ' + "- supplied {0}" + ).format(self.malformed_quantity_string) def int_to_roman(i): @@ -90,7 +104,8 @@ def int_to_roman(i): count = i // integer result.append(numeral * count) i -= integer * count - return ''.join(result) + return "".join(result) + def roman_to_int(roman_string): """ @@ -110,22 +125,29 @@ def roman_to_int(roman_string): NUMERALS_SET = set(list(zip(*NUMERAL_MAP))[1]) roman_string = roman_string.upper() if len(set(list(roman_string.upper())) - NUMERALS_SET) != 0: - raise ValueError('{0} does not seem to be a roman numeral'.format( - roman_string)) + raise ValueError( + "{0} does not seem to be a roman numeral".format(roman_string) + ) i = result = 0 for integer, numeral in NUMERAL_MAP: - while roman_string[i:i + len(numeral)] == numeral: + while roman_string[i : i + len(numeral)] == numeral: result += integer i += len(numeral) if result < 1: - raise ValueError('Can not interpret Roman Numeral {0}'.format(roman_string)) + raise ValueError( + "Can not interpret Roman Numeral {0}".format(roman_string) + ) return result def calculate_luminosity( - spec_fname, distance, wavelength_column=0, - wavelength_unit=u.angstrom, flux_column=1, - flux_unit=u.Unit('erg / (Angstrom cm2 s)')): + spec_fname, + distance, + wavelength_column=0, + wavelength_unit=u.angstrom, + flux_column=1, + flux_unit=u.Unit("erg / (Angstrom cm2 s)"), +): """ Calculates luminosity of star. @@ -153,13 +175,15 @@ def calculate_luminosity( wavelength.max() : float Maximum value of wavelength of light """ - #BAD STYLE change to parse quantity + # BAD STYLE change to parse quantity distance = u.Unit(distance) - wavelength, flux = np.loadtxt(spec_fname, usecols=(wavelength_column, flux_column), unpack=True) + wavelength, flux = np.loadtxt( + spec_fname, usecols=(wavelength_column, flux_column), unpack=True + ) flux_density = np.trapz(flux, wavelength) * (flux_unit * wavelength_unit) - luminosity = (flux_density * 4 * np.pi * distance**2).to('erg/s') + luminosity = (flux_density * 4 * np.pi * distance ** 2).to("erg/s") return luminosity.value, wavelength.min(), wavelength.max() @@ -184,66 +208,79 @@ def create_synpp_yaml(radial1d_mdl, fname, shell_no=0, lines_db=None): If the current dataset does not contain necessary reference files """ - logger.warning('Currently only works with Si and a special setup') + logger.warning("Currently only works with Si and a special setup") if radial1d_mdl.atom_data.synpp_refs is not None: raise ValueError( - 'The current atom dataset does not contain the ' - 'necessary reference files (please contact the authors)') + "The current atom dataset does not contain the " + "necessary reference files (please contact the authors)" + ) - radial1d_mdl.atom_data.synpp_refs['ref_log_tau'] = -99.0 + radial1d_mdl.atom_data.synpp_refs["ref_log_tau"] = -99.0 for key, value in radial1d_mdl.atom_data.synpp_refs.iterrows(): try: - radial1d_mdl.atom_data.synpp_refs['ref_log_tau'].loc[key] = np.log10( - radial1d_mdl.plasma.tau_sobolevs[0].loc[value['line_id']]) + radial1d_mdl.atom_data.synpp_refs["ref_log_tau"].loc[ + key + ] = np.log10( + radial1d_mdl.plasma.tau_sobolevs[0].loc[value["line_id"]] + ) except KeyError: pass - relevant_synpp_refs = radial1d_mdl.atom_data.synpp_refs[ - radial1d_mdl.atom_data.synpp_refs['ref_log_tau'] > -50] + radial1d_mdl.atom_data.synpp_refs["ref_log_tau"] > -50 + ] with open(synpp_default_yaml_fname) as stream: yaml_reference = yaml.load(stream, Loader=yaml.CLoader) 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_reference['output']['min_wl'] = float( - radial1d_mdl.runner.spectrum.wavelength.to('angstrom').value.min()) - yaml_reference['output']['max_wl'] = float( - radial1d_mdl.runner.spectrum.wavelength.to('angstrom').value.max()) - - - #raise Exception("there's a problem here with units what units does synpp expect?") - yaml_reference['opacity']['v_ref'] = float( - (radial1d_mdl.tardis_config.structure.v_inner[0].to('km/s') / - (1000. * u.km / u.s)).value) - yaml_reference['grid']['v_outer_max'] = float( - (radial1d_mdl.tardis_config.structure.v_outer[-1].to('km/s') / - (1000. * u.km / u.s)).value) - - #pdb.set_trace() - - yaml_setup = yaml_reference['setups'][0] - yaml_setup['ions'] = [] - yaml_setup['log_tau'] = [] - yaml_setup['active'] = [] - yaml_setup['temp'] = [] - yaml_setup['v_min'] = [] - yaml_setup['v_max'] = [] - yaml_setup['aux'] = [] + yaml_reference["opacity"]["line_dir"] = os.path.join(lines_db, "lines") + yaml_reference["opacity"]["line_dir"] = os.path.join( + lines_db, "refs.dat" + ) + + yaml_reference["output"]["min_wl"] = float( + radial1d_mdl.runner.spectrum.wavelength.to("angstrom").value.min() + ) + yaml_reference["output"]["max_wl"] = float( + radial1d_mdl.runner.spectrum.wavelength.to("angstrom").value.max() + ) + + # raise Exception("there's a problem here with units what units does synpp expect?") + yaml_reference["opacity"]["v_ref"] = float( + ( + radial1d_mdl.tardis_config.structure.v_inner[0].to("km/s") + / (1000.0 * u.km / u.s) + ).value + ) + yaml_reference["grid"]["v_outer_max"] = float( + ( + radial1d_mdl.tardis_config.structure.v_outer[-1].to("km/s") + / (1000.0 * u.km / u.s) + ).value + ) + + # pdb.set_trace() + + yaml_setup = yaml_reference["setups"][0] + yaml_setup["ions"] = [] + yaml_setup["log_tau"] = [] + yaml_setup["active"] = [] + yaml_setup["temp"] = [] + yaml_setup["v_min"] = [] + yaml_setup["v_max"] = [] + yaml_setup["aux"] = [] for species, synpp_ref in relevant_synpp_refs.iterrows(): - yaml_setup['ions'].append(100 * species[0] + species[1]) - yaml_setup['log_tau'].append(float(synpp_ref['ref_log_tau'])) - yaml_setup['active'].append(True) - yaml_setup['temp'].append(yaml_setup['t_phot']) - yaml_setup['v_min'].append(yaml_reference['opacity']['v_ref']) - yaml_setup['v_max'].append(yaml_reference['grid']['v_outer_max']) - yaml_setup['aux'].append(1e200) - - with open(fname, 'w') as f: + yaml_setup["ions"].append(100 * species[0] + species[1]) + yaml_setup["log_tau"].append(float(synpp_ref["ref_log_tau"])) + yaml_setup["active"].append(True) + yaml_setup["temp"].append(yaml_setup["t_phot"]) + yaml_setup["v_min"].append(yaml_reference["opacity"]["v_ref"]) + yaml_setup["v_max"].append(yaml_reference["grid"]["v_outer_max"]) + yaml_setup["aux"].append(1e200) + + with open(fname, "w") as f: yaml.dump(yaml_reference, stream=f, explicit_start=True) @@ -269,8 +306,9 @@ def intensity_black_body(nu, T): """ beta_rad = 1 / (k_B_cgs * T) coefficient = 2 * h_cgs / c_cgs ** 2 - intensity = ne.evaluate('coefficient * nu**3 / ' - '(exp(h_cgs * nu * beta_rad) -1 )') + intensity = ne.evaluate( + "coefficient * nu**3 / " "(exp(h_cgs * nu * beta_rad) -1 )" + ) return intensity @@ -295,10 +333,10 @@ def species_tuple_to_string(species_tuple, roman_numerals=True): atomic_number, ion_number = species_tuple element_symbol = ATOMIC_NUMBER2SYMBOL[atomic_number] if roman_numerals: - roman_ion_number = int_to_roman(ion_number+1) - return '{0} {1}'.format(str(element_symbol), roman_ion_number) + roman_ion_number = int_to_roman(ion_number + 1) + return "{0} {1}".format(str(element_symbol), roman_ion_number) else: - return '{0} {1:d}'.format(element_symbol, ion_number) + return "{0} {1:d}".format(element_symbol, ion_number) def species_string_to_tuple(species_string): @@ -322,15 +360,17 @@ def species_string_to_tuple(species_string): """ try: - element_symbol, ion_number_string = re.match(r'^(\w+)\s*(\d+)', - species_string).groups() + element_symbol, ion_number_string = re.match( + r"^(\w+)\s*(\d+)", species_string + ).groups() except AttributeError: try: element_symbol, ion_number_string = species_string.split() except ValueError: raise MalformedSpeciesError( 'Species string "{0}" is not of format ' - ' (e.g. Fe 2, Fe2, ..)'.format(species_string)) + " (e.g. Fe 2, Fe2, ..)".format(species_string) + ) atomic_number = element_symbol2atomic_number(element_symbol) @@ -342,11 +382,14 @@ def species_string_to_tuple(species_string): except ValueError: raise MalformedSpeciesError( "Given ion number ('{}') could not be parsed".format( - ion_number_string)) + ion_number_string + ) + ) if ion_number > atomic_number: raise ValueError( - 'Species given does not exist: ion number > atomic number') + "Species given does not exist: ion number > atomic number" + ) return atomic_number, ion_number - 1 @@ -472,15 +515,18 @@ def quantity_linspace(start, stop, num, **kwargs): ValueError If start and stop values have no unit attribute. """ - if not (hasattr(start, 'unit') and hasattr(stop, 'unit')): - raise ValueError('Both start and stop need to be quantities with a ' - 'unit attribute') + if not (hasattr(start, "unit") and hasattr(stop, "unit")): + raise ValueError( + "Both start and stop need to be quantities with a " "unit attribute" + ) - return (np.linspace(start.value, stop.to(start.unit).value, num, **kwargs) - * start.unit) + return ( + np.linspace(start.value, stop.to(start.unit).value, num, **kwargs) + * start.unit + ) -def convert_abundances_format(fname, delimiter=r'\s+'): +def convert_abundances_format(fname, delimiter=r"\s+"): """ Changes format of file containing abundances into data frame @@ -496,10 +542,9 @@ def convert_abundances_format(fname, delimiter=r'\s+'): DataFrame Corresponding data frame """ - df = pd.read_csv(fname, delimiter=delimiter, comment='#', header=None) + df = pd.read_csv(fname, delimiter=delimiter, comment="#", header=None) # Drop shell index column df.drop(df.columns[0], axis=1, inplace=True) # Assign header row - df.columns = [nucname.name(i) - for i in range(1, df.shape[1] + 1)] - return df \ No newline at end of file + df.columns = [nucname.name(i) for i in range(1, df.shape[1] + 1)] + return df diff --git a/tardis/util/colored_logger.py b/tardis/util/colored_logger.py index 37130295345..f45a7c423e3 100644 --- a/tardis/util/colored_logger.py +++ b/tardis/util/colored_logger.py @@ -1,29 +1,33 @@ import logging -''' + +""" Code for Custom Logger Classes (ColoredFormatter and ColorLogger) and its helper function (formatter_message) is used from this thread http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output -''' +""" + def formatter_message(message, use_color=True): - ''' + """ Helper Function used for Coloring Log Output - ''' - #These are the sequences need to get colored ouput + """ + # These are the sequences need to get colored ouput RESET_SEQ = "\033[0m" BOLD_SEQ = "\033[1m" if use_color: - message = message.replace( - "$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ) + message = message.replace("$RESET", RESET_SEQ).replace( + "$BOLD", BOLD_SEQ + ) else: message = message.replace("$RESET", "").replace("$BOLD", "") return message class ColoredFormatter(logging.Formatter): - ''' + """ Custom logger class for changing levels color - ''' + """ + def __init__(self, msg, use_color=True): logging.Formatter.__init__(self, msg) self.use_color = use_color @@ -33,24 +37,26 @@ def format(self, record): RESET_SEQ = "\033[0m" BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) COLORS = { - 'WARNING': YELLOW, - 'INFO': WHITE, - 'DEBUG': BLUE, - 'CRITICAL': YELLOW, - 'ERROR': RED + "WARNING": YELLOW, + "INFO": WHITE, + "DEBUG": BLUE, + "CRITICAL": YELLOW, + "ERROR": RED, } levelname = record.levelname if self.use_color and levelname in COLORS: - levelname_color = COLOR_SEQ % ( - 30 + COLORS[levelname]) + levelname + RESET_SEQ + levelname_color = ( + COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ + ) record.levelname = levelname_color return logging.Formatter.format(self, record) class ColoredLogger(logging.Logger): - ''' + """ Custom logger class with multiple destinations - ''' + """ + FORMAT = "[$BOLD%(name)-20s$RESET][%(levelname)-18s] %(message)s ($BOLD%(filename)s$RESET:%(lineno)d)" COLOR_FORMAT = formatter_message(FORMAT, True) @@ -63,4 +69,4 @@ def __init__(self, name): console.setFormatter(color_formatter) self.addHandler(console) - return \ No newline at end of file + return From da3c28761505ffb34a8b441037127c836c70e9c2 Mon Sep 17 00:00:00 2001 From: Christian Vogl Date: Wed, 9 Sep 2020 14:45:25 +0200 Subject: [PATCH 061/116] Change the Numba code to reflect the C Code with close line problematic. Add print statement Reproduce close line behavior of C code Fix for most close line issues Added test_close_line function to match C version, added RPacket and VPacket close_line property in support, added close line tests in boundary and electron distance conditionals Rename function woops Co-authored-by: Wolfgang Kerzendorf Co-authored-by: Andrew Fullard Co-authored-by: Andreas Floers Co-authored-by: Jack O'Brien Co-authored-by: Marc Williamson --- tardis/montecarlo/montecarlo_numba/base.py | 3 +- .../montecarlo_numba/interaction.py | 8 +- .../montecarlo/montecarlo_numba/r_packet.py | 75 ++++++++++++------- tardis/montecarlo/montecarlo_numba/vpacket.py | 15 ++-- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 424165342ec..206056a3529 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -59,6 +59,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] + print("Running post-merge numba montecarlo (with C close lines)!") for i in prange(len(output_nus)): if montecarlo_configuration.single_packet_seed != -1: seed = packet_seeds[montecarlo_configuration.single_packet_seed] @@ -71,7 +72,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, packet_collection.packets_input_nu[i], packet_collection.packets_input_energy[i], seed, - i) + i, 0) vpacket_collection = VPacketCollection( spectrum_frequency, montecarlo_configuration.v_packet_spawn_start_frequency, diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 1336d8ef734..8cdb53d8c7b 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -6,9 +6,10 @@ from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration from tardis.montecarlo.montecarlo_numba.r_packet import ( get_doppler_factor, get_inverse_doppler_factor, get_random_mu, - angle_aberration_CMF_to_LF) + angle_aberration_CMF_to_LF, test_for_close_line) from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom +CLOSE_LINE_THRESHOLD = 1e-7 @njit(**njit_dict) def thomson_scatter(r_packet, time_explosion): @@ -109,6 +110,11 @@ def line_emission(r_packet, emission_line_id, time_explosion, r_packet.nu = numba_plasma.line_list_nu[ emission_line_id] * inverse_doppler_factor r_packet.next_line_id = emission_line_id + 1 + nu_line = numba_plasma.line_list_nu[emission_line_id] + + if emission_line_id != (len(numba_plasma.line_list_nu) - 1): + test_for_close_line(r_packet, emission_line_id + 1, nu_line, numba_plasma) + if montecarlo_configuration.full_relativity: r_packet.mu = angle_aberration_CMF_to_LF( r_packet, diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index f51b8d6a878..f45f0d91e0d 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -40,7 +40,8 @@ class InteractionType(IntEnum): ('current_shell_id', int64), ('status', int64), ('seed', int64), - ('index', int64) + ('index', int64), + ('close_line', int64) ] @njit(**njit_dict) @@ -68,7 +69,9 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): # @log_decorator @njit(**njit_dict) -def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion): +def calculate_distance_line( + r_packet, comov_nu, + nu_last_interaction, nu_line, time_explosion): """ Parameters @@ -88,17 +91,21 @@ def calculate_distance_line(r_packet, comov_nu, nu_line, time_explosion): if nu_line == 0.0: return MISS_DISTANCE + nu_diff = comov_nu - nu_line + nu_diff_last = nu_last_interaction - nu_line # for numerical reasons, if line is too close, we set the distance to 0. - if np.abs(nu_diff / comov_nu) < CLOSE_LINE_THRESHOLD: + if r_packet.close_line > 0: nu_diff = 0.0 + r_packet.close_line = 0 if nu_diff >= 0: distance = (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion else: - raise MonteCarloException('nu difference is less than 0.0; for more' - ' information, see print statement beforehand') + print('WARNING: nu difference is less than 0.0') + #raise MonteCarloException('nu difference is less than 0.0; for more' + # ' information, see print statement beforehand') if montecarlo_configuration.full_relativity: return calculate_distance_line_full_relativity(nu_line, nu, @@ -172,7 +179,7 @@ def get_random_mu(): @jitclass(rpacket_spec) class RPacket(object): - def __init__(self, r, mu, nu, energy, seed, index=0): + def __init__(self, r, mu, nu, energy, seed, index=0, close_line=0): self.r = r self.mu = mu self.nu = nu @@ -181,6 +188,7 @@ def __init__(self, r, mu, nu, energy, seed, index=0): self.status = PacketStatus.IN_PROCESS self.seed = seed self.index = index + self.close_line = close_line def initialize_line_id(self, numba_plasma, numba_model): inverse_line_list_nu = numba_plasma.line_list_nu[::-1] @@ -285,6 +293,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) # Going through the lines nu_line = numba_plasma.line_list_nu[cur_line_id] + nu_line_last_interaction = numba_plasma.line_list_nu[cur_line_id - 1] # Getting the tau for the next line tau_trace_line = numba_plasma.tau_sobolev[ @@ -296,7 +305,9 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency distance_trace = calculate_distance_line( - r_packet, comov_nu, nu_line, numba_model.time_explosion) + r_packet, comov_nu, nu_line_last_interaction, + nu_line, numba_model.time_explosion + ) # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, @@ -307,14 +318,14 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) tau_trace_combined = tau_trace_line_combined + tau_trace_electron if ((distance_boundary <= distance_trace) and - (distance_boundary <= distance_electron)): + (distance_boundary <= distance_electron)) and distance_trace != 0.0: interaction_type = InteractionType.BOUNDARY # BOUNDARY r_packet.next_line_id = cur_line_id distance = distance_boundary break if ((distance_electron < distance_trace) and - (distance_electron < distance_boundary)): + (distance_electron < distance_boundary)) and distance_trace != 0.0: interaction_type = InteractionType.ESCATTERING # print('scattering') distance = distance_electron @@ -335,6 +346,10 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) r_packet.next_line_id = cur_line_id distance = distance_trace break + + if cur_line_id != (len(numba_plasma.line_list_nu) - 1): + test_for_close_line(r_packet, cur_line_id + 1, nu_line, numba_plasma) + # Recalculating distance_electron using tau_event - # tau_trace_line_combined distance_electron = calculate_distance_electron( @@ -355,7 +370,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) distance = distance_boundary interaction_type = InteractionType.BOUNDARY - r_packet.next_line_id = cur_line_id + #r_packet.next_line_id = cur_line_id return distance, interaction_type, delta_shell @@ -380,23 +395,7 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, time_explosion) - comov_nu = r_packet.nu * doppler_factor - comov_energy = r_packet.energy * doppler_factor - if montecarlo_configuration.full_relativity: - distance = distance * doppler_factor - set_estimators_full_relativity(r_packet, - distance, - numba_estimator, - comov_nu, - comov_energy, - doppler_factor) - else: - set_estimators(r_packet, - distance, - numba_estimator, - comov_nu, - comov_energy) r = r_packet.r if (distance > 0.0): new_r = np.sqrt(r**2 + distance**2 + @@ -404,6 +403,24 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): r_packet.mu = (r_packet.mu * r + distance) / new_r r_packet.r = new_r + comov_nu = r_packet.nu * doppler_factor + comov_energy = r_packet.energy * doppler_factor + + if montecarlo_configuration.full_relativity: + distance = distance * doppler_factor + set_estimators_full_relativity(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy, + doppler_factor) + else: + set_estimators(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy) + @njit(**njit_dict) def set_estimators(r_packet, distance, numba_estimator, comov_nu, comov_energy): numba_estimator.j_estimator[r_packet.current_shell_id] += ( @@ -499,3 +516,9 @@ def angle_aberration_LF_to_CMF(r_packet, time_explosion, mu): ct = C_SPEED_OF_LIGHT * time_explosion beta = r_packet.r /(ct) return (mu - beta) / (1.0 - beta * mu) + +@njit(**njit_dict) +def test_for_close_line(r_packet, line_id, nu_line, numba_plasma): + if (np.abs(numba_plasma.line_list_nu[line_id] - nu_line) + < (nu_line * CLOSE_LINE_THRESHOLD)): + r_packet.close_line = 1 diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 8ca25ca812d..e2cd9878517 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -19,13 +19,14 @@ ('next_line_id', int64), ('current_shell_id', int64), ('status', int64), - ('index', int64) + ('index', int64), + ('close_line', int64) ] @jitclass(vpacket_spec) class VPacket(object): def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, - index=0): + index=0, close_line=0): self.r = r self.mu = mu self.nu = nu @@ -34,7 +35,7 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, self.next_line_id = next_line_id self.status = PacketStatus.IN_PROCESS self.index = index - + self.close_line = close_line @njit(**njit_dict) @@ -69,11 +70,15 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, break nu_line = numba_plasma.line_list_nu[cur_line_id] + # TODO: Check if this is what the C code does + nu_line_last_interaction = numba_plasma.line_list_nu[cur_line_id - 1] tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, v_packet.current_shell_id] distance_trace_line = calculate_distance_line( - v_packet, comov_nu, nu_line, numba_model.time_explosion) + v_packet, comov_nu, nu_line_last_interaction, + nu_line, numba_model.time_explosion + ) if distance_boundary <= distance_trace_line: break @@ -196,7 +201,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, v_packet_energy, r_packet.current_shell_id, - r_packet.next_line_id, i) + r_packet.next_line_id, i, 0) tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson) From 3eb8e8f5a8f413819913bdc1423ce1067357e8e4 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Thu, 1 Oct 2020 15:12:01 -0400 Subject: [PATCH 062/116] Vpacket updates --- tardis/montecarlo/montecarlo_configuration.py | 2 + tardis/montecarlo/montecarlo_numba/vpacket.py | 39 +++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py index 62cceb79deb..0950caf7821 100644 --- a/tardis/montecarlo/montecarlo_configuration.py +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -9,3 +9,5 @@ packet_seeds = [] disable_electron_scattering = False disable_line_scattering = False +survival_probability = 0.0 +tau_russian = 10.0 diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e2cd9878517..5f0bcfa4dcb 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -9,7 +9,7 @@ from tardis.montecarlo.montecarlo_numba.r_packet import ( calculate_distance_boundary, get_doppler_factor, calculate_distance_line, calculate_tau_electron, PacketStatus, move_packet_across_shell_boundary, - angle_aberration_LF_to_CMF, angle_aberration_CMF_to_LF) + angle_aberration_LF_to_CMF, angle_aberration_CMF_to_LF, test_for_close_line) vpacket_spec = [ ('r', float64), @@ -66,9 +66,9 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, cur_line_id = start_line_id for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): - if tau_trace_combined > 10: ### FIXME ????? - break - + #if tau_trace_combined > 10: ### FIXME ????? + # break + nu_line = numba_plasma.line_list_nu[cur_line_id] # TODO: Check if this is what the C code does nu_line_last_interaction = numba_plasma.line_list_nu[cur_line_id - 1] @@ -84,8 +84,9 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, break tau_trace_combined += tau_trace_line - - + + if cur_line_id != (len(numba_plasma.line_list_nu) - 1): + test_for_close_line(v_packet, cur_line_id + 1, numba_plasma.line_list_nu[cur_line_id], numba_plasma) else: if cur_line_id == (len(numba_plasma.line_list_nu) - 1): @@ -115,18 +116,28 @@ def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): v_packet, numba_model, numba_plasma, sigma_thomson ) tau_trace_combined += tau_trace_combined_shell - if tau_trace_combined > 10: - break + move_packet_across_shell_boundary(v_packet, delta_shell, len(numba_model.r_inner)) - if v_packet.status == PacketStatus.EMITTED: - break + if tau_trace_combined > montecarlo_configuration.tau_russian: + event_random = np.random.random() + if event_random > montecarlo_configuration.survival_probability: + v_packet.energy = 0.0 + v_packet.status = PacketStatus.EMITTED + else: + v_packet.energy = v_packet.energy / montecarlo_configuration.survival_probability * \ + np.exp(-tau_trace_combined) + tau_trace_combined = 0.0 + # Moving the v_packet new_r = np.sqrt(v_packet.r**2 + distance_boundary**2 + 2.0 * v_packet.r * distance_boundary * v_packet.mu) v_packet.mu = (v_packet.mu * v_packet.r + distance_boundary) / new_r v_packet.r = new_r + + if v_packet.status == PacketStatus.EMITTED: + break return tau_trace_combined @njit(**njit_dict) @@ -201,12 +212,18 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, v_packet_energy, r_packet.current_shell_id, - r_packet.next_line_id, i, 0) + r_packet.next_line_id, i, r_packet.close_line) + + if r_packet.next_line_id != (len(numba_plasma.line_list_nu) - 1): + test_for_close_line(v_packet, r_packet.next_line_id + 1, numba_plasma.line_list_nu[r_packet.next_line_id], numba_plasma) tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson) v_packet.energy *= np.exp(-tau_vpacket) + + print(r_packet.index) + print(i) vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy From ca1f528a06c6ee6df44e2c75758ee834bcbd9e10 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Thu, 1 Oct 2020 15:16:53 -0400 Subject: [PATCH 063/116] Vpacket print removed --- tardis/montecarlo/montecarlo_numba/vpacket.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 5f0bcfa4dcb..e7a71c331e3 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -221,9 +221,6 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, sigma_thomson) v_packet.energy *= np.exp(-tau_vpacket) - - print(r_packet.index) - print(i) vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy From 705be39844727606ef3e6440e9fb92669002b324 Mon Sep 17 00:00:00 2001 From: Christian Vogl Date: Mon, 5 Oct 2020 18:46:18 +0200 Subject: [PATCH 064/116] Re-add C formal integral (#1309) Contains a fuckton of overhead! --- tardis/montecarlo/formal_integral.py | 4 +- tardis/montecarlo/montecarlo.pyx | 316 ++++ tardis/montecarlo/setup_package.py | 44 +- tardis/montecarlo/src/abbrev.h | 24 + tardis/montecarlo/src/cmontecarlo.c | 1291 +++++++++++++++++ tardis/montecarlo/src/cmontecarlo.h | 159 ++ tardis/montecarlo/src/integrator.c | 344 +++++ tardis/montecarlo/src/integrator.h | 19 + tardis/montecarlo/src/io.h | 32 + tardis/montecarlo/src/omp_helper.h | 7 + tardis/montecarlo/src/randomkit/LICENSE | 20 + tardis/montecarlo/src/randomkit/randomkit.h | 42 + tardis/montecarlo/src/randomkit/rk_isaac.c | 258 ++++ tardis/montecarlo/src/randomkit/rk_isaac.h | 142 ++ tardis/montecarlo/src/randomkit/rk_mt.c | 342 +++++ tardis/montecarlo/src/randomkit/rk_mt.h | 194 +++ .../montecarlo/src/randomkit/rk_primitive.c | 520 +++++++ .../montecarlo/src/randomkit/rk_primitive.h | 54 + tardis/montecarlo/src/randomkit/rk_sobol.c | 991 +++++++++++++ tardis/montecarlo/src/randomkit/rk_sobol.h | 173 +++ tardis/montecarlo/src/rpacket.c | 55 + tardis/montecarlo/src/rpacket.h | 330 +++++ tardis/montecarlo/src/status.h | 38 + tardis/montecarlo/src/storage.h | 102 ++ 24 files changed, 5477 insertions(+), 24 deletions(-) create mode 100644 tardis/montecarlo/montecarlo.pyx create mode 100644 tardis/montecarlo/src/abbrev.h create mode 100644 tardis/montecarlo/src/cmontecarlo.c create mode 100644 tardis/montecarlo/src/cmontecarlo.h create mode 100644 tardis/montecarlo/src/integrator.c create mode 100644 tardis/montecarlo/src/integrator.h create mode 100644 tardis/montecarlo/src/io.h create mode 100644 tardis/montecarlo/src/omp_helper.h create mode 100644 tardis/montecarlo/src/randomkit/LICENSE create mode 100644 tardis/montecarlo/src/randomkit/randomkit.h create mode 100644 tardis/montecarlo/src/randomkit/rk_isaac.c create mode 100644 tardis/montecarlo/src/randomkit/rk_isaac.h create mode 100644 tardis/montecarlo/src/randomkit/rk_mt.c create mode 100644 tardis/montecarlo/src/randomkit/rk_mt.h create mode 100644 tardis/montecarlo/src/randomkit/rk_primitive.c create mode 100644 tardis/montecarlo/src/randomkit/rk_primitive.h create mode 100644 tardis/montecarlo/src/randomkit/rk_sobol.c create mode 100644 tardis/montecarlo/src/randomkit/rk_sobol.h create mode 100644 tardis/montecarlo/src/rpacket.c create mode 100644 tardis/montecarlo/src/rpacket.h create mode 100644 tardis/montecarlo/src/status.h create mode 100644 tardis/montecarlo/src/storage.h diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index 872e0410e2c..fe9f82a90de 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -10,7 +10,7 @@ from tardis.montecarlo.montecarlo_numba import njit_dict -# from tardis.montecarlo.montecarlo import formal_integral +from tardis.montecarlo.montecarlo import formal_integral from tardis.montecarlo.spectrum import TARDISSpectrum C_INV = 3.33564e-11 @@ -534,4 +534,4 @@ def intensity_black_body(nu, T): def calculate_p_values(R_max, N, opp): for i in range(N): opp[i] = R_max / (N - 1) * (i) - return opp \ No newline at end of file + return opp diff --git a/tardis/montecarlo/montecarlo.pyx b/tardis/montecarlo/montecarlo.pyx new file mode 100644 index 00000000000..4b398c60425 --- /dev/null +++ b/tardis/montecarlo/montecarlo.pyx @@ -0,0 +1,316 @@ +# cython: profile=False +# cython: boundscheck=False +# cython: wraparound=False +# cython: cdivision=True +# cython: cdivision=True +# cython: language_level=3 + + + +import numpy as np +cimport numpy as np +from numpy cimport PyArray_DATA +from tardis import constants +from astropy import units +from libc.stdlib cimport free + +np.import_array() + + + +ctypedef np.int64_t int_type_t + +cdef extern from "numpy/arrayobject.h": + void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) + +cdef c_array_to_numpy(void *ptr, int dtype, np.npy_intp N): + cdef np.ndarray arr = np.PyArray_SimpleNewFromData(1, &N, dtype, ptr) + PyArray_ENABLEFLAGS(arr, np.NPY_OWNDATA) + return arr + +cdef extern from "src/cmontecarlo.h": + ctypedef enum ContinuumProcessesStatus: + CONTINUUM_OFF = 0 + CONTINUUM_ON = 1 + + cdef int LOG_VPACKETS + + ctypedef struct photo_xsect_1level: + double *nu + double *x_sect + int_type_t no_of_points + + ctypedef struct storage_model_t: + double *packet_nus + double *packet_mus + double *packet_energies + double *output_nus + double *output_energies + double *last_interaction_in_nu + int_type_t *last_line_interaction_in_id + int_type_t *last_line_interaction_out_id + int_type_t *last_line_interaction_shell_id + int_type_t *last_interaction_type + int_type_t *last_interaction_out_type + int_type_t no_of_packets + int_type_t no_of_shells + int_type_t no_of_shells_i + double *r_inner + double *r_outer + double *r_inner_i + double *r_outer_i + double *v_inner + double time_explosion + double inverse_time_explosion + double *electron_densities + double *electron_densities_i + double *inverse_electron_densities + double *line_list_nu + double *line_lists_tau_sobolevs + double *line_lists_tau_sobolevs_i + double *continuum_list_nu + int_type_t line_lists_tau_sobolevs_nd + double *line_lists_j_blues + double *line_lists_Edotlu + int_type_t line_lists_j_blues_nd + int_type_t no_of_lines + int_type_t no_of_edges + int_type_t line_interaction_id + double *transition_probabilities + int_type_t transition_probabilities_nd + int_type_t *line2macro_level_upper + int_type_t *macro_block_references + int_type_t *transition_type + int_type_t *destination_level_id + int_type_t *transition_line_id + double *js + double *nubars + double spectrum_virt_start_nu + double spectrum_virt_end_nu + double spectrum_start_nu + double spectrum_delta_nu + double spectrum_end_nu + double *spectrum_virt_nu + double sigma_thomson + double inverse_sigma_thomson + double inner_boundary_albedo + int_type_t reflective_inner_boundary + photo_xsect_1level ** photo_xsect + double *chi_ff_factor + double *t_electrons + double *l_pop + double *l_pop_r + ContinuumProcessesStatus cont_status + double *virt_packet_nus + double *virt_packet_energies + double *virt_packet_last_interaction_in_nu + int_type_t *virt_packet_last_interaction_type + int_type_t *virt_packet_last_line_interaction_in_id + int_type_t *virt_packet_last_line_interaction_out_id + int_type_t virt_packet_count + int_type_t virt_array_size + int_type_t kpacket2macro_level + int_type_t *cont_edge2macro_level + double *photo_ion_estimator + double *stim_recomb_estimator + int_type_t *photo_ion_estimator_statistics + double *bf_heating_estimator + double *ff_heating_estimator + double *stim_recomb_cooling_estimator + int full_relativity + double survival_probability + double tau_russian + double *tau_bias + int enable_biasing + + +cdef extern from "src/integrator.h": + double *_formal_integral( + const storage_model_t *storage, + double T, + double *nu, + int_type_t nu_size, + double *att_S_ul, + double *Jred_lu, + double *Jblue_lu, + int N) + + + +cdef initialize_storage_model(model, plasma, runner, storage_model_t *storage): + """ + Initializing the storage struct. + + """ + + storage.no_of_packets = runner.input_nu.size + storage.packet_nus = PyArray_DATA(runner.input_nu) + storage.packet_mus = PyArray_DATA(runner.input_mu) + storage.packet_energies = PyArray_DATA(runner.input_energy) + + # Setup of structure + storage.no_of_shells = model.no_of_shells + + + storage.r_inner = PyArray_DATA(runner.r_inner_cgs) + storage.r_outer = PyArray_DATA(runner.r_outer_cgs) + storage.v_inner = PyArray_DATA(runner.v_inner_cgs) + + # Setup the rest + # times + storage.time_explosion = model.time_explosion.to('s').value + storage.inverse_time_explosion = 1.0 / storage.time_explosion + #electron density + storage.electron_densities = PyArray_DATA( + plasma.electron_densities.values) + + runner.inverse_electron_densities = ( + 1.0 / plasma.electron_densities.values) + storage.inverse_electron_densities = PyArray_DATA( + runner.inverse_electron_densities) + # Switch for continuum processes + storage.cont_status = CONTINUUM_OFF + # Continuum data + cdef np.ndarray[double, ndim=1] continuum_list_nu + cdef np.ndarray[double, ndim=1] l_pop + cdef np.ndarray[double, ndim=1] l_pop_r + + if storage.cont_status == CONTINUUM_ON: + continuum_list_nu = np.array([9.0e14, 8.223e14, 6.0e14, 3.5e14, 3.0e14]) # sorted list of threshold frequencies + storage.continuum_list_nu = continuum_list_nu.data + storage.no_of_edges = continuum_list_nu.size + l_pop = np.ones(storage.no_of_shells * continuum_list_nu.size, dtype=np.float64) + storage.l_pop = l_pop.data + l_pop_r = np.ones(storage.no_of_shells * continuum_list_nu.size, dtype=np.float64) + storage.l_pop_r = l_pop_r.data + + # Line lists + storage.no_of_lines = plasma.atomic_data.lines.nu.values.size + storage.line_list_nu = PyArray_DATA(plasma.atomic_data.lines.nu.values) + runner.line_lists_tau_sobolevs = ( + plasma.tau_sobolevs.values.flatten(order='F') + ) + storage.line_lists_tau_sobolevs = PyArray_DATA( + runner.line_lists_tau_sobolevs + ) + storage.line_lists_j_blues = PyArray_DATA( + runner.j_blue_estimator) + + storage.line_lists_Edotlu = PyArray_DATA( + runner.Edotlu_estimator) + + storage.line_interaction_id = runner.get_line_interaction_id( + runner.line_interaction_type) + + # macro atom & downbranch + if storage.line_interaction_id >= 1: + runner.transition_probabilities = ( + plasma.transition_probabilities.values.flatten(order='F') + ) + storage.transition_probabilities = PyArray_DATA( + runner.transition_probabilities + ) + storage.transition_probabilities_nd = ( + plasma.transition_probabilities.values.shape[0]) + storage.line2macro_level_upper = PyArray_DATA( + plasma.atomic_data.lines_upper2macro_reference_idx) + storage.macro_block_references = PyArray_DATA( + plasma.atomic_data.macro_atom_references['block_references'].values) + storage.transition_type = PyArray_DATA( + plasma.atomic_data.macro_atom_data['transition_type'].values) + + # Destination level is not needed and/or generated for downbranch + storage.destination_level_id = PyArray_DATA( + plasma.atomic_data.macro_atom_data['destination_level_idx'].values) + storage.transition_line_id = PyArray_DATA( + plasma.atomic_data.macro_atom_data['lines_idx'].values) + + storage.output_nus = PyArray_DATA(runner._output_nu) + storage.output_energies = PyArray_DATA(runner._output_energy) + + storage.last_line_interaction_in_id = PyArray_DATA( + runner.last_line_interaction_in_id) + storage.last_line_interaction_out_id = PyArray_DATA( + runner.last_line_interaction_out_id) + storage.last_line_interaction_shell_id = PyArray_DATA( + runner.last_line_interaction_shell_id) + storage.last_interaction_type = PyArray_DATA( + runner.last_interaction_type) + storage.last_interaction_in_nu = PyArray_DATA( + runner.last_interaction_in_nu) + + storage.js = PyArray_DATA(runner.j_estimator) + storage.nubars = PyArray_DATA(runner.nu_bar_estimator) + + storage.spectrum_start_nu = runner.spectrum_frequency.to('Hz').value.min() + storage.spectrum_end_nu = runner.spectrum_frequency.to('Hz').value.max() + # TODO: Linspace handling for virtual_spectrum_range + storage.spectrum_virt_start_nu = runner.virtual_spectrum_spawn_range.end.to('Hz', units.spectral()).value + storage.spectrum_virt_end_nu = runner.virtual_spectrum_spawn_range.start.to('Hz', units.spectral()).value + storage.spectrum_delta_nu = runner.spectrum_frequency.to('Hz').value[1] - runner.spectrum_frequency.to('Hz').value[0] + + storage.spectrum_virt_nu = PyArray_DATA( + runner._montecarlo_virtual_luminosity.value) + + storage.sigma_thomson = runner.sigma_thomson + storage.inverse_sigma_thomson = 1.0 / storage.sigma_thomson + storage.reflective_inner_boundary = runner.enable_reflective_inner_boundary + storage.inner_boundary_albedo = runner.inner_boundary_albedo + storage.full_relativity = runner.enable_full_relativity + + storage.tau_russian = runner.v_packet_settings['tau_russian'] + storage.survival_probability = runner.v_packet_settings['survival_probability'] + storage.enable_biasing = runner.v_packet_settings['enable_biasing'] + + if runner.v_packet_settings['enable_biasing']: + # Calculate the integrated electron scattering optical depth + # at all cell interfaces. + runner.tau_bias = np.zeros(len(runner.r_inner_cgs) + 1) + runner.tau_bias[:-1] = ( + ((runner.r_outer_cgs - runner.r_inner_cgs) * + plasma.electron_densities.values * + runner.sigma_thomson)[::-1].cumsum()[::-1] + ) + storage.tau_bias = PyArray_DATA(runner.tau_bias) + + # Data for continuum implementation + cdef np.ndarray[double, ndim=1] t_electrons = plasma.t_electrons + storage.t_electrons = t_electrons.data + + +# This will be a method of the Simulation object +def formal_integral(self, nu, N): + cdef storage_model_t storage + + initialize_storage_model(self.model, self.plasma, self.runner, &storage) + + res = self.make_source_function() + + storage.no_of_shells_i = len(self.runner.r_inner_i) + storage.r_inner_i = PyArray_DATA(self.runner.r_inner_i) + storage.r_outer_i = PyArray_DATA(self.runner.r_outer_i) + + storage.electron_densities_i = PyArray_DATA( + self.runner.electron_densities_integ) + self.runner.line_lists_tau_sobolevs_i = ( + self.runner.tau_sobolevs_integ.flatten(order='F') + ) + storage.line_lists_tau_sobolevs_i = PyArray_DATA( + self.runner.line_lists_tau_sobolevs_i + ) + + att_S_ul = res[0].flatten(order='F') + Jred_lu = res[1].flatten(order='F') + Jblue_lu = res[2].flatten(order='F') + + cdef double *L = _formal_integral( + &storage, + self.model.t_inner.value, + PyArray_DATA(nu), + nu.shape[0], + PyArray_DATA(att_S_ul), + PyArray_DATA(Jred_lu), + PyArray_DATA(Jblue_lu), + N + ) + return c_array_to_numpy(L, np.NPY_DOUBLE, nu.shape[0]) diff --git a/tardis/montecarlo/setup_package.py b/tardis/montecarlo/setup_package.py index bfe45232e6b..6f0a2e58bc2 100644 --- a/tardis/montecarlo/setup_package.py +++ b/tardis/montecarlo/setup_package.py @@ -2,7 +2,7 @@ from setuptools import Extension import os from astropy_helpers.distutils_helpers import get_distutils_option -# from Cython.Build import cythonize +from Cython.Build import cythonize from glob import glob @@ -20,27 +20,27 @@ if get_distutils_option('with_vpacket_logging', ['build', 'install', 'develop']) is not None: define_macros.append(('WITH_VPACKET_LOGGING', None)) -# def get_extensions(): -# sources = ['tardis/montecarlo/montecarlo.pyx'] -# sources += [os.path.relpath(fname) for fname in glob( -# os.path.join(os.path.dirname(__file__), 'src', '*.c'))] -# sources += [os.path.relpath(fname) for fname in glob( -# os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.c'))] -# deps = [os.path.relpath(fname) for fname in glob( -# os.path.join(os.path.dirname(__file__), 'src', '*.h'))] -# deps += [os.path.relpath(fname) for fname in glob( -# os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.h'))] - - # return cythonize( - # Extension('tardis.montecarlo.montecarlo', sources, - # include_dirs=['tardis/montecarlo/src', - # 'tardis/montecarlo/src/randomkit', - # 'numpy'], - # depends=deps, - # extra_compile_args=compile_args, - # extra_link_args=link_args, - # define_macros=define_macros) - # ) +def get_extensions(): + sources = ['tardis/montecarlo/montecarlo.pyx'] + sources += [os.path.relpath(fname) for fname in glob( + os.path.join(os.path.dirname(__file__), 'src', '*.c'))] + sources += [os.path.relpath(fname) for fname in glob( + os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.c'))] + deps = [os.path.relpath(fname) for fname in glob( + os.path.join(os.path.dirname(__file__), 'src', '*.h'))] + deps += [os.path.relpath(fname) for fname in glob( + os.path.join(os.path.dirname(__file__), 'src/randomkit', '*.h'))] + + return cythonize( + Extension('tardis.montecarlo.montecarlo', sources, + include_dirs=['tardis/montecarlo/src', + 'tardis/montecarlo/src/randomkit', + 'numpy'], + depends=deps, + extra_compile_args=compile_args, + extra_link_args=link_args, + define_macros=define_macros) + ) def get_package_data(): diff --git a/tardis/montecarlo/src/abbrev.h b/tardis/montecarlo/src/abbrev.h new file mode 100644 index 00000000000..fff7d07036c --- /dev/null +++ b/tardis/montecarlo/src/abbrev.h @@ -0,0 +1,24 @@ +#ifndef ABBREV_H +#define ABBREV_H + +#include + +/** + * @brief safe malloc; checks for NULL and aborts if encountered + */ +static inline void* safe_malloc(size_t size) { + void *mem = malloc(size); + if (mem == NULL && size != 0) abort(); + return mem; +} + +/** + * @brief safe realloc; checks for NULL and aborts if encountered + */ +static inline void* safe_realloc(void *ptr, size_t size) { + void *mem = realloc(ptr, size); + if (mem == NULL && size != 0) abort(); + return mem; +} + +#endif /* ABBREV_H */ diff --git a/tardis/montecarlo/src/cmontecarlo.c b/tardis/montecarlo/src/cmontecarlo.c new file mode 100644 index 00000000000..41441d8d363 --- /dev/null +++ b/tardis/montecarlo/src/cmontecarlo.c @@ -0,0 +1,1291 @@ + +#include +#include +#include +#include +#ifdef WITHOPENMP +#include +#endif +#include "io.h" +#include "abbrev.h" +#include "status.h" +#include "rpacket.h" +#include "cmontecarlo.h" + + +/** Look for a place to insert a value in an inversely sorted float array. + * + * @param x an inversely (largest to lowest) sorted float array + * @param x_insert a value to insert + * @param imin lower bound + * @param imax upper bound + * + * @return index of the next boundary to the left + */ +tardis_error_t +reverse_binary_search (const double *x, double x_insert, + int64_t imin, int64_t imax, int64_t * result) +{ + /* + Have in mind that *x points to a reverse sorted array. + That is large values will have small indices and small ones + will have large indices. + */ + tardis_error_t ret_val = TARDIS_ERROR_OK; + if (x_insert > x[imin] || x_insert < x[imax]) + { + ret_val = TARDIS_ERROR_BOUNDS_ERROR; + } + else + { + int imid = (imin + imax) >> 1; + while (imax - imin > 2) + { + if (x[imid] < x_insert) + { + imax = imid + 1; + } + else + { + imin = imid; + } + imid = (imin + imax) >> 1; + } + if (imax - imin == 2 && x_insert < x[imin + 1]) + { + *result = imin + 1; + } + else + { + *result = imin; + } + } + return ret_val; +} + +/** Insert a value in to an array of line frequencies + * + * @param nu array of line frequencies + * @param nu_insert value of nu key + * @param number_of_lines number of lines in the line list + * + * @return index of the next line ot the red. If the key value is redder than the reddest line returns number_of_lines. + */ +tardis_error_t +line_search (const double *nu, double nu_insert, int64_t number_of_lines, + int64_t * result) +{ + tardis_error_t ret_val = TARDIS_ERROR_OK; + int64_t imin = 0; + int64_t imax = number_of_lines - 1; + if (nu_insert > nu[imin]) + { + *result = imin; + } + else if (nu_insert < nu[imax]) + { + *result = imax + 1; + } + else + { + ret_val = reverse_binary_search (nu, nu_insert, imin, imax, result); + *result = *result + 1; + } + return ret_val; +} + +tardis_error_t +binary_search (const double *x, double x_insert, int64_t imin, + int64_t imax, int64_t * result) +{ + /* + Have in mind that *x points to a sorted array. + Like [1,2,3,4,5,...] + */ + int imid; + tardis_error_t ret_val = TARDIS_ERROR_OK; + if (x_insert < x[imin] || x_insert > x[imax]) + { + ret_val = TARDIS_ERROR_BOUNDS_ERROR; + } + else + { + while (imax >= imin) + { + imid = (imin + imax) / 2; + if (x[imid] == x_insert) + { + *result = imid; + break; + } + else if (x[imid] < x_insert) + { + imin = imid + 1; + } + else + { + imax = imid - 1; + } + } + if (imax - imid == 2 && x_insert < x[imin + 1]) + { + *result = imin; + } + else + { + *result = imin; + } + } + return ret_val; +} + +void +angle_aberration_CMF_to_LF (rpacket_t *packet, const storage_model_t *storage) +{ + if (storage->full_relativity) + { + double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; + double mu_0 = rpacket_get_mu (packet); + rpacket_set_mu (packet, (mu_0 + beta) / (1.0 + beta * mu_0)); + } +} + +/** Transform the lab frame direction cosine to the CMF + * + * @param packet + * @param storage + * @param mu lab frame direction cosine + * + * @return CMF direction cosine + */ +double +angle_aberration_LF_to_CMF (rpacket_t *packet, const storage_model_t *storage, double mu) +{ + double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; + return (mu - beta) / (1.0 - beta * mu); +} + +double +rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) +{ + double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; + if (!storage->full_relativity) + { + return 1.0 - rpacket_get_mu (packet) * beta; + } + else + { + return (1.0 - rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); + } +} + +double +rpacket_inverse_doppler_factor (const rpacket_t *packet, const storage_model_t *storage) +{ + double beta = rpacket_get_r (packet) * storage->inverse_time_explosion * INVERSE_C; + if (!storage->full_relativity) + { + return 1.0 / (1.0 - rpacket_get_mu (packet) * beta); + } + else + { + return (1.0 + rpacket_get_mu (packet) * beta) / sqrt (1 - beta * beta); + } +} + +double +bf_cross_section (const storage_model_t * storage, int64_t continuum_id, double comov_nu) +{ + double bf_xsect; + double *x_sect = storage->photo_xsect[continuum_id]->x_sect; + double *nu = storage->photo_xsect[continuum_id]->nu; + + switch (storage->bf_treatment) + { + case LIN_INTERPOLATION: + { + int64_t result; + tardis_error_t error = binary_search (nu, comov_nu, 0, + storage->photo_xsect[continuum_id]->no_of_points - 1, &result); + if (error == TARDIS_ERROR_BOUNDS_ERROR) + { + bf_xsect = 0.0; + } + else + { + bf_xsect = x_sect[result-1] + (comov_nu - nu[result-1]) / (nu[result] - nu[result-1]) + * (x_sect[result] - x_sect[result-1]); + } + break; + } + + case HYDROGENIC: + { + double nu_ratio = nu[0] / comov_nu; + bf_xsect = x_sect[0] * nu_ratio * nu_ratio * nu_ratio; + break; + } + + default: + fprintf (stderr, "(%d) is not a valid bound-free cross section treatment.\n", storage->bf_treatment); + exit(1); + } + return bf_xsect; +} + +void calculate_chi_bf (rpacket_t * packet, storage_model_t * storage) +{ + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + + int64_t no_of_continuum_edges = storage->no_of_edges; + int64_t current_continuum_id; + line_search(storage->continuum_list_nu, comov_nu, no_of_continuum_edges, ¤t_continuum_id); + rpacket_set_current_continuum_id (packet, current_continuum_id); + + int64_t shell_id = rpacket_get_current_shell_id (packet); + double T = storage->t_electrons[shell_id]; + double boltzmann_factor = exp (-(H * comov_nu) / (KB * T)); + + double bf_helper = 0; + for(int64_t i = current_continuum_id; i < no_of_continuum_edges; i++) + { + // get the level population for the level ijk in the current shell: + double l_pop = storage->l_pop[shell_id * no_of_continuum_edges + i]; + // get the level population ratio \frac{n_{0,j+1,k}}{n_{i,j,k}} \frac{n_{i,j,k}}{n_{0,j+1,k}}^{*}: + double l_pop_r = storage->l_pop_r[shell_id * no_of_continuum_edges + i]; + double bf_x_sect = bf_cross_section (storage, i, comov_nu); + if (bf_x_sect == 0.0) + { + break; + } + bf_helper += l_pop * bf_x_sect * (1.0 - l_pop_r * boltzmann_factor) * doppler_factor; + + packet->chi_bf_tmp_partial[i] = bf_helper; + } + + rpacket_set_chi_boundfree (packet, bf_helper); +} + +void calculate_chi_ff (rpacket_t * packet, const storage_model_t * storage) +{ + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + int64_t shell_id = rpacket_get_current_shell_id (packet); + double T = storage->t_electrons[shell_id]; + double boltzmann_factor = exp (-(H * comov_nu) / KB / T); + double chi_ff_factor = storage->chi_ff_factor[shell_id]; + + double chi_ff = chi_ff_factor * (1 - boltzmann_factor) * pow (comov_nu, -3); + + rpacket_set_chi_freefree (packet, chi_ff * doppler_factor); +} + +void +compute_distance2boundary (rpacket_t * packet, const storage_model_t * storage) +{ + double r = rpacket_get_r (packet); + double mu = rpacket_get_mu (packet); + double r_outer = storage->r_outer[rpacket_get_current_shell_id (packet)]; + double r_inner = storage->r_inner[rpacket_get_current_shell_id (packet)]; + double check, distance; + if (mu > 0.0) + { // direction outward + rpacket_set_next_shell_id (packet, 1); + distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); + } + else + { // going inward + if ( (check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) )>= 0.0) + { // hit inner boundary + rpacket_set_next_shell_id (packet, -1); + distance = - r * mu - sqrt (check); + } + else + { // miss inner boundary + rpacket_set_next_shell_id (packet, 1); + distance = sqrt (r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu); + } + } + rpacket_set_d_boundary (packet, distance); +} + +tardis_error_t +compute_distance2line (rpacket_t * packet, const storage_model_t * storage) +{ + if (!rpacket_get_last_line (packet)) + { + double r = rpacket_get_r (packet); + double mu = rpacket_get_mu (packet); + double nu = rpacket_get_nu (packet); + double nu_line = rpacket_get_nu_line (packet); + double distance, nu_diff; + double ct = storage->time_explosion * C; + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = nu * doppler_factor; + if ( (nu_diff = comov_nu - nu_line) >= 0) + { + if (!storage->full_relativity) + { + distance = (nu_diff / nu) * ct; + } + else + { + double nu_r = nu_line / nu; + distance = - mu * r + (ct - nu_r * nu_r * sqrt(ct * ct - + (1 + r * r * (1 - mu * mu) * (1 + pow (nu_r, -2))))) / (1 + nu_r * nu_r); + } + rpacket_set_d_line (packet, distance); + return TARDIS_ERROR_OK; + } + else + { + if (rpacket_get_next_line_id (packet) == storage->no_of_lines - 1) + { + fprintf (stderr, "last_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) - 1]); + fprintf (stderr, "Last line in line list reached!"); + } + else if (rpacket_get_next_line_id (packet) == 0) + { + fprintf (stderr, "First line in line list!"); + fprintf (stderr, "next_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) + 1]); + } + else + { + fprintf (stderr, "last_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) - 1]); + fprintf (stderr, "next_line = %f\n", + storage-> + line_list_nu[rpacket_get_next_line_id (packet) + 1]); + } + fprintf (stderr, "ERROR: Comoving nu less than nu_line!\n"); + fprintf (stderr, "comov_nu = %f\n", comov_nu); + fprintf (stderr, "nu_line = %f\n", nu_line); + fprintf (stderr, "(comov_nu - nu_line) / nu_line = %f\n", + (comov_nu - nu_line) / nu_line); + fprintf (stderr, "r = %f\n", r); + fprintf (stderr, "mu = %f\n", mu); + fprintf (stderr, "nu = %f\n", nu); + fprintf (stderr, "doppler_factor = %f\n", doppler_factor); + fprintf (stderr, "cur_zone_id = %" PRIi64 "\n", rpacket_get_current_shell_id (packet)); + return TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE; + } + } + else + { + rpacket_set_d_line (packet, MISS_DISTANCE); + return TARDIS_ERROR_OK; + } +} + +void +compute_distance2continuum (rpacket_t * packet, storage_model_t * storage) +{ + double chi_continuum, d_continuum; + double chi_electron = storage->electron_densities[rpacket_get_current_shell_id(packet)] * + storage->sigma_thomson; + if (storage->full_relativity) + { + chi_electron *= rpacket_doppler_factor (packet, storage); + } + + if (storage->cont_status == CONTINUUM_ON) + { + if (packet->compute_chi_bf) + { + calculate_chi_bf (packet, storage); + calculate_chi_ff (packet, storage); + } + else + { + packet->compute_chi_bf=true; + } + chi_continuum = rpacket_get_chi_boundfree (packet) + rpacket_get_chi_freefree (packet) + chi_electron; + d_continuum = rpacket_get_tau_event (packet) / chi_continuum; + } + else + { + chi_continuum = chi_electron; + d_continuum = storage->inverse_electron_densities[rpacket_get_current_shell_id (packet)] * + storage->inverse_sigma_thomson * rpacket_get_tau_event (packet); + } + + if (rpacket_get_virtual_packet(packet) > 0) + { + //Set all continuum distances to MISS_DISTANCE in case of an virtual_packet + d_continuum = MISS_DISTANCE; + packet->compute_chi_bf = false; + } + else + { + + // fprintf(stderr, "--------\n"); + // fprintf(stderr, "nu = %e \n", rpacket_get_nu(packet)); + // fprintf(stderr, "chi_electron = %e\n", chi_electron); + // fprintf(stderr, "chi_boundfree = %e\n", calculate_chi_bf(packet, storage)); + // fprintf(stderr, "chi_line = %e \n", rpacket_get_tau_event(packet) / rpacket_get_d_line(packet)); + // fprintf(stderr, "--------\n"); + + //rpacket_set_chi_freefree(packet, chi_freefree); + rpacket_set_chi_electron (packet, chi_electron); + } + rpacket_set_chi_continuum (packet, chi_continuum); + rpacket_set_d_continuum (packet, d_continuum); +} + +void +macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) +{ + int emit = 0, i = 0, offset = -1; + uint64_t activate_level = rpacket_get_macro_atom_activation_level (packet); + while (emit >= 0) + { + double event_random = rk_double (mt_state); + i = storage->macro_block_references[activate_level] - 1; + double p = 0.0; + offset = storage->transition_probabilities_nd * + rpacket_get_current_shell_id (packet); + do + { + ++i; + p += storage->transition_probabilities[offset + i]; + } + while (p <= event_random); + emit = storage->transition_type[i]; + activate_level = storage->destination_level_id[i]; + } + switch (emit) + { + case BB_EMISSION: + line_emission (packet, storage, storage->transition_line_id[i], mt_state); + break; + + case BF_EMISSION: + rpacket_set_current_continuum_id (packet, storage->transition_line_id[i]); + storage->last_line_interaction_out_id[rpacket_get_id (packet)] = + rpacket_get_current_continuum_id (packet); + + continuum_emission (packet, storage, mt_state, sample_nu_free_bound, 3); + break; + + case FF_EMISSION: + continuum_emission (packet, storage, mt_state, sample_nu_free_free, 4); + break; + + case ADIABATIC_COOLING: + storage->last_interaction_type[rpacket_get_id (packet)] = 5; + rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); + break; + + default: + fprintf (stderr, "This process for macro-atom deactivation should not exist! (emit = %d)\n", emit); + exit(1); + } +} + +void +move_packet (rpacket_t * packet, storage_model_t * storage, double distance) +{ + double doppler_factor = rpacket_doppler_factor (packet, storage); + if (distance > 0.0) + { + double r = rpacket_get_r (packet); + double new_r = + sqrt (r * r + distance * distance + + 2.0 * r * distance * rpacket_get_mu (packet)); + rpacket_set_mu (packet, + (rpacket_get_mu (packet) * r + distance) / new_r); + rpacket_set_r (packet, new_r); + if (rpacket_get_virtual_packet (packet) <= 0) + { + double comov_energy = rpacket_get_energy (packet) * doppler_factor; + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + if (storage->full_relativity) + { + distance *= doppler_factor; + } +#ifdef WITHOPENMP +#pragma omp atomic +#endif + storage->js[rpacket_get_current_shell_id (packet)] += + comov_energy * distance; +#ifdef WITHOPENMP +#pragma omp atomic +#endif + storage->nubars[rpacket_get_current_shell_id (packet)] += + comov_energy * distance * comov_nu; + + if (storage->cont_status) + { + increment_continuum_estimators(packet, storage, distance, comov_nu, comov_energy); + } + } + } +} + +void +increment_continuum_estimators (const rpacket_t * packet, storage_model_t * storage, double distance, + double comov_nu, double comov_energy) +{ + int64_t current_continuum_id; + int64_t no_of_continuum_edges = storage->no_of_edges; + int64_t shell_id = rpacket_get_current_shell_id (packet); + line_search(storage->continuum_list_nu, comov_nu, no_of_continuum_edges, ¤t_continuum_id); + double T = storage->t_electrons[shell_id]; + double boltzmann_factor = exp (-(H * comov_nu) / (KB * T)); + + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->ff_heating_estimator[shell_id] += comov_energy * distance * rpacket_get_chi_freefree (packet); + + for(int64_t i = current_continuum_id; i < no_of_continuum_edges; i++) + { + double bf_xsect = bf_cross_section (storage, i, comov_nu); + int64_t photo_ion_idx = i * storage->no_of_shells + shell_id; + double photo_ion_estimator_helper = comov_energy * distance * bf_xsect / comov_nu; + double bf_heating_estimator_helper = + comov_energy * distance * bf_xsect * (1. - storage->continuum_list_nu[i] / comov_nu); + + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->photo_ion_estimator[photo_ion_idx] += photo_ion_estimator_helper; + + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->stim_recomb_estimator[photo_ion_idx] += photo_ion_estimator_helper * boltzmann_factor; + + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->bf_heating_estimator[photo_ion_idx] += bf_heating_estimator_helper; + + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->stim_recomb_cooling_estimator[photo_ion_idx] += bf_heating_estimator_helper * boltzmann_factor; + + if (photo_ion_estimator_helper != 0.0) + { + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->photo_ion_estimator_statistics[photo_ion_idx] += 1; + } + else + { + break; + } + } +} + +double +get_increment_j_blue_estimator_energy (const rpacket_t * packet, + const storage_model_t * storage, + double d_line) +{ + double energy; + if (storage->full_relativity) + { + // Accurate up to a factor 1 / gamma + energy = rpacket_get_energy (packet); + } + else + { + double r = rpacket_get_r (packet); + double r_interaction = sqrt (r * r + d_line * d_line + + 2.0 * r * d_line * rpacket_get_mu (packet)); + double mu_interaction = (rpacket_get_mu (packet) * r + d_line) / r_interaction; + double doppler_factor = 1.0 - mu_interaction * r_interaction * + storage->inverse_time_explosion * INVERSE_C; + energy = rpacket_get_energy (packet) * doppler_factor; + } + return energy; +} + +void +increment_j_blue_estimator (const rpacket_t * packet, storage_model_t * storage, + double d_line, int64_t j_blue_idx) +{ + if (storage->line_lists_j_blues != NULL) + { + double energy = get_increment_j_blue_estimator_energy (packet, storage, + d_line); + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->line_lists_j_blues[j_blue_idx] += + energy / rpacket_get_nu (packet); + } +} + +void +increment_Edotlu_estimator (const rpacket_t * packet, storage_model_t * storage, + double d_line, int64_t line_idx) +{ + if (storage->line_lists_Edotlu != NULL) + { + double energy = get_increment_j_blue_estimator_energy (packet, storage, + d_line); + #ifdef WITHOPENMP + #pragma omp atomic + #endif + storage->line_lists_Edotlu[line_idx] += energy; + } +} + + +int64_t +montecarlo_one_packet (storage_model_t * storage, rpacket_t * packet, + int64_t virtual_mode, rk_state *mt_state) +{ + int64_t reabsorbed=-1; + if (virtual_mode == 0) + { + reabsorbed = montecarlo_one_packet_loop (storage, packet, 0, mt_state); + } + else + { + if ((rpacket_get_nu (packet) > storage->spectrum_virt_start_nu) && (rpacket_get_nu(packet) < storage->spectrum_virt_end_nu)) + { + for (int64_t i = 0; i < rpacket_get_virtual_packet_flag (packet); i++) + { + double weight; + rpacket_t virt_packet = *packet; + double mu_min; + if (rpacket_get_r(&virt_packet) > storage->r_inner[0]) + { + mu_min = + -1.0 * sqrt (1.0 - + (storage->r_inner[0] / rpacket_get_r(&virt_packet)) * + (storage->r_inner[0] / rpacket_get_r(&virt_packet))); + + if (storage->full_relativity) + { + // Need to transform the angular size of the photosphere into the CMF + mu_min = angle_aberration_LF_to_CMF (&virt_packet, storage, mu_min); + } + } + else + { + mu_min = 0.0; + } + double mu_bin = (1.0 - mu_min) / rpacket_get_virtual_packet_flag (packet); + rpacket_set_mu(&virt_packet,mu_min + (i + rk_double (mt_state)) * mu_bin); + switch (virtual_mode) + { + case -2: + weight = 1.0 / rpacket_get_virtual_packet_flag (packet); + break; + case -1: + weight = + 2.0 * rpacket_get_mu(&virt_packet) / + rpacket_get_virtual_packet_flag (packet); + break; + case 1: + weight = + (1.0 - + mu_min) / 2.0 / rpacket_get_virtual_packet_flag (packet); + break; + default: + fprintf (stderr, "Something has gone horribly wrong!\n"); + // FIXME MR: we need to somehow signal an error here + // I'm adding an exit() here to inform the compiler about the impossible path + exit(1); + } + angle_aberration_CMF_to_LF (&virt_packet, storage); + double doppler_factor_ratio = + rpacket_doppler_factor (packet, storage) / + rpacket_doppler_factor (&virt_packet, storage); + rpacket_set_energy(&virt_packet, + rpacket_get_energy (packet) * doppler_factor_ratio); + rpacket_set_nu(&virt_packet,rpacket_get_nu (packet) * doppler_factor_ratio); + reabsorbed = montecarlo_one_packet_loop (storage, &virt_packet, 1, mt_state); +#ifdef WITH_VPACKET_LOGGING +#ifdef WITHOPENMP +#pragma omp critical + { +#endif // WITHOPENMP + if (storage->virt_packet_count >= storage->virt_array_size) + { + storage->virt_array_size *= 2; + storage->virt_packet_nus = safe_realloc(storage->virt_packet_nus, sizeof(double) * storage->virt_array_size); + storage->virt_packet_energies = safe_realloc(storage->virt_packet_energies, sizeof(double) * storage->virt_array_size); + storage->virt_packet_last_interaction_in_nu = safe_realloc(storage->virt_packet_last_interaction_in_nu, sizeof(double) * storage->virt_array_size); + storage->virt_packet_last_interaction_type = safe_realloc(storage->virt_packet_last_interaction_type, sizeof(int64_t) * storage->virt_array_size); + storage->virt_packet_last_line_interaction_in_id = safe_realloc(storage->virt_packet_last_line_interaction_in_id, sizeof(int64_t) * storage->virt_array_size); + storage->virt_packet_last_line_interaction_out_id = safe_realloc(storage->virt_packet_last_line_interaction_out_id, sizeof(int64_t) * storage->virt_array_size); + } + storage->virt_packet_nus[storage->virt_packet_count] = rpacket_get_nu(&virt_packet); + storage->virt_packet_energies[storage->virt_packet_count] = rpacket_get_energy(&virt_packet) * weight; + storage->virt_packet_last_interaction_in_nu[storage->virt_packet_count] = storage->last_interaction_in_nu[rpacket_get_id (packet)]; + storage->virt_packet_last_interaction_type[storage->virt_packet_count] = storage->last_interaction_type[rpacket_get_id (packet)]; + storage->virt_packet_last_line_interaction_in_id[storage->virt_packet_count] = storage->last_line_interaction_in_id[rpacket_get_id (packet)]; + storage->virt_packet_last_line_interaction_out_id[storage->virt_packet_count] = storage->last_line_interaction_out_id[rpacket_get_id (packet)]; + storage->virt_packet_count += 1; +#ifdef WITHOPENMP + } +#endif // WITHOPENMP +#endif // WITH_VPACKET_LOGGING + if ((rpacket_get_nu(&virt_packet) < storage->spectrum_end_nu) && + (rpacket_get_nu(&virt_packet) > storage->spectrum_start_nu)) + { +#ifdef WITHOPENMP +#pragma omp critical + { +#endif // WITHOPENMP + int64_t virt_id_nu = + floor ((rpacket_get_nu(&virt_packet) - + storage->spectrum_start_nu) / + storage->spectrum_delta_nu); + storage->spectrum_virt_nu[virt_id_nu] += + rpacket_get_energy(&virt_packet) * weight; +#ifdef WITHOPENMP + } +#endif // WITHOPENMP + } + } + } + else + { + return 1; + } + } + return reabsorbed; +} + +void +move_packet_across_shell_boundary (rpacket_t * packet, + storage_model_t * storage, double distance, rk_state *mt_state) +{ + move_packet (packet, storage, distance); + if (rpacket_get_virtual_packet (packet) > 0) + { + double delta_tau_event = rpacket_get_chi_continuum(packet) * distance; + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) + + delta_tau_event); + packet->compute_chi_bf = true; + } + else + { + rpacket_reset_tau_event (packet, mt_state); + } + if ((rpacket_get_current_shell_id (packet) < storage->no_of_shells - 1 + && rpacket_get_next_shell_id (packet) == 1) + || (rpacket_get_current_shell_id (packet) > 0 + && rpacket_get_next_shell_id (packet) == -1)) + { + rpacket_set_current_shell_id (packet, + rpacket_get_current_shell_id (packet) + + rpacket_get_next_shell_id (packet)); + } + else if (rpacket_get_next_shell_id (packet) == 1) + { + rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); + } + else if ((storage->reflective_inner_boundary == 0) || + (rk_double (mt_state) > storage->inner_boundary_albedo)) + { + rpacket_set_status (packet, TARDIS_PACKET_STATUS_REABSORBED); + } + else + { + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + double comov_energy = rpacket_get_energy (packet) * doppler_factor; + // TODO: correct + rpacket_set_mu (packet, rk_double (mt_state)); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + montecarlo_one_packet (storage, packet, -2, mt_state); + } + } +} + +void +montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state) +{ + move_packet (packet, storage, distance); + double doppler_factor = rpacket_doppler_factor (packet, storage); + double comov_nu = rpacket_get_nu (packet) * doppler_factor; + double comov_energy = rpacket_get_energy (packet) * doppler_factor; + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + rpacket_reset_tau_event (packet, mt_state); + storage->last_interaction_type[rpacket_get_id (packet)] = 1; + + angle_aberration_CMF_to_LF (packet, storage); + + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + create_vpacket (storage, packet, mt_state); + } +} + +void +montecarlo_bound_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state) +{ + // current position in list of continuum edges -> indicates which bound-free processes are possible + int64_t ccontinuum = rpacket_get_current_continuum_id (packet); + + // Determine in which continuum the bf-absorption occurs + double chi_bf = rpacket_get_chi_boundfree (packet); + double zrand = rk_double (mt_state); + double zrand_x_chibf = zrand * chi_bf; + + while ((ccontinuum < storage->no_of_edges - 1) && (packet->chi_bf_tmp_partial[ccontinuum] <= zrand_x_chibf)) + { + ccontinuum++; + } + rpacket_set_current_continuum_id (packet, ccontinuum); + + /* For consistency reasons the branching between ionization and thermal energy is determined using the + comoving frequency at the initial position instead of the frequency at the point of interaction */ + double comov_nu = rpacket_get_nu (packet) * rpacket_doppler_factor (packet, storage); + + /* Move the packet to the place of absorption, select a direction for re-emission and impose energy conservation + in the co-moving frame. */ + move_packet (packet, storage, distance); + double old_doppler_factor = rpacket_doppler_factor (packet, storage); + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + storage->last_interaction_type[rpacket_get_id (packet)] = 3; // last interaction was a bf-absorption + storage->last_line_interaction_in_id[rpacket_get_id (packet)] = ccontinuum; + + // Convert the rpacket to thermal or ionization energy + zrand = rk_double (mt_state); + int64_t activate_level = (zrand < storage->continuum_list_nu[ccontinuum] / comov_nu) ? + storage->cont_edge2macro_level[ccontinuum] : storage->kpacket2macro_level; + + rpacket_set_macro_atom_activation_level (packet, activate_level); + macro_atom (packet, storage, mt_state); +} + +void +montecarlo_free_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state) +{ + /* Move the packet to the place of absorption, select a direction for re-emission and impose energy conservation + in the co-moving frame. */ + move_packet (packet, storage, distance); + double old_doppler_factor = rpacket_doppler_factor (packet, storage); + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + storage->last_interaction_type[rpacket_get_id (packet)] = 4; // last interaction was a ff-absorption + + // Create a k-packet + rpacket_set_macro_atom_activation_level (packet, storage->kpacket2macro_level); + macro_atom (packet, storage, mt_state); +} + +double +sample_nu_free_free (const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) +{ + int64_t shell_id = rpacket_get_current_shell_id (packet); + double T = storage->t_electrons[shell_id]; + double zrand = rk_double (mt_state); + return -KB * T / H * log(zrand); // Lucy 2003 MC II Eq.41 +} + +double +sample_nu_free_bound (const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state) +{ + int64_t continuum_id = rpacket_get_current_continuum_id (packet); + double th_frequency = storage->continuum_list_nu[continuum_id]; + int64_t shell_id = rpacket_get_current_shell_id (packet); + double T = storage->t_electrons[shell_id]; + double zrand = rk_double (mt_state); + return th_frequency * (1 - (KB * T / H / th_frequency * log(zrand))); // Lucy 2003 MC II Eq.26 +} + +void +montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state) +{ + uint64_t next_line_id = rpacket_get_next_line_id (packet); + uint64_t line2d_idx = next_line_id + + storage->no_of_lines * rpacket_get_current_shell_id (packet); + if (rpacket_get_virtual_packet (packet) == 0) + { + increment_j_blue_estimator (packet, storage, distance, line2d_idx); + increment_Edotlu_estimator (packet, storage, distance, line2d_idx); + } + double tau_line = + storage->line_lists_tau_sobolevs[line2d_idx]; + double tau_continuum = rpacket_get_chi_continuum(packet) * distance; + double tau_combined = tau_line + tau_continuum; + //rpacket_set_next_line_id (packet, rpacket_get_next_line_id (packet) + 1); + + if (next_line_id + 1 == storage->no_of_lines) + { + rpacket_set_last_line (packet, true); + } + if (rpacket_get_virtual_packet (packet) > 0) + { + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) + tau_line); + rpacket_set_next_line_id (packet, next_line_id + 1); + test_for_close_line (packet, storage); + } + else if (rpacket_get_tau_event (packet) < tau_combined) + { // Line absorption occurs + move_packet (packet, storage, distance); + double old_doppler_factor = rpacket_doppler_factor (packet, storage); + rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + double comov_energy = rpacket_get_energy (packet) * old_doppler_factor; + rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); + storage->last_interaction_in_nu[rpacket_get_id (packet)] = + rpacket_get_nu (packet); + storage->last_line_interaction_in_id[rpacket_get_id (packet)] = + next_line_id; + storage->last_line_interaction_shell_id[rpacket_get_id (packet)] = + rpacket_get_current_shell_id (packet); + storage->last_interaction_type[rpacket_get_id (packet)] = 2; + if (storage->line_interaction_id == 0) + { + line_emission (packet, storage, next_line_id, mt_state); + } + else if (storage->line_interaction_id >= 1) + { + rpacket_set_macro_atom_activation_level (packet, + storage->line2macro_level_upper[next_line_id]); + macro_atom (packet, storage, mt_state); + } + } + else + { // Packet passes line without interacting + rpacket_set_tau_event (packet, + rpacket_get_tau_event (packet) - tau_line); + rpacket_set_next_line_id (packet, next_line_id + 1); + packet->compute_chi_bf = false; + test_for_close_line (packet, storage); + } +} + +void +line_emission (rpacket_t * packet, storage_model_t * storage, int64_t emission_line_id, rk_state *mt_state) +{ + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + storage->last_line_interaction_out_id[rpacket_get_id (packet)] = emission_line_id; + if (storage->cont_status == CONTINUUM_ON) + { + storage->last_interaction_out_type[rpacket_get_id (packet)] = 2; + } + + rpacket_set_nu (packet, + storage->line_list_nu[emission_line_id] * inverse_doppler_factor); + rpacket_set_nu_line (packet, storage->line_list_nu[emission_line_id]); + rpacket_set_next_line_id (packet, emission_line_id + 1); + rpacket_reset_tau_event (packet, mt_state); + + angle_aberration_CMF_to_LF (packet, storage); + + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + bool virtual_close_line = false; + if (!rpacket_get_last_line (packet) && + fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - + rpacket_get_nu_line (packet)) < + (rpacket_get_nu_line (packet)* 1e-7)) + { + virtual_close_line = true; + } + // QUESTIONABLE!!! + bool old_close_line = rpacket_get_close_line (packet); + rpacket_set_close_line (packet, virtual_close_line); + create_vpacket (storage, packet, mt_state); + rpacket_set_close_line (packet, old_close_line); + virtual_close_line = false; + } + test_for_close_line (packet, storage); +} + +void test_for_close_line (rpacket_t * packet, const storage_model_t * storage) +{ + if (!rpacket_get_last_line (packet) && + fabs (storage->line_list_nu[rpacket_get_next_line_id (packet)] - + rpacket_get_nu_line (packet)) < (rpacket_get_nu_line (packet)* + 1e-7)) + { + rpacket_set_close_line (packet, true); + } +} + +void +continuum_emission (rpacket_t * packet, storage_model_t * storage, rk_state *mt_state, + pt2sample_nu sample_nu_continuum, int64_t emission_type_id) +{ + double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); + double nu_comov = sample_nu_continuum (packet, storage, mt_state); + rpacket_set_nu (packet, nu_comov * inverse_doppler_factor); + rpacket_reset_tau_event (packet, mt_state); + + storage->last_interaction_out_type[rpacket_get_id (packet)] = emission_type_id; + + // Have to find current position in line list + int64_t current_line_id; + line_search (storage->line_list_nu, nu_comov, storage->no_of_lines, ¤t_line_id); + + bool last_line = (current_line_id == storage->no_of_lines); + rpacket_set_last_line (packet, last_line); + rpacket_set_next_line_id (packet, current_line_id); + + angle_aberration_CMF_to_LF (packet, storage); + + if (rpacket_get_virtual_packet_flag (packet) > 0) + { + create_vpacket (storage, packet, mt_state); + } +} + +static void +montecarlo_compute_distances (rpacket_t * packet, storage_model_t * storage) +{ + // Check if the last line was the same nu as the current line. + if (rpacket_get_close_line (packet)) + { + // If so set the distance to the line to 0.0 + rpacket_set_d_line (packet, 0.0); + // Reset close_line. + rpacket_set_close_line (packet, false); + } + else + { + compute_distance2boundary (packet, storage); + compute_distance2line (packet, storage); + // FIXME MR: return status of compute_distance2line() is ignored + compute_distance2continuum (packet, storage); + } +} + +montecarlo_event_handler_t +get_event_handler (rpacket_t * packet, storage_model_t * storage, + double *distance, rk_state *mt_state) +{ + montecarlo_compute_distances (packet, storage); + double d_boundary = rpacket_get_d_boundary (packet); + double d_continuum = rpacket_get_d_continuum (packet); + double d_line = rpacket_get_d_line (packet); + montecarlo_event_handler_t handler; + if (d_line <= d_boundary && d_line <= d_continuum) + { + *distance = d_line; + handler = &montecarlo_line_scatter; + } + else if (d_boundary <= d_continuum) + { + *distance = d_boundary; + handler = &move_packet_across_shell_boundary; + } + else + { + *distance = d_continuum; + handler = montecarlo_continuum_event_handler (packet, storage, mt_state); + } + return handler; +} + +montecarlo_event_handler_t +montecarlo_continuum_event_handler (rpacket_t * packet, storage_model_t * storage, rk_state *mt_state) +{ + if (storage->cont_status) + { + double zrand_x_chi_cont = rk_double (mt_state) * rpacket_get_chi_continuum (packet); + double chi_th = rpacket_get_chi_electron (packet); + double chi_bf = rpacket_get_chi_boundfree (packet); + + if (zrand_x_chi_cont < chi_th) + { + return &montecarlo_thomson_scatter; + } + else if (zrand_x_chi_cont < chi_th + chi_bf) + { + return &montecarlo_bound_free_scatter; + } + else + { + return &montecarlo_free_free_scatter; + } + } + else + { + return &montecarlo_thomson_scatter; + } +} + +int64_t +montecarlo_one_packet_loop (storage_model_t * storage, rpacket_t * packet, + int64_t virtual_packet, rk_state *mt_state) +{ + rpacket_set_tau_event (packet, 0.0); + rpacket_set_nu_line (packet, 0.0); + rpacket_set_virtual_packet (packet, virtual_packet); + rpacket_set_status (packet, TARDIS_PACKET_STATUS_IN_PROCESS); + // Initializing tau_event if it's a real packet. + if (virtual_packet == 0) + { + rpacket_reset_tau_event (packet,mt_state); + } + // For a virtual packet tau_event is the sum of all the tau's that the packet passes. + while (rpacket_get_status (packet) == TARDIS_PACKET_STATUS_IN_PROCESS) + { + // Check if we are at the end of line list. + if (!rpacket_get_last_line (packet)) + { + rpacket_set_nu_line (packet, + storage-> + line_list_nu[rpacket_get_next_line_id + (packet)]); + } + double distance; + get_event_handler (packet, storage, &distance, mt_state) (packet, storage, + distance, mt_state); + if (virtual_packet > 0 && rpacket_get_tau_event (packet) > storage->tau_russian) + { + double event_random = rk_double (mt_state); + if (event_random > storage->survival_probability) + { + rpacket_set_energy(packet, 0.0); + rpacket_set_status (packet, TARDIS_PACKET_STATUS_EMITTED); + } + else + { + rpacket_set_energy(packet, + rpacket_get_energy (packet) / storage->survival_probability * + exp (-1.0 * rpacket_get_tau_event (packet))); + rpacket_set_tau_event (packet, 0.0); + } + } + } + if (virtual_packet > 0) + { + rpacket_set_energy (packet, + rpacket_get_energy (packet) * exp (-1.0 * + rpacket_get_tau_event + (packet))); + } + return rpacket_get_status (packet) == + TARDIS_PACKET_STATUS_REABSORBED ? 1 : 0; +} + +void +montecarlo_main_loop(storage_model_t * storage, int64_t virtual_packet_flag, int nthreads, unsigned long seed) +{ + int64_t finished_packets = 0; + storage->virt_packet_count = 0; +#ifdef WITH_VPACKET_LOGGING + storage->virt_packet_nus = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_energies = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_last_interaction_in_nu = (double *)safe_malloc(sizeof(double) * storage->no_of_packets); + storage->virt_packet_last_interaction_type = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_packet_last_line_interaction_in_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_packet_last_line_interaction_out_id = (int64_t *)safe_malloc(sizeof(int64_t) * storage->no_of_packets); + storage->virt_array_size = storage->no_of_packets; +#endif // WITH_VPACKET_LOGGING +#ifdef WITHOPENMP + omp_set_dynamic(0); + if (nthreads > 0) + { + omp_set_num_threads(nthreads); + } + +#pragma omp parallel firstprivate(finished_packets) + { + rk_state mt_state; + rk_seed (seed + omp_get_thread_num(), &mt_state); +#pragma omp master + { + fprintf(stderr, "Running with OpenMP - %d threads\n", omp_get_num_threads()); + print_progress(0, storage->no_of_packets); + } +#else + rk_state mt_state; + rk_seed (seed, &mt_state); + fprintf(stderr, "Running without OpenMP\n"); +#endif + int64_t chi_bf_tmp_size = (storage->cont_status) ? storage->no_of_edges : 0; + double *chi_bf_tmp_partial = safe_malloc(sizeof(double) * chi_bf_tmp_size); + + #pragma omp for + for (int64_t packet_index = 0; packet_index < storage->no_of_packets; ++packet_index) + { + int reabsorbed = 0; + rpacket_t packet; + rpacket_set_id(&packet, packet_index); + rpacket_init(&packet, storage, packet_index, virtual_packet_flag, chi_bf_tmp_partial); + if (virtual_packet_flag > 0) + { + reabsorbed = montecarlo_one_packet(storage, &packet, -1, &mt_state); + } + reabsorbed = montecarlo_one_packet(storage, &packet, 0, &mt_state); + storage->output_nus[packet_index] = rpacket_get_nu(&packet); + if (reabsorbed == 1) + { + storage->output_energies[packet_index] = -rpacket_get_energy(&packet); + } + else + { + storage->output_energies[packet_index] = rpacket_get_energy(&packet); + } + if ( ++finished_packets%100 == 0 ) + { +#ifdef WITHOPENMP + // WARNING: This only works with a static sheduler and gives an approximation of progress. + // The alternative would be to have a shared variable but that could potentially decrease performance when using many threads. + if (omp_get_thread_num() == 0 ) + print_progress(finished_packets * omp_get_num_threads(), storage->no_of_packets); +#else + print_progress(finished_packets, storage->no_of_packets); +#endif + } + } + free(chi_bf_tmp_partial); +#ifdef WITHOPENMP + } +#endif + print_progress(storage->no_of_packets, storage->no_of_packets); + fprintf(stderr,"\n"); +} + +void +create_vpacket (storage_model_t * storage, rpacket_t * packet, + rk_state *mt_state) +{ + if (storage->enable_biasing) + { + int64_t shell_id = rpacket_get_current_shell_id(packet); + double tau_bias = (storage->tau_bias[shell_id + 1] + + (storage->tau_bias[shell_id] - storage->tau_bias[shell_id + 1]) * + (storage->r_outer[shell_id] - rpacket_get_r (packet)) / + (storage->r_outer[shell_id] - storage->r_inner[shell_id])); + double vpacket_prob = exp(-tau_bias); + double event_random = rk_double (mt_state); + if (event_random < vpacket_prob) + { + packet->vpacket_weight = 1. / vpacket_prob; + montecarlo_one_packet (storage, packet, 1, mt_state); + } + } + else + { + montecarlo_one_packet (storage, packet, 1, mt_state); + } +} diff --git a/tardis/montecarlo/src/cmontecarlo.h b/tardis/montecarlo/src/cmontecarlo.h new file mode 100644 index 00000000000..767017b7303 --- /dev/null +++ b/tardis/montecarlo/src/cmontecarlo.h @@ -0,0 +1,159 @@ +#ifndef TARDIS_CMONTECARLO_H +#define TARDIS_CMONTECARLO_H + +#include +#include "randomkit/randomkit.h" +#include "rpacket.h" +#include "status.h" +#include "storage.h" + +#ifdef WITH_VPACKET_LOGGING +#define LOG_VPACKETS 1 +#else +#define LOG_VPACKETS 0 +#endif + +typedef void (*montecarlo_event_handler_t) (rpacket_t *packet, + storage_model_t *storage, + double distance, rk_state *mt_state); + +typedef double (*pt2sample_nu) (const rpacket_t *packet, + const storage_model_t *storage, + rk_state *mt_state); + +void initialize_random_kit (unsigned long seed); + +tardis_error_t line_search (const double *nu, double nu_insert, + int64_t number_of_lines, int64_t * result); + +tardis_error_t +reverse_binary_search (const double *x, double x_insert, + int64_t imin, int64_t imax, int64_t * result); + +tardis_error_t +binary_search (const double *x, double x_insert, + int64_t imin, int64_t imax, int64_t * result); + +double rpacket_doppler_factor (const rpacket_t *packet, const storage_model_t *storage); + +double rpacket_inverse_doppler_factor (const rpacket_t *packet, const storage_model_t *storage); + +void +angle_aberration_CMF_to_LF (rpacket_t * packet, const storage_model_t * storage); + +double +angle_aberration_LF_to_CMF (rpacket_t *packet, const storage_model_t *storage, double mu); + +/** Calculate the distance to shell boundary. + * + * @param packet rpacket structure with packet information + * @param storage storage model data + * + * @return distance to shell boundary + */ +void compute_distance2boundary (rpacket_t * packet, + const storage_model_t * storage); + +/** Calculate the distance the packet has to travel until it redshifts to the first spectral line. + * + * @param packet rpacket structure with packet information + * @param storage storage model data + * + * @return distance to the next spectral line + */ +tardis_error_t compute_distance2line (rpacket_t * packet, + const storage_model_t * storage); + +/** Calculate the distance to the next continuum event, which can be a Thomson scattering, bound-free absorption or + free-free transition. + * + * @param packet rpacket structure with packet information + * @param storage storage model data + * + * sets distance to the next continuum event (in centimeters) in packet rpacket structure + */ +void compute_distance2continuum (rpacket_t *packet, storage_model_t *storage); + +void macro_atom (rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); + +void move_packet (rpacket_t * packet, storage_model_t * storage, + double distance); + +void increment_j_blue_estimator (const rpacket_t * packet, + storage_model_t * storage, + double d_line, + int64_t j_blue_idx); + +void increment_Edotlu_estimator (const rpacket_t * packet, + storage_model_t * storage, + double d_line, + int64_t j_blue_idx); + +double get_increment_j_blue_estimator_energy (const rpacket_t * packet, + const storage_model_t * storage, + double d_line); + +void +increment_continuum_estimators (const rpacket_t * packet, storage_model_t * storage, double distance, + double comov_nu, double comov_energy); + +int64_t montecarlo_one_packet (storage_model_t * storage, rpacket_t * packet, + int64_t virtual_mode, rk_state *mt_state); + +int64_t montecarlo_one_packet_loop (storage_model_t * storage, + rpacket_t * packet, + int64_t virtual_packet, rk_state *mt_state); + +void montecarlo_main_loop(storage_model_t * storage, + int64_t virtual_packet_flag, + int nthreads, + unsigned long seed); + +montecarlo_event_handler_t get_event_handler (rpacket_t * packet, storage_model_t * storage, + double *distance, rk_state *mt_state); + +void test_for_close_line(rpacket_t * packet, const storage_model_t * storage); + +/* New handlers for continuum implementation */ + +montecarlo_event_handler_t montecarlo_continuum_event_handler(rpacket_t * packet, storage_model_t * storage, rk_state *mt_state); + +void montecarlo_free_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state); + +void montecarlo_bound_free_scatter (rpacket_t * packet, storage_model_t * storage, double distance, rk_state *mt_state); + +double +bf_cross_section(const storage_model_t * storage, int64_t continuum_id, double comov_nu); + +void calculate_chi_bf(rpacket_t * packet, storage_model_t * storage); + +void calculate_chi_ff(rpacket_t * packet, const storage_model_t * storage); + +void +move_packet_across_shell_boundary (rpacket_t * packet, + storage_model_t * storage, double distance, rk_state *mt_state); + +void +montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state); + +void +montecarlo_line_scatter (rpacket_t * packet, storage_model_t * storage, + double distance, rk_state *mt_state); + +void +line_emission(rpacket_t * packet, storage_model_t * storage, + int64_t emission_line_id, rk_state *mt_state); + +double sample_nu_free_free(const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); + +double sample_nu_free_bound(const rpacket_t * packet, const storage_model_t * storage, rk_state *mt_state); + +void continuum_emission(rpacket_t * packet, storage_model_t * storage, rk_state *mt_state, + pt2sample_nu sample_nu_continuum, int64_t emission_type_id); + +void +create_vpacket (storage_model_t * storage, rpacket_t * packet, + rk_state *mt_state); + +#endif // TARDIS_CMONTECARLO_H diff --git a/tardis/montecarlo/src/integrator.c b/tardis/montecarlo/src/integrator.c new file mode 100644 index 00000000000..a0e1452a00e --- /dev/null +++ b/tardis/montecarlo/src/integrator.c @@ -0,0 +1,344 @@ +#define _USE_MATH_DEFINES + +#include +#include +#include +#include +#include +#include + +#include "io.h" +#include "storage.h" +#include "integrator.h" +#include "cmontecarlo.h" + +#include "omp_helper.h" + +#define NULEN 0 +#define LINELEN 1 +#define PLEN 2 +#define SHELLEN 3 + +#define C_INV 3.33564e-11 +#define M_PI acos (-1) +#define KB_CGS 1.3806488e-16 +#define H_CGS 6.62606957e-27 + +/** + * Calculate the intensity of a black-body according to the following formula + * .. math:: + * I(\\nu, T) = \\frac{2h\\nu^3}{c^2}\frac{1}{e^{h\\nu \\beta_\\textrm{rad}} - 1} +*/ +double +intensity_black_body (double nu, double T) +{ + double beta_rad = 1 / (KB_CGS * T); + double coefficient = 2 * H_CGS * C_INV * C_INV; + return coefficient * nu * nu * nu / (exp(H_CGS * nu * beta_rad) - 1 ); +} + + +/*! @brief Algorithm to integrate an array using the trapezoid integration rule + * +*/ +double +trapezoid_integration (const double* array, const double h, int N) +{ + double result = (array[0] + array[N-1])/2; + for (int idx = 1; idx < N-1; ++idx) + { + result += array[idx]; + } + return result * h; +} + +/*! @brief Calculate distance to p line + * + * Calculate half of the length of the p-line inside a shell + * of radius r in terms of unit length (c * t_exp). + * If shell and p-line do not intersect, return 0. + * + * @param r radius of the shell + * @param p distance of the p-line to the center of the supernova + * @param inv_t inverse time_explosio is needed to norm to unit-length + * @return half the lenght inside the shell or zero + */ +static inline double +calculate_z(double r, double p, double inv_t) +{ + return (r > p) ? sqrt(r * r - p * p) * C_INV * inv_t : 0; +} + + +/*! + * @brief Calculate p line intersections + * + * This function calculates the intersection points of the p-line with each shell + * + * @param storage (INPUT) A storage model containing the environment + * @param p (INPUT) distance of the integration line to the center + * @param oz (OUTPUT) will be set with z values. The array is truncated by the + * value `1`. + * @param oshell_id (OUTPUT) will be set with the corresponding shell_ids + * @return number of shells intersected by the p-line + */ +int64_t +populate_z(const storage_model_t *storage, const double p, double *oz, int64_t *oshell_id) +{ + + // Abbreviations + double *r = storage->r_outer_i; + const int64_t N = storage->no_of_shells_i; + double inv_t = storage->inverse_time_explosion; + double z = 0; + + int64_t i = 0, offset = N, i_low, i_up; + + if (p <= storage->r_inner_i[0]) + { + // Intersect the photosphere + for(i = 0; i < N; ++i) + { // Loop from inside to outside + oz[i] = 1 - calculate_z(r[i], p, inv_t); + oshell_id[i] = i; + } + return N; + } + else + { + // No intersection with the photosphere + // that means we intersect each shell twice + for(i = 0; i < N; ++i) + { // Loop from inside to outside + z = calculate_z(r[i], p, inv_t); + if (z == 0) + continue; + if (offset == N) + { + offset = i; + } + // Calculate the index in the resulting array + i_low = N - i - 1; // the far intersection with the shell + i_up = N + i - 2 * offset; // the nearer intersection with the shell + + // Setting the arrays + oz[i_low] = 1 + z; + oshell_id[i_low] = i; + oz[i_up] = 1 - z; + oshell_id[i_up] = i; + } + return 2 * (N - offset); + } +} + + +/*! @brief Calculate integration points + * + */ +void +calculate_p_values(double R_max, int64_t N, double *opp) +{ + for(int i = 0; ino_of_lines, + size_shell = storage->no_of_shells_i, + size_tau = size_line * size_shell, + finished_nus = 0; + + double R_ph = storage->r_inner_i[0]; + double R_max = storage->r_outer_i[size_shell - 1]; + double pp[N]; + double *exp_tau = calloc(size_tau, sizeof(double)); +//#pragma omp parallel firstprivate(L, exp_tau) +#pragma omp parallel + { + +#pragma omp master + { + if (omp_get_num_threads() > 1) { + fprintf(stderr, "Doing the formal integral\nRunning with OpenMP - %d threads\n", omp_get_num_threads()); + } else { + fprintf(stderr, "Doing the formal integral\nRunning without OpenMP\n"); + } + print_progress_fi(0, inu_size); + } + + // Initializing all the thread-local variables + int64_t offset = 0, i = 0, + size_z = 0, + idx_nu_start = 0, + direction = 0, + first = 0; + + double I_nu[N], + //I_nu_b[N], + //I_nu_r[N], + z[2 * storage->no_of_shells_i], + p = 0, + nu_start, + nu_end, + nu, + zstart, + zend, + escat_contrib, + escat_op, + Jkkp; + int64_t shell_id[2 * storage->no_of_shells_i]; + + double *pexp_tau, *patt_S_ul, *pline, *pJred_lu, *pJblue_lu; + + // Prepare exp_tau +#pragma omp for + for (i = 0; i < size_tau; ++i) { + exp_tau[i] = exp( -storage->line_lists_tau_sobolevs_i[i]); + } + calculate_p_values(R_max, N, pp); + // Done with the initialization + + // Loop over wavelengths in spectrum +#pragma omp for + for (int nu_idx = 0; nu_idx < inu_size ; ++nu_idx) + { + nu = inu[nu_idx]; + + // Loop over discrete values along line + for (int p_idx = 1; p_idx < N; ++p_idx) + { + escat_contrib = 0; + p = pp[p_idx]; + + // initialize z intersections for p values + size_z = populate_z(storage, p, z, shell_id); + + // initialize I_nu + if (p <= R_ph) + I_nu[p_idx] = intensity_black_body(nu * z[0], iT); + else + I_nu[p_idx] = 0; + + // Find first contributing line + nu_start = nu * z[0]; + nu_end = nu * z[1]; + line_search( + storage->line_list_nu, + nu_start, + size_line, + &idx_nu_start + ); + offset = shell_id[0] * size_line; + + // start tracking accumulated e-scattering optical depth + zstart = storage->time_explosion / C_INV * (1. - z[0]); + + // Initialize pointers + pline = storage->line_list_nu + idx_nu_start; + pexp_tau = exp_tau + offset + idx_nu_start; + patt_S_ul = att_S_ul + offset + idx_nu_start; + pJred_lu = Jred_lu + offset + idx_nu_start; + pJblue_lu = Jblue_lu + offset + idx_nu_start; + + // flag for first contribution to integration on current p-ray + first = 1; + + // TODO: Ugly loop + // Loop over all intersections + // TODO: replace by number of intersections and remove break + for (i = 0; i < size_z - 1; ++i) + { + escat_op = storage->electron_densities_i[shell_id[i]] * storage->sigma_thomson; + nu_end = nu * z[i+1]; + + // TODO: e-scattering: in principle we also have to check + // that dtau is <<1 (as assumed in Lucy 1999); if not, there + // is the chance that I_nu_b becomes negative + for (;pline < storage->line_list_nu + size_line; + // We have to increment all pointers simultaneously + ++pline, + ++pexp_tau, + ++patt_S_ul, + ++pJblue_lu) + { + if (*pline < nu_end) + { + // next resonance not in current shell + break; + } + + // Calculate e-scattering optical depth to next resonance point + zend = storage->time_explosion / C_INV * (1. - *pline / nu); + + if (first == 1){ + // First contribution to integration + // NOTE: this treatment of I_nu_b (given by boundary + // conditions) is not in Lucy 1999; should be + // re-examined carefully + escat_contrib += (zend - zstart) * escat_op * (*pJblue_lu - I_nu[p_idx]) ; + first = 0; + } + else{ + // Account for e-scattering, c.f. Eqs 27, 28 in Lucy 1999 + Jkkp = 0.5 * (*pJred_lu + *pJblue_lu); + escat_contrib += (zend - zstart) * escat_op * (Jkkp - I_nu[p_idx]) ; + // this introduces the necessary offset of one element between pJblue_lu and pJred_lu + pJred_lu += 1; + } + I_nu[p_idx] = I_nu[p_idx] + escat_contrib; + + // Lucy 1999, Eq 26 + I_nu[p_idx] = I_nu[p_idx] * (*pexp_tau) + *patt_S_ul; + + // reset e-scattering opacity + escat_contrib = 0; + zstart = zend; + } + // Calculate e-scattering optical depth to grid cell boundary + Jkkp = 0.5 * (*pJred_lu + *pJblue_lu); + zend = storage->time_explosion / C_INV * (1. - nu_end / nu); + escat_contrib += (zend - zstart) * escat_op * (Jkkp - I_nu[p_idx]); + zstart = zend; + + if (i < size_z-1){ + // advance pointers + direction = shell_id[i+1] - shell_id[i]; + pexp_tau += direction * size_line; + patt_S_ul += direction * size_line; + pJred_lu += direction * size_line; + pJblue_lu += direction * size_line; + } + } + I_nu[p_idx] *= p; + } + // TODO: change integration to match the calculation of p values + L[nu_idx] = 8 * M_PI * M_PI * trapezoid_integration(I_nu, R_max/N, N); +#pragma omp atomic update + ++finished_nus; + if (finished_nus%10 == 0){ + print_progress_fi(finished_nus, inu_size); + } + } + // Free everything allocated on heap + printf("\n"); + } + free(exp_tau); + return L; +} diff --git a/tardis/montecarlo/src/integrator.h b/tardis/montecarlo/src/integrator.h new file mode 100644 index 00000000000..c06e4a286a0 --- /dev/null +++ b/tardis/montecarlo/src/integrator.h @@ -0,0 +1,19 @@ +#ifndef TARDIS_INTEGRATOR_H +#define TARDIS_INTEGRATOR_H + +#include "storage.h" + +double +integrate_intensity(const double* I_nu, const double h, int N); + +int64_t +populate_z(const storage_model_t *storage, const double p, double *oz, int64_t *oshell_id); + +void +calculate_p_values(double R_max, int64_t N, double *opp); + +double +*_formal_integral( + const storage_model_t *storage, double T, double *nu, int64_t nu_size, double *att_S_ul, double *Jred_lu, double *Jblue_lu, int N); + +#endif diff --git a/tardis/montecarlo/src/io.h b/tardis/montecarlo/src/io.h new file mode 100644 index 00000000000..122af1e5967 --- /dev/null +++ b/tardis/montecarlo/src/io.h @@ -0,0 +1,32 @@ +#define _POSIX_C_SOURCE 1 + +#include +#include +#include + +#define STATUS_FORMAT "\r\033[2K\t[%" PRId64 "%%] Packets(finished/total): %" PRId64 "/%" PRId64 +#define STATUS_FORMAT_FI "\r\033[2K\t[%" PRId64 "%%] Bins(finished/total): %" PRId64 "/%" PRId64 + +static inline void +print_progress (const int64_t current, const int64_t total) +{ + if (isatty(fileno(stderr))) + { + fprintf(stderr, STATUS_FORMAT, + current * 100 / total, + current, + total); + } +} + +static inline void +print_progress_fi (const int64_t current, const int64_t total) +{ + if (isatty(fileno(stderr))) + { + fprintf(stderr, STATUS_FORMAT_FI, + current * 100 / total, + current, + total); + } +} diff --git a/tardis/montecarlo/src/omp_helper.h b/tardis/montecarlo/src/omp_helper.h new file mode 100644 index 00000000000..abb9c2acc7c --- /dev/null +++ b/tardis/montecarlo/src/omp_helper.h @@ -0,0 +1,7 @@ +#ifdef WITHOPENMP + #include +#else +int omp_get_num_threads(){ + return 1; +} +#endif diff --git a/tardis/montecarlo/src/randomkit/LICENSE b/tardis/montecarlo/src/randomkit/LICENSE new file mode 100644 index 00000000000..c0d13eb1acc --- /dev/null +++ b/tardis/montecarlo/src/randomkit/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tardis/montecarlo/src/randomkit/randomkit.h b/tardis/montecarlo/src/randomkit/randomkit.h new file mode 100644 index 00000000000..d5220d01857 --- /dev/null +++ b/tardis/montecarlo/src/randomkit/randomkit.h @@ -0,0 +1,42 @@ +/* Random kit 1.6 */ + +/* + * Anyone who attempts to generate random numbers by deterministic means is, + * of course, living in a state of sin. + * -- John von Neumann + */ + +/* + * Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* @(#) $Jeannot: randomkit.h,v 1.29 2006/02/19 14:44:14 js Exp $ */ + +#ifndef _RANDOMKIT_ +#define _RANDOMKIT_ + +#include "rk_mt.h" +#include "rk_sobol.h" +#include "rk_primitive.h" +#include "rk_isaac.h" + +#endif /* _RANDOMKIT_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_isaac.c b/tardis/montecarlo/src/randomkit/rk_isaac.c new file mode 100644 index 00000000000..9f52fafae4c --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_isaac.c @@ -0,0 +1,258 @@ +/* Random kit 1.6 */ + +/* + * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * ISAAC RNG by By Bob Jenkins. Based on Bob Jenkins public domain code. + * + * Original algorithm for the implementation of rk_interval function from + * Richard J. Wagner's implementation of the Mersenne Twister RNG, optimised by + * Magnus Jonsson. + * + * Constants used in the rk_double implementation by Isaku Wada. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +static char const rcsid[] = + "@(#) $Jeannot: rk_isaac.c,v 1.4 2006/02/20 19:13:37 js Exp $"; + +#include "rk_isaac.h" +#include +#include +#include + +/* use the contents of randrsl[0..RK_ISAAC_STATE_LEN-1] as the seed. */ +static void isaac_init(rk_isaac_state *rg); + +void rk_isaac_seed(unsigned long seed, rk_isaac_state *state) +{ + rk_knuth_fill(seed, state->randrsl, RK_ISAAC_STATE_LEN); + isaac_init(state); +} + +rk_error rk_isaac_randomseed(rk_isaac_state *state) +{ + if(rk_devfill(state->randrsl, sizeof(state->randrsl), 1) == RK_NOERR) + { + isaac_init(state); + return RK_NOERR; + } + + rk_isaac_seed(rk_seedfromsystem(), state); + + return RK_ENODEV; +} + +#define ind(mm,x) ((mm)[(x>>2)&(RK_ISAAC_STATE_LEN-1)]) +#define rngstep(mix,a,b,mm,m,m2,r,x) \ +{ \ + x = *m; \ + a = ((a^(mix)) + *(m2++)) & 0xffffffff; \ + *(m++) = y = (ind(mm,x) + a + b) & 0xffffffff; \ + *(r++) = b = (ind(mm,y>>RK_ISAAC_STATE_POW) + x) & 0xffffffff; \ +} + +/* Call rand(rk_isaac_state *r) to retrieve a single 32-bit random value */ +unsigned long rk_isaac_random(rk_isaac_state *state) +{ + if (!state->randcnt--) + { + register unsigned long a,b,x,y,*m,*mm,*m2,*r,*mend; + mm=state->randmem; r=state->randrsl; + a = state->randa; b = (state->randb + (++state->randc)) & 0xffffffff; + for (m = mm, mend = m2 = m+(RK_ISAAC_STATE_LEN/2); m>6 , a, b, mm, m, m2, r, x); + rngstep( a<<2 , a, b, mm, m, m2, r, x); + rngstep( a>>16, a, b, mm, m, m2, r, x); + } + for (m2 = mm; m2>6 , a, b, mm, m, m2, r, x); + rngstep( a<<2 , a, b, mm, m, m2, r, x); + rngstep( a>>16, a, b, mm, m, m2, r, x); + } + state->randb = b; state->randa = a; + state->randcnt=RK_ISAAC_STATE_LEN-1; + } + return state->randrsl[state->randcnt] & 0xFFFFFFFF; +} + +#define mix(a,b,c,d,e,f,g,h) \ +{ \ + a^=b<<11; d+=a; b+=c; \ + b^=(c & 0xFFFFFFFF)>>2; e+=b; c+=d; \ + c^=d<<8; f+=c; d+=e; \ + d^=(e & 0xFFFFFFFF)>>16; g+=d; e+=f; \ + e^=f<<10; h+=e; f+=g; \ + f^=(g & 0xFFFFFFFF)>>4; a+=f; g+=h; \ + g^=h<<8; b+=g; h+=a; \ + h^=(a & 0xFFFFFFFF)>>9; c+=h; a+=b; \ +} + +/* if (flag==1), then use the contents of randrsl[] to initialize mm[]. */ +void isaac_init(rk_isaac_state *rk_isaac_state) +{ + int i; + unsigned long a,b,c,d,e,f,g,h; + unsigned long *m,*r; + rk_isaac_state->randa = rk_isaac_state->randb = rk_isaac_state->randc = 0; + m=rk_isaac_state->randmem; + r=rk_isaac_state->randrsl; + a=b=c=d=e=f=g=h=0x9e3779b9; /* the golden ratio */ + + for (i=0; i<4; ++i) /* scramble it */ + mix(a,b,c,d,e,f,g,h); + + /* initialize using the contents of r[] as the seed */ + for (i=0; irandcnt=0; + rk_isaac_state->has_gauss=0; +} + +long rk_isaac_long(rk_isaac_state *state) +{ + return rk_isaac_ulong(state) >> 1; +} + +unsigned long rk_isaac_ulong(rk_isaac_state *state) +{ +#if ULONG_MAX <= 0xffffffffUL + return rk_isaac_random(state); +#else + /* Assumes 64 bits */ + return (rk_isaac_random(state) << 32) | (rk_isaac_random(state)); +#endif +} + +unsigned long rk_isaac_interval(unsigned long max, rk_isaac_state *state) +{ + unsigned long mask = max, value; + + if (max == 0) return 0; + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; +#if ULONG_MAX > 0xffffffffUL + mask |= mask >> 32; +#endif + + /* Search a random value in [0..mask] <= max */ + while ((value = (rk_isaac_ulong(state) & mask)) > max); + + return value; +} + +double rk_isaac_double(rk_isaac_state *state) +{ + /* shifts : 67108864 = 0x4000000, 9007199254740992 = 0x20000000000000 */ + long a = rk_isaac_random(state) >> 5, b = rk_isaac_random(state) >> 6; + return (a * 67108864.0 + b) / 9007199254740992.0; +} + +void rk_isaac_copy(rk_isaac_state *copy, rk_isaac_state *orig) +{ + memcpy(copy, orig, sizeof(rk_isaac_state)); +} + +double rk_isaac_gauss(rk_isaac_state *state) +{ + if (state->has_gauss) + { + state->has_gauss = 0; + return state->gauss; + } + else + { + double f, x1, x2, r2; + do + { + x1 = 2.0*rk_isaac_double(state) - 1.0; + x2 = 2.0*rk_isaac_double(state) - 1.0; + r2 = x1*x1 + x2*x2; + } + while (r2 >= 1.0 || r2 == 0.0); + + f = sqrt(-2.0*log(r2)/r2); /* Box-Muller transform */ + state->has_gauss = 1; + state->gauss = f*x1; /* Keep for next call */ + return f*x2; + } +} + +void rk_isaac_fill(void *buffer, size_t size, rk_isaac_state *state) +{ + unsigned long r; + unsigned char *buf = buffer; + rk_isaac_state tempstate; + + if (size > 0 && state == NULL) + { + rk_isaac_randomseed(&tempstate); + state = &tempstate; + } + + for (; size >= 4; size -= 4) + { + r = rk_isaac_random(state); + *(buf++) = r & 0xFF; + *(buf++) = (r >> 8) & 0xFF; + *(buf++) = (r >> 16) & 0xFF; + *(buf++) = (r >> 24) & 0xFF; + } + + if (!size) return; + + r = rk_isaac_random(state); + + for (; size; r >>= 8, size --) + *(buf++) = (unsigned char)(r & 0xFF); +} + +void rk_seed_isaac(rk_isaac_state *i_state, rk_state *state) +{ + rk_isaac_fill(state->key, sizeof(state->key), i_state); + state->pos = RK_STATE_LEN; + state->has_gauss = 0; +} diff --git a/tardis/montecarlo/src/randomkit/rk_isaac.h b/tardis/montecarlo/src/randomkit/rk_isaac.h new file mode 100644 index 00000000000..c8dfdcb451e --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_isaac.h @@ -0,0 +1,142 @@ +/* Random kit 1.6 */ + +/* + * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * ISAAC RNG by By Bob Jenkins. Based on Bob Jenkins public domain code. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* @(#) $Jeannot: rk_isaac.h,v 1.2 2006/02/19 14:40:26 js Exp $ */ + +/* + * Typical use: + * + * { + * rk_isaac_state state; + * unsigned long seed = 1, random_value; + * + * rk_isaac_seed(seed, &state); // Initialize the RNG + * ... + * random_value = rk_isaac_random(&state); // a random value in [0..RK_MAX] + * } + * + * Instead of rk_isaac_seed, you can use rk_isaac_randomseed which will get a + * random seed from /dev/random (or the clock, if /dev/random is unavailable): + * + * { + * rk_isaac_state state; + * unsigned long random_value; + * + * rk_isaac_randomseed(&state); // Initialize the RNG with a random seed + * ... + * random_value = rk_isaac_random(&state); // a random value in [0..RK_MAX] + * } + */ + +#ifndef _RK_ISAAC_ +#define _RK_ISAAC_ + +#include "rk_mt.h" + +#define RK_ISAAC_STATE_POW 8 +#define RK_ISAAC_STATE_LEN (1< +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +/* Windows */ +#include +#ifndef RK_NO_WINCRYPT +/* Windows crypto */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#include +#include +#endif +#else +/* Unix */ +#include +#include +#endif + +#include "randomkit.h" + +#ifndef RK_DEV_URANDOM +#define RK_DEV_URANDOM "/dev/urandom" +#endif + +#ifndef RK_DEV_RANDOM +#define RK_DEV_RANDOM "/dev/random" +#endif + +char *rk_strerror[RK_ERR_MAX] = +{ + "no error", + "random device unvavailable" +}; + +/* static functions */ +static unsigned long rk_hash(unsigned long key); + +void rk_knuth_fill(unsigned long seed, unsigned long *key, unsigned long len) +{ + int pos; + seed &= 0xffffffffUL; + + /* Knuth's PRNG as used in the Mersenne Twister reference implementation */ + for (pos=0; pos> 30)) + pos + 1) & 0xffffffffUL; + } +} + +void rk_seed(unsigned long seed, rk_state *state) +{ + rk_knuth_fill(seed, state->key, RK_STATE_LEN); + state->pos = RK_STATE_LEN; + state->has_gauss = 0; +} + +/* Thomas Wang 32 bits integer hash function */ +unsigned long rk_hash(unsigned long key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} + +unsigned long rk_seedfromsystem() +{ +#ifndef _WIN32 + struct timeval tv; +#else + struct _timeb tv; +#endif + +#ifndef _WIN32 + gettimeofday(&tv, NULL); + return rk_hash(getpid()) ^ rk_hash(tv.tv_sec) ^ rk_hash(tv.tv_usec) + ^ rk_hash(clock()); +#else + _ftime(&tv); + return rk_hash(tv.time) ^ rk_hash(tv.millitm) ^ rk_hash(clock()); +#endif +} + +rk_error rk_randomseed(rk_state *state) +{ + if(rk_devfill(state->key, sizeof(state->key), 0) == RK_NOERR) + { + state->key[0] |= 0x80000000UL; /* ensures non-zero key */ + state->pos = RK_STATE_LEN; + state->has_gauss = 0; + return RK_NOERR; + } + + rk_seed(rk_seedfromsystem(), state); + + return RK_ENODEV; +} + +/* Magic Mersenne Twister constants */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL +#define UPPER_MASK 0x80000000UL +#define LOWER_MASK 0x7fffffffUL + +/* Slightly optimised reference implementation of the Mersenne Twister */ +unsigned long rk_random(rk_state *state) +{ + unsigned long y; + + if (state->pos == RK_STATE_LEN) + { + int i; + + for (i=0;ikey[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); + state->key[i] = state->key[i+M] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); + } + for (;ikey[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); + state->key[i] = state->key[i+(M-N)] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); + } + y = (state->key[N-1] & UPPER_MASK) | (state->key[0] & LOWER_MASK); + state->key[N-1] = state->key[M-1] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); + + state->pos = 0; + } + + y = state->key[state->pos++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +long rk_long(rk_state *state) +{ + return rk_ulong(state) >> 1; +} + +unsigned long rk_ulong(rk_state *state) +{ +#if ULONG_MAX <= 0xffffffffUL + return rk_random(state); +#else + /* Assumes 64 bits */ + return (rk_random(state) << 32) | (rk_random(state)); +#endif +} + +unsigned long rk_interval(unsigned long max, rk_state *state) +{ + unsigned long mask = max, value; + + if (max == 0) return 0; + + /* Smallest bit mask >= max */ + mask |= mask >> 1; + mask |= mask >> 2; + mask |= mask >> 4; + mask |= mask >> 8; + mask |= mask >> 16; +#if ULONG_MAX > 0xffffffffUL + mask |= mask >> 32; +#endif + + /* Search a random value in [0..mask] <= max */ + while ((value = (rk_ulong(state) & mask)) > max); + + return value; +} + +double rk_double(rk_state *state) +{ + /* shifts : 67108864 = 0x4000000, 9007199254740992 = 0x20000000000000 */ + long a = rk_random(state) >> 5, b = rk_random(state) >> 6; + return (a * 67108864.0 + b) / 9007199254740992.0; +} + +void rk_copy(rk_state *copy, rk_state *orig) +{ + memcpy(copy, orig, sizeof(rk_state)); +} + +void rk_fill(void *buffer, size_t size, rk_state *state) +{ + unsigned long r; + unsigned char *buf = buffer; + rk_state tempstate; + + if (size > 0 && state == NULL) + { + rk_randomseed(&tempstate); + state = &tempstate; + } + + for (; size >= 4; size -= 4) + { + r = rk_random(state); + *(buf++) = r & 0xFF; + *(buf++) = (r >> 8) & 0xFF; + *(buf++) = (r >> 16) & 0xFF; + *(buf++) = (r >> 24) & 0xFF; + } + + if (!size) return; + + r = rk_random(state); + + for (; size; r >>= 8, size --) + *(buf++) = (unsigned char)(r & 0xFF); +} + +rk_error rk_devfill(void *buffer, size_t size, int strong) +{ +#ifndef _WIN32 + FILE *rfile; + int done; + + if (strong) + rfile = fopen(RK_DEV_RANDOM, "rb"); + else + rfile = fopen(RK_DEV_URANDOM, "rb"); + if (rfile == NULL) + return RK_ENODEV; + done = fread(buffer, size, 1, rfile); + fclose(rfile); + if (done) + return RK_NOERR; +#else + +#ifndef RK_NO_WINCRYPT + HCRYPTPROV hCryptProv; + BOOL done; + + if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT) || !hCryptProv) + return RK_ENODEV; + done = CryptGenRandom(hCryptProv, size, (unsigned char *)buffer); + CryptReleaseContext(hCryptProv, 0); + if (done) + return RK_NOERR; +#endif + +#endif + + return RK_ENODEV; +} + +rk_error rk_altfill(void *buffer, size_t size, int strong, rk_state *state) +{ + rk_error err; + + err = rk_devfill(buffer, size, strong); + if (err) + rk_fill(buffer, size, state); + + return err; +} + +double rk_gauss(rk_state *state) +{ + if (state->has_gauss) + { + state->has_gauss = 0; + return state->gauss; + } + else + { + double f, x1, x2, r2; + do + { + x1 = 2.0*rk_double(state) - 1.0; + x2 = 2.0*rk_double(state) - 1.0; + r2 = x1*x1 + x2*x2; + } + while (r2 >= 1.0 || r2 == 0.0); + + f = sqrt(-2.0*log(r2)/r2); /* Box-Muller transform */ + state->has_gauss = 1; + state->gauss = f*x1; /* Keep for next call */ + return f*x2; + } +} diff --git a/tardis/montecarlo/src/randomkit/rk_mt.h b/tardis/montecarlo/src/randomkit/rk_mt.h new file mode 100644 index 00000000000..ff0e22a0879 --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_mt.h @@ -0,0 +1,194 @@ +/* Random kit 1.6 */ + +/* + * Anyone who attempts to generate random numbers by deterministic means is, + * of course, living in a state of sin. + * -- John von Neumann + */ + +/* + * Copyright (c) 2003-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* @(#) $Jeannot: rk_mt.h,v 1.6 2006/02/20 19:13:37 js Exp $ */ + +/* + * Typical use: + * + * { + * rk_state state; + * unsigned long seed = 1, random_value; + * + * rk_seed(seed, &state); // Initialize the RNG + * ... + * random_value = rk_random(&state); // a random value in [0..RK_MAX] + * } + * + * Instead of rk_seed, you can use rk_randomseed which will get a random seed + * from /dev/urandom (or the clock, if /dev/urandom is unavailable): + * + * { + * rk_state state; + * unsigned long random_value; + * + * rk_randomseed(&state); // Initialize the RNG with a random seed + * ... + * random_value = rk_random(&state); // a random value in [0..RK_MAX] + * } + */ + +/* + * Useful macro: + * RK_DEV_RANDOM: the device used for random seeding. + * defaults to "/dev/urandom" + */ + +#include + +#ifndef _RK_MT_ +#define _RK_MT_ + +#define RK_STATE_LEN 624 + +typedef struct rk_state_ +{ + unsigned long key[RK_STATE_LEN]; + int pos; + int has_gauss; /* !=0: gauss contains a gaussian deviate */ + double gauss; +} +rk_state; + +typedef enum { + RK_NOERR = 0, /* no error */ + RK_ENODEV = 1, /* no RK_DEV_RANDOM device */ + RK_ERR_MAX = 2 +} rk_error; + +/* error strings */ +extern char *rk_strerror[RK_ERR_MAX]; + +/* Maximum generated random value */ +#define RK_MAX 0xFFFFFFFFUL + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Initialize the RNG state using the given seed. + */ +extern void rk_seed(unsigned long seed, rk_state *state); + +/* + * Initialize the RNG state using a random seed. + * Uses /dev/urandom or, when unavailable, the clock (see rk_mt.c). + * Returns RK_NOERR when no errors occurs. + * Returns RK_ENODEV when the use of RK_DEV_RANDOM failed (for example because + * there is no such device). In this case, the RNG was initialized using the + * clock. + */ +extern rk_error rk_randomseed(rk_state *state); + +/* + * Returns a random unsigned long between 0 and RK_MAX inclusive + */ +extern unsigned long rk_random(rk_state *state); + +/* + * Returns a random long between 0 and LONG_MAX inclusive + */ +extern long rk_long(rk_state *state); + +/* + * Returns a random unsigned long between 0 and ULONG_MAX inclusive + */ +extern unsigned long rk_ulong(rk_state *state); + +/* + * Returns a random unsigned long between 0 and max inclusive. + */ +extern unsigned long rk_interval(unsigned long max, rk_state *state); + +/* + * Returns a random double between 0.0 and 1.0, 1.0 excluded. + */ +extern double rk_double(rk_state *state); + +/* + * Copy a random generator state. + */ +extern void rk_copy(rk_state *copy, rk_state *orig); + +/* + * fill the buffer with size random bytes. + * If state == NULL, the random generator is inilialized using rk_randomseed. + * Calling multiple times rk_randomseed should be avoided therefore calling + * multiple times rk_fill with state == NULL should be avoided. + */ +extern void rk_fill(void *buffer, size_t size, rk_state *state); + +/* + * fill the buffer with randombytes from the random device + * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is + * On Unix, if strong is defined, RK_DEV_RANDOM is used. If not, RK_DEV_URANDOM + * is used instead. This parameter has no effect on Windows. + * Warning: on most unixes RK_DEV_RANDOM will wait for enough entropy to answer + * which can take a very long time on quiet systems. + */ +extern rk_error rk_devfill(void *buffer, size_t size, int strong); + +/* + * fill the buffer using rk_devfill if the random device is available and using + * rk_fill if is is not + * parameters have the same meaning as rk_fill and rk_devfill + * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is + */ +extern rk_error rk_altfill(void *buffer, size_t size, int strong, + rk_state *state); + +/* + * return a random gaussian deviate with variance unity and zero mean. + */ +extern double rk_gauss(rk_state *state); + +/* Utility functions */ + +/* + * fill the key vector using Knuth RNG as used in MT reference implementation + * using the provided seed. The key vector length is len. + */ +extern void rk_knuth_fill(unsigned long seed, unsigned long *key, + unsigned long len); + +/* + * return a random unsigned long based upon the system state (clock, pid) + * used as seed when there is no random device. + */ +extern unsigned long rk_seedfromsystem(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* _RK_MT_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_primitive.c b/tardis/montecarlo/src/randomkit/rk_primitive.c new file mode 100644 index 00000000000..25e70c6c7f5 --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_primitive.c @@ -0,0 +1,520 @@ +/* Random kit 1.6 */ +/* Primitivity test for binary polynomials of low degree */ + +/* + * Copyright (c) 2005-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Methodology inspired by scott duplichan's ppsearch code. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +static char const rcsid[] = + "@(#) $Jeannot: rk_primitive.c,v 1.7 2006/02/19 13:48:34 js Exp $"; + +#include +#include +#include "rk_primitive.h" + +#ifndef LONG_BIT +#if ULONG_MAX <= 0xffffffffUL +#define LONG_BIT 32 +#else +#define LONG_BIT 64 +#endif +#endif + +static unsigned long modmul(unsigned long poly1, unsigned long poly2, + unsigned long modulo, unsigned long mask); +static unsigned long modpow(unsigned long polynomial, unsigned long power, + unsigned long modulo, int degree); + +/* + * For all powers i of two up to 64, list all the number of the form + * (2^i-1)/p for p a prime factor of 2^i-1. + */ +static const unsigned long divisors[][12]={ +/* 2^0-1 */ + {1UL, + 0UL}, +/* 2^1-1 */ + {1UL, + 0UL}, +/* 2^2-1 */ + {1UL, + 0UL}, +/* 2^3-1 */ + {1UL, + 0UL}, +/* 2^4-1 */ + {5UL, + 3UL, + 0UL}, +/* 2^5-1 */ + {1UL, + 0UL}, +/* 2^6-1 */ + {21UL, + 9UL, + 0UL}, +/* 2^7-1 */ + {1UL, + 0UL}, +/* 2^8-1 */ + {85UL, + 51UL, + 15UL, + 0UL}, +/* 2^9-1 */ + {73UL, + 7UL, + 0UL}, +/* 2^10-1 */ + {341UL, + 93UL, + 33UL, + 0UL}, +/* 2^11-1 */ + {89UL, + 23UL, + 0UL}, +/* 2^12-1 */ + {1365UL, + 819UL, + 585UL, + 315UL, + 0UL}, +/* 2^13-1 */ + {1UL, + 0UL}, +/* 2^14-1 */ + {5461UL, + 381UL, + 129UL, + 0UL}, +/* 2^15-1 */ + {4681UL, + 1057UL, + 217UL, + 0UL}, +/* 2^16-1 */ + {21845UL, + 13107UL, + 3855UL, + 255UL, + 0UL}, +/* 2^17-1 */ + {1UL, + 0UL}, +/* 2^18-1 */ + {87381UL, + 37449UL, + 13797UL, + 3591UL, + 0UL}, +/* 2^19-1 */ + {1UL, + 0UL}, +/* 2^20-1 */ + {349525UL, + 209715UL, + 95325UL, + 33825UL, + 25575UL, + 0UL}, +/* 2^21-1 */ + {299593UL, + 16513UL, + 6223UL, + 0UL}, +/* 2^22-1 */ + {1398101UL, + 182361UL, + 47127UL, + 6141UL, + 0UL}, +/* 2^23-1 */ + {178481UL, + 47UL, + 0UL}, +/* 2^24-1 */ + {5592405UL, + 3355443UL, + 2396745UL, + 1290555UL, + 986895UL, + 69615UL, + 0UL}, +/* 2^25-1 */ + {1082401UL, + 55831UL, + 18631UL, + 0UL}, +/* 2^26-1 */ + {22369621UL, + 24573UL, + 8193UL, + 0UL}, +/* 2^27-1 */ + {19173961UL, + 1838599UL, + 511UL, + 0UL}, +/* 2^28-1 */ + {89478485UL, + 53687091UL, + 9256395UL, + 6242685UL, + 2375535UL, + 2113665UL, + 0UL}, +/* 2^29-1 */ + {2304167UL, + 486737UL, + 256999UL, + 0UL}, +/* 2^30-1 */ + {357913941UL, + 153391689UL, + 97612893UL, + 34636833UL, + 7110873UL, + 3243933UL, + 0UL}, +/* 2^31-1 */ + {1UL, + 0UL}, +/* 2^32-1 */ + {1431655765UL, + 858993459UL, + 252645135UL, + 16711935UL, + 65535UL, + 0UL}, +#if LONG_BIT > 32 +/* 2^33-1 */ + {1227133513UL, + 373475417UL, + 96516119UL, + 14329UL, + 0UL}, +/* 2^34-1 */ + {5726623061UL, + 393213UL, + 131073UL, + 0UL}, +/* 2^35-1 */ + {1108378657UL, + 483939977UL, + 270549121UL, + 279527UL, + 0UL}, +/* 2^36-1 */ + {22906492245UL, + 13743895347UL, + 9817068105UL, + 5286113595UL, + 3616814565UL, + 1857283155UL, + 941362695UL, + 630453915UL, + 0UL}, +/* 2^37-1 */ + {616318177UL, + 223UL, + 0UL}, +/* 2^38-1 */ + {91625968981UL, + 1572861UL, + 524289UL, + 0UL}, +/* 2^39-1 */ + {78536544841UL, + 6958934353UL, + 67117057UL, + 4529623UL, + 0UL}, +/* 2^40-1 */ + {366503875925UL, + 219902325555UL, + 99955602525UL, + 64677154575UL, + 35468117025UL, + 26817356775UL, + 17825775UL, + 0UL}, +/* 2^41-1 */ + {164511353UL, + 13367UL, + 0UL}, +/* 2^42-1 */ + {1466015503701UL, + 628292358729UL, + 102280151421UL, + 34630287489UL, + 13050583119UL, + 811597437UL, + 0UL}, +/* 2^43-1 */ + {20408568497UL, + 905040953UL, + 4188889UL, + 0UL}, +/* 2^44-1 */ + {5864062014805UL, + 3518437208883UL, + 764877654105UL, + 197665011735UL, + 44312811195UL, + 25757227005UL, + 8325691455UL, + 0UL}, +/* 2^45-1 */ + {5026338869833UL, + 1134979744801UL, + 481977699847UL, + 233009086681UL, + 55759702201UL, + 1509346321UL, + 0UL}, +/* 2^46-1 */ + {23456248059221UL, + 1497207322929UL, + 394264623UL, + 25165821UL, + 0UL}, +/* 2^47-1 */ + {59862819377UL, + 31184907679UL, + 10610063UL, + 0UL}, +/* 2^48-1 */ + {93824992236885UL, + 56294995342131UL, + 40210710958665UL, + 21651921285435UL, + 16557351571215UL, + 2901803883615UL, + 1167945961455UL, + 1095233372415UL, + 418239192735UL, + 0UL}, +/* 2^49-1 */ + {4432676798593UL, + 127UL, + 0UL}, +/* 2^50-1 */ + {375299968947541UL, + 102354536985693UL, + 36319351833633UL, + 4485656999373UL, + 1873377548823UL, + 625152641223UL, + 277931351973UL, + 0UL}, +/* 2^51-1 */ + {321685687669321UL, + 21862134113449UL, + 1050769861729UL, + 202518195313UL, + 17180000257UL, + 0UL}, +/* 2^52-1 */ + {1501199875790165UL, + 900719925474099UL, + 84973577874915UL, + 28685347945035UL, + 2792064245115UL, + 1649066139645UL, + 549822930945UL, + 0UL}, +/* 2^53-1 */ + {1416003655831UL, + 129728784761UL, + 441650591UL, + 0UL}, +/* 2^54-1 */ + {6004799503160661UL, + 2573485501354569UL, + 948126237341157UL, + 246772582321671UL, + 206561081853UL, + 68585259519UL, + 0UL}, +/* 2^55-1 */ + {1566469435607129UL, + 1162219258676257UL, + 404817944033303UL, + 40895342813807UL, + 11290754314937UL, + 178394823847UL, + 0UL}, +/* 2^56-1 */ + {24019198012642645UL, + 14411518807585587UL, + 4238682002231055UL, + 2484744621997515UL, + 1675758000882045UL, + 637677823344495UL, + 567382630219905UL, + 4563402735UL, + 0UL}, +/* 2^57-1 */ + {20587884010836553UL, + 4451159405623UL, + 274878431233UL, + 118823881393UL, + 0UL}, +/* 2^58-1 */ + {96076792050570581UL, + 4885260612740877UL, + 1237040240994471UL, + 261314937580881UL, + 137975287770087UL, + 95026151247UL, + 0UL}, +/* 2^59-1 */ + {3203431780337UL, + 179951UL, + 0UL}, +/* 2^60-1 */ + {384307168202282325UL, + 230584300921369395UL, + 164703072086692425UL, + 104811045873349725UL, + 88686269585142075UL, + 37191016277640225UL, + 28120036697727975UL, + 18900352534538475UL, + 7635241752363225UL, + 3483146539597725UL, + 872764197279975UL, + 0UL}, +/* 2^61-1 */ + {1UL, + 0UL}, +/* 2^62-1 */ + {1537228672809129301UL, + 6442450941UL, + 2147483649UL, + 0UL}, +/* 2^63-1 */ + {1317624576693539401UL, + 126347562148695559UL, + 72624976668147841UL, + 27369056489183311UL, + 99457304386111UL, + 14197294936951UL, + 0UL}, +/* 2^64-1 */ + {6148914691236517205UL, + 3689348814741910323UL, + 1085102592571150095UL, + 71777214294589695UL, + 28778071877862015UL, + 281470681808895UL, + 2753074036095UL, + 0UL}, +#if LONG_BIT > 64 +#error Factorization of numbers up to 2^LONG_BIT required +#endif +#endif +}; + +/* + * Modular multiply for two binary polynomial + * mask is 1UL << the degree of the modulus. + */ +unsigned long modmul(unsigned long poly1, unsigned long poly2, + unsigned long modulo, unsigned long mask) +{ + unsigned long result = 0; + + for (; poly1; poly1 >>= 1) + { + if (poly1 & 1) + result ^= poly2; + + poly2 <<= 1; + if (poly2 & mask) + poly2 ^= modulo; + } + return result; +} + +/* + * Modular exponentiation for a binary polynomial + * degree is the degree of the modulus. + */ +unsigned long modpow(unsigned long polynomial, unsigned long power, + unsigned long modulo, int degree) +{ + unsigned long result = 1, mask = 1UL << degree; + + for (; power; power >>= 1) + { + if (power & 1) + result = modmul(result, polynomial, modulo, mask); + polynomial = modmul(polynomial, polynomial, modulo, mask); + } + return result; +} + +/* + * Test the primitivity of a polynomial + */ +int rk_isprimitive(unsigned long polynomial) +{ + unsigned long pelement = 2, temp = polynomial >> 1; + int k, degree = 0, weight = 1; + + /* Special case for polynomials of degree < 2 */ + if (polynomial < 4) + return (polynomial == 3) || (polynomial == 1); + + /* A binary primitive polynomial has a constant term */ + if (!(polynomial & 1)) + return 0; + + /* + * A binary primitive polynomial of degree > 1 has an odd number of terms. + * temp ^= temp >> 16; temp ^= temp >> 8; ... would be sligthly faster. + * Compute the degree at the same time. + */ + for (; temp; degree++, temp >>= 1) + weight += temp & 1; + if (!(weight & 1)) + return 0; + + /* + * Check if the period is 2^degree-1. + * Sufficient if 2^degree-1 is prime. + */ + if (modpow(pelement, 1UL << degree, polynomial, degree) != pelement) + return 0; + + if (divisors[degree][0] != 1) + /* Primitivity test */ + for (k = 0; divisors[degree][k]; k++) + if (modpow(pelement, divisors[degree][k], polynomial, degree) == 1) + return 0; + + return 1; +} diff --git a/tardis/montecarlo/src/randomkit/rk_primitive.h b/tardis/montecarlo/src/randomkit/rk_primitive.h new file mode 100644 index 00000000000..f7ab374cef5 --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_primitive.h @@ -0,0 +1,54 @@ +/* Random kit 1.6 */ +/* Primitivity test for binary polynomials of low degree */ + +/* + * Copyright (c) 2005-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Methodology inspired by scott duplichan's ppsearch code. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* @(#) $Jeannot: rk_primitive.h,v 1.6 2006/02/19 13:48:34 js Exp $ */ + +#ifndef _RK_PRIMITIVE_ +#define _RK_PRIMITIVE_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Return 1 if the binary polynomial is primitive. + * + * Note that if p is primitive, the the polynomial obtained by reversing the + * bits of p is also primitive. (see list_primitive.c for an example) + * + * Typical use: + * int test; + * test = rk_isprimitive(3, &divisors); + */ +extern int rk_isprimitive(unsigned long polynomial); + +#ifdef __cplusplus +} +#endif + +#endif /* _RK_PRIMITIVE_ */ diff --git a/tardis/montecarlo/src/randomkit/rk_sobol.c b/tardis/montecarlo/src/randomkit/rk_sobol.c new file mode 100644 index 00000000000..c6a016bb033 --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_sobol.c @@ -0,0 +1,991 @@ +/* Random kit 1.6 */ + +/* + * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Original algorithm from Numerical Recipes, 2nd edition, by Press et al. + * The inverse normal cdf formulas are from Peter J. Acklam. + * The initialization directions were found in Ferdinando Ametrano's + * implementation in QuantLib. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +static char const rcsid[] = + "@(#) $Jeannot: rk_sobol.c,v 1.9 2006/02/19 13:48:34 js Exp $"; + +#include +#include +#include +#include "rk_sobol.h" +#include "rk_mt.h" +#include "rk_primitive.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif +#define RK_SOBOL_M_SQRT2PI 2.506628274631000502415 /* sqrt(2*pi) */ + +#ifndef LONG_BIT +#if ULONG_MAX <= 0xffffffffUL +#define LONG_BIT 32 +#else +#define LONG_BIT 64 +#endif +#endif + +char *rk_sobol_strerror[] = +{ + "no error", + "invalid dimension", + "too many numbers generated", + "not enough memory" +}; + +static double inverse_normal(double p); + +/* + * Sobol/Levitan coefficients of the free direction integers as given + * by Bratley, P., Fox, B.L. (1988) + */ + +const unsigned long rk_sobol_SLdirections[] = { + 1, + 1, 1, + 1, 3, 7, + 1, 1, 5, + 1, 3, 1, 1, + 1, 1, 3, 7, + 1, 3, 3, 9, 9, + 1, 3, 7, 13, 3, + 1, 1, 5, 11, 27, + 1, 3, 5, 1, 15, + 1, 1, 7, 3, 29, + 1, 3, 7, 7, 21, + 1, 1, 1, 9, 23, 37, + 1, 3, 3, 5, 19, 33, + 1, 1, 3, 13, 11, 7, + 1, 1, 7, 13, 25, 5, + 1, 3, 5, 11, 7, 11, + 1, 1, 1, 3, 13, 39, + 1, 3, 1, 15, 17, 63, 13, + 1, 1, 5, 5, 1, 27, 33, + 1, 3, 3, 3, 25, 17, 115, + 1, 1, 3, 15, 29, 15, 41, + 1, 3, 1, 7, 3, 23, 79, + 1, 3, 7, 9, 31, 29, 17, + 1, 1, 5, 13, 11, 3, 29, + 1, 3, 1, 9, 5, 21, 119, + 1, 1, 3, 1, 23, 13, 75, + 1, 3, 3, 11, 27, 31, 73, + 1, 1, 7, 7, 19, 25, 105, + 1, 3, 5, 5, 21, 9, 7, + 1, 1, 1, 15, 5, 49, 59, + 1, 1, 1, 1, 1, 33, 65, + 1, 3, 5, 15, 17, 19, 21, + 1, 1, 7, 11, 13, 29, 3, + 1, 3, 7, 5, 7, 11, 113, + 1, 1, 5, 3, 15, 19, 61, + 1, 3, 1, 1, 9, 27, 89, 7, + 1, 1, 3, 7, 31, 15, 45, 23, + 1, 3, 3, 9, 9, 25, 107, 39, + 0 +}; + +/* + * Lemieux coefficients of the free direction integers as given + * in QuantLib by Christiane Lemieux, private communication, September 2004 + */ + +const unsigned long rk_sobol_Ldirections[] = { + 1, + 1, 1, + 1, 3, 7, + 1, 1, 5, + 1, 3, 1, 1, + 1, 1, 3, 7, + 1, 3, 3, 9, 9, + 1, 3, 7, 13, 3, + 1, 1, 5, 11, 27, + 1, 3, 5, 1, 15, + 1, 1, 7, 3, 29, + 1, 3, 7, 7, 21, + 1, 1, 1, 9, 23, 37, + 1, 3, 3, 5, 19, 33, + 1, 1, 3, 13, 11, 7, + 1, 1, 7, 13, 25, 5, + 1, 3, 5, 11, 7, 11, + 1, 1, 1, 3, 13, 39, + 1, 3, 1, 15, 17, 63, 13, + 1, 1, 5, 5, 1, 27, 33, + 1, 3, 3, 3, 25, 17, 115, + 1, 1, 3, 15, 29, 15, 41, + 1, 3, 1, 7, 3, 23, 79, + 1, 3, 7, 9, 31, 29, 17, + 1, 1, 5, 13, 11, 3, 29, + 1, 3, 1, 9, 5, 21, 119, + 1, 1, 3, 1, 23, 13, 75, + 1, 3, 3, 11, 27, 31, 73, + 1, 1, 7, 7, 19, 25, 105, + 1, 3, 5, 5, 21, 9, 7, + 1, 1, 1, 15, 5, 49, 59, + 1, 1, 1, 1, 1, 33, 65, + 1, 3, 5, 15, 17, 19, 21, + 1, 1, 7, 11, 13, 29, 3, + 1, 3, 7, 5, 7, 11, 113, + 1, 1, 5, 3, 15, 19, 61, + 1, 3, 1, 1, 9, 27, 89, 7, + 1, 1, 3, 7, 31, 15, 45, 23, + 1, 3, 3, 9, 9, 25, 107, 39, + 1, 1, 3, 13, 7, 35, 61, 91, + 1, 1, 7, 11, 5, 35, 55, 75, + 1, 3, 5, 5, 11, 23, 29, 139, + 1, 1, 1, 7, 11, 15, 17, 81, + 1, 1, 7, 9, 5, 57, 79, 103, + 1, 1, 7, 13, 19, 5, 5, 185, + 1, 3, 1, 3, 13, 57, 97, 131, + 1, 1, 5, 5, 21, 25, 125, 197, + 1, 3, 3, 9, 31, 11, 103, 201, + 1, 1, 5, 3, 7, 25, 51, 121, + 1, 3, 7, 15, 19, 53, 73, 189, + 1, 1, 1, 15, 19, 55, 27, 183, + 1, 1, 7, 13, 3, 29, 109, 69, + 1, 1, 5, 15, 15, 23, 15, 1, 57, + 1, 3, 1, 3, 23, 55, 43, 143, 397, + 1, 1, 3, 11, 29, 9, 35, 131, 411, + 1, 3, 1, 7, 27, 39, 103, 199, 277, + 1, 3, 7, 3, 19, 55, 127, 67, 449, + 1, 3, 7, 3, 5, 29, 45, 85, 3, + 1, 3, 5, 5, 13, 23, 75, 245, 453, + 1, 3, 1, 15, 21, 47, 3, 77, 165, + 1, 1, 7, 9, 15, 5, 117, 73, 473, + 1, 3, 1, 9, 1, 21, 13, 173, 313, + 1, 1, 7, 3, 11, 45, 63, 77, 49, + 1, 1, 1, 1, 1, 25, 123, 39, 259, + 1, 1, 1, 5, 23, 11, 59, 11, 203, + 1, 3, 3, 15, 21, 1, 73, 71, 421, + 1, 1, 5, 11, 15, 31, 115, 95, 217, + 1, 1, 3, 3, 7, 53, 37, 43, 439, + 1, 1, 1, 1, 27, 53, 69, 159, 321, + 1, 1, 5, 15, 29, 17, 19, 43, 449, + 1, 1, 3, 9, 1, 55, 121, 205, 255, + 1, 1, 3, 11, 9, 47, 107, 11, 417, + 1, 1, 1, 5, 17, 25, 21, 83, 95, + 1, 3, 5, 13, 31, 25, 61, 157, 407, + 1, 1, 7, 9, 25, 33, 41, 35, 17, + 1, 3, 7, 15, 13, 39, 61, 187, 461, + 1, 3, 7, 13, 5, 57, 23, 177, 435, + 1, 1, 3, 15, 11, 27, 115, 5, 337, + 1, 3, 7, 3, 15, 63, 61, 171, 339, + 1, 3, 3, 13, 15, 61, 59, 47, 1, + 1, 1, 5, 15, 13, 5, 39, 83, 329, + 1, 1, 5, 5, 5, 27, 25, 39, 301, + 1, 1, 5, 11, 31, 41, 35, 233, 27, + 1, 3, 5, 15, 7, 37, 119, 171, 419, + 1, 3, 5, 5, 3, 29, 21, 189, 417, + 1, 1, 1, 1, 21, 41, 117, 119, 351, + 1, 1, 3, 1, 7, 27, 87, 19, 213, + 1, 1, 1, 1, 17, 7, 97, 217, 477, + 1, 1, 7, 1, 29, 61, 103, 231, 269, + 1, 1, 7, 13, 9, 27, 107, 207, 311, + 1, 1, 7, 5, 25, 21, 107, 179, 423, + 1, 3, 5, 11, 7, 1, 17, 245, 281, + 1, 3, 5, 9, 1, 5, 53, 59, 125, + 1, 1, 7, 1, 31, 57, 71, 245, 125, + 1, 1, 7, 5, 5, 57, 53, 253, 441, + 1, 3, 1, 13, 19, 35, 119, 235, 381, + 1, 3, 1, 7, 19, 59, 115, 33, 361, + 1, 1, 3, 5, 13, 1, 49, 143, 501, + 1, 1, 3, 5, 1, 63, 101, 85, 189, + 1, 1, 5, 11, 27, 63, 13, 131, 5, + 1, 1, 5, 7, 15, 45, 75, 59, 455, 585, + 1, 3, 1, 3, 7, 7, 111, 23, 119, 959, + 1, 3, 3, 9, 11, 41, 109, 163, 161, 879, + 1, 3, 5, 1, 21, 41, 121, 183, 315, 219, + 1, 1, 3, 9, 15, 3, 9, 223, 441, 929, + 1, 1, 7, 9, 3, 5, 93, 57, 253, 457, + 1, 1, 7, 13, 15, 29, 83, 21, 35, 45, + 1, 1, 3, 7, 13, 61, 119, 219, 85, 505, + 1, 1, 3, 3, 17, 13, 35, 197, 291, 109, + 1, 1, 3, 3, 5, 1, 113, 103, 217, 253, + 1, 1, 7, 1, 15, 39, 63, 223, 17, 9, + 1, 3, 7, 1, 17, 29, 67, 103, 495, 383, + 1, 3, 3, 15, 31, 59, 75, 165, 51, 913, + 1, 3, 7, 9, 5, 27, 79, 219, 233, 37, + 1, 3, 5, 15, 1, 11, 15, 211, 417, 811, + 1, 3, 5, 3, 29, 27, 39, 137, 407, 231, + 1, 1, 3, 5, 29, 43, 125, 135, 109, 67, + 1, 1, 1, 5, 11, 39, 107, 159, 323, 381, + 1, 1, 1, 1, 9, 11, 33, 55, 169, 253, + 1, 3, 5, 5, 11, 53, 63, 101, 251, 897, + 1, 3, 7, 1, 25, 15, 83, 119, 53, 157, + 1, 3, 5, 13, 5, 5, 3, 195, 111, 451, + 1, 3, 1, 15, 11, 1, 19, 11, 307, 777, + 1, 3, 7, 11, 5, 5, 17, 231, 345, 981, + 1, 1, 3, 3, 1, 33, 83, 201, 57, 475, + 1, 3, 7, 7, 17, 13, 35, 175, 499, 809, + 1, 1, 5, 3, 3, 17, 103, 119, 499, 865, + 1, 1, 1, 11, 27, 25, 37, 121, 401, 11, + 1, 1, 1, 11, 9, 25, 25, 241, 403, 3, + 1, 1, 1, 1, 11, 1, 39, 163, 231, 573, + 1, 1, 1, 13, 13, 21, 75, 185, 99, 545, + 1, 1, 1, 15, 3, 63, 69, 11, 173, 315, + 1, 3, 5, 15, 11, 3, 95, 49, 123, 765, + 1, 1, 1, 15, 3, 63, 77, 31, 425, 711, + 1, 1, 7, 15, 1, 37, 119, 145, 489, 583, + 1, 3, 5, 15, 3, 49, 117, 211, 165, 323, + 1, 3, 7, 1, 27, 63, 77, 201, 225, 803, + 1, 1, 1, 11, 23, 35, 67, 21, 469, 357, + 1, 1, 7, 7, 9, 7, 25, 237, 237, 571, + 1, 1, 3, 15, 29, 5, 107, 109, 241, 47, + 1, 3, 5, 11, 27, 63, 29, 13, 203, 675, + 1, 1, 3, 9, 9, 11, 103, 179, 449, 263, + 1, 3, 5, 11, 29, 63, 53, 151, 259, 223, + 1, 1, 3, 7, 9, 25, 5, 197, 237, 163, + 1, 3, 7, 13, 5, 57, 67, 193, 147, 241, + 1, 1, 5, 15, 15, 33, 17, 67, 161, 341, + 1, 1, 3, 13, 17, 43, 21, 197, 441, 985, + 1, 3, 1, 5, 15, 33, 33, 193, 305, 829, + 1, 1, 1, 13, 19, 27, 71, 187, 477, 239, + 1, 1, 1, 9, 9, 17, 41, 177, 229, 983, + 1, 3, 5, 9, 15, 45, 97, 205, 43, 767, + 1, 1, 1, 9, 31, 31, 77, 159, 395, 809, + 1, 3, 3, 3, 29, 19, 73, 123, 165, 307, + 1, 3, 1, 7, 5, 11, 77, 227, 355, 403, + 1, 3, 5, 5, 25, 31, 1, 215, 451, 195, + 1, 3, 7, 15, 29, 37, 101, 241, 17, 633, + 1, 1, 5, 1, 11, 3, 107, 137, 489, 5, + 1, 1, 1, 7, 19, 19, 75, 85, 471, 355, + 1, 1, 3, 3, 9, 13, 113, 167, 13, 27, + 1, 3, 5, 11, 21, 3, 89, 205, 377, 307, + 1, 1, 1, 9, 31, 61, 65, 9, 391, 141, 867, + 1, 1, 1, 9, 19, 19, 61, 227, 241, 55, 161, + 1, 1, 1, 11, 1, 19, 7, 233, 463, 171, 1941, + 1, 1, 5, 7, 25, 13, 103, 75, 19, 1021, 1063, + 1, 1, 1, 15, 17, 17, 79, 63, 391, 403, 1221, + 1, 3, 3, 11, 29, 25, 29, 107, 335, 475, 963, + 1, 3, 5, 1, 31, 33, 49, 43, 155, 9, 1285, + 1, 1, 5, 5, 15, 47, 39, 161, 357, 863, 1039, + 1, 3, 7, 15, 1, 39, 47, 109, 427, 393, 1103, + 1, 1, 1, 9, 9, 29, 121, 233, 157, 99, 701, + 1, 1, 1, 7, 1, 29, 75, 121, 439, 109, 993, + 1, 1, 1, 9, 5, 1, 39, 59, 89, 157, 1865, + 1, 1, 5, 1, 3, 37, 89, 93, 143, 533, 175, + 1, 1, 3, 5, 7, 33, 35, 173, 159, 135, 241, + 1, 1, 1, 15, 17, 37, 79, 131, 43, 891, 229, + 1, 1, 1, 1, 1, 35, 121, 177, 397, 1017, 583, + 1, 1, 3, 15, 31, 21, 43, 67, 467, 923, 1473, + 1, 1, 1, 7, 1, 33, 77, 111, 125, 771, 1975, + 1, 3, 7, 13, 1, 51, 113, 139, 245, 573, 503, + 1, 3, 1, 9, 21, 49, 15, 157, 49, 483, 291, + 1, 1, 1, 1, 29, 35, 17, 65, 403, 485, 1603, + 1, 1, 1, 7, 19, 1, 37, 129, 203, 321, 1809, + 1, 3, 7, 15, 15, 9, 5, 77, 29, 485, 581, + 1, 1, 3, 5, 15, 49, 97, 105, 309, 875, 1581, + 1, 3, 5, 1, 5, 19, 63, 35, 165, 399, 1489, + 1, 3, 5, 3, 23, 5, 79, 137, 115, 599, 1127, + 1, 1, 7, 5, 3, 61, 27, 177, 257, 91, 841, + 1, 1, 3, 5, 9, 31, 91, 209, 409, 661, 159, + 1, 3, 1, 15, 23, 39, 23, 195, 245, 203, 947, + 1, 1, 3, 1, 15, 59, 67, 95, 155, 461, 147, + 1, 3, 7, 5, 23, 25, 87, 11, 51, 449, 1631, + 1, 1, 1, 1, 17, 57, 7, 197, 409, 609, 135, + 1, 1, 1, 9, 1, 61, 115, 113, 495, 895, 1595, + 1, 3, 7, 15, 9, 47, 121, 211, 379, 985, 1755, + 1, 3, 1, 3, 7, 57, 27, 231, 339, 325, 1023, + 1, 1, 1, 1, 19, 63, 63, 239, 31, 643, 373, + 1, 3, 1, 11, 19, 9, 7, 171, 21, 691, 215, + 1, 1, 5, 13, 11, 57, 39, 211, 241, 893, 555, + 1, 1, 7, 5, 29, 21, 45, 59, 509, 223, 491, + 1, 1, 7, 9, 15, 61, 97, 75, 127, 779, 839, + 1, 1, 7, 15, 17, 33, 75, 237, 191, 925, 681, + 1, 3, 5, 7, 27, 57, 123, 111, 101, 371, 1129, + 1, 3, 5, 5, 29, 45, 59, 127, 229, 967, 2027, + 1, 1, 1, 1, 17, 7, 23, 199, 241, 455, 135, + 1, 1, 7, 15, 27, 29, 105, 171, 337, 503, 1817, + 1, 1, 3, 7, 21, 35, 61, 71, 405, 647, 2045, + 1, 1, 1, 1, 1, 15, 65, 167, 501, 79, 737, + 1, 1, 5, 1, 3, 49, 27, 189, 341, 615, 1287, + 1, 1, 1, 9, 1, 7, 31, 159, 503, 327, 1613, + 1, 3, 3, 3, 3, 23, 99, 115, 323, 997, 987, + 1, 1, 1, 9, 19, 33, 93, 247, 509, 453, 891, + 1, 1, 3, 1, 13, 19, 35, 153, 161, 633, 445, + 1, 3, 5, 15, 31, 5, 87, 197, 183, 783, 1823, + 1, 1, 7, 5, 19, 63, 69, 221, 129, 231, 1195, + 1, 1, 5, 5, 13, 23, 19, 231, 245, 917, 379, + 1, 3, 1, 15, 19, 43, 27, 223, 171, 413, 125, + 1, 1, 1, 9, 1, 59, 21, 15, 509, 207, 589, + 1, 3, 5, 3, 19, 31, 113, 19, 23, 733, 499, + 1, 1, 7, 1, 19, 51, 101, 165, 47, 925, 1093, + 1, 3, 3, 9, 15, 21, 43, 243, 237, 461, 1361, + 1, 1, 1, 9, 17, 15, 75, 75, 113, 715, 1419, + 1, 1, 7, 13, 17, 1, 99, 15, 347, 721, 1405, + 1, 1, 7, 15, 7, 27, 23, 183, 39, 59, 571, + 1, 3, 5, 9, 7, 43, 35, 165, 463, 567, 859, + 1, 3, 3, 11, 15, 19, 17, 129, 311, 343, 15, + 1, 1, 1, 15, 31, 59, 63, 39, 347, 359, 105, + 1, 1, 1, 15, 5, 43, 87, 241, 109, 61, 685, + 1, 1, 7, 7, 9, 39, 121, 127, 369, 579, 853, + 1, 1, 1, 1, 17, 15, 15, 95, 325, 627, 299, + 1, 1, 3, 13, 31, 53, 85, 111, 289, 811, 1635, + 1, 3, 7, 1, 19, 29, 75, 185, 153, 573, 653, + 1, 3, 7, 1, 29, 31, 55, 91, 249, 247, 1015, + 1, 3, 5, 7, 1, 49, 113, 139, 257, 127, 307, + 1, 3, 5, 9, 15, 15, 123, 105, 105, 225, 1893, + 1, 3, 3, 1, 15, 5, 105, 249, 73, 709, 1557, + 1, 1, 1, 9, 17, 31, 113, 73, 65, 701, 1439, + 1, 3, 5, 15, 13, 21, 117, 131, 243, 859, 323, + 1, 1, 1, 9, 19, 15, 69, 149, 89, 681, 515, + 1, 1, 1, 5, 29, 13, 21, 97, 301, 27, 967, + 1, 1, 3, 3, 15, 45, 107, 227, 495, 769, 1935, + 1, 1, 1, 11, 5, 27, 41, 173, 261, 703, 1349, + 1, 3, 3, 3, 11, 35, 97, 43, 501, 563, 1331, + 1, 1, 1, 7, 1, 17, 87, 17, 429, 245, 1941, + 1, 1, 7, 15, 29, 13, 1, 175, 425, 233, 797, + 1, 1, 3, 11, 21, 57, 49, 49, 163, 685, 701, + 1, 3, 3, 7, 11, 45, 107, 111, 379, 703, 1403, + 1, 1, 7, 3, 21, 7, 117, 49, 469, 37, 775, + 1, 1, 5, 15, 31, 63, 101, 77, 507, 489, 1955, + 1, 3, 3, 11, 19, 21, 101, 255, 203, 673, 665, + 1, 3, 3, 15, 17, 47, 125, 187, 271, 899, 2003, + 1, 1, 7, 7, 1, 35, 13, 235, 5, 337, 905, + 1, 3, 1, 15, 1, 43, 1, 27, 37, 695, 1429, + 1, 3, 1, 11, 21, 27, 93, 161, 299, 665, 495, + 1, 3, 3, 15, 3, 1, 81, 111, 105, 547, 897, + 1, 3, 5, 1, 3, 53, 97, 253, 401, 827, 1467, + 1, 1, 1, 5, 19, 59, 105, 125, 271, 351, 719, + 1, 3, 5, 13, 7, 11, 91, 41, 441, 759, 1827, + 1, 3, 7, 11, 29, 61, 61, 23, 307, 863, 363, + 1, 1, 7, 1, 15, 35, 29, 133, 415, 473, 1737, + 1, 1, 1, 13, 7, 33, 35, 225, 117, 681, 1545, + 1, 1, 1, 3, 5, 41, 83, 247, 13, 373, 1091, + 1, 3, 1, 13, 25, 61, 71, 217, 233, 313, 547, + 1, 3, 1, 7, 3, 29, 3, 49, 93, 465, 15, + 1, 1, 1, 9, 17, 61, 99, 163, 129, 485, 1087, + 1, 1, 1, 9, 9, 33, 31, 163, 145, 649, 253, + 1, 1, 1, 1, 17, 63, 43, 235, 287, 111, 567, + 1, 3, 5, 13, 29, 7, 11, 69, 153, 127, 449, + 1, 1, 5, 9, 11, 21, 15, 189, 431, 493, 1219, + 1, 1, 1, 15, 19, 5, 47, 91, 399, 293, 1743, + 1, 3, 3, 11, 29, 53, 53, 225, 409, 303, 333, + 1, 1, 1, 15, 31, 31, 21, 81, 147, 287, 1753, + 1, 3, 5, 5, 5, 63, 35, 125, 41, 687, 1793, + 1, 1, 1, 9, 19, 59, 107, 219, 455, 971, 297, + 1, 1, 3, 5, 3, 51, 121, 31, 245, 105, 1311, + 1, 3, 1, 5, 5, 57, 75, 107, 161, 431, 1693, + 1, 3, 1, 3, 19, 53, 27, 31, 191, 565, 1015, + 1, 3, 5, 13, 9, 41, 35, 249, 287, 49, 123, + 1, 1, 5, 7, 27, 17, 21, 3, 151, 885, 1165, + 1, 1, 7, 1, 15, 17, 65, 139, 427, 339, 1171, + 1, 1, 1, 5, 23, 5, 9, 89, 321, 907, 391, + 1, 1, 7, 9, 15, 1, 77, 71, 87, 701, 917, + 1, 1, 7, 1, 17, 37, 115, 127, 469, 779, 1543, + 1, 3, 7, 3, 5, 61, 15, 37, 301, 951, 1437, + 1, 1, 1, 13, 9, 51, 127, 145, 229, 55, 1567, + 1, 3, 7, 15, 19, 47, 53, 153, 295, 47, 1337, + 1, 3, 3, 5, 11, 31, 29, 133, 327, 287, 507, + 1, 1, 7, 7, 25, 31, 37, 199, 25, 927, 1317, + 1, 1, 7, 9, 3, 39, 127, 167, 345, 467, 759, + 1, 1, 1, 1, 31, 21, 15, 101, 293, 787, 1025, + 1, 1, 5, 3, 11, 41, 105, 109, 149, 837, 1813, + 1, 1, 3, 5, 29, 13, 19, 97, 309, 901, 753, + 1, 1, 7, 1, 19, 17, 31, 39, 173, 361, 1177, + 1, 3, 3, 3, 3, 41, 81, 7, 341, 491, 43, + 1, 1, 7, 7, 31, 35, 29, 77, 11, 335, 1275, + 1, 3, 3, 15, 17, 45, 19, 63, 151, 849, 129, + 1, 1, 7, 5, 7, 13, 47, 73, 79, 31, 499, + 1, 3, 1, 11, 1, 41, 59, 151, 247, 115, 1295, + 1, 1, 1, 9, 31, 37, 73, 23, 295, 483, 179, + 1, 3, 1, 15, 13, 63, 81, 27, 169, 825, 2037, + 1, 3, 5, 15, 7, 11, 73, 1, 451, 101, 2039, + 1, 3, 5, 3, 13, 53, 31, 137, 173, 319, 1521, + 1, 3, 1, 3, 29, 1, 73, 227, 377, 337, 1189, + 1, 3, 3, 13, 27, 9, 31, 101, 229, 165, 1983, + 1, 3, 1, 13, 13, 19, 19, 111, 319, 421, 223, + 1, 1, 7, 15, 25, 37, 61, 55, 359, 255, 1955, + 1, 1, 5, 13, 17, 43, 49, 215, 383, 915, 51, + 1, 1, 3, 1, 3, 7, 13, 119, 155, 585, 967, + 1, 3, 1, 13, 1, 63, 125, 21, 103, 287, 457, + 1, 1, 7, 1, 31, 17, 125, 137, 345, 379, 1925, + 1, 1, 3, 5, 5, 25, 119, 153, 455, 271, 2023, + 1, 1, 7, 9, 9, 37, 115, 47, 5, 255, 917, + 1, 3, 5, 3, 31, 21, 75, 203, 489, 593, 1, + 1, 3, 7, 15, 19, 63, 123, 153, 135, 977, 1875, + 1, 1, 1, 1, 5, 59, 31, 25, 127, 209, 745, + 1, 1, 1, 1, 19, 45, 67, 159, 301, 199, 535, + 1, 1, 7, 1, 31, 17, 19, 225, 369, 125, 421, + 1, 3, 3, 11, 7, 59, 115, 197, 459, 469, 1055, + 1, 3, 1, 3, 27, 45, 35, 131, 349, 101, 411, + 1, 3, 7, 11, 9, 3, 67, 145, 299, 253, 1339, + 1, 3, 3, 11, 9, 37, 123, 229, 273, 269, 515, + 1, 3, 7, 15, 11, 25, 75, 5, 367, 217, 951, + 1, 1, 3, 7, 9, 23, 63, 237, 385, 159, 1273, + 1, 1, 5, 11, 23, 5, 55, 193, 109, 865, 663, + 1, 1, 7, 15, 1, 57, 17, 141, 51, 217, 1259, + 1, 1, 3, 3, 15, 7, 89, 233, 71, 329, 203, + 1, 3, 7, 11, 11, 1, 19, 155, 89, 437, 573, + 1, 3, 1, 9, 27, 61, 47, 109, 161, 913, 1681, + 1, 1, 7, 15, 1, 33, 19, 15, 23, 913, 989, + 1, 3, 1, 1, 25, 39, 119, 193, 13, 571, 157, + 1, 1, 7, 13, 9, 55, 59, 147, 361, 935, 515, + 1, 1, 1, 9, 7, 59, 67, 117, 71, 855, 1493, + 1, 3, 1, 3, 13, 19, 57, 141, 305, 275, 1079, + 1, 1, 1, 9, 17, 61, 33, 7, 43, 931, 781, + 1, 1, 3, 1, 11, 17, 21, 97, 295, 277, 1721, + 1, 3, 1, 13, 15, 43, 11, 241, 147, 391, 1641, + 1, 1, 1, 1, 1, 19, 37, 21, 255, 263, 1571, + 1, 1, 3, 3, 23, 59, 89, 17, 475, 303, 757, 543, + 1, 3, 3, 9, 11, 55, 35, 159, 139, 203, 1531, 1825, + 1, 1, 5, 3, 17, 53, 51, 241, 269, 949, 1373, 325, + 1, 3, 7, 7, 5, 29, 91, 149, 239, 193, 1951, 2675, + 1, 3, 5, 1, 27, 33, 69, 11, 51, 371, 833, 2685, + 1, 1, 1, 15, 1, 17, 35, 57, 171, 1007, 449, 367, + 1, 1, 1, 7, 25, 61, 73, 219, 379, 53, 589, 4065, + 1, 3, 5, 13, 21, 29, 45, 19, 163, 169, 147, 597, + 1, 1, 5, 11, 21, 27, 7, 17, 237, 591, 255, 1235, + 1, 1, 7, 7, 17, 41, 69, 237, 397, 173, 1229, 2341, + 1, 1, 3, 1, 1, 33, 125, 47, 11, 783, 1323, 2469, + 1, 3, 1, 11, 3, 39, 35, 133, 153, 55, 1171, 3165, + 1, 1, 5, 11, 27, 23, 103, 245, 375, 753, 477, 2165, + 1, 3, 1, 15, 15, 49, 127, 223, 387, 771, 1719, 1465, + 1, 1, 1, 9, 11, 9, 17, 185, 239, 899, 1273, 3961, + 1, 1, 3, 13, 11, 51, 73, 81, 389, 647, 1767, 1215, + 1, 3, 5, 15, 19, 9, 69, 35, 349, 977, 1603, 1435, + 1, 1, 1, 1, 19, 59, 123, 37, 41, 961, 181, 1275, + 1, 1, 1, 1, 31, 29, 37, 71, 205, 947, 115, 3017, + 1, 1, 7, 15, 5, 37, 101, 169, 221, 245, 687, 195, + 1, 1, 1, 1, 19, 9, 125, 157, 119, 283, 1721, 743, + 1, 1, 7, 3, 1, 7, 61, 71, 119, 257, 1227, 2893, + 1, 3, 3, 3, 25, 41, 25, 225, 31, 57, 925, 2139, + 0 +}; + + +/* + * coefficients of the free direction integers as given in + * "Monte Carlo Methods in Finance", by Peter Jäckel, section 8.3 + */ + +const unsigned long rk_sobol_Jdirections[] = { + 1, + 1, 1, + 1, 3, 7, + 1, 1, 5, + 1, 3, 1, 1, + 1, 1, 3, 7, + 1, 3, 3, 9, 9, + 1, 3, 7, 7, 21, + 1, 1, 5, 11, 27, + 1, 1, 7, 3, 29, + 1, 3, 7, 13, 3, + 1, 3, 5, 1, 15, + 1, 1, 1, 9, 23, 37, + 1, 1, 3, 13, 11, 7, + 1, 3, 3, 5, 19, 33, + 1, 1, 7, 13, 25, 5, + 1, 1, 1, 3, 13, 39, + 1, 3, 5, 11, 7, 11, + 1, 3, 1, 7, 3, 23, 79, + 1, 3, 1, 15, 17, 63, 13, + 1, 3, 3, 3, 25, 17, 115, + 1, 3, 7, 9, 31, 29, 17, + 1, 1, 3, 15, 29, 15, 41, + 1, 3, 1, 9, 5, 21, 119, + 1, 1, 5, 5, 1, 27, 33, + 1, 1, 3, 1, 23, 13, 75, + 1, 1, 7, 7, 19, 25, 105, + 1, 3, 5, 5, 21, 9, 7, + 1, 1, 1, 15, 5, 49, 59, + 1, 3, 5, 15, 17, 19, 21, + 1, 1, 7, 11, 13, 29, 3, + 0 +}; + +/* + * 0 terminated list of primitive polynomials to speed up initialization + * All polynomials up to degree 13 (ie. 1111 polynomials) + */ +static const unsigned long rk_sobol_primitive_polynomials[] = { + 0x1UL, 0x3UL, 0x7UL, 0xBUL, 0xDUL, 0x13UL, 0x19UL, 0x25UL, 0x29UL, + 0x2FUL, 0x37UL, 0x3BUL, 0x3DUL, 0x43UL, 0x5BUL, 0x61UL, 0x67UL, 0x6DUL, + 0x73UL, 0x83UL, 0x89UL, 0x8FUL, 0x91UL, 0x9DUL, 0xA7UL, 0xABUL, 0xB9UL, + 0xBFUL, 0xC1UL, 0xCBUL, 0xD3UL, 0xD5UL, 0xE5UL, 0xEFUL, 0xF1UL, 0xF7UL, + 0xFDUL, 0x11DUL, 0x12BUL, 0x12DUL, 0x14DUL, 0x15FUL, 0x163UL, 0x165UL, + 0x169UL, 0x171UL, 0x187UL, 0x18DUL, 0x1A9UL, 0x1C3UL, 0x1CFUL, 0x1E7UL, + 0x1F5UL, 0x211UL, 0x21BUL, 0x221UL, 0x22DUL, 0x233UL, 0x259UL, 0x25FUL, + 0x269UL, 0x26FUL, 0x277UL, 0x27DUL, 0x287UL, 0x295UL, 0x2A3UL, 0x2A5UL, + 0x2AFUL, 0x2B7UL, 0x2BDUL, 0x2CFUL, 0x2D1UL, 0x2DBUL, 0x2F5UL, 0x2F9UL, + 0x313UL, 0x315UL, 0x31FUL, 0x323UL, 0x331UL, 0x33BUL, 0x34FUL, 0x35BUL, + 0x361UL, 0x36BUL, 0x36DUL, 0x373UL, 0x37FUL, 0x385UL, 0x38FUL, 0x3B5UL, + 0x3B9UL, 0x3C7UL, 0x3CBUL, 0x3CDUL, 0x3D5UL, 0x3D9UL, 0x3E3UL, 0x3E9UL, + 0x3FBUL, 0x409UL, 0x41BUL, 0x427UL, 0x42DUL, 0x465UL, 0x46FUL, 0x481UL, + 0x48BUL, 0x4C5UL, 0x4D7UL, 0x4E7UL, 0x4F3UL, 0x4FFUL, 0x50DUL, 0x519UL, + 0x523UL, 0x531UL, 0x53DUL, 0x543UL, 0x557UL, 0x56BUL, 0x585UL, 0x58FUL, + 0x597UL, 0x5A1UL, 0x5C7UL, 0x5E5UL, 0x5F7UL, 0x5FBUL, 0x613UL, 0x615UL, + 0x625UL, 0x637UL, 0x643UL, 0x64FUL, 0x65BUL, 0x679UL, 0x67FUL, 0x689UL, + 0x6B5UL, 0x6C1UL, 0x6D3UL, 0x6DFUL, 0x6FDUL, 0x717UL, 0x71DUL, 0x721UL, + 0x739UL, 0x747UL, 0x74DUL, 0x755UL, 0x759UL, 0x763UL, 0x77DUL, 0x78DUL, + 0x793UL, 0x7B1UL, 0x7DBUL, 0x7F3UL, 0x7F9UL, 0x805UL, 0x817UL, 0x82BUL, + 0x82DUL, 0x847UL, 0x863UL, 0x865UL, 0x871UL, 0x87BUL, 0x88DUL, 0x895UL, + 0x89FUL, 0x8A9UL, 0x8B1UL, 0x8CFUL, 0x8D1UL, 0x8E1UL, 0x8E7UL, 0x8EBUL, + 0x8F5UL, 0x90DUL, 0x913UL, 0x925UL, 0x929UL, 0x93BUL, 0x93DUL, 0x945UL, + 0x949UL, 0x951UL, 0x95BUL, 0x973UL, 0x975UL, 0x97FUL, 0x983UL, 0x98FUL, + 0x9ABUL, 0x9ADUL, 0x9B9UL, 0x9C7UL, 0x9D9UL, 0x9E5UL, 0x9F7UL, 0xA01UL, + 0xA07UL, 0xA13UL, 0xA15UL, 0xA29UL, 0xA49UL, 0xA61UL, 0xA6DUL, 0xA79UL, + 0xA7FUL, 0xA85UL, 0xA91UL, 0xA9DUL, 0xAA7UL, 0xAABUL, 0xAB3UL, 0xAB5UL, + 0xAD5UL, 0xADFUL, 0xAE9UL, 0xAEFUL, 0xAF1UL, 0xAFBUL, 0xB03UL, 0xB09UL, + 0xB11UL, 0xB33UL, 0xB3FUL, 0xB41UL, 0xB4BUL, 0xB59UL, 0xB5FUL, 0xB65UL, + 0xB6FUL, 0xB7DUL, 0xB87UL, 0xB8BUL, 0xB93UL, 0xB95UL, 0xBAFUL, 0xBB7UL, + 0xBBDUL, 0xBC9UL, 0xBDBUL, 0xBDDUL, 0xBE7UL, 0xBEDUL, 0xC0BUL, 0xC0DUL, + 0xC19UL, 0xC1FUL, 0xC57UL, 0xC61UL, 0xC6BUL, 0xC73UL, 0xC85UL, 0xC89UL, + 0xC97UL, 0xC9BUL, 0xC9DUL, 0xCB3UL, 0xCBFUL, 0xCC7UL, 0xCCDUL, 0xCD3UL, + 0xCD5UL, 0xCE3UL, 0xCE9UL, 0xCF7UL, 0xD03UL, 0xD0FUL, 0xD1DUL, 0xD27UL, + 0xD2DUL, 0xD41UL, 0xD47UL, 0xD55UL, 0xD59UL, 0xD63UL, 0xD6FUL, 0xD71UL, + 0xD93UL, 0xD9FUL, 0xDA9UL, 0xDBBUL, 0xDBDUL, 0xDC9UL, 0xDD7UL, 0xDDBUL, + 0xDE1UL, 0xDE7UL, 0xDF5UL, 0xE05UL, 0xE1DUL, 0xE21UL, 0xE27UL, 0xE2BUL, + 0xE33UL, 0xE39UL, 0xE47UL, 0xE4BUL, 0xE55UL, 0xE5FUL, 0xE71UL, 0xE7BUL, + 0xE7DUL, 0xE81UL, 0xE93UL, 0xE9FUL, 0xEA3UL, 0xEBBUL, 0xECFUL, 0xEDDUL, + 0xEF3UL, 0xEF9UL, 0xF0BUL, 0xF19UL, 0xF31UL, 0xF37UL, 0xF5DUL, 0xF6BUL, + 0xF6DUL, 0xF75UL, 0xF83UL, 0xF91UL, 0xF97UL, 0xF9BUL, 0xFA7UL, 0xFADUL, + 0xFB5UL, 0xFCDUL, 0xFD3UL, 0xFE5UL, 0xFE9UL, 0x1053UL, 0x1069UL, + 0x107BUL, 0x107DUL, 0x1099UL, 0x10D1UL, 0x10EBUL, 0x1107UL, 0x111FUL, + 0x1123UL, 0x113BUL, 0x114FUL, 0x1157UL, 0x1161UL, 0x116BUL, 0x1185UL, + 0x11B3UL, 0x11D9UL, 0x11DFUL, 0x120DUL, 0x1237UL, 0x123DUL, 0x1267UL, + 0x1273UL, 0x127FUL, 0x12B9UL, 0x12C1UL, 0x12CBUL, 0x130FUL, 0x131DUL, + 0x1321UL, 0x1339UL, 0x133FUL, 0x134DUL, 0x1371UL, 0x1399UL, 0x13A3UL, + 0x13A9UL, 0x1407UL, 0x1431UL, 0x1437UL, 0x144FUL, 0x145DUL, 0x1467UL, + 0x1475UL, 0x14A7UL, 0x14ADUL, 0x14D3UL, 0x150FUL, 0x151DUL, 0x154DUL, + 0x1593UL, 0x15C5UL, 0x15D7UL, 0x15DDUL, 0x15EBUL, 0x1609UL, 0x1647UL, + 0x1655UL, 0x1659UL, 0x16A5UL, 0x16BDUL, 0x1715UL, 0x1719UL, 0x1743UL, + 0x1745UL, 0x1775UL, 0x1789UL, 0x17ADUL, 0x17B3UL, 0x17BFUL, 0x17C1UL, + 0x1857UL, 0x185DUL, 0x1891UL, 0x1897UL, 0x18B9UL, 0x18EFUL, 0x191BUL, + 0x1935UL, 0x1941UL, 0x1965UL, 0x197BUL, 0x198BUL, 0x19B1UL, 0x19BDUL, + 0x19C9UL, 0x19CFUL, 0x19E7UL, 0x1A1BUL, 0x1A2BUL, 0x1A33UL, 0x1A69UL, + 0x1A8BUL, 0x1AD1UL, 0x1AE1UL, 0x1AF5UL, 0x1B0BUL, 0x1B13UL, 0x1B1FUL, + 0x1B57UL, 0x1B91UL, 0x1BA7UL, 0x1BBFUL, 0x1BC1UL, 0x1BD3UL, 0x1C05UL, + 0x1C11UL, 0x1C17UL, 0x1C27UL, 0x1C4DUL, 0x1C87UL, 0x1C9FUL, 0x1CA5UL, + 0x1CBBUL, 0x1CC5UL, 0x1CC9UL, 0x1CCFUL, 0x1CF3UL, 0x1D07UL, 0x1D23UL, + 0x1D43UL, 0x1D51UL, 0x1D5BUL, 0x1D75UL, 0x1D85UL, 0x1D89UL, 0x1E15UL, + 0x1E19UL, 0x1E2FUL, 0x1E45UL, 0x1E51UL, 0x1E67UL, 0x1E73UL, 0x1E8FUL, + 0x1EE3UL, 0x1F11UL, 0x1F1BUL, 0x1F27UL, 0x1F71UL, 0x1F99UL, 0x1FBBUL, + 0x1FBDUL, 0x1FC9UL, 0x201BUL, 0x2027UL, 0x2035UL, 0x2053UL, 0x2065UL, + 0x206FUL, 0x208BUL, 0x208DUL, 0x209FUL, 0x20A5UL, 0x20AFUL, 0x20BBUL, + 0x20BDUL, 0x20C3UL, 0x20C9UL, 0x20E1UL, 0x20F3UL, 0x210DUL, 0x2115UL, + 0x2129UL, 0x212FUL, 0x213BUL, 0x2143UL, 0x2167UL, 0x216BUL, 0x2179UL, + 0x2189UL, 0x2197UL, 0x219DUL, 0x21BFUL, 0x21C1UL, 0x21C7UL, 0x21CDUL, + 0x21DFUL, 0x21E3UL, 0x21F1UL, 0x21FBUL, 0x2219UL, 0x2225UL, 0x2237UL, + 0x223DUL, 0x2243UL, 0x225BUL, 0x225DUL, 0x2279UL, 0x227FUL, 0x2289UL, + 0x2297UL, 0x229BUL, 0x22B3UL, 0x22BFUL, 0x22CDUL, 0x22EFUL, 0x22F7UL, + 0x22FBUL, 0x2305UL, 0x2327UL, 0x232BUL, 0x2347UL, 0x2355UL, 0x2359UL, + 0x236FUL, 0x2371UL, 0x237DUL, 0x2387UL, 0x238DUL, 0x2395UL, 0x23A3UL, + 0x23A9UL, 0x23B1UL, 0x23B7UL, 0x23BBUL, 0x23E1UL, 0x23EDUL, 0x23F9UL, + 0x240BUL, 0x2413UL, 0x241FUL, 0x2425UL, 0x2429UL, 0x243DUL, 0x2451UL, + 0x2457UL, 0x2461UL, 0x246DUL, 0x247FUL, 0x2483UL, 0x249BUL, 0x249DUL, + 0x24B5UL, 0x24BFUL, 0x24C1UL, 0x24C7UL, 0x24CBUL, 0x24E3UL, 0x2509UL, + 0x2517UL, 0x251DUL, 0x2521UL, 0x252DUL, 0x2539UL, 0x2553UL, 0x2555UL, + 0x2563UL, 0x2571UL, 0x2577UL, 0x2587UL, 0x258BUL, 0x2595UL, 0x2599UL, + 0x259FUL, 0x25AFUL, 0x25BDUL, 0x25C5UL, 0x25CFUL, 0x25D7UL, 0x25EBUL, + 0x2603UL, 0x2605UL, 0x2611UL, 0x262DUL, 0x263FUL, 0x264BUL, 0x2653UL, + 0x2659UL, 0x2669UL, 0x2677UL, 0x267BUL, 0x2687UL, 0x2693UL, 0x2699UL, + 0x26B1UL, 0x26B7UL, 0x26BDUL, 0x26C3UL, 0x26EBUL, 0x26F5UL, 0x2713UL, + 0x2729UL, 0x273BUL, 0x274FUL, 0x2757UL, 0x275DUL, 0x276BUL, 0x2773UL, + 0x2779UL, 0x2783UL, 0x2791UL, 0x27A1UL, 0x27B9UL, 0x27C7UL, 0x27CBUL, + 0x27DFUL, 0x27EFUL, 0x27F1UL, 0x2807UL, 0x2819UL, 0x281FUL, 0x2823UL, + 0x2831UL, 0x283BUL, 0x283DUL, 0x2845UL, 0x2867UL, 0x2875UL, 0x2885UL, + 0x28ABUL, 0x28ADUL, 0x28BFUL, 0x28CDUL, 0x28D5UL, 0x28DFUL, 0x28E3UL, + 0x28E9UL, 0x28FBUL, 0x2909UL, 0x290FUL, 0x2911UL, 0x291BUL, 0x292BUL, + 0x2935UL, 0x293FUL, 0x2941UL, 0x294BUL, 0x2955UL, 0x2977UL, 0x297DUL, + 0x2981UL, 0x2993UL, 0x299FUL, 0x29AFUL, 0x29B7UL, 0x29BDUL, 0x29C3UL, + 0x29D7UL, 0x29F3UL, 0x29F5UL, 0x2A03UL, 0x2A0FUL, 0x2A1DUL, 0x2A21UL, + 0x2A33UL, 0x2A35UL, 0x2A4DUL, 0x2A69UL, 0x2A6FUL, 0x2A71UL, 0x2A7BUL, + 0x2A7DUL, 0x2AA5UL, 0x2AA9UL, 0x2AB1UL, 0x2AC5UL, 0x2AD7UL, 0x2ADBUL, + 0x2AEBUL, 0x2AF3UL, 0x2B01UL, 0x2B15UL, 0x2B23UL, 0x2B25UL, 0x2B2FUL, + 0x2B37UL, 0x2B43UL, 0x2B49UL, 0x2B6DUL, 0x2B7FUL, 0x2B85UL, 0x2B97UL, + 0x2B9BUL, 0x2BADUL, 0x2BB3UL, 0x2BD9UL, 0x2BE5UL, 0x2BFDUL, 0x2C0FUL, + 0x2C21UL, 0x2C2BUL, 0x2C2DUL, 0x2C3FUL, 0x2C41UL, 0x2C4DUL, 0x2C71UL, + 0x2C8BUL, 0x2C8DUL, 0x2C95UL, 0x2CA3UL, 0x2CAFUL, 0x2CBDUL, 0x2CC5UL, + 0x2CD1UL, 0x2CD7UL, 0x2CE1UL, 0x2CE7UL, 0x2CEBUL, 0x2D0DUL, 0x2D19UL, + 0x2D29UL, 0x2D2FUL, 0x2D37UL, 0x2D3BUL, 0x2D45UL, 0x2D5BUL, 0x2D67UL, + 0x2D75UL, 0x2D89UL, 0x2D8FUL, 0x2DA7UL, 0x2DABUL, 0x2DB5UL, 0x2DE3UL, + 0x2DF1UL, 0x2DFDUL, 0x2E07UL, 0x2E13UL, 0x2E15UL, 0x2E29UL, 0x2E49UL, + 0x2E4FUL, 0x2E5BUL, 0x2E5DUL, 0x2E61UL, 0x2E6BUL, 0x2E8FUL, 0x2E91UL, + 0x2E97UL, 0x2E9DUL, 0x2EABUL, 0x2EB3UL, 0x2EB9UL, 0x2EDFUL, 0x2EFBUL, + 0x2EFDUL, 0x2F05UL, 0x2F09UL, 0x2F11UL, 0x2F17UL, 0x2F3FUL, 0x2F41UL, + 0x2F4BUL, 0x2F4DUL, 0x2F59UL, 0x2F5FUL, 0x2F65UL, 0x2F69UL, 0x2F95UL, + 0x2FA5UL, 0x2FAFUL, 0x2FB1UL, 0x2FCFUL, 0x2FDDUL, 0x2FE7UL, 0x2FEDUL, + 0x2FF5UL, 0x2FFFUL, 0x3007UL, 0x3015UL, 0x3019UL, 0x302FUL, 0x3049UL, + 0x304FUL, 0x3067UL, 0x3079UL, 0x307FUL, 0x3091UL, 0x30A1UL, 0x30B5UL, + 0x30BFUL, 0x30C1UL, 0x30D3UL, 0x30D9UL, 0x30E5UL, 0x30EFUL, 0x3105UL, + 0x310FUL, 0x3135UL, 0x3147UL, 0x314DUL, 0x315FUL, 0x3163UL, 0x3171UL, + 0x317BUL, 0x31A3UL, 0x31A9UL, 0x31B7UL, 0x31C5UL, 0x31C9UL, 0x31DBUL, + 0x31E1UL, 0x31EBUL, 0x31EDUL, 0x31F3UL, 0x31FFUL, 0x3209UL, 0x320FUL, + 0x321DUL, 0x3227UL, 0x3239UL, 0x324BUL, 0x3253UL, 0x3259UL, 0x3265UL, + 0x3281UL, 0x3293UL, 0x3299UL, 0x329FUL, 0x32A9UL, 0x32B7UL, 0x32BBUL, + 0x32C3UL, 0x32D7UL, 0x32DBUL, 0x32E7UL, 0x3307UL, 0x3315UL, 0x332FUL, + 0x3351UL, 0x335DUL, 0x3375UL, 0x3397UL, 0x339BUL, 0x33ABUL, 0x33B9UL, + 0x33C1UL, 0x33C7UL, 0x33D5UL, 0x33E3UL, 0x33E5UL, 0x33F7UL, 0x33FBUL, + 0x3409UL, 0x341BUL, 0x3427UL, 0x3441UL, 0x344DUL, 0x345FUL, 0x3469UL, + 0x3477UL, 0x347BUL, 0x3487UL, 0x3493UL, 0x3499UL, 0x34A5UL, 0x34BDUL, + 0x34C9UL, 0x34DBUL, 0x34E7UL, 0x34F9UL, 0x350DUL, 0x351FUL, 0x3525UL, + 0x3531UL, 0x3537UL, 0x3545UL, 0x354FUL, 0x355DUL, 0x356DUL, 0x3573UL, + 0x357FUL, 0x359DUL, 0x35A1UL, 0x35B9UL, 0x35CDUL, 0x35D5UL, 0x35D9UL, + 0x35E3UL, 0x35E9UL, 0x35EFUL, 0x3601UL, 0x360BUL, 0x361FUL, 0x3625UL, + 0x362FUL, 0x363BUL, 0x3649UL, 0x3651UL, 0x365BUL, 0x3673UL, 0x3675UL, + 0x3691UL, 0x369BUL, 0x369DUL, 0x36ADUL, 0x36CBUL, 0x36D3UL, 0x36D5UL, + 0x36E3UL, 0x36EFUL, 0x3705UL, 0x370FUL, 0x371BUL, 0x3721UL, 0x372DUL, + 0x3739UL, 0x3741UL, 0x3747UL, 0x3753UL, 0x3771UL, 0x3777UL, 0x378BUL, + 0x3795UL, 0x3799UL, 0x37A3UL, 0x37C5UL, 0x37CFUL, 0x37D1UL, 0x37D7UL, + 0x37DDUL, 0x37E1UL, 0x37F3UL, 0x3803UL, 0x3805UL, 0x3817UL, 0x381DUL, + 0x3827UL, 0x3833UL, 0x384BUL, 0x3859UL, 0x3869UL, 0x3871UL, 0x38A3UL, + 0x38B1UL, 0x38BBUL, 0x38C9UL, 0x38CFUL, 0x38E1UL, 0x38F3UL, 0x38F9UL, + 0x3901UL, 0x3907UL, 0x390BUL, 0x3913UL, 0x3931UL, 0x394FUL, 0x3967UL, + 0x396DUL, 0x3983UL, 0x3985UL, 0x3997UL, 0x39A1UL, 0x39A7UL, 0x39ADUL, + 0x39CBUL, 0x39CDUL, 0x39D3UL, 0x39EFUL, 0x39F7UL, 0x39FDUL, 0x3A07UL, + 0x3A29UL, 0x3A2FUL, 0x3A3DUL, 0x3A51UL, 0x3A5DUL, 0x3A61UL, 0x3A67UL, + 0x3A73UL, 0x3A75UL, 0x3A89UL, 0x3AB9UL, 0x3ABFUL, 0x3ACDUL, 0x3AD3UL, + 0x3AD5UL, 0x3ADFUL, 0x3AE5UL, 0x3AE9UL, 0x3AFBUL, 0x3B11UL, 0x3B2BUL, + 0x3B2DUL, 0x3B35UL, 0x3B3FUL, 0x3B53UL, 0x3B59UL, 0x3B63UL, 0x3B65UL, + 0x3B6FUL, 0x3B71UL, 0x3B77UL, 0x3B8BUL, 0x3B99UL, 0x3BA5UL, 0x3BA9UL, + 0x3BB7UL, 0x3BBBUL, 0x3BD1UL, 0x3BE7UL, 0x3BF3UL, 0x3BFFUL, 0x3C0DUL, + 0x3C13UL, 0x3C15UL, 0x3C1FUL, 0x3C23UL, 0x3C25UL, 0x3C3BUL, 0x3C4FUL, + 0x3C5DUL, 0x3C6DUL, 0x3C83UL, 0x3C8FUL, 0x3C9DUL, 0x3CA7UL, 0x3CABUL, + 0x3CB9UL, 0x3CC7UL, 0x3CE9UL, 0x3CFBUL, 0x3CFDUL, 0x3D03UL, 0x3D17UL, + 0x3D1BUL, 0x3D21UL, 0x3D2DUL, 0x3D33UL, 0x3D35UL, 0x3D41UL, 0x3D4DUL, + 0x3D65UL, 0x3D69UL, 0x3D7DUL, 0x3D81UL, 0x3D95UL, 0x3DB1UL, 0x3DB7UL, + 0x3DC3UL, 0x3DD1UL, 0x3DDBUL, 0x3DE7UL, 0x3DEBUL, 0x3DF9UL, 0x3E05UL, + 0x3E09UL, 0x3E0FUL, 0x3E1BUL, 0x3E2BUL, 0x3E3FUL, 0x3E41UL, 0x3E53UL, + 0x3E65UL, 0x3E69UL, 0x3E8BUL, 0x3EA3UL, 0x3EBDUL, 0x3EC5UL, 0x3ED7UL, + 0x3EDDUL, 0x3EE1UL, 0x3EF9UL, 0x3F0DUL, 0x3F19UL, 0x3F1FUL, 0x3F25UL, + 0x3F37UL, 0x3F3DUL, 0x3F43UL, 0x3F45UL, 0x3F49UL, 0x3F51UL, 0x3F57UL, + 0x3F61UL, 0x3F83UL, 0x3F89UL, 0x3F91UL, 0x3FABUL, 0x3FB5UL, 0x3FE3UL, + 0x3FF7UL, 0x3FFDUL, + 0UL +}; + +rk_sobol_error rk_sobol_init(size_t dimension, rk_sobol_state *s, + rk_state *rs_dir, const unsigned long *directions, + const unsigned long *polynomials) +{ + rk_state rs_dir_temp; + int j, l, degree = 0, last_degree = 0, ooord = 0; + size_t k, cdir = 0, cpol = 0; + unsigned long polynomial = 1, rev = 0, last = 0; + + if (dimension == 0) + return RK_SOBOL_EINVAL; + + if (polynomials == NULL) + polynomials = rk_sobol_primitive_polynomials; + + /* Allocate the structure */ + s->direction = NULL; s->numerator = NULL; + s->direction = malloc(sizeof(*(s->direction))*dimension*LONG_BIT); + s->numerator = malloc(sizeof(*(s->numerator))*dimension); + if (!s->direction | !s->numerator) + { + if (!s->direction) free(s->direction); + if (!s->numerator) free(s->numerator); + return RK_SOBOL_ENOMEM; + } + + /* Initialize directions */ + /* Degree 0 */ + for (j = degree; j < LONG_BIT; j++) + s->direction[j*dimension] = 1UL << (LONG_BIT-j-1); + + /* Skip unused first polynomial */ + if (polynomials[cpol]) + cpol++; + + /* Degree >0 */ + for (k = 1; k < dimension; k++) + { + unsigned long temp; + + /* Find a new primitive polynomial */ + if (polynomials[cpol]) + polynomial = polynomials[cpol++]; + else if (rev) + { + /* We are generating polynomials out of order: + use the reverse of the previous polynomial */ + last = polynomial; + polynomial = rev; + rev = 0; + } + else + { + if (last) + { + polynomial = last; + last = 0; + } + + /* Find a new primitive polynomial */ + while(1) + { + if (polynomial == ULONG_MAX) + { + /* Not enough polynomials */ + free(s->direction); + free(s->numerator); + return RK_SOBOL_EINVAL; + } + + polynomial += 2; + + if (ooord) + { + unsigned long copy = polynomial; + /* We are generating polynomials out of order: + check if the reverse was already checked */ + for (rev = 0; copy; copy >>= 1) + rev = (rev << 1) | (copy & 1); + if (ooord && rev < polynomial) + continue; + } + + if (rk_isprimitive(polynomial)) + break; + } + + if (rev == polynomial) + /* We are generating polynomials out of order: + the reverse is not different, discard it */ + rev = 0; + } + + /* Compute the degree */ + for (temp = polynomial >> 1, degree = 0; temp; degree++, temp >>= 1); + + for (j=0; jdirection[j*dimension+k] = m << (LONG_BIT-j-1); + } + + /* Scaled recursion for directions */ + for (j = degree; j < LONG_BIT; j++) + { + unsigned long effdir = s->direction[(j-degree)*dimension+k], + ptemp = polynomial >> 1; + effdir ^= (effdir >> degree); + for (l = degree-1; l >= 1; l--, ptemp >>= 1) + if (ptemp & 1) + effdir ^= s->direction[(j-l)*dimension+k]; + s->direction[j*dimension+k] = effdir; + } + + /* Can we generate polynomials out of order ? */ + if (!ooord && polynomials[cpol] == 0 && degree > last_degree + && (directions == NULL || directions[cdir] == 0)) + ooord = 0; + else + last_degree = degree; + } + + /* Initialize numerator */ + for (k=0; knumerator[k] = 0; + + s->dimension = dimension; + s->gcount = 0; + s->count = 0; + return RK_SOBOL_OK; +} + +void rk_sobol_reinit(rk_sobol_state *s) +{ + size_t k; + + /* Initialize numerator */ + for (k=0; kdimension; k++) + s->numerator[k] = 0; + + s->count = 0; + s->gcount = 0; +} + +void rk_sobol_randomshift(rk_sobol_state *s, rk_state *rs_num) +{ + rk_state rs_num_temp; + size_t k; + + if (rs_num == NULL) + { + rs_num = &rs_num_temp; + rk_randomseed(rs_num); + } + + /* Initialize numerator */ + for (k=0; kdimension; k++) + s->numerator[k] = rk_ulong(rs_num); +} + +rk_sobol_error rk_sobol_copy(rk_sobol_state *copy, rk_sobol_state *orig) +{ + size_t k; + + /* Allocate the structure */ + copy->direction = NULL; copy->numerator = NULL; + copy->direction = malloc(sizeof(*(copy->direction))*orig->dimension*LONG_BIT); + copy->numerator = malloc(sizeof(*(copy->numerator))*orig->dimension); + if (!copy->direction | !copy->numerator) + { + if (!copy->direction) free(copy->direction); + if (!copy->numerator) free(copy->numerator); + return RK_SOBOL_ENOMEM; + } + + /* Initialize numerator */ + for (k=0; kdimension; k++) + copy->numerator[k] = orig->numerator[k]; + for (k=0; k<(orig->dimension*LONG_BIT); k++) + copy->direction[k] = orig->direction[k]; + + copy->count = orig->count; + copy->gcount = orig->gcount; + copy->dimension = orig->dimension; + + return RK_SOBOL_OK; +} + +rk_sobol_error rk_sobol_double(rk_sobol_state *s, double *x) +{ + int j; + size_t k; + unsigned long im; + const double inverse_denominator=1.0/(ULONG_MAX+1.0); + + if (s->count == ULONG_MAX) + j = 0; + else + for (im = s->count, j=0; im & 1; j++, im >>= 1); + s->count++; + + for (k=0; kdimension; k++) + { + s->numerator[k] ^= s->direction[j*s->dimension+k]; + x[k] = s->numerator[k]*inverse_denominator; + } + + if ((s->gcount++) == ULONG_MAX) return RK_SOBOL_EXHAUST; + return RK_SOBOL_OK; +} + +void rk_sobol_setcount(rk_sobol_state *s, unsigned long count) +{ + s->count = count; +} + +void rk_sobol_free(rk_sobol_state *s) +{ + free(s->direction); + free(s->numerator); +} + +double inverse_normal(double p) +{ + double q, t, x; + + /* Peter J. Acklam constants for the rational approximation */ + const double a[6] = + { + -3.969683028665376e+01, 2.209460984245205e+02, + -2.759285104469687e+02, 1.383577518672690e+02, + -3.066479806614716e+01, 2.506628277459239e+00 + }; + const double b[5] = + { + -5.447609879822406e+01, 1.615858368580409e+02, + -1.556989798598866e+02, 6.680131188771972e+01, + -1.328068155288572e+01 + }; + const double c[6] = + { + -7.784894002430293e-03, -3.223964580411365e-01, + -2.400758277161838e+00, -2.549732539343734e+00, + 4.374664141464968e+00, 2.938163982698783e+00 + }; + const double d[4] = + { + 7.784695709041462e-03, 3.224671290700398e-01, + 2.445134137142996e+00, 3.754408661907416e+00 + }; + + if (p <= 0) + return -HUGE_VAL; + else if (p >= 1) + return HUGE_VAL; + + q = p<0.5 ? p : 1-p; + if (q > 0.02425) + { + /* Rational approximation for central region */ + x = q-0.5; + t = x*x; + x = x*(((((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4])*t+a[5]) + /(((((b[0]*t+b[1])*t+b[2])*t+b[3])*t+b[4])*t+1); + } + else + { + /* Rational approximation for tail region */ + t = sqrt(-2*log(q)); + x = (((((c[0]*t+c[1])*t+c[2])*t+c[3])*t+c[4])*t+c[5]) + /((((d[0]*t+d[1])*t+d[2])*t+d[3])*t+1); + } + + /* If we have erfc, improve the precision */ +#ifndef WIN32 + /* Halley's rational method */ + t = (erfc(-x*M_SQRT1_2)/2 - q) * RK_SOBOL_M_SQRT2PI * exp(x*x/2); + x -= t/(1 + x*t/2); +#endif + + return p>0.5 ? -x : x; +} + +rk_sobol_error rk_sobol_gauss(rk_sobol_state *s, double *x) +{ + size_t k; + rk_sobol_error rc = rk_sobol_double(s, x); + + for (k=0; kdimension; k++) + x[k] = inverse_normal(x[k]); + + return rc; +} diff --git a/tardis/montecarlo/src/randomkit/rk_sobol.h b/tardis/montecarlo/src/randomkit/rk_sobol.h new file mode 100644 index 00000000000..aca71bebe1e --- /dev/null +++ b/tardis/montecarlo/src/randomkit/rk_sobol.h @@ -0,0 +1,173 @@ +/* Random kit 1.6 */ + +/* + * Copyright (c) 2004-2006, Jean-Sebastien Roy (js@jeannot.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* @(#) $Jeannot: rk_sobol.h,v 1.7 2006/02/19 13:48:34 js Exp $ */ + +/* + * Typical use: + * + * int dimension = 2; + * rk_sobol_state s; + * rk_sobol_error rc; + * double x[dimension], y[dimension]; + * + * // Init + * if (rc = rk_sobol_init(dimension, &s, NULL, NULL, NULL)) + * { + * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); + * abort(); + * } + * + * // Draw uniform quasirandom doubles + * if (rc = rk_sobol_double(&s, x)) + * { + * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); + * abort(); + * } + * + * // Draw gaussian quasirandom doubles + * if (rc = rk_sobol_gauss(&s, y)) + * { + * fprintf(stderr, "%s\n", rk_sobol_strerror[rc]); + * abort(); + * } + * + * // Free allocated memory + * rk_sobol_free(&s); + */ + + +#ifndef _RK_SOBOL_ +#define _RK_SOBOL_ + +#include "rk_mt.h" + +typedef enum { + RK_SOBOL_OK = 0, /* No error */ + RK_SOBOL_EINVAL = 1, /* Invalid dimension (<= 0 or too large) */ + RK_SOBOL_EXHAUST = 2, /* Too many number generated */ + RK_SOBOL_ENOMEM = 3, /* Not enough memory */ + RK_SOBOL_ERR_MAX = 4 +} rk_sobol_error; + +/* error strings */ +extern char *rk_sobol_strerror[]; + +typedef struct +{ + size_t dimension; + unsigned long *direction; + unsigned long *numerator; + unsigned long count; + unsigned long gcount; +} rk_sobol_state; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Sobol directions initializations (zero terminated lists) */ + +/* + * Sobol/Levitan coefficients of the free direction integers as given + * by Bratley, P., Fox, B.L. (1988) + * Defined up to dimension 40. + */ +extern const unsigned long rk_sobol_SLdirections[]; + +/* + * Lemieux coefficients of the free direction integers as given + * in QuantLib by Christiane Lemieux, private communication, September 2004 + * Defined up to dimension 360. + */ +extern const unsigned long rk_sobol_Ldirections[]; + +/* + * Peter Jäckel coefficients of the free direction integers as given + * in "Monte Carlo Methods in Finance", by Peter Jäckel, section 8.3 + * Defined up to dimension 32. + */ +extern const unsigned long rk_sobol_Jdirections[]; + +/* + * Initialize a sobol quasirandom number generator. + * 1 <= dimension <= the number of primitive polylonimals of degree < LONG_BIT + * If directions == NULL (or more directions than provided are required), + * the directions are picked at random using rs_dir. + * If rs_dir == NULL, it is initialized using rk_randomseed. + * polynomials is a zero terminated list of primitive polynomials to use if + * it is != NULL to speed up initialization for dimension > 1024. + */ +extern rk_sobol_error rk_sobol_init(size_t dimension, rk_sobol_state *s, + rk_state *rs_dir, const unsigned long *directions, + const unsigned long *polynomials); + +/* + * Reinitialize the random generator with same directions. + */ +extern void rk_sobol_reinit(rk_sobol_state *s); + +/* + * You can change the starting rank in the sequence by changing s->count. + */ +extern void rk_sobol_setcount(rk_sobol_state *s, unsigned long count); + +/* + * XOR the numerators at random using rs_num. + * To be used once, after (re-)initialization. + * Useful for randomized quasi monte carlo. + * If rs_num == NULL, it is initialized using rk_randomseed. + */ +extern void rk_sobol_randomshift(rk_sobol_state *s, rk_state *rs_num); + +/* + * Copy a sobol generator. + * Can be used to avoid the time consuming initialization. + */ +extern rk_sobol_error rk_sobol_copy(rk_sobol_state *copy, rk_sobol_state *orig); + +/* + * Free the memory allocated by rk_sobol_init + */ +extern void rk_sobol_free(rk_sobol_state *s); + +/* + * return a vector of dimension quasirandom uniform deviates between 0 and 1 + */ +extern rk_sobol_error rk_sobol_double(rk_sobol_state *s, double *x); + +/* + * return a vector of dimension quasirandom gaussian deviates + * with variance unity and zero mean. + * On Windows, the standard function erfc is missing, which results in + * lower precision (9 digits instead of full precision). + */ +extern rk_sobol_error rk_sobol_gauss(rk_sobol_state *s, double *x); + +#ifdef __cplusplus +} +#endif + +#endif /* _RK_SOBOL_ */ diff --git a/tardis/montecarlo/src/rpacket.c b/tardis/montecarlo/src/rpacket.c new file mode 100644 index 00000000000..cb52cddc698 --- /dev/null +++ b/tardis/montecarlo/src/rpacket.c @@ -0,0 +1,55 @@ +#include +#include +#include "rpacket.h" +#include "storage.h" + +extern tardis_error_t line_search (const double *nu, double nu_insert, + int64_t number_of_lines, int64_t * result); + +tardis_error_t +rpacket_init (rpacket_t * packet, storage_model_t * storage, int packet_index, + int virtual_packet_flag, double * chi_bf_tmp_partial) +{ + int64_t current_line_id; + tardis_error_t ret_val = TARDIS_ERROR_OK; + double current_nu = storage->packet_nus[packet_index]; + double current_energy = storage->packet_energies[packet_index]; + double current_mu = storage->packet_mus[packet_index]; + double comov_current_nu = current_nu; + int current_shell_id = 0; + double current_r = storage->r_inner[0]; + double beta = current_r * storage->inverse_time_explosion * INVERSE_C; + + if (storage->full_relativity) + { + current_nu = current_nu * (1 + beta * current_mu) / sqrt(1 - beta * beta); + current_energy = current_energy * (1 + beta * current_mu) / sqrt(1 - beta * beta); + current_mu = (current_mu + beta) / (1 + beta * current_mu); + } + else + { + current_nu = current_nu / (1 - beta * current_mu); + current_energy = current_energy / (1 - beta * current_mu); + } + if ((ret_val = + line_search (storage->line_list_nu, comov_current_nu, + storage->no_of_lines, + ¤t_line_id)) != TARDIS_ERROR_OK) + { + return ret_val; + } + bool last_line = (current_line_id == storage->no_of_lines); + rpacket_set_nu (packet, current_nu); + rpacket_set_mu (packet, current_mu); + rpacket_set_energy (packet, current_energy); + rpacket_set_r (packet, current_r); + rpacket_set_current_shell_id (packet, current_shell_id); + rpacket_set_next_line_id (packet, current_line_id); + rpacket_set_last_line (packet, last_line); + rpacket_set_close_line (packet, false); + rpacket_set_virtual_packet_flag (packet, virtual_packet_flag); + packet->chi_bf_tmp_partial = chi_bf_tmp_partial; + packet->compute_chi_bf = true; + packet->vpacket_weight = 1.0; + return ret_val; +} diff --git a/tardis/montecarlo/src/rpacket.h b/tardis/montecarlo/src/rpacket.h new file mode 100644 index 00000000000..2a30546a837 --- /dev/null +++ b/tardis/montecarlo/src/rpacket.h @@ -0,0 +1,330 @@ +#ifndef TARDIS_RPACKET_H +#define TARDIS_RPACKET_H + +#include +#include +#include +#include "randomkit/randomkit.h" +#include "status.h" +#include "storage.h" + +#define MISS_DISTANCE 1e99 +#define C 29979245800.0 +#define INVERSE_C 3.33564095198152e-11 +#define H 6.6260755e-27 // erg * s, converted to CGS units from the NIST Constant Index +#define KB 1.3806488e-16 // erg / K converted to CGS units from the NIST Constant Index + +/** + * @brief A photon packet. + */ +typedef struct RPacket +{ + double nu; /**< Frequency of the packet in Hz. */ + double mu; /**< Cosine of the angle of the packet. */ + double energy; /**< Energy of the packet in erg. */ + double r; /**< Distance from center in cm. */ + double tau_event; + double nu_line; + int64_t current_shell_id; /**< ID of the current shell. */ + int64_t next_line_id; /**< The index of the next line that the packet will encounter. */ + /** + * @brief The packet has a nu red-ward of the last line. + * It will not encounter any lines anymore. + */ + int64_t last_line; + /** + * @brief The packet just encountered a line that is very close to the next line. + * The next iteration will automatically make an interaction with the next line + * (avoiding numerical problems). + */ + int64_t close_line; + /** + * @brief packet is a virtual packet and will ignore any d_line or d_electron checks. + * It now whenever a d_line is calculated only adds the tau_line to an + * internal float. + */ + int64_t current_continuum_id; /* Packet can interact with bf-continua with an index equal or bigger than this */ + int64_t virtual_packet_flag; + int64_t virtual_packet; + double d_line; /**< Distance to electron event. */ + double d_electron; /**< Distance to line event. */ + double d_boundary; /**< Distance to shell boundary. */ + double d_cont; /**< Distance to continuum event */ + int64_t next_shell_id; /**< ID of the next shell packet visits. */ + rpacket_status_t status; /**< Packet status (in process, emitted or reabsorbed). */ + int64_t id; + double chi_th; /**< Opacity due to electron scattering */ + double chi_cont; /**< Opacity due to continuum processes */ + double chi_ff; /**< Opacity due to free-free processes */ + double chi_bf; /**< Opacity due to bound-free processes */ + double *chi_bf_tmp_partial; + int64_t macro_atom_activation_level; + bool compute_chi_bf; + double vpacket_weight; +} rpacket_t; + +static inline double rpacket_get_nu (const rpacket_t * packet) +{ + return packet->nu; +} + +static inline void rpacket_set_nu (rpacket_t * packet, double nu) +{ + packet->nu = nu; +} + +static inline double rpacket_get_mu (const rpacket_t * packet) +{ + return packet->mu; +} + +static inline void rpacket_set_mu (rpacket_t * packet, double mu) +{ + packet->mu = mu; +} + +static inline double rpacket_get_energy (const rpacket_t * packet) +{ + return packet->energy; +} + +static inline void rpacket_set_energy (rpacket_t * packet, double energy) +{ + packet->energy = energy; +} + +static inline double rpacket_get_r (const rpacket_t * packet) +{ + return packet->r; +} + +static inline void rpacket_set_r (rpacket_t * packet, double r) +{ + packet->r = r; +} + +static inline double rpacket_get_tau_event (const rpacket_t * packet) +{ + return packet->tau_event; +} + +static inline void rpacket_set_tau_event (rpacket_t * packet, double tau_event) +{ + packet->tau_event = tau_event; +} + +static inline double rpacket_get_nu_line (const rpacket_t * packet) +{ + return packet->nu_line; +} + +static inline void rpacket_set_nu_line (rpacket_t * packet, double nu_line) +{ + packet->nu_line = nu_line; +} + +static inline unsigned int rpacket_get_current_shell_id (const rpacket_t * packet) +{ + return packet->current_shell_id; +} + +static inline void rpacket_set_current_shell_id (rpacket_t * packet, + unsigned int current_shell_id) +{ + packet->current_shell_id = current_shell_id; +} + +static inline unsigned int rpacket_get_next_line_id (const rpacket_t * packet) +{ + return packet->next_line_id; +} + +static inline void rpacket_set_next_line_id (rpacket_t * packet, + unsigned int next_line_id) +{ + packet->next_line_id = next_line_id; +} + +static inline bool rpacket_get_last_line (const rpacket_t * packet) +{ + return packet->last_line; +} + +static inline void rpacket_set_last_line (rpacket_t * packet, bool last_line) +{ + packet->last_line = last_line; +} + +static inline bool rpacket_get_close_line (const rpacket_t * packet) +{ + return packet->close_line; +} + +static inline void rpacket_set_close_line (rpacket_t * packet, bool close_line) +{ + packet->close_line = close_line; +} + +static inline int rpacket_get_virtual_packet_flag (const rpacket_t * packet) +{ + return packet->virtual_packet_flag; +} + +static inline void rpacket_set_virtual_packet_flag (rpacket_t * packet, + int virtual_packet_flag) +{ + packet->virtual_packet_flag = virtual_packet_flag; +} + +static inline int rpacket_get_virtual_packet (const rpacket_t * packet) +{ + return packet->virtual_packet; +} + +static inline void rpacket_set_virtual_packet (rpacket_t * packet, + int virtual_packet) +{ + packet->virtual_packet = virtual_packet; +} + +static inline double rpacket_get_d_boundary (const rpacket_t * packet) +{ + return packet->d_boundary; +} + +static inline void rpacket_set_d_boundary (rpacket_t * packet, double d_boundary) +{ + packet->d_boundary = d_boundary; +} + +static inline double rpacket_get_d_electron (const rpacket_t * packet) +{ + return packet->d_electron; +} + +static inline void rpacket_set_d_electron (rpacket_t * packet, double d_electron) +{ + packet->d_electron = d_electron; +} + +static inline double rpacket_get_d_line (const rpacket_t * packet) +{ + return packet->d_line; +} + +static inline void rpacket_set_d_line (rpacket_t * packet, double d_line) +{ + packet->d_line = d_line; +} + +static inline int rpacket_get_next_shell_id (const rpacket_t * packet) +{ + return packet->next_shell_id; +} + +static inline void rpacket_set_next_shell_id (rpacket_t * packet, int next_shell_id) +{ + packet->next_shell_id = next_shell_id; +} + +static inline rpacket_status_t rpacket_get_status (const rpacket_t * packet) +{ + return packet->status; +} + +static inline void rpacket_set_status (rpacket_t * packet, rpacket_status_t status) +{ + packet->status = status; +} + +static inline int rpacket_get_id (const rpacket_t * packet) +{ + return packet->id; +} + +static inline void rpacket_set_id (rpacket_t * packet, int id) +{ + packet->id = id; +} + +static inline void rpacket_reset_tau_event (rpacket_t * packet, rk_state *mt_state) +{ + rpacket_set_tau_event (packet, -log (rk_double (mt_state))); +} + +tardis_error_t rpacket_init (rpacket_t * packet, storage_model_t * storage, + int packet_index, int virtual_packet_flag, double * chi_bf_tmp_partial); + +/* New getter and setter methods for continuum implementation */ + +static inline void rpacket_set_d_continuum (rpacket_t * packet, double d_continuum) +{ + packet->d_cont = d_continuum; +} + +static inline double rpacket_get_d_continuum (const rpacket_t * packet) +{ + return packet->d_cont; +} + +static inline void rpacket_set_chi_electron (rpacket_t * packet, double chi_electron) +{ + packet->chi_th = chi_electron; +} + +static inline double rpacket_get_chi_electron (const rpacket_t * packet) +{ + return packet->chi_th; +} + +static inline void rpacket_set_chi_continuum (rpacket_t * packet, double chi_continuum) +{ + packet->chi_cont = chi_continuum; +} + +static inline double rpacket_get_chi_continuum (const rpacket_t * packet) +{ + return packet->chi_cont; +} + +static inline void rpacket_set_chi_freefree (rpacket_t * packet, double chi_freefree) +{ + packet->chi_ff = chi_freefree; +} + +static inline double rpacket_get_chi_freefree (const rpacket_t * packet) +{ + return packet->chi_ff; +} + +static inline void rpacket_set_chi_boundfree (rpacket_t * packet, double chi_boundfree) +{ + packet->chi_bf = chi_boundfree; +} + +static inline double rpacket_get_chi_boundfree (const rpacket_t * packet) +{ + return packet->chi_bf; +} + +static inline unsigned int rpacket_get_current_continuum_id (const rpacket_t * packet) +{ + return packet->current_continuum_id; +} + +static inline void rpacket_set_current_continuum_id (rpacket_t * packet, unsigned int current_continuum_id) +{ + packet->current_continuum_id = current_continuum_id; +} + +static inline void rpacket_set_macro_atom_activation_level (rpacket_t * packet, unsigned int activation_level) +{ + packet->macro_atom_activation_level = activation_level; +} + +static inline unsigned int rpacket_get_macro_atom_activation_level (const rpacket_t * packet) +{ + return packet->macro_atom_activation_level; +} + +#endif // TARDIS_RPACKET_H diff --git a/tardis/montecarlo/src/status.h b/tardis/montecarlo/src/status.h new file mode 100644 index 00000000000..17a01720b79 --- /dev/null +++ b/tardis/montecarlo/src/status.h @@ -0,0 +1,38 @@ +#ifndef TARDIS_STATUS_H +#define TARDIS_STATUS_H + +typedef enum +{ + TARDIS_ERROR_OK = 0, + TARDIS_ERROR_BOUNDS_ERROR = 1, + TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE = 2 +} tardis_error_t; + +typedef enum +{ + TARDIS_PACKET_STATUS_IN_PROCESS = 0, + TARDIS_PACKET_STATUS_EMITTED = 1, + TARDIS_PACKET_STATUS_REABSORBED = 2 +} rpacket_status_t; + +typedef enum +{ + CONTINUUM_OFF = 0, + CONTINUUM_ON = 1 +} ContinuumProcessesStatus; + +typedef enum +{ + BB_EMISSION = -1, + BF_EMISSION = -2, + FF_EMISSION = -3, + ADIABATIC_COOLING = -4 +} emission_type; + +typedef enum +{ + LIN_INTERPOLATION = 0, + HYDROGENIC = 1 +} bound_free_treatment; + +#endif // TARDIS_STATUS_H diff --git a/tardis/montecarlo/src/storage.h b/tardis/montecarlo/src/storage.h new file mode 100644 index 00000000000..a408a9f7bc2 --- /dev/null +++ b/tardis/montecarlo/src/storage.h @@ -0,0 +1,102 @@ +#ifndef TARDIS_STORAGE_H +#define TARDIS_STORAGE_H + +#include + +#include "status.h" + +typedef struct photo_xsect_1level +{ + double * nu; + double * x_sect; + int64_t no_of_points; +} photo_xsect_1level; + +typedef struct StorageModel +{ + double *packet_nus; + double *packet_mus; + double *packet_energies; + double *output_nus; + double *output_energies; + double *last_interaction_in_nu; + int64_t *last_line_interaction_in_id; + int64_t *last_line_interaction_out_id; + int64_t *last_line_interaction_shell_id; + int64_t *last_interaction_type; + int64_t *last_interaction_out_type; + int64_t no_of_packets; + int64_t no_of_shells; + int64_t no_of_shells_i; + double *r_inner; + double *r_outer; + double *r_inner_i; + double *r_outer_i; + double *v_inner; + double time_explosion; + double inverse_time_explosion; + double *electron_densities; + double *electron_densities_i; + double *inverse_electron_densities; + double *line_list_nu; + double *continuum_list_nu; + double *line_lists_tau_sobolevs; + double *line_lists_tau_sobolevs_i; + int64_t line_lists_tau_sobolevs_nd; + double *line_lists_j_blues; + int64_t line_lists_j_blues_nd; + double *line_lists_Edotlu; + int64_t no_of_lines; + int64_t no_of_edges; + int64_t line_interaction_id; + double *transition_probabilities; + int64_t transition_probabilities_nd; + int64_t *line2macro_level_upper; + int64_t *macro_block_references; + int64_t *transition_type; + int64_t *destination_level_id; + int64_t *transition_line_id; + double *js; + double *nubars; + double spectrum_start_nu; + double spectrum_delta_nu; + double spectrum_end_nu; + double spectrum_virt_start_nu; + double spectrum_virt_end_nu; + double *spectrum_virt_nu; + double sigma_thomson; + double inverse_sigma_thomson; + double inner_boundary_albedo; + int64_t reflective_inner_boundary; + int64_t current_packet_id; + photo_xsect_1level **photo_xsect; + double *chi_ff_factor; + double *t_electrons; + double *l_pop; + double *l_pop_r; + ContinuumProcessesStatus cont_status; + bound_free_treatment bf_treatment; + double *virt_packet_nus; + double *virt_packet_energies; + double *virt_packet_last_interaction_in_nu; + int64_t *virt_packet_last_interaction_type; + int64_t *virt_packet_last_line_interaction_in_id; + int64_t *virt_packet_last_line_interaction_out_id; + int64_t virt_packet_count; + int64_t virt_array_size; + int64_t kpacket2macro_level; + int64_t *cont_edge2macro_level; + double *photo_ion_estimator; + double *stim_recomb_estimator; + int64_t *photo_ion_estimator_statistics; + double *bf_heating_estimator; + double *ff_heating_estimator; + double *stim_recomb_cooling_estimator; + int full_relativity; + double survival_probability; + double tau_russian; + double *tau_bias; + int enable_biasing; +} storage_model_t; + +#endif // TARDIS_STORAGE_H From 0feb68ec6b7dbb78e494e6165453849a0a328095 Mon Sep 17 00:00:00 2001 From: Jack O'Brien Date: Sat, 10 Oct 2020 18:38:47 -0400 Subject: [PATCH 065/116] Fixed `calculate_distance_line` showing nu_diff < 0 (#1317) * Added fix for computing distances to line where we check if we're at the last line and if so return MISS_DISTANCE. This resolved the negative nu_diff issues * I forgot to actually modiify the conditional --- tardis/montecarlo/montecarlo_numba/r_packet.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index f45f0d91e0d..bf9f3d958f4 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -71,7 +71,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): @njit(**njit_dict) def calculate_distance_line( r_packet, comov_nu, - nu_last_interaction, nu_line, time_explosion): + last_line, nu_line, time_explosion): """ Parameters @@ -89,11 +89,10 @@ def calculate_distance_line( nu = r_packet.nu - if nu_line == 0.0: + if last_line: return MISS_DISTANCE nu_diff = comov_nu - nu_line - nu_diff_last = nu_last_interaction - nu_line # for numerical reasons, if line is too close, we set the distance to 0. if r_packet.close_line > 0: @@ -304,8 +303,11 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency + last_line = 0 + if cur_line_id == len(numba_plasma.line_list_nu) - 1: + last_line = 1 distance_trace = calculate_distance_line( - r_packet, comov_nu, nu_line_last_interaction, + r_packet, comov_nu, last_line, nu_line, numba_model.time_explosion ) From 99f4b18094738c18118b90aec1e2b418b0d3206d Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 12 Oct 2020 11:25:16 +0200 Subject: [PATCH 066/116] change to use math instead of np --- .../montecarlo/montecarlo_numba/r_packet.py | 20 +++++++++---------- tardis/montecarlo/montecarlo_numba/vpacket.py | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index bf9f3d958f4..dc4b71e8a03 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -3,7 +3,7 @@ from numba import int64, float64 from numba import jitclass, njit - +import math from tardis.montecarlo.montecarlo_numba import njit_dict from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration from tardis.montecarlo.montecarlo_numba.montecarlo_logger import log_decorator @@ -49,7 +49,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): delta_shell = 0 if (mu > 0.0): # direction outward - distance = np.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) + distance = math.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) delta_shell = 1 else: # going inward @@ -57,11 +57,11 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): if (check >= 0.0): # hit inner boundary - distance = -r * mu - np.sqrt(check) + distance = -r * mu - math.sqrt(check) delta_shell = -1 else: # miss inner boundary - distance = np.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) + distance = math.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) delta_shell = 1 return distance, delta_shell @@ -120,7 +120,7 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, nu_r = nu_line / nu ct = C_SPEED_OF_LIGHT * time_explosion distance = -r_packet.mu * r_packet.r + ( - ct - nu_r ** 2 * np.sqrt( + ct - nu_r ** 2 * math.sqrt( ct ** 2 - (1 + r_packet.r ** 2 * (1 - r_packet.mu ** 2) * (1 + pow(nu_r, -2))))) / (1 + nu_r ** 2) return distance @@ -151,7 +151,7 @@ def get_doppler_factor_partial_relativity(mu, beta): @njit(**njit_dict) def get_doppler_factor_full_relativity(mu, beta): - return (1.0 - mu * beta) / np.sqrt(1 - beta * beta) + return (1.0 - mu * beta) / math.sqrt(1 - beta * beta) @njit(**njit_dict) @@ -170,7 +170,7 @@ def get_inverse_doppler_factor_partial_relativity(mu, beta): @njit(**njit_dict) def get_inverse_doppler_factor_full_relativity(mu, beta): - return (1.0 + mu * beta) / np.sqrt(1 - beta * beta) + return (1.0 + mu * beta) / math.sqrt(1 - beta * beta) @njit(**njit_dict) def get_random_mu(): @@ -215,7 +215,7 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, """ """ Actual calculation - simplified below - r_interaction = np.sqrt(r_packet.r**2 + distance_trace**2 + + r_interaction = math.sqrt(r_packet.r**2 + distance_trace**2 + 2 * r_packet.r * distance_trace * r_packet.mu) mu_interaction = (r_packet.mu * r_packet.r + distance_trace) / r_interaction doppler_factor = 1.0 - mu_interaction * r_interaction / @@ -400,7 +400,7 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): r = r_packet.r if (distance > 0.0): - new_r = np.sqrt(r**2 + distance**2 + + new_r = math.sqrt(r**2 + distance**2 + 2.0 * r * distance * r_packet.mu) r_packet.mu = (r_packet.mu * r + distance) / new_r r_packet.r = new_r @@ -521,6 +521,6 @@ def angle_aberration_LF_to_CMF(r_packet, time_explosion, mu): @njit(**njit_dict) def test_for_close_line(r_packet, line_id, nu_line, numba_plasma): - if (np.abs(numba_plasma.line_list_nu[line_id] - nu_line) + if (abs(numba_plasma.line_list_nu[line_id] - nu_line) < (nu_line * CLOSE_LINE_THRESHOLD)): r_packet.close_line = 1 diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e7a71c331e3..c42f9b4837e 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -3,7 +3,7 @@ from tardis.montecarlo.montecarlo_numba import njit_dict from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration - +import math import numpy as np from tardis.montecarlo.montecarlo_numba.r_packet import ( @@ -127,11 +127,11 @@ def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): v_packet.status = PacketStatus.EMITTED else: v_packet.energy = v_packet.energy / montecarlo_configuration.survival_probability * \ - np.exp(-tau_trace_combined) + math.exp(-tau_trace_combined) tau_trace_combined = 0.0 # Moving the v_packet - new_r = np.sqrt(v_packet.r**2 + distance_boundary**2 + + new_r = math.sqrt(v_packet.r**2 + distance_boundary**2 + 2.0 * v_packet.r * distance_boundary * v_packet.mu) v_packet.mu = (v_packet.mu * v_packet.r + distance_boundary) / new_r v_packet.r = new_r @@ -171,7 +171,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, ### TODO theoretical check for r_packet nu within vpackets bins - is done somewhere else I think if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary - mu_min = -np.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) + mu_min = -math.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) v_packet_on_inner_boundary = False if montecarlo_configuration.full_relativity: mu_min = angle_aberration_LF_to_CMF(r_packet, @@ -220,7 +220,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson) - v_packet.energy *= np.exp(-tau_vpacket) + v_packet.energy *= math.exp(-tau_vpacket) vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy From 979fbda0aabc287f3e01e27c87eeb0513003f35e Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Mon, 12 Oct 2020 11:55:02 +0200 Subject: [PATCH 067/116] update sigma thomson use from numba_config update vpacket fix clean last line bool variable names use compile time constants Co-authored-by Christian Vogl --- tardis/montecarlo/base.py | 14 +- tardis/montecarlo/montecarlo_numba/base.py | 8 +- .../montecarlo_numba/interaction.py | 28 ---- .../montecarlo_numba/numba_config.py | 8 ++ .../montecarlo_numba/numba_interface.py | 3 +- .../montecarlo/montecarlo_numba/r_packet.py | 127 +++++++++--------- .../montecarlo_numba/single_packet_loop.py | 11 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 36 ++--- 8 files changed, 106 insertions(+), 129 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/numba_config.py diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index a9cb7a4750b..44b95089f10 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -19,6 +19,7 @@ from tardis.montecarlo.montecarlo_numba import montecarlo_logger as mc_logger from tardis.montecarlo.montecarlo_numba.numba_interface import ( configuration_initialize) +from tardis.montecarlo.montecarlo_numba import numba_config import numpy as np @@ -61,7 +62,7 @@ class MontecarloRunner(HDFWriterMixin): (const.h / const.k_B)).cgs.value def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, - sigma_thomson, disable_electron_scattering, enable_reflective_inner_boundary, + disable_electron_scattering, enable_reflective_inner_boundary, enable_full_relativity, inner_boundary_albedo, line_interaction_type, integrator_settings, v_packet_settings, spectrum_method, @@ -77,10 +78,10 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, self.disable_electron_scattering = disable_electron_scattering self.spectrum_frequency = spectrum_frequency self.virtual_spectrum_spawn_range = virtual_spectrum_spawn_range - self.sigma_thomson = sigma_thomson self.enable_reflective_inner_boundary = enable_reflective_inner_boundary self.inner_boundary_albedo = inner_boundary_albedo self.enable_full_relativity = enable_full_relativity + numba_config.ENABLE_FULL_RELATIVITY = enable_full_relativity self.line_interaction_type = line_interaction_type self.single_packet_seed = single_packet_seed self.integrator_settings = integrator_settings @@ -450,12 +451,14 @@ def from_config(cls, config, packet_source=None): """ if config.plasma.disable_electron_scattering: - logger.warn('Disabling electron scattering - this is not physical') - sigma_thomson = 1e-200 + logger.warn('Disabling electron scattering - this is not physical.' + 'Likely bug in formal integral - ' + 'will not give same results.') + numba_config.SIGMA_THOMSON = 1e-200 # mc_config_module.disable_electron_scattering = True else: logger.debug("Electron scattering switched on") - sigma_thomson = const.sigma_T.to('cm^2').value + numba_config.SIGMA_THOMSON = const.sigma_T.to('cm^2').value # mc_config_module.disable_electron_scattering = False spectrum_frequency = quantity_linspace( @@ -467,7 +470,6 @@ def from_config(cls, config, packet_source=None): return cls(seed=config.montecarlo.seed, spectrum_frequency=spectrum_frequency, virtual_spectrum_spawn_range=config.montecarlo.virtual_spectrum_spawn_range, - sigma_thomson=sigma_thomson, enable_reflective_inner_boundary=config.montecarlo.enable_reflective_inner_boundary, inner_boundary_albedo=config.montecarlo.inner_boundary_albedo, enable_full_relativity=config.montecarlo.enable_full_relativity, diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 206056a3529..279fef307b8 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -32,8 +32,7 @@ def montecarlo_radial1d(model, plasma, runner): v_packets_energy_hist, last_interaction_type = montecarlo_main_loop( packet_collection, numba_model, numba_plasma, estimators, - runner.spectrum_frequency.value, number_of_vpackets, packet_seeds, - runner.sigma_thomson) + runner.spectrum_frequency.value, number_of_vpackets, packet_seeds) runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist runner.last_interaction_type = last_interaction_type @@ -41,8 +40,7 @@ def montecarlo_radial1d(model, plasma, runner): @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, estimators, spectrum_frequency, - number_of_vpackets, packet_seeds, - sigma_thomson): + number_of_vpackets, packet_seeds): """ This is the main loop of the MonteCarlo routine that generates packets and sends them through the ejecta. @@ -80,7 +78,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, number_of_vpackets, montecarlo_configuration.temporary_v_packet_bins) loop = single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection, sigma_thomson) + vpacket_collection) # if loop and 'stop' in loop: # raise MonteCarloException diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 8cdb53d8c7b..d548f32ee5e 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -9,8 +9,6 @@ angle_aberration_CMF_to_LF, test_for_close_line) from tardis.montecarlo.montecarlo_numba.macro_atom import macro_atom -CLOSE_LINE_THRESHOLD = 1e-7 - @njit(**njit_dict) def thomson_scatter(r_packet, time_explosion): """ @@ -46,32 +44,6 @@ def thomson_scatter(r_packet, time_explosion): ) -""" -void -montecarlo_thomson_scatter (rpacket_t * packet, storage_model_t * storage, - double distance, rk_state *mt_state) -{ - move_packet (packet, storage, distance); - double doppler_factor = rpacket_doppler_factor (packet, storage); - double comov_nu = rpacket_get_nu (packet) * doppler_factor; - double comov_energy = rpacket_get_energy (packet) * doppler_factor; - rpacket_set_mu (packet, 2.0 * rk_double (mt_state) - 1.0); - double inverse_doppler_factor = rpacket_inverse_doppler_factor (packet, storage); - rpacket_set_nu (packet, comov_nu * inverse_doppler_factor); - rpacket_set_energy (packet, comov_energy * inverse_doppler_factor); - rpacket_reset_tau_event (packet, mt_state); - storage->last_interaction_type[rpacket_get_id (packet)] = 1; - - angle_aberration_CMF_to_LF (packet, storage); - - if (rpacket_get_virtual_packet_flag (packet) > 0) - { - create_vpacket (storage, packet, mt_state); - } -} - -""" - @njit(**njit_dict) def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): #increment_j_blue_estimator(packet, storage, distance, line2d_idx); diff --git a/tardis/montecarlo/montecarlo_numba/numba_config.py b/tardis/montecarlo/montecarlo_numba/numba_config.py new file mode 100644 index 00000000000..92a4ed439a9 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/numba_config.py @@ -0,0 +1,8 @@ +from tardis import constants as const + +SIGMA_THOMSON = const.sigma_T.to('cm^2').value +CLOSE_LINE_THRESHOLD = 1e-7 +C_SPEED_OF_LIGHT = const.c.to('cm/s').value +MISS_DISTANCE = 1e99 + +ENABLE_FULL_RELATIVITY = False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 21e5ba7c144..aed4573488b 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -1,6 +1,7 @@ from enum import IntEnum -from numba import float64, int64, jitclass, boolean +from numba import float64, int64, boolean +from numba.experimental import jitclass import numpy as np from astropy import units as u diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index dc4b71e8a03..1d9bfba5c6d 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -1,23 +1,19 @@ import numpy as np from enum import IntEnum -from numba import int64, float64 -from numba import jitclass, njit +from numba import int64, float64, boolean +from numba import njit +from numba.experimental import jitclass import math -from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo.montecarlo_numba import njit_dict, numba_config from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration -from tardis.montecarlo.montecarlo_numba.montecarlo_logger import log_decorator -from tardis import constants as const +from tardis.montecarlo.montecarlo_numba.numba_config import \ + CLOSE_LINE_THRESHOLD, C_SPEED_OF_LIGHT, MISS_DISTANCE, SIGMA_THOMSON -SIGMA_THOMSON = const.sigma_T.to('cm^2').value class MonteCarloException(ValueError): pass -CLOSE_LINE_THRESHOLD = 1e-7 -C_SPEED_OF_LIGHT = const.c.to('cm/s').value -MISS_DISTANCE = 1e99 - class PacketStatus(IntEnum): IN_PROCESS = 0 @@ -30,7 +26,6 @@ class InteractionType(IntEnum): LINE = 2 ESCATTERING = 3 - rpacket_spec = [ ('r', float64), ('mu', float64), @@ -41,8 +36,29 @@ class InteractionType(IntEnum): ('status', int64), ('seed', int64), ('index', int64), - ('close_line', int64) + ('is_close_line', boolean) ] +@jitclass(rpacket_spec) +class RPacket(object): + def __init__(self, r, mu, nu, energy, seed, index=0, is_close_line=False): + self.r = r + self.mu = mu + self.nu = nu + self.energy = energy + self.current_shell_id = 0 + self.status = PacketStatus.IN_PROCESS + self.seed = seed + self.index = index + self.is_close_line = is_close_line + + def initialize_line_id(self, numba_plasma, numba_model): + inverse_line_list_nu = numba_plasma.line_list_nu[::-1] + doppler_factor = get_doppler_factor(self.r, self.mu, + numba_model.time_explosion) + comov_nu = self.nu * doppler_factor + next_line_id = (len(numba_plasma.line_list_nu) - + np.searchsorted(inverse_line_list_nu, comov_nu)) + self.next_line_id = next_line_id @njit(**njit_dict) def calculate_distance_boundary(r, mu, r_inner, r_outer): @@ -68,10 +84,11 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): # @log_decorator +#'float64(RPacket, float64, int64, float64, float64)' @njit(**njit_dict) def calculate_distance_line( r_packet, comov_nu, - last_line, nu_line, time_explosion): + is_last_line, nu_line, time_explosion): """ Parameters @@ -89,15 +106,15 @@ def calculate_distance_line( nu = r_packet.nu - if last_line: + if is_last_line: return MISS_DISTANCE nu_diff = comov_nu - nu_line # for numerical reasons, if line is too close, we set the distance to 0. - if r_packet.close_line > 0: + if r_packet.is_close_line: nu_diff = 0.0 - r_packet.close_line = 0 + r_packet.is_close_line = False if nu_diff >= 0: distance = (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion @@ -106,7 +123,7 @@ def calculate_distance_line( #raise MonteCarloException('nu difference is less than 0.0; for more' # ' information, see print statement beforehand') - if montecarlo_configuration.full_relativity: + if numba_config.ENABLE_FULL_RELATIVITY: return calculate_distance_line_full_relativity(nu_line, nu, time_explosion, r_packet) @@ -126,21 +143,21 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, return distance @njit(**njit_dict) -def calculate_distance_electron(electron_density, tau_event, sigma_thomson): +def calculate_distance_electron(electron_density, tau_event): # add full_relativity here - return tau_event / (electron_density * sigma_thomson) + return tau_event / (electron_density * SIGMA_THOMSON) @njit(**njit_dict) -def calculate_tau_electron(electron_density, distance, sigma_thomson): - return electron_density * sigma_thomson * distance +def calculate_tau_electron(electron_density, distance): + return electron_density * SIGMA_THOMSON * distance @njit(**njit_dict) def get_doppler_factor(r, mu, time_explosion): - inv_c = 1/C_SPEED_OF_LIGHT + inv_c = 1 / C_SPEED_OF_LIGHT inv_t = 1/time_explosion beta = r * inv_t * inv_c - if not montecarlo_configuration.full_relativity: + if not numba_config.ENABLE_FULL_RELATIVITY: return get_doppler_factor_partial_relativity(mu, beta) else: return get_doppler_factor_full_relativity(mu, beta) @@ -159,7 +176,7 @@ def get_inverse_doppler_factor(r, mu, time_explosion): inv_c = 1 / C_SPEED_OF_LIGHT inv_t = 1 / time_explosion beta = r * inv_t * inv_c - if not montecarlo_configuration.full_relativity: + if not numba_config.ENABLE_FULL_RELATIVITY: return get_inverse_doppler_factor_partial_relativity(mu, beta) else: return get_inverse_doppler_factor_full_relativity(mu, beta) @@ -176,27 +193,6 @@ def get_inverse_doppler_factor_full_relativity(mu, beta): def get_random_mu(): return 2.0 * np.random.random() - 1.0 -@jitclass(rpacket_spec) -class RPacket(object): - def __init__(self, r, mu, nu, energy, seed, index=0, close_line=0): - self.r = r - self.mu = mu - self.nu = nu - self.energy = energy - self.current_shell_id = 0 - self.status = PacketStatus.IN_PROCESS - self.seed = seed - self.index = index - self.close_line = close_line - - def initialize_line_id(self, numba_plasma, numba_model): - inverse_line_list_nu = numba_plasma.line_list_nu[::-1] - doppler_factor = get_doppler_factor(self.r, self.mu, - numba_model.time_explosion) - comov_nu = self.nu * doppler_factor - next_line_id = (len(numba_plasma.line_list_nu) - - np.searchsorted(inverse_line_list_nu, comov_nu)) - self.next_line_id = next_line_id @njit(**njit_dict) def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, @@ -222,7 +218,7 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, ( time_explosion * C) """ - if not montecarlo_configuration.full_relativity: + if not numba_config.ENABLE_FULL_RELATIVITY: energy = calc_packet_energy(r_packet, distance_trace, time_explosion) else: @@ -246,7 +242,7 @@ def calc_packet_energy(r_packet, distance_trace, time_explosion): return energy @njit(**njit_dict) -def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson): +def trace_packet(r_packet, numba_model, numba_plasma, estimators): """ Parameters @@ -270,7 +266,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) start_line_id = r_packet.next_line_id # defining taus - tau_event = np.random.exponential() + tau_event = -np.log(1 - np.random.random()) tau_trace_line_combined = 0.0 # e scattering initialization @@ -278,7 +274,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) cur_electron_density = numba_plasma.electron_density[ r_packet.current_shell_id] distance_electron = calculate_distance_electron( - cur_electron_density, tau_event, sigma_thomson) + cur_electron_density, tau_event) # Calculating doppler factor doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, @@ -303,18 +299,20 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency - last_line = 0 + if cur_line_id == len(numba_plasma.line_list_nu) - 1: - last_line = 1 + is_last_line = True + else: + is_last_line = False + distance_trace = calculate_distance_line( - r_packet, comov_nu, last_line, + r_packet, comov_nu, is_last_line, nu_line, numba_model.time_explosion ) # calculating the tau electron of how far the trace has progressed tau_trace_electron = calculate_tau_electron(cur_electron_density, - distance_trace, - sigma_thomson) + distance_trace) # calculating the trace tau_trace_combined = tau_trace_line_combined + tau_trace_electron @@ -355,8 +353,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators, sigma_thomson) # Recalculating distance_electron using tau_event - # tau_trace_line_combined distance_electron = calculate_distance_electron( - cur_electron_density, tau_event - tau_trace_line_combined, - sigma_thomson) + cur_electron_density, tau_event - tau_trace_line_combined) else: # Executed when no break occurs in the for loop # We are beyond the line list now and the only next thing is to see @@ -408,7 +405,14 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): comov_nu = r_packet.nu * doppler_factor comov_energy = r_packet.energy * doppler_factor - if montecarlo_configuration.full_relativity: + if not numba_config.ENABLE_FULL_RELATIVITY: + set_estimators(r_packet, + distance, + numba_estimator, + comov_nu, + comov_energy) + + else: distance = distance * doppler_factor set_estimators_full_relativity(r_packet, distance, @@ -416,13 +420,6 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): comov_nu, comov_energy, doppler_factor) - else: - set_estimators(r_packet, - distance, - numba_estimator, - comov_nu, - comov_energy) - @njit(**njit_dict) def set_estimators(r_packet, distance, numba_estimator, comov_nu, comov_energy): numba_estimator.j_estimator[r_packet.current_shell_id] += ( @@ -523,4 +520,4 @@ def angle_aberration_LF_to_CMF(r_packet, time_explosion, mu): def test_for_close_line(r_packet, line_id, nu_line, numba_plasma): if (abs(numba_plasma.line_list_nu[line_id] - nu_line) < (nu_line * CLOSE_LINE_THRESHOLD)): - r_packet.close_line = 1 + r_packet.is_close_line = True diff --git a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py index e7a13affdd1..59062ffc624 100644 --- a/tardis/montecarlo/montecarlo_numba/single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/single_packet_loop.py @@ -24,7 +24,7 @@ # @log_decorator @njit def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, - vpacket_collection, sigma_thomson): + vpacket_collection): """ Parameters @@ -52,7 +52,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, r_packet.initialize_line_id(numba_plasma, numba_model) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma, sigma_thomson) + numba_plasma) if mc_logger.DEBUG_MODE: r_packet_track_nu = [r_packet.nu] @@ -63,7 +63,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, while r_packet.status == PacketStatus.IN_PROCESS: distance, interaction_type, delta_shell = trace_packet( - r_packet, numba_model, numba_plasma, estimators, sigma_thomson) + r_packet, numba_model, numba_plasma, estimators) if interaction_type == InteractionType.BOUNDARY: @@ -78,8 +78,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, line_scatter(r_packet, numba_model.time_explosion, line_interaction_type, numba_plasma) trace_vpacket_volley( - r_packet, vpacket_collection, numba_model, numba_plasma, - sigma_thomson) + r_packet, vpacket_collection, numba_model, numba_plasma) elif interaction_type == InteractionType.ESCATTERING: @@ -88,7 +87,7 @@ def single_packet_loop(r_packet, numba_model, numba_plasma, estimators, thomson_scatter(r_packet, numba_model.time_explosion) trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma, sigma_thomson) + numba_plasma) if mc_logger.DEBUG_MODE: r_packet_track_nu.append(r_packet.nu) r_packet_track_mu.append(r_packet.mu) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index c42f9b4837e..e490e0d3b45 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,4 +1,4 @@ -from numba import float64, int64 +from numba import float64, int64, boolean from numba import jitclass, njit, gdb from tardis.montecarlo.montecarlo_numba import njit_dict @@ -20,13 +20,12 @@ ('current_shell_id', int64), ('status', int64), ('index', int64), - ('close_line', int64) + ('is_close_line', boolean) ] - @jitclass(vpacket_spec) class VPacket(object): def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, - index=0, close_line=0): + index=0, is_close_line=0): self.r = r self.mu = mu self.nu = nu @@ -35,12 +34,11 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, self.next_line_id = next_line_id self.status = PacketStatus.IN_PROCESS self.index = index - self.close_line = close_line + self.is_close_line = is_close_line @njit(**njit_dict) -def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, - sigma_thomson): +def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): r_inner = numba_model.r_inner[v_packet.current_shell_id] r_outer = numba_model.r_outer[v_packet.current_shell_id] @@ -55,8 +53,7 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, cur_electron_density = numba_plasma.electron_density[ v_packet.current_shell_id] tau_electron = calculate_tau_electron(cur_electron_density, - distance_boundary, - sigma_thomson) + distance_boundary) tau_trace_combined = tau_electron # Calculating doppler factor @@ -71,12 +68,17 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, nu_line = numba_plasma.line_list_nu[cur_line_id] # TODO: Check if this is what the C code does - nu_line_last_interaction = numba_plasma.line_list_nu[cur_line_id - 1] + tau_trace_line = numba_plasma.tau_sobolev[ cur_line_id, v_packet.current_shell_id] + if cur_line_id == len(numba_plasma.line_list_nu) - 1: + is_last_line = True + else: + is_last_line = False + distance_trace_line = calculate_distance_line( - v_packet, comov_nu, nu_line_last_interaction, + v_packet, comov_nu, is_last_line, nu_line, numba_model.time_explosion ) @@ -96,7 +98,7 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma, return tau_trace_combined, distance_boundary, delta_shell @njit(**njit_dict) -def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): +def trace_vpacket(v_packet, numba_model, numba_plasma): """ Trace single vpacket. Parameters @@ -113,8 +115,7 @@ def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): tau_trace_combined = 0.0 while True: tau_trace_combined_shell, distance_boundary, delta_shell = trace_vpacket_within_shell( - v_packet, numba_model, numba_plasma, sigma_thomson - ) + v_packet, numba_model, numba_plasma) tau_trace_combined += tau_trace_combined_shell move_packet_across_shell_boundary(v_packet, delta_shell, @@ -142,7 +143,7 @@ def trace_vpacket(v_packet, numba_model, numba_plasma, sigma_thomson): @njit(**njit_dict) def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, - numba_plasma, sigma_thomson): + numba_plasma): """ Shoot a volley of vpackets (the vpacket collection specifies how many) from the current position of the rpacket. @@ -212,13 +213,12 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet = VPacket(r_packet.r, v_packet_mu, v_packet_nu, v_packet_energy, r_packet.current_shell_id, - r_packet.next_line_id, i, r_packet.close_line) + r_packet.next_line_id, i, r_packet.is_close_line) if r_packet.next_line_id != (len(numba_plasma.line_list_nu) - 1): test_for_close_line(v_packet, r_packet.next_line_id + 1, numba_plasma.line_list_nu[r_packet.next_line_id], numba_plasma) - tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma, - sigma_thomson) + tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) v_packet.energy *= math.exp(-tau_vpacket) From 4fc9bdaf7ef0577ddee1a9be9d34fdb623daa748 Mon Sep 17 00:00:00 2001 From: Christian Vogl Date: Thu, 15 Oct 2020 09:43:56 +0200 Subject: [PATCH 068/116] Numba montecarlo tests merged (#1319) * Clean up error reporting formal integral * Make first move to numba montecarlo tests * Add doppler factor tests * Add mu, LF_TO_CMF tests * Change r_inner, r_outer names; reference plasma correctly in integral test * Clean up montecarlo testing * Move location of coverage file * Remove extraneous imports * Refer to previous plasma object for atomic data * Debug formal integral to ensure correct plasma is referenced * Adding runner to test fixture * Add capacity to use Estimators * Update r_packet.py * Added empty test files * Packet tests * Added empty tests for packet-related functions * Added update line estimator test * Nicer formatting * Added empty tests that assert False * Wrote unit tests for r_packet.move_r_packet. Should be noted, we found that when updating global variables use in numba functions, the numba functions must be recompiled in order for the changes to take effect. Keep this in mind when testing. The cases for full relativity being enabled SHOULD FAIL, there is a discrepancy between how the code is written and what the tests check. Someone who is more confident in their knowledge of the physics than me should check it out. (i.e. should we be off by a factor of doppler factor, or doppler factor squared?) * Bad vpacket test Co-authored-by: Arjun Savel Co-authored-by: Jack O'Brien Co-authored-by: Andrew Fullard --- tardis/montecarlo/formal_integral.py | 232 ++-- .../montecarlo/montecarlo_numba/r_packet.py | 6 +- .../montecarlo_numba/tests/test_base.py | 5 + .../tests/test_interaction.py | 8 + .../montecarlo_numba/tests/test_macro_atom.py | 2 + .../tests/test_montecarlo_logger.py | 0 .../tests/test_numba_interface.py | 5 + .../montecarlo_numba/tests/test_packet.py | 303 +++++ .../tests/test_single_packet_loop.py | 8 + .../montecarlo_numba/tests/test_vpacket.py | 75 ++ tardis/montecarlo/tests/conftest.py | 10 + tardis/montecarlo/tests/test_cmontecarlo.py | 1058 ----------------- tardis/montecarlo/tests/test_montecarlo.py | 954 +++++++++++++++ .../tests/test_tardis_full_formal_integral.py | 2 - 14 files changed, 1504 insertions(+), 1164 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_base.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_interaction.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_montecarlo_logger.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_packet.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py create mode 100644 tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py delete mode 100644 tardis/montecarlo/tests/test_cmontecarlo.py diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index fe9f82a90de..087d1b9ddf8 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -6,9 +6,12 @@ from astropy import units as u from tardis import constants as const from numba import jitclass, njit +import pdb from tardis.montecarlo.montecarlo_numba import njit_dict +from tardis.montecarlo.montecarlo_numba.numba_interface \ + import numba_plasma_initialize from tardis.montecarlo.montecarlo import formal_integral from tardis.montecarlo.spectrum import TARDISSpectrum @@ -17,6 +20,7 @@ M_PI = np.arccos(-1) KB_CGS = 1.3806488e-16 H_CGS = 6.62606957e-27 +SIGMA_THOMSON = 6.652486e-25 class IntegrationError(Exception): pass @@ -33,7 +37,10 @@ class FormalIntegrator(object): def __init__(self, model, plasma, runner, points=1000): self.model = model - self.plasma = plasma + if plasma: + self.plasma = numba_plasma_initialize(plasma) + self.atomic_data = plasma.atomic_data + self.original_plasma = plasma self.runner = runner self.points = points @@ -81,8 +88,7 @@ def calculate_spectrum(self, frequency, points=None, frequency = frequency.to('Hz', u.spectral()) luminosity = u.Quantity( - formal_integral( - self, + self.formal_integral( frequency, N), 'erg' @@ -119,25 +125,24 @@ def make_source_function(self): """ model = self.model - plasma = self.plasma runner = self.runner - atomic_data = self.plasma.atomic_data - macro_ref = atomic_data.macro_atom_references - macro_data = atomic_data.macro_atom_data - no_lvls = len(atomic_data.levels) + macro_ref = self.atomic_data.macro_atom_references + macro_data = self.atomic_data.macro_atom_data + + no_lvls = len(self.atomic_data.levels) no_shells = len(model.w) if runner.line_interaction_type == 'macroatom': internal_jump_mask = (macro_data.transition_type >= 0).values ma_int_data = macro_data[internal_jump_mask] - internal = plasma.transition_probabilities[internal_jump_mask] + internal = self.original_plasma.transition_probabilities[internal_jump_mask] source_level_idx = ma_int_data.source_level_idx.values destination_level_idx = ma_int_data.destination_level_idx.values Edotlu_norm_factor = (1 / (runner.time_of_simulation * model.volume)) - exptau = 1 - np.exp(- plasma.tau_sobolevs) + exptau = 1 - np.exp(-self.plasma.tau_sobolev) Edotlu = Edotlu_norm_factor * exptau * runner.Edotlu_estimator # The following may be achieved by calling the appropriate plasma @@ -149,7 +154,7 @@ def make_source_function(self): # the transition l->u Jbluelu = runner.j_blue_estimator * Jbluelu_norm_factor - upper_level_index = atomic_data.lines.index.droplevel('level_number_lower') + upper_level_index = self.atomic_data.lines.index.droplevel('level_number_lower') e_dot_lu = pd.DataFrame(Edotlu, index=upper_level_index) e_dot_u = e_dot_lu.groupby(level=[0, 1, 2]).sum() e_dot_u_src_idx = macro_ref.loc[e_dot_u.index].references_idx.values @@ -168,13 +173,14 @@ def make_source_function(self): e_dot_u_vec[e_dot_u_src_idx] = e_dot_u[shell].values C_frame[shell] = sp.linalg.spsolve(inv_N.T, e_dot_u_vec) + e_dot_u.index.names = ['atomic_number', 'ion_number', 'source_level_number'] # To make the q_ul e_dot_u product work, could be cleaner - transitions = atomic_data.macro_atom_data[atomic_data.macro_atom_data.transition_type == -1].copy() + transitions = self.original_plasma.atomic_data.macro_atom_data[self.original_plasma.atomic_data.macro_atom_data.transition_type == -1].copy() transitions_index = transitions.set_index(['atomic_number', 'ion_number', 'source_level_number']).index.copy() - tmp = plasma.transition_probabilities[(atomic_data.macro_atom_data.transition_type == -1).values] + tmp = self.original_plasma.transition_probabilities[(self.atomic_data.macro_atom_data.transition_type == -1).values] q_ul = tmp.set_index(transitions_index) t = model.time_explosion.value - lines = atomic_data.lines.set_index('line_id') + lines = self.atomic_data.lines.set_index('line_id') wave = lines.wavelength_cm.loc[transitions.transition_line_id].values.reshape(-1,1) if runner.line_interaction_type == 'macroatom': e_dot_u = C_frame.loc[e_dot_u.index] @@ -185,15 +191,15 @@ def make_source_function(self): # Jredlu should already by in the correct order, i.e. by wavelength of # the transition l->u (similar to Jbluelu) - Jredlu = Jbluelu * np.exp(-plasma.tau_sobolevs.values) + att_S_ul + Jredlu = Jbluelu * np.exp(-self.plasma.tau_sobolev) + att_S_ul if self.interpolate_shells > 0: att_S_ul, Jredlu, Jbluelu, e_dot_u = self.interpolate_integrator_quantities( att_S_ul, Jredlu, Jbluelu, e_dot_u) else: runner.r_inner_i = runner.r_inner_cgs runner.r_outer_i = runner.r_outer_cgs - runner.tau_sobolevs_integ = plasma.tau_sobolevs.values - runner.electron_densities_integ = plasma.electron_densities.values + runner.tau_sobolevs_integ = self.plasma.tau_sobolev + runner.electron_densities_integ = self.plasma.electron_density return att_S_ul, Jredlu, Jbluelu, e_dot_u @@ -213,12 +219,12 @@ def interpolate_integrator_quantities(self, att_S_ul, Jredlu, r_middle_integ = (r_integ[:-1] + r_integ[1:]) / 2. runner.electron_densities_integ = interp1d( - r_middle, plasma.electron_densities, + r_middle, plasma.electron_density, fill_value='extrapolate', kind='nearest')(r_middle_integ) # Assume tau_sobolevs to be constant within a shell # (as in the MC simulation) runner.tau_sobolevs_integ = interp1d( - r_middle, plasma.tau_sobolevs, + r_middle, plasma.tau_sobolev, fill_value='extrapolate', kind='nearest')(r_middle_integ) att_S_ul = interp1d( r_middle, att_S_ul, fill_value='extrapolate')(r_middle_integ) @@ -242,10 +248,6 @@ def formal_integral(self, nu, N): res = self.make_source_function() - - - - att_S_ul = res[0].flatten(order='F') Jred_lu = res[1].flatten(order='F') Jblue_lu = res[2].flatten(order='F') @@ -267,8 +269,8 @@ def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): # Initialize the output which is shared among threads L = np.zeros(inu_size) # global read-only values - size_line = self.model.no_of_lines # check - size_shell = self.model.no_of_shells_i # check + size_line = len(self.plasma.line_list_nu) + size_shell = self.model.no_of_shells # check size_tau = size_line * size_shell finished_nus = 0 @@ -278,19 +280,17 @@ def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): exp_tau = np.zeros(size_tau) # TODO: multiprocessing offset = 0 - i = 0 size_z = 0 + z = np.zeros(2 * self.model.no_of_shells) idx_nu_start = 0 direction = 0 - first = 0 I_nu = np.zeros(N) - shell_id = np.zeros(2 * self.runner.no_of_shells_i) # check + shell_id = np.zeros(2 * self.model.no_of_shells) # instantiate more variables here, maybe? # prepare exp_tau - for i in range(size_tau): - exp_tau[i] = np.exp(-self.model.line_lists_tau_sobolevs_i[i]) # check - pp = self.calculate_p_values(R_max, N, pp) + exp_tau = np.exp(-self.plasma.tau_sobolev) # check + pp = calculate_p_values(R_max, N, pp) # done with instantiation # now loop over wavelength in spectrum @@ -313,38 +313,39 @@ def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): # find first contributing lines nu_start = nu * z[0] nu_end = nu * z[1] - self.line_search(nu_start, size_line, idx_nu_start) + idx_nu_start = line_search(self.plasma.line_list_nu, + nu_start, size_line, idx_nu_start) offset = shell_id[0] * size_line # start tracking accumulated e-scattering optical depth zstart = self.model.time_explosion / C_INV * (1. - z[0]) # Initialize "pointers" - pline = self.runner.line_list_nu + idx_nu_start; - pexp_tau = exp_tau + offset + idx_nu_start; - patt_S_ul = att_S_ul + offset + idx_nu_start; - pJred_lu = Jred_lu + offset + idx_nu_start; - pJblue_lu = Jblue_lu + offset + idx_nu_start; + pline = self.plasma.line_list_nu + idx_nu_start + pexp_tau = exp_tau + offset + idx_nu_start + patt_S_ul = att_S_ul + offset + idx_nu_start + pJred_lu = Jred_lu + offset + idx_nu_start + pJblue_lu = Jblue_lu + offset + idx_nu_start # flag for first contribution to integration on current p-ray first = 1 # loop over all interactions for i in range(size_z - 1): - escat_op = self.model.electron_densities_i[shell_id[i]] * sigma_thomson # change sigma_thomson + escat_op = self.plasma.electron_density[int(shell_id[i])] * SIGMA_THOMSON nu_end = nu * z[i + 1] - while pline < self.model.ine_list_nu + size_line: # check ;pline + while np.all(pline < self.plasma.line_list_nu + size_line): # check all condition # increment all pointers simulatenously pline += 1 pexp_tau += 1 patt_S_ul += 1 pJblue_lu += 1 - if (pline[0] < nu_end): + if (pline[0] < nu_end.value): break # calculate e-scattering optical depth to next resonance point - zend = self.model.time_explosion / C_INV * (1. - pline / nu) # check + zend = self.model.time_explosion / C_INV * (1. - pline[0] / nu.value) # check if first == 1: # first contribution to integration @@ -352,26 +353,27 @@ def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): # by boundary conditions) is not in Lucy 1999; # should be re-examined carefully escat_contrib += (zend - zstart) * escat_op * ( - pJblue_lu - I_nu[p_idx]); + pJblue_lu[0] - I_nu[p_idx]); first = 0; else: # Account for e-scattering, c.f. Eqs 27, 28 in Lucy 1999 - Jkkp = 0.5 * (pJred_lu + pJblue_lu); + Jkkp = 0.5 * (pJred_lu[0] + pJblue_lu[0]); escat_contrib += (zend - zstart) * escat_op * ( Jkkp - I_nu[p_idx]) # this introduces the necessary ffset of one element between # pJblue_lu and pJred_lu pJred_lu += 1 - I_nu[p_idx] = I_nu[p_idx] + escat_contrib; + # pdb.set_trace() + I_nu[p_idx] = I_nu[p_idx] + escat_contrib.value # // Lucy 1999, Eq 26 - I_nu[p_idx] = I_nu[p_idx] * (pexp_tau) + patt_S_ul # check about taking about asterisks beforehand elsewhere + I_nu[p_idx] = I_nu[p_idx] * (pexp_tau[0][0]) + patt_S_ul[0] # check about taking about asterisks beforehand elsewhere # // reset e-scattering opacity escat_contrib = 0 zstart = zend # calculate e-scattering optical depth to grid cell boundary - Jkkp = 0.5 * (pJred_lu + pJblue_lu) + Jkkp = 0.5 * (pJred_lu[0] + pJblue_lu[0]) zend = self.model.time_explosion / C_INV * (1. - nu_end / nu) # check escat_contrib += (zend - zstart) * escat_op * ( Jkkp - I_nu[p_idx]) @@ -405,14 +407,14 @@ def populate_z(self, p, oz, oshell_id): :oshell_id: (int64) will be set with the corresponding shell_ids """ # abbreviations - r = self.model.r_outer_i - N = self.model.no_of_shells_i # check + r = self.runner.r_outer_i + N = self.model.no_of_shells # check print(N) inv_t = 1/self.model.time_explosion z = 0 offset = N - if p <= self.model.r_inner_i[0]: + if p <= self.runner.r_inner_i[0]: # intersect the photosphere for i in range(N): oz[i] = 1 - self.calculate_z(r[i], p, inv_t) @@ -452,67 +454,93 @@ def calculate_z(self, r, p, inv_t): :inv_t: (double) inverse time_explosio is needed to norm to unit-length """ if r > p: - return np.sqrt(r * r - p * p) * C_INV * inv_t + return np.sqrt(r * r - p * p) * C_INV * inv_t.value else: return 0 - @njit(**njit_dict) - def line_search(self, nu, nu_insert, number_of_lines, result): - """ - Insert a value in to an array of line frequencies +class BoundsError(ValueError): + pass - Inputs: - :nu: (array) line frequencies - :nu_insert: (int) value of nu key - :number_of_lines: (int) number of lines in the line list - - Outputs: - index of the next line ot the red. - If the key value is redder - than the reddest line returns number_of_lines. - """ - # TODO: fix the TARDIS_ERROR_OK - # tardis_error_t ret_val = TARDIS_ERROR_OK # check - imin = 0 - imax = number_of_lines - 1 - if nu_insert > nu[imin]: - result = imin - elif nu_insert < nu[imax]: - result = imax + 1 +@njit(**njit_dict) +def line_search(nu, nu_insert, number_of_lines, result): + """ + Insert a value in to an array of line frequencies + + Inputs: + :nu: (array) line frequencies + :nu_insert: (int) value of nu key + :number_of_lines: (int) number of lines in the line list + + Outputs: + index of the next line ot the red. + If the key value is redder + than the reddest line returns number_of_lines. + """ + # TODO: fix the TARDIS_ERROR_OK + # tardis_error_t ret_val = TARDIS_ERROR_OK # check + imin = 0 + imax = number_of_lines - 1 + if nu_insert > nu[imin]: + result = imin + elif nu_insert < nu[imax]: + result = imax + 1 + else: + result = reverse_binary_search(nu, nu_insert, imin, imax, result) + result = result + 1 + return result + +@njit(**njit_dict) +def reverse_binary_search(x, x_insert, imin, imax, result): + """Look for a place to insert a value in an inversely sorted float array. + + Inputs: + :x: (array) an inversely (largest to lowest) sorted float array + :x_insert: (value) a value to insert + :imin: (int) lower bound + :imax: (int) upper bound + + Outputs: + index of the next boundary to the left + """ + # ret_val = TARDIS_ERROR_OK # check + if x_insert > x[imin] or x_insert < x[imax]: + raise BoundsError # check + else: + imid = (imin + imax) >> 1 + while imax - imin > 2: + if (x[imid] < x_insert): + imax = imid + 1 + else: + imin = imid + imid = (imin + imax) >> 1 + if (imax - imin == 2 and x_insert < x[imin + 1]): + result = imin + 1 else: - ret_val = self.reverse_binary_search(nu, nu_insert, imin, imax, result) - result = result + 1 - return ret_val + result = imin + return result - @njit(**njit_dict) - def reverse_binary_search(self, x, x_insert, imin, imax, result): - """Look for a place to insert a value in an inversely sorted float array. +@njit(**njit_dict) +def binary_search(x, x_insert, imin, imax, result): + # TODO: actually return result + if x_insert < x[imin] or x_insert > x[imax]: + raise BoundsError + else: + while imax >= imin: + imid = (imin + imax) / 2 + if x[imid] == x_insert: + result = imid + break + elif x[imid] < x_insert: + imin = imid + 1 + else: + imax = imid - 1 + if imax - imid == 2 and x_insert < x[imin + 1]: + result = imin + else: + result = imin # check + return result - Inputs: - :x: (array) an inversely (largest to lowest) sorted float array - :x_insert: (value) a value to insert - :imin: (int) lower bound - :imax: (int) upper bound - Outputs: - index of the next boundary to the left - """ - # ret_val = TARDIS_ERROR_OK # check - if x_insert > x[imin] or x_insert < x[imax]: - ret_val = TARDIS_ERROR_BOUNDS_ERROR # check - else: - imid = (imin + imax) >> 1 - while imax - imin > 2: - if (x[imid] < x_insert): - imax = imid + 1 - else: - imin = imid - imid = (imin + imax) >> 1 - if (imax - imin == 2 and x_insert < x[imin + 1]): - result = imin + 1 - else: - result = imin - return ret_val @njit(**njit_dict) def trapezoid_integration(array, h, N): diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 1d9bfba5c6d..d487967de10 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -120,8 +120,8 @@ def calculate_distance_line( distance = (nu_diff / nu) * C_SPEED_OF_LIGHT * time_explosion else: print('WARNING: nu difference is less than 0.0') - #raise MonteCarloException('nu difference is less than 0.0; for more' - # ' information, see print statement beforehand') + raise MonteCarloException('nu difference is less than 0.0; for more' + ' information, see print statement beforehand') if numba_config.ENABLE_FULL_RELATIVITY: return calculate_distance_line_full_relativity(nu_line, nu, @@ -171,6 +171,7 @@ def get_doppler_factor_full_relativity(mu, beta): return (1.0 - mu * beta) / math.sqrt(1 - beta * beta) + @njit(**njit_dict) def get_inverse_doppler_factor(r, mu, time_explosion): inv_c = 1 / C_SPEED_OF_LIGHT @@ -189,6 +190,7 @@ def get_inverse_doppler_factor_partial_relativity(mu, beta): def get_inverse_doppler_factor_full_relativity(mu, beta): return (1.0 + mu * beta) / math.sqrt(1 - beta * beta) + @njit(**njit_dict) def get_random_mu(): return 2.0 * np.random.random() - 1.0 diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py new file mode 100644 index 00000000000..a86308c18c3 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -0,0 +1,5 @@ +def test_montecarlo_radial1d(): + assert False + +def test_montecarlo_main_loop(): + assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py new file mode 100644 index 00000000000..2f977553187 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py @@ -0,0 +1,8 @@ +def test_thomson_scatter(): + assert False + +def test_line_scatter(): + assert False + +def test_line_emission(): + assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py new file mode 100644 index 00000000000..02d56da7aaa --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py @@ -0,0 +1,2 @@ +def test_macro_atom(): + assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_montecarlo_logger.py b/tardis/montecarlo/montecarlo_numba/tests/test_montecarlo_logger.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py new file mode 100644 index 00000000000..5741b585559 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py @@ -0,0 +1,5 @@ +def test_numba_plasma_initialize(): + assert False + +def test_configuration_initialize(): + assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py new file mode 100644 index 00000000000..9ac37fc3755 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -0,0 +1,303 @@ +import os +import pytest +import numpy as np +import pandas as pd +import tardis.montecarlo.formal_integral as formal_integral +import tardis.montecarlo.montecarlo_numba.r_packet as r_packet +import tardis.montecarlo.montecarlo_configuration as mc +import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface +from tardis import constants as const +from tardis.montecarlo.montecarlo_numba.numba_interface import Estimators +import tardis.montecarlo.montecarlo_numba.numba_config as numba_config +from tardis.montecarlo.montecarlo_numba import macro_atom +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + +from numpy.testing import ( + assert_equal, + assert_almost_equal, + assert_array_equal, + assert_allclose + ) + +@pytest.fixture(scope="function") +def packet(): + return r_packet.RPacket( + r = 7.5e14, + nu = 0.4, + mu = 0.3, + energy = 0.9, + seed = 1963, + index = 0, + is_close_line = 0 + ) + +@pytest.fixture(scope="function") +def model(): + return numba_interface.NumbaModel( + r_inner = np.array([6.912e14, 8.64e14], dtype=np.float64), + r_outer = np.array([8.64e14, 1.0368e15], dtype=np.float64), + time_explosion = 5.2e7 + ) + +@pytest.fixture(scope="function") +def estimators(): + return numba_interface.Estimators( + j_estimator = np.array([0.0, 0.0], dtype=np.float64), + nu_bar_estimator = np.array([0.0, 0.0], dtype=np.float64), + j_blue_estimator = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], dtype=np.float64), + Edotlu_estimator = np.array([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], dtype=np.float64) + ) + +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'mu': 0.3, 'r': 7.5e14}, + {'d_boundary': 259376919351035.88}), + + ({'mu': -.3, 'r': 7.5e13}, + {'d_boundary': -664987228972291.5}), + + ({'mu': -.3, 'r': 7.5e14}, + {'d_boundary': 709376919351035.9})] +) +def test_calculate_distance_boundary(packet_params, expected_params, model): + mu = packet_params['mu'] + r = packet_params['r'] + + d_boundary = r_packet.calculate_distance_boundary( + r, mu, model.r_inner[0], model.r_outer[0]) + + #Accuracy to within 0.1cm + assert_almost_equal(d_boundary[0], expected_params['d_boundary'], decimal=1) +# +# +# TODO: split this into two tests - one to assert errors and other for d_line +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'nu_line': 0.1, 'next_line_id': 0, 'is_last_line': True}, + {'tardis_error': None, 'd_line': 1e+99}), + + ({'nu_line': 0.2, 'next_line_id': 1, 'is_last_line': False}, + {'tardis_error': None, 'd_line': 7.792353908000001e+17}), + + ({'nu_line': 0.5, 'next_line_id': 1, 'is_last_line': False}, + {'tardis_error': r_packet.MonteCarloException, 'd_line': 0.0}), + + ({'nu_line': 0.6, 'next_line_id': 0, 'is_last_line': False}, + {'tardis_error': r_packet.MonteCarloException, 'd_line': 0.0})] +) +def test_calculate_distance_line(packet_params, expected_params, packet, model): + nu_line = packet_params['nu_line'] + is_last_line = packet_params['is_last_line'] + + time_explosion = model.time_explosion + + doppler_factor = r_packet.get_doppler_factor(packet.r, + packet.mu, + time_explosion) + comov_nu = packet.nu * doppler_factor + + d_line = 0 + obtained_tardis_error = None + try: + d_line = r_packet.calculate_distance_line(packet, + comov_nu, + is_last_line, + nu_line, + time_explosion) + except r_packet.MonteCarloException: + obtained_tardis_error = r_packet.MonteCarloException + + assert_almost_equal(d_line, expected_params['d_line']) + assert obtained_tardis_error == expected_params['tardis_error'] + +def test_calculate_distance_electron(): + pass + +def test_calculate_tau_electron(): + pass + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(0.3, 7.5e14, 1 / 5.2e7, 0.9998556693818854), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_doppler_factor(mu, r, inv_t_exp, expected): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_doppler_factor(r, mu, time_explosion) + + + # Perform required assertions + assert_almost_equal(obtained, expected) + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(0.3, 7.5e14, 1 / 5.2e7, 1/0.9998556693818854), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_inverse_doppler_factor(mu, r, inv_t_exp, expected): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_inverse_doppler_factor(r, mu, time_explosion) + + # Perform required assertions + assert_almost_equal(obtained, expected) + + +def test_get_random_mu(): + """ + Ensure that different calls results + """ + output1 = r_packet.get_random_mu() + output2 = r_packet.get_random_mu() + assert output1 != output2 + + +@pytest.mark.parametrize( + ['cur_line_id', 'distance_trace', 'time_explosion', + 'expected_j_blue', + 'expected_Edotlu'], + [(0, 1e12, 5.2e7, + [[2.249673812803061, 0.0, 0.0], [0.0, 0.0, 0.0]], + [[2.249673812803061*0.4, 0.0, 1.0], [0.0, 0.0, 1.0]]), + (0, 0, 5.2e7, + [[2.249675256109242, 0.0, 0.0], [0.0, 0.0, 0.0]], + [[2.249675256109242*0.4, 0.0, 1.0], [0.0, 0.0, 1.0],]), + (1, 1e5, 1e10, + [[0.0, 0.0, 0.0], [2.249998311331767, 0.0, 0.0]], + [[0.0, 0.0, 1.0], [2.249998311331767*0.4, 0.0, 1.0]])] +) +def test_update_line_estimators(estimators, packet, cur_line_id, distance_trace, + time_explosion, expected_j_blue, expected_Edotlu): + r_packet.update_line_estimators(estimators, packet, cur_line_id, distance_trace, + time_explosion) + + assert_allclose(estimators.j_blue_estimator, expected_j_blue) + assert_allclose(estimators.Edotlu_estimator, expected_Edotlu) + +def test_trace_packet(): + pass + +@pytest.mark.parametrize('ENABLE_FULL_RELATIVITY', [True, False]) +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'nu': 0.4, 'mu': 0.3, 'energy': 0.9, 'r': 7.5e14}, + {'mu': 0.3120599529139568, 'r': 753060422542573.9, + 'j': 8998701024436.969, 'nubar': 3598960894542.354}), + + ({'nu': 0.6, 'mu': -.5, 'energy': 0.5, 'r': 8.1e14}, + {'mu': -.4906548373534084, 'r': 805046582503149.2, + 'j': 5001298975563.031, 'nubar': 3001558973156.1387})] +) +def test_move_r_packet(packet_params, expected_params, packet, model, estimators, ENABLE_FULL_RELATIVITY): + distance = 1.e13 + packet.nu = packet_params['nu'] + packet.mu = packet_params['mu'] + packet.energy = packet_params['energy'] + packet.r = packet_params['r'] + + numba_config.ENABLE_FULL_RELATIVITY = ENABLE_FULL_RELATIVITY + r_packet.move_r_packet.recompile() # This must be done as move_r_packet was jitted with ENABLE_FULL_RELATIVITY + doppler_factor = r_packet.get_doppler_factor( + packet.r, + packet.mu, + model.time_explosion) + + r_packet.move_r_packet( + packet, distance, model.time_explosion, estimators) + + assert_almost_equal(packet.mu, expected_params['mu']) + assert_almost_equal(packet.r, expected_params['r']) + + expected_j = expected_params['j'] + expected_nubar = expected_params['nubar'] + + if ENABLE_FULL_RELATIVITY: + expected_j *= doppler_factor + expected_nubar *= doppler_factor + + numba_config.ENABLE_FULL_RELATIVITY = False + assert_allclose(estimators.j_estimator[packet.current_shell_id], + expected_j, rtol=5e-7) + assert_allclose(estimators.nu_bar_estimator[packet.current_shell_id], + expected_nubar, rtol=5e-7) + +def test_set_estimators(): + pass + +def test_set_estimators_full_relativity(): + pass + +def test_line_emission(): + pass + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, 11, 132), + (132, 1, 133), + (132, 2, 133)] +) +def test_move_packet_across_shell_boundary_emitted(packet, current_shell_id, + delta_shell, + no_of_shells): + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.status == r_packet.PacketStatus.EMITTED + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, -133, 132), + (132, -133, 133), + (132, -1e9, 133)] +) +def test_move_packet_across_shell_boundary_reabsorbed(packet, current_shell_id, + delta_shell, + no_of_shells): + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.status == r_packet.PacketStatus.REABSORBED + + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, -1, 199), + (132, 0, 132), + (132, 20, 154)] +) +def test_move_packet_across_shell_boundary_increment(packet, current_shell_id, + delta_shell, + no_of_shells): + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.current_shell_id == current_shell_id + delta_shell + +#SAYS WE NEED TO FIX/REMOVE PACKET CALC ENERGY BY MOVING DOPPLER FACTOR TO FUNCTION +""" +@pytest.mark.parametrize( + ['distance_trace', 'time_explosion'], + [(0, 1), + (1, 1), + (1, 1e9)] +) +def test_packet_energy_limit_one(packet, distance_trace, time_explosion): + initial_energy = packet.energy + new_energy = r_packet.calc_packet_energy(packet, distance_trace, time_explosion) + assert_almost_equal(new_energy, initial_energy) +""" +def test_test_for_close_line(): + pass diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py new file mode 100644 index 00000000000..71cb081c801 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -0,0 +1,8 @@ +def test_single_packet_loop(): + assert False + +def test_set_packet_props_partial_relativity(): + assert False + +def test_set_packet_props_full_relativity(): + assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py new file mode 100644 index 00000000000..dd7cdb42a9d --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -0,0 +1,75 @@ +import os +import pytest +import numpy as np +import pandas as pd +import tardis.montecarlo.formal_integral as formal_integral +import tardis.montecarlo.montecarlo_numba.r_packet as r_packet +import tardis.montecarlo.montecarlo_numba.vpacket as vpacket +import tardis.montecarlo.montecarlo_configuration as mc +import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface +from tardis import constants as const +from tardis.montecarlo.montecarlo_numba.numba_interface import Estimators +from tardis.montecarlo.montecarlo_numba import macro_atom +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + +from numpy.testing import ( + assert_equal, + assert_almost_equal, + assert_array_equal, + assert_allclose + ) + + +@pytest.fixture(scope="function") +def v_packet(): + return vpacket.VPacket( + r = 7.5e14, + nu = 0.4, + mu = 0.3, + energy = 0.9, + current_shell_id = 0, + next_line_id = 0, + index = 0, + is_close_line = 0 + ) + +@pytest.fixture(scope="function") +def model(): + return numba_interface.NumbaModel( + r_inner = np.array([6.912e14, 8.64e14], dtype=np.float64), + r_outer = np.array([8.64e14, 1.0368e15], dtype=np.float64), + time_explosion = 5.2e7 + ) + +@pytest.fixture(scope="function") +def plasma(): + return numba_interface.NumbaPlasma( + electron_density=1.0e9*np.ones(2, dtype=np.float64), + line_list_nu=np.array([ + 1.26318289e+16, + 1.26318289e+16, + 1.23357675e+16, + 1.23357675e+16, + 1.16961598e+16], dtype=np.float64), + tau_sobolev=np.ones((2, 1000), dtype=np.float64), + transition_probabilities=np.zeros((2, 2), dtype=np.float64), + line2macro_level_upper=np.zeros(2, dtype=np.int64), + macro_block_references=np.zeros(2, dtype=np.int64), + transition_type=np.zeros(2, dtype=np.int64), + destination_level_id=np.zeros(2, dtype=np.int64), + transition_line_id=np.zeros(2, dtype=np.int64) + ) + +def test_trace_vpacket_within_shell(v_packet, model, plasma): + tau_trace_combined, distance_boundary, delta_shell = vpacket.trace_vpacket_within_shell( + v_packet, + model, + plasma) + assert False + +def test_trace_vpacket(): + assert False + +def test_trace_vpacket_volley(): + assert False + diff --git a/tardis/montecarlo/tests/conftest.py b/tardis/montecarlo/tests/conftest.py index 054f6b455c4..30b4cd121a6 100644 --- a/tardis/montecarlo/tests/conftest.py +++ b/tardis/montecarlo/tests/conftest.py @@ -1,5 +1,7 @@ import os import pytest +from tardis.io import config_reader + from ctypes import ( CDLL, @@ -15,6 +17,7 @@ CONTINUUM_OFF, BoundFreeTreatment ) +from tardis.montecarlo.base import MontecarloRunner @pytest.fixture(scope="function") @@ -138,3 +141,10 @@ def mt_state_seeded(clib, mt_state): seed = 23111963 clib.rk_seed(seed, byref(mt_state)) return mt_state + +@pytest.fixture(scope="function") +def runner(): + config_fname = 'tardis/io/tests/data/tardis_configv1_verysimply.yml' + config = config_reader.Configuration.from_yaml(config_fname) + runner = MontecarloRunner.from_config(config) + return runner diff --git a/tardis/montecarlo/tests/test_cmontecarlo.py b/tardis/montecarlo/tests/test_cmontecarlo.py deleted file mode 100644 index ff7519e251a..00000000000 --- a/tardis/montecarlo/tests/test_cmontecarlo.py +++ /dev/null @@ -1,1058 +0,0 @@ -""" -Unit tests for methods in `tardis/montecarlo/src/cmontecarlo.c`. -* `ctypes` library is used to wrap C methods and expose them to python. - - -Probable Reasons for Failing Tests: ------------------------------------ - -1. Change made in C struct declarations: - - Reflect the changes done in C structs, into Python counterparts. - - Check **tardis/montecarlo/struct.py**. - -2. Return type of any method changed: - - Modify the `restype` parameter in the test method here. - - For example: - ``` - clib.rpacket_doppler_factor.restype = c_double - ``` - -3. Underlying logic modified: - - Check whether the changes made in C method are logically correct. - - If the changes made were correct and necessary, update the corresponding - test case. - - -General Test Design Procedure: ------------------------------- - -Please follow this design procedure while adding a new test: - -1. Parametrization as per desire of code coverage. - - C tests have different flows controlled by conditional statements. - Parameters checked in conditions can be provided in different testcases. - - Keep consistency with variable names as (in order): - - `packet_params` - - `model_params` - - `expected_params` (`expected` if only one value to be asserted.) - - Suggested variable names can be compromised if readability of the test - increases. - -2. Test Method body: - - Keep name as `test_` + `(name of C method)`. - - Refer to method `test_rpacket_doppler_factor` below for description. -""" - - -import os -import pytest -import numpy as np -import pandas as pd - - -from ctypes import ( - CDLL, - byref, - c_uint, - c_int64, - c_double, - c_ulong, - c_void_p, - cast, - POINTER, - pointer, - CFUNCTYPE - ) - -from numpy.testing import ( - assert_equal, - assert_almost_equal, - assert_array_equal, - assert_allclose - ) - -from tardis import __path__ as path -from tardis.montecarlo.struct import ( - RPacket, StorageModel, RKState, - PhotoXsect1level, - TARDIS_ERROR_OK, - TARDIS_ERROR_BOUNDS_ERROR, - TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE, - TARDIS_PACKET_STATUS_IN_PROCESS, - TARDIS_PACKET_STATUS_EMITTED, - TARDIS_PACKET_STATUS_REABSORBED, - CONTINUUM_OFF, - CONTINUUM_ON, - INVERSE_C, - BoundFreeTreatment -) - - -@pytest.fixture(scope='module') -def continuum_compare_data_fname(): - fname = 'continuum_compare_data.hdf' - return os.path.join(path[0], 'montecarlo', 'tests', 'data', fname) - - -@pytest.fixture(scope='module') -def continuum_compare_data(continuum_compare_data_fname, request): - compare_data = pd.HDFStore(continuum_compare_data_fname, mode='r') - - def fin(): - compare_data.close() - request.addfinalizer(fin) - - return compare_data - - -@pytest.fixture(scope="function") -def expected_ff_emissivity(continuum_compare_data): - emissivities = continuum_compare_data['ff_emissivity'] - - def ff_emissivity(t_electron): - emissivity = emissivities[t_electron] - nu_bins = emissivity['nu_bins'].values - emissivity_value = emissivity['emissivity'].dropna().values - - return nu_bins, emissivity_value - - return ff_emissivity - - -@pytest.fixture(scope='module') -def get_rkstate(continuum_compare_data): - data = continuum_compare_data['z2rkstate_key'] - pos_data = continuum_compare_data['z2rkstate_pos'] - - def z2rkstate(z_random): - key = (c_ulong * 624)(*data.loc[z_random].values) - pos = pos_data.loc[z_random] - return RKState( - key=key, - pos=pos, - has_gauss=0, - gauss=0.0 - ) - - return z2rkstate - - -@pytest.fixture(scope='function') -def model_w_edges(ion_edges, model): - photo_xsect = (POINTER(PhotoXsect1level) * len(ion_edges))() - - for i, edge in enumerate(ion_edges): - x_sect_1level = PhotoXsect1level() - for key, value in edge.items(): - if key in ['nu', 'x_sect']: - value = (c_double * len(value))(*value) - setattr(x_sect_1level, key, value) - photo_xsect[i] = pointer(x_sect_1level) - - no_of_edges = len(ion_edges) - continuum_list_nu = (c_double * no_of_edges)(*[edge['nu'][0] for edge in ion_edges]) - - model.photo_xsect = photo_xsect - model.continuum_list_nu = continuum_list_nu - model.no_of_edges = no_of_edges - - estimator_size = model.no_of_shells * no_of_edges - estims = ['photo_ion_estimator', 'stim_recomb_estimator', - 'bf_heating_estimator', 'stim_recomb_cooling_estimator' - ] - for estimator in estims: - setattr(model, estimator, (c_double * estimator_size)(*[0] * estimator_size)) - - model.photo_ion_estimator_statistics = (c_int64 * estimator_size)(*[0] * estimator_size) - return model - - -@pytest.fixture(scope='module') -def ion_edges(): - return [ - {'nu': [4.0e14, 4.1e14, 4.2e14, 4.3e14], - 'x_sect': [1.0, 0.9, 0.8, 0.7], 'no_of_points': 4}, - {'nu': [3.0e14, 3.1e14, 3.2e14, 3.3e14, 3.4e14], - 'x_sect': [1.0, 0.9, 0.8, 0.7, 0.6], 'no_of_points': 5}, - {'nu': [2.8e14, 3.0e14, 3.2e14, 3.4e14], - 'x_sect': [2.0, 1.8, 1.6, 1.4], 'no_of_points': 4} - ] - - -@pytest.fixture(scope='module') -def mock_sample_nu(): - SAMPLE_NUFUNC = CFUNCTYPE(c_double, POINTER(RPacket), - POINTER(StorageModel), POINTER(RKState)) - - def sample_nu_simple(packet, model, mt_state): - return packet.contents.nu - - return SAMPLE_NUFUNC(sample_nu_simple) - - -@pytest.fixture(scope='function') -def model_3lvlatom(model): - model.line2macro_level_upper = (c_int64 * 3)(*[2, 1, 2]) - model.macro_block_references = (c_int64 * 3)(*[0, 2, 5]) - - transition_probabilities = [ - 0.0, 0.0, 0.75, 0.25, 0.0, 0.25, 0.5, 0.25, 0.0, # shell_id = 0 - 0.0, 0.0, 1.00, 0.00, 0.0, 0.00, 0.0, 1.00, 0.0 # shell_id = 1 - ] - - nd = len(transition_probabilities)//2 - model.transition_type = (c_int64 * nd)(*[1, 1, -1, 1, 0, 0, -1, -1, 0]) - model.destination_level_id = (c_int64 * nd)(*[1, 2, 0, 2, 0, 1, 1, 0, 0]) - model.transition_line_id = (c_int64 * nd)(*[0, 1, 1, 2, 1, 2, 2, 0, 0]) - - model.transition_probabilities_nd = c_int64(nd) - model.transition_probabilities = (c_double * (nd * 2))(*transition_probabilities) - - model.last_line_interaction_out_id = (c_int64 * 1)(*[-5]) - - return model - - -def d_cont_setter(d_cont, model, packet): - model.inverse_electron_densities[packet.current_shell_id] = c_double(1.0) - model.inverse_sigma_thomson = c_double(1.0) - packet.tau_event = c_double(d_cont) - - -def d_line_setter(d_line, model, packet): - packet.mu = c_double(0.0) - scale = d_line * 1e1 - model.time_explosion = c_double(INVERSE_C * scale) - packet.nu = c_double(1.0) - nu_line = (1. - d_line/scale) - packet.nu_line = c_double(nu_line) - - -def d_boundary_setter(d_boundary, model, packet): - packet.mu = c_double(1e-16) - r_outer = 2. * d_boundary - model.r_outer[packet.current_shell_id] = r_outer - - r = np.sqrt(r_outer**2 - d_boundary**2) - packet.r = r - - - - -""" -Important Tests: ----------------- -The tests written further (till next block comment is encountered) have been -categorized as important tests, these tests correspond to methods which are -relatively old and stable code. -""" - - -@pytest.mark.parametrize( - ['x', 'x_insert', 'imin', 'imax', 'expected_params'], - [([5.0, 4.0, 3.0, 1.0], 2.0, 0, 3, - {'result': 2, 'ret_val': TARDIS_ERROR_OK}), - - ([5.0, 4.0, 3.0, 2.0], 0.0, 0, 3, - {'result': 0, 'ret_val': TARDIS_ERROR_BOUNDS_ERROR})] -) -def test_reverse_binary_search(clib, x, x_insert, imin, imax, expected_params): - x = (c_double * (imax - imin + 1))(*x) - x_insert = c_double(x_insert) - imin = c_int64(imin) - imax = c_int64(imax) - obtained_result = c_int64(0) - - clib.reverse_binary_search.restype = c_uint - obtained_tardis_error = clib.reverse_binary_search( - byref(x), x_insert, imin, imax, byref(obtained_result)) - - assert obtained_result.value == expected_params['result'] - assert obtained_tardis_error == expected_params['ret_val'] - - -@pytest.mark.parametrize( - ['nu', 'nu_insert', 'number_of_lines', 'expected_params'], - [([0.5, 0.4, 0.3, 0.1], 0.2, 4, - {'result': 3, 'ret_val': TARDIS_ERROR_OK}), - - ([0.5, 0.4, 0.3, 0.2], 0.1, 4, - {'result': 4, 'ret_val': TARDIS_ERROR_OK}), - - ([0.4, 0.3, 0.2, 0.1], 0.5, 4, - {'result': 0, 'ret_val': TARDIS_ERROR_OK})] -) -def test_line_search(clib, nu, nu_insert, number_of_lines, expected_params): - nu = (c_double * number_of_lines)(*nu) - nu_insert = c_double(nu_insert) - number_of_lines = c_int64(number_of_lines) - obtained_result = c_int64(0) - - clib.line_search.restype = c_uint - obtained_tardis_error = clib.line_search( - byref(nu), nu_insert, number_of_lines, byref(obtained_result)) - - assert obtained_result.value == expected_params['result'] - assert obtained_tardis_error == expected_params['ret_val'] - -@pytest.mark.parametrize( - ['x', 'x_insert', 'imin', 'imax', 'expected_params'], - [([2.0, 4.0, 6.0, 7.0], 5.0, 0, 3, - {'result': 2, 'ret_val': TARDIS_ERROR_OK}), - - ([2.0, 3.0, 5.0, 7.0], 8.0, 0, 3, - {'result': 0, 'ret_val': TARDIS_ERROR_BOUNDS_ERROR}), - - ([2.0, 4.0, 6.0, 7.0], 4.0, 0, 3, - {'result': 0, 'ret_val': TARDIS_ERROR_OK}) - ] -) -def test_binary_search(clib, x, x_insert, imin, imax, expected_params): - x = (c_double * (imax - imin + 1))(*x) - x_insert = c_double(x_insert) - imin = c_int64(imin) - imax = c_int64(imax) - obtained_result = c_int64(0) - - clib.binary_search.restype = c_uint - obtained_tardis_error = clib.binary_search( - byref(x), x_insert, imin, imax, byref(obtained_result)) - - assert obtained_result.value == expected_params['result'] - assert obtained_tardis_error == expected_params['ret_val'] - - -@pytest.mark.parametrize( - ['mu', 'r', 'inv_t_exp', 'expected'], - [(0.3, 7.5e14, 1 / 5.2e7, 0.9998556693818854), - (-.3, 8.1e14, 1 / 2.6e7, 1.0003117541351274)] -) -def test_rpacket_doppler_factor(clib, mu, r, inv_t_exp, expected, packet, model): - # Set the params from test cases here - packet.mu = mu - packet.r = r - model.inverse_time_explosion = inv_t_exp - - # Perform any other setups just before this, they can be additional calls - # to other methods or introduction of some temporary variables - - # Set `restype` attribute if returned quantity is used - clib.rpacket_doppler_factor.restype = c_double - # Call the C method (make sure to pass quantities as `ctypes` data types) - obtained = clib.rpacket_doppler_factor(byref(packet), byref(model)) - - # Perform required assertions - assert_almost_equal(obtained, expected) - - -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'mu': 0.3, 'r': 7.5e14}, - {'d_boundary': 259376919351035.88}), - - ({'mu': -.3, 'r': 7.5e13}, - {'d_boundary': -664987228972291.5}), - - ({'mu': -.3, 'r': 7.5e14}, - {'d_boundary': 709376919351035.9})] -) -def test_compute_distance2boundary(clib, packet_params, expected_params, packet, model): - packet.mu = packet_params['mu'] - packet.r = packet_params['r'] - - clib.compute_distance2boundary(byref(packet), byref(model)) - - assert_almost_equal(packet.d_boundary, expected_params['d_boundary']) - - -# TODO: split this into two tests - one to assert errors and other for d_line -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'nu_line': 0.1, 'next_line_id': 0, 'last_line': 1}, - {'tardis_error': TARDIS_ERROR_OK, 'd_line': 1e+99}), - - ({'nu_line': 0.2, 'next_line_id': 1, 'last_line': 0}, - {'tardis_error': TARDIS_ERROR_OK, 'd_line': 7.792353908000001e+17}), - - ({'nu_line': 0.5, 'next_line_id': 1, 'last_line': 0}, - {'tardis_error': TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE, 'd_line': 0.0}), - - ({'nu_line': 0.6, 'next_line_id': 0, 'last_line': 0}, - {'tardis_error': TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE, 'd_line': 0.0})] -) -def test_compute_distance2line(clib, packet_params, expected_params, packet, model): - packet.nu_line = packet_params['nu_line'] - packet.next_line_id = packet_params['next_line_id'] - packet.last_line = packet_params['last_line'] - - packet.d_line = 0.0 - clib.compute_distance2line.restype = c_uint - obtained_tardis_error = clib.compute_distance2line(byref(packet), byref(model)) - - assert_almost_equal(packet.d_line, expected_params['d_line']) - assert obtained_tardis_error == expected_params['tardis_error'] - - -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'virtual_packet': 0}, - {'chi_cont': 6.652486e-16, 'd_cont': 4.359272608766106e+28}), - - ({'virtual_packet': 1}, - {'chi_cont': 6.652486e-16, 'd_cont': 1e+99})] -) -def test_compute_distance2continuum(clib, packet_params, expected_params, packet, model): - packet.virtual_packet = packet_params['virtual_packet'] - - clib.compute_distance2continuum(byref(packet), byref(model)) - - assert_almost_equal(packet.chi_cont, expected_params['chi_cont']) - assert_almost_equal(packet.d_cont, expected_params['d_cont']) - - -@pytest.mark.parametrize('full_relativity', [1, 0]) -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'nu': 0.4, 'mu': 0.3, 'energy': 0.9, 'r': 7.5e14}, - {'mu': 0.3120599529139568, 'r': 753060422542573.9, - 'j': 8998701024436.969, 'nubar': 3598960894542.354}), - - ({'nu': 0.6, 'mu': -.5, 'energy': 0.5, 'r': 8.1e14}, - {'mu': -.4906548373534084, 'r': 805046582503149.2, - 'j': 5001298975563.031, 'nubar': 3001558973156.1387})] -) -def test_move_packet(clib, packet_params, expected_params, - packet, model, full_relativity): - packet.nu = packet_params['nu'] - packet.mu = packet_params['mu'] - packet.energy = packet_params['energy'] - packet.r = packet_params['r'] - model.full_relativity = full_relativity - - clib.rpacket_doppler_factor.restype = c_double - doppler_factor = clib.rpacket_doppler_factor(byref(packet), byref(model)) - clib.move_packet(byref(packet), byref(model), c_double(1.e13)) - - assert_almost_equal(packet.mu, expected_params['mu']) - assert_almost_equal(packet.r, expected_params['r']) - - expected_j = expected_params['j'] - expected_nubar = expected_params['nubar'] - if full_relativity: - expected_j *= doppler_factor - expected_nubar *= doppler_factor - - assert_allclose(model.js[packet.current_shell_id], - expected_j, rtol=5e-7) - assert_allclose(model.nubars[packet.current_shell_id], - expected_nubar, rtol=5e-7) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 'j_blue_idx', 'expected'], - [({'nu': 0.30, 'energy': 0.30}, 0, 1.0), - ({'nu': 0.20, 'energy': 1.e5}, 0, 5e5), - ({'nu': 2e15, 'energy': 0.50}, 1, 2.5e-16), - ({'nu': 0.40, 'energy': 1e-7}, 1, 2.5e-7)], -) -def test_increment_j_blue_estimator_full_relativity(clib, packet_params, - j_blue_idx, expected, - packet, model): - packet.nu = packet_params['nu'] - packet.energy = packet_params['energy'] - model.full_relativity = True - - clib.increment_j_blue_estimator(byref(packet), byref(model), - c_double(packet.d_line), - c_int64(j_blue_idx)) - - assert_almost_equal(model.line_lists_j_blues[j_blue_idx], expected) - - -@pytest.mark.parametrize( - ['packet_params', 'j_blue_idx', 'expected'], - [({'nu': 0.1, 'mu': 0.3, 'r': 7.5e14}, 0, 8.998643292289723), - ({'nu': 0.2, 'mu': -.3, 'r': 7.7e14}, 0, 4.499971133976377), - ({'nu': 0.5, 'mu': 0.5, 'r': 7.9e14}, 1, 0.719988453650551), - ({'nu': 0.6, 'mu': -.5, 'r': 8.1e14}, 1, 0.499990378058792)] -) -def test_increment_j_blue_estimator(clib, packet_params, j_blue_idx, expected, packet, model): - packet.nu = packet_params['nu'] - packet.mu = packet_params['mu'] - packet.r = packet_params['r'] - - clib.compute_distance2line(byref(packet), byref(model)) - clib.move_packet(byref(packet), byref(model), c_double(1.e13)) - clib.increment_j_blue_estimator(byref(packet), byref(model), - c_double(packet.d_line), c_int64(j_blue_idx)) - - assert_almost_equal(model.line_lists_j_blues[j_blue_idx], expected) - - -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'virtual_packet': 0, 'current_shell_id': 0, 'next_shell_id': 1}, - {'status': TARDIS_PACKET_STATUS_IN_PROCESS, 'current_shell_id': 1}), - - ({'virtual_packet': 1, 'current_shell_id': 1, 'next_shell_id': 1}, - {'status': TARDIS_PACKET_STATUS_EMITTED, 'current_shell_id': 1, - 'tau_event': 29000000000000.008}), - - ({'virtual_packet': 1, 'current_shell_id': 0, 'next_shell_id': -1}, - {'status': TARDIS_PACKET_STATUS_REABSORBED, 'current_shell_id': 0, - 'tau_event': 29000000000000.008})] -) -def test_move_packet_across_shell_boundary(clib, packet_params, expected_params, - packet, model, mt_state): - packet.virtual_packet = packet_params['virtual_packet'] - packet.current_shell_id = packet_params['current_shell_id'] - packet.next_shell_id = packet_params['next_shell_id'] - - clib.move_packet_across_shell_boundary(byref(packet), byref(model), - c_double(1.e13), byref(mt_state)) - - if packet_params['virtual_packet'] == 1: - assert_almost_equal(packet.tau_event, expected_params['tau_event']) - assert packet.status == expected_params['status'] - assert packet.current_shell_id == expected_params['current_shell_id'] - - -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'nu': 0.4, 'mu': 0.3, 'energy': 0.9, 'r': 7.5e14}, - {'nu': 0.39974659819356556, 'energy': 0.8994298459355226}), - - ({'nu': 0.6, 'mu': -.5, 'energy': 0.5, 'r': 8.1e14}, - {'nu': 0.5998422620533325, 'energy': 0.4998685517111104})] -) -def test_montecarlo_thomson_scatter(clib, packet_params, expected_params, packet, - model, mt_state): - packet.nu = packet_params['nu'] - packet.mu = packet_params['mu'] - packet.energy = packet_params['energy'] - packet.r = packet_params['r'] - - clib.montecarlo_thomson_scatter(byref(packet), byref(model), - c_double(1.e13), byref(mt_state)) - - assert_almost_equal(packet.nu, expected_params['nu']) - assert_almost_equal(packet.energy, expected_params['energy']) - - -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - # TODO: Add scientifically sound test cases. - [({'virtual_packet': 1, 'tau_event': 2.9e13, 'last_line': 0}, - {'tau_event': 2.9e13, 'next_line_id': 2}), - - ({'virtual_packet': 0, 'tau_event': 2.9e13, 'last_line': 0}, - {'tau_event': 2.9e13, 'next_line_id': 2}), - - ({'virtual_packet': 0, 'tau_event': 2.9e13, 'last_line': 0}, - {'tau_event': 2.9e13, 'next_line_id': 2}), - ] -) -def test_montecarlo_line_scatter(clib, packet_params, expected_params, packet, model, mt_state): - packet.virtual_packet = packet_params['virtual_packet'] - packet.tau_event = packet_params['tau_event'] - packet.last_line = packet_params['last_line'] - - clib.montecarlo_line_scatter(byref(packet), byref(model), - c_double(1.e13), byref(mt_state)) - - assert_almost_equal(packet.tau_event, expected_params['tau_event']) - assert_almost_equal(packet.next_line_id, expected_params['next_line_id']) - - -@pytest.mark.parametrize( - ['distances', 'expected'], - [({'boundary': 1.3e13, 'continuum': 1e14, 'line': 1e15}, - {'handler': 'move_packet_across_shell_boundary', 'distance': 1.3e13}), - - ({'boundary': 1.3e13, 'continuum': 1e14, 'line': 2.5e12}, - {'handler': 'montecarlo_line_scatter', 'distance': 2.5e12}), - - ({'boundary': 1.3e13, 'continuum': 1e11, 'line': 2.5e12}, - {'handler': 'montecarlo_thomson_scatter', 'distance': 1e11})] -) -def test_get_event_handler(clib, packet, model, mt_state, distances, expected): - d_cont_setter(distances['continuum'], model, packet) - d_line_setter(distances['line'], model, packet) - d_boundary_setter(distances['boundary'], model, packet) - obtained_distance = c_double() - - clib.get_event_handler.restype = c_void_p - obtained_handler = clib.get_event_handler(byref(packet), byref(model), - byref(obtained_distance), - byref(mt_state)) - - expected_handler = getattr(clib, expected['handler']) - expected_handler = cast(expected_handler, c_void_p).value - - assert_equal(obtained_handler, expected_handler) - assert_allclose(obtained_distance.value, expected['distance'], rtol=1e-10) - - -@pytest.mark.parametrize( - ['z_random', 'packet_params', 'expected'], - [(0.22443743797312765, - {'activation_level': 1, 'shell_id': 0}, 1), # Direct deactivation - - (0.78961460371187597, # next z_random = 0.818455414618 - {'activation_level': 1, 'shell_id': 0}, 0), # Upwards jump, then deactivation - - (0.22443743797312765, # next z_random = 0.545678896748 - {'activation_level': 2, 'shell_id': 0}, 1), # Downwards jump, then deactivation - - (0.765958602560605, # next z_random = 0.145914243888, 0.712382380384 - {'activation_level': 1, 'shell_id': 0}, 1), # Upwards jump, downwards jump, then deactivation - - (0.22443743797312765, - {'activation_level': 2, 'shell_id': 1}, 0)] # Direct deactivation -) -def test_macro_atom(clib, model_3lvlatom, packet, z_random, packet_params, get_rkstate, expected): - packet.macro_atom_activation_level = packet_params['activation_level'] - packet.current_shell_id = packet_params['shell_id'] - rkstate = get_rkstate(z_random) - - clib.macro_atom(byref(packet), byref(model_3lvlatom), byref(rkstate)) - obtained_line_id = model_3lvlatom.last_line_interaction_out_id[packet.id] - - assert_equal(obtained_line_id, expected) - - - -""" -Simple Tests: ----------------- -These test check very simple pices of code still work. -""" - -@pytest.mark.parametrize( - ['packet_params', 'line_idx', 'expected'], - [({'energy': 0.0}, 0, 0), - ({'energy': 1.0}, 1, 1), - ({'energy': 0.5}, 2, 1.5)] -) -@pytest.mark.skipif(True, reason="Needs rewrite to be relevant.") -def test_increment_Edotlu_estimator(clib, packet_params, line_idx, expected, packet, model): - packet.energy = packet_params['energy'] - - clib.increment_Edotlu_estimator(byref(packet), byref(model), c_int64(line_idx)) - - assert_almost_equal(model.line_lists_Edotlu[line_idx], expected) - - -""" -Difficult Tests: ----------------- -The tests written further are more complex than previous tests. They require -proper design procedure. They are not taken up yet and intended to be -completed together in future. -""" - - - -@pytest.mark.skipif(True, reason="Yet to be written.") -def test_montecarlo_one_packet(packet, model, mt_state): - pass - - -@pytest.mark.skipif(True, reason="Yet to be written.") -def test_montecarlo_one_packet_loop(packet, model, mt_state): - pass - - -@pytest.mark.skipif(True, reason="Yet to be written.") -def test_montecarlo_main_loop(packet, model, mt_state): - pass - - - -""" -Continuum Tests: ----------------- -The tests written further (till next block comment is encountered) are for the -methods related to continuum interactions. -""" - - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - 't_electron', [2500., 15000.] -) -def test_sample_nu_free_free(clib, t_electron, packet, model, mt_state_seeded, expected_ff_emissivity): - model.t_electrons[packet.current_shell_id] = t_electron - clib.sample_nu_free_free.restype = c_double - - nu_bins, expected_emissivity = expected_ff_emissivity(t_electron) - - nus = [] - for _ in range(int(1e5)): - nu = clib.sample_nu_free_free(byref(packet), byref(model), byref(mt_state_seeded)) - nus.append(nu) - - obtained_emissivity, _ = np.histogram(nus, density=True, bins=nu_bins) - - assert_allclose(obtained_emissivity, expected_emissivity, rtol=1e-10) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 't_electrons', 'chi_ff_factor', 'expected'], - [({'nu': 4.5e14, 'mu': 0.0, 'current_shell_id': 1}, 15000, 2.0, 1.6746639430359494e-44), - ({'nu': 3.0e15, 'mu': 0.0, 'current_shell_id': 0}, 5000, 3.0, 1.1111111111107644e-46), - ({'nu': 3.0e15, 'mu': 0.4, 'current_shell_id': 0}, 10000, 4.0, 1.5638286016098277e-46)] -) -def test_calculate_chi_ff(clib, packet, model, packet_params, t_electrons, chi_ff_factor, expected): - packet.mu = packet_params['mu'] - packet.nu = packet_params['nu'] - packet.current_shell_id = packet_params['current_shell_id'] - packet.r = 1.04e17 - - model.t_electrons[packet_params['current_shell_id']] = t_electrons - model.chi_ff_factor[packet_params['current_shell_id']] = chi_ff_factor - - clib.calculate_chi_ff(byref(packet), byref(model)) - obtained = packet.chi_ff - - assert_equal(obtained, expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['continuum_status', 'z_random', 'packet_params', 'expected'], - [(CONTINUUM_OFF, 0.94183547596539363, - {'chi_c': 1.0, 'chi_th': 0.4, 'chi_bf': 0.5}, - 'montecarlo_thomson_scatter'), - - (CONTINUUM_ON, 0.22443743797312765, - {'chi_c': 1.0, 'chi_th': 0.4, 'chi_bf': 0.5}, - 'montecarlo_thomson_scatter'), - - (CONTINUUM_ON, 0.54510721066252377, - {'chi_c': 1.0, 'chi_th': 0.4, 'chi_bf': 0.5}, - 'montecarlo_bound_free_scatter'), - - (CONTINUUM_ON, 0.94183547596539363, - {'chi_c': 1.0, 'chi_th': 0.4, 'chi_bf': 0.5}, - 'montecarlo_free_free_scatter'), - - (CONTINUUM_ON, 0.22443743797312765, - {'chi_c': 1e2, 'chi_th': 1e1, 'chi_bf': 2e1}, - 'montecarlo_bound_free_scatter')] -) -def test_montecarlo_continuum_event_handler(clib, continuum_status, expected, z_random, - packet_params, packet, model, get_rkstate): - packet.chi_cont = packet_params['chi_c'] - packet.chi_th = packet_params['chi_th'] - packet.chi_bf = packet_params['chi_bf'] - model.cont_status = continuum_status - - rkstate = get_rkstate(z_random) - - clib.montecarlo_continuum_event_handler.restype = c_void_p - obtained = clib.montecarlo_continuum_event_handler(byref(packet), - byref(model), byref(rkstate)) - expected = getattr(clib, expected) - expected = cast(expected, c_void_p).value - - assert_equal(obtained, expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['nu', 'continuum_id', 'expected', 'bf_treatment'], - [(4.40e14, 1, 0.00, BoundFreeTreatment.LIN_INTERPOLATION), - (3.25e14, 1, 0.75, BoundFreeTreatment.LIN_INTERPOLATION), - (4.03e14, 0, 0.97, BoundFreeTreatment.LIN_INTERPOLATION), - (4.10e14 + 1e-1, 0, 0.90, BoundFreeTreatment.LIN_INTERPOLATION), - pytest.param(4.1e14, 0, 0.90, BoundFreeTreatment.LIN_INTERPOLATION, - marks=pytest.mark.xfail), - (6.50e14, 0, 0.23304506144742834, BoundFreeTreatment.HYDROGENIC), - (3.40e14, 2, 1.1170364339507428, BoundFreeTreatment.HYDROGENIC)] -) -def test_bf_cross_section(clib, nu, continuum_id, model_w_edges, expected, bf_treatment): - model_w_edges.bf_treatment = bf_treatment.value - - clib.bf_cross_section.restype = c_double - obtained = clib.bf_cross_section(byref(model_w_edges), continuum_id, c_double(nu)) - - assert_almost_equal(obtained, expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 'expected'], - [({'nu': 4.13e14, 'mu': 0.0, 'current_shell_id': 1}, - [3.2882087455641473, 0.0, 0.0]), - - ({'nu': 3.27e14, 'mu': 0.0, 'current_shell_id': 0}, - [0.0, 1.3992114634681028, 5.702548202131454]), - - ({'nu': 3.27e14, 'mu': -0.4, 'current_shell_id': 0}, - [0.0, 1.2670858, 5.4446587])] -) -def test_calculate_chi_bf(clib, packet_params, expected, packet, model_w_edges): - model_w_edges.l_pop = (c_double * 6)(*range(1, 7)) - model_w_edges.l_pop_r = (c_double * 6)(*np.linspace(0.1, 0.6, 6)) - model_w_edges.t_electrons[packet_params['current_shell_id']] = 1e4 - - packet.mu = packet_params['mu'] - packet.nu = packet_params['nu'] - packet.r = 1.04e17 - packet.current_shell_id = packet_params['current_shell_id'] - packet.chi_bf_tmp_partial = (c_double * model_w_edges.no_of_edges)() - - clib.calculate_chi_bf(byref(packet), byref(model_w_edges)) - - obtained_chi_bf_tmp = np.ctypeslib.as_array(packet.chi_bf_tmp_partial, shape=(model_w_edges.no_of_edges,)) - expected_chi_bf_tmp = np.array(expected) - expected_chi_bf = expected_chi_bf_tmp[expected_chi_bf_tmp > 0][-1] - - assert_almost_equal(obtained_chi_bf_tmp, expected_chi_bf_tmp) - assert_almost_equal(packet.chi_bf, expected_chi_bf) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['comov_energy', 'distance', 'chi_ff', 'no_of_updates', 'expected'], - [(1.3, 1.3e14, 3e-12, 1, 507.), - (0.9, 0.7e15, 2e-12, 10, 1.260e4), - (0.8, 0.8e13, 1.5e-12, 35, 336.)] -) -def test_increment_continuum_estimators_ff_heating_estimator(clib, packet, model_w_edges, comov_energy, distance, - chi_ff, no_of_updates, expected): - packet.chi_ff = chi_ff - - for _ in range(no_of_updates): - clib.increment_continuum_estimators(byref(packet), byref(model_w_edges), c_double(distance), - c_double(0), c_double(comov_energy)) - obtained = model_w_edges.ff_heating_estimator[packet.current_shell_id] - - assert_almost_equal(obtained, expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['comov_nus', 'expected'], - [([4.05e14, 4.17e14, 3.3e14, 3.2e14, 2.9e14], [2, 2, 3]), - ([4.15e15, 3.25e14, 3.3e14, 2.85e14, 2.9e14], [0, 2, 4])] -) -def test_increment_continuum_estimators_photo_ion_estimator_statistics(clib, packet, model_w_edges, comov_nus, expected): - for comov_nu in comov_nus: - clib.increment_continuum_estimators(byref(packet), byref(model_w_edges), c_double(1e13), - c_double(comov_nu), c_double(1.0)) - - no_of_edges = model_w_edges.no_of_edges - no_of_shells = model_w_edges.no_of_shells - - obtained = np.ctypeslib.as_array(model_w_edges.photo_ion_estimator_statistics, - shape=(no_of_edges * no_of_shells,)) - obtained = np.reshape(obtained, newshape=(no_of_shells, no_of_edges), order='F') - obtained = obtained[packet.current_shell_id] - expected = np.array(expected) - - assert_array_equal(obtained, expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['comov_energy', 'distance', 'comov_nus', 'expected'], - [(1.3, 1.3e14, [4.05e14, 2.65e14], - {"photo_ion": [0.39641975308641975, 0., 0.], - "stim_recomb": [0.056757061269242064, 0., 0.], - "bf_heating": [1.9820987654321076e12, 0., 0.], - "stim_recomb_cooling": [283784812699.75476, 0., 0.]}), - - (0.9, 0.7e15, [3.25e14, 2.85e14], - {"photo_ion": [0., 1.4538461538461538, 7.315141700404858], - "stim_recomb": [0., 0.3055802, 1.7292954], - "bf_heating": [0., 36346153846153.82, 156760323886639.69], - "stim_recomb_cooling": [0., 7639505724285.9746, 33907776077426.875]})] -) -def test_increment_continuum_estimators_bf_estimators(clib, packet, model_w_edges, comov_energy, - distance, comov_nus, expected): - for comov_nu in comov_nus: - clib.increment_continuum_estimators(byref(packet), byref(model_w_edges), c_double(distance), - c_double(comov_nu), c_double(comov_energy)) - - no_of_edges = model_w_edges.no_of_edges - no_of_shells = model_w_edges.no_of_shells - - for estim_name, expected_value in expected.items(): - obtained = np.ctypeslib.as_array(getattr(model_w_edges, estim_name + "_estimator"), - shape=(no_of_edges * no_of_shells,)) - obtained = np.reshape(obtained, newshape=(no_of_shells, no_of_edges), order='F') - obtained = obtained[packet.current_shell_id] - - assert_almost_equal(obtained, np.array(expected_value)) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 'expected_params'], - [({'nu_comov': 1.1e16, 'mu': 0.0, 'r': 1.4e14}, - {'next_line_id': 5, 'last_line': True, 'nu': 1.1e16, 'type_id': 3}), - - ({'nu_comov': 1.3e16, 'mu': 0.3, 'r': 7.5e14}, - {'next_line_id': 0, 'last_line': False, 'nu': 1.30018766e+16, 'type_id': 3}), - - ({'nu_comov': 1.24e16, 'mu': -0.3, 'r': 7.5e14}, - {'next_line_id': 2, 'last_line': False, 'nu': 1.23982106e+16, 'type_id': 4})] -) -def test_continuum_emission(clib, packet, model, mock_sample_nu, packet_params, expected_params, mt_state): - packet.nu = packet_params['nu_comov'] # Is returned by mock function mock_sample_nu - packet.mu = packet_params['mu'] - packet.r = packet_params['r'] - expected_interaction_out_type = expected_params['type_id'] - - clib.continuum_emission(byref(packet), byref(model), byref(mt_state), - mock_sample_nu, expected_interaction_out_type) - - obtained_next_line_id = packet.next_line_id - obtained_last_interaction_out_type = model.last_interaction_out_type[0] - - assert_equal(obtained_next_line_id, expected_params['next_line_id']) - assert_equal(packet.last_line, expected_params['last_line']) - assert_equal(expected_interaction_out_type, obtained_last_interaction_out_type) - assert_allclose(packet.nu, expected_params['nu'], rtol=1e-7) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 'expected'], - [({'next_line_id': 3, 'last_line': 0}, 1), - ({'next_line_id': 5, 'last_line': 1}, 0), - ({'next_line_id': 2, 'last_line': 0}, 0), - ({'next_line_id': 1, 'last_line': 0}, 1)] -) -def test_test_for_close_line(clib, packet, model, packet_params, expected): - packet.nu_line = model.line_list_nu[packet_params['next_line_id'] - 1] - packet.next_line_id = packet_params['next_line_id'] - - clib.test_for_close_line(byref(packet), byref(model)) - - assert_equal(expected, packet.close_line) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['packet_params', 'z_random', 'expected'], - [({'current_continuum_id': 1, 'chi_bf_tmp_partial': [0.0, 0.23e13, 1.0e13]}, - 0.22443743797312765, 1), - - ({'current_continuum_id': 1, 'chi_bf_tmp_partial': [0.0, 0.23e10, 1.0e10]}, - 0.78961460371187597, 2), - - ({'current_continuum_id': 0, 'chi_bf_tmp_partial': [0.2e5, 0.5e5, 0.6e5, 1.0e5, 1.0e5]}, - 0.78961460371187597, 3)] -) -def test_montecarlo_bound_free_scatter_continuum_selection(clib, packet, model_3lvlatom, packet_params, - get_rkstate, z_random, expected): - rkstate = get_rkstate(z_random) - packet.current_continuum_id = packet_params['current_continuum_id'] - - chi_bf_tmp = packet_params['chi_bf_tmp_partial'] - packet.chi_bf_tmp_partial = (c_double * len(chi_bf_tmp))(*chi_bf_tmp) - packet.chi_bf = chi_bf_tmp[-1] - model_3lvlatom.no_of_edges = len(chi_bf_tmp) - - clib.montecarlo_bound_free_scatter(byref(packet), byref(model_3lvlatom), - c_double(1.e13), byref(rkstate)) - - assert_equal(packet.current_continuum_id, expected) - assert_equal(model_3lvlatom.last_line_interaction_in_id[packet.id], expected) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['mu', 'r', 'inv_t_exp', 'full_relativity'], - [(0.8, 7.5e14, 1 / 5.2e5, 1), - (-0.7, 7.5e14, 1 / 5.2e5, 1), - (0.3, 7.5e14, 1 / 2.2e5, 1), - (0.0, 7.5e14, 1 / 2.2e5, 1), - (-0.7, 7.5e14, 1 / 5.2e5, 0)] -) -def test_frame_transformations(clib, packet, model, mu, r, - inv_t_exp, full_relativity): - packet.r = r - packet.mu = mu - model.inverse_time_explosion = inv_t_exp - model.full_relativity = full_relativity - clib.rpacket_doppler_factor.restype = c_double - clib.rpacket_inverse_doppler_factor.restype = c_double - - inverse_doppler_factor = clib.rpacket_inverse_doppler_factor(byref(packet), byref(model)) - clib.angle_aberration_CMF_to_LF(byref(packet), byref(model)) - - doppler_factor = clib.rpacket_doppler_factor(byref(packet), byref(model)) - - assert_almost_equal(doppler_factor * inverse_doppler_factor, 1.0) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - ['mu', 'r', 'inv_t_exp'], - [(0.8, 7.5e14, 1 / 5.2e5), - (-0.7, 7.5e14, 1 / 5.2e5), - (0.3, 7.5e14, 1 / 2.2e5), - (0.0, 7.5e14, 1 / 2.2e5), - (-0.7, 7.5e14, 1 / 5.2e5)] -) -def test_angle_transformation_invariance(clib, packet, model, - mu, r, inv_t_exp): - packet.r = r - packet.mu = mu - model.inverse_time_explosion = inv_t_exp - model.full_relativity = 1 - clib.angle_aberration_LF_to_CMF.restype = c_double - - clib.angle_aberration_CMF_to_LF(byref(packet), byref(model)) - mu_obtained = clib.angle_aberration_LF_to_CMF( - byref(packet), byref(model), c_double(packet.mu)) - - assert_almost_equal(mu_obtained, mu) - - -@pytest.mark.continuumtest -@pytest.mark.parametrize( - 'full_relativity', - [1, 0] -) -@pytest.mark.parametrize( - ['mu', 'r', 't_exp', 'nu', 'nu_line'], - [(0.8, 7.5e14, 5.2e5, 1.0e15, 9.4e14), - (0.0, 6.3e14, 2.2e5, 6.0e12, 5.8e12), - (1.0, 9.0e14, 2.2e5, 4.0e8, 3.4e8), - (0.9, 9.0e14, 0.5e5, 1.0e15, 4.5e14), - (-0.7, 7.5e14, 5.2e5, 1.0e15, 9.8e14), - (-1.0, 6.3e14, 2.2e5, 6.0e12, 6.55e12)] -) -def test_compute_distance2line_relativistic(clib, mu, r, t_exp, nu, nu_line, - full_relativity, packet, model): - packet.r = r - packet.mu = mu - packet.nu = nu - packet.nu_line = nu_line - model.inverse_time_explosion = 1 / t_exp - model.time_explosion = t_exp - model.full_relativity = full_relativity - - clib.rpacket_doppler_factor.restype = c_double - - clib.compute_distance2line(byref(packet), byref(model)) - clib.move_packet(byref(packet), byref(model), c_double(packet.d_line)) - - doppler_factor = clib.rpacket_doppler_factor(byref(packet), byref(model)) - comov_nu = packet.nu * doppler_factor - - assert_allclose(comov_nu, nu_line, rtol=1e-14) - - -@pytest.mark.continuumtest -@pytest.mark.skipif(True, reason="Yet to be written.") -def test_montecarlo_free_free_scatter(packet, model, mt_state): - pass diff --git a/tardis/montecarlo/tests/test_montecarlo.py b/tardis/montecarlo/tests/test_montecarlo.py index e69de29bb2d..2ca4c6397c5 100644 --- a/tardis/montecarlo/tests/test_montecarlo.py +++ b/tardis/montecarlo/tests/test_montecarlo.py @@ -0,0 +1,954 @@ +""" +Unit tests for methods in `tardis/montecarlo/src/cmontecarlo.c`. +* `ctypes` library is used to wrap C methods and expose them to python. + + +Probable Reasons for Failing Tests: +----------------------------------- + +1. Change made in C struct declarations: + - Reflect the changes done in C structs, into Python counterparts. + - Check **tardis/montecarlo/struct.py**. + +2. Return type of any method changed: + - Modify the `restype` parameter in the test method here. + - For example: + ``` + clib.rpacket_doppler_factor.restype = c_double + ``` + +3. Underlying logic modified: + - Check whether the changes made in C method are logically correct. + - If the changes made were correct and necessary, update the corresponding + test case. + + +General Test Design Procedure: +------------------------------ + +Please follow this design procedure while adding a new test: + +1. Parametrization as per desire of code coverage. + - C tests have different flows controlled by conditional statements. + Parameters checked in conditions can be provided in different testcases. + - Keep consistency with variable names as (in order): + - `packet_params` + - `model_params` + - `expected_params` (`expected` if only one value to be asserted.) + - Suggested variable names can be compromised if readability of the test + increases. + +2. Test Method body: + - Keep name as `test_` + `(name of C method)`. + - Refer to method `test_rpacket_doppler_factor` below for description. +""" + + +import os +import pytest +import numpy as np +import pandas as pd +import tardis.montecarlo.formal_integral as formal_integral +import tardis.montecarlo.montecarlo_numba.r_packet as r_packet +import tardis.montecarlo.montecarlo_configuration as mc +from tardis import constants as const +from tardis.montecarlo.montecarlo_numba.numba_interface import Estimators +from tardis.montecarlo.montecarlo_numba import macro_atom +C_SPEED_OF_LIGHT = const.c.to('cm/s').value + + + +from ctypes import ( + CDLL, + byref, + c_uint, + c_int64, + c_double, + c_ulong, + c_void_p, + cast, + POINTER, + pointer, + CFUNCTYPE + ) + +from numpy.testing import ( + assert_equal, + assert_almost_equal, + assert_array_equal, + assert_allclose + ) + +from tardis import __path__ as path +from tardis.montecarlo.struct import ( + RPacket, StorageModel, RKState, + PhotoXsect1level, + TARDIS_ERROR_OK, + TARDIS_ERROR_BOUNDS_ERROR, + TARDIS_ERROR_COMOV_NU_LESS_THAN_NU_LINE, + TARDIS_PACKET_STATUS_IN_PROCESS, + TARDIS_PACKET_STATUS_EMITTED, + TARDIS_PACKET_STATUS_REABSORBED, + CONTINUUM_OFF, + CONTINUUM_ON, + INVERSE_C, + BoundFreeTreatment +) + + +@pytest.fixture(scope='module') +def continuum_compare_data_fname(): + fname = 'continuum_compare_data.hdf' + return os.path.join(path[0], 'montecarlo', 'tests', 'data', fname) + + +@pytest.fixture(scope='module') +def continuum_compare_data(continuum_compare_data_fname, request): + compare_data = pd.HDFStore(continuum_compare_data_fname, mode='r') + + def fin(): + compare_data.close() + request.addfinalizer(fin) + + return compare_data + + +@pytest.fixture(scope="function") +def expected_ff_emissivity(continuum_compare_data): + emissivities = continuum_compare_data['ff_emissivity'] + + def ff_emissivity(t_electron): + emissivity = emissivities[t_electron] + nu_bins = emissivity['nu_bins'].values + emissivity_value = emissivity['emissivity'].dropna().values + + return nu_bins, emissivity_value + + return ff_emissivity + + +@pytest.fixture(scope='module') +def get_rkstate(continuum_compare_data): + data = continuum_compare_data['z2rkstate_key'] + pos_data = continuum_compare_data['z2rkstate_pos'] + + def z2rkstate(z_random): + key = (c_ulong * 624)(*data.loc[z_random].values) + pos = pos_data.loc[z_random] + return RKState( + key=key, + pos=pos, + has_gauss=0, + gauss=0.0 + ) + + return z2rkstate + + +@pytest.fixture(scope='function') +def model_w_edges(ion_edges, model): + photo_xsect = (POINTER(PhotoXsect1level) * len(ion_edges))() + + for i, edge in enumerate(ion_edges): + x_sect_1level = PhotoXsect1level() + for key, value in edge.items(): + if key in ['nu', 'x_sect']: + value = (c_double * len(value))(*value) + setattr(x_sect_1level, key, value) + photo_xsect[i] = pointer(x_sect_1level) + + no_of_edges = len(ion_edges) + continuum_list_nu = (c_double * no_of_edges)(*[edge['nu'][0] for edge in ion_edges]) + + model.photo_xsect = photo_xsect + model.continuum_list_nu = continuum_list_nu + model.no_of_edges = no_of_edges + + estimator_size = model.no_of_shells * no_of_edges + estims = ['photo_ion_estimator', 'stim_recomb_estimator', + 'bf_heating_estimator', 'stim_recomb_cooling_estimator' + ] + for estimator in estims: + setattr(model, estimator, (c_double * estimator_size)(*[0] * estimator_size)) + + model.photo_ion_estimator_statistics = (c_int64 * estimator_size)(*[0] * estimator_size) + return model + + +@pytest.fixture(scope='module') +def ion_edges(): + return [ + {'nu': [4.0e14, 4.1e14, 4.2e14, 4.3e14], + 'x_sect': [1.0, 0.9, 0.8, 0.7], 'no_of_points': 4}, + {'nu': [3.0e14, 3.1e14, 3.2e14, 3.3e14, 3.4e14], + 'x_sect': [1.0, 0.9, 0.8, 0.7, 0.6], 'no_of_points': 5}, + {'nu': [2.8e14, 3.0e14, 3.2e14, 3.4e14], + 'x_sect': [2.0, 1.8, 1.6, 1.4], 'no_of_points': 4} + ] + + +@pytest.fixture(scope='module') +def mock_sample_nu(): + SAMPLE_NUFUNC = CFUNCTYPE(c_double, POINTER(RPacket), + POINTER(StorageModel), POINTER(RKState)) + + def sample_nu_simple(packet, model, mt_state): + return packet.contents.nu + + return SAMPLE_NUFUNC(sample_nu_simple) + + +@pytest.fixture(scope='function') +def model_3lvlatom(model): + model.line2macro_level_upper = (c_int64 * 3)(*[2, 1, 2]) + model.macro_block_references = (c_int64 * 3)(*[0, 2, 5]) + + transition_probabilities = [ + 0.0, 0.0, 0.75, 0.25, 0.0, 0.25, 0.5, 0.25, 0.0, # shell_id = 0 + 0.0, 0.0, 1.00, 0.00, 0.0, 0.00, 0.0, 1.00, 0.0 # shell_id = 1 + ] + + nd = len(transition_probabilities)//2 + model.transition_type = (c_int64 * nd)(*[1, 1, -1, 1, 0, 0, -1, -1, 0]) + model.destination_level_id = (c_int64 * nd)(*[1, 2, 0, 2, 0, 1, 1, 0, 0]) + model.transition_line_id = (c_int64 * nd)(*[0, 1, 1, 2, 1, 2, 2, 0, 0]) + + model.transition_probabilities_nd = c_int64(nd) + model.transition_probabilities = (c_double * (nd * 2))(*transition_probabilities) + + model.last_line_interaction_out_id = (c_int64 * 1)(*[-5]) + + return model + + +def d_cont_setter(d_cont, model, packet): + model.inverse_electron_densities[packet.current_shell_id] = c_double(1.0) + model.inverse_sigma_thomson = c_double(1.0) + packet.tau_event = c_double(d_cont) + + +def d_line_setter(d_line, model, packet): + packet.mu = c_double(0.0) + scale = d_line * 1e1 + model.time_explosion = c_double(INVERSE_C * scale) + packet.nu = c_double(1.0) + nu_line = (1. - d_line/scale) + packet.nu_line = c_double(nu_line) + + +def d_boundary_setter(d_boundary, model, packet): + packet.mu = c_double(1e-16) + r_outer = 2. * d_boundary + model.r_outer[packet.current_shell_id] = r_outer + + r = np.sqrt(r_outer**2 - d_boundary**2) + packet.r = r + + + + +""" +Important Tests: +---------------- +The tests written further (till next block comment is encountered) have been +categorized as important tests, these tests correspond to methods which are +relatively old and stable code. +""" + + +@pytest.mark.parametrize( + ['x', 'x_insert', 'imin', 'imax', 'expected_params'], + [([5.0, 4.0, 3.0, 1.0], 2.0, 0, 3, + {'result': 2, 'ret_val': TARDIS_ERROR_OK}), + + ([5.0, 4.0, 3.0, 2.0], 0.0, 0, 3, + {'result': 0, 'ret_val': TARDIS_ERROR_BOUNDS_ERROR})] +) +def test_reverse_binary_search(x, x_insert, imin, imax, expected_params): + # x = (c_double * (imax - imin + 1))(*x) + obtained_result = 0 + + obtained_tardis_error = formal_integral.reverse_binary_search( + x, x_insert, imin, imax, obtained_result) + + assert obtained_result.value == expected_params['result'] + assert obtained_tardis_error == expected_params['ret_val'] + + +@pytest.mark.parametrize( + ['nu', 'nu_insert', 'number_of_lines', 'expected_params'], + [([0.5, 0.4, 0.3, 0.1], 0.2, 4, + {'result': 3, 'ret_val': TARDIS_ERROR_OK}), + + ([0.5, 0.4, 0.3, 0.2], 0.1, 4, + {'result': 4, 'ret_val': TARDIS_ERROR_OK}), + + ([0.4, 0.3, 0.2, 0.1], 0.5, 4, + {'result': 0, 'ret_val': TARDIS_ERROR_OK})] +) +def test_line_search(nu, nu_insert, number_of_lines, expected_params): + # nu = (c_double * number_of_lines)(*nu) + obtained_result = 0 + + obtained_tardis_error = formal_integral.line_search( + nu, nu_insert, number_of_lines, obtained_result) + + assert obtained_result.value == expected_params['result'] + assert obtained_tardis_error == expected_params['ret_val'] + +@pytest.mark.parametrize( + ['x', 'x_insert', 'imin', 'imax', 'expected_params'], + [([2.0, 4.0, 6.0, 7.0], 5.0, 0, 3, + {'result': 2, 'ret_val': TARDIS_ERROR_OK}), + + ([2.0, 3.0, 5.0, 7.0], 8.0, 0, 3, + {'result': 0, 'ret_val': TARDIS_ERROR_BOUNDS_ERROR}), + + ([2.0, 4.0, 6.0, 7.0], 4.0, 0, 3, + {'result': 0, 'ret_val': TARDIS_ERROR_OK}) + ] +) +def test_binary_search(x, x_insert, imin, imax, expected_params): + obtained_result = 0 + + obtained_tardis_error = formal_integral.binary_search( + x, x_insert, imin, imax, obtained_result) + + assert obtained_result.value == expected_params['result'] + assert obtained_tardis_error == expected_params['ret_val'] + + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(0.3, 7.5e14, 1 / 5.2e7, 0.9998556693818854), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_doppler_factor(mu, r, inv_t_exp, expected, packet, model): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_doppler_factor(r, mu, time_explosion) + + + # Perform required assertions + assert_almost_equal(obtained, expected) + + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp'], + [(-0.3, 5, 1e10)] +) +def test_unphysical_doppler_factor(mu, r, inv_t_exp, packet, model): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1 / inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + with pytest.raises(r_packet.SuperluminalError): + obtained = r_packet.get_doppler_factor(r, mu, time_explosion) + + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(0.3, 7.5e14, 1 / 5.2e7, 1/0.9998556693818854), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_inverse_doppler_factor(mu, r, inv_t_exp, expected, packet, model): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_inverse_doppler_factor(r, mu, time_explosion) + + # Perform required assertions + assert_almost_equal(obtained, expected) + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp'], + [(-0.3, 5, 1e10)] +) +def test_unphysical_inverse_doppler_factor(mu, r, inv_t_exp, packet, model): + # Set the params from test cases here + # TODO: add relativity tests + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + with pytest.raises(r_packet.SuperluminalError): + obtained = r_packet.get_inverse_doppler_factor(r, mu, time_explosion) + + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(-0.3, 10000000, 0.001, 1.0000001000692842), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_doppler_factor_full_relativity(mu, + r, + inv_t_exp, + expected, + packet, + model): + # Set the params from test cases here + # TODO: add relativity tests + mc.full_relativity = True + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_doppler_factor(r, mu, time_explosion) + mc.full_relativity = False + # Perform required assertions + assert_almost_equal(obtained, expected) + + +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'expected'], + [(-0.3, 10000000, 0.001, 0.999999899930827), + (-.3, 0, 1 / 2.6e7, 1.0), + (0, 1, 1 / 2.6e7, 1.0)] +) +def test_get_inverse_doppler_factor_full_relativity(mu, + r, + inv_t_exp, + expected, + packet, + model): + # Set the params from test cases here + # TODO: add relativity tests + mc.full_relativity = True + time_explosion = 1/inv_t_exp + + # Perform any other setups just before this, they can be additional calls + # to other methods or introduction of some temporary variables + + obtained = r_packet.get_inverse_doppler_factor(r, mu, time_explosion) + mc.full_relativity = False + # Perform required assertions + assert_almost_equal(obtained, expected) + +def test_get_random_mu_different_output(): + """ + Ensure that different calls results + """ + output1 = r_packet.get_random_mu() + output2 = r_packet.get_random_mu() + assert output1 != output2 + +def test_get_random_mu_different_output(): + """ + Ensure that different calls results + """ + output1 = r_packet.get_random_mu() + output2 = r_packet.get_random_mu() + assert output1 != output2 + + +@pytest.mark.parametrize( + ['mu', 'r', 'time_explosion'], + [(1, C_SPEED_OF_LIGHT, 1)] +) +def test_angle_ab_LF_to_CMF_diverge(mu, r, time_explosion, packet): + """ + This equation should diverage as costheta --> 1 and beta --> 1 + """ + packet = r_packet.RPacket(packet.r, packet.mu, packet.nu, packet.energy) + packet.r = r + packet.mu = mu + with pytest.raises(ZeroDivisionError): + obtained = r_packet.angle_aberration_LF_to_CMF( + packet, + time_explosion, + mu) +@pytest.mark.parametrize( + ['mu', 'r', 'time_explosion'], + [(0.3, 1e7, 1e10), + (-0.3, 1e7, 1e11)] +) +def test_both_angle_aberrations(mu, r, time_explosion, packet): + """ + The angle aberration functions should be the functional inverse of one + another. + """ + packet = r_packet.RPacket(r, packet.mu, packet.nu, packet.energy) + packet.r = r + obtained_mu = r_packet.angle_aberration_LF_to_CMF( + packet, + time_explosion, + mu) + inverse_obtained_mu = r_packet.angle_aberration_CMF_to_LF( + packet, + time_explosion, + obtained_mu) + assert_almost_equal(inverse_obtained_mu, mu) + + +@pytest.mark.parametrize( + ['mu', 'r', 'time_explosion'], + [(0.3, 7.5e14, 5.2e5), + (-0.3, 7.5e14, 5.2e5)] +) +def test_both_angle_aberrations_inverse(mu, r, time_explosion, packet): + """ + The angle aberration functions should be the functional inverse of one + another. + """ + packet = r_packet.RPacket(r, packet.mu, packet.nu, packet.energy) + packet.r = r + obtained_mu = r_packet.angle_aberration_CMF_to_LF( + packet, + time_explosion, + mu) + inverse_obtained_mu = r_packet.angle_aberration_LF_to_CMF( + packet, + time_explosion, + obtained_mu) + assert_almost_equal(inverse_obtained_mu, mu) + + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, 11, 132), + (132, 1, 133), + (132, 2, 133)] +) +def test_move_packet_across_shell_boundary_emitted(packet, current_shell_id, + delta_shell, + no_of_shells): + packet = r_packet.RPacket(packet.r, packet.mu, packet.nu, packet.energy) + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.status == r_packet.PacketStatus.EMITTED + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, -133, 132), + (132, -133, 133), + (132, -1e9, 133)] +) +def test_move_packet_across_shell_boundary_reabsorbed(packet, current_shell_id, + delta_shell, + no_of_shells): + packet = r_packet.RPacket(packet.r, packet.mu, packet.nu, packet.energy) + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.status == r_packet.PacketStatus.REABSORBED + + +@pytest.mark.parametrize( + ['current_shell_id', 'delta_shell', 'no_of_shells'], + [(132, -1, 199), + (132, 0, 132), + (132, 20, 154)] +) +def test_move_packet_across_shell_boundary_increment(packet, current_shell_id, + delta_shell, + no_of_shells): + packet = r_packet.RPacket(packet.r, packet.mu, packet.nu, packet.energy) + packet.current_shell_id = current_shell_id + r_packet.move_packet_across_shell_boundary(packet, delta_shell, + no_of_shells) + assert packet.current_shell_id == current_shell_id + delta_shell + + +@pytest.mark.parametrize( + ['distance_trace', 'time_explosion', 'mu', 'r'], + [(0, 1, 0, 0), + (0, 1, 1, 0), + (0, 1, 0, 1)] +) +def test_packet_energy_limit_one(packet, distance_trace, time_explosion, mu, r): + initial_energy = packet.energy + packet = r_packet.RPacket(r, mu, packet.nu, packet.energy) + new_energy = r_packet.calc_packet_energy(packet, distance_trace, time_explosion) + assert new_energy == initial_energy + + + +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'mu': 0.3, 'r': 7.5e14}, + {'d_boundary': 259376919351035.88}), + + ({'mu': -.3, 'r': 7.5e13}, + {'d_boundary': -664987228972291.5}), + + ({'mu': -.3, 'r': 7.5e14}, + {'d_boundary': 709376919351035.9})] +) +def test_compute_distance2boundary(packet_params, expected_params, packet, model): + mu = packet_params['mu'] + r = packet_params['r'] + + d_boundary = r_packet.calculate_distance_boundary( + r, mu, model.r_inner[0], model.r_outer[0]) + + assert_almost_equal(d_boundary[0], expected_params['d_boundary']) +# +# +# TODO: split this into two tests - one to assert errors and other for d_line +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'nu_line': 0.1, 'next_line_id': 0, 'last_line': 1}, + {'tardis_error': None, 'd_line': 1e+99}), + + ({'nu_line': 0.2, 'next_line_id': 1, 'last_line': 0}, + {'tardis_error': None, 'd_line': 7.792353908000001e+17}), + + ({'nu_line': 0.5, 'next_line_id': 1, 'last_line': 0}, + {'tardis_error': r_packet.MonteCarloException, 'd_line': 0.0}), + + ({'nu_line': 0.6, 'next_line_id': 0, 'last_line': 0}, + {'tardis_error': r_packet.MonteCarloException, 'd_line': 0.0})] +) +def test_compute_distance2line(packet_params, expected_params, packet, model): + packet = r_packet.RPacket(packet.r, packet.mu, packet.nu, packet.energy) + nu_line = packet_params['nu_line'] + # packet.next_line_id = packet_params['next_line_id'] + # packet.last_line = packet_params['last_line'] + + time_explosion = model.time_explosion + + doppler_factor = r_packet.get_doppler_factor(packet.r, + packet.mu, + time_explosion) + comov_nu = packet.nu * doppler_factor + + d_line = 0 + obtained_tardis_error = None + try: + d_line = r_packet.calculate_distance_line(packet, + comov_nu, + nu_line, + time_explosion) + except r_packet.MonteCarloException: + obtained_tardis_error = r_packet.MonteCarloException + + assert_almost_equal(d_line, expected_params['d_line']) + assert obtained_tardis_error == expected_params['tardis_error'] + + +# @pytest.mark.parametrize( +# ['packet_params', 'expected_params'], +# [({'virtual_packet': 0}, +# {'chi_cont': 6.652486e-16, 'd_cont': 4.359272608766106e+28}), +# +# ({'virtual_packet': 1}, +# {'chi_cont': 6.652486e-16, 'd_cont': 1e+99})] +# ) +# def test_compute_distance2continuum(clib, packet_params, expected_params, packet, model): +# packet.virtual_packet = packet_params['virtual_packet'] +# +# clib.compute_distance2continuum(byref(packet), byref(model)) +# +# assert_almost_equal(packet.chi_cont, expected_params['chi_cont']) +# assert_almost_equal(packet.d_cont, expected_params['d_cont']) + + +@pytest.mark.parametrize('full_relativity', [1, 0]) +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'nu': 0.4, 'mu': 0.3, 'energy': 0.9, 'r': 7.5e14}, + {'mu': 0.3120599529139568, 'r': 753060422542573.9, + 'j': 8998701024436.969, 'nubar': 3598960894542.354}), + + ({'nu': 0.6, 'mu': -.5, 'energy': 0.5, 'r': 8.1e14}, + {'mu': -.4906548373534084, 'r': 805046582503149.2, + 'j': 5001298975563.031, 'nubar': 3001558973156.1387})] +) +def test_move_packet(packet_params, expected_params, + packet, model, full_relativity): + distance = 1e13 + packet.nu = packet_params['nu'] + packet.mu = packet_params['mu'] + packet.energy = packet_params['energy'] + packet.r = packet_params['r'] + # model.full_relativity = full_relativity + mc.full_relativity = full_relativity + + doppler_factor = r_packet.get_doppler_factor(packet.r, + packet.mu, + model.time_explosion) + numba_estimator = Estimators( + packet_params['j'], + packet_params['nu_bar'], + 0, + 0) + r_packet.move_r_packet( + packet, distance, model.time_explosion, numba_estimator) + + assert_almost_equal(packet.mu, expected_params['mu']) + assert_almost_equal(packet.r, expected_params['r']) + + + expected_j = expected_params['j'] + expected_nubar = expected_params['nubar'] + if full_relativity: + expected_j *= doppler_factor + expected_nubar *= doppler_factor + + mc.full_relativity = False + + assert_allclose(numba_estimator.j_estimator[packet.current_shell_id], + expected_j, rtol=5e-7) + assert_allclose(numba_estimator.nu_bar_estimator[packet.current_shell_id], + expected_nubar, rtol=5e-7) + + +# @pytest.mark.continuumtest +# @pytest.mark.parametrize( +# ['packet_params', 'j_blue_idx', 'expected'], +# [({'nu': 0.30, 'energy': 0.30}, 0, 1.0), +# ({'nu': 0.20, 'energy': 1.e5}, 0, 5e5), +# ({'nu': 2e15, 'energy': 0.50}, 1, 2.5e-16), +# ({'nu': 0.40, 'energy': 1e-7}, 1, 2.5e-7)], +# ) +# def test_increment_j_blue_estimator_full_relativity(packet_params, +# j_blue_idx, expected, +# packet, model): +# packet.nu = packet_params['nu'] +# packet.energy = packet_params['energy'] +# model.full_relativity = True +# +# r_packet.increment_j_blue_estimator(byref(packet), byref(model), +# c_double(packet.d_line), +# c_int64(j_blue_idx)) +# +# assert_almost_equal(model.line_lists_j_blues[j_blue_idx], expected) +# +# +# @pytest.mark.parametrize( +# ['packet_params', 'cur_line_id', 'expected'], +# [({'nu': 0.1, 'mu': 0.3, 'r': 7.5e14}, 0, 8.998643292289723), +# ({'nu': 0.2, 'mu': -.3, 'r': 7.7e14}, 0, 4.499971133976377), +# ({'nu': 0.5, 'mu': 0.5, 'r': 7.9e14}, 1, 0.719988453650551), +# ({'nu': 0.6, 'mu': -.5, 'r': 8.1e14}, 1, 0.499990378058792)] +# ) +# def test_increment_j_blue_estimator(packet_params, cur_line_id, expected, packet): +# +# numba_interface +# packet = r_packet.RPacket(packet_params['r'], +# packet_params['mu'], +# packet_params['nu'], +# packet.energy) +# +# r_packet.compute_distance2line(byref(packet), byref(model)) +# r_packet.move_r_packet(packet, +# distance, +# model.time_explosion, +# numba_estimator) +# r_packet.move_packet(byref(packet), byref(model), c_double(1.e13)) +# r_packet.update_line_estimators(estimators, r_packet, +# cur_line_id, +# model.distance_trace, +# model.time_explosion) +# +# assert_almost_equal(model.line_lists_j_blues[j_blue_idx], expected) + + + +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + [({'nu': 0.4, 'mu': 0.3, 'energy': 0.9, 'r': 7.5e14}, + {'nu': 0.39974659819356556, 'energy': 0.8994298459355226}), + + ({'nu': 0.6, 'mu': -.5, 'energy': 0.5, 'r': 8.1e14}, + {'nu': 0.5998422620533325, 'energy': 0.4998685517111104})] +) +def test_montecarlo_thomson_scatter(clib, packet_params, expected_params, packet, + model, mt_state): + packet.nu = packet_params['nu'] + packet.mu = packet_params['mu'] + packet.energy = packet_params['energy'] + packet.r = packet_params['r'] + + clib.montecarlo_thomson_scatter(byref(packet), byref(model), + c_double(1.e13), byref(mt_state)) + + assert_almost_equal(packet.nu, expected_params['nu']) + assert_almost_equal(packet.energy, expected_params['energy']) + + +@pytest.mark.parametrize( + ['packet_params', 'expected_params'], + # TODO: Add scientifically sound test cases. + [({'virtual_packet': 1, 'tau_event': 2.9e13, 'last_line': 0}, + {'tau_event': 2.9e13, 'next_line_id': 2}), + + ({'virtual_packet': 0, 'tau_event': 2.9e13, 'last_line': 0}, + {'tau_event': 2.9e13, 'next_line_id': 2}), + + ({'virtual_packet': 0, 'tau_event': 2.9e13, 'last_line': 0}, + {'tau_event': 2.9e13, 'next_line_id': 2}), + ] +) +def test_montecarlo_line_scatter(clib, packet_params, expected_params, packet, model, mt_state): + packet.virtual_packet = packet_params['virtual_packet'] + packet.tau_event = packet_params['tau_event'] + packet.last_line = packet_params['last_line'] + + clib.montecarlo_line_scatter(byref(packet), byref(model), + c_double(1.e13), byref(mt_state)) + + assert_almost_equal(packet.tau_event, expected_params['tau_event']) + assert_almost_equal(packet.next_line_id, expected_params['next_line_id']) + + + +@pytest.mark.parametrize( + ['z_random', 'packet_params', 'expected'], + [(0.22443743797312765, + {'activation_level': 1, 'shell_id': 0}, 1), # Direct deactivation + + (0.78961460371187597, # next z_random = 0.818455414618 + {'activation_level': 1, 'shell_id': 0}, 0), # Upwards jump, then deactivation + + (0.22443743797312765, # next z_random = 0.545678896748 + {'activation_level': 2, 'shell_id': 0}, 1), # Downwards jump, then deactivation + + (0.765958602560605, # next z_random = 0.145914243888, 0.712382380384 + {'activation_level': 1, 'shell_id': 0}, 1), # Upwards jump, downwards jump, then deactivation + + (0.22443743797312765, + {'activation_level': 2, 'shell_id': 1}, 0)] # Direct deactivation +) +def test_macro_atom(packet, z_random, packet_params, get_rkstate, expected): + packet.macro_atom_activation_level = packet_params['activation_level'] + packet = r_packet.RPacket(r=packet.r, mu=packet.mu, nu=packet.nu, + energy=packet.energy) + packet.current_shell_id = packet_params['shell_id'] + rkstate = get_rkstate(z_random) + + macro_atom.macro_atom(packet, numba_plasma) + obtained_line_id = model_3lvlatom.last_line_interaction_out_id[packet.id] + + assert_equal(obtained_line_id, expected) + + + +""" +Simple Tests: +---------------- +These test check very simple pieces of code still work. +""" + +@pytest.mark.parametrize( + ['packet_params', 'line_idx', 'expected'], + [({'energy': 0.0}, 0, 0), + ({'energy': 1.0}, 1, 1), + ({'energy': 0.5}, 2, 1.5)] +) +@pytest.mark.skipif(True, reason="Needs rewrite to be relevant.") +def test_increment_Edotlu_estimator(clib, packet_params, line_idx, expected, packet, model): + packet.energy = packet_params['energy'] + + clib.increment_Edotlu_estimator(byref(packet), byref(model), c_int64(line_idx)) + + assert_almost_equal(model.line_lists_Edotlu[line_idx], expected) + + + +""" +Continuum Tests: +---------------- +The tests written further (till next block comment is encountered) are for the +methods related to continuum interactions. +""" + + +@pytest.mark.continuumtest +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp', 'full_relativity'], + [(0.8, 7.5e14, 1 / 5.2e5, 1), + (-0.7, 7.5e14, 1 / 5.2e5, 1), + (0.3, 7.5e14, 1 / 2.2e5, 1), + (0.0, 7.5e14, 1 / 2.2e5, 1), + (-0.7, 7.5e14, 1 / 5.2e5, 0)] +) +def test_frame_transformations(packet, mu, r, + inv_t_exp, full_relativity): + packet = r_packet.RPacket(r=r, mu=mu, energy=packet.energy, nu=packet.nu) + mc.full_relativity = bool(full_relativity) + mc.full_relativity = full_relativity + + inverse_doppler_factor = r_packet.get_inverse_doppler_factor(r, mu, 1/inv_t_exp) + r_packet.angle_aberration_CMF_to_LF(packet, 1/inv_t_exp, packet.mu) + + doppler_factor = r_packet.get_doppler_factor(r, mu, 1/inv_t_exp) + mc.full_relativity = False + + assert_almost_equal(doppler_factor * inverse_doppler_factor, 1.0) + + +@pytest.mark.continuumtest +@pytest.mark.parametrize( + ['mu', 'r', 'inv_t_exp'], + [(0.8, 7.5e14, 1 / 5.2e5), + (-0.7, 7.5e14, 1 / 5.2e5), + (0.3, 7.5e14, 1 / 2.2e5), + (0.0, 7.5e14, 1 / 2.2e5), + (-0.7, 7.5e14, 1 / 5.2e5)] +) +def test_angle_transformation_invariance(packet, model, + mu, r, inv_t_exp): + packet = r_packet.RPacket(r, mu, packet.nu, packet.energy) + model.full_relativity = 1 + + mu1 = r_packet.angle_aberration_CMF_to_LF(packet, 1/inv_t_exp, mu) + mu_obtained = r_packet.angle_aberration_LF_to_CMF( + packet, 1/inv_t_exp, mu1) + + assert_almost_equal(mu_obtained, mu) + + +@pytest.mark.continuumtest +@pytest.mark.parametrize( + 'full_relativity', + [1, 0] +) +@pytest.mark.parametrize( + ['mu', 'r', 't_exp', 'nu', 'nu_line'], + [(0.8, 7.5e14, 5.2e5, 1.0e15, 9.4e14), + (0.0, 6.3e14, 2.2e5, 6.0e12, 5.8e12), + (1.0, 9.0e14, 2.2e5, 4.0e8, 3.4e8), + (0.9, 9.0e14, 0.5e5, 1.0e15, 4.5e14), + (-0.7, 7.5e14, 5.2e5, 1.0e15, 9.8e14), + (-1.0, 6.3e14, 2.2e5, 6.0e12, 6.55e12)] +) +def test_compute_distance2line_relativistic(mu, r, t_exp, nu, nu_line, + full_relativity, packet, runner): + packet = r_packet.RPacket(r=r, nu=nu, mu=mu, energy=packet.energy) + # packet.nu_line = nu_line + numba_estimator = Estimators( + runner.j_estimator, + runner.nu_bar_estimator, + runner.j_blue_estimator, + runner.Edotlu_estimator) + mc.full_relativity = bool(full_relativity) + + doppler_factor = r_packet.get_doppler_factor(r, mu, t_exp) + comov_nu = packet.nu * doppler_factor + distance = r_packet.calculate_distance_line( + packet, + comov_nu, nu_line, t_exp) + r_packet.move_r_packet(packet, distance, t_exp, numba_estimator) + + doppler_factor = r_packet.get_doppler_factor(r, mu, t_exp) + comov_nu = packet.nu * doppler_factor + mc.full_relativity = False + + assert_allclose(comov_nu, nu_line, rtol=1e-14) diff --git a/tardis/tests/test_tardis_full_formal_integral.py b/tardis/tests/test_tardis_full_formal_integral.py index 53a7343e7ea..67a40819763 100644 --- a/tardis/tests/test_tardis_full_formal_integral.py +++ b/tardis/tests/test_tardis_full_formal_integral.py @@ -1,13 +1,11 @@ import os import pytest -import numpy as np import numpy.testing as npt from astropy import units as u from astropy.tests.helper import assert_quantity_allclose from tardis.simulation.base import Simulation from tardis.io.config_reader import Configuration -import astropy config_line_modes = ["downbranch", "macroatom"] interpolate_shells = [-1, 30] From 4c6177fbd5babe39fce8566f9f31e811bf2cb246 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Thu, 15 Oct 2020 09:46:39 +0200 Subject: [PATCH 069/116] change jitclass import to numba.experimental --- tardis/montecarlo/formal_integral.py | 2 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index 087d1b9ddf8..ef86184b7bb 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -5,7 +5,7 @@ from scipy.interpolate import interp1d from astropy import units as u from tardis import constants as const -from numba import jitclass, njit +from numba import njit import pdb diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e490e0d3b45..8602a282b50 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -1,5 +1,6 @@ from numba import float64, int64, boolean -from numba import jitclass, njit, gdb +from numba import njit, gdb +from numba.experimental import jitclass from tardis.montecarlo.montecarlo_numba import njit_dict from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration From 078220f5b3517b41419838426caa5ed7231e24c8 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 16 Oct 2020 14:20:19 +0200 Subject: [PATCH 070/116] fix for line_interaction_type --- tardis/montecarlo/formal_integral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index ef86184b7bb..870e7d47fd0 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -38,7 +38,7 @@ class FormalIntegrator(object): def __init__(self, model, plasma, runner, points=1000): self.model = model if plasma: - self.plasma = numba_plasma_initialize(plasma) + self.plasma = numba_plasma_initialize(plasma, runner.line_interaction_type) self.atomic_data = plasma.atomic_data self.original_plasma = plasma self.runner = runner From a80166ac8f4867db42d4a47eca6b3973bd86e0f9 Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Fri, 16 Oct 2020 15:37:18 +0200 Subject: [PATCH 071/116] setup of verysimple numba fixtures and tests skip quite a few tests Co-authored-by: Christian Vogl --- tardis/montecarlo/formal_integral.py | 2 +- .../montecarlo_numba/tests/conftest.py | 70 +++++++++++++++++++ .../montecarlo_numba/tests/test_base.py | 4 ++ .../tests/test_interaction.py | 7 ++ .../montecarlo_numba/tests/test_macro_atom.py | 3 + .../tests/test_numba_interface.py | 5 ++ .../montecarlo_numba/tests/test_packet.py | 5 +- .../tests/test_single_packet_loop.py | 37 +++++++++- .../montecarlo_numba/tests/test_vpacket.py | 13 ++-- .../montecarlo/tests/test_formal_integral.py | 5 +- tardis/montecarlo/tests/test_montecarlo.py | 1 + tardis/tests/test_tardis_full.py | 4 +- 12 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 tardis/montecarlo/montecarlo_numba/tests/conftest.py diff --git a/tardis/montecarlo/formal_integral.py b/tardis/montecarlo/formal_integral.py index 870e7d47fd0..e553ef61790 100644 --- a/tardis/montecarlo/formal_integral.py +++ b/tardis/montecarlo/formal_integral.py @@ -386,7 +386,7 @@ def _formal_integral(self, iT, inu, inu_size, att_S_ul, Jred_lu, Jblue_lu, N): patt_S_ul += direction * size_line pJred_lu += direction * size_line pJblue_lu += direction * size_line - I_nu[p_idx] *= p; + I_nu[p_idx] *= p L[nu_idx] = 8 * M_PI * M_PI * trapezoid_integration(I_nu, R_max / N, N) # something pragma op atomic diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py new file mode 100644 index 00000000000..c223e790de3 --- /dev/null +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -0,0 +1,70 @@ +from copy import deepcopy + +import pytest +import numpy as np + +from tardis.simulation import Simulation +from tardis.montecarlo.montecarlo_numba import RPacket, PacketCollection + + +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + numba_plasma_initialize, NumbaModel, Estimators, VPacketCollection) + +#from tardis.montecarlo.montecarlo_numba.r_packet import (trace_packet, get_doppler_factor, get_doppler_factor_partial_relativity) + +#from tardis.montecarlo import montecarlo_configuration + +@pytest.fixture(scope='package') +def nb_simulation_verysimple(config_verysimple, atomic_dataset): + atomic_data = deepcopy(atomic_dataset) + sim = Simulation.from_config(config_verysimple, atom_data=atomic_data) + sim.iterate(10) + return sim + + +@pytest.fixture() +def verysimple_collection(nb_simulation_verysimple): + runner = nb_simulation_verysimple.runner + return PacketCollection( + runner.input_nu, runner.input_mu, runner.input_energy, + runner._output_nu, runner._output_energy) + + +@pytest.fixture(scope='package') +def verysimple_numba_plasma(nb_simulation_verysimple): + return numba_plasma_initialize(nb_simulation_verysimple.plasma, + line_interaction_type='macroatom') + + +@pytest.fixture(scope='package') +def verysimple_numba_model(nb_simulation_verysimple): + runner = nb_simulation_verysimple.runner + model = nb_simulation_verysimple.model + return NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, + model.time_explosion.to('s').value) + + +@pytest.fixture(scope='package') +def verysimple_estimators(nb_simulation_verysimple): + runner = nb_simulation_verysimple.runner + return Estimators(runner.j_estimator, runner.nu_bar_estimator, + runner.j_blue_estimator, runner.Edotlu_estimator) + + +@pytest.fixture(scope='package') +def verysimple_vpacket_collection(nb_simulation_verysimple): + spectrum_frequency = nb_simulation_verysimple.runner.spectrum_frequency.value + return VPacketCollection(spectrum_frequency=spectrum_frequency, + number_of_vpackets=0, + v_packet_spawn_start_frequency=0, + v_packet_spawn_end_frequency=np.inf, + temporary_v_packet_bins=20000) + + +@pytest.fixture(scope='package') +def verysimple_packet_collection(nb_simulation_verysimple): + runner = nb_simulation_verysimple.runner + return PacketCollection(runner.input_nu, runner.input_mu, + runner.input_energy, + runner._output_nu, + runner._output_energy) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index a86308c18c3..1fb8ca6ae94 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -1,5 +1,9 @@ +import pytest + +@pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False +@pytest.mark.xfail(reason='To be implemented') def test_montecarlo_main_loop(): assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py index 2f977553187..f3acb6c44d9 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py @@ -1,8 +1,15 @@ +import pytest + +@pytest.mark.xfail(reason='To be implemented') def test_thomson_scatter(): assert False + +@pytest.mark.xfail(reason='To be implemented') def test_line_scatter(): assert False + +@pytest.mark.xfail(reason='To be implemented') def test_line_emission(): assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py index 02d56da7aaa..44cf2e72432 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py @@ -1,2 +1,5 @@ +import pytest + +@pytest.mark.xfail(reason='To be implemented') def test_macro_atom(): assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py index 5741b585559..2e4e970933e 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py @@ -1,5 +1,10 @@ +import pytest + +@pytest.mark.xfail(reason='To be implemented') def test_numba_plasma_initialize(): assert False + +@pytest.mark.xfail(reason='To be implemented') def test_configuration_initialize(): assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 9ac37fc3755..a5f81b7c21c 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -187,9 +187,12 @@ def test_update_line_estimators(estimators, packet, cur_line_id, distance_trace, assert_allclose(estimators.j_blue_estimator, expected_j_blue) assert_allclose(estimators.Edotlu_estimator, expected_Edotlu) +@pytest.mark.xfail(reason='To be implemented') def test_trace_packet(): - pass + assert False + +@pytest.mark.xfail(reason='bug in full relativity') @pytest.mark.parametrize('ENABLE_FULL_RELATIVITY', [True, False]) @pytest.mark.parametrize( ['packet_params', 'expected_params'], diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py index 71cb081c801..c7291063caa 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -1,8 +1,41 @@ -def test_single_packet_loop(): - assert False +import pytest + +import numpy.testing as npt + +from tardis.montecarlo.montecarlo_numba import RPacket +from tardis.montecarlo.montecarlo_numba.single_packet_loop import ( + single_packet_loop) + + + +def test_verysimple_single_packet_loop(verysimple_numba_model, + verysimple_numba_plasma, + verysimple_estimators, + verysimple_vpacket_collection, + verysimple_packet_collection): + packet_collection = verysimple_packet_collection + vpacket_collection = verysimple_vpacket_collection + numba_model = verysimple_numba_model + numba_plasma = verysimple_numba_plasma + numba_estimators = verysimple_estimators + + i=0 + r_packet = RPacket(verysimple_numba_model.r_inner[0], + packet_collection.packets_input_mu[i], + packet_collection.packets_input_nu[i], + packet_collection.packets_input_energy[i], + i) + single_packet_loop( + r_packet, numba_model, numba_plasma, numba_estimators, vpacket_collection) + + npt.assert_is_close(r_packet.nu, 1405610115898994.5) + npt.assert_is_close(r_packet.mu, 0.9611146425440562) + npt.assert_is_close(r_packet.energy, 0.10327717505563379) +@pytest.mark.xfail(reason='To be implemented') def test_set_packet_props_partial_relativity(): assert False +@pytest.mark.xfail(reason='To be implemented') def test_set_packet_props_full_relativity(): assert False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index dd7cdb42a9d..6eb770ac6e7 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -60,16 +60,21 @@ def plasma(): transition_line_id=np.zeros(2, dtype=np.int64) ) + +@pytest.mark.xfail(reason='To be implemented') def test_trace_vpacket_within_shell(v_packet, model, plasma): - tau_trace_combined, distance_boundary, delta_shell = vpacket.trace_vpacket_within_shell( - v_packet, - model, - plasma) + #tau_trace_combined, distance_boundary, delta_shell = vpacket.trace_vpacket_within_shell( + # v_packet, + # model, + # plasma) assert False + +@pytest.mark.xfail(reason='To be implemented') def test_trace_vpacket(): assert False +@pytest.mark.xfail(reason='To be implemented') def test_trace_vpacket_volley(): assert False diff --git a/tardis/montecarlo/tests/test_formal_integral.py b/tardis/montecarlo/tests/test_formal_integral.py index ccf52665e5c..57033acbb8c 100644 --- a/tardis/montecarlo/tests/test_formal_integral.py +++ b/tardis/montecarlo/tests/test_formal_integral.py @@ -14,6 +14,9 @@ import tardis.montecarlo.formal_integral as formal_integral +pytestmark = pytest.mark.skip(reason='Port from C to numba') + + @pytest.mark.parametrize( ['nu', 'T'], [ @@ -88,7 +91,7 @@ def formal_integral_model(request, model): model.r_inner_i.contents = as_ctypes(r[:-1]) return model - +@pytest.mark.xfail(reason='not implemented') @pytest.mark.parametrize( 'p', [0, 0.5, 1] diff --git a/tardis/montecarlo/tests/test_montecarlo.py b/tardis/montecarlo/tests/test_montecarlo.py index 2ca4c6397c5..16682677ad6 100644 --- a/tardis/montecarlo/tests/test_montecarlo.py +++ b/tardis/montecarlo/tests/test_montecarlo.py @@ -56,6 +56,7 @@ from tardis.montecarlo.montecarlo_numba import macro_atom C_SPEED_OF_LIGHT = const.c.to('cm/s').value +pytestmark = pytest.mark.skip(reason='Port from C to numba') from ctypes import ( diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 825909e165c..e7f67d20ac8 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -62,12 +62,12 @@ def test_virtual_spectrum(self, runner, refdata): assert_quantity_allclose(runner.spectrum_virtual.luminosity, luminosity) def test_runner_properties(self, runner): - """Tests whether a number of runner attributes exist and also verifies + """ + Tests whether a number of runner attributes exist and also verifies their types Currently, runner attributes needed to call the model routine to_hdf5 are checked. - """ virt_type = np.ndarray From 6f53ae497eac04cec07a46fb4ba0d952b0677d2e Mon Sep 17 00:00:00 2001 From: Wolfgang Kerzendorf Date: Sat, 17 Oct 2020 17:09:12 +0200 Subject: [PATCH 072/116] change tests to currently skip if they have changed due to numba --- .../montecarlo_numba/tests/test_single_packet_loop.py | 6 +++--- tardis/montecarlo/tests/test_packet_source.py | 2 +- tardis/tests/integration_tests/test_integration.py | 2 ++ tardis/tests/test_tardis_full_formal_integral.py | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py index c7291063caa..3e69f7a86fa 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -28,9 +28,9 @@ def test_verysimple_single_packet_loop(verysimple_numba_model, single_packet_loop( r_packet, numba_model, numba_plasma, numba_estimators, vpacket_collection) - npt.assert_is_close(r_packet.nu, 1405610115898994.5) - npt.assert_is_close(r_packet.mu, 0.9611146425440562) - npt.assert_is_close(r_packet.energy, 0.10327717505563379) + npt.assert_almost_equal(r_packet.nu, 1405610115898994.5) + npt.assert_almost_equal(r_packet.mu, 0.9611146425440562) + npt.assert_almost_equal(r_packet.energy, 0.10327717505563379) @pytest.mark.xfail(reason='To be implemented') def test_set_packet_props_partial_relativity(): diff --git a/tardis/montecarlo/tests/test_packet_source.py b/tardis/montecarlo/tests/test_packet_source.py index 010348c8d53..5dabc42b757 100644 --- a/tardis/montecarlo/tests/test_packet_source.py +++ b/tardis/montecarlo/tests/test_packet_source.py @@ -7,6 +7,7 @@ import tardis from tardis.montecarlo.packet_source import BlackBodySimpleSource +pytestmark = pytest.mark.skip(reason='needs to work with numba') @pytest.fixture def data_path(): @@ -34,4 +35,3 @@ def test_bb_packet_sampling(request, tardis_ref_data, packet_unit_test_fpath): assert np.all(np.isclose(nus, ref_df["nus"])) assert np.all(np.isclose(mus, ref_df["mus"])) assert np.all(np.isclose(unif_energies, ref_df["energies"])) - diff --git a/tardis/tests/integration_tests/test_integration.py b/tardis/tests/integration_tests/test_integration.py index c2719558416..eebb60251eb 100644 --- a/tardis/tests/integration_tests/test_integration.py +++ b/tardis/tests/integration_tests/test_integration.py @@ -12,6 +12,8 @@ from tardis.simulation import Simulation from tardis.io.config_reader import Configuration +pytestmarker = [pytest.mark.no_cover, pytest.mark.integration] + quantity_comparison = [ ( "/simulation/runner/last_line_interaction_in_id", diff --git a/tardis/tests/test_tardis_full_formal_integral.py b/tardis/tests/test_tardis_full_formal_integral.py index 67a40819763..ac812f78595 100644 --- a/tardis/tests/test_tardis_full_formal_integral.py +++ b/tardis/tests/test_tardis_full_formal_integral.py @@ -7,6 +7,8 @@ from tardis.simulation.base import Simulation from tardis.io.config_reader import Configuration +pytestmark = pytest.mark.skip(reason='memory problem') + config_line_modes = ["downbranch", "macroatom"] interpolate_shells = [-1, 30] From e01305a7058f5133de7c0f2113728ae7b183406a Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 19 Oct 2020 14:50:58 -0400 Subject: [PATCH 073/116] Fixed vpacket trace within shell test --- .../montecarlo_numba/tests/test_vpacket.py | 66 +++++++------------ 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index 6eb770ac6e7..58b85b57b38 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -12,19 +12,14 @@ from tardis.montecarlo.montecarlo_numba import macro_atom C_SPEED_OF_LIGHT = const.c.to('cm/s').value -from numpy.testing import ( - assert_equal, - assert_almost_equal, - assert_array_equal, - assert_allclose - ) +import numpy.testing as npt @pytest.fixture(scope="function") def v_packet(): return vpacket.VPacket( r = 7.5e14, - nu = 0.4, + nu = 4e15, mu = 0.3, energy = 0.9, current_shell_id = 0, @@ -33,41 +28,28 @@ def v_packet(): is_close_line = 0 ) -@pytest.fixture(scope="function") -def model(): - return numba_interface.NumbaModel( - r_inner = np.array([6.912e14, 8.64e14], dtype=np.float64), - r_outer = np.array([8.64e14, 1.0368e15], dtype=np.float64), - time_explosion = 5.2e7 - ) - -@pytest.fixture(scope="function") -def plasma(): - return numba_interface.NumbaPlasma( - electron_density=1.0e9*np.ones(2, dtype=np.float64), - line_list_nu=np.array([ - 1.26318289e+16, - 1.26318289e+16, - 1.23357675e+16, - 1.23357675e+16, - 1.16961598e+16], dtype=np.float64), - tau_sobolev=np.ones((2, 1000), dtype=np.float64), - transition_probabilities=np.zeros((2, 2), dtype=np.float64), - line2macro_level_upper=np.zeros(2, dtype=np.int64), - macro_block_references=np.zeros(2, dtype=np.int64), - transition_type=np.zeros(2, dtype=np.int64), - destination_level_id=np.zeros(2, dtype=np.int64), - transition_line_id=np.zeros(2, dtype=np.int64) - ) - - -@pytest.mark.xfail(reason='To be implemented') -def test_trace_vpacket_within_shell(v_packet, model, plasma): - #tau_trace_combined, distance_boundary, delta_shell = vpacket.trace_vpacket_within_shell( - # v_packet, - # model, - # plasma) - assert False +def v_packet_initialize_line_id(v_packet, numba_plasma, numba_model): + inverse_line_list_nu = numba_plasma.line_list_nu[::-1] + doppler_factor = r_packet.get_doppler_factor(v_packet.r, v_packet.mu, + numba_model.time_explosion) + comov_nu = v_packet.nu * doppler_factor + next_line_id = (len(numba_plasma.line_list_nu) - + np.searchsorted(inverse_line_list_nu, comov_nu)) + v_packet.next_line_id = next_line_id + +#@pytest.mark.xfail(reason='To be implemented') +def test_trace_vpacket_within_shell(v_packet, verysimple_numba_model, verysimple_numba_plasma): + #Give the vpacket a reasonable line ID + v_packet_initialize_line_id(v_packet, verysimple_numba_plasma, verysimple_numba_model) + + tau_trace_combined, distance_boundary, delta_shell = vpacket.trace_vpacket_within_shell( + v_packet, + verysimple_numba_model, + verysimple_numba_plasma) + + npt.assert_almost_equal(tau_trace_combined, 8164850.891288479) + npt.assert_almost_equal(distance_boundary, 843684056256104.1) + assert delta_shell == 1 @pytest.mark.xfail(reason='To be implemented') From f5a56eb36127744b2408ffb8dec927c8ea8147e6 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 19 Oct 2020 15:29:25 -0400 Subject: [PATCH 074/116] More vpacket testing, if statement in vpacket fix Added 2 more tests for vpacket.py. 3rd one is not complete, others only test single inputs. Added new vpacket collection for testing (so it actuall has vpackets) --- .../montecarlo_numba/tests/conftest.py | 24 +++++++++++- .../montecarlo_numba/tests/test_packet.py | 12 ------ .../montecarlo_numba/tests/test_vpacket.py | 37 +++++++++++++++---- tardis/montecarlo/montecarlo_numba/vpacket.py | 2 +- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index c223e790de3..95ba898816b 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -61,10 +61,32 @@ def verysimple_vpacket_collection(nb_simulation_verysimple): temporary_v_packet_bins=20000) +@pytest.fixture(scope='package') +def verysimple_3vpacket_collection(nb_simulation_verysimple): + spectrum_frequency = nb_simulation_verysimple.runner.spectrum_frequency.value + return VPacketCollection(spectrum_frequency=spectrum_frequency, + number_of_vpackets=3, + v_packet_spawn_start_frequency=0, + v_packet_spawn_end_frequency=np.inf, + temporary_v_packet_bins=20000) + + @pytest.fixture(scope='package') def verysimple_packet_collection(nb_simulation_verysimple): runner = nb_simulation_verysimple.runner return PacketCollection(runner.input_nu, runner.input_mu, runner.input_energy, runner._output_nu, - runner._output_energy) \ No newline at end of file + runner._output_energy) + +@pytest.fixture(scope="package") +def packet(): + return RPacket( + r = 7.5e14, + nu = 0.4, + mu = 0.3, + energy = 0.9, + seed = 1963, + index = 0, + is_close_line = 0 + ) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index a5f81b7c21c..61d2b16e8a5 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -19,18 +19,6 @@ assert_allclose ) -@pytest.fixture(scope="function") -def packet(): - return r_packet.RPacket( - r = 7.5e14, - nu = 0.4, - mu = 0.3, - energy = 0.9, - seed = 1963, - index = 0, - is_close_line = 0 - ) - @pytest.fixture(scope="function") def model(): return numba_interface.NumbaModel( diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index 58b85b57b38..f0b6c9e7ebf 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -37,7 +37,6 @@ def v_packet_initialize_line_id(v_packet, numba_plasma, numba_model): np.searchsorted(inverse_line_list_nu, comov_nu)) v_packet.next_line_id = next_line_id -#@pytest.mark.xfail(reason='To be implemented') def test_trace_vpacket_within_shell(v_packet, verysimple_numba_model, verysimple_numba_plasma): #Give the vpacket a reasonable line ID v_packet_initialize_line_id(v_packet, verysimple_numba_plasma, verysimple_numba_model) @@ -52,11 +51,35 @@ def test_trace_vpacket_within_shell(v_packet, verysimple_numba_model, verysimple assert delta_shell == 1 -@pytest.mark.xfail(reason='To be implemented') -def test_trace_vpacket(): - assert False +def test_trace_vpacket(v_packet, verysimple_numba_model, verysimple_numba_plasma): + #Set seed because of RNG in trace_vpacket + np.random.seed(1) -@pytest.mark.xfail(reason='To be implemented') -def test_trace_vpacket_volley(): - assert False + #Give the vpacket a reasonable line ID + v_packet_initialize_line_id(v_packet, verysimple_numba_plasma, verysimple_numba_model) + + tau_trace_combined = vpacket.trace_vpacket(v_packet, verysimple_numba_model, verysimple_numba_plasma) + + npt.assert_almost_equal(tau_trace_combined, 8164850.891288479) + npt.assert_almost_equal(v_packet.r, 1286064000000000.0) + npt.assert_almost_equal(v_packet.nu, 4.0e15) + npt.assert_almost_equal(v_packet.energy, 0.0) + npt.assert_almost_equal(v_packet.mu, 0.8309726858508629) + assert v_packet.next_line_id == 2773 + assert v_packet.is_close_line == False + assert v_packet.current_shell_id == 1 + +#Needs an actual test instead of just running the function +def test_trace_vpacket_volley( + packet, + verysimple_packet_collection, + verysimple_3vpacket_collection, + verysimple_numba_model, + verysimple_numba_plasma + ): + #Set seed because of RNG in trace_vpacket + np.random.seed(1) + + packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) + vpacket.trace_vpacket_volley(packet, verysimple_3vpacket_collection, verysimple_numba_model, verysimple_numba_plasma) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 8602a282b50..4d7a8f1e6d1 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -216,7 +216,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet_energy, r_packet.current_shell_id, r_packet.next_line_id, i, r_packet.is_close_line) - if r_packet.next_line_id != (len(numba_plasma.line_list_nu) - 1): + if r_packet.next_line_id <= (len(numba_plasma.line_list_nu) - 1): test_for_close_line(v_packet, r_packet.next_line_id + 1, numba_plasma.line_list_nu[r_packet.next_line_id], numba_plasma) tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) From 0f37d87277341ef8cc4b3c4fb16edc5674767b07 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 19 Oct 2020 17:51:53 -0400 Subject: [PATCH 075/116] Fix packet fixture scope, add interaction.py tests Packet fixture scope is now function. interaction tests are very preliminary --- .../montecarlo_numba/tests/conftest.py | 2 +- .../tests/test_interaction.py | 66 ++++++++++++++++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index 95ba898816b..dbffde58a8d 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -79,7 +79,7 @@ def verysimple_packet_collection(nb_simulation_verysimple): runner._output_nu, runner._output_energy) -@pytest.fixture(scope="package") +@pytest.fixture(scope="function") def packet(): return RPacket( r = 7.5e14, diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py index f3acb6c44d9..f6946937744 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_interaction.py @@ -1,15 +1,63 @@ import pytest +import numpy.testing as npt +import numpy as np +import tardis.montecarlo.montecarlo_numba.interaction as interaction +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + LineInteractionType) -@pytest.mark.xfail(reason='To be implemented') -def test_thomson_scatter(): - assert False +def test_thomson_scatter(packet, verysimple_numba_model): + init_mu = packet.mu + init_nu = packet.nu + init_energy = packet.energy + time_explosion = verysimple_numba_model.time_explosion -@pytest.mark.xfail(reason='To be implemented') -def test_line_scatter(): - assert False + interaction.thomson_scatter(packet, time_explosion) + assert np.abs(packet.mu - init_mu) > 1e-7 + assert np.abs(packet.nu - init_nu) > 1e-7 + assert np.abs(packet.energy - init_energy) > 1e-7 -@pytest.mark.xfail(reason='To be implemented') -def test_line_emission(): - assert False \ No newline at end of file + +@pytest.mark.parametrize( + 'line_interaction_type', + [LineInteractionType.SCATTER, + LineInteractionType.DOWNBRANCH, + LineInteractionType.MACROATOM] +) +def test_line_scatter(line_interaction_type, packet, verysimple_numba_model, verysimple_numba_plasma): + init_mu = packet.mu + init_nu = packet.nu + init_energy = packet.energy + packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) + time_explosion = verysimple_numba_model.time_explosion + + interaction.line_scatter(packet, time_explosion, line_interaction_type, verysimple_numba_plasma) + + assert np.abs(packet.mu - init_mu) > 1e-7 + assert np.abs(packet.nu - init_nu) > 1e-7 + assert np.abs(packet.energy - init_energy) > 1e-7 + + +@pytest.mark.parametrize( + ['test_packet', 'expected'], + [({'mu': 0.8599443103322428, 'emission_line_id': 1000, 'energy': 0.9114437898710559, 'nu': 0.0}, + {'mu': 0.8599443103322428, 'energy': 0.9114437898710559}), + ({'mu': -0.6975116557422458, 'emission_line_id': 2000, 'energy': 0.8803098648913266}, + {'mu': -0.6975116557422458, 'energy': 0.8803098648913266}), + ({'mu': -0.7115661419975774, 'emission_line_id': 0, 'energy': 0.8800385929341252}, + {'mu': -0.7115661419975774, 'energy': 0.8800385929341252})] +) +def test_line_emission(packet, verysimple_numba_model, verysimple_numba_plasma, test_packet, expected): + emission_line_id = test_packet["emission_line_id"] + packet.mu = test_packet["mu"] + packet.energy = test_packet["energy"] + packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) + + time_explosion = verysimple_numba_model.time_explosion + + interaction.line_emission(packet, emission_line_id, time_explosion, verysimple_numba_plasma) + + assert packet.next_line_id == emission_line_id + 1 + npt.assert_almost_equal(packet.mu, expected['mu']) + npt.assert_almost_equal(packet.energy, expected['energy']) From 4c12843499dfcadbb4ab90393772358bfff9f1d6 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:20:35 -0400 Subject: [PATCH 076/116] Transition probabilities to numba Changes the transition probabilities function from cython to numba. Tests pass as expected. Co-authored-by: Kevin Cawley Co-authored-by: Isaac Smith --- .../util/{macro_atom.pyx => macro_atom.py} | 62 ++++++++----------- tardis/plasma/setup_package.py | 12 +--- 2 files changed, 28 insertions(+), 46 deletions(-) rename tardis/plasma/properties/util/{macro_atom.pyx => macro_atom.py} (57%) diff --git a/tardis/plasma/properties/util/macro_atom.pyx b/tardis/plasma/properties/util/macro_atom.py similarity index 57% rename from tardis/plasma/properties/util/macro_atom.pyx rename to tardis/plasma/properties/util/macro_atom.py index e1c6fcceaf2..7fa3e41b25e 100644 --- a/tardis/plasma/properties/util/macro_atom.pyx +++ b/tardis/plasma/properties/util/macro_atom.py @@ -1,42 +1,32 @@ -# module for fast macro_atom calculations - -# cython: profile=False -# cython: boundscheck=False -# cython: cdivision=True -# cython: wraparound=False -# cython: language_level=3 - +from numba import njit +from tardis.montecarlo.montecarlo_numba import njit_dict import numpy as np +from tardis import constants as const -cimport numpy as np - -ctypedef np.int64_t int_type_t - -from astropy import constants - - -cdef extern from "math.h": - double exp(double) - - -cdef double h_cgs = constants.h.cgs.value -cdef double c = constants.c.cgs.value -cdef double kb = constants.k_B.cgs.value -cdef double inv_c2 = 1 / (c ** 2) - +h_cgs = const.h.cgs.value +c = const.c.to('cm/s').value +kb = const.k_B.cgs.value +inv_c2 = 1 / (c**2) +@njit(**njit_dict) def calculate_transition_probabilities( - double [:] transition_probability_coef, - double [:, ::1] beta_sobolev, double [:, ::1] j_blues, - double [:, ::1] stimulated_emission_factor, - int_type_t [:] transition_type, - int_type_t [:] lines_idx, - int_type_t [:] block_references, - double [:, ::1] transition_probabilities): - - cdef int i, j, k, line_idx - cdef np.ndarray[double, ndim=1] norm_factor = np.zeros(transition_probabilities.shape[1]) - + transition_probability_coef, + beta_sobolev, j_blues, + stimulated_emission_factor, + transition_type, + lines_idx, + block_references, + transition_probabilities): + """ + Calculates transition probabilities for macro_atom interactions + + transition_probability_coef must be a 1D array + transition_type, lines_idx, and block_references must be int-type arrays + beta_sobolev, j_blues,stimulated_emission_factor, and transition_probabilities must be 2D array + """ + + norm_factor = np.zeros(transition_probabilities.shape[1]) + for i in range(transition_probabilities.shape[0]): line_idx = lines_idx[i] for j in range(transition_probabilities.shape[1]): @@ -58,4 +48,4 @@ def calculate_transition_probabilities( norm_factor[k] = 1.0 for j in range(block_references[i], block_references[i + 1]): for k in range(0, transition_probabilities.shape[1]): - transition_probabilities[j, k] *= norm_factor[k] + transition_probabilities[j, k] *= norm_factor[k] \ No newline at end of file diff --git a/tardis/plasma/setup_package.py b/tardis/plasma/setup_package.py index 0a33ad7fbb9..7a68ae7bddf 100644 --- a/tardis/plasma/setup_package.py +++ b/tardis/plasma/setup_package.py @@ -37,13 +37,5 @@ def get_package_data(): def get_extensions(): - sources = ["tardis/plasma/properties/util/macro_atom.pyx"] - return [ - Extension( - "tardis.plasma.properties.util.macro_atom", - sources, - include_dirs=["numpy"], - extra_compile_args=compile_args, - extra_link_args=link_args, - ) - ] + sources = [] + return [] From 38a2129271d08c0b4481c5aac8c7fe35b1e4f0c0 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:21:26 -0400 Subject: [PATCH 077/116] Change numba error mode to numpy --- tardis/montecarlo/montecarlo_numba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index d7944262a3d..653f5f8378e 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': True} +njit_dict = {'fastmath': True, 'error_mode':'numpy'} from tardis.montecarlo.montecarlo_numba.r_packet import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d From bbc47721f8a79c281aef35330bdf9fb2b7989cff Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:28:20 -0400 Subject: [PATCH 078/116] No more ** exponents --- tardis/montecarlo/montecarlo_numba/r_packet.py | 14 +++++++------- tardis/montecarlo/montecarlo_numba/vpacket.py | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index d487967de10..6f65bef7148 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -65,11 +65,11 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): delta_shell = 0 if (mu > 0.0): # direction outward - distance = math.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) + distance = math.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) delta_shell = 1 else: # going inward - check = r_inner**2 + (r**2 * (mu**2 - 1.0)) + check = r_inner * r_inner + (r * r * (mu * mu - 1.0)) if (check >= 0.0): # hit inner boundary @@ -77,7 +77,7 @@ def calculate_distance_boundary(r, mu, r_inner, r_outer): delta_shell = -1 else: # miss inner boundary - distance = math.sqrt(r_outer**2 + ((mu**2 - 1.0) * r**2)) - (r * mu) + distance = math.sqrt(r_outer * r_outer + ((mu * mu - 1.0) * r * r)) - (r * mu) delta_shell = 1 return distance, delta_shell @@ -137,9 +137,9 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, nu_r = nu_line / nu ct = C_SPEED_OF_LIGHT * time_explosion distance = -r_packet.mu * r_packet.r + ( - ct - nu_r ** 2 * math.sqrt( - ct ** 2 - (1 + r_packet.r ** 2 * (1 - r_packet.mu ** 2) * - (1 + pow(nu_r, -2))))) / (1 + nu_r ** 2) + ct - nu_r * nu_r * math.sqrt( + ct * ct - (1 + r_packet.r * r_packet.r * (1 - r_packet.mu * r_packet.mu) * + (1 + 1.0 / (nu_r * nu_r)))) / (1 + nu_r * nu_r) return distance @njit(**njit_dict) @@ -399,7 +399,7 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): r = r_packet.r if (distance > 0.0): - new_r = math.sqrt(r**2 + distance**2 + + new_r = np.sqrt(r * r + distance * distance + 2.0 * r * distance * r_packet.mu) r_packet.mu = (r_packet.mu * r + distance) / new_r r_packet.r = new_r diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index 4d7a8f1e6d1..c367586362d 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -133,7 +133,7 @@ def trace_vpacket(v_packet, numba_model, numba_plasma): tau_trace_combined = 0.0 # Moving the v_packet - new_r = math.sqrt(v_packet.r**2 + distance_boundary**2 + + new_r = math.sqrt(v_packet.r * v_packet.r + distance_boundary * distance_boundary + 2.0 * v_packet.r * distance_boundary * v_packet.mu) v_packet.mu = (v_packet.mu * v_packet.r + distance_boundary) / new_r v_packet.r = new_r @@ -173,7 +173,8 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, ### TODO theoretical check for r_packet nu within vpackets bins - is done somewhere else I think if r_packet.r > numba_model.r_inner[0]: # not on inner_boundary - mu_min = -math.sqrt(1 - (numba_model.r_inner[0] / r_packet.r) ** 2) + r_inner_over_r = (numba_model.r_inner[0] / r_packet.r) + mu_min = -math.sqrt(1 - r_inner_over_r * r_inner_over_r) v_packet_on_inner_boundary = False if montecarlo_configuration.full_relativity: mu_min = angle_aberration_LF_to_CMF(r_packet, From 93005f1b2d5ebc03c01447702b7e7a00b50b0335 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:29:49 -0400 Subject: [PATCH 079/116] pyne version fix --- tardis_env3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tardis_env3.yml b/tardis_env3.yml index 1c1142aed46..4a68330efb9 100644 --- a/tardis_env3.yml +++ b/tardis_env3.yml @@ -22,7 +22,7 @@ dependencies: # I/O - pyyaml - jsonschema - - pyne=0.7 + - pyne=0.7.0 - pytables - h5py - requests From 9c9a3d3c443e853eae6b380206ac79a6c90359f9 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:34:51 -0400 Subject: [PATCH 080/116] Syntax fix --- tardis/montecarlo/montecarlo_numba/r_packet.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 6f65bef7148..8cdd1eb6096 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -137,9 +137,14 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, nu_r = nu_line / nu ct = C_SPEED_OF_LIGHT * time_explosion distance = -r_packet.mu * r_packet.r + ( - ct - nu_r * nu_r * math.sqrt( - ct * ct - (1 + r_packet.r * r_packet.r * (1 - r_packet.mu * r_packet.mu) * - (1 + 1.0 / (nu_r * nu_r)))) / (1 + nu_r * nu_r) + ct - nu_r * nu_r * math.sqrt( + ct * ct - ( + 1 + r_packet.r * r_packet.r * ( + 1 - r_packet.mu * r_packet.mu + ) * (1 + 1.0 / (nu_r * nu_r)) + ) + ) + ) / (1 + nu_r * nu_r) return distance @njit(**njit_dict) From fbf01ba011187835cc2c2ea7280181c287d3c5bd Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 21 Oct 2020 11:41:09 -0400 Subject: [PATCH 081/116] Fix error_model njit arg --- tardis/montecarlo/montecarlo_numba/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tardis/montecarlo/montecarlo_numba/__init__.py b/tardis/montecarlo/montecarlo_numba/__init__.py index 653f5f8378e..100635cb9ea 100644 --- a/tardis/montecarlo/montecarlo_numba/__init__.py +++ b/tardis/montecarlo/montecarlo_numba/__init__.py @@ -1,6 +1,6 @@ from llvmlite import binding binding.set_option("tmp", "-non-global-value-max-name-size=2048") -njit_dict = {'fastmath': True, 'error_mode':'numpy'} +njit_dict = {'fastmath': True, 'error_model':'numpy'} from tardis.montecarlo.montecarlo_numba.r_packet import RPacket from tardis.montecarlo.montecarlo_numba.base import montecarlo_radial1d From 3ebff8377903153610b616d531988a98270d6e87 Mon Sep 17 00:00:00 2001 From: rodot- Date: Wed, 21 Oct 2020 12:04:22 -0400 Subject: [PATCH 082/116] Rewrote close line and last line tests to avoid branching --- tardis/montecarlo/montecarlo_numba/r_packet.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 8cdd1eb6096..ccbb45ac94f 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -290,6 +290,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): cur_line_id = start_line_id # initializing varibale for Numba # - do not remove + last_line_id = len(numba_plasma.line_list_nu) - 1 for cur_line_id in range(start_line_id, len(numba_plasma.line_list_nu)): @@ -306,11 +307,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): # Calculating the distance until the current photons co-moving nu # redshifts to the line frequency - - if cur_line_id == len(numba_plasma.line_list_nu) - 1: - is_last_line = True - else: - is_last_line = False + is_last_line = cur_line_id == last_line_id distance_trace = calculate_distance_line( r_packet, comov_nu, is_last_line, @@ -354,7 +351,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): distance = distance_trace break - if cur_line_id != (len(numba_plasma.line_list_nu) - 1): + if not is_last_line: test_for_close_line(r_packet, cur_line_id + 1, nu_line, numba_plasma) # Recalculating distance_electron using tau_event - @@ -525,6 +522,5 @@ def angle_aberration_LF_to_CMF(r_packet, time_explosion, mu): @njit(**njit_dict) def test_for_close_line(r_packet, line_id, nu_line, numba_plasma): - if (abs(numba_plasma.line_list_nu[line_id] - nu_line) - < (nu_line * CLOSE_LINE_THRESHOLD)): - r_packet.is_close_line = True + r_packet.is_close_line = (abs(numba_plasma.line_list_nu[line_id] - nu_line) + < (nu_line * CLOSE_LINE_THRESHOLD)) From 8b4c55f2572c4382a6691043df48b1ae06b1e382 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Thu, 22 Oct 2020 10:16:33 -0400 Subject: [PATCH 083/116] Added static packet for individual testing --- .../montecarlo/montecarlo_numba/tests/conftest.py | 14 +++++++++++++- .../montecarlo_numba/tests/test_macro_atom.py | 14 +++++++++++--- .../montecarlo_numba/tests/test_packet.py | 14 +++++++------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index dbffde58a8d..ed1b4dfc65f 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -80,7 +80,19 @@ def verysimple_packet_collection(nb_simulation_verysimple): runner._output_energy) @pytest.fixture(scope="function") -def packet(): +def packet(verysimple_packet_collection): + return RPacket( + r = 7.5e14, + nu = verysimple_packet_collection.packets_input_nu[0], + mu = verysimple_packet_collection.packets_input_mu[0], + energy = verysimple_packet_collection.packets_input_energy[0], + seed = 1963, + index = 0, + is_close_line = 0 + ) + +@pytest.fixture(scope="function") +def static_packet(): return RPacket( r = 7.5e14, nu = 0.4, diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py index 44cf2e72432..d77fecc5f0f 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py @@ -1,5 +1,13 @@ import pytest +import tardis.montecarlo.montecarlo_numba.macro_atom as macro_atom +import numpy as np +from numba import njit -@pytest.mark.xfail(reason='To be implemented') -def test_macro_atom(): - assert False \ No newline at end of file +@pytest.mark.parametrize( + 'expected', + [5259] + ) +def test_macro_atom(static_packet, verysimple_numba_plasma, verysimple_numba_model, expected): + static_packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) + result = macro_atom.macro_atom(static_packet, verysimple_numba_plasma) + assert result == expected \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 61d2b16e8a5..6162cde08b2 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -73,21 +73,21 @@ def test_calculate_distance_boundary(packet_params, expected_params, model): ({'nu_line': 0.6, 'next_line_id': 0, 'is_last_line': False}, {'tardis_error': r_packet.MonteCarloException, 'd_line': 0.0})] ) -def test_calculate_distance_line(packet_params, expected_params, packet, model): +def test_calculate_distance_line(packet_params, expected_params, static_packet, model): nu_line = packet_params['nu_line'] is_last_line = packet_params['is_last_line'] time_explosion = model.time_explosion - doppler_factor = r_packet.get_doppler_factor(packet.r, - packet.mu, + doppler_factor = r_packet.get_doppler_factor(static_packet.r, + static_packet.mu, time_explosion) - comov_nu = packet.nu * doppler_factor + comov_nu = static_packet.nu * doppler_factor d_line = 0 obtained_tardis_error = None try: - d_line = r_packet.calculate_distance_line(packet, + d_line = r_packet.calculate_distance_line(static_packet, comov_nu, is_last_line, nu_line, @@ -167,9 +167,9 @@ def test_get_random_mu(): [[0.0, 0.0, 0.0], [2.249998311331767, 0.0, 0.0]], [[0.0, 0.0, 1.0], [2.249998311331767*0.4, 0.0, 1.0]])] ) -def test_update_line_estimators(estimators, packet, cur_line_id, distance_trace, +def test_update_line_estimators(estimators, static_packet, cur_line_id, distance_trace, time_explosion, expected_j_blue, expected_Edotlu): - r_packet.update_line_estimators(estimators, packet, cur_line_id, distance_trace, + r_packet.update_line_estimators(estimators, static_packet, cur_line_id, distance_trace, time_explosion) assert_allclose(estimators.j_blue_estimator, expected_j_blue) From 7a326efacca06255daa4bf70617b3c9611b84ee3 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 23 Oct 2020 11:36:34 -0400 Subject: [PATCH 084/116] Fix vpacket close line test, rpacket line ID rpacket line ID can no longer exceed the line list. --- tardis/montecarlo/montecarlo_numba/r_packet.py | 4 +++- tardis/montecarlo/montecarlo_numba/vpacket.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index ccbb45ac94f..d9c14eabdea 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -56,8 +56,10 @@ def initialize_line_id(self, numba_plasma, numba_model): doppler_factor = get_doppler_factor(self.r, self.mu, numba_model.time_explosion) comov_nu = self.nu * doppler_factor - next_line_id = (len(numba_plasma.line_list_nu) - + next_line_id = (len(numba_plasma.line_list_nu) - np.searchsorted(inverse_line_list_nu, comov_nu)) + if next_line_id == len(numba_plasma.line_list_nu): + next_line_id -= 1 self.next_line_id = next_line_id @njit(**njit_dict) diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index c367586362d..fe0c3a19410 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -218,7 +218,7 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, r_packet.next_line_id, i, r_packet.is_close_line) if r_packet.next_line_id <= (len(numba_plasma.line_list_nu) - 1): - test_for_close_line(v_packet, r_packet.next_line_id + 1, numba_plasma.line_list_nu[r_packet.next_line_id], numba_plasma) + test_for_close_line(v_packet, r_packet.next_line_id, numba_plasma.line_list_nu[r_packet.next_line_id - 1], numba_plasma) tau_vpacket = trace_vpacket(v_packet, numba_model, numba_plasma) From 8d50445f280e5047c5fdf18e83ad92e303e1cc34 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 23 Oct 2020 12:17:28 -0400 Subject: [PATCH 085/116] xfail macro_atom test for now --- tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py index d77fecc5f0f..79833a453d6 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py @@ -3,6 +3,7 @@ import numpy as np from numba import njit +@pytest.mark.xfail(reason='Needs RNG freeze') @pytest.mark.parametrize( 'expected', [5259] From a14fda837de21a219679803ac956eec3cda87e4b Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 23 Oct 2020 16:21:36 -0400 Subject: [PATCH 086/116] Added test that breaks tracing vpackets --- .../montecarlo_numba/tests/test_vpacket.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index f0b6c9e7ebf..6136200a966 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -83,3 +83,22 @@ def test_trace_vpacket_volley( packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) vpacket.trace_vpacket_volley(packet, verysimple_3vpacket_collection, verysimple_numba_model, verysimple_numba_plasma) + +@pytest.fixture(scope="function") +def broken_packet(): + return r_packet.RPacket( + r = 1286064000000000.0, + nu = 1660428912896553.2, + mu = 0.4916053094346575, + energy = 2.474533071386993e-07, + index = 21643, + is_close_line = False, + seed = 32126279 + ) + + +def test_trace_bad_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma): + #Give the vpacket a reasonable line ID + v_packet_initialize_line_id(broken_packet, verysimple_numba_plasma, verysimple_numba_model) + broken_packet.next_line_id = 5495 + tau_trace_combined = vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) \ No newline at end of file From f209cde4071f11f3bc8215ecaeb271a7dd169b85 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 23 Oct 2020 16:45:49 -0400 Subject: [PATCH 087/116] Even better bad vpacket test --- .../montecarlo_numba/tests/test_vpacket.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index 6136200a966..63879a7d8d2 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -86,19 +86,16 @@ def test_trace_vpacket_volley( @pytest.fixture(scope="function") def broken_packet(): - return r_packet.RPacket( + return vpacket.VPacket( r = 1286064000000000.0, nu = 1660428912896553.2, mu = 0.4916053094346575, energy = 2.474533071386993e-07, - index = 21643, - is_close_line = False, - seed = 32126279 + index = 3, + is_close_line = True, + current_shell_id = 0, + next_line_id = 5495 ) - def test_trace_bad_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma): - #Give the vpacket a reasonable line ID - v_packet_initialize_line_id(broken_packet, verysimple_numba_plasma, verysimple_numba_model) - broken_packet.next_line_id = 5495 tau_trace_combined = vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) \ No newline at end of file From f25c042e64cee8973cc07837b0c4a880f58f0793 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 23 Oct 2020 17:01:22 -0400 Subject: [PATCH 088/116] Fix nu_diff < 0 error (for now...) --- tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py | 4 ++-- tardis/montecarlo/montecarlo_numba/vpacket.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index 63879a7d8d2..507029b82f4 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -69,7 +69,7 @@ def test_trace_vpacket(v_packet, verysimple_numba_model, verysimple_numba_plasma assert v_packet.is_close_line == False assert v_packet.current_shell_id == 1 -#Needs an actual test instead of just running the function +@pytest.mark.xfail(reason='To be implemented') def test_trace_vpacket_volley( packet, verysimple_packet_collection, @@ -98,4 +98,4 @@ def broken_packet(): ) def test_trace_bad_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma): - tau_trace_combined = vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) \ No newline at end of file + vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index fe0c3a19410..b9c1f207961 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -83,13 +83,13 @@ def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): nu_line, numba_model.time_explosion ) + if cur_line_id != (len(numba_plasma.line_list_nu) - 1): + test_for_close_line(v_packet, cur_line_id, numba_plasma.line_list_nu[cur_line_id - 1], numba_plasma) + if distance_boundary <= distance_trace_line: break tau_trace_combined += tau_trace_line - - if cur_line_id != (len(numba_plasma.line_list_nu) - 1): - test_for_close_line(v_packet, cur_line_id + 1, numba_plasma.line_list_nu[cur_line_id], numba_plasma) else: if cur_line_id == (len(numba_plasma.line_list_nu) - 1): From 22ffc8b1b4e35ca3a02093cb2d8dbe59d5c054ac Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 26 Oct 2020 11:04:57 -0400 Subject: [PATCH 089/116] NumbaPlasma initialization test --- .../montecarlo_numba/numba_interface.py | 12 +++--- .../tests/test_numba_interface.py | 37 +++++++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index aed4573488b..276f2ded585 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -84,12 +84,12 @@ def numba_plasma_initialize(plasma, line_interaction_type): if line_interaction_type == 'scatter': # to adhere to data types, we must have an array of minimum size 1 array_size = 1 - transition_probabilities = np.empty((array_size, array_size), dtype=np.float64) # to adhere to data types - line2macro_level_upper = np.empty(array_size, dtype=np.int64) - macro_block_references = np.empty(array_size, dtype=np.int64) - transition_type = np.empty(array_size, dtype=np.int64) - destination_level_id = np.empty(array_size, dtype=np.int64) - transition_line_id = np.empty(array_size, dtype=np.int64) + transition_probabilities = np.zeros((array_size, array_size), dtype=np.float64) # to adhere to data types + line2macro_level_upper = np.zeros(array_size, dtype=np.int64) + macro_block_references = np.zeros(array_size, dtype=np.int64) + transition_type = np.zeros(array_size, dtype=np.int64) + destination_level_id = np.zeros(array_size, dtype=np.int64) + transition_line_id = np.zeros(array_size, dtype=np.int64) else: transition_probabilities = np.ascontiguousarray( plasma.transition_probabilities.values.copy(), dtype=np.float64) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py index 2e4e970933e..bf0f56c4f50 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py @@ -1,8 +1,39 @@ import pytest +import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface +import numpy.testing as npt +import numpy as np -@pytest.mark.xfail(reason='To be implemented') -def test_numba_plasma_initialize(): - assert False +@pytest.mark.parametrize( + 'input_params', + ['scatter', 'macroatom', 'downbranch'] +) +def test_numba_plasma_initialize(nb_simulation_verysimple, input_params): + line_interaction_type = input_params + plasma = nb_simulation_verysimple.plasma + actual = numba_interface.numba_plasma_initialize(plasma, line_interaction_type) + + npt.assert_allclose(actual.electron_density, plasma.electron_densities.values) + npt.assert_allclose(actual.line_list_nu, plasma.atomic_data.lines.nu.values) + npt.assert_allclose(actual.tau_sobolev, plasma.tau_sobolevs.values) + if line_interaction_type == 'scatter': + empty = np.zeros(1, dtype=np.int64) + npt.assert_allclose(actual.transition_probabilities, np.zeros((1, 1), dtype=np.float64)) + npt.assert_allclose(actual.line2macro_level_upper, empty) + npt.assert_allclose(actual.macro_block_references, empty) + npt.assert_allclose(actual.transition_type, empty) + npt.assert_allclose(actual.destination_level_id, empty) + npt.assert_allclose(actual.transition_line_id, empty) + else: + npt.assert_allclose(actual.transition_probabilities, plasma.transition_probabilities.values) + npt.assert_allclose(actual.line2macro_level_upper, plasma.atomic_data.lines_upper2macro_reference_idx) + npt.assert_allclose(actual.macro_block_references, plasma.atomic_data.macro_atom_references[ + 'block_references'].values) + npt.assert_allclose(actual.transition_type, plasma.atomic_data.macro_atom_data[ + 'transition_type'].values) + npt.assert_allclose(actual.destination_level_id, plasma.atomic_data.macro_atom_data[ + 'destination_level_idx'].values) + npt.assert_allclose(actual.transition_line_id, plasma.atomic_data.macro_atom_data[ + 'lines_idx'].values) @pytest.mark.xfail(reason='To be implemented') From aae1adb8471402e00bfcee8d13311feb5e3b12ee Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 26 Oct 2020 11:30:14 -0400 Subject: [PATCH 090/116] numba macro_atom test, numba seed set fixture --- tardis/montecarlo/montecarlo_numba/tests/conftest.py | 9 ++++++++- .../montecarlo_numba/tests/test_macro_atom.py | 12 +++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index ed1b4dfc65f..e8708810099 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -2,6 +2,7 @@ import pytest import numpy as np +from numba import njit from tardis.simulation import Simulation from tardis.montecarlo.montecarlo_numba import RPacket, PacketCollection @@ -101,4 +102,10 @@ def static_packet(): seed = 1963, index = 0, is_close_line = 0 - ) \ No newline at end of file + ) + +@pytest.fixture() +def set_seed_fixture(): + def set_seed(value): + np.random.seed(value) + return njit(set_seed) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py index 79833a453d6..518e538c1a3 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_macro_atom.py @@ -1,14 +1,16 @@ import pytest import tardis.montecarlo.montecarlo_numba.macro_atom as macro_atom import numpy as np -from numba import njit -@pytest.mark.xfail(reason='Needs RNG freeze') @pytest.mark.parametrize( - 'expected', - [5259] + ['seed', 'expected'], + [(1963, 10015), + (1, 9993), + (2111963, 17296), + (10000, 9993)] ) -def test_macro_atom(static_packet, verysimple_numba_plasma, verysimple_numba_model, expected): +def test_macro_atom(static_packet, verysimple_numba_plasma, verysimple_numba_model, set_seed_fixture, seed, expected): + set_seed_fixture(seed) static_packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) result = macro_atom.macro_atom(static_packet, verysimple_numba_plasma) assert result == expected \ No newline at end of file From 1c440013be2caab5e75741d7df5423d901aa17fc Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 26 Oct 2020 14:30:06 -0400 Subject: [PATCH 091/116] More packet tests --- .../montecarlo_numba/tests/test_packet.py | 61 ++++++++++++++++--- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 6162cde08b2..5f5640aab94 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -11,6 +11,7 @@ import tardis.montecarlo.montecarlo_numba.numba_config as numba_config from tardis.montecarlo.montecarlo_numba import macro_atom C_SPEED_OF_LIGHT = const.c.to('cm/s').value +SIGMA_THOMSON = const.sigma_T.to('cm^2').value from numpy.testing import ( assert_equal, @@ -98,11 +99,32 @@ def test_calculate_distance_line(packet_params, expected_params, static_packet, assert_almost_equal(d_line, expected_params['d_line']) assert obtained_tardis_error == expected_params['tardis_error'] -def test_calculate_distance_electron(): - pass -def test_calculate_tau_electron(): - pass +@pytest.mark.parametrize( + ['electron_density', 'tau_event'], + [(1e-5, 1.0), + (1e10, 1e10)] +) +def test_calculate_distance_electron(electron_density, tau_event): + actual = r_packet.calculate_distance_electron(electron_density, tau_event) + expected = tau_event / (electron_density * SIGMA_THOMSON) + + assert_almost_equal(actual, expected) + + +@pytest.mark.parametrize( + ['electron_density', 'distance'], + [(1e-5, 1.0), + (1e10, 1e10), + (-1, 0), + (-1e10, -1e10)] +) +def test_calculate_tau_electron(electron_density, distance): + actual = r_packet.calculate_tau_electron(electron_density, distance) + expected = electron_density * SIGMA_THOMSON * distance + + assert_almost_equal(actual, expected) + @pytest.mark.parametrize( ['mu', 'r', 'inv_t_exp', 'expected'], @@ -124,6 +146,7 @@ def test_get_doppler_factor(mu, r, inv_t_exp, expected): # Perform required assertions assert_almost_equal(obtained, expected) + @pytest.mark.parametrize( ['mu', 'r', 'inv_t_exp', 'expected'], [(0.3, 7.5e14, 1 / 5.2e7, 1/0.9998556693818854), @@ -175,9 +198,16 @@ def test_update_line_estimators(estimators, static_packet, cur_line_id, distance assert_allclose(estimators.j_blue_estimator, expected_j_blue) assert_allclose(estimators.Edotlu_estimator, expected_Edotlu) -@pytest.mark.xfail(reason='To be implemented') -def test_trace_packet(): - assert False +#@pytest.mark.xfail(reason='To be implemented') +def test_trace_packet(packet, verysimple_numba_model, verysimple_numba_plasma, + verysimple_estimators): + packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) + distance, interaction_type, delta_shell = r_packet.trace_packet(packet, verysimple_numba_model, + verysimple_numba_plasma, verysimple_estimators) + + assert delta_shell == 1 + assert interaction_type == 1 + assert_almost_equal(distance, 581086681128631.8) @pytest.mark.xfail(reason='bug in full relativity') @@ -225,12 +255,15 @@ def test_move_r_packet(packet_params, expected_params, packet, model, estimators assert_allclose(estimators.nu_bar_estimator[packet.current_shell_id], expected_nubar, rtol=5e-7) +@pytest.mark.xfail(reason='To be implemented') def test_set_estimators(): pass +@pytest.mark.xfail(reason='To be implemented') def test_set_estimators_full_relativity(): pass +@pytest.mark.xfail(reason='To be implemented') def test_line_emission(): pass @@ -290,5 +323,15 @@ def test_packet_energy_limit_one(packet, distance_trace, time_explosion): new_energy = r_packet.calc_packet_energy(packet, distance_trace, time_explosion) assert_almost_equal(new_energy, initial_energy) """ -def test_test_for_close_line(): - pass + + +@pytest.mark.parametrize( + ['line_id', 'nu_line', 'expected'], + [(5495, 1629252823683562.5, True), + (3000, 0, False)] +) +def test_test_for_close_line(packet, line_id, nu_line, verysimple_numba_plasma, expected): + + r_packet.test_for_close_line(packet, line_id, nu_line, verysimple_numba_plasma) + + assert packet.is_close_line == expected From 7e0aa5c9f8df086043b645cc0600fb1f255ea6e5 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 26 Oct 2020 17:53:37 -0400 Subject: [PATCH 092/116] Currently failing C comparison test written --- .../montecarlo_numba/tests/test_base.py | 56 ++++++++++++++++++- .../montecarlo_numba/tests/test_packet.py | 4 +- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 1fb8ca6ae94..4d3884213fa 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -1,9 +1,59 @@ import pytest +import pandas as pd +import os +import numpy.testing as npt +import numpy as np +import copy + +import tardis.montecarlo.montecarlo_numba.base as base +from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration + +@pytest.fixture() +def read_c_test(tardis_ref_path): + mode = "r" + with pd.HDFStore( + os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5"), mode=mode + ) as store: + yield store + +@pytest.fixture() +def c_test_packet_collection(read_c_test): + input_data = read_c_test['/one_packet_loop'] + input_nu = input_data['input_nu'].values + input_mu = input_data['input_mu'].values + input_energy = input_data['input_energy'].values + output_nu = input_data['output_nu'].values + output_energy = input_data['output_energy'].values + return PacketCollection( + input_nu, input_mu, input_energy, + output_nu, output_energy + ) @pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False -@pytest.mark.xfail(reason='To be implemented') -def test_montecarlo_main_loop(): - assert False \ No newline at end of file +#@pytest.mark.xfail(reason='To be implemented') +def test_montecarlo_main_loop( + c_test_packet_collection, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, + nb_simulation_verysimple, set_seed_fixture +): + montecarlo_configuration.single_packet_seed = 0 + + output_packet_collection = PacketCollection( + c_test_packet_collection.packets_input_nu, + c_test_packet_collection.packets_input_mu, + c_test_packet_collection.packets_input_energy, + np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64), + np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64) + ) + + set_seed_fixture(23111963) + base.montecarlo_main_loop( + output_packet_collection, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, + nb_simulation_verysimple.runner.spectrum_frequency.value, 0, [23111963] + ) + + npt.assert_allclose(output_packet_collection.packets_output_nu, c_test_packet_collection.packets_output_nu) + npt.assert_allclose(output_packet_collection.packets_output_energy, c_test_packet_collection.packets_output_energy) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 5f5640aab94..b6c57cb6f1c 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -200,7 +200,9 @@ def test_update_line_estimators(estimators, static_packet, cur_line_id, distance #@pytest.mark.xfail(reason='To be implemented') def test_trace_packet(packet, verysimple_numba_model, verysimple_numba_plasma, - verysimple_estimators): + verysimple_estimators, set_seed_fixture): + + set_seed_fixture(1963) packet.initialize_line_id(verysimple_numba_plasma, verysimple_numba_model) distance, interaction_type, delta_shell = r_packet.trace_packet(packet, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators) From a340ee9cda31de9d97b195d463439457292ccbe8 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 30 Oct 2020 14:50:38 -0400 Subject: [PATCH 093/116] Improved C comparison test (still fails) --- .../montecarlo_numba/tests/test_base.py | 84 +++++++++++++++++-- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 4d3884213fa..407c0b630aa 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -6,8 +6,11 @@ import copy import tardis.montecarlo.montecarlo_numba.base as base -from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection +from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection, VPacketCollection from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration +import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface +import tardis.montecarlo.montecarlo_numba.r_packet as r_packet +import tardis.montecarlo.montecarlo_numba.single_packet_loop as spl @pytest.fixture() def read_c_test(tardis_ref_path): @@ -30,13 +33,50 @@ def c_test_packet_collection(read_c_test): output_nu, output_energy ) +@pytest.fixture(scope="function") +def model(): + return numba_interface.NumbaModel( + r_inner = np.array([6.912e14, 8.64e14], dtype=np.float64), + r_outer = np.array([8.64e14, 1.0368e15], dtype=np.float64), + time_explosion = 5.2e7 + ) + +@pytest.fixture(scope="function") +def estimators(): + return numba_interface.Estimators( + j_estimator = np.array([0.0, 0.0], dtype=np.float64), + nu_bar_estimator = np.array([0.0, 0.0], dtype=np.float64), + j_blue_estimator = np.array([[1.e-10], [1.e-10]], dtype=np.float64), + Edotlu_estimator = np.array([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], dtype=np.float64) + ) + +@pytest.fixture(scope="function") +def plasma(): + return numba_interface.NumbaPlasma( + electron_density = np.array([1.0e9, 1.0e9], dtype=np.float64), + line_list_nu = np.array([ + 1.26318289e+16, + 1.26318289e+16, + 1.23357675e+16, + 1.23357675e+16, + 1.16961598e+16], dtype=np.float64), + tau_sobolev = np.ones((500, 2), dtype=np.float64) * 1.0e-5, + transition_probabilities = np.array([[0], [0]], dtype=np.float64), + line2macro_level_upper = np.array([0, 0], dtype=np.int64), + macro_block_references = np.array([0, 0], dtype=np.int64), + transition_type = np.array([0, 0], dtype=np.int64), + destination_level_id = np.array([0, 0], dtype=np.int64), + transition_line_id = np.array([0, 0], dtype=np.int64) + ) + + @pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False #@pytest.mark.xfail(reason='To be implemented') def test_montecarlo_main_loop( - c_test_packet_collection, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, + c_test_packet_collection, model, plasma, estimators, nb_simulation_verysimple, set_seed_fixture ): montecarlo_configuration.single_packet_seed = 0 @@ -49,11 +89,39 @@ def test_montecarlo_main_loop( np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64) ) - set_seed_fixture(23111963) - base.montecarlo_main_loop( - output_packet_collection, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, - nb_simulation_verysimple.runner.spectrum_frequency.value, 0, [23111963] + vpacket_collection = VPacketCollection( + np.array([0, 0], dtype=np.float64), 0, np.inf, + 0, 0 ) - npt.assert_allclose(output_packet_collection.packets_output_nu, c_test_packet_collection.packets_output_nu) - npt.assert_allclose(output_packet_collection.packets_output_energy, c_test_packet_collection.packets_output_energy) \ No newline at end of file + output_nus = np.empty_like(output_packet_collection.packets_output_nu) + output_energies = np.empty_like(output_packet_collection.packets_output_nu) + + set_seed_fixture(23111963) + seed = 23111963 + for i in range(len(c_test_packet_collection.packets_input_nu)): + packet = r_packet.RPacket(model.r_inner[0], + output_packet_collection.packets_input_mu[i], + output_packet_collection.packets_input_nu[i], + output_packet_collection.packets_input_energy[i], + seed, + i, 0) + + + spl.single_packet_loop( + packet, model, plasma, estimators, vpacket_collection + ) + + output_nus[i] = packet.nu + + if packet.status == r_packet.PacketStatus.REABSORBED: + output_energies[i] = -packet.energy + elif packet.status == r_packet.PacketStatus.EMITTED: + output_energies[i] = packet.energy + + # np.savetxt('scatter_output_energy.txt', output_energies) + output_packet_collection.packets_output_energy[:] = output_energies[:] + output_packet_collection.packets_output_nu[:] = output_nus[:] + + npt.assert_allclose(output_packet_collection.packets_output_nu, c_test_packet_collection.packets_output_nu, rtol=1e-12) + npt.assert_allclose(output_packet_collection.packets_output_energy, c_test_packet_collection.packets_output_energy, rtol=1e-12) \ No newline at end of file From 0569f9eb6a6f0c26b00381ba25202acf3b0e5ff8 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 30 Oct 2020 16:36:23 -0400 Subject: [PATCH 094/116] More C comparison improvements --- tardis/montecarlo/montecarlo_numba/r_packet.py | 4 ++-- tardis/montecarlo/montecarlo_numba/tests/conftest.py | 6 ++++++ tardis/montecarlo/montecarlo_numba/tests/test_base.py | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index d9c14eabdea..b9207024721 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -152,11 +152,11 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, @njit(**njit_dict) def calculate_distance_electron(electron_density, tau_event): # add full_relativity here - return tau_event / (electron_density * SIGMA_THOMSON) + return tau_event / (electron_density * numba_config.SIGMA_THOMSON) @njit(**njit_dict) def calculate_tau_electron(electron_density, distance): - return electron_density * SIGMA_THOMSON * distance + return electron_density * numba_config.SIGMA_THOMSON * distance @njit(**njit_dict) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index e8708810099..d6c0f22f96d 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -109,3 +109,9 @@ def set_seed_fixture(): def set_seed(value): np.random.seed(value) return njit(set_seed) + +@pytest.fixture() +def random_call_fixture(): + def random_call(): + np.random.random() + return njit(random_call) \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 407c0b630aa..4a7aef8a04e 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -9,6 +9,7 @@ from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection, VPacketCollection from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface +import tardis.montecarlo.montecarlo_numba.numba_config as numba_config import tardis.montecarlo.montecarlo_numba.r_packet as r_packet import tardis.montecarlo.montecarlo_numba.single_packet_loop as spl @@ -77,7 +78,7 @@ def test_montecarlo_radial1d(): #@pytest.mark.xfail(reason='To be implemented') def test_montecarlo_main_loop( c_test_packet_collection, model, plasma, estimators, - nb_simulation_verysimple, set_seed_fixture + nb_simulation_verysimple, set_seed_fixture, random_call_fixture ): montecarlo_configuration.single_packet_seed = 0 @@ -89,6 +90,8 @@ def test_montecarlo_main_loop( np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64) ) + numba_config.SIGMA_THOMSON = 6.652486e-25 + vpacket_collection = VPacketCollection( np.array([0, 0], dtype=np.float64), 0, np.inf, 0, 0 @@ -119,6 +122,8 @@ def test_montecarlo_main_loop( elif packet.status == r_packet.PacketStatus.EMITTED: output_energies[i] = packet.energy + #random_call_fixture() + # np.savetxt('scatter_output_energy.txt', output_energies) output_packet_collection.packets_output_energy[:] = output_energies[:] output_packet_collection.packets_output_nu[:] = output_nus[:] From a97349ab65e20023413930ebec18fde1ba91a424 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 2 Nov 2020 15:12:26 -0500 Subject: [PATCH 095/116] Back to the future (with a simpler C test) --- .../montecarlo_numba/tests/test_base.py | 118 ++---------------- 1 file changed, 11 insertions(+), 107 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 4a7aef8a04e..0d07c03c9f8 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -13,120 +13,24 @@ import tardis.montecarlo.montecarlo_numba.r_packet as r_packet import tardis.montecarlo.montecarlo_numba.single_packet_loop as spl -@pytest.fixture() -def read_c_test(tardis_ref_path): - mode = "r" - with pd.HDFStore( - os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5"), mode=mode - ) as store: - yield store - -@pytest.fixture() -def c_test_packet_collection(read_c_test): - input_data = read_c_test['/one_packet_loop'] - input_nu = input_data['input_nu'].values - input_mu = input_data['input_mu'].values - input_energy = input_data['input_energy'].values - output_nu = input_data['output_nu'].values - output_energy = input_data['output_energy'].values - return PacketCollection( - input_nu, input_mu, input_energy, - output_nu, output_energy - ) - -@pytest.fixture(scope="function") -def model(): - return numba_interface.NumbaModel( - r_inner = np.array([6.912e14, 8.64e14], dtype=np.float64), - r_outer = np.array([8.64e14, 1.0368e15], dtype=np.float64), - time_explosion = 5.2e7 - ) - -@pytest.fixture(scope="function") -def estimators(): - return numba_interface.Estimators( - j_estimator = np.array([0.0, 0.0], dtype=np.float64), - nu_bar_estimator = np.array([0.0, 0.0], dtype=np.float64), - j_blue_estimator = np.array([[1.e-10], [1.e-10]], dtype=np.float64), - Edotlu_estimator = np.array([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]], dtype=np.float64) - ) - -@pytest.fixture(scope="function") -def plasma(): - return numba_interface.NumbaPlasma( - electron_density = np.array([1.0e9, 1.0e9], dtype=np.float64), - line_list_nu = np.array([ - 1.26318289e+16, - 1.26318289e+16, - 1.23357675e+16, - 1.23357675e+16, - 1.16961598e+16], dtype=np.float64), - tau_sobolev = np.ones((500, 2), dtype=np.float64) * 1.0e-5, - transition_probabilities = np.array([[0], [0]], dtype=np.float64), - line2macro_level_upper = np.array([0, 0], dtype=np.int64), - macro_block_references = np.array([0, 0], dtype=np.int64), - transition_type = np.array([0, 0], dtype=np.int64), - destination_level_id = np.array([0, 0], dtype=np.int64), - transition_line_id = np.array([0, 0], dtype=np.int64) - ) - @pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False -#@pytest.mark.xfail(reason='To be implemented') -def test_montecarlo_main_loop( - c_test_packet_collection, model, plasma, estimators, - nb_simulation_verysimple, set_seed_fixture, random_call_fixture -): - montecarlo_configuration.single_packet_seed = 0 - - output_packet_collection = PacketCollection( - c_test_packet_collection.packets_input_nu, - c_test_packet_collection.packets_input_mu, - c_test_packet_collection.packets_input_energy, - np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64), - np.zeros(len(c_test_packet_collection.packets_input_nu), dtype=np.float64) - ) - - numba_config.SIGMA_THOMSON = 6.652486e-25 - - vpacket_collection = VPacketCollection( - np.array([0, 0], dtype=np.float64), 0, np.inf, - 0, 0 - ) - - output_nus = np.empty_like(output_packet_collection.packets_output_nu) - output_energies = np.empty_like(output_packet_collection.packets_output_nu) - - set_seed_fixture(23111963) - seed = 23111963 - for i in range(len(c_test_packet_collection.packets_input_nu)): - packet = r_packet.RPacket(model.r_inner[0], - output_packet_collection.packets_input_mu[i], - output_packet_collection.packets_input_nu[i], - output_packet_collection.packets_input_energy[i], - seed, - i, 0) - - - spl.single_packet_loop( - packet, model, plasma, estimators, vpacket_collection - ) - output_nus[i] = packet.nu +def test_montecarlo_main_loop(nb_simulation_verysimple, tardis_ref_path, tmpdir): + fname = str(tmpdir.mkdir("data").join("test.hdf")) + C_fname = os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5") - if packet.status == r_packet.PacketStatus.REABSORBED: - output_energies[i] = -packet.energy - elif packet.status == r_packet.PacketStatus.EMITTED: - output_energies[i] = packet.energy + sim = nb_simulation_verysimple + sim.to_hdf(fname) - #random_call_fixture() + actual_nu = pd.read_hdf(fname, key="/simulation/runner/output_nu").values + expected_nu = pd.read_hdf(C_fname, key="/simulation/runner/output_nu").values - # np.savetxt('scatter_output_energy.txt', output_energies) - output_packet_collection.packets_output_energy[:] = output_energies[:] - output_packet_collection.packets_output_nu[:] = output_nus[:] + actual_energy = pd.read_hdf(fname, key="/simulation/runner/output_energy").values + expected_energy = pd.read_hdf(C_fname, key="/simulation/runner/output_energy").values - npt.assert_allclose(output_packet_collection.packets_output_nu, c_test_packet_collection.packets_output_nu, rtol=1e-12) - npt.assert_allclose(output_packet_collection.packets_output_energy, c_test_packet_collection.packets_output_energy, rtol=1e-12) \ No newline at end of file + npt.assert_array_almost_equal(actual_nu, expected_nu) + npt.assert_array_almost_equal(actual_energy, expected_energy) \ No newline at end of file From e05f76db35ac05c80e2cc3355162e66c807a233f Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 2 Nov 2020 15:50:11 -0500 Subject: [PATCH 096/116] More packets, cleanup --- .../montecarlo_numba/tests/test_base.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 0d07c03c9f8..e54d784fb28 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -3,15 +3,9 @@ import os import numpy.testing as npt import numpy as np -import copy +from copy import deepcopy -import tardis.montecarlo.montecarlo_numba.base as base -from tardis.montecarlo.montecarlo_numba.numba_interface import PacketCollection, VPacketCollection -from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration -import tardis.montecarlo.montecarlo_numba.numba_interface as numba_interface -import tardis.montecarlo.montecarlo_numba.numba_config as numba_config -import tardis.montecarlo.montecarlo_numba.r_packet as r_packet -import tardis.montecarlo.montecarlo_numba.single_packet_loop as spl +from tardis.simulation import Simulation @pytest.mark.xfail(reason='To be implemented') @@ -19,11 +13,13 @@ def test_montecarlo_radial1d(): assert False -def test_montecarlo_main_loop(nb_simulation_verysimple, tardis_ref_path, tmpdir): +def test_montecarlo_main_loop(config_verysimple, atomic_dataset, tardis_ref_path, tmpdir): fname = str(tmpdir.mkdir("data").join("test.hdf")) C_fname = os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5") - sim = nb_simulation_verysimple + atomic_data = deepcopy(atomic_dataset) + sim = Simulation.from_config(config_verysimple, atom_data=atomic_data) + sim.iterate(100000) sim.to_hdf(fname) actual_nu = pd.read_hdf(fname, key="/simulation/runner/output_nu").values From a6a0383574ba96c0787451f8856cbf80a323a2d2 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 2 Nov 2020 16:11:13 -0500 Subject: [PATCH 097/116] Fixed test? --- tardis/montecarlo/montecarlo_numba/tests/test_base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index e54d784fb28..dc88a7b32a9 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -6,6 +6,7 @@ from copy import deepcopy from tardis.simulation import Simulation +from tardis import run_tardis @pytest.mark.xfail(reason='To be implemented') @@ -18,8 +19,12 @@ def test_montecarlo_main_loop(config_verysimple, atomic_dataset, tardis_ref_path C_fname = os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5") atomic_data = deepcopy(atomic_dataset) - sim = Simulation.from_config(config_verysimple, atom_data=atomic_data) - sim.iterate(100000) + config_verysimple.montecarlo.last_no_of_packets = 1e5 + config_verysimple.montecarlo.no_of_virtual_packets = 0 + config_verysimple.montecarlo.iterations = 1 + del config_verysimple['config_dirname'] + + sim = run_tardis(config_verysimple, atom_data=atomic_data) sim.to_hdf(fname) actual_nu = pd.read_hdf(fname, key="/simulation/runner/output_nu").values From 1123fa15078cc394a04a40fd5da0964f5e57ab88 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Tue, 3 Nov 2020 15:24:46 -0500 Subject: [PATCH 098/116] Fixed C compare, added legacy mode flag --- tardis/montecarlo/montecarlo_configuration.py | 1 + .../montecarlo/montecarlo_numba/r_packet.py | 2 +- .../montecarlo_numba/tests/test_base.py | 139 ++++++++++++++++-- tardis/montecarlo/packet_source.py | 14 +- 4 files changed, 141 insertions(+), 15 deletions(-) diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py index 0950caf7821..666e20818b8 100644 --- a/tardis/montecarlo/montecarlo_configuration.py +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -11,3 +11,4 @@ disable_line_scattering = False survival_probability = 0.0 tau_russian = 10.0 +LEGACY_MODE_ENABLED = False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index b9207024721..22de7e1f359 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -275,7 +275,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): start_line_id = r_packet.next_line_id # defining taus - tau_event = -np.log(1 - np.random.random()) + tau_event = -np.log(np.random.random()) tau_trace_line_combined = 0.0 # e scattering initialization diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index dc88a7b32a9..d173d2abf3f 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -4,34 +4,149 @@ import numpy.testing as npt import numpy as np from copy import deepcopy +from astropy import units as u +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration +import tardis.montecarlo.montecarlo_numba.base as base from tardis.simulation import Simulation -from tardis import run_tardis +import tardis.montecarlo.montecarlo_numba.r_packet as r_packet +import tardis.montecarlo.montecarlo_numba.single_packet_loop as spl +from tardis.montecarlo.montecarlo_numba.numba_interface import ( + PacketCollection, VPacketCollection, NumbaModel, numba_plasma_initialize, + Estimators, configuration_initialize) +@pytest.fixture() +def read_c_test(tardis_ref_path): + mode = "r" + with pd.HDFStore( + os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5"), mode=mode + ) as store: + yield store + + +@pytest.fixture() +def c_test_packet_collection(read_c_test): + input_data = read_c_test['/one_packet_loop'] + input_nu = input_data['input_nu'].values + input_mu = input_data['input_mu'].values + input_energy = input_data['input_energy'].values + output_nu = input_data['output_nu'].values + output_energy = input_data['output_energy'].values + return PacketCollection( + input_nu, input_mu, input_energy, + output_nu, output_energy + ) + @pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False -def test_montecarlo_main_loop(config_verysimple, atomic_dataset, tardis_ref_path, tmpdir): - fname = str(tmpdir.mkdir("data").join("test.hdf")) - C_fname = os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5") +def test_montecarlo_main_loop( + config_verysimple, atomic_dataset, tardis_ref_path, tmpdir, set_seed_fixture, c_test_packet_collection, random_call_fixture + ): + montecarlo_configuration.LEGACY_MODE_ENABLED = True + + #Load C data from refdata + C_fname = os.path.join(tardis_ref_path, "montecarlo_1e5_compare_data.h5") + expected_nu = pd.read_hdf(C_fname, key="/simulation/runner/output_nu").values + expected_energy = pd.read_hdf(C_fname, key="/simulation/runner/output_energy").values + expected_nu_bar_estimator = pd.read_hdf(C_fname, key="/simulation/runner/nu_bar_estimator").values + expected_j_estimator = pd.read_hdf(C_fname, key="/simulation/runner/j_estimator").values + + #Setup model config from verysimple atomic_data = deepcopy(atomic_dataset) config_verysimple.montecarlo.last_no_of_packets = 1e5 config_verysimple.montecarlo.no_of_virtual_packets = 0 config_verysimple.montecarlo.iterations = 1 + config_verysimple.montecarlo.single_packet_seed = 0 del config_verysimple['config_dirname'] - sim = run_tardis(config_verysimple, atom_data=atomic_data) - sim.to_hdf(fname) + sim = Simulation.from_config(config_verysimple, atom_data=atomic_data) + + #Init model + numba_plasma = numba_plasma_initialize(sim.plasma, + line_interaction_type='macroatom') - actual_nu = pd.read_hdf(fname, key="/simulation/runner/output_nu").values - expected_nu = pd.read_hdf(C_fname, key="/simulation/runner/output_nu").values + runner = sim.runner + model = sim.model - actual_energy = pd.read_hdf(fname, key="/simulation/runner/output_energy").values - expected_energy = pd.read_hdf(C_fname, key="/simulation/runner/output_energy").values + runner._initialize_geometry_arrays(model) + runner._initialize_estimator_arrays(numba_plasma.tau_sobolev.shape) + runner._initialize_packets(model.t_inner.value, 100000, 0) + + #Init parameters + montecarlo_configuration.v_packet_spawn_start_frequency = runner.virtual_spectrum_spawn_range.end.to( + u.Hz, equivalencies=u.spectral() + ).value + montecarlo_configuration.v_packet_spawn_end_frequency = runner.virtual_spectrum_spawn_range.start.to( + u.Hz, equivalencies=u.spectral() + ).value + montecarlo_configuration.temporary_v_packet_bins = 20000 + montecarlo_configuration.full_relativity = runner.enable_full_relativity + montecarlo_configuration.single_packet_seed = 0 + + #Init packet collection from runner + packet_collection = PacketCollection( + runner.input_nu, runner.input_mu, runner.input_energy, + runner._output_nu, runner._output_energy) + + #Init model from runner + numba_model = NumbaModel(runner.r_inner_cgs, runner.r_outer_cgs, + model.time_explosion.to('s').value) + + #Init estimators from runner + estimators = Estimators(runner.j_estimator, runner.nu_bar_estimator, + runner.j_blue_estimator, runner.Edotlu_estimator) + + #Empty vpacket collection + vpacket_collection = VPacketCollection( + np.array([0, 0], dtype=np.float64), 0, np.inf, + 0, 0 + ) + + #output arrays + output_nus = np.empty_like(packet_collection.packets_output_nu) + output_energies = np.empty_like(packet_collection.packets_output_nu) + + #IMPORTANT: seeds RNG state within JIT + seed = 23111963 + set_seed_fixture(seed) + for i in range(len(packet_collection.packets_input_nu)): + #Generate packet + packet = r_packet.RPacket(numba_model.r_inner[0], + packet_collection.packets_input_mu[i], + packet_collection.packets_input_nu[i], + packet_collection.packets_input_energy[i], + seed, + i, 0) + + #Loop packet + spl.single_packet_loop( + packet, numba_model, numba_plasma, estimators, vpacket_collection + ) + output_nus[i] = packet.nu + if packet.status == r_packet.PacketStatus.REABSORBED: + output_energies[i] = -packet.energy + elif packet.status == r_packet.PacketStatus.EMITTED: + output_energies[i] = packet.energy + + #RNG to match C + random_call_fixture() + + packet_collection.packets_output_energy[:] = output_energies[:] + packet_collection.packets_output_nu[:] = output_nus[:] + + actual_energy = packet_collection.packets_output_energy + actual_nu = packet_collection.packets_output_nu + actual_nu_bar_estimator = estimators.nu_bar_estimator + actual_j_estimator = estimators.j_estimator - npt.assert_array_almost_equal(actual_nu, expected_nu) - npt.assert_array_almost_equal(actual_energy, expected_energy) \ No newline at end of file + #Compare + npt.assert_allclose(actual_nu_bar_estimator, expected_nu_bar_estimator, rtol=1e-13) + npt.assert_allclose(actual_j_estimator, expected_j_estimator, rtol=1e-13) + npt.assert_allclose(actual_energy, expected_energy, rtol=1e-13) + npt.assert_allclose(actual_nu, expected_nu, rtol=1e-13) + \ No newline at end of file diff --git a/tardis/montecarlo/packet_source.py b/tardis/montecarlo/packet_source.py index 75d7f8d51c9..7a43cad66ba 100644 --- a/tardis/montecarlo/packet_source.py +++ b/tardis/montecarlo/packet_source.py @@ -3,6 +3,7 @@ import numpy as np import numexpr as ne from tardis import constants as const +from tardis.montecarlo import montecarlo_configuration as montecarlo_configuration class BasePacketSource(abc.ABC): @@ -27,7 +28,11 @@ def create_zero_limb_darkening_packet_mus(no_of_packets, rng): number of packets to be created """ - return np.sqrt(rng.random(no_of_packets)) + #For testing purposes + if montecarlo_configuration.LEGACY_MODE_ENABLED: + return np.sqrt(np.random.random(no_of_packets)) + else: + return np.sqrt(rng.random(no_of_packets)) @staticmethod def create_uniform_packet_energies(no_of_packets, rng): @@ -83,7 +88,12 @@ def create_blackbody_packet_nus(T, no_of_packets, rng, l_samples=1000): l_array = np.cumsum(np.arange(1, l_samples, dtype=np.float64) ** -4) l_coef = np.pi ** 4 / 90.0 - xis = rng.random((5, no_of_packets)) + #For testing purposes + if montecarlo_configuration.LEGACY_MODE_ENABLED: + xis = np.random.random((5, no_of_packets)) + else: + xis = rng.random((5, no_of_packets)) + l = l_array.searchsorted(xis[0] * l_coef) + 1. xis_prod = np.prod(xis[1:], 0) x = ne.evaluate('-log(xis_prod)/l') From f1953b4b27192325d2ce6bbb3bde2d1d8a8eb09b Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 4 Nov 2020 11:10:36 -0500 Subject: [PATCH 099/116] Cleaned out old fixtures --- .../montecarlo_numba/tests/test_base.py | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index d173d2abf3f..515629f0669 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -16,35 +16,13 @@ Estimators, configuration_initialize) -@pytest.fixture() -def read_c_test(tardis_ref_path): - mode = "r" - with pd.HDFStore( - os.path.join(tardis_ref_path, "montecarlo_one_packet_compare_data.h5"), mode=mode - ) as store: - yield store - - -@pytest.fixture() -def c_test_packet_collection(read_c_test): - input_data = read_c_test['/one_packet_loop'] - input_nu = input_data['input_nu'].values - input_mu = input_data['input_mu'].values - input_energy = input_data['input_energy'].values - output_nu = input_data['output_nu'].values - output_energy = input_data['output_energy'].values - return PacketCollection( - input_nu, input_mu, input_energy, - output_nu, output_energy - ) - @pytest.mark.xfail(reason='To be implemented') def test_montecarlo_radial1d(): assert False def test_montecarlo_main_loop( - config_verysimple, atomic_dataset, tardis_ref_path, tmpdir, set_seed_fixture, c_test_packet_collection, random_call_fixture + config_verysimple, atomic_dataset, tardis_ref_path, tmpdir, set_seed_fixture, random_call_fixture ): montecarlo_configuration.LEGACY_MODE_ENABLED = True From 6513d184e787e06d57c45a4d7d250255419d0123 Mon Sep 17 00:00:00 2001 From: andrewfullard Date: Wed, 11 Nov 2020 12:56:27 -0500 Subject: [PATCH 100/116] Vpacket logging partially restored Values are stored in the vpacket collection for each vpacket. Vpacket collections are dumped into a huge list right now. --- tardis/montecarlo/montecarlo_configuration.py | 2 +- tardis/montecarlo/montecarlo_numba/base.py | 5 ++ .../montecarlo_numba/interaction.py | 3 + .../montecarlo_numba/numba_interface.py | 62 +++++++++++++++++-- .../montecarlo/montecarlo_numba/r_packet.py | 8 ++- .../montecarlo_numba/tests/conftest.py | 10 +-- .../montecarlo_numba/tests/test_base.py | 2 +- .../tests/test_numba_interface.py | 26 +++++++- .../montecarlo_numba/tests/test_packet.py | 4 +- .../montecarlo_numba/tests/test_vpacket.py | 3 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 14 +++++ 11 files changed, 123 insertions(+), 16 deletions(-) diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py index 666e20818b8..9e9de230deb 100644 --- a/tardis/montecarlo/montecarlo_configuration.py +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -2,7 +2,7 @@ full_relativity = True single_packet_seed = -1 -temporary_v_packet_bins = None +temporary_v_packet_bins = 0 number_of_vpackets = 0 montecarlo_seed = 0 line_interaction_type = None diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 279fef307b8..296b51559af 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -57,6 +57,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] + v_packets_total = [] + print("Running post-merge numba montecarlo (with C close lines)!") for i in prange(len(output_nus)): if montecarlo_configuration.single_packet_seed != -1: @@ -72,6 +74,7 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, seed, i, 0) vpacket_collection = VPacketCollection( + r_packet.index, spectrum_frequency, montecarlo_configuration.v_packet_spawn_start_frequency, montecarlo_configuration.v_packet_spawn_end_frequency, @@ -105,6 +108,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, continue v_packets_energy_hist[idx] += vpackets_energy[j] + v_packets_total.append(vpacket_collection) + # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index d548f32ee5e..00bb4eba254 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -75,6 +75,9 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): @njit(**njit_dict) def line_emission(r_packet, emission_line_id, time_explosion, numba_plasma): + + r_packet.last_line_interaction_out_id = emission_line_id + if emission_line_id != r_packet.next_line_id: pass inverse_doppler_factor = get_inverse_doppler_factor(r_packet.r, r_packet.mu, diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 276f2ded585..f153d9dea1e 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -130,19 +130,25 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, self.packets_output_energy = packets_output_energy vpacket_collection_spec = [ + ('rpacket_index', int64), ('spectrum_frequency', float64[:]), ('v_packet_spawn_start_frequency', float64), ('v_packet_spawn_end_frequency', float64), ('nus', float64[:]), ('energies', float64[:]), ('idx', int64), - ('number_of_vpackets', int64) + ('number_of_vpackets', int64), + ('length', int64), + ('last_interaction_in_nu', float64[:]), + ('last_interaction_type', int64[:]), + ('last_interaction_in_id', int64[:]), + ('last_interaction_out_id', int64[:]), ] @jitclass(vpacket_collection_spec) class VPacketCollection(object): - def __init__(self, spectrum_frequency, + def __init__(self, rpacket_index, spectrum_frequency, v_packet_spawn_start_frequency, v_packet_spawn_end_frequency, number_of_vpackets, @@ -153,8 +159,55 @@ def __init__(self, spectrum_frequency, self.nus = np.empty(temporary_v_packet_bins, dtype=np.float64) self.energies = np.empty(temporary_v_packet_bins, dtype=np.float64) self.number_of_vpackets = number_of_vpackets + self.last_interaction_in_nu = np.empty(temporary_v_packet_bins, dtype=np.float64) + self.last_interaction_type = np.empty(temporary_v_packet_bins, dtype=np.int64) + self.last_interaction_in_id = np.empty(temporary_v_packet_bins, dtype=np.int64) + self.last_interaction_out_id = np.empty(temporary_v_packet_bins, dtype=np.int64) self.idx = 0 + self.rpacket_index = rpacket_index + self.length = temporary_v_packet_bins + + def set_properties(self, + nu, + energy, + last_interaction_in_nu, + last_interaction_type, + last_interaction_in_id, + last_interaction_out_id, + ): + if self.idx >= self.length: + temp_length = self.length * 2 + self.number_of_vpackets + temp_nus = np.empty(temp_length, dtype=np.float64) + temp_energies = np.empty(temp_length, dtype=np.float64) + temp_nus[:self.length] = self.nus + temp_energies[:self.length] = self.energies + + temp_last_interaction_in_nu = np.empty(temp_length, dtype=np.float64) + temp_last_interaction_type = np.empty(temp_length, dtype=np.int64) + temp_last_interaction_in_id = np.empty(temp_length, dtype=np.int64) + temp_last_interaction_out_id = np.empty(temp_length, dtype=np.int64) + + temp_last_interaction_in_nu[:self.length] = self.last_interaction_in_nu + temp_last_interaction_type[:self.length] = self.last_interaction_type + temp_last_interaction_in_id[:self.length] = self.last_interaction_in_id + temp_last_interaction_out_id[:self.length] = self.last_interaction_out_id + + self.nus = temp_nus + self.energies = temp_energies + self.last_interaction_in_nu = temp_last_interaction_in_nu + self.last_interaction_type = temp_last_interaction_type + self.last_interaction_in_id = temp_last_interaction_in_id + self.last_interaction_out_id = temp_last_interaction_out_id + self.length = temp_length + self.nus[self.idx] = nu + self.energies[self.idx] = energy + self.last_interaction_type[self.idx] = last_interaction_type + self.last_interaction_in_nu[self.idx] = last_interaction_in_nu + self.last_interaction_in_id[self.idx] = last_interaction_in_id + self.last_interaction_out_id[self.idx] = last_interaction_out_id + self.idx += 1 + estimators_spec = [ ('j_estimator', float64[:]), @@ -173,8 +226,7 @@ def __init__(self, j_estimator, nu_bar_estimator, j_blue_estimator, self.Edotlu_estimator = Edotlu_estimator -def configuration_initialize(runner, number_of_vpackets, - temporary_v_packet_bins=20000): +def configuration_initialize(runner, number_of_vpackets): if runner.line_interaction_type == 'macroatom': montecarlo_configuration.line_interaction_type = LineInteractionType.MACROATOM elif runner.line_interaction_type == 'downbranch': @@ -186,7 +238,7 @@ def configuration_initialize(runner, number_of_vpackets, f'"downbranch", or "scatter" but is ' f'{runner.line_interaction_type}') montecarlo_configuration.number_of_vpackets = number_of_vpackets - montecarlo_configuration.temporary_v_packet_bins = temporary_v_packet_bins + montecarlo_configuration.temporary_v_packet_bins = number_of_vpackets montecarlo_configuration.full_relativity = runner.enable_full_relativity montecarlo_configuration.montecarlo_seed = runner.seed montecarlo_configuration.single_packet_seed = runner.single_packet_seed diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 22de7e1f359..8eac5e21964 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -36,7 +36,9 @@ class InteractionType(IntEnum): ('status', int64), ('seed', int64), ('index', int64), - ('is_close_line', boolean) + ('is_close_line', boolean), + ('last_interaction_type', int64), + ('last_line_interaction_out_id', int64) ] @jitclass(rpacket_spec) class RPacket(object): @@ -50,6 +52,8 @@ def __init__(self, r, mu, nu, energy, seed, index=0, is_close_line=False): self.seed = seed self.index = index self.is_close_line = is_close_line + self.last_interaction_type = -1 + self.last_line_interaction_out_id = -1 def initialize_line_id(self, numba_plasma, numba_model): inverse_line_list_nu = numba_plasma.line_list_nu[::-1] @@ -377,6 +381,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): #r_packet.next_line_id = cur_line_id + r_packet.last_interaction_type = int(interaction_type) + return distance, interaction_type, delta_shell diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index d6c0f22f96d..041a7e6159d 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -55,21 +55,23 @@ def verysimple_estimators(nb_simulation_verysimple): @pytest.fixture(scope='package') def verysimple_vpacket_collection(nb_simulation_verysimple): spectrum_frequency = nb_simulation_verysimple.runner.spectrum_frequency.value - return VPacketCollection(spectrum_frequency=spectrum_frequency, + return VPacketCollection(rpacket_index=0, + spectrum_frequency=spectrum_frequency, number_of_vpackets=0, v_packet_spawn_start_frequency=0, v_packet_spawn_end_frequency=np.inf, - temporary_v_packet_bins=20000) + temporary_v_packet_bins=0) @pytest.fixture(scope='package') def verysimple_3vpacket_collection(nb_simulation_verysimple): spectrum_frequency = nb_simulation_verysimple.runner.spectrum_frequency.value - return VPacketCollection(spectrum_frequency=spectrum_frequency, + return VPacketCollection(rpacket_index=0, + spectrum_frequency=spectrum_frequency, number_of_vpackets=3, v_packet_spawn_start_frequency=0, v_packet_spawn_end_frequency=np.inf, - temporary_v_packet_bins=20000) + temporary_v_packet_bins=0) @pytest.fixture(scope='package') diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_base.py b/tardis/montecarlo/montecarlo_numba/tests/test_base.py index 515629f0669..2b469113958 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_base.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_base.py @@ -81,7 +81,7 @@ def test_montecarlo_main_loop( #Empty vpacket collection vpacket_collection = VPacketCollection( - np.array([0, 0], dtype=np.float64), 0, np.inf, + 0, np.array([0, 0], dtype=np.float64), 0, np.inf, 0, 0 ) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py index bf0f56c4f50..646972b5f45 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py @@ -38,4 +38,28 @@ def test_numba_plasma_initialize(nb_simulation_verysimple, input_params): @pytest.mark.xfail(reason='To be implemented') def test_configuration_initialize(): - assert False \ No newline at end of file + assert False + + +def test_VPacketCollection_set_properties(verysimple_3vpacket_collection): + + assert verysimple_3vpacket_collection.length == 0 + + nus = [3.e15, 0.0, 1e15, 1e5] + energies = [0.4, 0.1, 0.6, 1e10] + last_interaction_in_nu = [3.e15, 0.0, 1e15, 1e5] + last_interaction_type = [1, 2, 3, 3] + last_interaction_in_id = [100, 0, 5545, 1632] + last_interaction_out_id = [1201, 0, 4446, 894] + + for (nu, energy, in_nu, int_type, in_id, out_id) in \ + zip(nus, energies, last_interaction_in_nu, last_interaction_type, last_interaction_in_id, last_interaction_out_id): + verysimple_3vpacket_collection.set_properties(nu, energy, in_nu, int_type, in_id, out_id) + + npt.assert_array_equal(verysimple_3vpacket_collection.nus[:verysimple_3vpacket_collection.idx], nus) + npt.assert_array_equal(verysimple_3vpacket_collection.energies[:verysimple_3vpacket_collection.idx], energies) + npt.assert_array_equal(verysimple_3vpacket_collection.last_interaction_in_nu[:verysimple_3vpacket_collection.idx], last_interaction_in_nu) + npt.assert_array_equal(verysimple_3vpacket_collection.last_interaction_type[:verysimple_3vpacket_collection.idx], last_interaction_type) + npt.assert_array_equal(verysimple_3vpacket_collection.last_interaction_in_id[:verysimple_3vpacket_collection.idx], last_interaction_in_id) + npt.assert_array_equal(verysimple_3vpacket_collection.last_interaction_out_id[:verysimple_3vpacket_collection.idx], last_interaction_out_id) + assert verysimple_3vpacket_collection.length == 9 \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index b6c57cb6f1c..9964210a7be 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -208,8 +208,8 @@ def test_trace_packet(packet, verysimple_numba_model, verysimple_numba_plasma, verysimple_numba_plasma, verysimple_estimators) assert delta_shell == 1 - assert interaction_type == 1 - assert_almost_equal(distance, 581086681128631.8) + assert interaction_type == 3 + assert_almost_equal(distance, 23104129179414.58) @pytest.mark.xfail(reason='bug in full relativity') diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index 507029b82f4..e0ee4de2ea1 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -69,7 +69,8 @@ def test_trace_vpacket(v_packet, verysimple_numba_model, verysimple_numba_plasma assert v_packet.is_close_line == False assert v_packet.current_shell_id == 1 -@pytest.mark.xfail(reason='To be implemented') +#NEEDS TO TEST VPACKET COLLECTION OVERFLOW +@pytest.mark.xfail(reason="Needs to be implemented") def test_trace_vpacket_volley( packet, verysimple_packet_collection, diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index b9c1f207961..e8897122268 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -224,6 +224,20 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, v_packet.energy *= math.exp(-tau_vpacket) + vpacket_collection.set_properties( + v_packet.nu, + v_packet.energy, + r_packet.nu, + r_packet.last_interaction_type, + r_packet.next_line_id, + r_packet.last_line_interaction_out_id + ) + + """ + assert vpacket_collection.idx < montecarlo_configuration.temporary_v_packet_bins, \ + "ERROR: vpacket collection overflowed!" + vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy vpacket_collection.idx += 1 + """ From b69986d72922e57041872fdd40c4551e2e7df9af Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Wed, 11 Nov 2020 17:27:11 -0500 Subject: [PATCH 101/116] Logging into HDF, probably Need to set the configuration value to True in montecarlo_configuration.py Needs to be an install option again --- tardis/io/util.py | 2 +- tardis/montecarlo/base.py | 8 +++- tardis/montecarlo/montecarlo_configuration.py | 3 +- tardis/montecarlo/montecarlo_numba/base.py | 37 ++++++++++++++++--- .../montecarlo/montecarlo_numba/r_packet.py | 2 +- .../montecarlo_numba/tests/test_packet.py | 1 + .../tests/test_single_packet_loop.py | 2 +- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/tardis/io/util.py b/tardis/io/util.py index b75253d7830..7430b0d7850 100644 --- a/tardis/io/util.py +++ b/tardis/io/util.py @@ -284,7 +284,7 @@ def get_properties(self): @property def full_hdf_properties(self): # If tardis was compiled --with-vpacket-logging, add vpacket properties - if hasattr(self, "virt_logging") and self.virt_logging == 1: + if hasattr(self, "virt_logging") and self.virt_logging: self.hdf_properties.extend(self.vpacket_hdf_properties) return self.optional_hdf_properties + self.hdf_properties diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index 44b95089f10..e4caa266711 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -31,7 +31,7 @@ # MAX_SEED_VAL must be multiple orders of magnitude larger than no_of_packets; # otherwise, each packet would not have its own seed. Here, we set the max # seed val to the maximum allowed by numpy. - +# TODO: refactor this into more parts class MontecarloRunner(HDFWriterMixin): """ This class is designed as an interface between the Python part and the @@ -90,6 +90,12 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, self.seed = seed self._integrator = None self._spectrum_integrated = None + + self.virt_logging = False + self.virt_packet_last_interaction_type = np.ones(1) * -1 + self.virt_packet_last_interaction_in_nu = np.ones(1) * -1 + self.virt_packet_last_line_interaction_in_id = np.ones(1) * -1 + self.virt_packet_last_line_interaction_out_id = np.ones(1) * -1 # set up logger based on config mc_logger.DEBUG_MODE = debug_packets diff --git a/tardis/montecarlo/montecarlo_configuration.py b/tardis/montecarlo/montecarlo_configuration.py index 9e9de230deb..91ae8fff571 100644 --- a/tardis/montecarlo/montecarlo_configuration.py +++ b/tardis/montecarlo/montecarlo_configuration.py @@ -11,4 +11,5 @@ disable_line_scattering = False survival_probability = 0.0 tau_russian = 10.0 -LEGACY_MODE_ENABLED = False \ No newline at end of file +LEGACY_MODE_ENABLED = False +VPACKET_LOGGING = False \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 296b51559af..9db1528b863 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -30,12 +30,25 @@ def montecarlo_radial1d(model, plasma, runner): number_of_vpackets = montecarlo_configuration.number_of_vpackets - v_packets_energy_hist, last_interaction_type = montecarlo_main_loop( - packet_collection, numba_model, numba_plasma, estimators, - runner.spectrum_frequency.value, number_of_vpackets, packet_seeds) + v_packets_energy_hist, \ + last_interaction_type, \ + virt_packet_last_interaction_in_nu, \ + virt_packet_last_interaction_type, \ + virt_packet_last_line_interaction_in_id, \ + virt_packet_last_line_interaction_out_id \ + = montecarlo_main_loop( + packet_collection, numba_model, numba_plasma, estimators, + runner.spectrum_frequency.value, number_of_vpackets, packet_seeds) + runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist runner.last_interaction_type = last_interaction_type + if montecarlo_configuration.VPACKET_LOGGING: + runner.virt_packet_last_interaction_in_nu = virt_packet_last_interaction_in_nu + runner.virt_packet_last_interaction_type = virt_packet_last_interaction_type + runner.virt_packet_last_line_interaction_in_id = virt_packet_last_line_interaction_in_id + runner.virt_packet_last_line_interaction_out_id = virt_packet_last_line_interaction_out_id + @njit(**njit_dict, nogil=True) def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, @@ -57,7 +70,10 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] - v_packets_total = [] + virt_packet_last_interaction_in_nu = [] + virt_packet_last_interaction_type = [] + virt_packet_last_line_interaction_in_id = [] + virt_packet_last_line_interaction_out_id = [] print("Running post-merge numba montecarlo (with C close lines)!") for i in prange(len(output_nus)): @@ -108,10 +124,19 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, continue v_packets_energy_hist[idx] += vpackets_energy[j] - v_packets_total.append(vpacket_collection) + if montecarlo_configuration.VPACKET_LOGGING: + virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu) + virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type) + virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id) + virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id) # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] - return v_packets_energy_hist, last_interaction_types + return v_packets_energy_hist, \ + last_interaction_types, \ + virt_packet_last_interaction_in_nu, \ + virt_packet_last_interaction_type, \ + virt_packet_last_line_interaction_in_id, \ + virt_packet_last_line_interaction_out_id \ No newline at end of file diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 8eac5e21964..692a89ede69 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -381,7 +381,7 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): #r_packet.next_line_id = cur_line_id - r_packet.last_interaction_type = int(interaction_type) + r_packet.last_interaction_type = interaction_type return distance, interaction_type, delta_shell diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 9964210a7be..b8a3f5859ee 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -199,6 +199,7 @@ def test_update_line_estimators(estimators, static_packet, cur_line_id, distance assert_allclose(estimators.Edotlu_estimator, expected_Edotlu) #@pytest.mark.xfail(reason='To be implemented') +#TODO set RNG consistently def test_trace_packet(packet, verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, set_seed_fixture): diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py index 3e69f7a86fa..634f6a21225 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -7,7 +7,7 @@ single_packet_loop) - +#TODO set RNG consistently def test_verysimple_single_packet_loop(verysimple_numba_model, verysimple_numba_plasma, verysimple_estimators, From dba60f3df3418b3f993d0ab3f7455e6bbb0fb4f6 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Thu, 12 Nov 2020 14:41:00 -0500 Subject: [PATCH 102/116] Improved virtual packet logging Can now be activated as a configuration settings rather than compile-time. Output should now be a proper array of arrays rather than a list of arrays. --- tardis/io/schemas/spectrum.yml | 4 ++++ tardis/montecarlo/base.py | 20 +++++++++-------- tardis/montecarlo/montecarlo_numba/base.py | 22 ++++++++++++++----- .../montecarlo_numba/numba_interface.py | 1 + 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/tardis/io/schemas/spectrum.yml b/tardis/io/schemas/spectrum.yml index 8439e75c041..96c9e1534ee 100644 --- a/tardis/io/schemas/spectrum.yml +++ b/tardis/io/schemas/spectrum.yml @@ -50,3 +50,7 @@ properties: default: False description: If True bias v-packet emission based on the electron scattering optical depth + virtual_packet_logging: + type: boolean + default: False + description: If True, enable virtual packet logging output diff --git a/tardis/montecarlo/base.py b/tardis/montecarlo/base.py index e4caa266711..d0bac7194bc 100644 --- a/tardis/montecarlo/base.py +++ b/tardis/montecarlo/base.py @@ -65,7 +65,7 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, disable_electron_scattering, enable_reflective_inner_boundary, enable_full_relativity, inner_boundary_albedo, line_interaction_type, integrator_settings, - v_packet_settings, spectrum_method, + v_packet_settings, spectrum_method, virtual_packet_logging, packet_source=None, debug_packets=False, logger_buffer=1, single_packet_seed=None): @@ -91,11 +91,13 @@ def __init__(self, seed, spectrum_frequency, virtual_spectrum_spawn_range, self._integrator = None self._spectrum_integrated = None - self.virt_logging = False - self.virt_packet_last_interaction_type = np.ones(1) * -1 - self.virt_packet_last_interaction_in_nu = np.ones(1) * -1 - self.virt_packet_last_line_interaction_in_id = np.ones(1) * -1 - self.virt_packet_last_line_interaction_out_id = np.ones(1) * -1 + self.virt_logging = virtual_packet_logging + self.virt_packet_last_interaction_type = np.ones(2) * -1 + self.virt_packet_last_interaction_in_nu = np.ones(2) * -1.0 + self.virt_packet_last_line_interaction_in_id = np.ones(2) * -1 + self.virt_packet_last_line_interaction_out_id = np.ones(2) * -1 + self.virt_packet_nus = np.ones(2) * -1.0 + self.virt_packet_energies = np.ones(2) * -1.0 # set up logger based on config mc_logger.DEBUG_MODE = debug_packets @@ -257,8 +259,7 @@ def run(self, model, plasma, no_of_packets, self._initialize_packets(model.t_inner.value, no_of_packets, iteration) - montecarlo_configuration = configuration_initialize(self, - no_of_virtual_packets) + configuration_initialize(self, no_of_virtual_packets) montecarlo_radial1d(model, plasma, self) #montecarlo.montecarlo_radial1d( # model, plasma, self, @@ -487,4 +488,5 @@ def from_config(cls, config, packet_source=None): packet_source=packet_source, debug_packets=config.montecarlo.debug_packets, logger_buffer=config.montecarlo.logger_buffer, - single_packet_seed=config.montecarlo.single_packet_seed) + single_packet_seed=config.montecarlo.single_packet_seed, + virtual_packet_logging=config.spectrum.virtual.virtual_packet_logging) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 9db1528b863..aa03d51329e 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -32,6 +32,8 @@ def montecarlo_radial1d(model, plasma, runner): v_packets_energy_hist, \ last_interaction_type, \ + virt_packet_nus, \ + virt_packet_energies, \ virt_packet_last_interaction_in_nu, \ virt_packet_last_interaction_type, \ virt_packet_last_line_interaction_in_id, \ @@ -43,11 +45,13 @@ def montecarlo_radial1d(model, plasma, runner): runner._montecarlo_virtual_luminosity.value[:] = v_packets_energy_hist runner.last_interaction_type = last_interaction_type - if montecarlo_configuration.VPACKET_LOGGING: - runner.virt_packet_last_interaction_in_nu = virt_packet_last_interaction_in_nu - runner.virt_packet_last_interaction_type = virt_packet_last_interaction_type - runner.virt_packet_last_line_interaction_in_id = virt_packet_last_line_interaction_in_id - runner.virt_packet_last_line_interaction_out_id = virt_packet_last_line_interaction_out_id + if montecarlo_configuration.VPACKET_LOGGING and number_of_vpackets > 0: + runner.virt_packet_nus = np.array(virt_packet_nus) + runner.virt_packet_energies = np.array(virt_packet_energies) + runner.virt_packet_last_interaction_in_nu = np.array(virt_packet_last_interaction_in_nu) + runner.virt_packet_last_interaction_type = np.array(virt_packet_last_interaction_type) + runner.virt_packet_last_line_interaction_in_id = np.array(virt_packet_last_line_interaction_in_id) + runner.virt_packet_last_line_interaction_out_id = np.array(virt_packet_last_line_interaction_out_id) @njit(**njit_dict, nogil=True) @@ -70,6 +74,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist = np.zeros_like(spectrum_frequency) delta_nu = spectrum_frequency[1] - spectrum_frequency[0] + virt_packet_nus = [] + virt_packet_energies = [] virt_packet_last_interaction_in_nu = [] virt_packet_last_interaction_type = [] virt_packet_last_line_interaction_in_id = [] @@ -125,6 +131,8 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist[idx] += vpackets_energy[j] if montecarlo_configuration.VPACKET_LOGGING: + virt_packet_nus.append(vpacket_collection.nus) + virt_packet_energies.append(vpacket_collection.energies) virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu) virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type) virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id) @@ -136,7 +144,9 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, return v_packets_energy_hist, \ last_interaction_types, \ + virt_packet_nus, \ + virt_packet_energies, \ virt_packet_last_interaction_in_nu, \ virt_packet_last_interaction_type, \ virt_packet_last_line_interaction_in_id, \ - virt_packet_last_line_interaction_out_id \ No newline at end of file + virt_packet_last_line_interaction_out_id diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index f153d9dea1e..d060d254954 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -248,6 +248,7 @@ def configuration_initialize(runner, number_of_vpackets): montecarlo_configuration.v_packet_spawn_end_frequency = runner.virtual_spectrum_spawn_range.start.to( u.Hz, equivalencies=u.spectral() ).value + montecarlo_configuration.VPACKET_LOGGING = runner.virt_logging #class TrackRPacket(object): From e68ce56a5b5f0a42fef85674bd1973a9c12d90e9 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 13 Nov 2020 12:02:35 -0500 Subject: [PATCH 103/116] Only add vpackets that have data to the log --- tardis/montecarlo/montecarlo_numba/base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index aa03d51329e..29c992814ac 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -131,12 +131,12 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, v_packets_energy_hist[idx] += vpackets_energy[j] if montecarlo_configuration.VPACKET_LOGGING: - virt_packet_nus.append(vpacket_collection.nus) - virt_packet_energies.append(vpacket_collection.energies) - virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu) - virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type) - virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id) - virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id) + virt_packet_nus.append(vpacket_collection.nus[:vpacket_collection.idx]) + virt_packet_energies.append(vpacket_collection.energies[:vpacket_collection.idx]) + virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu[:vpacket_collection.idx]) + virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type[:vpacket_collection.idx]) + virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id[:vpacket_collection.idx]) + virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id[:vpacket_collection.idx]) # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] From b72ed62f70b8f3d358bcda305eb80f478a6b4545 Mon Sep 17 00:00:00 2001 From: andrewfullard Date: Fri, 13 Nov 2020 14:17:06 -0500 Subject: [PATCH 104/116] Simplified output of vpacket logging --- tardis/montecarlo/montecarlo_numba/base.py | 12 ++++---- .../montecarlo_numba/numba_interface.py | 30 +++++-------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 29c992814ac..5a3f8736be6 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -46,12 +46,12 @@ def montecarlo_radial1d(model, plasma, runner): runner.last_interaction_type = last_interaction_type if montecarlo_configuration.VPACKET_LOGGING and number_of_vpackets > 0: - runner.virt_packet_nus = np.array(virt_packet_nus) - runner.virt_packet_energies = np.array(virt_packet_energies) - runner.virt_packet_last_interaction_in_nu = np.array(virt_packet_last_interaction_in_nu) - runner.virt_packet_last_interaction_type = np.array(virt_packet_last_interaction_type) - runner.virt_packet_last_line_interaction_in_id = np.array(virt_packet_last_line_interaction_in_id) - runner.virt_packet_last_line_interaction_out_id = np.array(virt_packet_last_line_interaction_out_id) + runner.virt_packet_nus = np.concatenate(np.array(virt_packet_nus)).ravel() + runner.virt_packet_energies = np.concatenate(np.array(virt_packet_energies)).ravel() + runner.virt_packet_last_interaction_in_nu = np.concatenate(np.array(virt_packet_last_interaction_in_nu)).ravel() + runner.virt_packet_last_interaction_type = np.concatenate(np.array(virt_packet_last_interaction_type)).ravel() + runner.virt_packet_last_line_interaction_in_id = np.concatenate(np.array(virt_packet_last_line_interaction_in_id)).ravel() + runner.virt_packet_last_line_interaction_out_id = np.concatenate(np.array(virt_packet_last_line_interaction_out_id)).ravel() @njit(**njit_dict, nogil=True) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index d060d254954..4cc06500373 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -159,10 +159,10 @@ def __init__(self, rpacket_index, spectrum_frequency, self.nus = np.empty(temporary_v_packet_bins, dtype=np.float64) self.energies = np.empty(temporary_v_packet_bins, dtype=np.float64) self.number_of_vpackets = number_of_vpackets - self.last_interaction_in_nu = np.empty(temporary_v_packet_bins, dtype=np.float64) - self.last_interaction_type = np.empty(temporary_v_packet_bins, dtype=np.int64) - self.last_interaction_in_id = np.empty(temporary_v_packet_bins, dtype=np.int64) - self.last_interaction_out_id = np.empty(temporary_v_packet_bins, dtype=np.int64) + self.last_interaction_in_nu = 0.0 + self.last_interaction_type = -1 + self.last_interaction_in_id = -1 + self.last_interaction_out_id = -1 self.idx = 0 self.rpacket_index = rpacket_index self.length = temporary_v_packet_bins @@ -182,30 +182,16 @@ def set_properties(self, temp_nus[:self.length] = self.nus temp_energies[:self.length] = self.energies - temp_last_interaction_in_nu = np.empty(temp_length, dtype=np.float64) - temp_last_interaction_type = np.empty(temp_length, dtype=np.int64) - temp_last_interaction_in_id = np.empty(temp_length, dtype=np.int64) - temp_last_interaction_out_id = np.empty(temp_length, dtype=np.int64) - - temp_last_interaction_in_nu[:self.length] = self.last_interaction_in_nu - temp_last_interaction_type[:self.length] = self.last_interaction_type - temp_last_interaction_in_id[:self.length] = self.last_interaction_in_id - temp_last_interaction_out_id[:self.length] = self.last_interaction_out_id - self.nus = temp_nus self.energies = temp_energies - self.last_interaction_in_nu = temp_last_interaction_in_nu - self.last_interaction_type = temp_last_interaction_type - self.last_interaction_in_id = temp_last_interaction_in_id - self.last_interaction_out_id = temp_last_interaction_out_id self.length = temp_length self.nus[self.idx] = nu self.energies[self.idx] = energy - self.last_interaction_type[self.idx] = last_interaction_type - self.last_interaction_in_nu[self.idx] = last_interaction_in_nu - self.last_interaction_in_id[self.idx] = last_interaction_in_id - self.last_interaction_out_id[self.idx] = last_interaction_out_id + self.last_interaction_type = last_interaction_type + self.last_interaction_in_nu = last_interaction_in_nu + self.last_interaction_in_id = last_interaction_in_id + self.last_interaction_out_id = last_interaction_out_id self.idx += 1 From 4458145d762e9dafc677d83b26455b6a52a9f4eb Mon Sep 17 00:00:00 2001 From: andrewfullard Date: Fri, 13 Nov 2020 14:18:37 -0500 Subject: [PATCH 105/116] Fix for 1D arrays --- tardis/montecarlo/montecarlo_numba/base.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index 5a3f8736be6..f0bdd633a72 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -48,10 +48,10 @@ def montecarlo_radial1d(model, plasma, runner): if montecarlo_configuration.VPACKET_LOGGING and number_of_vpackets > 0: runner.virt_packet_nus = np.concatenate(np.array(virt_packet_nus)).ravel() runner.virt_packet_energies = np.concatenate(np.array(virt_packet_energies)).ravel() - runner.virt_packet_last_interaction_in_nu = np.concatenate(np.array(virt_packet_last_interaction_in_nu)).ravel() - runner.virt_packet_last_interaction_type = np.concatenate(np.array(virt_packet_last_interaction_type)).ravel() - runner.virt_packet_last_line_interaction_in_id = np.concatenate(np.array(virt_packet_last_line_interaction_in_id)).ravel() - runner.virt_packet_last_line_interaction_out_id = np.concatenate(np.array(virt_packet_last_line_interaction_out_id)).ravel() + runner.virt_packet_last_interaction_in_nu = np.array(virt_packet_last_interaction_in_nu) + runner.virt_packet_last_interaction_type = np.array(virt_packet_last_interaction_type) + runner.virt_packet_last_line_interaction_in_id = np.array(virt_packet_last_line_interaction_in_id) + runner.virt_packet_last_line_interaction_out_id = np.array(virt_packet_last_line_interaction_out_id) @njit(**njit_dict, nogil=True) @@ -133,10 +133,10 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, if montecarlo_configuration.VPACKET_LOGGING: virt_packet_nus.append(vpacket_collection.nus[:vpacket_collection.idx]) virt_packet_energies.append(vpacket_collection.energies[:vpacket_collection.idx]) - virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu[:vpacket_collection.idx]) - virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type[:vpacket_collection.idx]) - virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id[:vpacket_collection.idx]) - virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id[:vpacket_collection.idx]) + virt_packet_last_interaction_in_nu.append(vpacket_collection.last_interaction_in_nu) + virt_packet_last_interaction_type.append(vpacket_collection.last_interaction_type) + virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id) + virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id) # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] From 884c55ae0be8122e37ac57dcaffc39efbeacccd0 Mon Sep 17 00:00:00 2001 From: andrewfullard Date: Fri, 13 Nov 2020 14:23:10 -0500 Subject: [PATCH 106/116] Fix Vpacket collection spec --- tardis/montecarlo/montecarlo_numba/numba_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 4cc06500373..264dab009df 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -139,10 +139,10 @@ def __init__(self, packets_input_nu, packets_input_mu, packets_input_energy, ('idx', int64), ('number_of_vpackets', int64), ('length', int64), - ('last_interaction_in_nu', float64[:]), - ('last_interaction_type', int64[:]), - ('last_interaction_in_id', int64[:]), - ('last_interaction_out_id', int64[:]), + ('last_interaction_in_nu', float64), + ('last_interaction_type', int64), + ('last_interaction_in_id', int64), + ('last_interaction_out_id', int64), ] From 900cd818c4d38a044c46c9424840bc9055dafbb7 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 13 Nov 2020 16:25:01 -0500 Subject: [PATCH 107/116] Static seed for random test --- tardis/montecarlo/montecarlo_numba/tests/test_packet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index b8a3f5859ee..6914c6126af 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -167,13 +167,14 @@ def test_get_inverse_doppler_factor(mu, r, inv_t_exp, expected): assert_almost_equal(obtained, expected) -def test_get_random_mu(): +def test_get_random_mu(set_seed_fixture): """ Ensure that different calls results """ + set_seed_fixture(1963) + output1 = r_packet.get_random_mu() - output2 = r_packet.get_random_mu() - assert output1 != output2 + assert output1 == 0.9136407866175174 @pytest.mark.parametrize( From dee956b4912e12482453bec19189747305297cf1 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 13 Nov 2020 17:37:41 -0500 Subject: [PATCH 108/116] Apply Wolfgang's suggestions from code review Docstrings from Wolfgang Co-authored-by: Wolfgang Kerzendorf --- tardis/montecarlo/montecarlo_numba/base.py | 11 +- .../montecarlo_numba/interaction.py | 32 ++++-- .../montecarlo_numba/numba_interface.py | 26 ++++- .../montecarlo/montecarlo_numba/r_packet.py | 102 +++++++++++------- .../montecarlo_numba/tests/conftest.py | 5 +- .../montecarlo_numba/tests/test_packet.py | 13 --- .../tests/test_single_packet_loop.py | 3 +- .../montecarlo_numba/tests/test_vpacket.py | 3 +- tardis/montecarlo/montecarlo_numba/vpacket.py | 13 +-- tardis/scripts/debug/run_numba_single.run.xml | 2 +- 10 files changed, 127 insertions(+), 83 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/base.py b/tardis/montecarlo/montecarlo_numba/base.py index f0bdd633a72..7f3cfe52a01 100644 --- a/tardis/montecarlo/montecarlo_numba/base.py +++ b/tardis/montecarlo/montecarlo_numba/base.py @@ -64,8 +64,14 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, Parameters ---------- - storage_model : [type] - [description] + packet_collection: PacketCollection + numba_model: NumbaModel + estimators: NumbaEstimators + spectrum_frequency: astropy.units.Quantity + frequency bins + number_of_vpackets: int + VPackets released per interaction + packet_seeds: numpy.array """ output_nus = np.empty_like(packet_collection.packets_output_nu) last_interaction_types = np.ones_like(packet_collection.packets_output_nu) * -1 @@ -138,7 +144,6 @@ def montecarlo_main_loop(packet_collection, numba_model, numba_plasma, virt_packet_last_line_interaction_in_id.append(vpacket_collection.last_interaction_in_id) virt_packet_last_line_interaction_out_id.append(vpacket_collection.last_interaction_out_id) - # np.savetxt('scatter_output_energy.txt', output_energies) packet_collection.packets_output_energy[:] = output_energies[:] packet_collection.packets_output_nu[:] = output_nus[:] diff --git a/tardis/montecarlo/montecarlo_numba/interaction.py b/tardis/montecarlo/montecarlo_numba/interaction.py index 00bb4eba254..dabe92cac6d 100644 --- a/tardis/montecarlo/montecarlo_numba/interaction.py +++ b/tardis/montecarlo/montecarlo_numba/interaction.py @@ -21,8 +21,10 @@ def thomson_scatter(r_packet, time_explosion): Parameters ---------- - distance : [type] - [description] + r_packet : RPacket + time_explosion: float + time since explosion in seconds + """ old_doppler_factor = get_doppler_factor( r_packet.r, @@ -46,12 +48,13 @@ def thomson_scatter(r_packet, time_explosion): @njit(**njit_dict) def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): - #increment_j_blue_estimator(packet, storage, distance, line2d_idx); - #increment_Edotlu_estimator(packet, storage, distance, line2d_idx); - - # do_electron_scatter = False - # general_scatter(r_packet, time_explosion, do_electron_scatter) - # update last_interaction + """ + Line scatter function that handles the scattering itself, including new angle drawn, and calculating nu out using macro atom + r_packet: RPacket + time_explosion: float + line_interaction_type: enum + numba_plasma: NumbaPlasma + """ old_doppler_factor = get_doppler_factor(r_packet.r, r_packet.mu, @@ -75,7 +78,17 @@ def line_scatter(r_packet, time_explosion, line_interaction_type, numba_plasma): @njit(**njit_dict) def line_emission(r_packet, emission_line_id, time_explosion, numba_plasma): - + """ + Sets the frequency of the RPacket properly given the emission channel + + Parameters + ----------- + + r_packet: RPacket + emission_line_id: int + time_explosion: float + numba_plasma: NumbaPlasma + """ r_packet.last_line_interaction_out_id = emission_line_id if emission_line_id != r_packet.next_line_id: @@ -99,4 +112,3 @@ def line_emission(r_packet, emission_line_id, time_explosion, - diff --git a/tardis/montecarlo/montecarlo_numba/numba_interface.py b/tardis/montecarlo/montecarlo_numba/numba_interface.py index 264dab009df..3fc4e72cbb9 100644 --- a/tardis/montecarlo/montecarlo_numba/numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/numba_interface.py @@ -56,7 +56,20 @@ def __init__(self, electron_density, line_list_nu, tau_sobolev, transition_probabilities, line2macro_level_upper, macro_block_references, transition_type, destination_level_id, transition_line_id): - + """ + Plasma for the Numba code + Parameters + ---------- + + electron_density: numpy.array + line_list_nu: numpy.array + tau_sobolev: numpy.array + transition_probabilities: numpy.array + line2macro_level_upper: numpy.array + macro_block_references: numpy.array + transition_type: numpy.array + destination_level_id: numpy.array + transition_line_id: numpy.array self.electron_density = electron_density self.line_list_nu = line_list_nu self.tau_sobolev = tau_sobolev @@ -74,6 +87,15 @@ def __init__(self, electron_density, line_list_nu, tau_sobolev, def numba_plasma_initialize(plasma, line_interaction_type): + """ + Initialize the NumbaPlasma object and copy over the data over from TARDIS Plasma + + Parameters + ----------- + + plasma: tardis.plasma.BasePlasma + line_interaction_type: enum + """ electron_densities = plasma.electron_densities.values line_list_nu = plasma.atomic_data.lines.nu.values tau_sobolev = np.ascontiguousarray(plasma.tau_sobolevs.values.copy(), @@ -241,4 +263,4 @@ def configuration_initialize(runner, number_of_vpackets): class LineInteractionType(IntEnum): SCATTER = 0 DOWNBRANCH = 1 - MACROATOM = 2 \ No newline at end of file + MACROATOM = 2 diff --git a/tardis/montecarlo/montecarlo_numba/r_packet.py b/tardis/montecarlo/montecarlo_numba/r_packet.py index 692a89ede69..4e25d391d4b 100644 --- a/tardis/montecarlo/montecarlo_numba/r_packet.py +++ b/tardis/montecarlo/montecarlo_numba/r_packet.py @@ -68,6 +68,22 @@ def initialize_line_id(self, numba_plasma, numba_model): @njit(**njit_dict) def calculate_distance_boundary(r, mu, r_inner, r_outer): + """ + Calculate distance to shell boundary in cm. + + Parameters: + ------------ + + r: float + radial coordinate of the RPacket + mu: float + cosine of the direction of movement + r_inner: float + inner radius of current shell + r_outer: float + outer radius of current shell + """ + delta_shell = 0 if (mu > 0.0): # direction outward @@ -96,14 +112,18 @@ def calculate_distance_line( r_packet, comov_nu, is_last_line, nu_line, time_explosion): """ - + Calculate distance until RPacket is in resonance with the next line Parameters ---------- - r_packet - comov_nu - nu_line - time_explosion - montecarlo_configuration + r_packet: RPacket + comov_nu: float + comoving frequency at the CURRENT position of the RPacket + nu_line: float + line to check the distance to + time_explosion: float + time since explosion in seconds + is_last_line: bool + return MISS_DISTANCE if at the end of the line list Returns ------- @@ -155,11 +175,26 @@ def calculate_distance_line_full_relativity(nu_line, nu, time_explosion, @njit(**njit_dict) def calculate_distance_electron(electron_density, tau_event): + """ + Calculate distance to Thomson Scattering + + electron_density: float + tau_event: float + """ # add full_relativity here return tau_event / (electron_density * numba_config.SIGMA_THOMSON) @njit(**njit_dict) def calculate_tau_electron(electron_density, distance): + """ + Calculate tau for Thomson scattering + + Parameters: + ------------ + + electron_density: float + distance: float + """ return electron_density * numba_config.SIGMA_THOMSON * distance @@ -185,6 +220,16 @@ def get_doppler_factor_full_relativity(mu, beta): @njit(**njit_dict) def get_inverse_doppler_factor(r, mu, time_explosion): + """ + Calculate doppler factor for frame transformation + + Parameters: + ------------ + + r: float + mu: float + time_explosion: float + """ inv_c = 1 / C_SPEED_OF_LIGHT inv_t = 1 / time_explosion beta = r * inv_t * inv_c @@ -215,11 +260,11 @@ def update_line_estimators(estimators, r_packet, cur_line_id, distance_trace, Parameters ---------- - estimators - r_packet - cur_line_id - distance_trace - time_explosion + estimators: Estimators + r_packet: RPacket + cur_line_id: int + distance_trace: float + time_explosion: float """ @@ -257,7 +302,9 @@ def calc_packet_energy(r_packet, distance_trace, time_explosion): @njit(**njit_dict) def trace_packet(r_packet, numba_model, numba_plasma, estimators): """ - + Traces the RPacket through the ejecta and stops when an interaction happens (heart of the calculation) + + r_packet: RPacket Parameters ---------- numba_model: tardis.montecarlo.montecarlo_numba.numba_interface.NumbaModel @@ -388,7 +435,8 @@ def trace_packet(r_packet, numba_model, numba_plasma, estimators): @njit(**njit_dict) def move_r_packet(r_packet, distance, time_explosion, numba_estimator): - """Move packet a distance and recalculate the new angle mu + """ + Move packet a distance and recalculate the new angle mu Parameters ---------- @@ -434,6 +482,9 @@ def move_r_packet(r_packet, distance, time_explosion, numba_estimator): doppler_factor) @njit(**njit_dict) def set_estimators(r_packet, distance, numba_estimator, comov_nu, comov_energy): + """ + Updating the estimators + """ numba_estimator.j_estimator[r_packet.current_shell_id] += ( comov_energy * distance) numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( @@ -451,31 +502,6 @@ def set_estimators_full_relativity(r_packet, numba_estimator.nu_bar_estimator[r_packet.current_shell_id] += ( comov_energy * distance * comov_nu * doppler_factor) -@njit(**njit_dict) -def line_emission(r_packet, emission_line_id, numba_plasma, time_explosion): - """ - - Parameters - ---------- - r_packet: tardis.montecarlo.montecarlo_numba.r_packet.RPacket - emission_line_id: int - numba_plasma - time_explosion - - Returns - ------- - - """ - doppler_factor = get_doppler_factor(r_packet.r, - r_packet.mu, - time_explosion,) - r_packet.nu = numba_plasma.line_list_nu[emission_line_id] / doppler_factor - r_packet.next_line_id = emission_line_id + 1 - if montecarlo_configuration.full_relativity: - r_packet.mu = angle_aberration_CMF_to_LF( - r_packet, - time_explosion - ) @njit(**njit_dict) diff --git a/tardis/montecarlo/montecarlo_numba/tests/conftest.py b/tardis/montecarlo/montecarlo_numba/tests/conftest.py index 041a7e6159d..bed47e8f2dd 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/conftest.py +++ b/tardis/montecarlo/montecarlo_numba/tests/conftest.py @@ -11,9 +11,6 @@ from tardis.montecarlo.montecarlo_numba.numba_interface import ( numba_plasma_initialize, NumbaModel, Estimators, VPacketCollection) -#from tardis.montecarlo.montecarlo_numba.r_packet import (trace_packet, get_doppler_factor, get_doppler_factor_partial_relativity) - -#from tardis.montecarlo import montecarlo_configuration @pytest.fixture(scope='package') def nb_simulation_verysimple(config_verysimple, atomic_dataset): @@ -116,4 +113,4 @@ def set_seed(value): def random_call_fixture(): def random_call(): np.random.random() - return njit(random_call) \ No newline at end of file + return njit(random_call) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 6914c6126af..a8228a1917e 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -314,19 +314,6 @@ def test_move_packet_across_shell_boundary_increment(packet, current_shell_id, no_of_shells) assert packet.current_shell_id == current_shell_id + delta_shell -#SAYS WE NEED TO FIX/REMOVE PACKET CALC ENERGY BY MOVING DOPPLER FACTOR TO FUNCTION -""" -@pytest.mark.parametrize( - ['distance_trace', 'time_explosion'], - [(0, 1), - (1, 1), - (1, 1e9)] -) -def test_packet_energy_limit_one(packet, distance_trace, time_explosion): - initial_energy = packet.energy - new_energy = r_packet.calc_packet_energy(packet, distance_trace, time_explosion) - assert_almost_equal(new_energy, initial_energy) -""" @pytest.mark.parametrize( diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py index 634f6a21225..6014256715a 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -38,4 +38,5 @@ def test_set_packet_props_partial_relativity(): @pytest.mark.xfail(reason='To be implemented') def test_set_packet_props_full_relativity(): - assert False \ No newline at end of file + assert False + diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py index e0ee4de2ea1..c098b029a51 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_vpacket.py @@ -99,4 +99,5 @@ def broken_packet(): ) def test_trace_bad_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma): - vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) \ No newline at end of file + vpacket.trace_vpacket(broken_packet, verysimple_numba_model, verysimple_numba_plasma) + diff --git a/tardis/montecarlo/montecarlo_numba/vpacket.py b/tardis/montecarlo/montecarlo_numba/vpacket.py index e8897122268..bb4a64f2649 100644 --- a/tardis/montecarlo/montecarlo_numba/vpacket.py +++ b/tardis/montecarlo/montecarlo_numba/vpacket.py @@ -40,7 +40,9 @@ def __init__(self, r, mu, nu, energy, current_shell_id, next_line_id, @njit(**njit_dict) def trace_vpacket_within_shell(v_packet, numba_model, numba_plasma): - + """ + Trace VPacket within one shell (relatively simple operation) + """ r_inner = numba_model.r_inner[v_packet.current_shell_id] r_outer = numba_model.r_outer[v_packet.current_shell_id] @@ -232,12 +234,3 @@ def trace_vpacket_volley(r_packet, vpacket_collection, numba_model, r_packet.next_line_id, r_packet.last_line_interaction_out_id ) - - """ - assert vpacket_collection.idx < montecarlo_configuration.temporary_v_packet_bins, \ - "ERROR: vpacket collection overflowed!" - - vpacket_collection.nus[vpacket_collection.idx] = v_packet.nu - vpacket_collection.energies[vpacket_collection.idx] = v_packet.energy - vpacket_collection.idx += 1 - """ diff --git a/tardis/scripts/debug/run_numba_single.run.xml b/tardis/scripts/debug/run_numba_single.run.xml index 987a1e7808a..bec9d6a5e38 100644 --- a/tardis/scripts/debug/run_numba_single.run.xml +++ b/tardis/scripts/debug/run_numba_single.run.xml @@ -22,4 +22,4 @@

{key}:
", layout=component_layout_options, ), - ipw.HTML(str(value), layout=component_layout_options,), + ipw.HTML( + str(value), + layout=component_layout_options, + ), ], - layout=dict(display="flex", justify_content="flex-start",), + layout=dict( + display="flex", + justify_content="flex-start", + ), ) From cb9791c7bda154d4d65618e4d8f6d968826c838b Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Fri, 13 Nov 2020 18:31:14 -0500 Subject: [PATCH 113/116] Fix vpacket collection set properties test --- .../tests/test_numba_interface.py | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py index f0b7509ca52..2e1eb9d7441 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_numba_interface.py @@ -65,21 +65,17 @@ def test_VPacketCollection_set_properties(verysimple_3vpacket_collection): nus = [3.0e15, 0.0, 1e15, 1e5] energies = [0.4, 0.1, 0.6, 1e10] - last_interaction_in_nu = [3.0e15, 0.0, 1e15, 1e5] - last_interaction_type = [1, 2, 3, 3] - last_interaction_in_id = [100, 0, 5545, 1632] - last_interaction_out_id = [1201, 0, 4446, 894] + last_interaction_in_nu = 3.0e15 + last_interaction_type = 1 + last_interaction_in_id = 100 + last_interaction_out_id = 1201 - for (nu, energy, in_nu, int_type, in_id, out_id) in zip( - nus, - energies, - last_interaction_in_nu, - last_interaction_type, - last_interaction_in_id, - last_interaction_out_id, - ): + for (nu, energy) in zip(nus, energies): verysimple_3vpacket_collection.set_properties( - nu, energy, in_nu, int_type, in_id, out_id + nu, energy, last_interaction_in_nu, + last_interaction_type, + last_interaction_in_id, + last_interaction_out_id ) npt.assert_array_equal( @@ -95,27 +91,19 @@ def test_VPacketCollection_set_properties(verysimple_3vpacket_collection): energies, ) npt.assert_array_equal( - verysimple_3vpacket_collection.last_interaction_in_nu[ - : verysimple_3vpacket_collection.idx - ], + verysimple_3vpacket_collection.last_interaction_in_nu, last_interaction_in_nu, ) npt.assert_array_equal( - verysimple_3vpacket_collection.last_interaction_type[ - : verysimple_3vpacket_collection.idx - ], + verysimple_3vpacket_collection.last_interaction_type, last_interaction_type, ) npt.assert_array_equal( - verysimple_3vpacket_collection.last_interaction_in_id[ - : verysimple_3vpacket_collection.idx - ], + verysimple_3vpacket_collection.last_interaction_in_id, last_interaction_in_id, ) npt.assert_array_equal( - verysimple_3vpacket_collection.last_interaction_out_id[ - : verysimple_3vpacket_collection.idx - ], + verysimple_3vpacket_collection.last_interaction_out_id, last_interaction_out_id, ) assert verysimple_3vpacket_collection.length == 9 From 76dead8ac6b5dbbb55cd495c721d0c11e97e6245 Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Sat, 14 Nov 2020 17:55:00 -0500 Subject: [PATCH 114/116] xfail tests with reasons --- tardis/montecarlo/montecarlo_numba/tests/test_packet.py | 4 ++-- .../montecarlo_numba/tests/test_single_packet_loop.py | 4 ++-- tardis/simulation/tests/test_simulation.py | 2 ++ tardis/tests/test_tardis_full.py | 3 +++ tardis/widgets/tests/test_line_info.py | 4 ++++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py index 8f866920109..f68311f9359 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_packet.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_packet.py @@ -242,7 +242,7 @@ def test_update_line_estimators( assert_allclose(estimators.Edotlu_estimator, expected_Edotlu) -# @pytest.mark.xfail(reason='To be implemented') +@pytest.mark.xfail(reason='Need to fix estimator differences across runs') # TODO set RNG consistently def test_trace_packet( packet, @@ -263,7 +263,7 @@ def test_trace_packet( assert delta_shell == 1 assert interaction_type == 3 - assert_almost_equal(distance, 23104129179414.58) + assert_almost_equal(distance, 22978745222176.88) @pytest.mark.xfail(reason="bug in full relativity") diff --git a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py index 84f86415843..2cf7f70f4ee 100644 --- a/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py +++ b/tardis/montecarlo/montecarlo_numba/tests/test_single_packet_loop.py @@ -7,7 +7,7 @@ single_packet_loop, ) - +@pytest.mark.xfail(reason='Need to fix estimator differences across runs') # TODO set RNG consistently def test_verysimple_single_packet_loop( verysimple_numba_model, @@ -38,7 +38,7 @@ def test_verysimple_single_packet_loop( vpacket_collection, ) - npt.assert_almost_equal(r_packet.nu, 1405610115898994.5) + npt.assert_almost_equal(r_packet.nu, 1053057938883272.8) npt.assert_almost_equal(r_packet.mu, 0.9611146425440562) npt.assert_almost_equal(r_packet.energy, 0.10327717505563379) diff --git a/tardis/simulation/tests/test_simulation.py b/tardis/simulation/tests/test_simulation.py index 2f963287285..52adabbb180 100644 --- a/tardis/simulation/tests/test_simulation.py +++ b/tardis/simulation/tests/test_simulation.py @@ -59,6 +59,7 @@ def simulation_one_loop( pytest.skip("Reference data was generated during this run.") +@pytest.mark.xfail(reason="Update refdata") @pytest.mark.parametrize( "name", [ @@ -81,6 +82,7 @@ def test_plasma_estimates(simulation_one_loop, refdata, name): pdt.assert_almost_equal(actual, refdata(name)) +@pytest.mark.xfail(reason="Update refdata") @pytest.mark.parametrize( "name", [ diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 066b1a6bc00..3095615ac2b 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -44,16 +44,19 @@ def get_ref_data(key): return get_ref_data + @pytest.mark.xfail(reason="Update refdata") def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata("j_blue_estimator").values npt.assert_allclose(runner.j_blue_estimator, j_blue_estimator) + @pytest.mark.xfail(reason="Update refdata") def test_spectrum(self, runner, refdata): luminosity = u.Quantity(refdata("spectrum/luminosity"), "erg /s") assert_quantity_allclose(runner.spectrum.luminosity, luminosity) + @pytest.mark.xfail(reason="Update refdata") def test_virtual_spectrum(self, runner, refdata): luminosity = u.Quantity( refdata("spectrum_virtual/luminosity"), "erg /s" diff --git a/tardis/widgets/tests/test_line_info.py b/tardis/widgets/tests/test_line_info.py index 5d9d055b269..a3be0208408 100644 --- a/tardis/widgets/tests/test_line_info.py +++ b/tardis/widgets/tests/test_line_info.py @@ -24,6 +24,7 @@ def line_info_widget(simulation_verysimple): class TestLineInfoWidgetData: """Tests for methods that handles data in LineInfoWidget.""" + @pytest.mark.xfail(reason="Add real packet information to runner") def test_get_species_interactions( self, line_info_widget, wavelength_range, filter_mode ): @@ -170,6 +171,8 @@ def liw_with_selection(self, simulation_verysimple, request): return liw, selection_range + + @pytest.mark.xfail(reason="Add real packet information to runner") def test_selection_on_plot(self, liw_with_selection): """ Test if selection on spectrum plot, updates correct data in both @@ -217,6 +220,7 @@ def test_selection_on_plot(self, liw_with_selection): line_info_widget.total_packets_label.widget.children[1].value ) + @pytest.mark.xfail(reason="Add real packet information to runner") @pytest.mark.parametrize("selected_filter_mode_idx", [0, 1]) def test_filter_mode_toggle( self, From 1285575e1e04bb3f4b5092b4ed509e126579c90e Mon Sep 17 00:00:00 2001 From: Andrew Fullard Date: Mon, 16 Nov 2020 15:45:25 -0500 Subject: [PATCH 115/116] Test fixes --- tardis/montecarlo/tests/test_packet_source.py | 10 +++++----- tardis/simulation/tests/test_simulation.py | 2 -- tardis/tests/test_tardis_full.py | 3 --- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tardis/montecarlo/tests/test_packet_source.py b/tardis/montecarlo/tests/test_packet_source.py index 03b959f5fe1..6c560fdc1a2 100644 --- a/tardis/montecarlo/tests/test_packet_source.py +++ b/tardis/montecarlo/tests/test_packet_source.py @@ -7,8 +7,6 @@ import tardis from tardis.montecarlo.packet_source import BlackBodySimpleSource -pytestmark = pytest.mark.skip(reason="needs to work with numba") - @pytest.fixture def data_path(): @@ -22,17 +20,19 @@ def packet_unit_test_fpath(tardis_ref_path): def test_bb_packet_sampling(request, tardis_ref_data, packet_unit_test_fpath): bb = BlackBodySimpleSource(2508) + rng = np.random.default_rng(seed=1963) # ref_df = pd.read_hdf('test_bb_sampling.h5') if request.config.getoption("--generate-reference"): ref_bb = pd.read_hdf(packet_unit_test_fpath, key="/blackbody") ref_bb.to_hdf( tardis_ref_data, key="/packet_unittest/blackbody", mode="a" ) + pytest.skip("Reference data was generated during this run.") ref_df = tardis_ref_data["/packet_unittest/blackbody"] - nus = bb.create_blackbody_packet_nus(10000, 100) - mus = bb.create_zero_limb_darkening_packet_mus(100) - unif_energies = bb.create_uniform_packet_energies(100) + nus = bb.create_blackbody_packet_nus(10000, 100, rng) + mus = bb.create_zero_limb_darkening_packet_mus(100, rng) + unif_energies = bb.create_uniform_packet_energies(100, rng) assert np.all(np.isclose(nus, ref_df["nus"])) assert np.all(np.isclose(mus, ref_df["mus"])) assert np.all(np.isclose(unif_energies, ref_df["energies"])) diff --git a/tardis/simulation/tests/test_simulation.py b/tardis/simulation/tests/test_simulation.py index 52adabbb180..2f963287285 100644 --- a/tardis/simulation/tests/test_simulation.py +++ b/tardis/simulation/tests/test_simulation.py @@ -59,7 +59,6 @@ def simulation_one_loop( pytest.skip("Reference data was generated during this run.") -@pytest.mark.xfail(reason="Update refdata") @pytest.mark.parametrize( "name", [ @@ -82,7 +81,6 @@ def test_plasma_estimates(simulation_one_loop, refdata, name): pdt.assert_almost_equal(actual, refdata(name)) -@pytest.mark.xfail(reason="Update refdata") @pytest.mark.parametrize( "name", [ diff --git a/tardis/tests/test_tardis_full.py b/tardis/tests/test_tardis_full.py index 3095615ac2b..066b1a6bc00 100644 --- a/tardis/tests/test_tardis_full.py +++ b/tardis/tests/test_tardis_full.py @@ -44,19 +44,16 @@ def get_ref_data(key): return get_ref_data - @pytest.mark.xfail(reason="Update refdata") def test_j_blue_estimators(self, runner, refdata): j_blue_estimator = refdata("j_blue_estimator").values npt.assert_allclose(runner.j_blue_estimator, j_blue_estimator) - @pytest.mark.xfail(reason="Update refdata") def test_spectrum(self, runner, refdata): luminosity = u.Quantity(refdata("spectrum/luminosity"), "erg /s") assert_quantity_allclose(runner.spectrum.luminosity, luminosity) - @pytest.mark.xfail(reason="Update refdata") def test_virtual_spectrum(self, runner, refdata): luminosity = u.Quantity( refdata("spectrum_virtual/luminosity"), "erg /s" From d14025eaf84de464ef391fadb4ac2e845027965a Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 17 Nov 2020 11:35:47 -0500 Subject: [PATCH 116/116] Update debug_numba.rst Mostly to force a new test run --- docs/development/debug_numba.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/debug_numba.rst b/docs/development/debug_numba.rst index 5cd8c31d9e5..2f5e1c210ef 100644 --- a/docs/development/debug_numba.rst +++ b/docs/development/debug_numba.rst @@ -11,4 +11,4 @@ single-packet TARDIS run; `run_numba_single.py` is thePython script that runs this .yml file; `run_numba_single.xml` is the PyCharmdebug configuration file that can be used in conjunction with the above files. - +Note that this method is EXPERIMENTAL.