From fa6f28ac405c29f1d6621ef2783163741a19b9aa Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Tue, 17 Sep 2024 13:08:32 -0400 Subject: [PATCH 01/21] halton ray sampling --- include/openmc/random_dist.h | 11 +++ include/openmc/random_ray/random_ray.h | 1 + src/random_dist.cpp | 9 ++ src/random_ray/random_ray.cpp | 111 +++++++++++++++++++++++-- 4 files changed, 127 insertions(+), 5 deletions(-) diff --git a/include/openmc/random_dist.h b/include/openmc/random_dist.h index 0fb186edca0..ec3f6d2a5f3 100644 --- a/include/openmc/random_dist.h +++ b/include/openmc/random_dist.h @@ -16,6 +16,17 @@ namespace openmc { double uniform_distribution(double a, double b, uint64_t* seed); +//============================================================================== +//! Sample an integer from uniform distribution [a, b] +// +//! \param a Lower bound of uniform distribution +//! \param b Upper bound of uniform distribtion +//! \param seed A pointer to the pseudorandom seed +//! \return Sampled variate +//============================================================================== + +double uniform_int_distribution(double a, double b, uint64_t* seed); + //============================================================================== //! Samples an energy from the Maxwell fission distribution based on a direct //! sampling scheme. diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index 5ee64574ec1..a53a7cf48c8 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -27,6 +27,7 @@ class RandomRay : public Particle { void attenuate_flux(double distance, bool is_active); void initialize_ray(uint64_t ray_id, FlatSourceDomain* domain); uint64_t transport_history_based_single_ray(); + SourceSite sample_lds(int64_t seed, int64_t skip); //---------------------------------------------------------------------------- // Static data members diff --git a/src/random_dist.cpp b/src/random_dist.cpp index 1aa35a689cf..253b48e4389 100644 --- a/src/random_dist.cpp +++ b/src/random_dist.cpp @@ -12,6 +12,15 @@ double uniform_distribution(double a, double b, uint64_t* seed) return a + (b - a) * prn(seed); } +double uniform_int_distribution(double a, double b, uint64_t* seed) +{ + // Generate a random uint64_t value using the PRNG + uint64_t random_value = + static_cast(prn(seed) * std::numeric_limits::max()); + // Map the random value to the range [a, b] + return a + (random_value % static_cast(b - a + 1)); +} + double maxwell_spectrum(double T, uint64_t* seed) { // Set the random numbers diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 63d728cce8b..7515e0d7c10 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -7,6 +7,9 @@ #include "openmc/search.h" #include "openmc/settings.h" #include "openmc/simulation.h" + +#include "openmc/distribution_spatial.h" +#include "openmc/random_dist.h" #include "openmc/source.h" namespace openmc { @@ -60,6 +63,71 @@ float cjosey_exponential(float tau) return num / den; } +// Implementation of the Fisher-Yates shuffle algorithm. +// Algorithm adapted from: +// https://en.cppreference.com/w/cpp/algorithm/random_shuffle#Version_3 +void fisher_yates_shuffle(vector& arr, uint64_t* seed) +{ + // Loop over the array from the last element down to the second + for (size_t i = arr.size() - 1; i > 0; --i) { + // Generate a random index in the range [0, i] + size_t j = uniform_int_distribution(0, i, seed); + // Swap arr[i] with arr[j] + std::swap(arr[i], arr[j]); + } +} + +// Function to generate randomized Halton sequence samples +// +// Algorithm adapted from: +// A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. +// URL http:arxiv.org/abs/1706.02808 +// +// Results are not idential to Python implementation - the permutation process +// produces different results due to differences in shuffle/rng implementation. +vector> rhalton(int N, int dim, uint64_t seed, int64_t skip = 0) +{ + vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; + vector> halton(N, vector(dim, 0.0)); + + for (int D = 0; D < dim; ++D) { + int b = primes[D]; + vector ind(N); + std::iota(ind.begin(), ind.end(), skip); + double b2r = 1.0 / b; + vector ans(N, 0.0); + vector res(ind); + + while ((1.0 - b2r) < 1.0) { + vector dig(N); + // randomaly permute a sequence from skip to skip+N + vector perm(b); + std::iota(perm.begin(), perm.end(), 0); + fisher_yates_shuffle(perm, &seed); + + // compute element wise remainder of division (mod) + for (int i = 0; i < N; ++i) { + dig[i] = res[i] % b; + } + + for (int i = 0; i < N; ++i) { + ans[i] += perm[dig[i]] * b2r; + } + + b2r /= b; + for (int i = 0; i < N; ++i) { + res[i] = (res[i] - dig[i]) / b; + } + } + + for (int i = 0; i < N; ++i) { + halton[i][D] = ans[i]; + } + } + + return halton; +} + //============================================================================== // RandomRay implementation //============================================================================== @@ -251,13 +319,17 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) id() = simulation::work_index[mpi::rank] + ray_id; // set random number seed - int64_t particle_seed = - (simulation::current_batch - 1) * settings::n_particles + id(); - init_particle_seeds(particle_seed, seeds()); - stream() = STREAM_TRACKING; + int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles; + int64_t skip = id(); + // TODO: I'm not sure what init_particle_seeds and stream() lines do. + // are they needed? + // int64_t particle_seed = + // (simulation::current_batch - 1) * settings::n_particles + id(); + // init_particle_seeds(batch_seed, seeds()); + // stream() = STREAM_TRACKING; // Sample from ray source distribution - SourceSite site {ray_source_->sample(current_seed())}; + SourceSite site = sample_lds(batch_seed, skip); site.E = lower_bound_index( data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); site.E = negroups_ - site.E - 1.; @@ -286,4 +358,33 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) } } +SourceSite RandomRay::sample_lds(int64_t seed, int64_t skip) +{ + SourceSite site; + + // Calculate next samples in LDS + vector> samples = rhalton(1, 5, seed, skip = skip); + + // get spatial box of ray_source_ + SpatialBox* sb = dynamic_cast( + dynamic_cast(RandomRay::ray_source_.get())->space()); + + // Sample spatial distribution + Position xi {samples[0][0], samples[0][1], samples[0][2]}; + Position shift {1e-9, 1e-9, 1e-9}; + site.r = (sb->lower_left() + shift) + + xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); + + // Sample Polar cosine and azimuthal angles + float mu = 2.0 * samples[0][3] - 1.0; + float azi = 2.0 * PI * samples[0][4]; + // Convert to Cartesian coordinates + float c = std::pow((1.0 - std::pow(mu, 2)), 0.5); + site.u.x = mu; + site.u.y = std::cos(azi) * c; + site.u.z = std::sin(azi) * c; + + return site; +} + } // namespace openmc From d967bb1103a8d9cebfd8975028c5f967d8efecba Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Thu, 19 Sep 2024 16:24:30 -0400 Subject: [PATCH 02/21] fix batch seed --- include/openmc/random_ray/random_ray.h | 2 +- src/random_ray/random_ray.cpp | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index a53a7cf48c8..7771b746e35 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -27,7 +27,7 @@ class RandomRay : public Particle { void attenuate_flux(double distance, bool is_active); void initialize_ray(uint64_t ray_id, FlatSourceDomain* domain); uint64_t transport_history_based_single_ray(); - SourceSite sample_lds(int64_t seed, int64_t skip); + SourceSite sample_lds(uint64_t* seed, int64_t skip); //---------------------------------------------------------------------------- // Static data members diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 7515e0d7c10..07899836dd9 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -85,7 +85,7 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // // Results are not idential to Python implementation - the permutation process // produces different results due to differences in shuffle/rng implementation. -vector> rhalton(int N, int dim, uint64_t seed, int64_t skip = 0) +vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) { vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector> halton(N, vector(dim, 0.0)); @@ -103,7 +103,7 @@ vector> rhalton(int N, int dim, uint64_t seed, int64_t skip = 0) // randomaly permute a sequence from skip to skip+N vector perm(b); std::iota(perm.begin(), perm.end(), 0); - fisher_yates_shuffle(perm, &seed); + fisher_yates_shuffle(perm, seed); // compute element wise remainder of division (mod) for (int i = 0; i < N; ++i) { @@ -321,15 +321,11 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) // set random number seed int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles; int64_t skip = id(); - // TODO: I'm not sure what init_particle_seeds and stream() lines do. - // are they needed? - // int64_t particle_seed = - // (simulation::current_batch - 1) * settings::n_particles + id(); - // init_particle_seeds(batch_seed, seeds()); - // stream() = STREAM_TRACKING; + init_particle_seeds(batch_seed, seeds()); + stream() = STREAM_TRACKING; // Sample from ray source distribution - SourceSite site = sample_lds(batch_seed, skip); + SourceSite site = sample_lds(current_seed(), skip); site.E = lower_bound_index( data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); site.E = negroups_ - site.E - 1.; @@ -358,7 +354,7 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) } } -SourceSite RandomRay::sample_lds(int64_t seed, int64_t skip) +SourceSite RandomRay::sample_lds(uint64_t* seed, int64_t skip) { SourceSite site; From 0d14f44c4daa92b94af7dd0cc2091e043b00aa74 Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Sat, 11 Jan 2025 15:20:59 -0500 Subject: [PATCH 03/21] merge origin/develop --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/workflows/ci.yml | 30 +- .readthedocs.yaml | 7 +- CMakeLists.txt | 36 +- Dockerfile | 7 +- MANIFEST.in | 2 - cmake/Modules/FindLIBMESH.cmake | 6 +- cmake/OpenMCConfig.cmake.in | 6 +- docs/requirements-rtd.txt | 13 - docs/source/conf.py | 4 +- docs/source/devguide/contributing.rst | 2 +- docs/source/devguide/index.rst | 1 + docs/source/devguide/policies.rst | 35 + docs/source/devguide/styleguide.rst | 2 +- docs/source/io_formats/geometry.rst | 34 +- docs/source/io_formats/plots.rst | 12 + docs/source/io_formats/settings.rst | 108 +- docs/source/io_formats/statepoint.rst | 5 +- docs/source/io_formats/tallies.rst | 7 +- docs/source/methods/cross_sections.rst | 2 +- docs/source/methods/eigenvalue.rst | 19 +- docs/source/methods/random_numbers.rst | 39 +- docs/source/methods/random_ray.rst | 457 ++++++- docs/source/methods/tallies.rst | 16 +- docs/source/pythonapi/base.rst | 2 + docs/source/pythonapi/capi.rst | 1 + docs/source/pythonapi/model.rst | 3 + docs/source/pythonapi/stats.rst | 2 + docs/source/quickinstall.rst | 11 +- docs/source/releasenotes/0.15.0.rst | 262 ++++ docs/source/releasenotes/index.rst | 1 + docs/source/usersguide/geometry.rst | 25 +- docs/source/usersguide/install.rst | 4 - docs/source/usersguide/random_ray.rst | 307 ++++- docs/source/usersguide/settings.rst | 84 +- examples/custom_source/CMakeLists.txt | 2 +- examples/lattice/hexagonal/build_xml.py | 5 +- examples/lattice/nested/build_xml.py | 5 +- examples/lattice/simple/build_xml.py | 5 +- .../CMakeLists.txt | 2 +- examples/pincell/build_xml.py | 5 +- examples/pincell_multigroup/build_xml.py | 5 +- include/openmc/bounding_box.h | 61 + include/openmc/capi.h | 3 + include/openmc/cell.h | 12 +- include/openmc/constants.h | 3 + include/openmc/dagmc.h | 15 + include/openmc/distribution_spatial.h | 20 + include/openmc/lattice.h | 38 +- include/openmc/mesh.h | 87 +- include/openmc/mgxs.h | 2 + include/openmc/mgxs_interface.h | 3 + include/openmc/output.h | 8 +- include/openmc/particle.h | 4 +- include/openmc/particle_data.h | 8 +- include/openmc/position.h | 8 +- .../openmc/random_ray/flat_source_domain.h | 189 ++- .../openmc/random_ray/linear_source_domain.h | 68 + include/openmc/random_ray/moment_matrix.h | 90 ++ include/openmc/random_ray/random_ray.h | 15 +- .../openmc/random_ray/random_ray_simulation.h | 16 +- include/openmc/settings.h | 69 +- include/openmc/simulation.h | 1 + include/openmc/source.h | 12 +- include/openmc/state_point.h | 8 +- include/openmc/string_utils.h | 18 +- include/openmc/surface.h | 58 +- include/openmc/tallies/filter.h | 1 + include/openmc/tallies/filter_mu.h | 2 +- include/openmc/tallies/filter_musurface.h | 34 + include/openmc/universe.h | 1 + include/openmc/xml_interface.h | 3 + openmc/__init__.py | 7 +- openmc/arithmetic.py | 4 +- openmc/bounding_box.py | 5 +- openmc/checkvalue.py | 3 +- openmc/config.py | 27 +- openmc/dagmc.py | 625 +++++++++ openmc/data/_endf.pyx | 8 - openmc/data/data.py | 25 +- openmc/data/decay.py | 3 +- openmc/data/effective_dose/dose.py | 110 +- .../{ => icrp116}/electrons.txt | 0 .../{ => icrp116}/helium_ions.txt | 0 .../{ => icrp116}/negative_muons.txt | 0 .../{ => icrp116}/negative_pions.txt | 0 .../effective_dose/{ => icrp116}/neutrons.txt | 0 .../effective_dose/{ => icrp116}/photons.txt | 0 .../{ => icrp116}/photons_kerma.txt | 0 .../{ => icrp116}/positive_muons.txt | 0 .../{ => icrp116}/positive_pions.txt | 0 .../{ => icrp116}/positrons.txt | 0 .../effective_dose/{ => icrp116}/protons.txt | 0 .../icrp74/generate_photon_effective_dose.py | 69 + .../data/effective_dose/icrp74/neutrons.txt | 50 + openmc/data/effective_dose/icrp74/photons.txt | 26 + openmc/data/endf.c | 57 - openmc/data/endf.py | 10 +- openmc/data/function.py | 22 - openmc/data/neutron.py | 88 +- openmc/data/njoy.py | 23 +- openmc/data/photon.py | 16 +- openmc/data/reconstruct.pyx | 522 -------- openmc/data/thermal_angle_energy.py | 2 +- openmc/deplete/abc.py | 9 +- openmc/deplete/chain.py | 27 +- openmc/deplete/coupled_operator.py | 3 +- openmc/deplete/independent_operator.py | 9 +- openmc/deplete/microxs.py | 60 +- openmc/deplete/openmc_operator.py | 11 +- openmc/deplete/reaction_rates.py | 7 +- openmc/deplete/results.py | 35 +- openmc/examples.py | 529 +++++++- openmc/filter.py | 48 +- openmc/geometry.py | 44 +- openmc/lattice.py | 4 + openmc/lib/__init__.py | 9 +- openmc/lib/core.py | 16 +- openmc/lib/dagmc.py | 43 + openmc/lib/filter.py | 11 +- openmc/lib/math.py | 266 +--- openmc/lib/mesh.py | 41 +- openmc/material.py | 153 ++- openmc/mesh.py | 209 +-- openmc/mgxs_library.py | 30 +- openmc/mixin.py | 9 +- openmc/model/model.py | 290 ++++- openmc/model/surface_composite.py | 413 +++++- openmc/plots.py | 26 +- openmc/plotter.py | 49 +- openmc/polynomial.py | 2 +- openmc/region.py | 32 +- openmc/search.py | 2 +- openmc/settings.py | 270 ++-- openmc/source.py | 334 +++-- openmc/statepoint.py | 2 +- openmc/stats/multivariate.py | 136 +- openmc/stats/univariate.py | 188 ++- openmc/surface.py | 86 +- openmc/trigger.py | 2 +- openmc/universe.py | 822 ++++-------- openmc/utility_funcs.py | 25 +- openmc/weight_windows.py | 25 +- pyproject.toml | 77 +- scripts/openmc-update-inputs | 16 +- setup.py | 81 -- src/cell.cpp | 51 +- src/cross_sections.cpp | 4 + src/dagmc.cpp | 140 +- src/distribution.cpp | 3 + src/distribution_spatial.cpp | 45 + src/eigenvalue.cpp | 2 +- src/external/quartic_solver.cpp | 1 + src/finalize.cpp | 10 +- src/geometry_aux.cpp | 3 +- src/lattice.cpp | 46 +- src/mesh.cpp | 226 +++- src/mgxs.cpp | 33 +- src/mgxs_interface.cpp | 12 +- src/nuclide.cpp | 32 +- src/output.cpp | 2 +- src/particle.cpp | 175 ++- src/photon.cpp | 16 +- src/physics_mg.cpp | 4 + src/plot.cpp | 17 +- src/random_lcg.cpp | 3 +- src/random_ray/flat_source_domain.cpp | 642 ++++++++-- src/random_ray/linear_source_domain.cpp | 225 ++++ src/random_ray/moment_matrix.cpp | 84 ++ src/random_ray/random_ray.cpp | 294 ++++- src/random_ray/random_ray_simulation.cpp | 322 ++++- src/settings.cpp | 154 ++- src/simulation.cpp | 93 +- src/source.cpp | 25 +- src/state_point.cpp | 20 +- src/string_utils.cpp | 3 +- src/surface.cpp | 11 +- src/tallies/filter.cpp | 3 + src/tallies/filter_mesh.cpp | 2 +- src/tallies/filter_musurface.cpp | 36 + src/tallies/tally.cpp | 5 +- src/thermal.cpp | 24 +- src/weight_windows.cpp | 11 +- src/xml_interface.cpp | 19 + tests/conftest.py | 7 + tests/cpp_unit_tests/CMakeLists.txt | 1 + tests/cpp_unit_tests/test_math.cpp | 357 ++++++ tests/regression_tests/__init__.py | 11 + tests/regression_tests/conftest.py | 4 +- tests/regression_tests/dagmc/legacy/test.py | 3 +- .../dagmc/universes/inputs_true.dat | 13 +- .../dagmc/universes/results_true.dat | 13 +- .../regression_tests/dagmc/universes/test.py | 11 +- .../deplete_no_transport/test.py | 24 +- .../deplete_with_transfer_rates/test.py | 7 +- .../__init__.py | 0 .../filter_cellfrom/inputs_true.dat | 154 +++ .../filter_cellfrom/results_true.dat | 53 + .../regression_tests/filter_cellfrom/test.py | 316 +++++ .../filter_mesh/inputs_true.dat | 2 +- .../__init__.py | 0 .../filter_musurface/inputs_true.dat | 37 + .../filter_musurface/results_true.dat | 11 + .../regression_tests/filter_musurface/test.py | 43 + .../filter_translations/inputs_true.dat | 2 +- .../mg_temperature/build_2g.py | 5 +- .../mgxs_library_mesh/inputs_true.dat | 2 +- .../photon_production_inputs_true.dat | 2 +- .../photon_production/inputs_true.dat | 2 +- .../__init__.py | 0 .../inputs_true.dat | 245 ++++ .../results_true.dat | 9 + .../random_ray_adjoint_fixed_source/test.py | 20 + .../random_ray_adjoint_k_eff/__init__.py | 0 .../inputs_true.dat | 10 +- .../random_ray_adjoint_k_eff/results_true.dat | 171 +++ .../random_ray_adjoint_k_eff/test.py | 20 + .../random_ray_basic/results_true.dat | 171 --- .../regression_tests/random_ray_basic/test.py | 232 ---- .../random_ray_entropy/__init__.py | 0 .../random_ray_entropy/geometry.xml | 88 ++ .../random_ray_entropy/materials.xml | 8 + .../random_ray_entropy/mgxs.h5 | Bin 0 -> 10664 bytes .../random_ray_entropy/results_true.dat | 13 + .../random_ray_entropy/settings.xml | 17 + .../random_ray_entropy/test.py | 33 + .../__init__.py | 0 .../cell/inputs_true.dat | 244 ++++ .../cell/results_true.dat | 9 + .../material/inputs_true.dat | 244 ++++ .../material/results_true.dat | 9 + .../random_ray_fixed_source_domain/test.py | 51 + .../universe/inputs_true.dat | 244 ++++ .../universe/results_true.dat | 9 + .../__init__.py | 0 .../linear/inputs_true.dat | 245 ++++ .../linear/results_true.dat | 9 + .../linear_xy/inputs_true.dat | 245 ++++ .../linear_xy/results_true.dat | 9 + .../random_ray_fixed_source_linear/test.py | 29 + .../False/inputs_true.dat | 244 ++++ .../False/results_true.dat | 9 + .../True/inputs_true.dat | 244 ++++ .../True/results_true.dat | 9 + .../__init__.py | 0 .../test.py | 27 + .../__init__.py | 0 .../flat/inputs_true.dat | 140 ++ .../flat/results_true.dat | 169 +++ .../linear_xy/inputs_true.dat | 140 ++ .../linear_xy/results_true.dat | 169 +++ .../test.py | 133 ++ .../random_ray_k_eff/__init__.py | 0 .../inputs_true.dat | 1 + .../random_ray_k_eff/results_true.dat | 171 +++ .../regression_tests/random_ray_k_eff/test.py | 19 + .../random_ray_linear/__init__.py | 0 .../random_ray_linear/linear/inputs_true.dat | 110 ++ .../random_ray_linear/linear/results_true.dat | 171 +++ .../linear_xy/inputs_true.dat | 110 ++ .../linear_xy/results_true.dat | 171 +++ .../random_ray_linear/test.py | 28 + .../random_ray_vacuum/results_true.dat | 171 --- .../random_ray_vacuum/test.py | 235 ---- .../random_ray_volume_estimator/__init__.py | 0 .../hybrid/inputs_true.dat | 245 ++++ .../hybrid/results_true.dat | 9 + .../naive/inputs_true.dat | 245 ++++ .../naive/results_true.dat | 9 + .../simulation_averaged/inputs_true.dat | 245 ++++ .../simulation_averaged/results_true.dat | 9 + .../random_ray_volume_estimator/test.py | 30 + .../__init__.py | 0 .../hybrid/inputs_true.dat | 246 ++++ .../hybrid/results_true.dat | 9 + .../naive/inputs_true.dat | 246 ++++ .../naive/results_true.dat | 9 + .../simulation_averaged/inputs_true.dat | 246 ++++ .../simulation_averaged/results_true.dat | 9 + .../test.py | 32 + .../score_current/inputs_true.dat | 2 +- tests/regression_tests/source/inputs_true.dat | 18 +- .../regression_tests/source/results_true.dat | 2 +- tests/regression_tests/source_dlopen/test.py | 3 +- .../source_parameterized_dlopen/test.py | 3 +- tests/regression_tests/surface_source/test.py | 8 +- .../surface_source_write/__init__.py | 0 .../surface_source_write/_visualize.py | 66 + .../case-01/inputs_true.dat | 57 + .../case-01/results_true.dat | 2 + .../case-01/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-02/inputs_true.dat | 58 + .../case-02/results_true.dat | 2 + .../case-02/surface_source_true.h5 | Bin 0 -> 12752 bytes .../case-03/inputs_true.dat | 58 + .../case-03/results_true.dat | 2 + .../case-03/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-04/inputs_true.dat | 59 + .../case-04/results_true.dat | 2 + .../case-04/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-05/inputs_true.dat | 59 + .../case-05/results_true.dat | 2 + .../case-05/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-06/inputs_true.dat | 58 + .../case-06/results_true.dat | 2 + .../case-06/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-07/inputs_true.dat | 58 + .../case-07/results_true.dat | 2 + .../case-07/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-08/inputs_true.dat | 58 + .../case-08/results_true.dat | 2 + .../case-08/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-09/inputs_true.dat | 58 + .../case-09/results_true.dat | 2 + .../case-09/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-10/inputs_true.dat | 58 + .../case-10/results_true.dat | 2 + .../case-10/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-11/inputs_true.dat | 58 + .../case-11/results_true.dat | 2 + .../case-11/surface_source_true.h5 | Bin 0 -> 6408 bytes .../case-12/inputs_true.dat | 51 + .../case-12/results_true.dat | 2 + .../case-12/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-13/inputs_true.dat | 52 + .../case-13/results_true.dat | 2 + .../case-13/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-14/inputs_true.dat | 52 + .../case-14/results_true.dat | 2 + .../case-14/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-15/inputs_true.dat | 52 + .../case-15/results_true.dat | 2 + .../case-15/surface_source_true.h5 | Bin 0 -> 2144 bytes .../case-16/inputs_true.dat | 51 + .../case-16/results_true.dat | 2 + .../case-16/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-17/inputs_true.dat | 52 + .../case-17/results_true.dat | 2 + .../case-17/surface_source_true.h5 | Bin 0 -> 2144 bytes .../case-18/inputs_true.dat | 52 + .../case-18/results_true.dat | 2 + .../case-18/surface_source_true.h5 | Bin 0 -> 2144 bytes .../case-19/inputs_true.dat | 52 + .../case-19/results_true.dat | 2 + .../case-19/surface_source_true.h5 | Bin 0 -> 2144 bytes .../case-20/inputs_true.dat | 51 + .../case-20/results_true.dat | 2 + .../case-20/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-21/inputs_true.dat | 52 + .../case-21/results_true.dat | 2 + .../case-21/surface_source_true.h5 | Bin 0 -> 2144 bytes .../case-a01/inputs_true.dat | 59 + .../case-a01/results_true.dat | 2 + .../case-a01/surface_source_true.h5 | Bin 0 -> 6616 bytes .../case-d01/inputs_true.dat | 36 + .../case-d01/results_true.dat | 2 + .../case-d01/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-d02/inputs_true.dat | 37 + .../case-d02/results_true.dat | 2 + .../case-d02/surface_source_true.h5 | Bin 0 -> 31472 bytes .../case-d03/inputs_true.dat | 37 + .../case-d03/results_true.dat | 2 + .../case-d03/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-d04/inputs_true.dat | 38 + .../case-d04/results_true.dat | 2 + .../case-d04/surface_source_true.h5 | Bin 0 -> 31472 bytes .../case-d05/inputs_true.dat | 37 + .../case-d05/results_true.dat | 2 + .../case-d05/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-d06/inputs_true.dat | 37 + .../case-d06/results_true.dat | 2 + .../case-d06/surface_source_true.h5 | Bin 0 -> 29496 bytes .../case-d07/inputs_true.dat | 52 + .../case-d07/results_true.dat | 2 + .../case-d07/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-d08/inputs_true.dat | 52 + .../case-d08/results_true.dat | 2 + .../case-d08/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-e01/inputs_true.dat | 59 + .../case-e01/results_true.dat | 2 + .../case-e01/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-e02/inputs_true.dat | 58 + .../case-e02/results_true.dat | 2 + .../case-e02/surface_source_true.h5 | Bin 0 -> 33344 bytes .../case-e03/inputs_true.dat | 52 + .../case-e03/results_true.dat | 2 + .../case-e03/surface_source_true.h5 | Bin 0 -> 33344 bytes .../surface_source_write/test.py | 1125 +++++++++++++++++ .../tally_slice_merge/inputs_true.dat | 2 +- .../tally_slice_merge/test.py | 3 + .../weightwindows/generators/test.py | 2 +- .../weightwindows/inputs_true.dat | 2 +- tests/regression_tests/weightwindows/test.py | 2 +- .../cell_instances/test_hex_multilattice.py | 119 ++ .../test_rect_multilattice.py} | 22 +- tests/unit_tests/dagmc/test_model.py | 256 ++++ tests/unit_tests/mesh_to_vtk/test_vtk_dims.py | 2 +- tests/unit_tests/test_config.py | 12 +- tests/unit_tests/test_data_dose.py | 14 + tests/unit_tests/test_data_neutron.py | 27 +- tests/unit_tests/test_data_photon.py | 5 +- tests/unit_tests/test_deplete_decay.py | 11 +- .../test_deplete_independent_operator.py | 6 +- tests/unit_tests/test_deplete_microxs.py | 8 +- .../unit_tests/test_deplete_transfer_rates.py | 1 + tests/unit_tests/test_filter_musurface.py | 38 + tests/unit_tests/test_lib.py | 29 +- tests/unit_tests/test_material.py | 36 + tests/unit_tests/test_math.py | 247 ---- tests/unit_tests/test_mesh.py | 63 + tests/unit_tests/test_model.py | 29 + tests/unit_tests/test_plots.py | 25 +- tests/unit_tests/test_plotter.py | 2 +- tests/unit_tests/test_region.py | 2 +- tests/unit_tests/test_settings.py | 2 +- tests/unit_tests/test_source.py | 64 +- tests/unit_tests/test_source_file.py | 30 +- tests/unit_tests/test_stats.py | 34 +- tests/unit_tests/test_surface_composite.py | 160 ++- tests/unit_tests/test_surface_source_write.py | 301 +++++ .../unit_tests/test_tally_multiply_density.py | 2 +- tests/unit_tests/test_temp_interp.py | 4 +- tests/unit_tests/test_tracks.py | 2 +- tests/unit_tests/test_triggers.py | 31 + .../test_uniform_source_sampling.py | 75 ++ tests/unit_tests/weightwindows/test.py | 40 +- .../weightwindows/test_mesh_tets.exo | 1 + tests/unit_tests/weightwindows/test_ww_gen.py | 4 +- tools/ci/gha-install-libmesh.sh | 1 - tools/ci/gha-install.py | 24 +- tools/ci/gha-install.sh | 11 +- vendor/fmt | 2 +- 432 files changed, 19162 insertions(+), 4509 deletions(-) delete mode 100644 docs/requirements-rtd.txt create mode 100644 docs/source/devguide/policies.rst create mode 100644 docs/source/releasenotes/0.15.0.rst create mode 100644 include/openmc/bounding_box.h create mode 100644 include/openmc/random_ray/linear_source_domain.h create mode 100644 include/openmc/random_ray/moment_matrix.h create mode 100644 include/openmc/tallies/filter_musurface.h create mode 100644 openmc/dagmc.py delete mode 100644 openmc/data/_endf.pyx rename openmc/data/effective_dose/{ => icrp116}/electrons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/helium_ions.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/negative_muons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/negative_pions.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/neutrons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/photons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/photons_kerma.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/positive_muons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/positive_pions.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/positrons.txt (100%) rename openmc/data/effective_dose/{ => icrp116}/protons.txt (100%) create mode 100644 openmc/data/effective_dose/icrp74/generate_photon_effective_dose.py create mode 100644 openmc/data/effective_dose/icrp74/neutrons.txt create mode 100644 openmc/data/effective_dose/icrp74/photons.txt delete mode 100644 openmc/data/endf.c delete mode 100644 openmc/data/reconstruct.pyx create mode 100644 openmc/lib/dagmc.py delete mode 100755 setup.py create mode 100644 src/random_ray/linear_source_domain.cpp create mode 100644 src/random_ray/moment_matrix.cpp create mode 100644 src/tallies/filter_musurface.cpp create mode 100644 tests/cpp_unit_tests/test_math.cpp rename tests/regression_tests/{random_ray_basic => filter_cellfrom}/__init__.py (100%) create mode 100644 tests/regression_tests/filter_cellfrom/inputs_true.dat create mode 100644 tests/regression_tests/filter_cellfrom/results_true.dat create mode 100644 tests/regression_tests/filter_cellfrom/test.py rename tests/regression_tests/{random_ray_vacuum => filter_musurface}/__init__.py (100%) create mode 100644 tests/regression_tests/filter_musurface/inputs_true.dat create mode 100644 tests/regression_tests/filter_musurface/results_true.dat create mode 100644 tests/regression_tests/filter_musurface/test.py create mode 100644 tests/regression_tests/random_ray_adjoint_fixed_source/__init__.py create mode 100644 tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat create mode 100644 tests/regression_tests/random_ray_adjoint_fixed_source/test.py create mode 100644 tests/regression_tests/random_ray_adjoint_k_eff/__init__.py rename tests/regression_tests/{random_ray_vacuum => random_ray_adjoint_k_eff}/inputs_true.dat (90%) create mode 100644 tests/regression_tests/random_ray_adjoint_k_eff/results_true.dat create mode 100644 tests/regression_tests/random_ray_adjoint_k_eff/test.py delete mode 100644 tests/regression_tests/random_ray_basic/results_true.dat delete mode 100644 tests/regression_tests/random_ray_basic/test.py create mode 100644 tests/regression_tests/random_ray_entropy/__init__.py create mode 100644 tests/regression_tests/random_ray_entropy/geometry.xml create mode 100644 tests/regression_tests/random_ray_entropy/materials.xml create mode 100644 tests/regression_tests/random_ray_entropy/mgxs.h5 create mode 100644 tests/regression_tests/random_ray_entropy/results_true.dat create mode 100644 tests/regression_tests/random_ray_entropy/settings.xml create mode 100644 tests/regression_tests/random_ray_entropy/test.py create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/__init__.py create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/cell/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/material/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/test.py create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_domain/universe/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/__init__.py create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/linear_xy/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_linear/test.py create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/False/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/True/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/__init__.py create mode 100644 tests/regression_tests/random_ray_fixed_source_normalization/test.py create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/__init__.py create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat create mode 100644 tests/regression_tests/random_ray_fixed_source_subcritical/test.py create mode 100644 tests/regression_tests/random_ray_k_eff/__init__.py rename tests/regression_tests/{random_ray_basic => random_ray_k_eff}/inputs_true.dat (98%) create mode 100644 tests/regression_tests/random_ray_k_eff/results_true.dat create mode 100644 tests/regression_tests/random_ray_k_eff/test.py create mode 100644 tests/regression_tests/random_ray_linear/__init__.py create mode 100644 tests/regression_tests/random_ray_linear/linear/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_linear/linear/results_true.dat create mode 100644 tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_linear/linear_xy/results_true.dat create mode 100644 tests/regression_tests/random_ray_linear/test.py delete mode 100644 tests/regression_tests/random_ray_vacuum/results_true.dat delete mode 100644 tests/regression_tests/random_ray_vacuum/test.py create mode 100644 tests/regression_tests/random_ray_volume_estimator/__init__.py create mode 100644 tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator/test.py create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/__init__.py create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat create mode 100644 tests/regression_tests/random_ray_volume_estimator_linear/test.py create mode 100644 tests/regression_tests/surface_source_write/__init__.py create mode 100644 tests/regression_tests/surface_source_write/_visualize.py create mode 100644 tests/regression_tests/surface_source_write/case-01/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-01/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-01/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-02/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-02/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-02/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-03/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-03/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-04/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-04/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-04/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-05/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-05/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-05/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-06/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-06/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-06/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-07/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-07/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-07/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-08/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-08/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-08/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-09/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-09/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-09/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-10/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-10/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-10/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-11/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-11/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-11/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-12/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-12/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-13/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-13/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-14/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-14/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-15/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-15/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-15/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-16/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-16/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-16/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-17/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-17/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-17/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-18/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-18/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-18/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-19/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-19/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-19/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-20/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-20/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-20/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-21/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-21/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-21/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-a01/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-a01/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d01/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d01/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d01/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d02/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d02/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d02/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d03/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d03/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d03/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d04/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d04/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d04/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d05/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d05/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d05/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d06/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d06/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d06/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d07/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d07/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d07/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-d08/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d08/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-d08/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-e01/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e01/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-e02/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e02/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e02/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/case-e03/inputs_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e03/results_true.dat create mode 100644 tests/regression_tests/surface_source_write/case-e03/surface_source_true.h5 create mode 100644 tests/regression_tests/surface_source_write/test.py create mode 100644 tests/unit_tests/cell_instances/test_hex_multilattice.py rename tests/unit_tests/{test_cell_instance.py => cell_instances/test_rect_multilattice.py} (84%) create mode 100644 tests/unit_tests/dagmc/test_model.py create mode 100644 tests/unit_tests/test_filter_musurface.py delete mode 100644 tests/unit_tests/test_math.py create mode 100644 tests/unit_tests/test_surface_source_write.py create mode 100644 tests/unit_tests/test_uniform_source_sampling.py create mode 120000 tests/unit_tests/weightwindows/test_mesh_tets.exo diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ddf49b89417..3b13abdbcb7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,5 +1,5 @@ --- -name: Feature request +name: Feature or enhancement request about: Suggest a new feature or enhancement to existing capabilities title: '' labels: '' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7aaaf1da4b0..c5051b8d0bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: ["3.10"] + python-version: ["3.11"] mpi: [n, y] omp: [n, y] dagmc: [n] @@ -35,40 +35,34 @@ jobs: vectfit: [n] include: - - python-version: "3.8" - omp: n - mpi: n - - python-version: "3.9" + - python-version: "3.12" omp: n mpi: n - - python-version: "3.11" - omp: n - mpi: n - - python-version: "3.12" + - python-version: "3.13" omp: n mpi: n - dagmc: y - python-version: "3.10" + python-version: "3.11" mpi: y omp: y - ncrystal: y - python-version: "3.10" + python-version: "3.11" mpi: n omp: n - libmesh: y - python-version: "3.10" + python-version: "3.11" mpi: y omp: y - libmesh: y - python-version: "3.10" + python-version: "3.11" mpi: n omp: y - event: y - python-version: "3.10" + python-version: "3.11" omp: y mpi: n - vectfit: y - python-version: "3.10" + python-version: "3.11" omp: n mpi: y name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }}, @@ -102,7 +96,6 @@ jobs: - name: Environment Variables run: | - echo "DAGMC_ROOT=$HOME/DAGMC" echo "OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml" >> $GITHUB_ENV echo "OPENMC_ENDF_DATA=$HOME/endf-b-vii.1" >> $GITHUB_ENV @@ -137,6 +130,11 @@ jobs: echo "$HOME/NJOY2016/build" >> $GITHUB_PATH $GITHUB_WORKSPACE/tools/ci/gha-install.sh + - name: display-config + shell: bash + run: | + openmc -v + - name: cache-xs uses: actions/cache@v4 with: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 98ca8b581da..7b69b64b626 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,11 +3,14 @@ version: 2 build: os: "ubuntu-20.04" tools: - python: "3.9" + python: "3.12" sphinx: configuration: docs/source/conf.py python: install: - - requirements: docs/requirements-rtd.txt + - method: pip + path: . + extra_requirements: + - docs diff --git a/CMakeLists.txt b/CMakeLists.txt index 1841251ff5d..575e45373ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 3.10 FATAL_ERROR) +cmake_minimum_required(VERSION 3.16 FATAL_ERROR) project(openmc C CXX) # Set version numbers set(OPENMC_VERSION_MAJOR 0) -set(OPENMC_VERSION_MINOR 14) +set(OPENMC_VERSION_MINOR 15) set(OPENMC_VERSION_RELEASE 1) set(OPENMC_VERSION ${OPENMC_VERSION_MAJOR}.${OPENMC_VERSION_MINOR}.${OPENMC_VERSION_RELEASE}) configure_file(include/openmc/version.h.in "${CMAKE_BINARY_DIR}/include/openmc/version.h" @ONLY) @@ -16,11 +16,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) -# Allow user to specify _ROOT variables -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) - cmake_policy(SET CMP0074 NEW) -endif() - # Enable correct usage of CXX_EXTENSIONS if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.22) cmake_policy(SET CMP0128 NEW) @@ -272,11 +267,6 @@ endif() # xtensor header-only library #=============================================================================== -# CMake 3.13+ will complain about policy CMP0079 unless it is set explicitly -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) - cmake_policy(SET CMP0079 NEW) -endif() - find_package_write_status(xtensor) if (NOT xtensor_FOUND) add_subdirectory(vendor/xtl) @@ -390,6 +380,8 @@ list(APPEND libopenmc_SOURCES src/random_ray/random_ray_simulation.cpp src/random_ray/random_ray.cpp src/random_ray/flat_source_domain.cpp + src/random_ray/linear_source_domain.cpp + src/random_ray/moment_matrix.cpp src/reaction.cpp src/reaction_product.cpp src/scattdata.cpp @@ -424,6 +416,7 @@ list(APPEND libopenmc_SOURCES src/tallies/filter_meshborn.cpp src/tallies/filter_meshsurface.cpp src/tallies/filter_mu.cpp + src/tallies/filter_musurface.cpp src/tallies/filter_particle.cpp src/tallies/filter_polar.cpp src/tallies/filter_sph_harm.cpp @@ -517,6 +510,14 @@ endif() if(OPENMC_USE_DAGMC) target_compile_definitions(libopenmc PRIVATE DAGMC) target_link_libraries(libopenmc dagmc-shared) + + if(OPENMC_USE_UWUW) + target_compile_definitions(libopenmc PRIVATE OPENMC_UWUW) + target_link_libraries(libopenmc uwuw-shared) + endif() +elseif(OPENMC_USE_UWUW) + set(OPENMC_USE_UWUW OFF) + message(FATAL_ERROR "DAGMC must be enabled when UWUW is enabled.") endif() if(OPENMC_USE_LIBMESH) @@ -553,11 +554,6 @@ if(OPENMC_USE_NCRYSTAL) target_link_libraries(libopenmc NCrystal::NCrystal) endif() -if (OPENMC_USE_UWUW) - target_compile_definitions(libopenmc PRIVATE UWUW) - target_link_libraries(libopenmc uwuw-shared) -endif() - #=============================================================================== # Log build info that this executable can report later #=============================================================================== @@ -580,9 +576,9 @@ target_compile_options(openmc PRIVATE ${cxxflags}) target_include_directories(openmc PRIVATE ${CMAKE_BINARY_DIR}/include) target_link_libraries(openmc libopenmc) -# Ensure C++14 standard is used and turn off GNU extensions -target_compile_features(openmc PUBLIC cxx_std_14) -target_compile_features(libopenmc PUBLIC cxx_std_14) +# Ensure C++17 standard is used and turn off GNU extensions +target_compile_features(openmc PUBLIC cxx_std_17) +target_compile_features(libopenmc PUBLIC cxx_std_17) set_target_properties(openmc libopenmc PROPERTIES CXX_EXTENSIONS OFF) #=============================================================================== diff --git a/Dockerfile b/Dockerfile index 107df4afe76..583682ef595 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ ENV DD_REPO='https://github.com/pshriwise/double-down' ENV DD_INSTALL_DIR=$HOME/Double_down # DAGMC variables -ENV DAGMC_BRANCH='v3.2.3' +ENV DAGMC_BRANCH='v3.2.4' ENV DAGMC_REPO='https://github.com/svalinn/DAGMC' ENV DAGMC_INSTALL_DIR=$HOME/DAGMC/ @@ -95,7 +95,7 @@ RUN cd $HOME \ RUN if [ "$build_dagmc" = "on" ]; then \ # Install addition packages required for DAGMC apt-get -y install libeigen3-dev libnetcdf-dev libtbb-dev libglfw3-dev \ - && pip install --upgrade numpy "cython<3.0" \ + && pip install --upgrade numpy \ # Clone and install EMBREE && mkdir -p $HOME/EMBREE && cd $HOME/EMBREE \ && git clone --single-branch -b ${EMBREE_TAG} --depth 1 ${EMBREE_REPO} \ @@ -111,7 +111,8 @@ RUN if [ "$build_dagmc" = "on" ]; then \ mkdir -p $HOME/MOAB && cd $HOME/MOAB \ && git clone --single-branch -b ${MOAB_TAG} --depth 1 ${MOAB_REPO} \ && mkdir build && cd build \ - && cmake ../moab -DENABLE_HDF5=ON \ + && cmake ../moab -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_HDF5=ON \ -DENABLE_NETCDF=ON \ -DBUILD_SHARED_LIBS=OFF \ -DENABLE_FORTRAN=OFF \ diff --git a/MANIFEST.in b/MANIFEST.in index afd016cb021..b73218af0dc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -26,8 +26,6 @@ recursive-include include *.h recursive-include include *.h.in recursive-include include *.hh recursive-include man *.1 -recursive-include openmc *.pyx -recursive-include openmc *.c recursive-include src *.cc recursive-include src *.cpp recursive-include src *.rnc diff --git a/cmake/Modules/FindLIBMESH.cmake b/cmake/Modules/FindLIBMESH.cmake index df5b5b9d863..df9208c18b2 100644 --- a/cmake/Modules/FindLIBMESH.cmake +++ b/cmake/Modules/FindLIBMESH.cmake @@ -14,8 +14,8 @@ if(DEFINED ENV{METHOD}) message(STATUS "Using environment variable METHOD to determine libMesh build: ${LIBMESH_PC_FILE}") endif() -include(FindPkgConfig) -set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${LIBMESH_PC}") -set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH True) +find_package(PkgConfig REQUIRED) + +set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH TRUE) pkg_check_modules(LIBMESH REQUIRED ${LIBMESH_PC_FILE}>=1.7.0 IMPORTED_TARGET) pkg_get_variable(LIBMESH_PREFIX ${LIBMESH_PC_FILE} prefix) diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 44a5e0d5a3f..b3b901de427 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -39,6 +39,6 @@ if(@OPENMC_USE_MCPL@) find_package(MCPL REQUIRED) endif() -if(@OPENMC_USE_UWUW@) - find_package(UWUW REQUIRED) -endif() +if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW}) + message(FATAL_ERROR "UWUW is enabled in OpenMC but the DAGMC installation discovered was not configured with UWUW.") +endif() \ No newline at end of file diff --git a/docs/requirements-rtd.txt b/docs/requirements-rtd.txt deleted file mode 100644 index df033351705..00000000000 --- a/docs/requirements-rtd.txt +++ /dev/null @@ -1,13 +0,0 @@ -sphinx==5.0.2 -sphinx_rtd_theme==1.0.0 -sphinx-numfig -jupyter -sphinxcontrib-katex -sphinxcontrib-svg2pdfconverter -numpy -scipy -h5py -pandas -uncertainties -matplotlib -lxml diff --git a/docs/source/conf.py b/docs/source/conf.py index 53d529f3dc0..726269f0b7c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -69,9 +69,9 @@ # built documents. # # The short X.Y version. -version = "0.14" +version = "0.15" # The full version, including alpha/beta/rc tags. -release = "0.14.1-dev" +release = "0.15.1-dev" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/devguide/contributing.rst b/docs/source/devguide/contributing.rst index c08b1a362f7..cda0313892b 100644 --- a/docs/source/devguide/contributing.rst +++ b/docs/source/devguide/contributing.rst @@ -109,7 +109,7 @@ Leadership Team The TC consists of the following individuals: - `Paul Romano `_ -- `Sterling Harper `_ +- `Patrick Shriwise `_ - `Adam Nelson `_ - `Benoit Forget `_ diff --git a/docs/source/devguide/index.rst b/docs/source/devguide/index.rst index d100fdcdfd9..2e131e09490 100644 --- a/docs/source/devguide/index.rst +++ b/docs/source/devguide/index.rst @@ -15,6 +15,7 @@ other related topics. contributing workflow styleguide + policies tests user-input docbuild diff --git a/docs/source/devguide/policies.rst b/docs/source/devguide/policies.rst new file mode 100644 index 00000000000..2cf31998764 --- /dev/null +++ b/docs/source/devguide/policies.rst @@ -0,0 +1,35 @@ +.. _devguide_policies: + +======== +Policies +======== + +--------------------- +Python Version Policy +--------------------- + +OpenMC follows the Scientific Python Ecosystem Coordination guidelines `SPEC 0 +`_ on minimum supported +versions, which recommends that support for Python versions be dropped 3 years +after their initial release. + +------------------- +C++ Standard Policy +------------------- + +C++ code in OpenMC must conform to the most recent C++ standard that is fully +supported in the `version of the gcc compiler +`_ that is distributed with the +oldest version of Ubuntu that is still within its `standard support period +`_. Ubuntu 20.04 LTS will be supported +through April 2025 and is distributed with gcc 9.3.0, which fully supports the +C++17 standard. + +-------------------- +CMake Version Policy +-------------------- + +Similar to the C++ standard policy, the minimum supported version of CMake +corresponds to whatever version is distributed with the oldest version of Ubuntu +still within its standard support period. Ubuntu 20.04 LTS is distributed with +CMake 3.16. diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index 3c71d14ad92..b266f5f0222 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -40,7 +40,7 @@ Follow the `C++ Core Guidelines`_ except when they conflict with another guideline listed here. For convenience, many important guidelines from that list are repeated here. -Conform to the C++14 standard. +Conform to the C++17 standard. Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It is more difficult to comment out a large section of code that uses C-style diff --git a/docs/source/io_formats/geometry.rst b/docs/source/io_formats/geometry.rst index ac48e48d2d1..6d0a37a24fa 100644 --- a/docs/source/io_formats/geometry.rst +++ b/docs/source/io_formats/geometry.rst @@ -407,13 +407,33 @@ Each ```` element can have the following attributes or sub-eleme *Default*: None + :material_overrides: + This element contains information on material overrides to be applied to the + DAGMC universe. It has the following attributes and sub-elements: - .. note:: A geometry.xml file containing only a DAGMC model for a file named `dagmc.h5m` (no CSG) - looks as follows + :cell: + Material override information for a single cell. It contains the following + attributes and sub-elements: - .. code-block:: xml + :id: + The cell ID in the DAGMC geometry for which the material override will + apply. - - - - + :materials: + A list of material IDs that will apply to instances of the cell. If the + list contains only one ID, it will replace the original material + assignment of all instances of the DAGMC cell. If the list contains more + than one material, each material ID of the list will be assigned to the + various instances of the DAGMC cell. + + *Default*: None + +.. note:: A geometry.xml file containing only a DAGMC model for a file named + `dagmc.h5m` (no CSG) looks as follows: + + .. code-block:: xml + + + + + diff --git a/docs/source/io_formats/plots.rst b/docs/source/io_formats/plots.rst index e6b75eafcb6..04d5720c84f 100644 --- a/docs/source/io_formats/plots.rst +++ b/docs/source/io_formats/plots.rst @@ -151,6 +151,18 @@ attributes or sub-elements. These are not used in "voxel" plots: *Default*: 255 255 255 (white) + :show_overlaps: + Indicates whether overlapping regions of different cells are shown. + + *Default*: None + + :overlap_color: + Specifies the RGB color of overlapping regions of different cells. Does not + do anything if ``show_overlaps`` is "false" or not specified. Should be 3 + integers separated by spaces. + + *Default*: 255 0 0 (red) + :meshlines: The ``meshlines`` sub-element allows for plotting the boundaries of a regular mesh on top of a plot. Only one ``meshlines`` element is allowed per diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index ae3266dabed..34b73fc55c5 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -284,11 +284,11 @@ then, OpenMC will only use up to the :math:`P_1` data. .. note:: This element is not used in the continuous-energy :ref:`energy_mode`. ------------------------- -```` Element ------------------------- +-------------------------------- +```` Element +-------------------------------- -The ```` element indicates the number of times a particle can split during a history. +The ```` element indicates the number of times a particle can split during a history. *Default*: 1000 @@ -579,24 +579,38 @@ attributes/sub-elements: :type: The type of spatial distribution. Valid options are "box", "fission", - "point", "cartesian", "cylindrical", and "spherical". A "box" spatial - distribution has coordinates sampled uniformly in a parallelepiped. A - "fission" spatial distribution samples locations from a "box" + "point", "cartesian", "cylindrical", "spherical", "mesh", and "cloud". + + A "box" spatial distribution has coordinates sampled uniformly in a + parallelepiped. + + A "fission" spatial distribution samples locations from a "box" distribution but only locations in fissionable materials are accepted. + A "point" spatial distribution has coordinates specified by a triplet. + A "cartesian" spatial distribution specifies independent distributions of - x-, y-, and z-coordinates. A "cylindrical" spatial distribution specifies - independent distributions of r-, phi-, and z-coordinates where phi is the - azimuthal angle and the origin for the cylindrical coordinate system is - specified by origin. A "spherical" spatial distribution specifies - independent distributions of r-, cos_theta-, and phi-coordinates where - cos_theta is the cosine of the angle with respect to the z-axis, phi is - the azimuthal angle, and the sphere is centered on the coordinate - (x0,y0,z0). A "mesh" spatial distribution samples source sites from a mesh element + x-, y-, and z-coordinates. + + A "cylindrical" spatial distribution specifies independent distributions + of r-, phi-, and z-coordinates where phi is the azimuthal angle and the + origin for the cylindrical coordinate system is specified by origin. + + A "spherical" spatial distribution specifies independent distributions of + r-, cos_theta-, and phi-coordinates where cos_theta is the cosine of the + angle with respect to the z-axis, phi is the azimuthal angle, and the + sphere is centered on the coordinate (x0,y0,z0). + + A "mesh" spatial distribution samples source sites from a mesh element based on the relative strengths provided in the node. Source locations within an element are sampled isotropically. If no strengths are provided, the space within the mesh is uniformly sampled. + A "cloud" spatial distribution samples source sites from a list of spatial + positions provided in the node, based on the relative strengths provided + in the node. If no strengths are provided, the positions are uniformly + sampled. + *Default*: None :parameters: @@ -662,6 +676,26 @@ attributes/sub-elements: For "cylindrical and "spherical" distributions, this element specifies the coordinates for the origin of the coordinate system. + :mesh_id: + For "mesh" spatial distributions, this element specifies which mesh ID to + use for the geometric description of the mesh. + + :coords: + For "cloud" distributions, this element specifies a list of coordinates + for each of the points in the cloud. + + :strengths: + For "mesh" and "cloud" spatial distributions, this element specifies the + relative source strength of each mesh element or each point in the cloud. + + :volume_normalized: + For "mesh" spatial distrubtions, this optional boolean element specifies + whether the vector of relative strengths should be multiplied by the mesh + element volume. This is most common if the strengths represent a source + per unit volume. + + *Default*: false + :angle: An element specifying the angular distribution of source sites. This element has the following attributes: @@ -902,7 +936,12 @@ attributes/sub-elements: The ```` element triggers OpenMC to bank particles crossing certain surfaces and write out the source bank in a separate file called -``surface_source.h5``. This element has the following attributes/sub-elements: +``surface_source.h5``. One or multiple surface IDs and one cell ID can be used +to select the surfaces of interest. If no surface IDs are declared, every surface +of the model is eligible to bank particles. In that case, a cell ID (using +either the ``cell``, ``cellfrom`` or ``cellto`` attributes) can be used to select +every surface of a specific cell. This element has the following +attributes/sub-elements: :surface_ids: A list of integers separated by spaces indicating the unique IDs of surfaces @@ -918,6 +957,15 @@ certain surfaces and write out the source bank in a separate file called *Default*: None + :max_source_files: + An integer value indicating the number of surface source files to be written + containing the maximum number of particles each. The surface source bank + will be cleared in simulation memory each time a surface source file is + written. By default a ``surface_source.h5`` file will be created when the + maximum number of saved particles is reached. + + *Default*: 1 + :mcpl: An optional boolean which indicates if the banked particles should be written to a file in the MCPL_-format instead of the native HDF5-based @@ -928,6 +976,34 @@ certain surfaces and write out the source bank in a separate file called .. _MCPL: https://mctools.github.io/mcpl/mcpl.pdf + :cell: + An integer representing the cell ID used to determine if particles crossing + identified surfaces are to be banked. Particles coming from or going to this + declared cell will be banked if they cross the identified surfaces. + + *Default*: None + + :cellfrom: + An integer representing the cell ID used to determine if particles crossing + identified surfaces are to be banked. Particles coming from this declared + cell will be banked if they cross the identified surfaces. + + *Default*: None + + :cellto: + An integer representing the cell ID used to determine if particles crossing + identified surfaces are to be banked. Particles going to this declared cell + will be banked if they cross the identified surfaces. + + *Default*: None + +.. note:: The ``cell``, ``cellfrom`` and ``cellto`` attributes cannot be + used simultaneously. + +.. note:: Surfaces with boundary conditions that are not "transmission" or "vacuum" + are not eligible to store any particles when using ``cell``, ``cellfrom`` + or ``cellto`` attributes. It is recommended to use surface IDs instead. + ------------------------------ ```` Element ------------------------------ diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index f61e967ca8a..fd00a3e7735 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -72,7 +72,10 @@ The current version of the statepoint file format is 18.1. **/tallies/meshes/mesh /** -:Datasets: - **type** (*char[]*) -- Type of mesh. +:Attributes: - **id** (*int*) -- ID of the mesh + +:Datasets: - **name** (*char[]*) -- Name of the mesh. + - **type** (*char[]*) -- Type of mesh. - **dimension** (*int*) -- Number of mesh cells in each dimension. - **Regular Mesh Only:** - **lower_left** (*double[]*) -- Coordinates of lower-left corner of diff --git a/docs/source/io_formats/tallies.rst b/docs/source/io_formats/tallies.rst index 78101ab668d..9f29a949acb 100644 --- a/docs/source/io_formats/tallies.rst +++ b/docs/source/io_formats/tallies.rst @@ -109,7 +109,7 @@ The ```` element accepts the following sub-elements: prematurely if there are no hits in any bins at the first evalulation. It is the user's responsibility to specify enough particles per batch to get a nonzero score in at least one bin. - + *Default*: False :scores: @@ -329,6 +329,11 @@ If a mesh is desired as a filter for a tally, it must be specified in a separate element with the tag name ````. This element has the following attributes/sub-elements: + :name: + An optional string name to identify the mesh in output files. + + *Default*: "" + :type: The type of mesh. This can be either "regular", "rectilinear", "cylindrical", "spherical", or "unstructured". diff --git a/docs/source/methods/cross_sections.rst b/docs/source/methods/cross_sections.rst index 2cafa869162..21a1ef8dfeb 100644 --- a/docs/source/methods/cross_sections.rst +++ b/docs/source/methods/cross_sections.rst @@ -290,7 +290,7 @@ scattering information in the water while the fuel can be simulated with linear or even isotropic scattering. .. _logarithmic mapping technique: - https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf + https://mcnp.lanl.gov/pdf_files/TechReport_2014_LANL_LA-UR-14-24530_Brown.pdf .. _Hwang: https://doi.org/10.13182/NSE87-A16381 .. _Josey: https://doi.org/10.1016/j.jcp.2015.08.013 .. _WMP Library: https://github.com/mit-crpg/WMP_Library diff --git a/docs/source/methods/eigenvalue.rst b/docs/source/methods/eigenvalue.rst index 41bf8654920..8abcc09574b 100644 --- a/docs/source/methods/eigenvalue.rst +++ b/docs/source/methods/eigenvalue.rst @@ -55,15 +55,17 @@ in :ref:`fission-bank-algorithms`. Source Convergence Issues ------------------------- +.. _methods-shannon-entropy: + Diagnosing Convergence with Shannon Entropy ------------------------------------------- As discussed earlier, it is necessary to converge both :math:`k_{eff}` and the source distribution before any tallies can begin. Moreover, the convergence rate -of the source distribution is in general slower than that of -:math:`k_{eff}`. One should thus examine not only the convergence of -:math:`k_{eff}` but also the convergence of the source distribution in order to -make decisions on when to start active batches. +of the source distribution is in general slower than that of :math:`k_{eff}`. +One should thus examine not only the convergence of :math:`k_{eff}` but also the +convergence of the source distribution in order to make decisions on when to +start active batches. However, the representation of the source distribution makes it a bit more difficult to analyze its convergence. Since :math:`k_{eff}` is a scalar @@ -108,6 +110,13 @@ at plots of :math:`k_{eff}` and the Shannon entropy. A number of methods have been proposed (see e.g. [Romano]_, [Ueki]_), but each of these is not without problems. +Shannon entropy is calculated differently for the random ray solver, as +described :ref:`in the random ray theory section +`. Additionally, as the Shannon entropy only +serves as a diagnostic tool for convergence of the fission source distribution, +there is currently no diagnostic to determine if the scattering source +distribution in random ray is converged. + --------------------------- Uniform Fission Site Method --------------------------- @@ -142,7 +151,7 @@ than unity. By ensuring that the expected number of fission sites in each mesh cell is constant, the collision density across all cells, and hence the variance of tallies, is more uniform than it would be otherwise. -.. _Shannon entropy: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-06-3737.pdf +.. _Shannon entropy: https://mcnp.lanl.gov/pdf_files/TechReport_2006_LANL_LA-UR-06-3737_Brown.pdf .. [Lieberoth] J. Lieberoth, "A Monte Carlo Technique to Solve the Static Eigenvalue Problem of the Boltzmann Transport Equation," *Nukleonik*, **11**, diff --git a/docs/source/methods/random_numbers.rst b/docs/source/methods/random_numbers.rst index 0cefc9156bc..4376bbdb0c5 100644 --- a/docs/source/methods/random_numbers.rst +++ b/docs/source/methods/random_numbers.rst @@ -7,7 +7,7 @@ Random Number Generation In order to sample probability distributions, one must be able to produce random numbers. The standard technique to do this is to generate numbers on the interval :math:`[0,1)` from a deterministic sequence that has properties that -make it appear to be random, e.g. being uniformly distributed and not exhibiting +make it appear to be random, e.g., being uniformly distributed and not exhibiting correlation between successive terms. Since the numbers produced this way are not truly "random" in a strict sense, they are typically referred to as pseudorandom numbers, and the techniques used to generate them are pseudorandom @@ -15,6 +15,11 @@ number generators (PRNGs). Numbers sampled on the unit interval can then be transformed for the purpose of sampling other continuous or discrete probability distributions. +There are many different algorithms for pseudorandom number generation. OpenMC +currently uses `permuted congruential generator`_ (PCG), which builds on top of +the simpler linear congruential generator (LCG). Both algorithms are described +below. + ------------------------------ Linear Congruential Generators ------------------------------ @@ -37,8 +42,8 @@ be generated with a method chosen at random. Some theory should be used." Typically, :math:`M` is chosen to be a power of two as this enables :math:`x \mod M` to be performed using the bitwise AND operator with a bit mask. The constants for the linear congruential generator used by default in OpenMC are -:math:`g = 2806196910506780709`, :math:`c = 1`, and :math:`M = 2^{63}` (see -`L'Ecuyer`_). +:math:`g = 2806196910506780709`, :math:`c = 1`, and :math:`M = 2^{63}` (from +`L'Ecuyer `_). Skip-ahead Capability --------------------- @@ -50,7 +55,8 @@ want to skip ahead :math:`N` random numbers and :math:`N` is large, the cost of sampling :math:`N` random numbers to get to that position may be prohibitively expensive. Fortunately, algorithms have been developed that allow us to skip ahead in :math:`O(\log_2 N)` operations instead of :math:`O(N)`. One algorithm -to do so is described in a paper by Brown_. This algorithm relies on the following +to do so is described in a `paper by Brown +`_. This algorithm relies on the following relationship: .. math:: @@ -58,15 +64,26 @@ relationship: \xi_{i+k} = g^k \xi_i + c \frac{g^k - 1}{g - 1} \mod M -Note that equation :eq:`lcg-skipahead` has the same general form as equation :eq:`lcg`, so -the idea is to determine the new multiplicative and additive constants in -:math:`O(\log_2 N)` operations. +Note that equation :eq:`lcg-skipahead` has the same general form as equation +:eq:`lcg`, so the idea is to determine the new multiplicative and additive +constants in :math:`O(\log_2 N)` operations. -.. only:: html - .. rubric:: References +-------------------------------- +Permuted Congruential Generators +-------------------------------- +The `permuted congruential generator`_ (PCG) algorithm aims to improve upon the +LCG algorithm by permuting the output. The algorithm works on the basic +principle of first advancing the generator state using the LCG algorithm and +then applying a permutation function on the LCG state to obtain the output. This +results in increased statistical quality as measured by common statistical tests +while exhibiting a very small performance overhead relative to the LCG algorithm +and an equivalent memory footprint. For further details, see the original +technical report by `O'Neill +`_. OpenMC uses the +PCG-RXS-M-XS variant with a 64-bit state and 64-bit output. -.. _L'Ecuyer: https://doi.org/10.1090/S0025-5718-99-00996-5 -.. _Brown: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/anl-rn-arb-stride.pdf .. _linear congruential generator: https://en.wikipedia.org/wiki/Linear_congruential_generator + +.. _permuted congruential generator: https://en.wikipedia.org/wiki/Permuted_congruential_generator diff --git a/docs/source/methods/random_ray.rst b/docs/source/methods/random_ray.rst index aa436784766..c827c351dd0 100644 --- a/docs/source/methods/random_ray.rst +++ b/docs/source/methods/random_ray.rst @@ -10,7 +10,7 @@ Random Ray What is Random Ray? ------------------- -`Random ray `_ is a stochastic transport method, closely related to +`Random ray `_ is a stochastic transport method, closely related to the deterministic Method of Characteristics (MOC) [Askew-1972]_. Rather than each ray representing a single neutron as in Monte Carlo, it represents a characteristic line through the simulation geometry upon which the transport @@ -82,7 +82,7 @@ Random Ray Numerical Derivation In this section, we will derive the numerical basis for the random ray solver mode in OpenMC. The derivation of random ray is also discussed in several papers -(`1 `_, `2 `_, `3 `_), and some of those +(`1 `_, `2 `_, `3 `_), and some of those derivations are reproduced here verbatim. Several extensions are also made to add clarity, particularly on the topic of OpenMC's treatment of cell volumes in the random ray solver. @@ -94,17 +94,17 @@ Method of Characteristics The Boltzmann neutron transport equation is a partial differential equation (PDE) that describes the angular flux within a system. It is a balance equation, with the streaming and absorption terms typically appearing on the left hand -side, which are balanced by the scattering source and fission source terms on -the right hand side. +side, which are balanced by the scattering source, fission, and fixed source +terms on the right hand side. .. math:: :label: transport - \begin{align*} + \begin{aligned} \mathbf{\Omega} \cdot \mathbf{\nabla} \psi(\mathbf{r},\mathbf{\Omega},E) & + \Sigma_t(\mathbf{r},E) \psi(\mathbf{r},\mathbf{\Omega},E) = \\ & \int_0^\infty d E^\prime \int_{4\pi} d \Omega^{\prime} \Sigma_s(\mathbf{r},\mathbf{\Omega}^\prime \rightarrow \mathbf{\Omega}, E^\prime \rightarrow E) \psi(\mathbf{r},\mathbf{\Omega}^\prime, E^\prime) \\ & + \frac{\chi(\mathbf{r}, E)}{4\pi k_{eff}} \int_0^\infty dE^\prime \nu \Sigma_f(\mathbf{r},E^\prime) \int_{4\pi}d \Omega^\prime \psi(\mathbf{r},\mathbf{\Omega}^\prime,E^\prime) - \end{align*} + \end{aligned} In Equation :eq:`transport`, :math:`\psi` is the angular neutron flux. This parameter represents the total distance traveled by all neutrons in a particular @@ -218,9 +218,9 @@ Following the multigroup discretization, another assumption made is that a large and complex problem can be broken up into small constant cross section regions, and that these regions have group dependent, flat, isotropic sources (fission and scattering), :math:`Q_g`. Anisotropic as well as higher order sources are -also possible with MOC-based methods but are not used yet in OpenMC for -simplicity. With these key assumptions, the multigroup MOC form of the neutron -transport equation can be written as in Equation :eq:`moc_final`. +also possible with MOC-based methods. With these key assumptions, the multigroup +MOC form of the neutron transport equation can be written as in Equation +:eq:`moc_final`. .. math:: :label: moc_final @@ -287,7 +287,7 @@ final expression for the average angular flux for a ray crossing a region as: .. math:: :label: average_psi_final - \overline{\psi}_{r,i,g} = \frac{Q_{i,g}}{\Sigma_{t,i,g}} + \frac{\Delta \psi_{r,g}}{\ell_r \Sigma_{t,i,g}} + \overline{\psi}_{r,i,g} = \frac{Q_{i,g}}{\Sigma_{t,i,g}} + \frac{\Delta \psi_{r,g}}{\ell_r \Sigma_{t,i,g}}. ~~~~~~~~~~~ Random Rays @@ -411,6 +411,8 @@ which when partially simplified becomes: Note that there are now four (seemingly identical) volume terms in this equation. +.. _methods_random_ray_vol: + ~~~~~~~~~~~~~~ Volume Dilemma ~~~~~~~~~~~~~~ @@ -426,7 +428,7 @@ of terms. Mathematically, such cancellation allows us to arrive at the following This derivation appears mathematically sound at first glance but unfortunately raises a serious issue as discussed in more depth by `Tramm et al. -`_ and `Cosgrove and Tramm `_. Namely, the second +`_ and `Cosgrove and Tramm `_. Namely, the second term: .. math:: @@ -438,9 +440,11 @@ features stochastic variables (the sums over random ray lengths and angular fluxes) in both the numerator and denominator, making it a stochastic ratio estimator, which is inherently biased. In practice, usage of the naive estimator does result in a biased, but "consistent" estimator (i.e., it is biased, but -the bias tends towards zero as the sample size increases). Experimentally, the -right answer can be obtained with this estimator, though a very fine ray density -is required to eliminate the bias. +the bias tends towards zero as the sample size increases). Empirically, this +bias tends to effect eigenvalue calculations much more significantly than in +fixed source simulations. Experimentally, the right answer can be obtained with +this estimator, though for eigenvalue simulations a very fine ray density is +required to eliminate the bias. How might we solve the biased ratio estimator problem? While there is no obvious way to alter the numerator term (which arises from the characteristic @@ -461,17 +465,17 @@ replace the actual tracklength that was accumulated inside that FSR each iteration with the expected value. If we know the analytical volumes, then those can be used to directly compute -the expected value of the tracklength in each cell. However, as the analytical -volumes are not typically known in OpenMC due to the usage of user-defined -constructive solid geometry, we need to source this quantity from elsewhere. An -obvious choice is to simply accumulate the total tracklength through each FSR -across all iterations (batches) and to use that sum to compute the expected -average length per iteration, as: +the expected value of the tracklength in each cell, :math:`L_{avg}`. However, as +the analytical volumes are not typically known in OpenMC due to the usage of +user-defined constructive solid geometry, we need to source this quantity from +elsewhere. An obvious choice is to simply accumulate the total tracklength +through each FSR across all iterations (batches) and to use that sum to compute +the expected average length per iteration, as: .. math:: - :label: sim_estimator + :label: L_avg - \sum\limits^{}_{i} \ell_i \approx \frac{\sum\limits^{B}_{b}\sum\limits^{N_i}_{r} \ell_{b,r} }{B} + \sum\limits^{}_{i} \ell_i \approx L_{avg} = \frac{\sum\limits^{B}_{b}\sum\limits^{N_i}_{r=1} \ell_{b,r} }{B} where :math:`b` is a single batch in :math:`B` total batches simulated so far. @@ -484,7 +488,7 @@ averaged" estimator is therefore: .. math:: :label: phi_sim - \phi_{i,g}^{simulation} = \frac{Q_{i,g} }{\Sigma_{t,i,g}} + \frac{\sum\limits_{r=1}^{N_i} \Delta \psi_{r,g}}{\Sigma_{t,i,g} \frac{\sum\limits^{B}_{b}\sum\limits^{N_i}_{r} \ell_{b,r} }{B}} + \phi_{i,g}^{simulation} = \frac{Q_{i,g} }{\Sigma_{t,i,g}} + \frac{\sum\limits_{r=1}^{N_i} \Delta \psi_{r,g}}{\Sigma_{t,i,g} L_{avg}} In practical terms, the "simulation averaged" estimator is virtually indistinguishable numerically from use of the true analytical volume to estimate @@ -498,17 +502,81 @@ in which case the denominator served as a normalization term for the numerator integral in Equation :eq:`integral`. Essentially, we have now used a different term for the volume in the numerator as compared to the normalizing volume in the denominator. The inevitable mismatch (due to noise) between these two -quantities results in a significant increase in variance. Notably, the same -problem occurs if using a tracklength estimate based on the analytical volume, -as again the numerator integral and the normalizing denominator integral no -longer match on a per-iteration basis. - -In practice, the simulation averaged method does completely remove the bias, -though at the cost of a notable increase in variance. Empirical testing reveals -that on most problems, the simulation averaged estimator does win out overall in -numerical performance, as a much coarser quadrature can be used resulting in -faster runtimes overall. Thus, OpenMC uses the simulation averaged estimator in -its random ray mode. +quantities results in a significant increase in variance, and can even result in +the generation of negative fluxes. Notably, the same problem occurs if using a +tracklength estimate based on the analytical volume, as again the numerator +integral and the normalizing denominator integral no longer match on a +per-iteration basis. + +In practice, the simulation averaged method does completely remove the bias seen +when using the naive estimator, though at the cost of a notable increase in +variance. Empirical testing reveals that on most eigenvalue problems, the +simulation averaged estimator does win out overall in numerical performance, as +a much coarser quadrature can be used resulting in faster runtimes overall. +Thus, OpenMC uses the simulation averaged estimator as default in its random ray +mode for eigenvalue solves. + +OpenMC also features a "hybrid" volume estimator that uses the naive estimator +for all regions containing an external (fixed) source term. For all other +source regions, the "simulation averaged" estimator is used. This typically achieves +a best of both worlds result, with the benefits of the low bias simulation averaged +estimator in most regions, while preventing instability and/or large biases in regions +with external source terms via use of the naive estimator. In general, it is +recommended to use the "hybrid" estimator, which is the default method used +in OpenMC. If instability is encountered despite high ray densities, then +the naive estimator may be preferable. + +A table that summarizes the pros and cons, as well as recommendations for +different use cases, is given in the :ref:`volume +estimators` section of the user guide. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +What Happens When a Source Region is Missed? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Given the stochastic nature of random ray, when low ray densities are used it is +common for small source regions to occasionally not be hit by any rays in a +particular power iteration :math:`n`. This naturally collapses the flux estimate +in that cell for the iteration from Equation :eq:`phi_naive` to: + +.. math:: + :label: phi_missed_one + + \phi_{i,g,n}^{missed} = \frac{Q_{i,g,n} }{\Sigma_{t,i,g}} + +as the streaming operator has gone to zero. While this is obviously innacurate +as it ignores transport, for most problems where the region is only occasionally +missed this estimator does not tend to introduce any significant bias. + +However, in cases where the total cross section in the region is very small +(e.g., a void-like material) and where a strong external fixed source has been +placed, then this treatment causes major issues. In this pathological case, the +lack of transport forces the entirety of the fixed source to effectively be +contained and collided within the cell, which for a low cross section region is +highly unphysical. The net effect is that a very high estimate of the flux +(often orders of magnitude higher than is expected) is generated that iteration, +which cannot be washed out even with hundreds or thousands of iterations. Thus, +huge biases are often seen in spatial tallies containing void-like regions with +external sources unless a high enough ray density is used such that all source +regions are always hit each iteration. This is particularly problematic as +external sources placed in void-like regions are very common in many types of +fixed source analysis. + +For regions where external sources are present, to eliminate this bias it is +therefore preferable to simply use the previous iteration's estimate of the flux +in that cell, as: + +.. math:: + :label: phi_missed_two + + \phi_{i,g,n}^{missed} = \phi_{i,g,n-1} . + +When linear sources are present, the flux moments from the previous iteration +are used in the same manner. While this introduces some small degree of +correlation to the simulation, for miss rates on the order of a few percent the +correlations are trivial and the bias is eliminated. Thus, in OpenMC the +previous iteration's scalar flux estimate is applied to cells that are missed +where there is an external source term present within the cell. ~~~~~~~~~~~~~~~ Power Iteration @@ -522,8 +590,8 @@ make their traversals, and summing these contributions up as in Equation improve the estimate of the source and scalar flux over many iterations, given that our initial starting source will just be a guess? -The source :math:`Q^{n}` for iteration :math:`n` can be inferred -from the scalar flux from the previous iteration :math:`n-1` as: +In an eigenvalue simulation, the source :math:`Q^{n}` for iteration :math:`n` +can be inferred from the scalar flux from the previous iteration :math:`n-1` as: .. math:: :label: source_update @@ -535,7 +603,7 @@ where :math:`Q^{n}(i, g)` is the total source (fission + scattering) in region :math:`g` must be computed by summing over the contributions from all groups :math:`g' \in G`. -In a similar manner, the eigenvalue for iteration :math:`n` can be computed as: +The eigenvalue for iteration :math:`n` can be computed as: .. math:: :label: eigenvalue_update @@ -561,21 +629,33 @@ total spatial- and energy-integrated fission rate :math:`F^{n-1}` in iteration Notably, the volume term :math:`V_i` appears in the eigenvalue update equation. The same logic applies to the treatment of this term as was discussed earlier. -In OpenMC, we use the "simulation averaged" volume derived from summing over all -ray tracklength contributions to a FSR over all iterations and dividing by the -total integration tracklength to date. Thus, Equation :eq:`fission_source` -becomes: +In OpenMC, we use the "simulation averaged" volume (Equation :eq:`L_avg`) +derived from summing over all ray tracklength contributions to a FSR over all +iterations and dividing by the total integration tracklength to date. Thus, +Equation :eq:`fission_source` becomes: .. math:: :label: fission_source_volumed - F^n = \sum\limits^{M}_{i} \left( \frac{\sum\limits^{B}_{b}\sum\limits^{N_i}_{r} \ell_{b,r} }{B} \sum\limits^{G}_{g} \nu \Sigma_f(i, g) \phi^{n}(g) \right) + F^n = \sum\limits^{M}_{i} \left( L_{avg} \sum\limits^{G}_{g} \nu \Sigma_f(i, g) \phi^{n}(g) \right) and a similar substitution can be made to update Equation :eq:`fission_source_prev` . In OpenMC, the most up-to-date version of the volume estimate is used, such that the total fission source from the previous iteration (:math:`n-1`) is also recomputed each iteration. +In a fixed source simulation, the fission source is replaced by a user specified +fixed source term :math:`Q_\text{fixed}(i,E)`, which is defined for each FSR and +energy group. This additional source term is applied at this stage for +generating the next iteration's source estimate as: + +.. math:: + :label: fixed_source_update + + Q^{n}(i, g) = Q_\text{fixed}(i,g) + \sum\limits^{G}_{g'} \Sigma_{s}(i,g,g') \phi^{n-1}(g') + +and no eigenvalue is computed. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Ray Starting Conditions and Inactive Length ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -593,7 +673,7 @@ guess can be made by taking the isotropic source from the FSR the ray was sampled in, direct usage of this quantity would result in significant bias and error being imparted on the simulation. -Thus, an `on-the-fly approximation method `_ was developed (known +Thus, an `on-the-fly approximation method `_ was developed (known as the "dead zone"), where the first several mean free paths of a ray are considered to be "inactive" or "read only". In this sense, the angular flux is solved for using the MOC equation, but the ray does not "tally" any scalar flux @@ -726,6 +806,8 @@ scalar flux value for the FSR). global::volume[fsr] += s; } +.. _methods_random_tallies: + ------------------------ How are Tallies Handled? ------------------------ @@ -733,6 +815,7 @@ How are Tallies Handled? Most tallies, filters, and scores that you would expect to work with a multigroup solver like random ray should work. For example, you can define 3D mesh tallies with energy filters and flux, fission, and nu-fission scores, etc. + There are some restrictions though. For starters, it is assumed that all filter mesh boundaries will conform to physical surface boundaries (or lattice boundaries) in the simulation geometry. It is acceptable for multiple cells @@ -742,6 +825,284 @@ behavior if a single simulation cell is able to score to multiple filter mesh cells. In the future, the capability to fully support mesh tallies may be added to OpenMC, but for now this restriction needs to be respected. +Flux tallies are handled slightly differently than in Monte Carlo. By default, +in MC, flux tallies are reported in units of tracklength (cm), so must be +manually normalized by volume by the user to produce an estimate of flux in +units of cm\ :sup:`-2`\. Alternatively, MC flux tallies can be normalized via a +separated volume calculation process as discussed in the :ref:`Volume +Calculation Section`. In random ray, as the volumes are +computed on-the-fly as part of the transport process, the flux tallies can +easily be reported either in units of flux (cm\ :sup:`-2`\) or tracklength (cm). +By default, the unnormalized flux values (units of cm) will be reported. If the +user wishes to received volume normalized flux tallies, then an option for this +is available, as described in the :ref:`User Guide`. + +-------------- +Linear Sources +-------------- + +Instead of making a flat source approximation, as in the previous section, a +Linear Source (LS) approximation can be used. Different LS approximations have +been developed; the OpenMC implementation follows the MOC LS scheme described by +`Ferrer `_. The LS source along a characteristic is given by: + +.. math:: + :label: linear_source + + Q_{i,g}(s) = \bar{Q}_{r,i,g} + \hat{Q}_{r,i,g}(s-\ell_{r}/2), + +where the source, :math:`Q_{i,g}(s)`, varies linearly along the track and +:math:`\bar{Q}_{r,i,g}` and :math:`\hat{Q}_{r,i,g}` are track specific source +terms to define shortly. Integrating the source, as done in Equation +:eq:`moc_final`, leads to + +.. math:: + :label: lsr_attenuation + + \psi^{out}_{r,g}=\psi^{in}_{r,g} + \left(\frac{\bar{Q}_{r, i, g}}{\Sigma_{\mathrm{t}, i, g}}-\psi^{in}_{r,g}\right) + F_{1}\left(\tau_{i,g}\right)+\frac{\hat{Q}_{r, i, g}^{g}}{2\left(\Sigma_{\mathrm{t}, i,g}\right)^{2}} F_{2}\left(\tau_{i,g}\right), + +where for simplicity the term :math:`\tau_{i,g}` and the expoentials :math:`F_1` +and :math:`F_2` are introduced, given by: + +.. math:: + :label: tau + + \tau_{i,g} = \Sigma_{\mathrm{t,i,g}} \ell_{r} + +.. math:: + :label: f1 + + F_1(\tau) = 1 - e^{-\tau}, + +and + +.. math:: + :label: f2 + + F_{2}\left(\tau\right) = 2\left[\tau-F_{1}\left(\tau\right)\right]-\tau F_{1}\left(\tau\right). + + +To solve for the track specific source terms in Equation :eq:`linear_source` we +first define a local reference frame. If we now refer to :math:`\mathbf{r}` as +the global coordinate and introduce the source region specific coordinate +:math:`\mathbf{u}` such that, + +.. math:: + :label: local_coord + + \mathbf{u}_{r} = \mathbf{r}-\mathbf{r}_{\mathrm{c}}, + +where :math:`\mathbf{r}_{\mathrm{c}}` is the centroid of the source region of +interest. In turn :math:`\mathbf{u}_{r,\mathrm{c}}` and :math:`\mathbf{u}_{r,0}` +are the local centroid and entry positions of a ray. The computation of the +local and global centroids are described further by `Gunow `_. + +Using the local position, the source in a source region is given by: + +.. math:: + :label: region_source + + \tilde{Q}(\boldsymbol{x}) ={Q}_{i,g}+ \boldsymbol{\vec{Q}}_{i,g} \cdot \mathbf{u}_{r}\;\mathrm{,} + +This definition allows us to solve for our characteric source terms resulting in: + +.. math:: + :label: source_term_1 + + \bar{Q}_{r, i, g} = Q_{i,g} + \left[\mathbf{u}_{r,\mathrm{c}} \cdot \boldsymbol{\vec{Q}}_{i,g}\right], + +.. math:: + :label: source_term_2 + + \hat{Q}_{r, i, g} = \left[\boldsymbol{\Omega} \cdot \boldsymbol{\vec{Q}}_{i,g}\right]\;\mathrm{,} + +:math:`\boldsymbol{\Omega}` being the direction vector of the ray. The next step +is to solve for the LS source vector :math:`\boldsymbol{\vec{Q}}_{i,g}`. A +relationship between the LS source vector and the source moments, +:math:`\boldsymbol{\vec{q}}_{i,g}` can be derived, as in `Ferrer +`_ and `Gunow `_: + +.. math:: + :label: m_equation + + \mathbf{M}_{i} \boldsymbol{\vec{Q}}_{i,g} = \boldsymbol{\vec{q}}_{i,g} \;\mathrm{.} + +The spatial moments matrix :math:`M_i` in region :math:`i` represents the +spatial distribution of the 3D object composing the `source region +`_. This matrix is independent of the material of the source +region, fluxes, and any transport effects -- it is a purely geometric quantity. +It is a symmetric :math:`3\times3` matrix. While :math:`M_i` is not known +apriori to the simulation, similar to the source region volume, it can be +computed "on-the-fly" as a byproduct of the random ray integration process. Each +time a ray randomly crosses the region within its active length, an estimate of +the spatial moments matrix can be computed by using the midpoint of the ray as +an estimate of the centroid, and the distance and direction of the ray can be +used to inform the other spatial moments within the matrix. As this information +is purely geometric, the stochastic estimate of the centroid and spatial moments +matrix can be accumulated and improved over the entire duration of the +simulation, converging towards their true quantities. + +With an estimate of the spatial moments matrix :math:`M_i` resulting from the +ray tracing process naturally, the LS source vector +:math:`\boldsymbol{\vec{Q}}_{i,g}` can be obtained via a linear solve of +:eq:`m_equation`, or by the direct inversion of :math:`M_i`. However, to +accomplish this, we must first know the source moments +:math:`\boldsymbol{\vec{q}}_{i,g}`. Fortunately, the source moments are also +defined by the definition of the source: + +.. math:: + :label: source_moments + + q_{v, i, g}= \frac{\chi_{i,g}}{k_{eff}} \sum_{g^{\prime}=1}^{G} \nu + \Sigma_{\mathrm{f},i, g^{\prime}} \hat{\phi}_{v, i, g^{\prime}} + \sum_{g^{\prime}=1}^{G} + \Sigma_{\mathrm{s}, i, g^{\prime}\rightarrow g} \hat{\phi}_{v, i, g^{\prime}}\quad \forall v \in(x, y, z)\;\mathrm{,} + +where :math:`v` indicates the direction vector component, and we have introduced +the scalar flux moments :math:`\hat{\phi}`. The scalar flux moments can be +solved for by taking the `integral definition `_ of a spatial +moment, allowing us to derive a "simulation averaged" estimator for the scalar +moment, as in Equation :eq:`phi_sim`, + +.. math:: + :label: scalar_moments_sim + + \hat{\phi}_{v,i,g}^{simulation} = \frac{\sum\limits_{r=1}^{N_i} + \ell_{r} \left[\Omega_{v} \hat{\psi}_{r,i,g} + u_{r,v,0} \bar{\psi}_{r,i,g}\right]} + {\Sigma_{t,i,g} \frac{\sum\limits^{B}_{b}\sum\limits^{N_i}_{r} \ell_{b,r} }{B}} + \quad \forall v \in(x, y, z)\;\mathrm{,} + + +where the average angular flux is given by Equation :eq:`average_psi_final`, and +the angular flux spatial moments :math:`\hat{\psi}_{r,i,g}` by: + +.. math:: + :label: angular_moments + + \hat{\psi}_{r, i, g} = \frac{\ell_{r}\psi^{in}_{r,g}}{2} + + \left(\frac{\bar{Q}_{r,i, g}}{\Sigma_{\mathrm{t}, i, g}}-\psi^{in}_{r,g}\right) + \frac{G_{1}\left(\tau_{i,g}\right)}{\Sigma_{\mathrm{t}, i, g}} + \frac{\ell_{r}\hat{Q}_{r,i,g}} + {2\left(\Sigma_{\mathrm{t}, i, g}\right)^{2}}G_{2}\left(\tau_{i,g}\right)\;\mathrm{.} + + +The new exponentials introduced, again for simplicity, are simply: + +.. math:: + :label: G1 + + G_{1}(\tau) = 1+\frac{\tau}{2}-\left(1+\frac{1}{\tau}\right) F_{1}(\tau), + +.. math:: + :label: G2 + + G_{2}(\tau) = \frac{2}{3} \tau-\left(1+\frac{2}{\tau}\right) G_{1}(\tau) + +The contents of this section, alongside the equations for the flat source and +scalar flux, Equations :eq:`source_update` and :eq:`phi_sim` respectively, +completes the set of equations for LS. + +.. _methods-shannon-entropy-random-ray: + +----------------------------- +Shannon Entropy in Random Ray +----------------------------- + +As :math:`k_{eff}` is updated at each generation, the fission source at each FSR +is used to compute the Shannon entropy. This follows the :ref:`same procedure +for computing Shannon entropy in continuous-energy or multigroup Monte Carlo +simulations `, except that fission sources at FSRs are +considered, rather than fission sites of user-defined regular meshes. Thus, the +volume-weighted fission rate is considered instead, and the fraction of fission +sources is adjusted such that: + +.. math:: + :label: fraction-source-random-ray + + S_i = \frac{\text{Fission source in FSR $i \times$ Volume of FSR + $i$}}{\text{Total fission source}} = \frac{Q_{i} V_{i}}{\sum_{i=1}^{i=N} + Q_{i} V_{i}} + +The Shannon entropy is then computed normally as + +.. math:: + :label: shannon-entropy-random-ray + + H = - \sum_{i=1}^N S_i \log_2 S_i + +where :math:`N` is the number of FSRs. FSRs with no fission source (or, +occassionally, negative fission source, :ref:`due to the volume estimator +problem `) are skipped to avoid taking an undefined +logarithm in :eq:`shannon-entropy-random-ray`. + +.. _usersguide_fixed_source_methods: + +------------ +Fixed Source +------------ + +The random ray solver in OpenMC can be used for both eigenvalue and fixed source +problems. There are a few key differences between fixed source transport with +random ray and Monte Carlo, however. + +- **Source definition:** In Monte Carlo, it is relatively easy to define various + source distributions, including point sources, surface sources, volume + sources, and even custom user sources -- all with varying angular and spatial + statistical distributions. In random ray, the natural way to include a fixed + source term is by adding a fixed (flat) contribution to specific flat source + regions. Thus, in the OpenMC implementation of random ray, particle sources + are restricted to being volumetric and isotropic, although different energy + spectrums are supported. Fixed sources can be applied to specific materials, + cells, or universes. + +- **Inactive batches:** In Monte Carlo, use of a fixed source implies that all + batches are active batches, as there is no longer a need to develop a fission + source distribution. However, in random ray mode, there is still a need to + develop the scattering source by way of inactive batches before beginning + active batches. + +------------------------ +Adjoint Flux Solver Mode +------------------------ + +The random ray solver in OpenMC can also be used to solve for the adjoint flux, +:math:`\psi^{\dagger}`. In combination with the regular (forward) flux solution, +the adjoint flux is useful for perturbation methods as well as for computing +weight windows for subsequent Monte Carlo simulations. The adjoint flux can be +thought of as the "backwards" flux, representing the flux where a particle is +born at an absoprtion point (and typical absorption energy), and then undergoes +transport with a transposed scattering matrix. That is, instead of sampling a +particle and seeing where it might go as in a standard forward solve, we will +sample an absorption location and see where the particle that was absorbed there +might have come from. Notably, for typical neutron absorption at low energy +levels, this means that adjoint flux particles are typically sampled at a low +energy and then upscatter (via a transposed scattering matrix) over their +lifetimes. + +In OpenMC, the random ray adjoint solver is implemented simply by transposing +the scattering matrix, swapping :math:`\nu\Sigma_f` and :math:`\chi`, and then +running a normal transport solve. When no external fixed source is present, no +additional changes are needed in the transport process. However, if an external +fixed forward source is present in the simulation problem, then an additional +step is taken to compute the accompanying fixed adjoint source. In OpenMC, the +adjoint flux does *not* represent a response function for a particular detector +region. Rather, the adjoint flux is the global response, making it appropriate +for use with weight window generation schemes for global variance reduction. +Thus, if using a fixed source, the external source for the adjoint mode is +simply computed as being :math:`1 / \phi`, where :math:`\phi` is the forward +scalar flux that results from a normal forward solve (which OpenMC will run +first automatically when in adjoint mode). The adjoint external source will be +computed for each source region in the simulation mesh, independent of any +tallies. The adjoint external source is always flat, even when a linear +scattering and fission source shape is used. When in adjoint mode, all reported +results (e.g., tallies, eigenvalues, etc.) are derived from the adjoint flux, +even when the physical meaning is not necessarily obvious. These values are +still reported, though we emphasize that the primary use case for adjoint mode +is for producing adjoint flux tallies to support subsequent perturbation studies +and weight window generation. + +Note that the adjoint :math:`k_{eff}` is statistically the same as the forward +:math:`k_{eff}`, despite the flux distributions taking different shapes. + --------------------------- Fundamental Sources of Bias --------------------------- @@ -764,13 +1125,13 @@ in random ray particle transport are: areas typically have solutions that are highly effective at mitigating bias, error stemming from multigroup energy discretization is much harder to remedy. - - **Flat Source Approximation:**. In OpenMC, a "flat" (0th order) source - approximation is made, wherein the scattering and fission sources within a + - **Source Approximation:**. In OpenMC, a "flat" (0th order) source + approximation is often made, wherein the scattering and fission sources within a cell are assumed to be spatially uniform. As the source in reality is a continuous function, this leads to bias, although the bias can be reduced to acceptable levels if the flat source regions are sufficiently small. - The bias can also be mitigated by assuming a higher-order source (e.g., - linear or quadratic), although OpenMC does not yet have this capability. + The bias can also be mitigated by assuming a higher-order source such as the + linear source approximation currently implemented into OpenMC. In practical terms, this source of bias can become very large if cells are large (with dimensions beyond that of a typical particle mean free path), but the subdivision of cells can often reduce this bias to trivial levels. @@ -794,6 +1155,8 @@ in random ray particle transport are: .. _Tramm-2018: https://dspace.mit.edu/handle/1721.1/119038 .. _Tramm-2020: https://doi.org/10.1051/EPJCONF/202124703021 .. _Cosgrove-2023: https://doi.org/10.1080/00295639.2023.2270618 +.. _Ferrer-2016: https://doi.org/10.13182/NSE15-6 +.. _Gunow-2018: https://dspace.mit.edu/handle/1721.1/119030 .. only:: html diff --git a/docs/source/methods/tallies.rst b/docs/source/methods/tallies.rst index d833c157d5d..4b56d39559a 100644 --- a/docs/source/methods/tallies.rst +++ b/docs/source/methods/tallies.rst @@ -4,9 +4,9 @@ Tallies ======= -Note that the methods discussed in this section are written specifically for -continuous-energy mode but equivalent apply to the multi-group mode if the -particle's energy is replaced with the particle's group +The methods discussed in this section are written specifically for continuous- +energy mode. However, they can also apply to the multi-group mode if the +particle's energy is instead interpreted as the particle's group. ------------------ Filters and Scores @@ -207,6 +207,8 @@ the change-in-angle), we must use an analog estimator. .. TODO: Add description of surface current tallies +.. _tallies_statistics: + ---------- Statistics ---------- @@ -268,6 +270,14 @@ normal, log-normal, Weibull, etc. The central limit theorem states that as Estimating Statistics of a Random Variable ------------------------------------------ +After running OpenMC, each tallied quantity has a reported mean and standard +deviation. The below sections explain how these quantities are computed. Note +that OpenMC uses **batch statistics**, meaning that each observation for a tally +random variable corresponds to the aggregation of tally contributions from +multiple source particles that are grouped together into a single batch. See +:ref:`usersguide_particles` for more information on how the number of source +particles and statistical batches are specified. + Mean ++++ diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 5ae9f20edf4..23df02f2ee7 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -133,6 +133,7 @@ Constructing Tallies openmc.EnergyFilter openmc.EnergyoutFilter openmc.MuFilter + openmc.MuSurfaceFilter openmc.PolarFilter openmc.AzimuthalFilter openmc.DistribcellFilter @@ -190,6 +191,7 @@ Post-processing :template: myclass.rst openmc.Particle + openmc.ParticleList openmc.ParticleTrack openmc.StatePoint openmc.Summary diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 995ad97fa74..9ceff83fde8 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -19,6 +19,7 @@ Functions finalize find_cell find_material + dagmc_universe_cell_ids global_bounding_box global_tallies hard_reset diff --git a/docs/source/pythonapi/model.rst b/docs/source/pythonapi/model.rst index 99459f64d09..3034826bddd 100644 --- a/docs/source/pythonapi/model.rst +++ b/docs/source/pythonapi/model.rst @@ -22,14 +22,17 @@ Composite Surfaces :nosignatures: :template: myclass.rst + openmc.model.ConicalFrustum openmc.model.CruciformPrism openmc.model.CylinderSector openmc.model.HexagonalPrism openmc.model.IsogonalOctagon + openmc.model.OrthogonalBox openmc.model.Polygon openmc.model.RectangularParallelepiped openmc.model.RectangularPrism openmc.model.RightCircularCylinder + openmc.model.Vessel openmc.model.XConeOneSided openmc.model.YConeOneSided openmc.model.ZConeOneSided diff --git a/docs/source/pythonapi/stats.rst b/docs/source/pythonapi/stats.rst index ffb4fcda2ba..c8318ba8620 100644 --- a/docs/source/pythonapi/stats.rst +++ b/docs/source/pythonapi/stats.rst @@ -28,6 +28,7 @@ Univariate Probability Distributions :nosignatures: :template: myfunction.rst + openmc.stats.delta_function openmc.stats.muir Angular Distributions @@ -58,6 +59,7 @@ Spatial Distributions openmc.stats.Box openmc.stats.Point openmc.stats.MeshSpatial + openmc.stats.PointCloud .. autosummary:: :toctree: generated diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 7f222f77cb8..323cd7fd48d 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -137,12 +137,11 @@ packages should be installed, for example in Homebrew via: The compiler provided by the above LLVM package should be used in place of the one provisioned by XCode, which does not support the multithreading library used -by OpenMC. Consequently, the C++ compiler should explicitly be set before -proceeding: - -.. code-block:: sh - - export CXX=/opt/homebrew/opt/llvm/bin/clang++ +by OpenMC. To ensure CMake picks up the correct compiler, make sure that either +the :envvar:`CXX` environment variable is set to the brew-installed ``clang++`` +or that the directory containing it is on your :envvar:`PATH` environment +variable. Common locations for the brew-installed compiler are +``/opt/homebrew/opt/llvm/bin`` and ``/usr/local/opt/llvm/bin``. After the packages have been installed, follow the instructions to build from source below. diff --git a/docs/source/releasenotes/0.15.0.rst b/docs/source/releasenotes/0.15.0.rst new file mode 100644 index 00000000000..069bfd78368 --- /dev/null +++ b/docs/source/releasenotes/0.15.0.rst @@ -0,0 +1,262 @@ +==================== +What's New in 0.15.0 +==================== + +.. currentmodule:: openmc + +------- +Summary +------- + +This release of OpenMC includes many bug fixes, performance improvements, and +several notable new features. The major highlight of this release is the +introduction of a new transport solver based on the random ray method, which is +fully described in the :ref:`user's guide `. Other notable additions +include a mesh-based source class (:class:`openmc.MeshSource`), a generalization +of source domain rejection through the notion of "constraints", and new methods +on mesh-based classes for computing material volume fractions and homogenized +materials. + +------------------------------------ +Compatibility Notes and Deprecations +------------------------------------ + +Previously, specifying domain rejection for a source was only possible on the +:class:`~openmc.IndependentSoure` class and worked by specifying a `domains` +argument. This capability has been generalized to all source classes and +expanded as well; specifying a domain to reject on should now be done with the +`constraints` argument as follows:: + + source = openmc.IndependentSource(..., constraints={'domains': [cell]}) + +The `domains` argument is deprecated and will be removed in a future version of +OpenMC. Similarly, the ``only_fissionable`` argument to +:class:`openmc.stats.Box` has been replaced by a `'fissionable'` constraint. +That is, instead of specifying:: + + space = openmc.stats.Box(lower_left, upper_right, only_fissionable=True) + source = openmc.IndependentSource(space=space) + +You should now provide the constraint as:: + + space = openmc.stats.Box(lower_left, upper_right) + source = openmc.IndependentSource(space=space, constraints={'fissionable': True}) + +The :attr:`openmc.Settings.max_splits` attribute was renamed to +``max_history_splits`` and its default value has been changed to 1e7 (`#2954 +`_). + +------------ +New Features +------------ + +- When running OpenMC in volume calculation mode, only atomic weight ratio data + is loaded from data files which reduces initialization time. (`#2741 + `_) +- Introduced a ``GeometryState`` class in C++ to better separate particle and + geometry data. (`#2744 `_)) +- A new :class:`openmc.MaterialFromFilter` class allows filtering tallies by + which material a particle came from. (`#2750 + `_) +- Implemented a :meth:`openmc.deplete.MicroXS.from_multigroup_flux` method that + generates microscopic cross sections for depletion from a predetermined + multigroup flux. (`#2755 `_) +- A new :class:`openmc.MeshSource` class enables the specification of a source + distribution over a mesh, where each mesh element has a different + energy/angle/time distribution. (`#2759 + `_) +- Improve performance of depletion solver by utilizing CSR sparse matrix + representation. (`#2764 `_, + `#2771 `_) +- Added a :meth:`openmc.CylindricalMesh.get_indices_at_coords` method that + provides the mesh element index corresponding to a given point in space. + (`#2782 `_) +- Added a `path` argument to the :meth:`openmc.deplete.Integrator.integrate` + method. (`#2784 `_) +- Added a :meth:`openmc.Geometry.get_all_nuclides` method. (`#2796 + `_) +- A new capability to compute material volume fractions over mesh elements was + added in the :meth:`openmc.lib.Mesh.material_volumes` method. (`#2802 + `_) +- A new transport solver was added based on the `random ray + `_ method. (`#2823 + `_, `#2988 + `_) +- Added a :attr:`openmc.lib.Material.depletable` attribute. (`#2843 + `_) +- Added a :meth:`openmc.lib.Mesh.get_plot_bins` method and corresponding + ``openmc_mesh_get_plot_bins`` C API function that can be utilized to generate + mesh tally visualizations in the plotter application. (`#2854 + `_) +- Introduced a :func:`openmc.read_source_file` function that enables reading a + source file from the Python API. (`#2858 + `_) +- Added a ``bounding_box`` property on the :class:`openmc.RectilinearMesh` and + :class:`openmc.UnstructuredMesh` classes. (`#2861 + `_) +- Added a ``openmc_mesh_get_volumes`` C API function. (`#2869 + `_) +- The :attr:`openmc.Settings.surf_source_write` dictionary now accepts `cell`, + `cellfrom`, or `cellto` keys that limit surface source sites to those entering + or leaving specific cells. (`#2888 + `_) +- Added a :meth:`openmc.Region.plot` method that allows regions to be plotted + directly. (`#2895 `_) +- Implemented "contains" operator for the :class:`openmc.BoundingBox` class. + (`#2906 `_) +- Generalized source rejection via a new ``constraints`` argument to all source + classes. (`#2916 `_) +- Added a new :class:`openmc.MeshBornFilter` class that filters tally events + based on which mesh element a particle was born in. (`#2925 + `_) +- The :class:`openmc.Trigger` class now has a ``ignore_zeros`` argument that + results in any bins with zero score to be ignored when checking the trigger. + (`#2928 `_) +- Introduced a :attr:`openmc.Settings.max_events` attribute that controls the + maximum number of events a particle can undergo. (`#2945 + `_) +- Added support for :class:`openmc.UnstructuredMesh` in the + :class:`openmc.MeshSource` class. (`#2949 + `_) +- Added a :meth:`openmc.MeshBase.get_homogenized_materials` method that computes + homogenized materials over mesh elements. (`#2971 + `_) +- Add an ``options`` argument to :class:`openmc.UnstructuredMesh` that allows + configuring underlying data structures in MOAB. (`#2976 + `_) +- Type hints were added to several classes in the :mod:`openmc.deplete` module. + (`#2866 `_) + +--------- +Bug Fixes +--------- + +- Fix unit conversion in openmc.deplete.Results.get_mass (`#2761 `_) +- Fix Lagrangian interpolation (`#2775 `_) +- Depletion restart with MPI (`#2778 `_) +- Modify depletion transfer rates test to be more robust (`#2779 `_) +- Call simulation_finalize if needed when finalizing OpenMC (`#2790 `_) +- F90_NONE Removal (MGMC tallying optimization) (`#2785 `_) +- Correctly apply volumes to materials when using DAGMC geometries (`#2787 `_) +- Add inline to openmc::interpolate (`#2789 `_) +- Use huge_tree=True in lxml parsing (`#2791 `_) +- OpenMPMutex "Copying" (`#2794 `_) +- Do not link against several transitive dependencies of HDF5 (`#2797 `_) +- Added check to length of input arguments for IndependantOperator (`#2799 `_) +- Pytest Update Documentation (`#2801 `_) +- Move 'import lxml' to third-party block of imports (`#2803 `_) +- Fix creation of meshes when from loading settings from XML (`#2805 `_) +- Avoid high memory use when writing unstructured mesh VTK files (`#2806 `_) +- Consolidating thread information into the openmp interface header (`#2809 `_) +- Prevent underflow in calculation of speed (`#2811 `_) +- Provide error message if a cell path can't be determined (`#2812 `_) +- Fix distribcell labels for lattices used as fill in multiple cells (`#2813 `_) +- Make creation of spatial trees based on usage for unstructured mesh. (`#2815 `_) +- Ensure particle direction is normalized for plotting / volume calculations (`#2816 `_) +- Added missing meshes to documentation (`#2820 `_) +- Reset timers at correct place in deplete (`#2821 `_) +- Fix config change not propagating through to decay energies (`#2825 `_) +- Ensure that implicit complement cells appear last in DAGMC universes (`#2838 `_) +- Export model.tallies to XML in CoupledOperator (`#2840 `_) +- Fix locating h5m files references in DAGMC universes (`#2842 `_) +- Prepare for NumPy 2.0 (`#2845 `_) +- Added missing functions and classes to openmc.lib docs (`#2847 `_) +- Fix compilation on CentOS 7 (missing link to libdl) (`#2849 `_) +- Adding resulting nuclide to cross section plot legend (`#2851 `_) +- Updating file extension for Excel files when exporting MGXS data (`#2852 `_) +- Removed error raising when calling warn (`#2853 `_) +- Setting ``surf_source_`` attribute for DAGMC surfaces. (`#2857 `_) +- Changing y axis label for heating plots (`#2859 `_) +- Removed unused step_index arg from restart (`#2867 `_) +- Fix issue with Cell::get_contained_cells() utility function (`#2873 `_) +- Adding energy axis units to plot xs (`#2876 `_) +- Set OpenMCOperator materials when diff_burnable_mats = True (`#2877 `_) +- Fix expansion filter merging (`#2882 `_) +- Added checks that tolerance value is between 0 and 1 (`#2884 `_) +- Statepoint file loading refactor and CAPI function (`#2886 `_) +- Added check for length of value passed into EnergyFilter (`#2887 `_) +- Ensure that Model.run() works when specifying a custom XML path (`#2889 `_) +- Updating docker file base to bookworm (`#2890 `_) +- Clarifying documentation for cones (`#2892 `_) +- Abort on cmake config if openmp requested but not found (`#2893 `_) +- Tiny updates from experience building on Mac (`#2894 `_) +- Added damage-energy as optional reaction for micro (`#2903 `_) +- docs: add missing max_splits in settings specification (`#2910 `_) +- Changed CI to use latest actions to get away from the Node 16 deprecation. (`#2912 `_) +- Mkdir to always allow parents and exist ok (`#2914 `_) +- Fixed small sphinx typo (`#2915 `_) +- Hexagonal lattice iterators (`#2921 `_) +- Fix Chain.form_matrix to work with scipy 1.12 (`#2922 `_) +- Allow get_microxs_and_flux to use OPENMC_CHAIN_FILE environment variable (`#2934 `_) +- Polygon fix to better handle colinear points (`#2935 `_) +- Fix CMFD to work with scipy 1.13 (`#2936 `_) +- Print warning if no natural isotopes when using add_element (`#2938 `_) +- Update xtl and xtensor submodules (`#2941 `_) +- Ensure two surfaces with different boundary type are not considered redundant (`#2942 `_) +- Updated package versions in Dockerfile (`#2946 `_) +- Add MPI calls to DAGMC external test (`#2948 `_) +- Eliminate deprecation warnings from scipy and pandas (`#2951 `_) +- Update math function unit test with catch2 (`#2955 `_) +- Support track file writing for particle restart runs. (`#2957 `_) +- Make UWUW optional (`#2965 `_) +- Allow pure decay IndependentOperator (`#2966 `_) +- Added fix to cfloat_endf for length 11 endf floats (`#2967 `_) +- Moved apt get to optional CI parts (`#2970 `_) +- Update bounding_box docstrings (`#2972 `_) +- Added extra error checking on spherical mesh creation (`#2973 `_) +- Update CODEOWNERS file (`#2974 `_) +- Added error checking on cylindrical mesh (`#2977 `_) +- Correction for histogram interpolation of Tabular distributions (`#2981 `_) +- Enforce lower_left in lattice geometry (`#2982 `_) +- Update random_dist.h comment to be less specific (`#2991 `_) +- Apply memoization in get_all_universes (`#2995 `_) +- Make sure skewed dataset is cast to bool properly (`#3001 `_) +- Hexagonal lattice roundtrip (`#3003 `_) +- Fix CylinderSector and IsogonalOctagon translations (`#3018 `_) +- Sets used instead of lists when membership testing (`#3021 `_) +- Fixing plot xs for when plotting element string reaction (`#3029 `_) +- Fix shannon entropy broken link (`#3034 `_) +- Only add png or h5 extension if not present in plots.py (`#3036 `_) +- Fix non-existent path causing segmentation fault when saving plot (`#3038 `_) +- Resolve warnings related to numpy 2.0 (`#3044 `_) +- Update IsogonalOctagon to use xz basis (`#3045 `_) +- Determine whether nuclides are fissionable in volume calc mode (`#3047 `_) +- Avoiding more numpy 2.0 deprecation warnings (`#3049 `_) +- Set DAGMC cell instances on surface crossing (`#3052 `_) + +------------ +Contributors +------------ + +- `Aidan Crilly `_ +- `April Novak `_ +- `Davide Mancusi `_ +- `Baptiste Mouginot `_ +- `Chris Wagner `_ +- `Lorenzo Chierici `_ +- `Catherine Yu `_ +- `Erik Knudsen `_ +- `Ethan Peterson `_ +- `Gavin Ridley `_ +- `hsameer481 `_ +- `Hunter Belanger `_ +- `Isaac Meyer `_ +- `Jin Whan Bae `_ +- `Joffrey Dorville `_ +- `John Tramm `_ +- `Yue Jin `_ +- `Sigfrid Stjärnholm `_ +- `Kimberly Meagher `_ +- `lhchg `_ +- `Luke Labrie-Cleary `_ +- `Micah Gale `_ +- `Nicholas Linden `_ +- `pitkajuh `_ +- `Rosie Barker `_ +- `Paul Romano `_ +- `Patrick Shriwise `_ +- `Jonathan Shimwell `_ +- `Travis Labossiere-Hickman `_ +- `Vanessa Lulla `_ +- `Olek Yardas `_ +- `Perry Young `_ diff --git a/docs/source/releasenotes/index.rst b/docs/source/releasenotes/index.rst index 6923101cc43..bde1205891e 100644 --- a/docs/source/releasenotes/index.rst +++ b/docs/source/releasenotes/index.rst @@ -7,6 +7,7 @@ Release Notes .. toctree:: :maxdepth: 1 + 0.15.0 0.14.0 0.13.3 0.13.2 diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index 3a3d02231ad..6f14ebfa51c 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -474,7 +474,7 @@ applied as universes in the OpenMC geometry file. A geometry represented entirely by a DAGMC geometry will contain only the DAGMC universe. Using a :class:`openmc.DAGMCUniverse` looks like the following:: - dag_univ = openmc.DAGMCUniverse(filename='dagmc.h5m') + dag_univ = openmc.DAGMCUniverse('dagmc.h5m') geometry = openmc.Geometry(dag_univ) geometry.export_to_xml() @@ -495,13 +495,22 @@ It is important in these cases to understand the DAGMC model's position with respect to the CSG geometry. DAGMC geometries can be plotted with OpenMC to verify that the model matches one's expectations. -**Note:** DAGMC geometries used in OpenMC are currently required to be clean, -meaning that all surfaces have been `imprinted and merged -`_ successfully -and that the model is `watertight -`_. -Future implementations of DAGMC geometry will support small volume overlaps and -un-merged surfaces. +By default, when you specify a .h5m file for a :class:`~openmc.DAGMCUniverse` +instance, it will store the absolute path to the .h5m file. If you prefer to +store the relative path, you can set the ``'resolve_paths'`` configuration +variable:: + + openmc.config['resolve_paths'] = False + dag_univ = openmc.DAGMCUniverse('dagmc.h5m') + +.. note:: + DAGMC geometries used in OpenMC are currently required to be clean, + meaning that all surfaces have been `imprinted and merged + `_ successfully + and that the model is `watertight + `_. + Future implementations of DAGMC geometry will support small volume overlaps and + un-merged surfaces. Cell, Surface, and Material IDs ------------------------------- diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 130e96c0ae6..1c0b7fa5b32 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -584,10 +584,6 @@ distributions. parallel runs. This package is needed if you plan on running depletion simulations in parallel using MPI. - `Cython `_ - Cython is used for resonance reconstruction for ENDF data converted to - :class:`openmc.data.IncidentNeutron`. - `vtk `_ The Python VTK bindings are needed to convert voxel and track files to VTK format. diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index dad86c826e0..2b9cf67240b 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -40,13 +40,15 @@ Carlo, **inactive batches are required for both eigenvalue and fixed source solves in random ray mode** due to this additional need to converge the scattering source. +.. warning:: + Unlike Monte Carlo, the random ray solver still requires usage of inactive + batches when in fixed source mode so as to develop the scattering source. + The additional burden of converging the scattering source generally results in a higher requirement for the number of inactive batches---often by an order of magnitude or more. For instance, it may be reasonable to only use 50 inactive batches for a light water reactor simulation with Monte Carlo, but random ray -might require 500 or more inactive batches. Similar to Monte Carlo, -:ref:`Shannon entropy ` can be used to gauge whether the -combined scattering and fission source has fully developed. +might require 500 or more inactive batches. Similar to Monte Carlo, active batches are used in the random ray solver mode to accumulate and converge statistics on unknown quantities (i.e., the random ray @@ -60,6 +62,17 @@ solver:: settings.batches = 1200 settings.inactive = 600 +--------------- +Shannon Entropy +--------------- + +Similar to Monte Carlo, :ref:`Shannon entropy +` can be used to gauge whether the fission +source has fully developed. The Shannon entropy is calculated automatically +after each batch and is printed to the statepoint file. Unlike Monte Carlo, an +entropy mesh does not need to be defined, as the Shannon entropy is calculated +over FSRs using a volume-weighted approach. + ------------------------------- Inactive Ray Length (Dead Zone) ------------------------------- @@ -248,6 +261,8 @@ a larger value until the "low ray density" messages go away. ray lengths are sufficiently long to allow for transport to occur between source and target regions of interest. +.. _usersguide_ray_source: + ---------- Ray Source ---------- @@ -261,7 +276,7 @@ that the source must not be limited to only fissionable regions. Additionally, the source box must cover the entire simulation domain. In the case of a simulation domain that is not box shaped, a box source should still be used to bound the domain but with the source limited to rejection sampling the actual -simulation universe (which can be specified via the ``domains`` field of the +simulation universe (which can be specified via the ``domains`` constraint of the :class:`openmc.IndependentSource` Python class). Similar to Monte Carlo sources, for two-dimensional problems (e.g., a 2D pincell) it is desirable to make the source bounded near the origin of the infinite dimension. An example of an @@ -315,6 +330,8 @@ of a two-dimensional 2x2 reflective pincell lattice: In the future, automated subdivision of FSRs via mesh overlay may be supported. +.. _usersguide_flux_norm: + ------- Tallies ------- @@ -352,6 +369,25 @@ Note that there is no difference between the analog, tracklength, and collision estimators in random ray mode as individual particles are not being simulated. Tracklength-style tally estimation is inherent to the random ray method. +As discussed in the random ray theory section on :ref:`Random Ray +Tallies`, by default flux tallies in the random ray mode +are not normalized by the spatial tally volumes such that flux tallies are in +units of cm. While the volume information is readily available as a byproduct of +random ray integration, the flux value is reported in unnormalized units of cm +so that the user will be able to compare "apples to apples" with the default +flux tallies from the Monte Carlo solver (also reported by default in units of +cm). If volume normalized flux tallies (in units of cm\ :sup:`-2`) are desired, +then the user can set the ``volume_normalized_flux_tallies`` field in the +:attr:`openmc.Settings.random_ray` dictionary to ``True``. An example is given +below: + +:: + + settings.random_ray['volume_normalized_flux_tallies'] = True + +Note that MC mode flux tallies can also be normalized by volume, as discussed in +the :ref:`Volume Calculation Section` of the user guide. + -------- Plotting -------- @@ -411,11 +447,191 @@ in the `OpenMC Jupyter notebook collection separate materials can be defined each with a separate multigroup dataset corresponding to a given temperature. +-------------- +Linear Sources +-------------- + +Linear Sources (LS), are supported with the eigenvalue and fixed source random +ray solvers. General 3D LS can be toggled by setting the ``source_shape`` field +in the :attr:`openmc.Settings.random_ray` dictionary to ``'linear'`` as:: + + settings.random_ray['source_shape'] = 'linear' + +LS enables the use of coarser mesh discretizations and lower ray populations, +offsetting the increased computation per ray. + +While OpenMC has no specific mode for 2D simulations, such simulations can be +performed implicitly by leaving one of the dimensions of the geometry unbounded +or by imposing reflective boundary conditions with no variation in between them +in that dimension. When 3D linear sources are used in a 2D random ray +simulation, the extremely long (or potentially infinite) spatial dimension along +one of the axes can cause the linear source to become noisy, leading to +potentially large increases in variance. To mitigate this, the user can force +the z-terms of the linear source to zero by setting the ``source_shape`` field +as:: + + settings.random_ray['source_shape'] = 'linear_xy' + +which will greatly improve the quality of the linear source term in 2D +simulations. + +--------------------------------- +Fixed Source and Eigenvalue Modes +--------------------------------- + +Both fixed source and eigenvalue modes are supported with the random ray solver +in OpenMC. Modes can be selected as described in the :ref:`run modes section +`. In both modes, a ray source must be provided to let +OpenMC know where to sample ray starting locations from, as discussed in the +:ref:`ray source section `. In fixed source mode, at +least one regular source must be provided as well that represents the physical +particle fixed source. As discussed in the :ref:`fixed source methodology +section `, the types of fixed sources supported +in the random ray solver mode are limited compared to what is possible with the +Monte Carlo solver. + +Currently, all of the following conditions must be met for the particle source +to be valid in random ray mode: + +- One or more domain ids must be specified that indicate which cells, universes, + or materials the source applies to. This implicitly limits the source type to + being volumetric. This is specified via the ``domains`` constraint placed on the + :class:`openmc.IndependentSource` Python class. +- The source must be isotropic (default for a source) +- The source must use a discrete (i.e., multigroup) energy distribution. The + discrete energy distribution is input by defining a + :class:`openmc.stats.Discrete` Python class, and passed as the ``energy`` + field of the :class:`openmc.IndependentSource` Python class. + +Any other spatial distribution information contained in a particle source will +be ignored. Only the specified cell, material, or universe domains will be used +to define the spatial location of the source, as the source will be applied +during a pre-processing stage of OpenMC to all source regions that are contained +within the specified domains for the source. + +When defining a :class:`openmc.stats.Discrete` object, note that the ``x`` field +will correspond to the discrete energy points, and the ``p`` field will +correspond to the discrete probabilities. It is recommended to select energy +points that fall within energy groups rather than on boundaries between the +groups. That is, if the problem contains two energy groups (with bin edges of +1.0e-5, 1.0e-1, 1.0e7), then a good selection for the ``x`` field might be +points of 1.0e-2 and 1.0e1. + +:: + + # Define geometry, etc. + ... + source_cell = openmc.Cell(fill=source_mat, name='cell where fixed source will be') + ... + # Define physical neutron fixed source + energy_points = [1.0e-2, 1.0e1] + strengths = [0.25, 0.75] + energy_distribution = openmc.stats.Discrete(x=energy_points, p=strengths) + neutron_source = openmc.IndependentSource( + energy=energy_distribution, + constraints={'domains': [source_cell]} + ) + + # Add fixed source and ray sampling source to settings file + settings.source = [neutron_source] + +.. _usersguide_vol_estimators: + +----------------------------- +Alternative Volume Estimators +----------------------------- + +As discussed in the random ray theory section on :ref:`volume estimators +`, there are several possible derivations for the scalar +flux estimate. These options deal with different ways of treating the +accumulation over ray lengths crossing each FSR (a quantity directly +proportional to volume), which can be computed using several methods. The +following methods are currently available in OpenMC: + +.. list-table:: Comparison of Estimators + :header-rows: 1 + :widths: 10 30 30 30 + + * - Estimator + - Description + - Pros + - Cons + * - ``simulation_averaged`` + - Accumulates total active ray lengths in each FSR over all iterations, + improving the estimate of the volume in each cell each iteration. + - * Virtually unbiased after several iterations + * Asymptotically approaches the true analytical volume + * Typically most efficient in terms of speed vs. accuracy + - * Higher variance + * Can lead to negative fluxes and numerical instability in pathological + cases + * - ``naive`` + - Treats the volume as composed only of the active ray length through each + FSR per iteration, being a biased but numerically consistent ratio + estimator. + - * Low variance + * Unlikely to result in negative fluxes + * Recommended in cases where the simulation averaged estimator is + unstable + - * Biased estimator + * Requires more rays or longer active ray length to mitigate bias + * - ``hybrid`` (default) + - Applies the naive estimator to all cells that contain an external (fixed) + source contribution. Applies the simulation averaged estimator to all + other cells. + - * High accuracy/low bias of the simulation averaged estimator in most + cells + * Stability of the naive estimator in cells with fixed sources + - * Can lead to slightly negative fluxes in cells where the simulation + averaged estimator is used + +These estimators can be selected by setting the ``volume_estimator`` field in the +:attr:`openmc.Settings.random_ray` dictionary. For example, to use the naive +estimator, the following code would be used: + +:: + + settings.random_ray['volume_estimator'] = 'naive' + +----------------- +Adjoint Flux Mode +----------------- + +The adjoint flux random ray solver mode can be enabled as: +entire +:: + + settings.random_ray['adjoint'] = True + +When enabled, OpenMC will first run a forward transport simulation followed by +an adjoint transport simulation. The purpose of the forward solve is to compute +the adjoint external source when an external source is present in the +simulation. Simulation settings (e.g., number of rays, batches, etc.) will be +identical for both simulations. At the conclusion of the run, all results (e.g., +tallies, plots, etc.) will be derived from the adjoint flux rather than the +forward flux but are not labeled any differently. The initial forward flux +solution will not be stored or available in the final statepoint file. Those +wishing to do analysis requiring both the forward and adjoint solutions will +need to run two separate simulations and load both statepoint files. + +.. note:: + When adjoint mode is selected, OpenMC will always perform a full forward + solve and then run a full adjoint solve immediately afterwards. Statepoint + and tally results will be derived from the adjoint flux, but will not be + labeled any differently. + --------------------------------------- Putting it All Together: Example Inputs --------------------------------------- -An example of a settings definition for random ray is given below:: +~~~~~~~~~~~~~~~~~~ +Eigenvalue Example +~~~~~~~~~~~~~~~~~~ + +An example of a settings definition for an eigenvalue random ray simulation is +given below: + +:: # Geometry and MGXS material definition of 2x2 lattice (not shown) pitch = 1.26 @@ -478,3 +694,84 @@ Monte Carlo run (see the :ref:`geometry ` and There is also a complete example of a pincell available in the ``openmc/examples/pincell_random_ray`` folder. + +~~~~~~~~~~~~~~~~~~~~ +Fixed Source Example +~~~~~~~~~~~~~~~~~~~~ + +An example of a settings definition for a fixed source random ray simulation is +given below: + +:: + + # Geometry and MGXS material definition of 2x2 lattice (not shown) + pitch = 1.26 + source_cell = openmc.Cell(fill=source_mat, name='cell where fixed source will be') + ebins = [1e-5, 1e-1, 20.0e6] + ... + + # Instantiate a settings object for a random ray solve + settings = openmc.Settings() + settings.energy_mode = "multi-group" + settings.batches = 1200 + settings.inactive = 600 + settings.particles = 2000 + settings.run_mode = 'fixed source' + settings.random_ray['distance_inactive'] = 40.0 + settings.random_ray['distance_active'] = 400.0 + + # Create an initial uniform spatial source distribution for sampling rays + lower_left = (-pitch, -pitch, -pitch) + upper_right = ( pitch, pitch, pitch) + uniform_dist = openmc.stats.Box(lower_left, upper_right) + settings.random_ray['ray_source'] = openmc.IndependentSource(space=uniform_dist) + + # Define physical neutron fixed source + energy_points = [1.0e-2, 1.0e1] + strengths = [0.25, 0.75] + energy_distribution = openmc.stats.Discrete(x=energy_points, p=strengths) + neutron_source = openmc.IndependentSource( + energy=energy_distribution, + constraints={'domains': [source_cell]} + ) + + # Add fixed source and ray sampling source to settings file + settings.source = [neutron_source] + + settings.export_to_xml() + + # Define tallies + + # Create a mesh filter + mesh = openmc.RegularMesh() + mesh.dimension = (2, 2) + mesh.lower_left = (-pitch/2, -pitch/2) + mesh.upper_right = (pitch/2, pitch/2) + mesh_filter = openmc.MeshFilter(mesh) + + # Create a multigroup energy filter + energy_filter = openmc.EnergyFilter(ebins) + + # Create tally using our two filters and add scores + tally = openmc.Tally() + tally.filters = [mesh_filter, energy_filter] + tally.scores = ['flux'] + + # Instantiate a Tallies collection and export to XML + tallies = openmc.Tallies([tally]) + tallies.export_to_xml() + + # Create voxel plot + plot = openmc.Plot() + plot.origin = [0, 0, 0] + plot.width = [2*pitch, 2*pitch, 1] + plot.pixels = [1000, 1000, 1] + plot.type = 'voxel' + + # Instantiate a Plots collection and export to XML + plots = openmc.Plots([plot]) + plots.export_to_xml() + +All other inputs (e.g., geometry, material) will be unchanged from a typical +Monte Carlo run (see the :ref:`geometry ` and +:ref:`multigroup materials ` user guides for more information). diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index eb02f654c30..ca11d648748 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -183,6 +183,7 @@ source distributions and has four main attributes that one can set: :attr:`IndependentSource.energy`, which defines the energy distribution, and :attr:`IndependentSource.time`, which defines the time distribution. + The spatial distribution can be set equal to a sub-class of :class:`openmc.stats.Spatial`; common choices are :class:`openmc.stats.Point` or :class:`openmc.stats.Box`. To independently specify distributions in the @@ -192,7 +193,9 @@ distributions using spherical or cylindrical coordinates, you can use :class:`openmc.stats.SphericalIndependent` or :class:`openmc.stats.CylindricalIndependent`, respectively. Meshes can also be used to represent spatial distributions with :class:`openmc.stats.MeshSpatial` -by specifying a mesh and source strengths for each mesh element. +by specifying a mesh and source strengths for each mesh element. It is also +possible to define a "cloud" of source points, each with a different relative +probability, using :class:`openmc.stats.PointCloud`. The angular distribution can be set equal to a sub-class of :class:`openmc.stats.UnitSphere` such as :class:`openmc.stats.Isotropic`, @@ -223,6 +226,7 @@ distribution. This could be a probability mass function (:class:`openmc.stats.Tabular`). By default, if no time distribution is specified, particles are started at :math:`t=0`. + As an example, to create an isotropic, 10 MeV monoenergetic source uniformly distributed over a cube centered at the origin with an edge length of 10 cm, and emitting a pulse of particles from 0 to 10 µs, one @@ -250,6 +254,24 @@ sampled 70% of the time and another that should be sampled 30% of the time:: settings.source = [src1, src2] +When the relative strengths are several orders of magnitude different, it may +happen that not enough statistics are obtained from the lower strength source. +This can be improved by sampling among the sources with equal probability, +applying the source strength as a weight on the sampled source particles. The +:attr:`Settings.uniform_source_sampling` attribute can be used to enable this +option:: + + src1 = openmc.IndependentSource() + src1.strength = 100.0 + ... + + src2 = openmc.IndependentSource() + src2.strength = 1.0 + ... + + settings.source = [src1, src2] + settings.uniform_source_sampling = True + Finally, the :attr:`IndependentSource.particle` attribute can be used to indicate the source should be composed of particles other than neutrons. For example, the following would generate a photon source:: @@ -277,6 +299,9 @@ source file can be manually generated with the :func:`openmc.write_source_file` function. This is particularly useful for coupling OpenMC with another program that generates a source to be used in OpenMC. +Surface Sources ++++++++++++++++ + A source file based on particles that cross one or more surfaces can be generated during a simulation using the :attr:`Settings.surf_source_write` attribute:: @@ -287,7 +312,62 @@ attribute:: } In this example, at most 10,000 source particles are stored when particles cross -surfaces with IDs of 1, 2, or 3. +surfaces with IDs of 1, 2, or 3. If no surface IDs are declared, particles +crossing any surface of the model will be banked:: + + settings.surf_source_write = {'max_particles': 10000} + +A cell ID can also be used to bank particles that are crossing any surface of +a cell that particles are either coming from or going to:: + + settings.surf_source_write = {'cell': 1, 'max_particles': 10000} + +In this example, particles that are crossing any surface that bounds cell 1 will +be banked excluding any surface that does not use a 'transmission' or 'vacuum' +boundary condition. + +.. note:: Surfaces with boundary conditions that are not "transmission" or "vacuum" + are not eligible to store any particles when using ``cell``, ``cellfrom`` + or ``cellto`` attributes. It is recommended to use surface IDs instead. + +Surface IDs can be used in combination with a cell ID:: + + settings.surf_source_write = { + 'cell': 1, + 'surfaces_ids': [1, 2, 3], + 'max_particles': 10000 + } + +In that case, only particles that are crossing the declared surfaces coming from +cell 1 or going to cell 1 will be banked. To account specifically for particles +leaving or entering a given cell, ``cellfrom`` and ``cellto`` are also available +to respectively account for particles coming from a cell:: + + settings.surf_source_write = { + 'cellfrom': 1, + 'max_particles': 10000 + } + +or particles going to a cell:: + + settings.surf_source_write = { + 'cellto': 1, + 'max_particles': 10000 + } + +.. note:: The ``cell``, ``cellfrom`` and ``cellto`` attributes cannot be + used simultaneously. + +To generate more than one surface source files when the maximum number of stored +particles is reached, ``max_source_files`` is available. The surface source bank +will be cleared in simulation memory each time a surface source file is written. +As an example, to write a maximum of three surface source files::: + + settings.surf_source_write = { + 'surfaces_ids': [1, 2, 3], + 'max_particles': 10000, + 'max_source_files': 3 + } .. _compiled_source: diff --git a/examples/custom_source/CMakeLists.txt b/examples/custom_source/CMakeLists.txt index 21463ed51ed..ba5ae94adc6 100644 --- a/examples/custom_source/CMakeLists.txt +++ b/examples/custom_source/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10 FATAL_ERROR) +cmake_minimum_required(VERSION 3.16 FATAL_ERROR) project(openmc_sources CXX) add_library(source SHARED source_ring.cpp) find_package(OpenMC REQUIRED) diff --git a/examples/lattice/hexagonal/build_xml.py b/examples/lattice/hexagonal/build_xml.py index 96ff3f34a11..9485d0aa45f 100644 --- a/examples/lattice/hexagonal/build_xml.py +++ b/examples/lattice/hexagonal/build_xml.py @@ -114,8 +114,9 @@ # Create an initial uniform spatial source distribution over fissionable zones bounds = [-1, -1, -1, 1, 1, 1] -uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) -settings_file.source = openmc.IndependentSource(space=uniform_dist) +uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:]) +settings_file.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) settings_file.keff_trigger = {'type' : 'std_dev', 'threshold' : 5E-4} settings_file.trigger_active = True diff --git a/examples/lattice/nested/build_xml.py b/examples/lattice/nested/build_xml.py index 407a53a2327..2db23a46b33 100644 --- a/examples/lattice/nested/build_xml.py +++ b/examples/lattice/nested/build_xml.py @@ -124,8 +124,9 @@ # Create an initial uniform spatial source distribution over fissionable zones bounds = [-1, -1, -1, 1, 1, 1] -uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) -settings_file.source = openmc.IndependentSource(space=uniform_dist) +uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:]) +settings_file.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) settings_file.export_to_xml() diff --git a/examples/lattice/simple/build_xml.py b/examples/lattice/simple/build_xml.py index 0da8d8cf086..56c46612169 100644 --- a/examples/lattice/simple/build_xml.py +++ b/examples/lattice/simple/build_xml.py @@ -115,8 +115,9 @@ # Create an initial uniform spatial source distribution over fissionable zones bounds = [-1, -1, -1, 1, 1, 1] -uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) -settings_file.source = openmc.IndependentSource(space=uniform_dist) +uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:]) +settings_file.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) settings_file.trigger_active = True settings_file.trigger_max_batches = 100 diff --git a/examples/parameterized_custom_source/CMakeLists.txt b/examples/parameterized_custom_source/CMakeLists.txt index 8232f3b546a..20dac4d8f40 100644 --- a/examples/parameterized_custom_source/CMakeLists.txt +++ b/examples/parameterized_custom_source/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10 FATAL_ERROR) +cmake_minimum_required(VERSION 3.16 FATAL_ERROR) project(openmc_sources CXX) add_library(parameterized_source SHARED parameterized_source_ring.cpp) find_package(OpenMC REQUIRED) diff --git a/examples/pincell/build_xml.py b/examples/pincell/build_xml.py index d008c882237..aed0c790f10 100644 --- a/examples/pincell/build_xml.py +++ b/examples/pincell/build_xml.py @@ -67,8 +67,9 @@ # Create an initial uniform spatial source distribution over fissionable zones lower_left = (-pitch/2, -pitch/2, -1) upper_right = (pitch/2, pitch/2, 1) -uniform_dist = openmc.stats.Box(lower_left, upper_right, only_fissionable=True) -settings.source = openmc.IndependentSource(space=uniform_dist) +uniform_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) # For source convergence checks, add a mesh that can be used to calculate the # Shannon entropy diff --git a/examples/pincell_multigroup/build_xml.py b/examples/pincell_multigroup/build_xml.py index 019ae6a113f..0971e5de639 100644 --- a/examples/pincell_multigroup/build_xml.py +++ b/examples/pincell_multigroup/build_xml.py @@ -113,8 +113,9 @@ # Create an initial uniform spatial source distribution over fissionable zones lower_left = (-pitch/2, -pitch/2, -1) upper_right = (pitch/2, pitch/2, 1) -uniform_dist = openmc.stats.Box(lower_left, upper_right, only_fissionable=True) -settings.source = openmc.IndependentSource(space=uniform_dist) +uniform_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) settings.export_to_xml() ############################################################################### diff --git a/include/openmc/bounding_box.h b/include/openmc/bounding_box.h new file mode 100644 index 00000000000..40f603583b4 --- /dev/null +++ b/include/openmc/bounding_box.h @@ -0,0 +1,61 @@ +#ifndef OPENMC_BOUNDING_BOX_H +#define OPENMC_BOUNDING_BOX_H + +#include // for min, max + +#include "openmc/constants.h" + +namespace openmc { + +//============================================================================== +//! Coordinates for an axis-aligned cuboid that bounds a geometric object. +//============================================================================== + +struct BoundingBox { + double xmin = -INFTY; + double xmax = INFTY; + double ymin = -INFTY; + double ymax = INFTY; + double zmin = -INFTY; + double zmax = INFTY; + + inline BoundingBox operator&(const BoundingBox& other) + { + BoundingBox result = *this; + return result &= other; + } + + inline BoundingBox operator|(const BoundingBox& other) + { + BoundingBox result = *this; + return result |= other; + } + + // intersect operator + inline BoundingBox& operator&=(const BoundingBox& other) + { + xmin = std::max(xmin, other.xmin); + xmax = std::min(xmax, other.xmax); + ymin = std::max(ymin, other.ymin); + ymax = std::min(ymax, other.ymax); + zmin = std::max(zmin, other.zmin); + zmax = std::min(zmax, other.zmax); + return *this; + } + + // union operator + inline BoundingBox& operator|=(const BoundingBox& other) + { + xmin = std::min(xmin, other.xmin); + xmax = std::max(xmax, other.xmax); + ymin = std::min(ymin, other.ymin); + ymax = std::max(ymax, other.ymax); + zmin = std::min(zmin, other.zmin); + zmax = std::max(zmax, other.zmax); + return *this; + } +}; + +} // namespace openmc + +#endif diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 9401156a64f..8edd99c0785 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -29,6 +29,9 @@ int openmc_cell_set_temperature( int32_t index, double T, const int32_t* instance, bool set_contained = false); int openmc_cell_set_translation(int32_t index, const double xyz[]); int openmc_cell_set_rotation(int32_t index, const double rot[], size_t rot_len); +int openmc_dagmc_universe_get_cell_ids( + int32_t univ_id, int32_t* ids, size_t* n); +int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n); int openmc_energy_filter_get_bins( int32_t index, const double** energies, size_t* n); int openmc_energy_filter_set_bins( diff --git a/include/openmc/cell.h b/include/openmc/cell.h index e68443a32fd..032475ce982 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -12,6 +12,7 @@ #include "pugixml.hpp" #include +#include "openmc/bounding_box.h" #include "openmc/constants.h" #include "openmc/memory.h" // for unique_ptr #include "openmc/neighbor_list.h" @@ -128,8 +129,7 @@ class Region { void add_precedence(); //! Add parenthesis to enforce precedence - std::vector::iterator add_parentheses( - std::vector::iterator start); + gsl::index add_parentheses(gsl::index start); //! Remove complement operators from the expression void remove_complement_ops(); @@ -320,7 +320,6 @@ class Cell { int32_t universe_; //!< Universe # this cell is in int32_t fill_; //!< Universe # filling this cell int32_t n_instances_ {0}; //!< Number of instances of this cell - GeometryType geom_type_; //!< Geometric representation type (CSG, DAGMC) //! \brief Index corresponding to this cell in distribcell arrays int distribcell_index_ {C_NONE}; @@ -350,6 +349,13 @@ class Cell { vector rotation_; vector offset_; //!< Distribcell offset table + + // Accessors + const GeometryType& geom_type() const { return geom_type_; } + GeometryType& geom_type() { return geom_type_; } + +private: + GeometryType geom_type_; //!< Geometric representation type (CSG, DAGMC) }; struct CellInstanceItem { diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 1c683e5f50c..605ae1839d8 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -342,6 +342,9 @@ enum class RunMode { enum class SolverType { MONTE_CARLO, RANDOM_RAY }; +enum class RandomRayVolumeEstimator { NAIVE, SIMULATION_AVERAGED, HYBRID }; +enum class RandomRaySourceShape { FLAT, LINEAR, LINEAR_XY }; + //============================================================================== // Geometry Constants diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 2facf4fc05e..47fcfe237e3 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -29,6 +29,12 @@ void check_dagmc_root_univ(); #include "openmc/particle.h" #include "openmc/position.h" #include "openmc/surface.h" +#include "openmc/vector.h" + +#include // for shared_ptr, unique_ptr +#include +#include +#include // for pair class UWUW; @@ -133,6 +139,10 @@ class DAGUniverse : public Universe { void legacy_assign_material( std::string mat_string, std::unique_ptr& c) const; + //! Assign a material overriding normal assignement to a cell + //! \param[in] c The OpenMC cell to which the material is assigned + void override_assign_material(std::unique_ptr& c) const; + //! Return the index into the model cells vector for a given DAGMC volume //! handle in the universe //! \param[in] vol MOAB handle to the DAGMC volume set @@ -184,6 +194,11 @@ class DAGUniverse : public Universe { //!< generate new material IDs for the universe bool has_graveyard_; //!< Indicates if the DAGMC geometry has a "graveyard" //!< volume + std::unordered_map> + material_overrides_; //!< Map of material overrides + //!< keys correspond to the DAGMCCell id + //!< values are a list of material ids used + //!< for the override }; //============================================================================== diff --git a/include/openmc/distribution_spatial.h b/include/openmc/distribution_spatial.h index 28568c890d8..8ff766d1333 100644 --- a/include/openmc/distribution_spatial.h +++ b/include/openmc/distribution_spatial.h @@ -136,6 +136,26 @@ class MeshSpatial : public SpatialDistribution { //!< mesh element indices }; +//============================================================================== +//! Distribution of points +//============================================================================== + +class PointCloud : public SpatialDistribution { +public: + explicit PointCloud(pugi::xml_node node); + explicit PointCloud( + std::vector point_cloud, gsl::span strengths); + + //! Sample a position from the distribution + //! \param seed Pseudorandom number seed pointer + //! \return Sampled position + Position sample(uint64_t* seed) const override; + +private: + std::vector point_cloud_; + DiscreteIndex point_idx_dist_; //!< Distribution of Position indices +}; + //============================================================================== //! Uniform distribution of points over a box //============================================================================== diff --git a/include/openmc/lattice.h b/include/openmc/lattice.h index 11dda4a6b85..fb374ab75c3 100644 --- a/include/openmc/lattice.h +++ b/include/openmc/lattice.h @@ -56,13 +56,14 @@ class Lattice { virtual ~Lattice() {} - virtual int32_t const& operator[](array const& i_xyz) = 0; + virtual const int32_t& operator[](const array& i_xyz) = 0; virtual LatticeIter begin(); - LatticeIter end(); + virtual LatticeIter end(); + virtual int32_t& back(); virtual ReverseLatticeIter rbegin(); - ReverseLatticeIter rend(); + virtual ReverseLatticeIter rend(); //! Convert internal universe values from IDs to indices using universe_map. void adjust_indices(); @@ -70,7 +71,8 @@ class Lattice { //! Allocate offset table for distribcell. void allocate_offset_table(int n_maps) { - offsets_.resize(n_maps * universes_.size(), C_NONE); + offsets_.resize(n_maps * universes_.size()); + std::fill(offsets_.begin(), offsets_.end(), C_NONE); } //! Populate the distribcell offset tables. @@ -81,7 +83,7 @@ class Lattice { //! \param i_xyz[3] The indices for a lattice tile. //! \return true if the given indices fit within the lattice bounds. False //! otherwise. - virtual bool are_valid_indices(array const& i_xyz) const = 0; + virtual bool are_valid_indices(const array& i_xyz) const = 0; //! \brief Find the next lattice surface crossing //! \param r A 3D Cartesian coordinate. @@ -125,7 +127,7 @@ class Lattice { //! \param i_xyz[3] The indices for a lattice tile. //! \return Distribcell offset i.e. the largest instance number for the target //! cell found in the geometry tree under this lattice tile. - virtual int32_t& offset(int map, array const& i_xyz) = 0; + virtual int32_t& offset(int map, const array& i_xyz) = 0; //! \brief Get the distribcell offset for a lattice tile. //! \param The map index for the target cell. @@ -167,12 +169,12 @@ class LatticeIter { LatticeIter& operator++() { - while (indx_ < lat_.universes_.size()) { + while (indx_ < lat_.end().indx_) { ++indx_; if (lat_.is_valid_index(indx_)) return *this; } - indx_ = lat_.universes_.size(); + indx_ = lat_.end().indx_; return *this; } @@ -190,7 +192,7 @@ class ReverseLatticeIter : public LatticeIter { ReverseLatticeIter& operator++() { - while (indx_ > -1) { + while (indx_ > lat_.begin().indx_ - 1) { --indx_; if (lat_.is_valid_index(indx_)) return *this; @@ -206,9 +208,9 @@ class RectLattice : public Lattice { public: explicit RectLattice(pugi::xml_node lat_node); - int32_t const& operator[](array const& i_xyz) override; + const int32_t& operator[](const array& i_xyz) override; - bool are_valid_indices(array const& i_xyz) const override; + bool are_valid_indices(const array& i_xyz) const override; std::pair> distance( Position r, Direction u, const array& i_xyz) const override; @@ -221,7 +223,7 @@ class RectLattice : public Lattice { Position get_local_position( Position r, const array& i_xyz) const override; - int32_t& offset(int map, array const& i_xyz) override; + int32_t& offset(int map, const array& i_xyz) override; int32_t offset(int map, int indx) const override; @@ -241,13 +243,19 @@ class HexLattice : public Lattice { public: explicit HexLattice(pugi::xml_node lat_node); - int32_t const& operator[](array const& i_xyz) override; + const int32_t& operator[](const array& i_xyz) override; LatticeIter begin() override; ReverseLatticeIter rbegin() override; - bool are_valid_indices(array const& i_xyz) const override; + LatticeIter end() override; + + int32_t& back() override; + + ReverseLatticeIter rend() override; + + bool are_valid_indices(const array& i_xyz) const override; std::pair> distance( Position r, Direction u, const array& i_xyz) const override; @@ -262,7 +270,7 @@ class HexLattice : public Lattice { bool is_valid_index(int indx) const override; - int32_t& offset(int map, array const& i_xyz) override; + int32_t& offset(int map, const array& i_xyz) override; int32_t offset(int map, int indx) const override; diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 3917e6368a4..bd86d54fb72 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -11,6 +11,7 @@ #include "xtensor/xtensor.hpp" #include +#include "openmc/bounding_box.h" #include "openmc/error.h" #include "openmc/memory.h" // for unique_ptr #include "openmc/particle.h" @@ -82,8 +83,8 @@ class Mesh { virtual ~Mesh() = default; // Methods - //! Perform any preparation needed to support use in mesh filters - virtual void prepare_for_tallies() {}; + //! Perform any preparation needed to support point location within the mesh + virtual void prepare_for_point_location() {}; //! Update a position to the local coordinates of the mesh virtual void local_coords(Position& r) const {}; @@ -131,13 +132,18 @@ class Mesh { int32_t id() const { return id_; } + const std::string& name() const { return name_; } + //! Set the mesh ID void set_id(int32_t id = -1); + //! Write the mesh data to an HDF5 group + void to_hdf5(hid_t group) const; + //! Write mesh data to an HDF5 group // //! \param[in] group HDF5 group - virtual void to_hdf5(hid_t group) const = 0; + virtual void to_hdf5_inner(hid_t group) const = 0; //! Find the mesh lines that intersect an axis-aligned slice plot // @@ -185,9 +191,25 @@ class Mesh { vector material_volumes( int n_sample, int bin, uint64_t* seed) const; + //! Determine bounding box of mesh + // + //! \return Bounding box of mesh + BoundingBox bounding_box() const + { + auto ll = this->lower_left(); + auto ur = this->upper_right(); + return {ll.x, ur.x, ll.y, ur.y, ll.z, ur.z}; + } + + virtual Position lower_left() const = 0; + virtual Position upper_right() const = 0; + // Data members - int id_ {-1}; //!< User-specified ID - int n_dimension_ {-1}; //!< Number of dimensions + xt::xtensor lower_left_; //!< Lower-left coordinates of mesh + xt::xtensor upper_right_; //!< Upper-right coordinates of mesh + int id_ {-1}; //!< Mesh ID + std::string name_; //!< User-specified name + int n_dimension_ {-1}; //!< Number of dimensions }; class StructuredMesh : public Mesh { @@ -325,14 +347,30 @@ class StructuredMesh : public Mesh { return this->volume(get_indices_from_bin(bin)); } + Position lower_left() const override + { + int n = lower_left_.size(); + Position ll {lower_left_[0], 0.0, 0.0}; + ll.y = (n >= 2) ? lower_left_[1] : -INFTY; + ll.z = (n == 3) ? lower_left_[2] : -INFTY; + return ll; + }; + + Position upper_right() const override + { + int n = upper_right_.size(); + Position ur {upper_right_[0], 0.0, 0.0}; + ur.y = (n >= 2) ? upper_right_[1] : INFTY; + ur.z = (n == 3) ? upper_right_[2] : INFTY; + return ur; + }; + //! Get the volume of a specified element //! \param[in] ijk Mesh index to return the volume for //! \return Volume of the bin virtual double volume(const MeshIndex& ijk) const = 0; // Data members - xt::xtensor lower_left_; //!< Lower-left coordinates of mesh - xt::xtensor upper_right_; //!< Upper-right coordinates of mesh std::array shape_; //!< Number of mesh elements in each dimension protected: @@ -378,7 +416,7 @@ class RegularMesh : public StructuredMesh { std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; - void to_hdf5(hid_t group) const override; + void to_hdf5_inner(hid_t group) const override; //! Get the coordinate for the mesh grid boundary in the positive direction //! @@ -428,7 +466,7 @@ class RectilinearMesh : public StructuredMesh { std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; - void to_hdf5(hid_t group) const override; + void to_hdf5_inner(hid_t group) const override; //! Get the coordinate for the mesh grid boundary in the positive direction //! @@ -474,7 +512,7 @@ class CylindricalMesh : public PeriodicStructuredMesh { std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; - void to_hdf5(hid_t group) const override; + void to_hdf5_inner(hid_t group) const override; double volume(const MeshIndex& ijk) const override; @@ -538,7 +576,7 @@ class SphericalMesh : public PeriodicStructuredMesh { std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; - void to_hdf5(hid_t group) const override; + void to_hdf5_inner(hid_t group) const override; double r(int i) const { return grid_[0][i]; } double theta(int i) const { return grid_[1][i]; } @@ -600,7 +638,7 @@ class UnstructuredMesh : public Mesh { void surface_bins_crossed(Position r0, Position r1, const Direction& u, vector& bins) const override; - void to_hdf5(hid_t group) const override; + void to_hdf5_inner(hid_t group) const override; std::string bin_label(int bin) const override; @@ -655,6 +693,15 @@ class UnstructuredMesh : public Mesh { ElementType element_type(int bin) const; + Position lower_left() const override + { + return {lower_left_[0], lower_left_[1], lower_left_[2]}; + } + Position upper_right() const override + { + return {upper_right_[0], upper_right_[1], upper_right_[2]}; + } + protected: //! Set the length multiplier to apply to each point in the mesh void set_length_multiplier(const double length_multiplier); @@ -672,6 +719,9 @@ class UnstructuredMesh : public Mesh { -1.0}; //!< Multiplicative factor applied to mesh coordinates std::string options_; //!< Options for search data structures + //! Determine lower-left and upper-right bounds of mesh + void determine_bounds(); + private: //! Setup method for the mesh. Builds data structures, //! sets up element mapping, creates bounding boxes, etc. @@ -693,7 +743,7 @@ class MOABMesh : public UnstructuredMesh { // Overridden Methods //! Perform any preparation needed to support use in mesh filters - void prepare_for_tallies() override; + void prepare_for_point_location() override; Position sample_element(int32_t bin, uint64_t* seed) const override; @@ -904,6 +954,7 @@ class LibMesh : public UnstructuredMesh { private: void initialize() override; void set_mesh_pointer_from_filename(const std::string& filename); + void build_eqn_sys(); // Methods @@ -922,7 +973,8 @@ class LibMesh : public UnstructuredMesh { vector> pl_; //!< per-thread point locators unique_ptr - equation_systems_; //!< pointer to the equation systems of the mesh + equation_systems_; //!< pointer to the libMesh EquationSystems + //!< instance std::string eq_system_name_; //!< name of the equation system holding OpenMC results std::unordered_map @@ -931,6 +983,13 @@ class LibMesh : public UnstructuredMesh { libMesh::BoundingBox bbox_; //!< bounding box of the mesh libMesh::dof_id_type first_element_id_; //!< id of the first element in the mesh + + const bool adaptive_; //!< whether this mesh has adaptivity enabled or not + std::vector + bin_to_elem_map_; //!< mapping bin indices to dof indices for active + //!< elements + std::vector elem_to_bin_map_; //!< mapping dof indices to bin indices for + //!< active elements }; #endif diff --git a/include/openmc/mgxs.h b/include/openmc/mgxs.h index 3fb0608104f..9b1602f299a 100644 --- a/include/openmc/mgxs.h +++ b/include/openmc/mgxs.h @@ -86,8 +86,10 @@ class Mgxs { bool fissionable; // Is this fissionable bool is_isotropic { true}; // used to skip search for angle indices if isotropic + bool exists_in_model {true}; // Is this present in model Mgxs() = default; + Mgxs(bool exists) : exists_in_model(exists) {} //! \brief Constructor that loads the Mgxs object from the HDF5 file //! diff --git a/include/openmc/mgxs_interface.h b/include/openmc/mgxs_interface.h index 8bcdf6dc608..da074f825ee 100644 --- a/include/openmc/mgxs_interface.h +++ b/include/openmc/mgxs_interface.h @@ -46,6 +46,9 @@ class MgxsInterface { // Get the kT values which are used in the OpenMC model vector> get_mat_kTs(); + // Get the group index corresponding to a continuous energy + int get_group_index(double E); + int num_energy_groups_; int num_delayed_groups_; vector xs_names_; // available names in HDF5 file diff --git a/include/openmc/output.h b/include/openmc/output.h index 1ece3de960c..940ea78ceb9 100644 --- a/include/openmc/output.h +++ b/include/openmc/output.h @@ -76,10 +76,14 @@ struct formatter> { } template +#if FMT_VERSION >= 110000 // Version 11.0.0 and above + auto format(const std::array& arr, FormatContext& ctx) const { +#else // For versions below 11.0.0 auto format(const std::array& arr, FormatContext& ctx) { +#endif return format_to(ctx.out(), "({}, {})", arr[0], arr[1]); - } -}; +} +}; // namespace fmt } // namespace fmt diff --git a/include/openmc/particle.h b/include/openmc/particle.h index e423880f87c..6a2e67049fd 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -74,7 +74,7 @@ class Particle : public ParticleData { void pht_secondary_particles(); //! Cross a surface and handle boundary conditions - void cross_surface(); + void cross_surface(const Surface& surf); //! Cross a vacuum boundary condition. // @@ -127,6 +127,8 @@ std::string particle_type_to_str(ParticleType type); ParticleType str_to_particle_type(std::string str); +void add_surf_source_to_bank(Particle& p, const Surface& surf); + } // namespace openmc #endif // OPENMC_PARTICLE_H diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 5c765e2e605..164148cce10 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -211,9 +211,15 @@ class GeometryState { // resets all coordinate levels for the particle void clear() { - for (auto& level : coord_) + for (auto& level : coord_) { level.reset(); + } n_coord_ = 1; + + for (auto& cell : cell_last_) { + cell = C_NONE; + } + n_coord_last_ = 1; } // Initialize all internal state from position and direction diff --git a/include/openmc/position.h b/include/openmc/position.h index 11ea3764792..dc60ab35a04 100644 --- a/include/openmc/position.h +++ b/include/openmc/position.h @@ -221,12 +221,16 @@ namespace fmt { template<> struct formatter : formatter { template +#if FMT_VERSION >= 110000 // Version 11.0.0 and above + auto format(const openmc::Position& pos, FormatContext& ctx) const { +#else // For versions below 11.0.0 auto format(const openmc::Position& pos, FormatContext& ctx) { +#endif return formatter::format( fmt::format("({}, {}, {})", pos.x, pos.y, pos.z), ctx); - } -}; +} +}; // namespace fmt } // namespace fmt diff --git a/include/openmc/random_ray/flat_source_domain.h b/include/openmc/random_ray/flat_source_domain.h index 5f64914edb9..b5b5db7062e 100644 --- a/include/openmc/random_ray/flat_source_domain.h +++ b/include/openmc/random_ray/flat_source_domain.h @@ -1,11 +1,86 @@ #ifndef OPENMC_RANDOM_RAY_FLAT_SOURCE_DOMAIN_H #define OPENMC_RANDOM_RAY_FLAT_SOURCE_DOMAIN_H +#include "openmc/constants.h" #include "openmc/openmp_interface.h" #include "openmc/position.h" +#include "openmc/source.h" namespace openmc { +//---------------------------------------------------------------------------- +// Helper Functions + +// The hash_combine function is the standard hash combine function from boost +// that is typically used for combining multiple hash values into a single hash +// as is needed for larger objects being stored in a hash map. The function is +// taken from: +// https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine +// which carries the following license: +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +inline void hash_combine(size_t& seed, const size_t v) +{ + seed ^= (v + 0x9e3779b9 + (seed << 6) + (seed >> 2)); +} + +//---------------------------------------------------------------------------- +// Helper Structs + +// A mapping object that is used to map between a specific random ray +// source region and an OpenMC native tally bin that it should score to +// every iteration. +struct TallyTask { + int tally_idx; + int filter_idx; + int score_idx; + int score_type; + TallyTask(int tally_idx, int filter_idx, int score_idx, int score_type) + : tally_idx(tally_idx), filter_idx(filter_idx), score_idx(score_idx), + score_type(score_type) + {} + TallyTask() = default; + + // Comparison and Hash operators are defined to allow usage of the + // TallyTask struct as a key in an unordered_set + bool operator==(const TallyTask& other) const + { + return tally_idx == other.tally_idx && filter_idx == other.filter_idx && + score_idx == other.score_idx && score_type == other.score_type; + } + + struct HashFunctor { + size_t operator()(const TallyTask& task) const + { + size_t seed = 0; + hash_combine(seed, task.tally_idx); + hash_combine(seed, task.filter_idx); + hash_combine(seed, task.score_idx); + hash_combine(seed, task.score_type); + return seed; + } + }; +}; + /* * The FlatSourceDomain class encompasses data and methods for storing * scalar flux and source region for all flat source regions in a @@ -15,39 +90,42 @@ namespace openmc { class FlatSourceDomain { public: //---------------------------------------------------------------------------- - // Helper Structs - - // A mapping object that is used to map between a specific random ray - // source region and an OpenMC native tally bin that it should score to - // every iteration. - struct TallyTask { - int tally_idx; - int filter_idx; - int score_idx; - int score_type; - TallyTask(int tally_idx, int filter_idx, int score_idx, int score_type) - : tally_idx(tally_idx), filter_idx(filter_idx), score_idx(score_idx), - score_type(score_type) - {} - }; - - //---------------------------------------------------------------------------- - // Constructors + // Constructors and Destructors FlatSourceDomain(); + virtual ~FlatSourceDomain() = default; //---------------------------------------------------------------------------- // Methods - void update_neutron_source(double k_eff); + virtual void update_neutron_source(double k_eff); double compute_k_eff(double k_eff_old) const; - void normalize_scalar_flux_and_volumes( + virtual void normalize_scalar_flux_and_volumes( double total_active_distance_per_iteration); + int64_t add_source_to_scalar_flux(); - void batch_reset(); + virtual void batch_reset(); void convert_source_regions_to_tallies(); - void random_ray_tally() const; - void accumulate_iteration_flux(); + void reset_tally_volumes(); + void random_ray_tally(); + virtual void accumulate_iteration_flux(); void output_to_vtk() const; - void all_reduce_replicated_source_regions(); + virtual void all_reduce_replicated_source_regions(); + void convert_external_sources(); + void count_external_source_regions(); + void set_adjoint_sources(const vector& forward_flux); + virtual void flux_swap(); + virtual double evaluate_flux_at_point(Position r, int64_t sr, int g) const; + double compute_fixed_source_normalization_factor() const; + void flatten_xs(); + void transpose_scattering_matrix(); + + //---------------------------------------------------------------------------- + // Static Data members + static bool volume_normalized_flux_tallies_; + static bool adjoint_; // If the user wants outputs based on the adjoint flux + + //---------------------------------------------------------------------------- + // Static data members + static RandomRayVolumeEstimator volume_estimator_; //---------------------------------------------------------------------------- // Public Data members @@ -55,6 +133,8 @@ class FlatSourceDomain { bool mapped_all_tallies_ {false}; // If all source regions have been visited int64_t n_source_regions_ {0}; // Total number of source regions in the model + int64_t n_external_source_regions_ {0}; // Total number of source regions with + // non-zero external source terms // 1D array representing source region starting offset for each OpenMC Cell // in model::cells @@ -62,35 +142,78 @@ class FlatSourceDomain { // 1D arrays representing values for all source regions vector lock_; - vector was_hit_; vector volume_; + vector volume_t_; vector position_recorded_; vector position_; // 2D arrays stored in 1D representing values for all source regions x energy // groups - vector scalar_flux_old_; - vector scalar_flux_new_; + vector scalar_flux_old_; + vector scalar_flux_new_; vector source_; + vector external_source_; + vector external_source_present_; + vector scalar_flux_final_; + + // 2D arrays stored in 1D representing values for all materials x energy + // groups + int n_materials_; + vector sigma_t_; + vector nu_sigma_f_; + vector sigma_f_; + vector chi_; + + // 3D arrays stored in 1D representing values for all materials x energy + // groups x energy groups + vector sigma_s_; + +protected: + //---------------------------------------------------------------------------- + // Methods + void apply_external_source_to_source_region( + Discrete* discrete, double strength_factor, int64_t source_region); + void apply_external_source_to_cell_instances(int32_t i_cell, + Discrete* discrete, double strength_factor, int target_material_id, + const vector& instances); + void apply_external_source_to_cell_and_children(int32_t i_cell, + Discrete* discrete, double strength_factor, int32_t target_material_id); + virtual void set_flux_to_flux_plus_source( + int64_t idx, double volume, int material, int g); + void set_flux_to_source(int64_t idx); + virtual void set_flux_to_old_flux(int64_t idx); //---------------------------------------------------------------------------- // Private data members -private: int negroups_; // Number of energy groups in simulation int64_t n_source_elements_ {0}; // Total number of source regions in the model // times the number of energy groups - // 2D array representing values for all source regions x energy groups x tally + double + simulation_volume_; // Total physical volume of the simulation domain, as + // defined by the 3D box of the random ray source + + // 2D array representing values for all source elements x tally // tasks vector> tally_task_; + // 1D array representing values for all source regions, with each region + // containing a set of volume tally tasks. This more complicated data + // structure is convenient for ensuring that volumes are only tallied once per + // source region, regardless of how many energy groups are used for tallying. + vector> volume_task_; + // 1D arrays representing values for all source regions vector material_; - vector volume_t_; + vector volume_naive_; - // 2D arrays stored in 1D representing values for all source regions x energy - // groups - vector scalar_flux_final_; + // Volumes for each tally and bin/score combination. This intermediate data + // structure is used when tallying quantities that must be normalized by + // volume (i.e., flux). The vector is index by tally index, while the inner 2D + // xtensor is indexed by bin index and score index in a similar manner to the + // results tensor in the Tally class, though without the third dimension, as + // SUM and SUM_SQ do not need to be tracked. + vector> tally_volumes_; }; // class FlatSourceDomain diff --git a/include/openmc/random_ray/linear_source_domain.h b/include/openmc/random_ray/linear_source_domain.h new file mode 100644 index 00000000000..4812d14337c --- /dev/null +++ b/include/openmc/random_ray/linear_source_domain.h @@ -0,0 +1,68 @@ +#ifndef OPENMC_RANDOM_RAY_LINEAR_SOURCE_DOMAIN_H +#define OPENMC_RANDOM_RAY_LINEAR_SOURCE_DOMAIN_H + +#include "openmc/random_ray/flat_source_domain.h" +#include "openmc/random_ray/moment_matrix.h" + +#include "openmc/openmp_interface.h" +#include "openmc/position.h" +#include "openmc/source.h" + +namespace openmc { + +/* + * The LinearSourceDomain class encompasses data and methods for storing + * scalar flux and source region for all linear source regions in a + * random ray simulation domain. + */ + +class LinearSourceDomain : public FlatSourceDomain { +public: + //---------------------------------------------------------------------------- + // Constructors + LinearSourceDomain(); + + //---------------------------------------------------------------------------- + // Methods + void update_neutron_source(double k_eff) override; + double compute_k_eff(double k_eff_old) const; + void normalize_scalar_flux_and_volumes( + double total_active_distance_per_iteration) override; + + void batch_reset() override; + void convert_source_regions_to_tallies(); + void reset_tally_volumes(); + void random_ray_tally(); + void accumulate_iteration_flux() override; + void output_to_vtk() const; + void all_reduce_replicated_source_regions() override; + void convert_external_sources(); + void count_external_source_regions(); + void flux_swap() override; + double evaluate_flux_at_point(Position r, int64_t sr, int g) const override; + + //---------------------------------------------------------------------------- + // Public Data members + + vector source_gradients_; + vector flux_moments_old_; + vector flux_moments_new_; + vector flux_moments_t_; + vector centroid_; + vector centroid_iteration_; + vector centroid_t_; + vector mom_matrix_; + vector mom_matrix_t_; + +protected: + //---------------------------------------------------------------------------- + // Methods + void set_flux_to_flux_plus_source( + int64_t idx, double volume, int material, int g) override; + void set_flux_to_old_flux(int64_t idx) override; + +}; // class LinearSourceDomain + +} // namespace openmc + +#endif // OPENMC_RANDOM_RAY_LINEAR_SOURCE_DOMAIN_H diff --git a/include/openmc/random_ray/moment_matrix.h b/include/openmc/random_ray/moment_matrix.h new file mode 100644 index 00000000000..c95bb2c1286 --- /dev/null +++ b/include/openmc/random_ray/moment_matrix.h @@ -0,0 +1,90 @@ +#ifndef OPENMC_MOMENT_MATRIX_H +#define OPENMC_MOMENT_MATRIX_H + +#include + +#include "openmc/position.h" + +namespace openmc { + +// The MomentArray class is a 3-element array representing the x, y, and z +// moments. It is defined as an alias for the Position class to allow for +// dot products and other operations with Position objects. +// TODO: This class could in theory have 32-bit instead of 64-bit FP values. +using MomentArray = Position; + +// The MomentMatrix class is a sparse representation a 3x3 symmetric +// matrix, with elements labeled as follows: +// +// | a b c | +// | b d e | +// | c e f | +// +// This class uses FP64 values as objects that are accumulated to over many +// iterations. +class MomentMatrix { +public: + //---------------------------------------------------------------------------- + // Public data members + double a; + double b; + double c; + double d; + double e; + double f; + + //---------------------------------------------------------------------------- + // Constructors + MomentMatrix() = default; + MomentMatrix(double a, double b, double c, double d, double e, double f) + : a {a}, b {b}, c {c}, d {d}, e {e}, f {f} + {} + + //---------------------------------------------------------------------------- + // Methods + MomentMatrix inverse() const; + double determinant() const; + void compute_spatial_moments_matrix( + const Position& r, const Direction& u, const double& distance); + + inline void set_to_zero() { a = b = c = d = e = f = 0; } + + inline MomentMatrix& operator*=(double x) + { + a *= x; + b *= x; + c *= x; + d *= x; + e *= x; + f *= x; + return *this; + } + + inline MomentMatrix operator*(double x) const + { + MomentMatrix m_copy = *this; + m_copy *= x; + return m_copy; + } + + inline MomentMatrix& operator+=(const MomentMatrix& rhs) + { + a += rhs.a; + b += rhs.b; + c += rhs.c; + d += rhs.d; + e += rhs.e; + f += rhs.f; + return *this; + } + + MomentArray operator*(const MomentArray& rhs) const + { + return {a * rhs.x + b * rhs.y + c * rhs.z, + b * rhs.x + d * rhs.y + e * rhs.z, c * rhs.x + e * rhs.y + f * rhs.z}; + } +}; + +} // namespace openmc + +#endif // OPENMC_MOMENT_MATRIX_H diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index 7771b746e35..9b375f42c16 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -4,6 +4,7 @@ #include "openmc/memory.h" #include "openmc/particle.h" #include "openmc/random_ray/flat_source_domain.h" +#include "openmc/random_ray/moment_matrix.h" #include "openmc/source.h" namespace openmc { @@ -25,24 +26,32 @@ class RandomRay : public Particle { // Methods void event_advance_ray(); void attenuate_flux(double distance, bool is_active); + void attenuate_flux_flat_source(double distance, bool is_active); + void attenuate_flux_linear_source(double distance, bool is_active); + void initialize_ray(uint64_t ray_id, FlatSourceDomain* domain); uint64_t transport_history_based_single_ray(); SourceSite sample_lds(uint64_t* seed, int64_t skip); //---------------------------------------------------------------------------- // Static data members - static double distance_inactive_; // Inactive (dead zone) ray length - static double distance_active_; // Active ray length - static unique_ptr ray_source_; // Starting source for ray sampling + static double distance_inactive_; // Inactive (dead zone) ray length + static double distance_active_; // Active ray length + static unique_ptr ray_source_; // Starting source for ray sampling + static RandomRaySourceShape source_shape_; // Flag for linear source //---------------------------------------------------------------------------- // Public data members vector angular_flux_; + bool ray_trace_only_ {false}; // If true, only perform geometry operations + private: //---------------------------------------------------------------------------- // Private data members vector delta_psi_; + vector delta_moments_; + int negroups_; FlatSourceDomain* domain_ {nullptr}; // pointer to domain that has flat source // data needed for ray transport diff --git a/include/openmc/random_ray/random_ray_simulation.h b/include/openmc/random_ray/random_ray_simulation.h index cde0d27dff2..01f12bffb17 100644 --- a/include/openmc/random_ray/random_ray_simulation.h +++ b/include/openmc/random_ray/random_ray_simulation.h @@ -2,6 +2,7 @@ #define OPENMC_RANDOM_RAY_SIMULATION_H #include "openmc/random_ray/flat_source_domain.h" +#include "openmc/random_ray/linear_source_domain.h" namespace openmc { @@ -18,19 +19,28 @@ class RandomRaySimulation { //---------------------------------------------------------------------------- // Methods + void compute_segment_correction_factors(); + void prepare_fixed_sources(); + void prepare_fixed_sources_adjoint(vector& forward_flux); void simulate(); void reduce_simulation_statistics(); void output_simulation_results() const; void instability_check( int64_t n_hits, double k_eff, double& avg_miss_rate) const; void print_results_random_ray(uint64_t total_geometric_intersections, - double avg_miss_rate, int negroups, int64_t n_source_regions) const; + double avg_miss_rate, int negroups, int64_t n_source_regions, + int64_t n_external_source_regions) const; //---------------------------------------------------------------------------- - // Data members + // Accessors + FlatSourceDomain* domain() const { return domain_.get(); } + private: + //---------------------------------------------------------------------------- + // Data members + // Contains all flat source region data - FlatSourceDomain domain_; + unique_ptr domain_; // Random ray eigenvalue double k_eff_ {1.0}; diff --git a/include/openmc/settings.h b/include/openmc/settings.h index b71ec364931..9a4ce56ec8f 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -16,6 +16,14 @@ namespace openmc { +// Type of surface source write +enum class SSWCellType { + None, + Both, + From, + To, +}; + //============================================================================== // Global variable declarations //============================================================================== @@ -36,29 +44,30 @@ extern "C" bool entropy_on; //!< calculate Shannon entropy? extern "C" bool event_based; //!< use event-based mode (instead of history-based) extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular? -extern bool material_cell_offsets; //!< create material cells offsets? -extern "C" bool output_summary; //!< write summary.h5? -extern bool output_tallies; //!< write tallies.out? -extern bool particle_restart_run; //!< particle restart run? -extern "C" bool photon_transport; //!< photon transport turned on? -extern "C" bool reduce_tallies; //!< reduce tallies at end of batch? -extern bool res_scat_on; //!< use resonance upscattering method? -extern "C" bool restart_run; //!< restart run? -extern "C" bool run_CE; //!< run with continuous-energy data? -extern bool source_latest; //!< write latest source at each batch? -extern bool source_separate; //!< write source to separate file? -extern bool source_write; //!< write source in HDF5 files? -extern bool source_mcpl_write; //!< write source in mcpl files? -extern bool surf_source_write; //!< write surface source file? -extern bool surf_mcpl_write; //!< write surface mcpl file? -extern bool surf_source_read; //!< read surface source file? -extern bool survival_biasing; //!< use survival biasing? -extern bool temperature_multipole; //!< use multipole data? -extern "C" bool trigger_on; //!< tally triggers enabled? -extern bool trigger_predict; //!< predict batches for triggers? -extern bool ufs_on; //!< uniform fission site method on? -extern bool urr_ptables_on; //!< use unresolved resonance prob. tables? -extern "C" bool weight_windows_on; //!< are weight windows are enabled? +extern bool material_cell_offsets; //!< create material cells offsets? +extern "C" bool output_summary; //!< write summary.h5? +extern bool output_tallies; //!< write tallies.out? +extern bool particle_restart_run; //!< particle restart run? +extern "C" bool photon_transport; //!< photon transport turned on? +extern "C" bool reduce_tallies; //!< reduce tallies at end of batch? +extern bool res_scat_on; //!< use resonance upscattering method? +extern "C" bool restart_run; //!< restart run? +extern "C" bool run_CE; //!< run with continuous-energy data? +extern bool source_latest; //!< write latest source at each batch? +extern bool source_separate; //!< write source to separate file? +extern bool source_write; //!< write source in HDF5 files? +extern bool source_mcpl_write; //!< write source in mcpl files? +extern bool surf_source_write; //!< write surface source file? +extern bool surf_mcpl_write; //!< write surface mcpl file? +extern bool surf_source_read; //!< read surface source file? +extern bool survival_biasing; //!< use survival biasing? +extern bool temperature_multipole; //!< use multipole data? +extern "C" bool trigger_on; //!< tally triggers enabled? +extern bool trigger_predict; //!< predict batches for triggers? +extern bool uniform_source_sampling; //!< sample sources uniformly? +extern bool ufs_on; //!< uniform fission site method on? +extern bool urr_ptables_on; //!< use unresolved resonance prob. tables? +extern "C" bool weight_windows_on; //!< are weight windows are enabled? extern bool weight_window_checkpoint_surface; //!< enable weight window check //!< upon surface crossing? extern bool weight_window_checkpoint_collision; //!< enable weight window check @@ -121,9 +130,17 @@ extern std::unordered_set statepoint_batch; //!< Batches when state should be written extern std::unordered_set source_write_surf_id; //!< Surface ids where sources will be written -extern int max_splits; //!< maximum number of particle splits for weight windows -extern int64_t max_surface_particles; //!< maximum number of particles to be - //!< banked on surfaces per process + +extern int + max_history_splits; //!< maximum number of particle splits for weight windows +extern int64_t ssw_max_particles; //!< maximum number of particles to be + //!< banked on surfaces per process +extern int64_t ssw_max_files; //!< maximum number of surface source files + //!< to be created +extern int64_t ssw_cell_id; //!< Cell id for the surface source + //!< write setting +extern SSWCellType ssw_cell_type; //!< Type of option for the cell + //!< argument of surface source write extern TemperatureMethod temperature_method; //!< method for choosing temperatures extern double diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index dd8bb7cdfd7..3e4e24e1d06 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -37,6 +37,7 @@ extern "C" int n_lost_particles; //!< cumulative number of lost particles extern "C" bool need_depletion_rx; //!< need to calculate depletion rx? extern "C" int restart_batch; //!< batch at which a restart job resumed extern "C" bool satisfy_triggers; //!< have tally triggers been satisfied? +extern int ssw_current_file; //!< current surface source file extern "C" int total_gen; //!< total number of generations simulated extern double total_weight; //!< Total source weight in a batch extern int64_t work_per_rank; //!< number of particles per MPI rank diff --git a/include/openmc/source.h b/include/openmc/source.h index 9ef8b925127..6733eaeffca 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -36,6 +36,9 @@ namespace model { extern vector> external_sources; +// Probability distribution for selecting external sources +extern DiscreteIndex external_sources_probability; + } // namespace model //============================================================================== @@ -52,6 +55,8 @@ extern vector> external_sources; class Source { public: + // Domain types + enum class DomainType { UNIVERSE, MATERIAL, CELL }; // Constructors, destructors Source() = default; explicit Source(pugi::xml_node node); @@ -76,9 +81,6 @@ class Source { static unique_ptr create(pugi::xml_node node); protected: - // Domain types - enum class DomainType { UNIVERSE, MATERIAL, CELL }; - // Strategy used for rejecting sites when constraints are applied. KILL means // that sites are always accepted but if they don't satisfy constraints, they // are given weight 0. RESAMPLE means that a new source site will be sampled @@ -134,6 +136,10 @@ class IndependentSource : public Source { Distribution* energy() const { return energy_.get(); } Distribution* time() const { return time_.get(); } + // Make domain type and ids available + DomainType domain_type() const { return domain_type_; } + const std::unordered_set& domain_ids() const { return domain_ids_; } + protected: // Indicates whether derived class already handles constraints bool constraints_applied() const override { return true; } diff --git a/include/openmc/state_point.h b/include/openmc/state_point.h index 95524f6b622..f0d41e1697f 100644 --- a/include/openmc/state_point.h +++ b/include/openmc/state_point.h @@ -2,6 +2,7 @@ #define OPENMC_STATE_POINT_H #include +#include #include @@ -33,8 +34,11 @@ void load_state_point(); // values on each rank, used to create global indexing. This vector // can be created by calling calculate_parallel_index_vector on // source_bank.size() if such a vector is not already available. -void write_source_point(const char* filename, gsl::span source_bank, - const vector& bank_index); +void write_h5_source_point(const char* filename, + gsl::span source_bank, const vector& bank_index); + +void write_source_point(std::string, gsl::span source_bank, + const vector& bank_index, bool use_mcpl); // This appends a source bank specification to an HDF5 file // that's already open. It is used internally by write_source_point. diff --git a/include/openmc/string_utils.h b/include/openmc/string_utils.h index 6b4c69d48b7..2e8b0d14f39 100644 --- a/include/openmc/string_utils.h +++ b/include/openmc/string_utils.h @@ -1,6 +1,7 @@ #ifndef OPENMC_STRING_UTILS_H #define OPENMC_STRING_UTILS_H +#include #include #include "openmc/vector.h" @@ -15,7 +16,7 @@ std::string to_element(const std::string& name); void to_lower(std::string& str); -int word_count(std::string const& str); +int word_count(const std::string& str); vector split(const std::string& in); @@ -23,5 +24,20 @@ bool ends_with(const std::string& value, const std::string& ending); bool starts_with(const std::string& value, const std::string& beginning); +template +inline std::string concatenate(const T& values, const std::string& del = ", ") +{ + if (values.size() == 0) + return ""; + + std::stringstream oss; + auto it = values.begin(); + oss << *it++; + while (it != values.end()) { + oss << del << *it++; + } + return oss.str(); +} + } // namespace openmc #endif // OPENMC_STRING_UTILS_H diff --git a/include/openmc/surface.h b/include/openmc/surface.h index 350775123c1..498f71d4f9b 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -9,6 +9,7 @@ #include "pugixml.hpp" #include "openmc/boundary_condition.h" +#include "openmc/bounding_box.h" #include "openmc/constants.h" #include "openmc/memory.h" // for unique_ptr #include "openmc/particle.h" @@ -28,55 +29,6 @@ extern std::unordered_map surface_map; extern vector> surfaces; } // namespace model -//============================================================================== -//! Coordinates for an axis-aligned cuboid that bounds a geometric object. -//============================================================================== - -struct BoundingBox { - double xmin = -INFTY; - double xmax = INFTY; - double ymin = -INFTY; - double ymax = INFTY; - double zmin = -INFTY; - double zmax = INFTY; - - inline BoundingBox operator&(const BoundingBox& other) - { - BoundingBox result = *this; - return result &= other; - } - - inline BoundingBox operator|(const BoundingBox& other) - { - BoundingBox result = *this; - return result |= other; - } - - // intersect operator - inline BoundingBox& operator&=(const BoundingBox& other) - { - xmin = std::max(xmin, other.xmin); - xmax = std::min(xmax, other.xmax); - ymin = std::max(ymin, other.ymin); - ymax = std::min(ymax, other.ymax); - zmin = std::max(zmin, other.zmin); - zmax = std::min(zmax, other.zmax); - return *this; - } - - // union operator - inline BoundingBox& operator|=(const BoundingBox& other) - { - xmin = std::min(xmin, other.xmin); - xmax = std::max(xmax, other.xmax); - ymin = std::min(ymin, other.ymin); - ymax = std::max(ymax, other.ymax); - zmin = std::min(zmin, other.zmin); - zmax = std::max(zmax, other.zmax); - return *this; - } -}; - //============================================================================== //! A geometry primitive used to define regions of 3D space. //============================================================================== @@ -86,7 +38,6 @@ class Surface { int id_; //!< Unique ID std::string name_; //!< User-defined name unique_ptr bc_; //!< Boundary condition - GeometryType geom_type_; //!< Geometry type indicator (CSG or DAGMC) bool surf_source_ {false}; //!< Activate source banking for the surface? explicit Surface(pugi::xml_node surf_node); @@ -139,6 +90,13 @@ class Surface { //! Get the BoundingBox for this surface. virtual BoundingBox bounding_box(bool /*pos_side*/) const { return {}; } + // Accessors + const GeometryType& geom_type() const { return geom_type_; } + GeometryType& geom_type() { return geom_type_; } + +private: + GeometryType geom_type_; //!< Geometry type indicator (CSG or DAGMC) + protected: virtual void to_hdf5_inner(hid_t group_id) const = 0; }; diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index 1166c0eee53..210ab284ba9 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -36,6 +36,7 @@ enum class FilterType { MESHBORN, MESH_SURFACE, MU, + MUSURFACE, PARTICLE, POLAR, SPHERICAL_HARMONICS, diff --git a/include/openmc/tallies/filter_mu.h b/include/openmc/tallies/filter_mu.h index 942ee60c225..b0ee40eac9f 100644 --- a/include/openmc/tallies/filter_mu.h +++ b/include/openmc/tallies/filter_mu.h @@ -40,7 +40,7 @@ class MuFilter : public Filter { void set_bins(gsl::span bins); -private: +protected: //---------------------------------------------------------------------------- // Data members diff --git a/include/openmc/tallies/filter_musurface.h b/include/openmc/tallies/filter_musurface.h new file mode 100644 index 00000000000..2ca19e3a259 --- /dev/null +++ b/include/openmc/tallies/filter_musurface.h @@ -0,0 +1,34 @@ +#ifndef OPENMC_TALLIES_FILTER_MU_SURFACE_H +#define OPENMC_TALLIES_FILTER_MU_SURFACE_H + +#include + +#include "openmc/tallies/filter_mu.h" +#include "openmc/vector.h" + +namespace openmc { + +//============================================================================== +//! Bins the incoming-outgoing direction cosine. This is only used for surface +//! crossings. +//============================================================================== + +class MuSurfaceFilter : public MuFilter { +public: + //---------------------------------------------------------------------------- + // Constructors, destructors + + ~MuSurfaceFilter() = default; + + //---------------------------------------------------------------------------- + // Methods + + std::string type_str() const override { return "musurface"; } + FilterType type() const override { return FilterType::MUSURFACE; } + + void get_all_bins(const Particle& p, TallyEstimator estimator, + FilterMatch& match) const override; +}; + +} // namespace openmc +#endif // OPENMC_TALLIES_FILTER_MU_SURFACE_H diff --git a/include/openmc/universe.h b/include/openmc/universe.h index 26f33cb383d..9fea06bccba 100644 --- a/include/openmc/universe.h +++ b/include/openmc/universe.h @@ -1,6 +1,7 @@ #ifndef OPENMC_UNIVERSE_H #define OPENMC_UNIVERSE_H +#include "openmc/bounding_box.h" #include "openmc/cell.h" namespace openmc { diff --git a/include/openmc/xml_interface.h b/include/openmc/xml_interface.h index bd6554c134a..f49613ecde1 100644 --- a/include/openmc/xml_interface.h +++ b/include/openmc/xml_interface.h @@ -50,6 +50,9 @@ xt::xarray get_node_xarray( return xt::adapt(v, shape); } +std::vector get_node_position_array( + pugi::xml_node node, const char* name, bool lowercase = false); + Position get_node_position( pugi::xml_node node, const char* name, bool lowercase = false); diff --git a/openmc/__init__.py b/openmc/__init__.py index 17b0abe2f69..bb972b4e6ad 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -1,3 +1,4 @@ +import importlib.metadata from openmc.arithmetic import * from openmc.bounding_box import * from openmc.cell import * @@ -14,6 +15,7 @@ from openmc.weight_windows import * from openmc.surface import * from openmc.universe import * +from openmc.dagmc import * from openmc.source import * from openmc.settings import * from openmc.lattice import * @@ -32,11 +34,12 @@ from openmc.search import * from openmc.polynomial import * from openmc.tracks import * -from . import examples from .config import * # Import a few names from the model module from openmc.model import Model +from . import examples + -__version__ = '0.14.1-dev' +__version__ = importlib.metadata.version("openmc") diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index 4520553dafc..821014e36df 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -10,10 +10,10 @@ # Acceptable tally arithmetic binary operations -_TALLY_ARITHMETIC_OPS = ['+', '-', '*', '/', '^'] +_TALLY_ARITHMETIC_OPS = {'+', '-', '*', '/', '^'} # Acceptable tally aggregation operations -_TALLY_AGGREGATE_OPS = ['sum', 'avg'] +_TALLY_AGGREGATE_OPS = {'sum', 'avg'} class CrossScore: diff --git a/openmc/bounding_box.py b/openmc/bounding_box.py index 6e58ca8ba02..f0dc06a4a04 100644 --- a/openmc/bounding_box.py +++ b/openmc/bounding_box.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Iterable +from collections.abc import Iterable import numpy as np @@ -42,7 +42,8 @@ def __init__(self, lower_left: Iterable[float], upper_right: Iterable[float]): def __repr__(self) -> str: return "BoundingBox(lower_left={}, upper_right={})".format( - tuple(self.lower_left), tuple(self.upper_right)) + tuple(float(x) for x in self.lower_left), + tuple(float(x) for x in self.upper_right)) def __getitem__(self, key) -> np.ndarray: return self._bounds[key] diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 42df3e5efbe..4fa205b14f7 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -1,12 +1,11 @@ import copy import os -import typing # required to prevent typing.Union namespace overwriting Union from collections.abc import Iterable import numpy as np # Type for arguments that accept file paths -PathLike = typing.Union[str, os.PathLike] +PathLike = str | os.PathLike def check_type(name, value, expected_type, expected_iter_type=None, *, none_ok=False): diff --git a/openmc/config.py b/openmc/config.py index b823d6b06b2..ab53ab61b5f 100644 --- a/openmc/config.py +++ b/openmc/config.py @@ -1,4 +1,5 @@ from collections.abc import MutableMapping +from contextlib import contextmanager import os from pathlib import Path import warnings @@ -11,7 +12,7 @@ class _Config(MutableMapping): def __init__(self, data=()): - self._mapping = {} + self._mapping = {'resolve_paths': True} self.update(data) def __getitem__(self, key): @@ -42,10 +43,12 @@ def __setitem__(self, key, value): # Reset photon source data since it relies on chain file _DECAY_PHOTON_ENERGY.clear() _DECAY_ENERGY.clear() + elif key == 'resolve_paths': + self._mapping[key] = value else: raise KeyError(f'Unrecognized config key: {key}. Acceptable keys ' - 'are "cross_sections", "mg_cross_sections" and ' - '"chain_file"') + 'are "cross_sections", "mg_cross_sections", ' + '"chain_file", and "resolve_paths".') def __iter__(self): return iter(self._mapping) @@ -61,6 +64,24 @@ def _set_path(self, key, value): if not p.exists(): warnings.warn(f"'{value}' does not exist.") + @contextmanager + def patch(self, key, value): + """Temporarily change a value in the configuration. + + Parameters + ---------- + key : str + Key to change + value : object + New value + """ + previous_value = self.get(key) + self[key] = value + yield + if previous_value is None: + del self[key] + else: + self[key] = previous_value def _default_config(): """Return default configuration""" diff --git a/openmc/dagmc.py b/openmc/dagmc.py new file mode 100644 index 00000000000..8ab0aaf69e7 --- /dev/null +++ b/openmc/dagmc.py @@ -0,0 +1,625 @@ +from collections.abc import Iterable, Mapping +from numbers import Integral + +import h5py +import lxml.etree as ET +import numpy as np +import warnings + +import openmc +import openmc.checkvalue as cv +from ._xml import get_text +from .checkvalue import check_type, check_value +from .surface import _BOUNDARY_TYPES +from .bounding_box import BoundingBox +from .utility_funcs import input_path + + +class DAGMCUniverse(openmc.UniverseBase): + """A reference to a DAGMC file to be used in the model. + + .. versionadded:: 0.13.0 + + Parameters + ---------- + filename : str + Path to the DAGMC file used to represent this universe. + universe_id : int, optional + Unique identifier of the universe. If not specified, an identifier will + automatically be assigned. + name : str, optional + Name of the universe. If not specified, the name is the empty string. + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and DAGMC (False) + auto_mat_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between OpenMC and UWUW materials (False) + material_overrides : dict, optional + A dictionary of material overrides. The keys are material name strings + and the values are Iterables of openmc.Material objects. If a material + name is found in the DAGMC file, the material will be replaced with the + openmc.Material object in the value. + + Attributes + ---------- + id : int + Unique identifier of the universe + name : str + Name of the universe + filename : str + Path to the DAGMC file used to represent this universe. + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and DAGMC (False) + auto_mat_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between OpenMC and UWUW materials (False) + bounding_box : openmc.BoundingBox + Lower-left and upper-right coordinates of an axis-aligned bounding box + of the universe. + + .. versionadded:: 0.13.1 + material_names : list of str + Return a sorted list of materials names that are contained within the + DAGMC h5m file. This is useful when naming openmc.Material() objects as + each material name present in the DAGMC h5m file must have a matching + openmc.Material() with the same name. + + .. versionadded:: 0.13.2 + n_cells : int + The number of cells in the DAGMC model. This is the number of cells at + runtime and accounts for the implicit complement whether or not is it + present in the DAGMC file. + + .. versionadded:: 0.13.2 + n_surfaces : int + The number of surfaces in the model. + + .. versionadded:: 0.13.2 + material_overrides : dict + A dictionary of material overrides. Keys are cell IDs; values are + iterables of :class:`openmc.Material` objects. The material assignment + of each DAGMC cell ID key will be replaced with the + :class:`~openmc.Material` object in the value. If the value contains + multiple :class:`~openmc.Material` objects, each Material in the list + will be assigned to the corresponding instance of the cell. + + .. versionadded:: 0.15.1 + """ + + def __init__(self, + filename: cv.PathLike, + universe_id=None, + name='', + auto_geom_ids=False, + auto_mat_ids=False, + material_overrides=None): + super().__init__(universe_id, name) + # Initialize class attributes + self.filename = filename + self.auto_geom_ids = auto_geom_ids + self.auto_mat_ids = auto_mat_ids + self._material_overrides = {} + if material_overrides is not None: + self.material_overrides = material_overrides + + def __repr__(self): + string = super().__repr__() + string += '{: <16}=\t{}\n'.format('\tGeom', 'DAGMC') + string += '{: <16}=\t{}\n'.format('\tFile', self.filename) + return string + + @property + def bounding_box(self): + with h5py.File(self.filename) as dagmc_file: + coords = dagmc_file['tstt']['nodes']['coordinates'][()] + lower_left_corner = coords.min(axis=0) + upper_right_corner = coords.max(axis=0) + return openmc.BoundingBox(lower_left_corner, upper_right_corner) + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, val: cv.PathLike): + cv.check_type('DAGMC filename', val, cv.PathLike) + self._filename = input_path(val) + + @property + def material_overrides(self): + return self._material_overrides + + @material_overrides.setter + def material_overrides(self, val): + cv.check_type('material overrides', val, Mapping) + for key, value in val.items(): + self.add_material_override(key, value) + + def replace_material_assignment(self, material_name: str, material: openmc.Material): + """Replace a material assignment within the DAGMC universe. + + Replace the material assignment of all cells filled with a material in + the DAGMC universe. The universe must be synchronized in an initialized + Model (see :meth:`~openmc.DAGMCUniverse.sync_dagmc_cells`) before + calling this method. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + material_name : str + Material name to replace + material : openmc.Material + Material to replace the material_name with + + """ + if material_name not in self.material_names: + raise ValueError( + f"No material with name '{material_name}' found in the DAGMC universe") + + if not self.cells: + raise RuntimeError("This DAGMC universe has not been synchronized " + "on an initialized Model.") + + for cell in self.cells.values(): + if cell.fill is None: + continue + if isinstance(cell.fill, openmc.Iterable): + cell.fill = list(map(lambda x: material if x.name == material_name else x, cell.fill)) + else: + cell.fill = material if cell.fill.name == material_name else cell.fill + + def add_material_override(self, key, overrides=None): + """Add a material override to the universe. + + .. versionadded:: 0.15 + + Parameters + ---------- + key : openmc.DAGMCCell or int + Cell object or ID of the Cell to override + value : openmc.Material or Iterable of openmc.Material + Material(s) to be applied to the Cell passed as the key + + """ + # Ensure that they key is a valid type + if not isinstance(key, (int, openmc.DAGMCCell)): + raise ValueError("Unrecognized key type. " + "Must be an integer or openmc.DAGMCCell object") + + # Ensure that overrides is an iterable of openmc.Material + overrides = overrides if isinstance(overrides, openmc.Iterable) else [overrides] + cv.check_iterable_type('material objects', overrides, (openmc.Material, type(None))) + + # if a DAGMCCell is passed, redcue the key to the ID of the cell + if isinstance(key, openmc.DAGMCCell): + key = key.id + + if key not in self.cells: + raise ValueError(f"Cell ID '{key}' not found in DAGMC universe") + + self._material_overrides[key] = overrides + + @property + def auto_geom_ids(self): + return self._auto_geom_ids + + @auto_geom_ids.setter + def auto_geom_ids(self, val): + cv.check_type('DAGMC automatic geometry ids', val, bool) + self._auto_geom_ids = val + + @property + def auto_mat_ids(self): + return self._auto_mat_ids + + @auto_mat_ids.setter + def auto_mat_ids(self, val): + cv.check_type('DAGMC automatic material ids', val, bool) + self._auto_mat_ids = val + + @property + def material_names(self): + dagmc_file_contents = h5py.File(self.filename) + material_tags_hex = dagmc_file_contents['/tstt/tags/NAME'].get( + 'values') + material_tags_ascii = [] + for tag in material_tags_hex: + candidate_tag = tag.tobytes().decode().replace('\x00', '') + # tags might be for temperature or reflective surfaces + if candidate_tag.startswith('mat:'): + # if name ends with _comp remove it, it is not parsed + if candidate_tag.endswith('_comp'): + candidate_tag = candidate_tag[:-5] + # removes first 4 characters as openmc.Material name should be + # set without the 'mat:' part of the tag + material_tags_ascii.append(candidate_tag[4:]) + + return sorted(set(material_tags_ascii)) + + def _n_geom_elements(self, geom_type): + """ + Helper function for retrieving the number geometric entities in a DAGMC + file + + Parameters + ---------- + geom_type : str + The type of geometric entity to count. One of {'Volume', 'Surface'}. Returns + the runtime number of voumes in the DAGMC model (includes implicit complement). + + Returns + ------- + int + Number of geometry elements of the specified type + """ + cv.check_value('geometry type', geom_type, ('volume', 'surface')) + + def decode_str_tag(tag_val): + return tag_val.tobytes().decode().replace('\x00', '') + + with h5py.File(self.filename) as dagmc_file: + category_data = dagmc_file['tstt/tags/CATEGORY/values'] + category_strs = map(decode_str_tag, category_data) + n = sum([v == geom_type.capitalize() for v in category_strs]) + + # check for presence of an implicit complement in the file and + # increment the number of cells if it doesn't exist + if geom_type == 'volume': + name_data = dagmc_file['tstt/tags/NAME/values'] + name_strs = map(decode_str_tag, name_data) + if not sum(['impl_complement' in n for n in name_strs]): + n += 1 + return n + + @property + def n_cells(self): + return self._n_geom_elements('volume') + + @property + def n_surfaces(self): + return self._n_geom_elements('surface') + + def create_xml_subelement(self, xml_element, memo=None): + if memo is None: + memo = set() + + if self in memo: + return + + memo.add(self) + + # Ensure that the material overrides are up-to-date + for cell in self.cells.values(): + if cell.fill is None: + continue + self.add_material_override(cell, cell.fill) + + # Set xml element values + dagmc_element = ET.Element('dagmc_universe') + dagmc_element.set('id', str(self.id)) + + if self.auto_geom_ids: + dagmc_element.set('auto_geom_ids', 'true') + if self.auto_mat_ids: + dagmc_element.set('auto_mat_ids', 'true') + dagmc_element.set('filename', str(self.filename)) + if self._material_overrides: + mat_element = ET.Element('material_overrides') + for key in self._material_overrides: + cell_overrides = ET.Element('cell_override') + cell_overrides.set("id", str(key)) + material_element = ET.Element('material_ids') + material_element.text = ' '.join( + str(t.id) for t in self._material_overrides[key]) + cell_overrides.append(material_element) + mat_element.append(cell_overrides) + dagmc_element.append(mat_element) + xml_element.append(dagmc_element) + + def bounding_region( + self, + bounded_type: str = 'box', + boundary_type: str = 'vacuum', + starting_id: int = 10000, + padding_distance: float = 0. + ): + """Creates a either a spherical or box shaped bounding region around + the DAGMC geometry. + + .. versionadded:: 0.13.1 + + Parameters + ---------- + bounded_type : str + The type of bounding surface(s) to use when constructing the region. + Options include a single spherical surface (sphere) or a rectangle + made from six planes (box). + boundary_type : str + Boundary condition that defines the behavior for particles hitting + the surface. Defaults to vacuum boundary condition. Passed into the + surface construction. + starting_id : int + Starting ID of the surface(s) used in the region. For bounded_type + 'box', the next 5 IDs will also be used. Defaults to 10000 to reduce + the chance of an overlap of surface IDs with the DAGMC geometry. + padding_distance : float + Distance between the bounding region surfaces and the minimal + bounding box. Allows for the region to be larger than the DAGMC + geometry. + + Returns + ------- + openmc.Region + Region instance + """ + + check_type('boundary type', boundary_type, str) + check_value('boundary type', boundary_type, _BOUNDARY_TYPES) + check_type('starting surface id', starting_id, Integral) + check_type('bounded type', bounded_type, str) + check_value('bounded type', bounded_type, ('box', 'sphere')) + + bbox = self.bounding_box.expand(padding_distance, True) + + if bounded_type == 'sphere': + radius = np.linalg.norm(bbox.upper_right - bbox.center) + bounding_surface = openmc.Sphere( + surface_id=starting_id, + x0=bbox.center[0], + y0=bbox.center[1], + z0=bbox.center[2], + boundary_type=boundary_type, + r=radius, + ) + + return -bounding_surface + + if bounded_type == 'box': + # defines plane surfaces for all six faces of the bounding box + lower_x = openmc.XPlane(bbox[0][0], surface_id=starting_id) + upper_x = openmc.XPlane(bbox[1][0], surface_id=starting_id+1) + lower_y = openmc.YPlane(bbox[0][1], surface_id=starting_id+2) + upper_y = openmc.YPlane(bbox[1][1], surface_id=starting_id+3) + lower_z = openmc.ZPlane(bbox[0][2], surface_id=starting_id+4) + upper_z = openmc.ZPlane(bbox[1][2], surface_id=starting_id+5) + + region = +lower_x & -upper_x & +lower_y & -upper_y & +lower_z & -upper_z + + for surface in region.get_surfaces().values(): + surface.boundary_type = boundary_type + + return region + + def bounded_universe(self, bounding_cell_id=10000, **kwargs): + """Returns an openmc.Universe filled with this DAGMCUniverse and bounded + with a cell. Defaults to a box cell with a vacuum surface however this + can be changed using the kwargs which are passed directly to + DAGMCUniverse.bounding_region(). + + Parameters + ---------- + bounding_cell_id : int + The cell ID number to use for the bounding cell, defaults to 10000 to reduce + the chance of overlapping ID numbers with the DAGMC geometry. + + Returns + ------- + openmc.Universe + Universe instance + """ + bounding_cell = openmc.Cell( + fill=self, cell_id=bounding_cell_id, region=self.bounding_region(**kwargs)) + return openmc.Universe(cells=[bounding_cell]) + + @classmethod + def from_hdf5(cls, group): + """Create DAGMC universe from HDF5 group + + Parameters + ---------- + group : h5py.Group + Group in HDF5 file + + Returns + ------- + openmc.DAGMCUniverse + DAGMCUniverse instance + + """ + id = int(group.name.split('/')[-1].lstrip('universe ')) + fname = group['filename'][()].decode() + name = group['name'][()].decode() if 'name' in group else None + + out = cls(fname, universe_id=id, name=name) + + out.auto_geom_ids = bool(group.attrs['auto_geom_ids']) + out.auto_mat_ids = bool(group.attrs['auto_mat_ids']) + + return out + + @classmethod + def from_xml_element(cls, elem, mats = None): + """Generate DAGMC universe from XML element + + Parameters + ---------- + elem : lxml.etree._Element + `` element + mats : dict + Dictionary mapping material ID strings to :class:`openmc.Material` + instances (defined in :meth:`openmc.Geometry.from_xml`) + + Returns + ------- + openmc.DAGMCUniverse + DAGMCUniverse instance + + """ + id = int(get_text(elem, 'id')) + fname = get_text(elem, 'filename') + + out = cls(fname, universe_id=id) + + name = get_text(elem, 'name') + if name is not None: + out.name = name + + out.auto_geom_ids = bool(elem.get('auto_geom_ids')) + out.auto_mat_ids = bool(elem.get('auto_mat_ids')) + + el_mat_override = elem.find('material_overrides') + if el_mat_override is not None: + if mats is None: + raise ValueError("Material overrides found in DAGMC universe " + "but no materials were provided to populate " + "the mapping.") + out._material_overrides = {} + for elem in el_mat_override.findall('cell_override'): + cell_id = int(get_text(elem, 'id')) + mat_ids = get_text(elem, 'material_ids').split(' ') + mat_objs = [mats[mat_id] for mat_id in mat_ids] + out._material_overrides[cell_id] = mat_objs + + return out + + def _partial_deepcopy(self): + """Clone all of the openmc.DAGMCUniverse object's attributes except for + its cells, as they are copied within the clone function. This should + only to be used within the openmc.UniverseBase.clone() context. + """ + clone = openmc.DAGMCUniverse(name=self.name, filename=self.filename) + clone.volume = self.volume + clone.auto_geom_ids = self.auto_geom_ids + clone.auto_mat_ids = self.auto_mat_ids + return clone + + def add_cell(self, cell): + """Add a cell to the universe. + + Parameters + ---------- + cell : openmc.DAGMCCell + Cell to add + + """ + if not isinstance(cell, openmc.DAGMCCell): + msg = f'Unable to add a DAGMCCell to DAGMCUniverse ' \ + f'ID="{self._id}" since "{cell}" is not a DAGMCCell' + raise TypeError(msg) + + cell_id = cell.id + + if cell_id not in self._cells: + self._cells[cell_id] = cell + + def remove_cell(self, cell): + """Remove a cell from the universe. + + Parameters + ---------- + cell : openmc.Cell + Cell to remove + + """ + + if not isinstance(cell, openmc.DAGMCCell): + msg = f'Unable to remove a Cell from Universe ID="{self._id}" ' \ + f'since "{cell}" is not a Cell' + raise TypeError(msg) + + # If the Cell is in the Universe's list of Cells, delete it + self._cells.pop(cell.id, None) + + def sync_dagmc_cells(self, mats: Iterable[openmc.Material]): + """Synchronize DAGMC cell information between Python and C API + + .. versionadded:: 0.15.1 + + Parameters + ---------- + mats : iterable of openmc.Material + Iterable of materials to assign to the DAGMC cells + + """ + import openmc.lib + if not openmc.lib.is_initialized: + raise RuntimeError("This universe must be part of an openmc.Model " + "initialized via Model.init_lib before calling " + "this method.") + + dagmc_cell_ids = openmc.lib.dagmc.dagmc_universe_cell_ids(self.id) + if len(dagmc_cell_ids) != self.n_cells: + raise ValueError( + f"Number of cells in DAGMC universe {self.id} does not match " + f"the number of cells in the Python universe." + ) + + mats_per_id = {mat.id: mat for mat in mats} + for dag_cell_id in dagmc_cell_ids: + dag_cell = openmc.lib.cells[dag_cell_id] + if isinstance(dag_cell.fill, Iterable): + fill = [mats_per_id[mat.id] for mat in dag_cell.fill if mat] + else: + fill = mats_per_id[dag_cell.fill.id] if dag_cell.fill else None + self.add_cell(openmc.DAGMCCell(cell_id=dag_cell_id, fill=fill)) + + +class DAGMCCell(openmc.Cell): + """A cell class for DAGMC-based geometries. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + cell_id : int or None, optional + Unique identifier for the cell. If None, an identifier will be + automatically assigned. + name : str, optional + Name of the cell. + fill : openmc.Material or None, optional + Material filling the cell. If None, the cell is filled with vacuum. + + Attributes + ---------- + DAG_parent_universe : int + The parent universe of the cell. + + """ + def __init__(self, cell_id=None, name='', fill=None): + super().__init__(cell_id, name, fill, None) + + @property + def DAG_parent_universe(self): + """Get the parent universe of the cell.""" + return self._parent_universe + + @DAG_parent_universe.setter + def DAG_parent_universe(self, universe): + """Set the parent universe of the cell.""" + self._parent_universe = universe.id + + def bounding_box(self): + return BoundingBox.infinite() + + def get_all_cells(self, memo=None): + return {} + + def get_all_universes(self, memo=None): + return {} + + def clone(self, clone_materials=True, clone_regions=True, memo=None): + warnings.warn("clone is not available for cells in a DAGMC universe") + return self + + def plot(self, *args, **kwargs): + raise TypeError("plot is not available for DAGMC cells.") + + def create_xml_subelement(self, xml_element, memo=None): + raise TypeError("create_xml_subelement is not available for DAGMC cells.") + + @classmethod + def from_xml_element(cls, elem, surfaces, materials, get_universe): + raise TypeError("from_xml_element is not available for DAGMC cells.") diff --git a/openmc/data/_endf.pyx b/openmc/data/_endf.pyx deleted file mode 100644 index 991ee015b29..00000000000 --- a/openmc/data/_endf.pyx +++ /dev/null @@ -1,8 +0,0 @@ -# cython: c_string_type=str, c_string_encoding=ascii - -cdef extern from "endf.c": - double cfloat_endf(const char* buffer, int n) - -def float_endf(s): - cdef const char* c_string = s - return cfloat_endf(c_string, len(s)) diff --git a/openmc/data/data.py b/openmc/data/data.py index 7caf31e26ee..408adf2e429 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -5,7 +5,6 @@ from pathlib import Path from math import sqrt, log from warnings import warn -from typing import Dict # Isotopic abundances from Meija J, Coplen T B, et al, "Isotopic compositions # of the elements 2013 (IUPAC Technical Report)", Pure. Appl. Chem. 88 (3), @@ -283,13 +282,13 @@ NEUTRON_MASS = 1.00866491595 # Used in atomic_mass function as a cache -_ATOMIC_MASS: Dict[str, float] = {} +_ATOMIC_MASS: dict[str, float] = {} # Regex for GNDS nuclide names (used in zam function) _GNDS_NAME_RE = re.compile(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)') # Used in half_life function as a cache -_HALF_LIFE: Dict[str, float] = {} +_HALF_LIFE: dict[str, float] = {} _LOG_TWO = log(2.0) def atomic_mass(isotope): @@ -550,7 +549,18 @@ def gnds_name(Z, A, m=0): return f'{ATOMIC_SYMBOL[Z]}{A}' -def isotopes(element): + +def _get_element_symbol(element: str) -> str: + if len(element) > 2: + symbol = ELEMENT_SYMBOL.get(element.lower()) + if symbol is None: + raise ValueError(f'Element name "{element}" not recognized') + return symbol + else: + return element + + +def isotopes(element: str) -> list[tuple[str, float]]: """Return naturally occurring isotopes and their abundances .. versionadded:: 0.12.1 @@ -571,12 +581,7 @@ def isotopes(element): If the element name is not recognized """ - # Convert name to symbol if needed - if len(element) > 2: - symbol = ELEMENT_SYMBOL.get(element.lower()) - if symbol is None: - raise ValueError(f'Element name "{element}" not recognised') - element = symbol + element = _get_element_symbol(element) # Get the nuclides present in nature result = [] diff --git a/openmc/data/decay.py b/openmc/data/decay.py index be3dab77abf..66acb2212d8 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -2,7 +2,6 @@ from io import StringIO from math import log import re -from typing import Optional from warnings import warn import numpy as np @@ -579,7 +578,7 @@ def sources(self): _DECAY_PHOTON_ENERGY = {} -def decay_photon_energy(nuclide: str) -> Optional[Univariate]: +def decay_photon_energy(nuclide: str) -> Univariate | None: """Get photon energy distribution resulting from the decay of a nuclide This function relies on data stored in a depletion chain. Before calling it diff --git a/openmc/data/effective_dose/dose.py b/openmc/data/effective_dose/dose.py index ae981ee7dc8..c7f458d1c6c 100644 --- a/openmc/data/effective_dose/dose.py +++ b/openmc/data/effective_dose/dose.py @@ -2,40 +2,61 @@ import numpy as np -_FILES = ( - ('electron', 'electrons.txt'), - ('helium', 'helium_ions.txt'), - ('mu-', 'negative_muons.txt'), - ('pi-', 'negative_pions.txt'), - ('neutron', 'neutrons.txt'), - ('photon', 'photons.txt'), - ('photon kerma', 'photons_kerma.txt'), - ('mu+', 'positive_muons.txt'), - ('pi+', 'positive_pions.txt'), - ('positron', 'positrons.txt'), - ('proton', 'protons.txt') -) - -_DOSE_ICRP116 = {} - - -def _load_dose_icrp116(): - """Load effective dose tables from text files""" - for particle, filename in _FILES: - path = Path(__file__).parent / filename - data = np.loadtxt(path, skiprows=3, encoding='utf-8') - data[:, 0] *= 1e6 # Change energies to eV - _DOSE_ICRP116[particle] = data - - -def dose_coefficients(particle, geometry='AP'): - """Return effective dose conversion coefficients from ICRP-116 - - This function provides fluence (and air kerma) to effective dose conversion - coefficients for various types of external exposures based on values in - `ICRP Publication 116 `_. - Corrected values found in a correigendum are used rather than the values in - theoriginal report. +import openmc.checkvalue as cv + +_FILES = { + ('icrp74', 'neutron'): Path('icrp74') / 'neutrons.txt', + ('icrp74', 'photon'): Path('icrp74') / 'photons.txt', + ('icrp116', 'electron'): Path('icrp116') / 'electrons.txt', + ('icrp116', 'helium'): Path('icrp116') / 'helium_ions.txt', + ('icrp116', 'mu-'): Path('icrp116') / 'negative_muons.txt', + ('icrp116', 'pi-'): Path('icrp116') / 'negative_pions.txt', + ('icrp116', 'neutron'): Path('icrp116') / 'neutrons.txt', + ('icrp116', 'photon'): Path('icrp116') / 'photons.txt', + ('icrp116', 'photon kerma'): Path('icrp116') / 'photons_kerma.txt', + ('icrp116', 'mu+'): Path('icrp116') / 'positive_muons.txt', + ('icrp116', 'pi+'): Path('icrp116') / 'positive_pions.txt', + ('icrp116', 'positron'): Path('icrp116') / 'positrons.txt', + ('icrp116', 'proton'): Path('icrp116') / 'protons.txt', +} + +_DOSE_TABLES = {} + + +def _load_dose_icrp(data_source: str, particle: str): + """Load effective dose tables from text files. + + Parameters + ---------- + data_source : {'icrp74', 'icrp116'} + The dose conversion data source to use + particle : {'neutron', 'photon', 'photon kerma', 'electron', 'positron'} + Incident particle + + """ + path = Path(__file__).parent / _FILES[data_source, particle] + data = np.loadtxt(path, skiprows=3, encoding='utf-8') + data[:, 0] *= 1e6 # Change energies to eV + _DOSE_TABLES[data_source, particle] = data + + +def dose_coefficients(particle, geometry='AP', data_source='icrp116'): + """Return effective dose conversion coefficients. + + This function provides fluence (and air kerma) to effective or ambient dose + (H*(10)) conversion coefficients for various types of external exposures + based on values in ICRP publications. Corrected values found in a + corrigendum are used rather than the values in the original report. + Available libraries include `ICRP Publication 74 + ` and `ICRP Publication 116 + `. + + For ICRP 74 data, the photon effective dose per fluence is determined by + multiplying the air kerma per fluence values (Table A.1) by the effective + dose per air kerma (Table A.17). The neutron effective dose per fluence is + found in Table A.41. For ICRP 116 data, the photon effective dose per + fluence is found in Table A.1 and the neutron effective dose per fluence is + found in Table A.5. Parameters ---------- @@ -44,6 +65,8 @@ def dose_coefficients(particle, geometry='AP'): geometry : {'AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO'} Irradiation geometry assumed. Refer to ICRP-116 (Section 3.2) for the meaning of the options here. + data_source : {'icrp74', 'icrp116'} + The data source for the effective dose conversion coefficients. Returns ------- @@ -54,19 +77,24 @@ def dose_coefficients(particle, geometry='AP'): 'photon kerma', the coefficients are given in [Sv/Gy]. """ - if not _DOSE_ICRP116: - _load_dose_icrp116() + + cv.check_value('geometry', geometry, {'AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO'}) + cv.check_value('data_source', data_source, {'icrp74', 'icrp116'}) + + if (data_source, particle) not in _FILES: + raise ValueError(f"{particle} has no dose data in data source {data_source}.") + elif (data_source, particle) not in _DOSE_TABLES: + _load_dose_icrp(data_source, particle) # Get all data for selected particle - data = _DOSE_ICRP116.get(particle) - if data is None: - raise ValueError(f"{particle} has no effective dose data") + data = _DOSE_TABLES[data_source, particle] # Determine index for selected geometry if particle in ('neutron', 'photon', 'proton', 'photon kerma'): - index = ('AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO').index(geometry) + columns = ('AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO') else: - index = ('AP', 'PA', 'ISO').index(geometry) + columns = ('AP', 'PA', 'ISO') + index = columns.index(geometry) # Pull out energy and dose from table energy = data[:, 0].copy() diff --git a/openmc/data/effective_dose/electrons.txt b/openmc/data/effective_dose/icrp116/electrons.txt similarity index 100% rename from openmc/data/effective_dose/electrons.txt rename to openmc/data/effective_dose/icrp116/electrons.txt diff --git a/openmc/data/effective_dose/helium_ions.txt b/openmc/data/effective_dose/icrp116/helium_ions.txt similarity index 100% rename from openmc/data/effective_dose/helium_ions.txt rename to openmc/data/effective_dose/icrp116/helium_ions.txt diff --git a/openmc/data/effective_dose/negative_muons.txt b/openmc/data/effective_dose/icrp116/negative_muons.txt similarity index 100% rename from openmc/data/effective_dose/negative_muons.txt rename to openmc/data/effective_dose/icrp116/negative_muons.txt diff --git a/openmc/data/effective_dose/negative_pions.txt b/openmc/data/effective_dose/icrp116/negative_pions.txt similarity index 100% rename from openmc/data/effective_dose/negative_pions.txt rename to openmc/data/effective_dose/icrp116/negative_pions.txt diff --git a/openmc/data/effective_dose/neutrons.txt b/openmc/data/effective_dose/icrp116/neutrons.txt similarity index 100% rename from openmc/data/effective_dose/neutrons.txt rename to openmc/data/effective_dose/icrp116/neutrons.txt diff --git a/openmc/data/effective_dose/photons.txt b/openmc/data/effective_dose/icrp116/photons.txt similarity index 100% rename from openmc/data/effective_dose/photons.txt rename to openmc/data/effective_dose/icrp116/photons.txt diff --git a/openmc/data/effective_dose/photons_kerma.txt b/openmc/data/effective_dose/icrp116/photons_kerma.txt similarity index 100% rename from openmc/data/effective_dose/photons_kerma.txt rename to openmc/data/effective_dose/icrp116/photons_kerma.txt diff --git a/openmc/data/effective_dose/positive_muons.txt b/openmc/data/effective_dose/icrp116/positive_muons.txt similarity index 100% rename from openmc/data/effective_dose/positive_muons.txt rename to openmc/data/effective_dose/icrp116/positive_muons.txt diff --git a/openmc/data/effective_dose/positive_pions.txt b/openmc/data/effective_dose/icrp116/positive_pions.txt similarity index 100% rename from openmc/data/effective_dose/positive_pions.txt rename to openmc/data/effective_dose/icrp116/positive_pions.txt diff --git a/openmc/data/effective_dose/positrons.txt b/openmc/data/effective_dose/icrp116/positrons.txt similarity index 100% rename from openmc/data/effective_dose/positrons.txt rename to openmc/data/effective_dose/icrp116/positrons.txt diff --git a/openmc/data/effective_dose/protons.txt b/openmc/data/effective_dose/icrp116/protons.txt similarity index 100% rename from openmc/data/effective_dose/protons.txt rename to openmc/data/effective_dose/icrp116/protons.txt diff --git a/openmc/data/effective_dose/icrp74/generate_photon_effective_dose.py b/openmc/data/effective_dose/icrp74/generate_photon_effective_dose.py new file mode 100644 index 00000000000..f8e970137e2 --- /dev/null +++ b/openmc/data/effective_dose/icrp74/generate_photon_effective_dose.py @@ -0,0 +1,69 @@ +from prettytable import PrettyTable +import numpy as np + +# Data from Table A.1 (air kerma per fluence) +energy_a1 = np.array([ + 0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.1, 0.15, 0.2, + 0.3, 0.4, 0.5, 0.6, 0.8, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0 +]) +air_kerma = np.array([7.43, 3.12, 1.68, 0.721, 0.429, 0.323, 0.289, 0.307, 0.371, 0.599, 0.856, 1.38, + 1.89, 2.38, 2.84, 3.69, 4.47, 6.14, 7.55, 9.96, 12.1, 14.1, 16.1, 20.1, 24.0]) + +# Data from Table A.17 (effective dose per air kerma) +energy_a17 = np.array([ + 0.01, 0.015, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.15, 0.2, 0.3, + 0.4, 0.5, 0.6, 0.8, 1.0, 2.0, 4.0, 6.0, 8.0, 10.0 +]) +dose_per_airkerma = { + 'AP': np.array([ + 0.00653, 0.0402, 0.122, 0.416, 0.788, 1.106, 1.308, 1.407, 1.433, 1.394, + 1.256, 1.173, 1.093, 1.056, 1.036, 1.024, 1.010, 1.003, 0.992, 0.993, + 0.993, 0.991, 0.990 + ]), + 'PA': np.array([ + 0.00248, 0.00586, 0.0181, 0.128, 0.370, 0.640, 0.846, 0.966, 1.019, + 1.030, 0.959, 0.915, 0.880, 0.871, 0.869, 0.870, 0.875, 0.880, 0.901, + 0.918, 0.924, 0.927, 0.929 + ]), + 'RLAT': np.array([ + 0.00172, 0.00549, 0.0151, 0.0782, 0.205, 0.345, 0.455, 0.522, 0.554, + 0.571, 0.551, 0.549, 0.557, 0.570, 0.585, 0.600, 0.628, 0.651, 0.728, + 0.796, 0.827, 0.846, 0.860 + ]), + 'LLAT': np.array([ + 0.00172, 0.00549, 0.0155, 0.0904, 0.241, 0.405, 0.528, 0.598, 0.628, + 0.641, 0.620, 0.615, 0.615, 0.623, 0.635, 0.648, 0.670, 0.691, 0.757, + 0.813, 0.836, 0.850, 0.859 + ]), + 'ROT': np.array([ + 0.00326, 0.0153, 0.0462, 0.191, 0.426, 0.661, 0.828, 0.924, 0.961, + 0.960, 0.892, 0.854, 0.824, 0.814, 0.812, 0.814, 0.821, 0.831, 0.871, + 0.909, 0.925, 0.934, 0.941 + ]), + 'ISO': np.array([ + 0.00271, 0.0123, 0.0362, 0.143, 0.326, 0.511, 0.642, 0.720, 0.749, + 0.748, 0.700, 0.679, 0.664, 0.667, 0.675, 0.684, 0.703, 0.719, 0.774, + 0.824, 0.846, 0.859, 0.868 + ]) +} + +# Interpolate air kerma onto energy grid for Table A.17 +air_kerma = np.interp(energy_a17, energy_a1, air_kerma) + +# Compute effective dose per fluence +dose_per_fluence = { + geometry: air_kerma * dose_per_airkerma + for geometry, dose_per_airkerma in dose_per_airkerma.items() +} + +# Create table +table = PrettyTable() +table.field_names = ['Energy (MeV)', 'AP', 'PA', 'LLAT', 'RLAT', 'ROT', 'ISO'] +table.float_format = '.7' +for i, energy in enumerate(energy_a17): + row = [energy] + for geometry in table.field_names[1:]: + row.append(dose_per_fluence[geometry][i]) + table.add_row(row) +print('Photons: Effective dose per fluence, in units of pSv cm², for monoenergetic particles incident in various geometries.\n') +print(table.get_string(border=False)) diff --git a/openmc/data/effective_dose/icrp74/neutrons.txt b/openmc/data/effective_dose/icrp74/neutrons.txt new file mode 100644 index 00000000000..14aab48bd19 --- /dev/null +++ b/openmc/data/effective_dose/icrp74/neutrons.txt @@ -0,0 +1,50 @@ +Neutrons: Effective dose per fluence, in units of pSv cm², for monoenergetic particles incident in various geometries. + +Energy (MeV) AP PA LLAT RLAT ROT ISO +1.00E-09 5.24 3.52 1.68 1.36 2.99 2.4 +1.00E-08 6.55 4.39 2.04 1.7 3.72 2.89 +2.50E-08 7.6 5.16 2.31 1.99 4.4 3.3 +1.00E-07 9.95 6.77 2.86 2.58 5.75 4.13 +2.00E-07 11.2 7.63 3.21 2.92 6.43 4.59 +5.00E-07 12.8 8.76 3.72 3.35 7.27 5.2 +1.00E-06 13.8 9.55 4.12 3.67 7.84 5.63 +2.00E-06 14.5 10.2 4.39 3.89 8.31 5.96 +5.00E-06 15 10.7 4.66 4.08 8.72 6.28 +1.00E-05 15.1 11 4.8 4.16 8.9 6.44 +2.00E-05 15.1 11.1 4.89 4.2 8.92 6.51 +5.00E-05 14.8 11.1 4.95 4.19 8.82 6.51 +1.00E-04 14.6 11 4.95 4.15 8.69 6.45 +2.00E-04 14.4 10.9 4.92 4.1 8.56 6.32 +5.00E-04 14.2 10.7 4.86 4.03 8.4 6.14 +1.00E-03 14.2 10.7 4.84 4 8.34 6.04 +2.00E-03 14.4 10.8 4.87 4 8.39 6.05 +5.00E-03 15.7 11.6 5.25 4.29 9.06 6.52 +1.00E-02 18.3 13.5 6.14 5.02 10.6 7.7 +2.00E-02 23.8 17.3 7.95 6.48 13.8 10.2 +3.00E-02 29 21 9.74 7.93 16.9 12.7 +5.00E-02 38.5 27.6 13.1 10.6 22.7 17.3 +7.00E-02 47.2 33.5 16.1 13.1 27.8 21.5 +1.00E-01 59.8 41.3 20.1 16.4 34.8 27.2 +1.50E-01 80.2 52.2 25.5 21.2 45.4 35.2 +2.00E-01 99 61.5 30.3 25.6 54.8 42.4 +3.00E-01 133 77.1 38.6 33.4 71.6 54.7 +5.00E-01 188 103 53.2 46.8 99.4 75 +7.00E-01 231 124 66.6 58.3 123 92.8 +9.00E-01 267 144 79.6 69.1 144 108 +1 282 154 86 74.5 154 116 +1.2 310 175 99.8 85.8 173 130 +2 383 247 153 129 234 178 +3 432 308 195 171 283 220 +4 458 345 224 198 315 250 +5 474 366 244 217 335 272 +6 483 380 261 232 348 282 +7 490 391 274 244 358 290 +8 494 399 285 253 366 297 +9 497 406 294 261 373 303 +1.00E+01 499 412 302 268 378 309 +1.20E+01 499 422 315 278 385 322 +1.40E+01 496 429 324 286 390 333 +1.50E+01 494 431 328 290 391 338 +1.60E+01 491 433 331 293 393 342 +1.80E+01 486 435 335 299 394 345 +2.00E+01 480 436 338 305 395 343 diff --git a/openmc/data/effective_dose/icrp74/photons.txt b/openmc/data/effective_dose/icrp74/photons.txt new file mode 100644 index 00000000000..1ce3d67e03e --- /dev/null +++ b/openmc/data/effective_dose/icrp74/photons.txt @@ -0,0 +1,26 @@ +Photons: Effective dose per fluence, in units of pSv cm², for monoenergetic particles incident in various geometries. + + Energy (MeV) AP PA LLAT RLAT ROT ISO + 0.0100000 0.0485179 0.0184264 0.0127796 0.0127796 0.0242218 0.0201353 + 0.0150000 0.1254240 0.0182832 0.0171288 0.0171288 0.0477360 0.0383760 + 0.0200000 0.2049600 0.0304080 0.0260400 0.0253680 0.0776160 0.0608160 + 0.0300000 0.2999360 0.0922880 0.0651784 0.0563822 0.1377110 0.1031030 + 0.0400000 0.3380520 0.1587300 0.1033890 0.0879450 0.1827540 0.1398540 + 0.0500000 0.3572380 0.2067200 0.1308150 0.1114350 0.2135030 0.1650530 + 0.0600000 0.3780120 0.2444940 0.1525920 0.1314950 0.2392920 0.1855380 + 0.0700000 0.4192860 0.2878680 0.1782040 0.1555560 0.2753520 0.2145600 + 0.0800000 0.4399310 0.3128330 0.1927960 0.1700780 0.2950270 0.2299430 + 0.1000000 0.5171740 0.3821300 0.2378110 0.2118410 0.3561600 0.2775080 + 0.1500000 0.7523440 0.5744410 0.3713800 0.3300490 0.5343080 0.4193000 + 0.2000000 1.0040880 0.7832400 0.5264400 0.4699440 0.7310240 0.5812240 + 0.3000000 1.5083400 1.2144000 0.8487000 0.7686600 1.1371200 0.9163200 + 0.4000000 1.9958400 1.6461900 1.1774700 1.0773000 1.5384600 1.2606300 + 0.5000000 2.4656800 2.0682200 1.5113000 1.3923000 1.9325600 1.6065000 + 0.6000000 2.9081600 2.4708000 1.8403200 1.7040000 2.3117600 1.9425600 + 0.8000000 3.7269000 3.2287500 2.4723000 2.3173200 3.0294900 2.5940700 + 1.0000000 4.4834100 3.9336000 3.0887700 2.9099700 3.7145700 3.2139300 + 2.0000000 7.4896000 6.8025500 5.7153500 5.4964000 6.5760500 5.8437000 + 4.0000000 12.0153000 11.1078000 9.8373000 9.6316000 10.9989000 9.9704000 + 6.0000000 15.9873000 14.8764000 13.4596000 13.3147000 14.8925000 13.6206000 + 8.0000000 19.9191000 18.6327000 17.0850000 17.0046000 18.7734000 17.2659000 + 10.0000000 23.7600000 22.2960000 20.6160000 20.6400000 22.5840000 20.8320000 diff --git a/openmc/data/endf.c b/openmc/data/endf.c deleted file mode 100644 index dd5eee54184..00000000000 --- a/openmc/data/endf.c +++ /dev/null @@ -1,57 +0,0 @@ -#include - -//! Convert string representation of a floating point number into a double -// -//! This function handles converting floating point numbers from an ENDF 11 -//! character field into a double, covering all the corner cases. Floating point -//! numbers are allowed to contain whitespace (which is ignored). Also, in -//! exponential notation, it allows the 'e' to be omitted. A field containing -//! only whitespace is to be interpreted as a zero. -// -//! \param buffer character input from an ENDF file -//! \param n Length of character input -//! \return Floating point number - -double cfloat_endf(const char* buffer, int n) -{ - char arr[13]; // 11 characters plus e and a null terminator - int j = 0; // current position in arr - int found_significand = 0; - int found_exponent = 0; - - // limit n to 11 characters - n = n > 11 ? 11 : n; - - int i; - for (i = 0; i < n; ++i) { - char c = buffer[i]; - - // Skip whitespace characters - if (c == ' ') continue; - - if (found_significand) { - if (!found_exponent) { - if (c == '+' || c == '-') { - // In the case that we encounter +/- and we haven't yet encountered - // e/E, we manually add it - arr[j++] = 'e'; - found_exponent = 1; - - } else if (c == 'e' || c == 'E' || c == 'd' || c == 'D') { - arr[j++] = 'e'; - found_exponent = 1; - continue; - } - } - } else if (c == '.' || (c >= '0' && c <= '9')) { - found_significand = 1; - } - - // Copy character - arr[j++] = c; - } - - // Done copying. Add null terminator and convert to double - arr[j] = '\0'; - return atof(arr); -} diff --git a/openmc/data/endf.py b/openmc/data/endf.py index 73299723a8b..63cd092ebb1 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -14,11 +14,7 @@ from .data import gnds_name from .function import Tabulated1D -try: - from ._endf import float_endf - _CYTHON = True -except ImportError: - _CYTHON = False +from endf.records import float_endf _LIBRARY = {0: 'ENDF/B', 1: 'ENDF/A', 2: 'JEFF', 3: 'EFF', @@ -91,10 +87,6 @@ def py_float_endf(s): return float(ENDF_FLOAT_RE.sub(r'\1e\2\3', s)) -if not _CYTHON: - float_endf = py_float_endf - - def int_endf(s): """Convert string of integer number in ENDF to int. diff --git a/openmc/data/function.py b/openmc/data/function.py index 23fd5e9d4fa..c5914f513d5 100644 --- a/openmc/data/function.py +++ b/openmc/data/function.py @@ -708,28 +708,6 @@ def __init__(self, resonances, background, mt): self.background = background self.mt = mt - def __call__(self, x): - # Get background cross section - xs = self.background(x) - - for r in self.resonances: - if not isinstance(r, openmc.data.resonance._RESOLVED): - continue - - if isinstance(x, Iterable): - # Determine which energies are within resolved resonance range - within = (r.energy_min <= x) & (x <= r.energy_max) - - # Get resonance cross sections and add to background - resonant_xs = r.reconstruct(x[within]) - xs[within] += resonant_xs[self.mt] - else: - if r.energy_min <= x <= r.energy_max: - resonant_xs = r.reconstruct(x) - xs += resonant_xs[self.mt] - - return xs - @property def background(self): return self._background diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 894be187178..95a3424ea4d 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -16,8 +16,7 @@ Evaluation, SUM_RULES, get_head_record, get_tab1_record, get_evaluations) from .fission_energy import FissionEnergyRelease from .function import Tabulated1D, Sum, ResonancesWithBackground -from .grid import linearize, thin -from .njoy import make_ace +from .njoy import make_ace, make_pendf from .product import Product from .reaction import Reaction, _get_photon_products_ace, FISSION_MTS from . import resonance as res @@ -286,7 +285,7 @@ def add_temperature_from_ace(self, ace_or_filename, metastable_scheme='nndc'): if strT in data.urr: self.urr[strT] = data.urr[strT] - def add_elastic_0K_from_endf(self, filename, overwrite=False): + def add_elastic_0K_from_endf(self, filename, overwrite=False, **kwargs): """Append 0K elastic scattering cross section from an ENDF file. Parameters @@ -297,6 +296,8 @@ def add_elastic_0K_from_endf(self, filename, overwrite=False): If existing 0 K data is present, this flag can be used to indicate that it should be overwritten. Otherwise, an exception will be thrown. + **kwargs + Keyword arguments passed to :func:`openmc.data.njoy.make_pendf` Raises ------ @@ -309,75 +310,22 @@ def add_elastic_0K_from_endf(self, filename, overwrite=False): if '0K' in self.energy and not overwrite: raise ValueError('0 K data already exists for this nuclide.') - data = type(self).from_endf(filename) - if data.resonances is not None: - x = [] - y = [] - for rr in data.resonances: - if isinstance(rr, res.RMatrixLimited): - raise TypeError('R-Matrix Limited not supported.') - elif isinstance(rr, res.Unresolved): - continue + with tempfile.TemporaryDirectory() as tmpdir: + # Set arguments for make_pendf + pendf_path = os.path.join(tmpdir, 'pendf') + kwargs.setdefault('output_dir', tmpdir) + kwargs.setdefault('pendf', pendf_path) - # Get energies/widths for resonances - e_peak = rr.parameters['energy'].values - if isinstance(rr, res.MultiLevelBreitWigner): - gamma = rr.parameters['totalWidth'].values - elif isinstance(rr, res.ReichMoore): - df = rr.parameters - gamma = (df['neutronWidth'] + - df['captureWidth'] + - abs(df['fissionWidthA']) + - abs(df['fissionWidthB'])).values - - # Determine peak energies and widths - e_min, e_max = rr.energy_min, rr.energy_max - in_range = (e_peak > e_min) & (e_peak < e_max) - e_peak = e_peak[in_range] - gamma = gamma[in_range] - - # Get midpoints between resonances (use min/max energy of - # resolved region as absolute lower/upper bound) - e_mid = np.concatenate( - ([e_min], (e_peak[1:] + e_peak[:-1])/2, [e_max])) - - # Add grid around each resonance that includes the peak +/- the - # width times each value in _RESONANCE_ENERGY_GRID. Values are - # constrained so that points around one resonance don't overlap - # with points around another. This algorithm is from Fudge - # (https://doi.org/10.1063/1.1945057). - energies = [] - for e, g, e_lower, e_upper in zip(e_peak, gamma, e_mid[:-1], - e_mid[1:]): - e_left = e - g*_RESONANCE_ENERGY_GRID - energies.append(e_left[e_left > e_lower][::-1]) - e_right = e + g*_RESONANCE_ENERGY_GRID[1:] - energies.append(e_right[e_right < e_upper]) - - # Concatenate all points - energies = np.concatenate(energies) - - # Create 1000 equal log-spaced energies over RRR, combine with - # resonance peaks and half-height energies - e_log = np.logspace(log10(e_min), log10(e_max), 1000) - energies = np.union1d(e_log, energies) - - # Linearize and thin cross section - xi, yi = linearize(energies, data[2].xs['0K']) - xi, yi = thin(xi, yi) - - # If there are multiple resolved resonance ranges (e.g. Pu239 in - # ENDF/B-VII.1), combine them - x = np.concatenate((x, xi)) - y = np.concatenate((y, yi)) - else: - energies = data[2].xs['0K'].x - x, y = linearize(energies, data[2].xs['0K']) - x, y = thin(x, y) + # Run NJOY to create a pointwise ENDF file + make_pendf(filename, **kwargs) - # Set 0K energy grid and elastic scattering cross section - self.energy['0K'] = x - self[2].xs['0K'] = Tabulated1D(x, y) + # Add 0K elastic scattering cross section + pendf = Evaluation(pendf_path) + file_obj = StringIO(pendf.section[3, 2]) + get_head_record(file_obj) + params, xs = get_tab1_record(file_obj) + self.energy['0K'] = xs.x + self[2].xs['0K'] = xs def get_reaction_components(self, mt): """Determine what reactions make up redundant reaction. diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index c1dcbab17f5..f5ef836f9ad 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -221,7 +221,7 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, shutil.move(tmpfilename, str(filename)) -def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): +def make_pendf(filename, pendf='pendf', **kwargs): """Generate pointwise ENDF file from an ENDF file Parameters @@ -230,10 +230,9 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): Path to ENDF file pendf : str, optional Path of pointwise ENDF file to write - error : float, optional - Fractional error tolerance for NJOY processing - stdout : bool - Whether to display NJOY standard output + **kwargs + Keyword arguments passed to :func:`openmc.data.njoy.make_ace`. All NJOY + module arguments other than pendf default to False. Raises ------ @@ -241,9 +240,9 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): If the NJOY process returns with a non-zero status """ - - make_ace(filename, pendf=pendf, error=error, broadr=False, - heatr=False, purr=False, acer=False, stdout=stdout) + for key in ('broadr', 'heatr', 'gaspr', 'purr', 'acer'): + kwargs.setdefault(key, False) + make_ace(filename, pendf=pendf, **kwargs) def make_ace(filename, temperatures=None, acer=True, xsdir=None, @@ -427,7 +426,7 @@ def make_ace(filename, temperatures=None, acer=True, xsdir=None, def make_ace_thermal(filename, filename_thermal, temperatures=None, - ace='ace', xsdir=None, output_dir=None, error=0.001, + ace=None, xsdir=None, output_dir=None, error=0.001, iwt=2, evaluation=None, evaluation_thermal=None, table_name=None, zaids=None, nmix=None, **kwargs): """Generate thermal scattering ACE file from ENDF files @@ -442,7 +441,7 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, Temperatures in Kelvin to produce data at. If omitted, data is produced at all temperatures given in the ENDF thermal scattering sublibrary. ace : str, optional - Path of ACE file to write + Path of ACE file to write. Default to ``"ace"``. xsdir : str, optional Path of xsdir file to write. Defaults to ``"xsdir"`` in the same directory as ``ace`` @@ -453,7 +452,7 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, error : float, optional Fractional error tolerance for NJOY processing iwt : int - `iwt` parameter used in NJOR/ACER card 9 + `iwt` parameter used in NJOY/ACER card 9 evaluation : openmc.data.endf.Evaluation, optional If the ENDF neutron sublibrary file contains multiple material evaluations, this argument indicates which evaluation to use. @@ -590,7 +589,7 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, commands += 'stop\n' run(commands, tapein, tapeout, **kwargs) - ace = output_dir / ace + ace = (output_dir / "ace") if ace is None else Path(ace) xsdir = (ace.parent / "xsdir") if xsdir is None else Path(xsdir) with ace.open('w') as ace_file, xsdir.open('w') as xsdir_file: # Concatenate ACE and xsdir files together diff --git a/openmc/data/photon.py b/openmc/data/photon.py index c9d51f06238..25ded24cbe3 100644 --- a/openmc/data/photon.py +++ b/openmc/data/photon.py @@ -28,10 +28,10 @@ R0 = CM_PER_ANGSTROM * PLANCK_C / (2.0 * pi * FINE_STRUCTURE * MASS_ELECTRON_EV) # Electron subshell labels -_SUBSHELLS = [None, 'K', 'L1', 'L2', 'L3', 'M1', 'M2', 'M3', 'M4', 'M5', +_SUBSHELLS = (None, 'K', 'L1', 'L2', 'L3', 'M1', 'M2', 'M3', 'M4', 'M5', 'N1', 'N2', 'N3', 'N4', 'N5', 'N6', 'N7', 'O1', 'O2', 'O3', 'O4', 'O5', 'O6', 'O7', 'O8', 'O9', 'P1', 'P2', 'P3', 'P4', - 'P5', 'P6', 'P7', 'P8', 'P9', 'P10', 'P11', 'Q1', 'Q2', 'Q3'] + 'P5', 'P6', 'P7', 'P8', 'P9', 'P10', 'P11', 'Q1', 'Q2', 'Q3') _REACTION_NAME = { 501: ('Total photon interaction', 'total'), @@ -124,7 +124,7 @@ class AtomicRelaxation(EqualityMixin): Dictionary indicating the number of electrons in a subshell when neutral (values) for given subshells (keys). The subshells should be given as strings, e.g., 'K', 'L1', 'L2', etc. - transitions : pandas.DataFrame + transitions : dict of str to pandas.DataFrame Dictionary indicating allowed transitions and their probabilities (values) for given subshells (keys). The subshells should be given as strings, e.g., 'K', 'L1', 'L2', etc. The transitions are represented as @@ -363,8 +363,9 @@ def from_hdf5(cls, group): df = pd.DataFrame(sub_group['transitions'][()], columns=columns) # Replace float indexes back to subshell strings - df[columns[:2]] = df[columns[:2]].replace( - np.arange(float(len(_SUBSHELLS))), _SUBSHELLS) + with pd.option_context('future.no_silent_downcasting', True): + df[columns[:2]] = df[columns[:2]].replace( + np.arange(float(len(_SUBSHELLS))), _SUBSHELLS) transitions[shell] = df return cls(binding_energy, num_electrons, transitions) @@ -387,8 +388,9 @@ def to_hdf5(self, group, shell): # Write transition data with replacements if shell in self.transitions: - df = self.transitions[shell].replace( - _SUBSHELLS, range(len(_SUBSHELLS))) + with pd.option_context('future.no_silent_downcasting', True): + df = self.transitions[shell].replace( + _SUBSHELLS, range(len(_SUBSHELLS))) group.create_dataset('transitions', data=df.values.astype(float)) diff --git a/openmc/data/reconstruct.pyx b/openmc/data/reconstruct.pyx deleted file mode 100644 index cd0bbc38b95..00000000000 --- a/openmc/data/reconstruct.pyx +++ /dev/null @@ -1,522 +0,0 @@ -from libc.stdlib cimport malloc, calloc, free -from libc.math cimport cos, sin, sqrt, atan, M_PI - -cimport numpy as np -import numpy as np -from numpy.linalg import inv -cimport cython - - -cdef extern from "complex.h": - double cabs(double complex) - double complex conj(double complex) - double creal(complex double) - double cimag(complex double) - double complex cexp(double complex) - -# Physical constants are from CODATA 2014 -cdef double NEUTRON_MASS_ENERGY = 939.5654133e6 # eV/c^2 -cdef double HBAR_C = 197.3269788e5 # eV-b^0.5 - - -@cython.cdivision(True) -def wave_number(double A, double E): - r"""Neutron wave number in center-of-mass system. - - ENDF-102 defines the neutron wave number in the center-of-mass system in - Equation D.10 as - - .. math:: - k = \frac{2m_n}{\hbar} \frac{A}{A + 1} \sqrt{|E|} - - Parameters - ---------- - A : double - Ratio of target mass to neutron mass - E : double - Energy in eV - - Returns - ------- - double - Neutron wave number in b^-0.5 - - """ - return A/(A + 1)*sqrt(2*NEUTRON_MASS_ENERGY*abs(E))/HBAR_C - -@cython.cdivision(True) -cdef double _wave_number(double A, double E): - return A/(A + 1)*sqrt(2*NEUTRON_MASS_ENERGY*abs(E))/HBAR_C - - -@cython.cdivision(True) -cdef double phaseshift(int l, double rho): - """Calculate hardsphere phase shift as given in ENDF-102, Equation D.13 - - Parameters - ---------- - l : int - Angular momentum quantum number - rho : float - Product of the wave number and the channel radius - - Returns - ------- - double - Hardsphere phase shift - - """ - if l == 0: - return rho - elif l == 1: - return rho - atan(rho) - elif l == 2: - return rho - atan(3*rho/(3 - rho**2)) - elif l == 3: - return rho - atan((15*rho - rho**3)/(15 - 6*rho**2)) - elif l == 4: - return rho - atan((105*rho - 10*rho**3)/(105 - 45*rho**2 + rho**4)) - - -@cython.cdivision(True) -def penetration_shift(int l, double rho): - r"""Calculate shift and penetration factors as given in ENDF-102, Equations D.11 - and D.12. - - Parameters - ---------- - l : int - Angular momentum quantum number - rho : float - Product of the wave number and the channel radius - - Returns - ------- - double - Penetration factor for given :math:`l` - double - Shift factor for given :math:`l` - - """ - cdef double den - - if l == 0: - return rho, 0. - elif l == 1: - den = 1 + rho**2 - return rho**3/den, -1/den - elif l == 2: - den = 9 + 3*rho**2 + rho**4 - return rho**5/den, -(18 + 3*rho**2)/den - elif l == 3: - den = 225 + 45*rho**2 + 6*rho**4 + rho**6 - return rho**7/den, -(675 + 90*rho**2 + 6*rho**4)/den - elif l == 4: - den = 11025 + 1575*rho**2 + 135*rho**4 + 10*rho**6 + rho**8 - return rho**9/den, -(44100 + 4725*rho**2 + 270*rho**4 + 10*rho**6)/den - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def reconstruct_mlbw(mlbw, double E): - """Evaluate cross section using MLBW data. - - Parameters - ---------- - mlbw : openmc.data.MultiLevelBreitWigner - Multi-level Breit-Wigner resonance parameters - E : double - Energy in eV at which to evaluate the cross section - - Returns - ------- - elastic : double - Elastic scattering cross section in barns - capture : double - Radiative capture cross section in barns - fission : double - Fission cross section in barns - - """ - cdef int i, nJ, ij, l, n_res, i_res - cdef double elastic, capture, fission - cdef double A, k, rho, rhohat, I - cdef double P, S, phi, cos2phi, sin2phi - cdef double Ex, Q, rhoc, rhochat, P_c, S_c - cdef double jmin, jmax, j, Dl - cdef double E_r, gt, gn, gg, gf, gx, P_r, S_r, P_rx - cdef double gnE, gtE, Eprime, x, f - cdef double *g - cdef double (*s)[2] - cdef double [:,:] params - - I = mlbw.target_spin - A = mlbw.atomic_weight_ratio - k = _wave_number(A, E) - - elastic = 0. - capture = 0. - fission = 0. - - for i, l in enumerate(mlbw._l_values): - params = mlbw._parameter_matrix[l] - - rho = k*mlbw.channel_radius[l](E) - rhohat = k*mlbw.scattering_radius[l](E) - P, S = penetration_shift(l, rho) - phi = phaseshift(l, rhohat) - cos2phi = cos(2*phi) - sin2phi = sin(2*phi) - - # Determine shift and penetration at modified energy - if mlbw._competitive[i]: - Ex = E + mlbw.q_value[l]*(A + 1)/A - rhoc = mlbw.channel_radius[l](Ex) - rhochat = mlbw.scattering_radius[l](Ex) - P_c, S_c = penetration_shift(l, rhoc) - if Ex < 0: - P_c = 0 - - # Determine range of total angular momentum values based on equation - # 41 in LA-UR-12-27079 - jmin = abs(abs(I - l) - 0.5) - jmax = I + l + 0.5 - nJ = int(jmax - jmin + 1) - - # Determine Dl factor using Equation 43 in LA-UR-12-27079 - Dl = 2*l + 1 - g = malloc(nJ*sizeof(double)) - for ij in range(nJ): - j = jmin + ij - g[ij] = (2*j + 1)/(4*I + 2) - Dl -= g[ij] - - s = calloc(2*nJ, sizeof(double)) - for i_res in range(params.shape[0]): - # Copy resonance parameters - E_r = params[i_res, 0] - j = params[i_res, 2] - ij = int(j - jmin) - gt = params[i_res, 3] - gn = params[i_res, 4] - gg = params[i_res, 5] - gf = params[i_res, 6] - gx = params[i_res, 7] - P_r = params[i_res, 8] - S_r = params[i_res, 9] - P_rx = params[i_res, 10] - - # Calculate neutron and total width at energy E - gnE = P*gn/P_r # ENDF-102, Equation D.7 - gtE = gnE + gg + gf - if gx > 0: - gtE += gx*P_c/P_rx - - Eprime = E_r + (S_r - S)/(2*P_r)*gn # ENDF-102, Equation D.9 - x = 2*(E - Eprime)/gtE # LA-UR-12-27079, Equation 26 - f = 2*gnE/(gtE*(1 + x*x)) # Common factor in Equation 40 - s[ij][0] += f # First sum in Equation 40 - s[ij][1] += f*x # Second sum in Equation 40 - capture += f*g[ij]*gg/gtE - if gf > 0: - fission += f*g[ij]*gf/gtE - - for ij in range(nJ): - # Add all but last term of LA-UR-12-27079, Equation 40 - elastic += g[ij]*((1 - cos2phi - s[ij][0])**2 + - (sin2phi + s[ij][1])**2) - - # Add final term with Dl from Equation 40 - elastic += 2*Dl*(1 - cos2phi) - - # Free memory - free(g) - free(s) - - capture *= 2*M_PI/(k*k) - fission *= 2*M_PI/(k*k) - elastic *= M_PI/(k*k) - - return (elastic, capture, fission) - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def reconstruct_slbw(slbw, double E): - """Evaluate cross section using SLBW data. - - Parameters - ---------- - slbw : openmc.data.SingleLevelBreitWigner - Single-level Breit-Wigner resonance parameters - E : double - Energy in eV at which to evaluate the cross section - - Returns - ------- - elastic : double - Elastic scattering cross section in barns - capture : double - Radiative capture cross section in barns - fission : double - Fission cross section in barns - - """ - cdef int i, l, i_res - cdef double elastic, capture, fission - cdef double A, k, rho, rhohat, I - cdef double P, S, phi, cos2phi, sin2phi, sinphi2 - cdef double Ex, rhoc, rhochat, P_c, S_c - cdef double E_r, J, gt, gn, gg, gf, gx, P_r, S_r, P_rx - cdef double gnE, gtE, Eprime, f - cdef double x, theta, psi, chi - cdef double [:,:] params - - I = slbw.target_spin - A = slbw.atomic_weight_ratio - k = _wave_number(A, E) - - elastic = 0. - capture = 0. - fission = 0. - - for i, l in enumerate(slbw._l_values): - params = slbw._parameter_matrix[l] - - rho = k*slbw.channel_radius[l](E) - rhohat = k*slbw.scattering_radius[l](E) - P, S = penetration_shift(l, rho) - phi = phaseshift(l, rhohat) - cos2phi = cos(2*phi) - sin2phi = sin(2*phi) - sinphi2 = sin(phi)**2 - - # Add potential scattering -- first term in ENDF-102, Equation D.2 - elastic += 4*M_PI/(k*k)*(2*l + 1)*sinphi2 - - # Determine shift and penetration at modified energy - if slbw._competitive[i]: - Ex = E + slbw.q_value[l]*(A + 1)/A - rhoc = k*slbw.channel_radius[l](Ex) - rhochat = k*slbw.scattering_radius[l](Ex) - P_c, S_c = penetration_shift(l, rhoc) - if Ex < 0: - P_c = 0 - - for i_res in range(params.shape[0]): - # Copy resonance parameters - E_r = params[i_res, 0] - J = params[i_res, 2] - gt = params[i_res, 3] - gn = params[i_res, 4] - gg = params[i_res, 5] - gf = params[i_res, 6] - gx = params[i_res, 7] - P_r = params[i_res, 8] - S_r = params[i_res, 9] - P_rx = params[i_res, 10] - - # Calculate neutron and total width at energy E - gnE = P*gn/P_r # Equation D.7 - gtE = gnE + gg + gf - if gx > 0: - gtE += gx*P_c/P_rx - - Eprime = E_r + (S_r - S)/(2*P_r)*gn # Equation D.9 - gJ = (2*J + 1)/(4*I + 2) # Mentioned in section D.1.1.4 - - # Calculate common factor for elastic, capture, and fission - # cross sections - f = M_PI/(k*k)*gJ*gnE/((E - Eprime)**2 + gtE**2/4) - - # Add contribution to elastic per Equation D.2 - elastic += f*(gnE*cos2phi - 2*(gg + gf)*sinphi2 - + 2*(E - Eprime)*sin2phi) - - # Add contribution to capture per Equation D.3 - capture += f*gg - - # Add contribution to fission per Equation D.6 - if gf > 0: - fission += f*gf - - return (elastic, capture, fission) - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def reconstruct_rm(rm, double E): - """Evaluate cross section using Reich-Moore data. - - Parameters - ---------- - rm : openmc.data.ReichMoore - Reich-Moore resonance parameters - E : double - Energy in eV at which to evaluate the cross section - - Returns - ------- - elastic : double - Elastic scattering cross section in barns - capture : double - Radiative capture cross section in barns - fission : double - Fission cross section in barns - - """ - cdef int i, l, m, n, i_res - cdef int i_s, num_s, i_J, num_J - cdef double elastic, capture, fission, total - cdef double A, k, rho, rhohat, I - cdef double P, S, phi - cdef double smin, smax, s, Jmin, Jmax, J, j - cdef double E_r, gn, gg, gfa, gfb, P_r - cdef double E_diff, abs_value, gJ - cdef double Kr, Ki, x - cdef double complex Ubar, U_, factor - cdef bint hasfission - cdef np.ndarray[double, ndim=2] one - cdef np.ndarray[double complex, ndim=2] K, Imat, U - cdef double [:,:] params - - # Get nuclear spin - I = rm.target_spin - - elastic = 0. - fission = 0. - total = 0. - A = rm.atomic_weight_ratio - k = _wave_number(A, E) - one = np.eye(3) - K = np.zeros((3,3), dtype=complex) - - for i, l in enumerate(rm._l_values): - # Check for l-dependent scattering radius - rho = k*rm.channel_radius[l](E) - rhohat = k*rm.scattering_radius[l](E) - - # Calculate shift and penetrability - P, S = penetration_shift(l, rho) - - # Calculate phase shift - phi = phaseshift(l, rhohat) - - # Calculate common factor on collision matrix terms (term outside curly - # braces in ENDF-102, Eq. D.27) - Ubar = cexp(-2j*phi) - - # The channel spin is the vector sum of the target spin, I, and the - # neutron spin, 1/2, so can take on values of |I - 1/2| < s < I + 1/2 - smin = abs(I - 0.5) - smax = I + 0.5 - num_s = int(smax - smin + 1) - - for i_s in range(num_s): - s = i_s + smin - - # Total angular momentum is the vector sum of l and s and can assume - # values between |l - s| < J < l + s - Jmin = abs(l - s) - Jmax = l + s - num_J = int(Jmax - Jmin + 1) - - for i_J in range(num_J): - J = i_J + Jmin - - # Initialize K matrix - for m in range(3): - for n in range(3): - K[m,n] = 0.0 - - hasfission = False - if (l, J) in rm._parameter_matrix: - params = rm._parameter_matrix[l, J] - - for i_res in range(params.shape[0]): - # Sometimes, the same (l, J) quantum numbers can occur - # for different values of the channel spin, s. In this - # case, the sign of the channel spin indicates which - # spin is to be used. If the spin is negative assume - # this resonance comes from the I - 1/2 channel and vice - # versa. - j = params[i_res, 2] - if l > 0: - if (j < 0 and s != smin) or (j > 0 and s != smax): - continue - - # Copy resonance parameters - E_r = params[i_res, 0] - gn = params[i_res, 3] - gg = params[i_res, 4] - gfa = params[i_res, 5] - gfb = params[i_res, 6] - P_r = params[i_res, 7] - - # Calculate neutron width at energy E - gn = sqrt(P*gn/P_r) - - # Calculate j/2 * inverse of denominator of K matrix terms - factor = 0.5j/(E_r - E - 0.5j*gg) - - # Upper triangular portion of K matrix -- see ENDF-102, - # Equation D.28 - K[0,0] = K[0,0] + gn*gn*factor - if gfa != 0.0 or gfb != 0.0: - # Negate fission widths if necessary - gfa = (-1 if gfa < 0 else 1)*sqrt(abs(gfa)) - gfb = (-1 if gfb < 0 else 1)*sqrt(abs(gfb)) - - K[0,1] = K[0,1] + gn*gfa*factor - K[0,2] = K[0,2] + gn*gfb*factor - K[1,1] = K[1,1] + gfa*gfa*factor - K[1,2] = K[1,2] + gfa*gfb*factor - K[2,2] = K[2,2] + gfb*gfb*factor - hasfission = True - - # Get collision matrix - gJ = (2*J + 1)/(4*I + 2) - if hasfission: - # Copy upper triangular portion of K to lower triangular - K[1,0] = K[0,1] - K[2,0] = K[0,2] - K[2,1] = K[1,2] - - Imat = inv(one - K) - U = Ubar*(2*Imat - one) # ENDF-102, Eq. D.27 - elastic += gJ*cabs(1 - U[0,0])**2 # ENDF-102, Eq. D.24 - total += 2*gJ*(1 - creal(U[0,0])) # ENDF-102, Eq. D.23 - - # Calculate fission from ENDF-102, Eq. D.26 - fission += 4*gJ*(cabs(Imat[1,0])**2 + cabs(Imat[2,0])**2) - else: - U_ = Ubar*(2/(1 - K[0,0]) - 1) - if abs(creal(K[0,0])) < 3e-4 and abs(phi) < 3e-4: - # If K and phi are both very small, the calculated cross - # sections can lose precision because the real part of U - # ends up very close to unity. To get around this, we - # use Euler's formula to express Ubar by real and - # imaginary parts, expand cos(2phi) = 1 - 2phi^2 + - # O(phi^4), and then simplify - Kr = creal(K[0,0]) - Ki = cimag(K[0,0]) - x = 2*(-Kr + (Kr*Kr + Ki*Ki)*(1 - phi*phi) + phi*phi - - sin(2*phi)*Ki)/((1 - Kr)*(1 - Kr) + Ki*Ki) - total += 2*gJ*x - elastic += gJ*(x*x + cimag(U_)**2) - else: - total += 2*gJ*(1 - creal(U_)) # ENDF-102, Eq. D.23 - elastic += gJ*cabs(1 - U_)**2 # ENDF-102, Eq. D.24 - - # Calculate capture as difference of other cross sections as per ENDF-102, - # Equation D.25 - capture = total - elastic - fission - - elastic *= M_PI/(k*k) - capture *= M_PI/(k*k) - fission *= M_PI/(k*k) - - return (elastic, capture, fission) diff --git a/openmc/data/thermal_angle_energy.py b/openmc/data/thermal_angle_energy.py index 0dd2a0b7a51..4789ebcc6f2 100644 --- a/openmc/data/thermal_angle_energy.py +++ b/openmc/data/thermal_angle_energy.py @@ -225,7 +225,7 @@ def from_hdf5(cls, group): """ energy_out = group['energy_out'][()] mu_out = group['mu_out'][()] - skewed = bool(group['skewed']) + skewed = bool(group['skewed'][()]) return cls(energy_out, mu_out, skewed) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5d9b8a2403b..784023f26ff 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -754,8 +754,9 @@ def _get_bos_data_from_restart(self, source_rate, bos_conc): rates = res.rates[0] k = ufloat(res.k[0, 0], res.k[0, 1]) - # Scale reaction rates by ratio of source rates - rates *= source_rate / res.source_rate + if res.source_rate != 0.0: + # Scale reaction rates by ratio of source rates + rates *= source_rate / res.source_rate return bos_conc, OperatorResult(k, rates) def _get_start_data(self): @@ -786,7 +787,7 @@ def integrate( path : PathLike Path to file to write. Defaults to 'depletion_results.h5'. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 """ with change_directory(self.operator.output_dir): n = self.operator.initial_condition() @@ -993,7 +994,7 @@ def integrate( path : PathLike Path to file to write. Defaults to 'depletion_results.h5'. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 """ with change_directory(self.operator.output_dir): n = self.operator.initial_condition() diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1d383498052..2e81250dcfb 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -386,24 +386,31 @@ def from_endf(cls, decay_files, fpy_files, neutron_files, if not data.nuclide['stable'] and data.half_life.nominal_value != 0.0: nuclide.half_life = data.half_life.nominal_value nuclide.decay_energy = data.decay_energy.nominal_value - sum_br = 0.0 - for i, mode in enumerate(data.modes): + branch_ratios = [] + branch_ids = [] + for mode in data.modes: type_ = ','.join(mode.modes) if mode.daughter in decay_data: target = mode.daughter else: print('missing {} {} {}'.format( - parent, ','.join(mode.modes), mode.daughter)) + parent, type_, mode.daughter)) target = replace_missing(mode.daughter, decay_data) - - # Write branching ratio, taking care to ensure sum is unity br = mode.branching_ratio.nominal_value - sum_br += br - if i == len(data.modes) - 1 and sum_br != 1.0: - br = 1.0 - sum(m.branching_ratio.nominal_value - for m in data.modes[:-1]) + branch_ratios.append(br) + branch_ids.append((type_, target)) + + if not math.isclose(sum(branch_ratios), 1.0): + max_br = max(branch_ratios) + max_index = branch_ratios.index(max_br) + + # Adjust maximum branching ratio so they sum to unity + new_br = max_br - sum(branch_ratios) + 1.0 + branch_ratios[max_index] = new_br + assert math.isclose(sum(branch_ratios), 1.0) - # Append decay mode + # Append decay modes + for br, (type_, target) in zip(branch_ratios, branch_ids): nuclide.add_decay_mode(type_, target, br) nuclide.sources = data.sources diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index acdcf467c57..17af935f4a2 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -10,7 +10,6 @@ import copy from warnings import warn -from typing import Optional import numpy as np from uncertainties import ufloat @@ -34,7 +33,7 @@ __all__ = ["CoupledOperator", "Operator", "OperatorResult"] -def _find_cross_sections(model: Optional[str] = None): +def _find_cross_sections(model: str | None = None): """Determine cross sections to use for depletion Parameters diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py index 250b94deb42..226682ffa53 100644 --- a/openmc/deplete/independent_operator.py +++ b/openmc/deplete/independent_operator.py @@ -8,7 +8,6 @@ from __future__ import annotations from collections.abc import Iterable import copy -from typing import List, Set import numpy as np from uncertainties import ufloat @@ -45,7 +44,7 @@ class IndependentOperator(OpenMCOperator): Parameters ---------- - materials : openmc.Materials + materials : iterable of openmc.Material Materials to deplete. fluxes : list of numpy.ndarray Flux in each group in [n-cm/src] for each domain @@ -128,8 +127,9 @@ def __init__(self, reduce_chain_level=None, fission_yield_opts=None): # Validate micro-xs parameters - check_type('materials', materials, openmc.Materials) + check_type('materials', materials, Iterable, openmc.Material) check_type('micros', micros, Iterable, MicroXS) + materials = openmc.Materials(materials) if not (len(fluxes) == len(micros) == len(materials)): msg = (f'The length of fluxes ({len(fluxes)}) should be equal to ' @@ -245,7 +245,6 @@ def _consolidate_nuclides_to_material(nuclides, nuc_units, volume): """Puts nuclide list into an openmc.Materials object. """ - openmc.reset_auto_ids() mat = openmc.Material() if nuc_units == 'atom/b-cm': for nuc, conc in nuclides.items(): @@ -279,7 +278,7 @@ def _load_previous_results(self): new_res = res_obj.distribute(self.local_mats, mat_indexes) self.prev_res.append(new_res) - def _get_nuclides_with_data(self, cross_sections: List[MicroXS]) -> Set[str]: + def _get_nuclides_with_data(self, cross_sections: list[MicroXS]) -> set[str]: """Finds nuclides with cross section data""" return set(cross_sections[0].nuclides) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 497a5284a1d..bbe9b2a4322 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -5,8 +5,8 @@ """ from __future__ import annotations +from collections.abc import Iterable, Sequence from tempfile import TemporaryDirectory -from typing import List, Tuple, Iterable, Optional, Union, Sequence import pandas as pd import numpy as np @@ -21,13 +21,14 @@ from .chain import Chain, REACTIONS from .coupled_operator import _find_cross_sections, _get_nuclides_with_data import openmc.lib +from openmc.mpi import comm _valid_rxns = list(REACTIONS) _valid_rxns.append('fission') _valid_rxns.append('damage-energy') -def _resolve_chain_file_path(chain_file: str): +def _resolve_chain_file_path(chain_file: str | None): if chain_file is None: chain_file = openmc.config.get('chain_file') if 'chain_file' not in openmc.config: @@ -41,12 +42,12 @@ def _resolve_chain_file_path(chain_file: str): def get_microxs_and_flux( model: openmc.Model, domains, - nuclides: Optional[Iterable[str]] = None, - reactions: Optional[Iterable[str]] = None, - energies: Optional[Union[Iterable[float], str]] = None, - chain_file: Optional[PathLike] = None, + nuclides: Iterable[str] | None = None, + reactions: Iterable[str] | None = None, + energies: Iterable[float] | str | None = None, + chain_file: PathLike | None = None, run_kwargs=None - ) -> Tuple[List[np.ndarray], List[MicroXS]]: + ) -> tuple[list[np.ndarray], list[MicroXS]]: """Generate a microscopic cross sections and flux from a Model .. versionadded:: 0.14.0 @@ -124,6 +125,15 @@ def get_microxs_and_flux( flux_tally.scores = ['flux'] model.tallies = openmc.Tallies([rr_tally, flux_tally]) + if openmc.lib.is_initialized: + openmc.lib.finalize() + + if comm.rank == 0: + model.export_to_model_xml() + comm.barrier() + # Reinitialize with tallies + openmc.lib.init(intracomm=comm) + # create temporary run with TemporaryDirectory() as temp_dir: if run_kwargs is None: @@ -133,12 +143,15 @@ def get_microxs_and_flux( run_kwargs.setdefault('cwd', temp_dir) statepoint_path = model.run(**run_kwargs) - with StatePoint(statepoint_path) as sp: - rr_tally = sp.tallies[rr_tally.id] - rr_tally._read_results() - flux_tally = sp.tallies[flux_tally.id] - flux_tally._read_results() + if comm.rank == 0: + with StatePoint(statepoint_path) as sp: + rr_tally = sp.tallies[rr_tally.id] + rr_tally._read_results() + flux_tally = sp.tallies[flux_tally.id] + flux_tally._read_results() + rr_tally = comm.bcast(rr_tally) + flux_tally = comm.bcast(flux_tally) # Get reaction rates and flux values reaction_rates = rr_tally.get_reshaped_data() # (domains, groups, nuclides, reactions) flux = flux_tally.get_reshaped_data() # (domains, groups, 1, 1) @@ -183,8 +196,10 @@ class MicroXS: :data:`openmc.deplete.chain.REACTIONS` """ - def __init__(self, data: np.ndarray, nuclides: List[str], reactions: List[str]): + def __init__(self, data: np.ndarray, nuclides: list[str], reactions: list[str]): # Validate inputs + if len(data.shape) != 3: + raise ValueError('Data array must be 3D.') if data.shape[:2] != (len(nuclides), len(reactions)): raise ValueError( f'Nuclides list of length {len(nuclides)} and ' @@ -205,12 +220,12 @@ def __init__(self, data: np.ndarray, nuclides: List[str], reactions: List[str]): @classmethod def from_multigroup_flux( cls, - energies: Union[Sequence[float], str], + energies: Sequence[float] | str, multigroup_flux: Sequence[float], - chain_file: Optional[PathLike] = None, + chain_file: PathLike | None = None, temperature: float = 293.6, - nuclides: Optional[Sequence[str]] = None, - reactions: Optional[Sequence[str]] = None, + nuclides: Sequence[str] | None = None, + reactions: Sequence[str] | None = None, **init_kwargs: dict, ) -> MicroXS: """Generated microscopic cross sections from a known flux. @@ -219,7 +234,7 @@ def from_multigroup_flux( sections available. MicroXS entry will be 0 if the nuclide cross section is not found. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -278,11 +293,11 @@ def from_multigroup_flux( mts = [REACTION_MT[name] for name in reactions] # Normalize multigroup flux - multigroup_flux = np.asarray(multigroup_flux) + multigroup_flux = np.array(multigroup_flux) multigroup_flux /= multigroup_flux.sum() - # Create 2D array for microscopic cross sections - microxs_arr = np.zeros((len(nuclides), len(mts))) + # Create 3D array for microscopic cross sections + microxs_arr = np.zeros((len(nuclides), len(mts), 1)) # Create a material with all nuclides mat_all_nucs = openmc.Material() @@ -314,7 +329,7 @@ def from_multigroup_flux( xs = lib_nuc.collapse_rate( mt, temperature, energies, multigroup_flux ) - microxs_arr[nuc_index, mt_index] = xs + microxs_arr[nuc_index, mt_index, 0] = xs return cls(microxs_arr, nuclides, reactions) @@ -371,4 +386,3 @@ def to_csv(self, *args, **kwargs): ) df = pd.DataFrame({'xs': self.data.flatten()}, index=multi_index) df.to_csv(*args, **kwargs) - diff --git a/openmc/deplete/openmc_operator.py b/openmc/deplete/openmc_operator.py index ad7a3924b7e..049e0dd37ee 100644 --- a/openmc/deplete/openmc_operator.py +++ b/openmc/deplete/openmc_operator.py @@ -7,7 +7,6 @@ from abc import abstractmethod from warnings import warn -from typing import List, Tuple, Dict import numpy as np @@ -142,12 +141,11 @@ def __init__( if diff_burnable_mats: self._differentiate_burnable_mats() + self.materials = self.model.materials # Determine which nuclides have cross section data # This nuclides variables contains every nuclides # for which there is an entry in the micro_xs parameter - openmc.reset_auto_ids() - self.nuclides_with_data = self._get_nuclides_with_data( self.cross_sections) @@ -184,7 +182,7 @@ def _differentiate_burnable_mats(self): """Assign distribmats for each burnable material""" pass - def _get_burnable_mats(self) -> Tuple[List[str], Dict[str, float], List[str]]: + def _get_burnable_mats(self) -> tuple[list[str], dict[str, float], list[str]]: """Determine depletable materials, volumes, and nuclides Returns @@ -210,7 +208,7 @@ def _get_burnable_mats(self) -> Tuple[List[str], Dict[str, float], List[str]]: if nuclide in self.nuclides_with_data or self._decay_nucs: model_nuclides.add(nuclide) else: - msg = (f"Nuclilde {nuclide} in material {mat.id} is not " + msg = (f"Nuclide {nuclide} in material {mat.id} is not " "present in the depletion chain and has no cross " "section data.") warn(msg) @@ -396,9 +394,6 @@ def _update_materials_and_nuclides(self, vec): self.number.set_density(vec) self._update_materials() - # Prevent OpenMC from complaining about re-creating tallies - openmc.reset_auto_ids() - # Update tally nuclides data in preparation for transport solve nuclides = self._get_reaction_nuclides() self._rate_helper.nuclides = nuclides diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index 4aaf829a9a5..714d9048b47 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -2,7 +2,6 @@ An ndarray to store reaction rates with string, integer, or slice indexing. """ -from typing import Dict import numpy as np @@ -53,9 +52,9 @@ class ReactionRates(np.ndarray): # the __array_finalize__ method (discussed here: # https://docs.scipy.org/doc/numpy/user/basics.subclassing.html) - index_mat: Dict[str, int] - index_nuc: Dict[str, int] - index_rx: Dict[str, int] + index_mat: dict[str, int] + index_nuc: dict[str, int] + index_rx: dict[str, int] def __new__(cls, local_mats, nuclides, reactions, from_results=False): # Create appropriately-sized zeroed-out ndarray diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 2e537a1b735..f897a88422c 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -1,8 +1,7 @@ import numbers import bisect import math -import typing # required to prevent typing.Union namespace overwriting Union -from typing import Iterable, Optional, Tuple, List +from collections.abc import Iterable from warnings import warn import h5py @@ -97,11 +96,11 @@ def from_hdf5(cls, filename: PathLike): def get_activity( self, - mat: typing.Union[Material, str], + mat: Material | str, units: str = "Bq/cm3", by_nuclide: bool = False, - volume: Optional[float] = None - ) -> Tuple[np.ndarray, typing.Union[np.ndarray, List[dict]]]: + volume: float | None = None + ) -> tuple[np.ndarray, np.ndarray | list[dict]]: """Get activity of material over time. .. versionadded:: 0.14.0 @@ -152,11 +151,11 @@ def get_activity( def get_atoms( self, - mat: typing.Union[Material, str], + mat: Material | str, nuc: str, nuc_units: str = "atoms", time_units: str = "s" - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """Get number of nuclides over time from a single material Parameters @@ -215,11 +214,11 @@ def get_atoms( def get_decay_heat( self, - mat: typing.Union[Material, str], + mat: Material | str, units: str = "W", by_nuclide: bool = False, - volume: Optional[float] = None - ) -> Tuple[np.ndarray, typing.Union[np.ndarray, List[dict]]]: + volume: float | None = None + ) -> tuple[np.ndarray, np.ndarray | list[dict]]: """Get decay heat of material over time. .. versionadded:: 0.14.0 @@ -242,7 +241,7 @@ def get_decay_heat( ------- times : numpy.ndarray Array of times in [s] - decay_heat : numpy.ndarray or List[dict] + decay_heat : numpy.ndarray or list[dict] Array of total decay heat values if by_nuclide = False (default) or list of dictionaries of decay heat values by nuclide if by_nuclide = True. @@ -270,11 +269,11 @@ def get_decay_heat( return times, decay_heat def get_mass(self, - mat: typing.Union[Material, str], + mat: Material | str, nuc: str, mass_units: str = "g", time_units: str = "s" - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """Get mass of nuclides over time from a single material .. versionadded:: 0.14.0 @@ -324,10 +323,10 @@ def get_mass(self, def get_reaction_rate( self, - mat: typing.Union[Material, str], + mat: Material | str, nuc: str, rx: str - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """Get reaction rate in a single material/nuclide over time Parameters @@ -364,7 +363,7 @@ def get_reaction_rate( return times, rates - def get_keff(self, time_units: str = 's') -> Tuple[np.ndarray, np.ndarray]: + def get_keff(self, time_units: str = 's') -> tuple[np.ndarray, np.ndarray]: """Evaluates the eigenvalue from a results list. .. versionadded:: 0.13.1 @@ -400,7 +399,7 @@ def get_keff(self, time_units: str = 's') -> Tuple[np.ndarray, np.ndarray]: times = _get_time_as(times, time_units) return times, eigenvalues - def get_eigenvalue(self, time_units: str = 's') -> Tuple[np.ndarray, np.ndarray]: + def get_eigenvalue(self, time_units: str = 's') -> tuple[np.ndarray, np.ndarray]: warn("The get_eigenvalue(...) function has been renamed get_keff and " "will be removed in a future version of OpenMC.", FutureWarning) return self.get_keff(time_units) @@ -526,7 +525,7 @@ def get_step_where( def export_to_materials( self, burnup_index: int, - nuc_with_data: Optional[Iterable[str]] = None, + nuc_with_data: Iterable[str] | None = None, path: PathLike = 'materials.xml' ) -> Materials: """Return openmc.Materials object based on results at a given step diff --git a/openmc/examples.py b/openmc/examples.py index fc94b8b528e..538b6ea4ac7 100644 --- a/openmc/examples.py +++ b/openmc/examples.py @@ -3,10 +3,10 @@ import numpy as np import openmc -import openmc.model -def pwr_pin_cell(): + +def pwr_pin_cell() -> openmc.Model: """Create a PWR pin-cell model. This model is a single fuel pin with 2.4 w/o enriched UO2 corresponding to a @@ -16,11 +16,11 @@ def pwr_pin_cell(): Returns ------- - model : openmc.model.Model + model : openmc.Model A PWR pin-cell model """ - model = openmc.model.Model() + model = openmc.Model() # Define materials. fuel = openmc.Material(name='UO2 (2.4%)') @@ -77,7 +77,8 @@ def pwr_pin_cell(): model.settings.inactive = 5 model.settings.particles = 100 model.settings.source = openmc.IndependentSource( - space=openmc.stats.Box([-pitch/2, -pitch/2, -1], [pitch/2, pitch/2, 1]), + space=openmc.stats.Box([-pitch/2, -pitch/2, -1], + [pitch/2, pitch/2, 1]), constraints={'fissionable': True} ) @@ -89,7 +90,7 @@ def pwr_pin_cell(): return model -def pwr_core(): +def pwr_core() -> openmc.Model: """Create a PWR full-core model. This model is the OECD/NEA Monte Carlo Performance benchmark which is a @@ -99,11 +100,11 @@ def pwr_core(): Returns ------- - model : openmc.model.Model + model : openmc.Model Full-core PWR model """ - model = openmc.model.Model() + model = openmc.Model() # Define materials. fuel = openmc.Material(1, name='UOX fuel') @@ -166,7 +167,8 @@ def pwr_core(): lower_rad_ref.add_nuclide('Cr52', 0.145407678031, 'wo') lower_rad_ref.add_s_alpha_beta('c_H_in_H2O') - upper_rad_ref = openmc.Material(7, name='Upper radial reflector / Top plate region') + upper_rad_ref = openmc.Material( + 7, name='Upper radial reflector / Top plate region') upper_rad_ref.set_density('g/cm3', 4.28) upper_rad_ref.add_nuclide('H1', 0.0086117, 'wo') upper_rad_ref.add_nuclide('O16', 0.0683369, 'wo') @@ -313,13 +315,15 @@ def pwr_core(): 11, 11, 11, 11, 11, 13, 13, 14, 14, 14]) # Define fuel lattices. - l100 = openmc.RectLattice(name='Fuel assembly (lower half)', lattice_id=100) + l100 = openmc.RectLattice( + name='Fuel assembly (lower half)', lattice_id=100) l100.lower_left = (-10.71, -10.71) l100.pitch = (1.26, 1.26) l100.universes = np.tile(fuel_cold, (17, 17)) l100.universes[tube_x, tube_y] = tube_cold - l101 = openmc.RectLattice(name='Fuel assembly (upper half)', lattice_id=101) + l101 = openmc.RectLattice( + name='Fuel assembly (upper half)', lattice_id=101) l101.lower_left = (-10.71, -10.71) l101.pitch = (1.26, 1.26) l101.universes = np.tile(fuel_hot, (17, 17)) @@ -405,10 +409,14 @@ def pwr_core(): c6 = openmc.Cell(cell_id=6, fill=top_fa, region=-s5 & +s36 & -s37) c7 = openmc.Cell(cell_id=7, fill=top_nozzle, region=-s5 & +s37 & -s38) c8 = openmc.Cell(cell_id=8, fill=upper_rad_ref, region=-s7 & +s38 & -s39) - c9 = openmc.Cell(cell_id=9, fill=bot_nozzle, region=+s6 & -s7 & +s32 & -s38) - c10 = openmc.Cell(cell_id=10, fill=rpv_steel, region=+s7 & -s8 & +s31 & -s39) - c11 = openmc.Cell(cell_id=11, fill=lower_rad_ref, region=+s5 & -s6 & +s32 & -s34) - c12 = openmc.Cell(cell_id=12, fill=upper_rad_ref, region=+s5 & -s6 & +s36 & -s38) + c9 = openmc.Cell(cell_id=9, fill=bot_nozzle, + region=+s6 & -s7 & +s32 & -s38) + c10 = openmc.Cell(cell_id=10, fill=rpv_steel, + region=+s7 & -s8 & +s31 & -s39) + c11 = openmc.Cell(cell_id=11, fill=lower_rad_ref, + region=+s5 & -s6 & +s32 & -s34) + c12 = openmc.Cell(cell_id=12, fill=upper_rad_ref, + region=+s5 & -s6 & +s36 & -s38) root.add_cells((c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12)) # Assign root universe to geometry @@ -430,7 +438,7 @@ def pwr_core(): return model -def pwr_assembly(): +def pwr_assembly() -> openmc.Model: """Create a PWR assembly model. This model is a reflected 17x17 fuel assembly from the the `BEAVRS @@ -440,12 +448,12 @@ def pwr_assembly(): Returns ------- - model : openmc.model.Model + model : openmc.Model A PWR assembly model """ - model = openmc.model.Model() + model = openmc.Model() # Define materials. fuel = openmc.Material(name='Fuel') @@ -489,10 +497,10 @@ def pwr_assembly(): fuel_pin_universe = openmc.Universe(name='Fuel Pin') fuel_cell = openmc.Cell(name='fuel', fill=fuel, region=-fuel_or) clad_cell = openmc.Cell(name='clad', fill=clad, region=+fuel_or & -clad_or) - hot_water_cell = openmc.Cell(name='hot water', fill=hot_water, region=+clad_or) + hot_water_cell = openmc.Cell( + name='hot water', fill=hot_water, region=+clad_or) fuel_pin_universe.add_cells([fuel_cell, clad_cell, hot_water_cell]) - # Create a control rod guide tube universe guide_tube_universe = openmc.Universe(name='Guide Tube') gt_inner_cell = openmc.Cell(name='guide tube inner water', fill=hot_water, @@ -530,7 +538,8 @@ def pwr_assembly(): model.settings.inactive = 5 model.settings.particles = 100 model.settings.source = openmc.IndependentSource( - space=openmc.stats.Box([-pitch/2, -pitch/2, -1], [pitch/2, pitch/2, 1]), + space=openmc.stats.Box([-pitch/2, -pitch/2, -1], + [pitch/2, pitch/2, 1]), constraints={'fissionable': True} ) @@ -544,7 +553,7 @@ def pwr_assembly(): return model -def slab_mg(num_regions=1, mat_names=None, mgxslib_name='2g.h5'): +def slab_mg(num_regions=1, mat_names=None, mgxslib_name='2g.h5') -> openmc.Model: """Create a 1D slab model. Parameters @@ -561,7 +570,7 @@ def slab_mg(num_regions=1, mat_names=None, mgxslib_name='2g.h5'): Returns ------- - model : openmc.model.Model + model : openmc.Model One-group, 1D slab model """ @@ -637,10 +646,482 @@ def slab_mg(num_regions=1, mat_names=None, mgxslib_name='2g.h5'): settings_file.output = {'summary': False} - model = openmc.model.Model() + model = openmc.Model() model.geometry = geometry_file model.materials = materials_file model.settings = settings_file model.xs_data = macros return model + + +def random_ray_lattice() -> openmc.Model: + """Create a 2x2 PWR pincell asymmetrical lattic eexample. + + This model is a 2x2 reflective lattice of fuel pins with one of the lattice + locations having just moderator instead of a fuel pin. It uses 7 group + cross section data. + + Returns + ------- + model : openmc.Model + A PWR 2x2 lattice model + + """ + model = openmc.Model() + + ########################################################################### + # Create MGXS data for the problem + + # Instantiate the energy group data + group_edges = [1e-5, 0.0635, 10.0, 1.0e2, 1.0e3, 0.5e6, 1.0e6, 20.0e6] + groups = openmc.mgxs.EnergyGroups(group_edges) + + # Instantiate the 7-group (C5G7) cross section data + uo2_xsdata = openmc.XSdata('UO2', groups) + uo2_xsdata.order = 0 + uo2_xsdata.set_total( + [0.1779492, 0.3298048, 0.4803882, 0.5543674, 0.3118013, 0.3951678, + 0.5644058]) + uo2_xsdata.set_absorption([8.0248e-03, 3.7174e-03, 2.6769e-02, 9.6236e-02, + 3.0020e-02, 1.1126e-01, 2.8278e-01]) + scatter_matrix = np.array( + [[[0.1275370, 0.0423780, 0.0000094, 0.0000000, 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.3244560, 0.0016314, 0.0000000, + 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.4509400, 0.0026792, + 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.4525650, + 0.0055664, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.0001253, + 0.2714010, 0.0102550, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, + 0.0012968, 0.2658020, 0.0168090], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0085458, 0.2730800]]]) + scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) + uo2_xsdata.set_scatter_matrix(scatter_matrix) + uo2_xsdata.set_fission([7.21206e-03, 8.19301e-04, 6.45320e-03, + 1.85648e-02, 1.78084e-02, 8.30348e-02, + 2.16004e-01]) + uo2_xsdata.set_nu_fission([2.005998e-02, 2.027303e-03, 1.570599e-02, + 4.518301e-02, 4.334208e-02, 2.020901e-01, + 5.257105e-01]) + uo2_xsdata.set_chi([5.8791e-01, 4.1176e-01, 3.3906e-04, 1.1761e-07, 0.0000e+00, + 0.0000e+00, 0.0000e+00]) + + h2o_xsdata = openmc.XSdata('LWTR', groups) + h2o_xsdata.order = 0 + h2o_xsdata.set_total([0.15920605, 0.412969593, 0.59030986, 0.58435, + 0.718, 1.2544497, 2.650379]) + h2o_xsdata.set_absorption([6.0105e-04, 1.5793e-05, 3.3716e-04, + 1.9406e-03, 5.7416e-03, 1.5001e-02, + 3.7239e-02]) + scatter_matrix = np.array( + [[[0.0444777, 0.1134000, 0.0007235, 0.0000037, 0.0000001, 0.0000000, 0.0000000], + [0.0000000, 0.2823340, 0.1299400, 0.0006234, + 0.0000480, 0.0000074, 0.0000010], + [0.0000000, 0.0000000, 0.3452560, 0.2245700, + 0.0169990, 0.0026443, 0.0005034], + [0.0000000, 0.0000000, 0.0000000, 0.0910284, + 0.4155100, 0.0637320, 0.0121390], + [0.0000000, 0.0000000, 0.0000000, 0.0000714, + 0.1391380, 0.5118200, 0.0612290], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, + 0.0022157, 0.6999130, 0.5373200], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.1324400, 2.4807000]]]) + scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) + h2o_xsdata.set_scatter_matrix(scatter_matrix) + + mg_cross_sections = openmc.MGXSLibrary(groups) + mg_cross_sections.add_xsdatas([uo2_xsdata, h2o_xsdata]) + mg_cross_sections.export_to_hdf5('mgxs.h5') + + ########################################################################### + # Create materials for the problem + + # Instantiate some Materials and register the appropriate macroscopic data + uo2 = openmc.Material(name='UO2 fuel') + uo2.set_density('macro', 1.0) + uo2.add_macroscopic('UO2') + + water = openmc.Material(name='Water') + water.set_density('macro', 1.0) + water.add_macroscopic('LWTR') + + # Instantiate a Materials collection and export to XML + materials = openmc.Materials([uo2, water]) + materials.cross_sections = "mgxs.h5" + + ########################################################################### + # Define problem geometry + + ######################################## + # Define an unbounded pincell universe + + pitch = 1.26 + + # Create a surface for the fuel outer radius + fuel_or = openmc.ZCylinder(r=0.54, name='Fuel OR') + inner_ring_a = openmc.ZCylinder(r=0.33, name='inner ring a') + inner_ring_b = openmc.ZCylinder(r=0.45, name='inner ring b') + outer_ring_a = openmc.ZCylinder(r=0.60, name='outer ring a') + outer_ring_b = openmc.ZCylinder(r=0.69, name='outer ring b') + + # Instantiate Cells + fuel_a = openmc.Cell(fill=uo2, region=-inner_ring_a, name='fuel inner a') + fuel_b = openmc.Cell(fill=uo2, region=+inner_ring_a & - + inner_ring_b, name='fuel inner b') + fuel_c = openmc.Cell(fill=uo2, region=+inner_ring_b & - + fuel_or, name='fuel inner c') + moderator_a = openmc.Cell( + fill=water, region=+fuel_or & -outer_ring_a, name='moderator inner a') + moderator_b = openmc.Cell( + fill=water, region=+outer_ring_a & -outer_ring_b, name='moderator outer b') + moderator_c = openmc.Cell( + fill=water, region=+outer_ring_b, name='moderator outer c') + + # Create pincell universe + pincell_base = openmc.Universe() + + # Register Cells with Universe + pincell_base.add_cells( + [fuel_a, fuel_b, fuel_c, moderator_a, moderator_b, moderator_c]) + + # Create planes for azimuthal sectors + azimuthal_planes = [] + for i in range(8): + angle = 2 * i * openmc.pi / 8 + normal_vector = (-openmc.sin(angle), openmc.cos(angle), 0) + azimuthal_planes.append(openmc.Plane( + a=normal_vector[0], b=normal_vector[1], c=normal_vector[2], d=0)) + + # Create a cell for each azimuthal sector + azimuthal_cells = [] + for i in range(8): + azimuthal_cell = openmc.Cell(name=f'azimuthal_cell_{i}') + azimuthal_cell.fill = pincell_base + azimuthal_cell.region = +azimuthal_planes[i] & -azimuthal_planes[(i+1) % 8] + azimuthal_cells.append(azimuthal_cell) + + # Create a geometry with the azimuthal universes + pincell = openmc.Universe(cells=azimuthal_cells, name='pincell') + + ######################################## + # Define a moderator lattice universe + + moderator_infinite = openmc.Cell(fill=water, name='moderator infinite') + mu = openmc.Universe() + mu.add_cells([moderator_infinite]) + + lattice = openmc.RectLattice() + lattice.lower_left = [-pitch/2.0, -pitch/2.0] + lattice.pitch = [pitch/10.0, pitch/10.0] + lattice.universes = np.full((10, 10), mu) + + mod_lattice_cell = openmc.Cell(fill=lattice) + + mod_lattice_uni = openmc.Universe() + + mod_lattice_uni.add_cells([mod_lattice_cell]) + + ######################################## + # Define 2x2 outer lattice + lattice2x2 = openmc.RectLattice() + lattice2x2.lower_left = (-pitch, -pitch) + lattice2x2.pitch = (pitch, pitch) + lattice2x2.universes = [ + [pincell, pincell], + [pincell, mod_lattice_uni] + ] + + ######################################## + # Define cell containing lattice and other stuff + box = openmc.model.RectangularPrism( + pitch*2, pitch*2, boundary_type='reflective') + + assembly = openmc.Cell(fill=lattice2x2, region=-box, name='assembly') + + # Create a geometry with the top-level cell + geometry = openmc.Geometry([assembly]) + + ########################################################################### + # Define problem settings + + # Instantiate a Settings object, set all runtime parameters, and export to XML + settings = openmc.Settings() + settings.energy_mode = "multi-group" + settings.batches = 10 + settings.inactive = 5 + settings.particles = 100 + + # Create an initial uniform spatial source distribution over fissionable zones + lower_left = (-pitch, -pitch, -1) + upper_right = (pitch, pitch, 1) + uniform_dist = openmc.stats.Box(lower_left, upper_right) + rr_source = openmc.IndependentSource(space=uniform_dist) + + settings.random_ray['distance_active'] = 100.0 + settings.random_ray['distance_inactive'] = 20.0 + settings.random_ray['ray_source'] = rr_source + settings.random_ray['volume_normalized_flux_tallies'] = True + + ########################################################################### + # Define tallies + + # Create a mesh that will be used for tallying + mesh = openmc.RegularMesh() + mesh.dimension = (2, 2) + mesh.lower_left = (-pitch, -pitch) + mesh.upper_right = (pitch, pitch) + + # Create a mesh filter that can be used in a tally + mesh_filter = openmc.MeshFilter(mesh) + + # Create an energy group filter as well + group_edges = [1e-5, 0.0635, 10.0, 1.0e2, 1.0e3, 0.5e6, 1.0e6, 20.0e6] + energy_filter = openmc.EnergyFilter(group_edges) + + # Now use the mesh filter in a tally and indicate what scores are desired + tally = openmc.Tally(name="Mesh tally") + tally.filters = [mesh_filter, energy_filter] + tally.scores = ['flux', 'fission', 'nu-fission'] + tally.estimator = 'analog' + + # Instantiate a Tallies collection and export to XML + tallies = openmc.Tallies([tally]) + + ########################################################################### + # Exporting to OpenMC model + ########################################################################### + + model.geometry = geometry + model.materials = materials + model.settings = settings + model.tallies = tallies + return model + + +def random_ray_three_region_cube() -> openmc.Model: + """Create a three region cube model. + + This is a simple monoenergetic problem of a cube with three concentric cubic + regions. The innermost region is near void (with Sigma_t around 10^-5) and + contains an external isotropic source term, the middle region is void (with + Sigma_t around 10^-4), and the outer region of the cube is an absorber + (with Sigma_t around 1). + + Returns + ------- + model : openmc.Model + A three region cube model + + """ + + model = openmc.Model() + + ########################################################################### + # Helper function creates a 3 region cube with different fills in each region + def fill_cube(N, n_1, n_2, fill_1, fill_2, fill_3): + cube = [[[0 for _ in range(N)] for _ in range(N)] for _ in range(N)] + for i in range(N): + for j in range(N): + for k in range(N): + if i < n_1 and j >= (N-n_1) and k < n_1: + cube[i][j][k] = fill_1 + elif i < n_2 and j >= (N-n_2) and k < n_2: + cube[i][j][k] = fill_2 + else: + cube[i][j][k] = fill_3 + return cube + + ########################################################################### + # Create multigroup data + + # Instantiate the energy group data + ebins = [1e-5, 20.0e6] + groups = openmc.mgxs.EnergyGroups(group_edges=ebins) + + void_sigma_a = 4.0e-6 + void_sigma_s = 3.0e-4 + void_mat_data = openmc.XSdata('void', groups) + void_mat_data.order = 0 + void_mat_data.set_total([void_sigma_a + void_sigma_s]) + void_mat_data.set_absorption([void_sigma_a]) + void_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[void_sigma_s]]]), 0, 3)) + + absorber_sigma_a = 0.75 + absorber_sigma_s = 0.25 + absorber_mat_data = openmc.XSdata('absorber', groups) + absorber_mat_data.order = 0 + absorber_mat_data.set_total([absorber_sigma_a + absorber_sigma_s]) + absorber_mat_data.set_absorption([absorber_sigma_a]) + absorber_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[absorber_sigma_s]]]), 0, 3)) + + multiplier = 0.1 + source_sigma_a = void_sigma_a * multiplier + source_sigma_s = void_sigma_s * multiplier + source_mat_data = openmc.XSdata('source', groups) + source_mat_data.order = 0 + source_mat_data.set_total([source_sigma_a + source_sigma_s]) + source_mat_data.set_absorption([source_sigma_a]) + source_mat_data.set_scatter_matrix( + np.rollaxis(np.array([[[source_sigma_s]]]), 0, 3)) + + mg_cross_sections_file = openmc.MGXSLibrary(groups) + mg_cross_sections_file.add_xsdatas( + [source_mat_data, void_mat_data, absorber_mat_data]) + mg_cross_sections_file.export_to_hdf5() + + ########################################################################### + # Create materials for the problem + + # Instantiate some Macroscopic Data + source_data = openmc.Macroscopic('source') + void_data = openmc.Macroscopic('void') + absorber_data = openmc.Macroscopic('absorber') + + # Instantiate some Materials and register the appropriate Macroscopic objects + source_mat = openmc.Material(name='source') + source_mat.set_density('macro', 1.0) + source_mat.add_macroscopic(source_data) + + void_mat = openmc.Material(name='void') + void_mat.set_density('macro', 1.0) + void_mat.add_macroscopic(void_data) + + absorber_mat = openmc.Material(name='absorber') + absorber_mat.set_density('macro', 1.0) + absorber_mat.add_macroscopic(absorber_data) + + # Instantiate a Materials collection and export to XML + materials_file = openmc.Materials([source_mat, void_mat, absorber_mat]) + materials_file.cross_sections = "mgxs.h5" + + ########################################################################### + # Define problem geometry + + source_cell = openmc.Cell(fill=source_mat, name='infinite source region') + void_cell = openmc.Cell(fill=void_mat, name='infinite void region') + absorber_cell = openmc.Cell( + fill=absorber_mat, name='infinite absorber region') + + source_universe = openmc.Universe(name='source universe') + source_universe.add_cells([source_cell]) + + void_universe = openmc.Universe() + void_universe.add_cells([void_cell]) + + absorber_universe = openmc.Universe() + absorber_universe.add_cells([absorber_cell]) + + absorber_width = 30.0 + n_base = 6 + + # This variable can be increased above 1 to refine the FSR mesh resolution further + refinement_level = 2 + + n = n_base * refinement_level + pitch = absorber_width / n + + pattern = fill_cube(n, 1*refinement_level, 5*refinement_level, + source_universe, void_universe, absorber_universe) + + lattice = openmc.RectLattice() + lattice.lower_left = [0.0, 0.0, 0.0] + lattice.pitch = [pitch, pitch, pitch] + lattice.universes = pattern + + lattice_cell = openmc.Cell(fill=lattice) + + lattice_uni = openmc.Universe() + lattice_uni.add_cells([lattice_cell]) + + x_low = openmc.XPlane(x0=0.0, boundary_type='reflective') + x_high = openmc.XPlane(x0=absorber_width, boundary_type='vacuum') + + y_low = openmc.YPlane(y0=0.0, boundary_type='reflective') + y_high = openmc.YPlane(y0=absorber_width, boundary_type='vacuum') + + z_low = openmc.ZPlane(z0=0.0, boundary_type='reflective') + z_high = openmc.ZPlane(z0=absorber_width, boundary_type='vacuum') + + full_domain = openmc.Cell(fill=lattice_uni, region=+x_low & - + x_high & +y_low & -y_high & +z_low & -z_high, name='full domain') + + root = openmc.Universe(name='root universe') + root.add_cell(full_domain) + + # Create a geometry with the two cells and export to XML + geometry = openmc.Geometry(root) + + ########################################################################### + # Define problem settings + + # Instantiate a Settings object, set all runtime parameters, and export to XML + settings = openmc.Settings() + settings.energy_mode = "multi-group" + settings.inactive = 5 + settings.batches = 10 + settings.particles = 90 + settings.run_mode = 'fixed source' + + # Create an initial uniform spatial source for ray integration + lower_left_ray = [0.0, 0.0, 0.0] + upper_right_ray = [absorber_width, absorber_width, absorber_width] + uniform_dist_ray = openmc.stats.Box( + lower_left_ray, upper_right_ray, only_fissionable=False) + rr_source = openmc.IndependentSource(space=uniform_dist_ray) + + settings.random_ray['distance_active'] = 500.0 + settings.random_ray['distance_inactive'] = 100.0 + settings.random_ray['ray_source'] = rr_source + settings.random_ray['volume_normalized_flux_tallies'] = True + + # Create the neutron source in the bottom right of the moderator + # Good - fast group appears largest (besides most thermal) + strengths = [1.0] + midpoints = [100.0] + energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths) + + source = openmc.IndependentSource(energy=energy_distribution, constraints={ + 'domains': [source_universe]}, strength=3.14) + + settings.source = [source] + + ########################################################################### + # Define tallies + + estimator = 'tracklength' + + absorber_filter = openmc.MaterialFilter(absorber_mat) + absorber_tally = openmc.Tally(name="Absorber Tally") + absorber_tally.filters = [absorber_filter] + absorber_tally.scores = ['flux'] + absorber_tally.estimator = estimator + + void_filter = openmc.MaterialFilter(void_mat) + void_tally = openmc.Tally(name="Void Tally") + void_tally.filters = [void_filter] + void_tally.scores = ['flux'] + void_tally.estimator = estimator + + source_filter = openmc.MaterialFilter(source_mat) + source_tally = openmc.Tally(name="Source Tally") + source_tally.filters = [source_filter] + source_tally.scores = ['flux'] + source_tally.estimator = estimator + + # Instantiate a Tallies collection and export to XML + tallies = openmc.Tallies([source_tally, void_tally, absorber_tally]) + + ########################################################################### + # Assmble Model + + model.geometry = geometry + model.materials = materials_file + model.settings = settings + model.tallies = tallies + + return model diff --git a/openmc/filter.py b/openmc/filter.py index e005140eeb3..b5926af5ad9 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1,6 +1,6 @@ from __future__ import annotations from abc import ABCMeta -from collections.abc import Iterable +from collections.abc import Iterable, Sequence import hashlib from itertools import product from numbers import Real, Integral @@ -22,7 +22,7 @@ _FILTER_TYPES = ( 'universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', - 'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', + 'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre', 'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance', 'collision', 'time' @@ -736,7 +736,7 @@ class ParticleFilter(Filter): Parameters ---------- - bins : str, or iterable of str + bins : str, or sequence of str The particles to tally represented as strings ('neutron', 'photon', 'electron', 'positron'). filter_id : int @@ -744,7 +744,7 @@ class ParticleFilter(Filter): Attributes ---------- - bins : iterable of str + bins : sequence of str The particles to tally id : int Unique identifier for the filter @@ -764,8 +764,8 @@ def __eq__(self, other): @Filter.bins.setter def bins(self, bins): + cv.check_type('bins', bins, Sequence, str) bins = np.atleast_1d(bins) - cv.check_iterable_type('filter bins', bins, str) for edge in bins: cv.check_value('filter bin', edge, _PARTICLES) self._bins = bins @@ -1850,6 +1850,44 @@ def check_bins(self, bins): cv.check_less_than('filter value', x, 1., equality=True) +class MuSurfaceFilter(MuFilter): + """Bins tally events based on the angle of surface crossing. + + This filter bins events based on the cosine of the angle between the + direction of the particle and the normal to the surface at the point it + crosses. Only used in conjunction with a SurfaceFilter and current score. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + values : int or Iterable of Real + A grid of surface crossing angles which the events will be divided into. + Values represent the cosine of the angle between the direction of the + particle and the normal to the surface at the point it crosses. If an + iterable is given, the values will be used explicitly as grid points. If + a single int is given, the range [-1, 1] will be divided equally into + that number of bins. + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + surface crossing angle cosines for a single bin. + id : int + Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of cosines of surface + crossing angle for a single filter + num_bins : Integral + The number of filter bins + + """ + # Note: inherits implementation from MuFilter + + class PolarFilter(RealFilter): """Bins tally events based on the incident particle's direction. diff --git a/openmc/geometry.py b/openmc/geometry.py index db927c46ec1..28e1e48eca2 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -1,6 +1,5 @@ from __future__ import annotations import os -import typing from collections import defaultdict from copy import deepcopy from collections.abc import Iterable @@ -8,8 +7,6 @@ import warnings import lxml.etree as ET -import numpy as np - import openmc import openmc._xml as xml from .checkvalue import check_type, check_less_than, check_greater_than, PathLike @@ -41,7 +38,7 @@ class Geometry: def __init__( self, - root: typing.Optional[openmc.UniverseBase] = None, + root: openmc.UniverseBase | Iterable[openmc.Cell] | None = None, merge_surfaces: bool = False, surface_precision: int = 10 ): @@ -68,7 +65,7 @@ def root_universe(self, root_universe): self._root_universe = root_universe @property - def bounding_box(self) -> np.ndarray: + def bounding_box(self) -> openmc.BoundingBox: return self.root_universe.bounding_box @property @@ -220,7 +217,7 @@ def get_universe(univ_id): # Add any DAGMC universes for e in elem.findall('dagmc_universe'): - dag_univ = openmc.DAGMCUniverse.from_xml_element(e) + dag_univ = openmc.DAGMCUniverse.from_xml_element(e, mats) universes[dag_univ.id] = dag_univ # Dictionary that maps each universe to a list of cells/lattices that @@ -267,7 +264,7 @@ def get_universe(univ_id): def from_xml( cls, path: PathLike = 'geometry.xml', - materials: typing.Optional[typing.Union[PathLike, 'openmc.Materials']] = 'materials.xml' + materials: PathLike | 'openmc.Materials' | None = 'materials.xml' ) -> Geometry: """Generate geometry from XML file @@ -316,7 +313,7 @@ def find(self, point) -> list: """ return self.root_universe.find(point) - def get_instances(self, paths) -> typing.Union[int, typing.List[int]]: + def get_instances(self, paths) -> int | list[int]: """Return the instance number(s) for a cell/material in a geometry path. The instance numbers are used as indices into distributed @@ -363,7 +360,7 @@ def get_instances(self, paths) -> typing.Union[int, typing.List[int]]: return indices if return_list else indices[0] - def get_all_cells(self) -> typing.Dict[int, openmc.Cell]: + def get_all_cells(self) -> dict[int, openmc.Cell]: """Return all cells in the geometry. Returns @@ -377,7 +374,7 @@ def get_all_cells(self) -> typing.Dict[int, openmc.Cell]: else: return {} - def get_all_universes(self) -> typing.Dict[int, openmc.Universe]: + def get_all_universes(self) -> dict[int, openmc.Universe]: """Return all universes in the geometry. Returns @@ -392,7 +389,7 @@ def get_all_universes(self) -> typing.Dict[int, openmc.Universe]: universes.update(self.root_universe.get_all_universes()) return universes - def get_all_nuclides(self) -> typing.List[str]: + def get_all_nuclides(self) -> list[str]: """Return all nuclides within the geometry. Returns @@ -406,7 +403,7 @@ def get_all_nuclides(self) -> typing.List[str]: all_nuclides |= set(material.get_nuclides()) return sorted(all_nuclides) - def get_all_materials(self) -> typing.Dict[int, openmc.Material]: + def get_all_materials(self) -> dict[int, openmc.Material]: """Return all materials within the geometry. Returns @@ -421,7 +418,7 @@ def get_all_materials(self) -> typing.Dict[int, openmc.Material]: else: return {} - def get_all_material_cells(self) -> typing.Dict[int, openmc.Cell]: + def get_all_material_cells(self) -> dict[int, openmc.Cell]: """Return all cells filled by a material Returns @@ -440,7 +437,7 @@ def get_all_material_cells(self) -> typing.Dict[int, openmc.Cell]: return material_cells - def get_all_material_universes(self) -> typing.Dict[int, openmc.Universe]: + def get_all_material_universes(self) -> dict[int, openmc.Universe]: """Return all universes having at least one material-filled cell. This method can be used to find universes that have at least one cell @@ -463,7 +460,7 @@ def get_all_material_universes(self) -> typing.Dict[int, openmc.Universe]: return material_universes - def get_all_lattices(self) -> typing.Dict[int, openmc.Lattice]: + def get_all_lattices(self) -> dict[int, openmc.Lattice]: """Return all lattices defined Returns @@ -481,7 +478,7 @@ def get_all_lattices(self) -> typing.Dict[int, openmc.Lattice]: return lattices - def get_all_surfaces(self) -> typing.Dict[int, openmc.Surface]: + def get_all_surfaces(self) -> dict[int, openmc.Surface]: """ Return all surfaces used in the geometry @@ -517,7 +514,7 @@ def _get_domains_by_name(self, name, case_sensitive, matching, domain_type) -> l def get_materials_by_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Material]: + ) -> list[openmc.Material]: """Return a list of materials with matching names. Parameters @@ -540,7 +537,7 @@ def get_materials_by_name( def get_cells_by_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Cell]: + ) -> list[openmc.Cell]: """Return a list of cells with matching names. Parameters @@ -563,7 +560,7 @@ def get_cells_by_name( def get_surfaces_by_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Surface]: + ) -> list[openmc.Surface]: """Return a list of surfaces with matching names. .. versionadded:: 0.13.3 @@ -588,7 +585,7 @@ def get_surfaces_by_name( def get_cells_by_fill_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Cell]: + ) -> list[openmc.Cell]: """Return a list of cells with fills with matching names. Parameters @@ -635,7 +632,7 @@ def get_cells_by_fill_name( def get_universes_by_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Universe]: + ) -> list[openmc.Universe]: """Return a list of universes with matching names. Parameters @@ -658,7 +655,7 @@ def get_universes_by_name( def get_lattices_by_name( self, name, case_sensitive=False, matching=False - ) -> typing.List[openmc.Lattice]: + ) -> list[openmc.Lattice]: """Return a list of lattices with matching names. Parameters @@ -679,7 +676,7 @@ def get_lattices_by_name( """ return self._get_domains_by_name(name, case_sensitive, matching, 'lattice') - def remove_redundant_surfaces(self) -> typing.Dict[int, openmc.Surface]: + def remove_redundant_surfaces(self) -> dict[int, openmc.Surface]: """Remove and return all of the redundant surfaces. Uses surface_precision attribute of Geometry instance for rounding and @@ -801,6 +798,7 @@ def plot(self, *args, **kwargs): Units used on the plot axis **kwargs Keyword arguments passed to :func:`matplotlib.pyplot.imshow` + Returns ------- matplotlib.axes.Axes diff --git a/openmc/lattice.py b/openmc/lattice.py index f7f9da8ea8e..51806856051 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -884,6 +884,10 @@ def create_xml_subelement(self, xml_element, memo=None): dimension = ET.SubElement(lattice_subelement, "dimension") dimension.text = ' '.join(map(str, self.shape)) + # Make sure lower_left has been specified + if self.lower_left is None: + raise ValueError(f"Lattice {self.id} does not have lower_left specified.") + # Export Lattice lower left lower_left = ET.SubElement(lattice_subelement, "lower_left") lower_left.text = ' '.join(map(str, self._lower_left)) diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 42d77944146..5fe35b9745d 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -13,11 +13,10 @@ """ from ctypes import CDLL, c_bool, c_int +import importlib.resources import os import sys -import pkg_resources - # Determine shared-library suffix if sys.platform == 'darwin': @@ -27,9 +26,8 @@ if os.environ.get('READTHEDOCS', None) != 'True': # Open shared library - _filename = pkg_resources.resource_filename( - __name__, f'libopenmc.{_suffix}') - _dll = CDLL(_filename) + _filename = importlib.resources.files(__name__) / f'libopenmc.{_suffix}' + _dll = CDLL(str(_filename)) # TODO: Remove str() when Python 3.12+ else: # For documentation builds, we don't actually have the shared library # available. Instead, we create a mock object so that when the modules @@ -70,6 +68,7 @@ def _uwuw_enabled(): from .math import * from .plot import * from .weight_windows import * +from .dagmc import * # Flag to denote whether or not openmc.lib.init has been called # TODO: Establish and use a flag in the C++ code to represent the status of the diff --git a/openmc/lib/core.py b/openmc/lib/core.py index a9a549fa05a..8561602e670 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -474,8 +474,11 @@ def run(output=True): _dll.openmc_run() -def sample_external_source(n_samples=1, prn_seed=None): - """Sample external source +def sample_external_source( + n_samples: int = 1000, + prn_seed: int | None = None +) -> openmc.ParticleList: + """Sample external source and return source particles. .. versionadded:: 0.13.1 @@ -489,8 +492,8 @@ def sample_external_source(n_samples=1, prn_seed=None): Returns ------- - list of openmc.SourceParticle - List of samples source particles + openmc.ParticleList + List of sampled source particles """ if n_samples <= 0: @@ -503,14 +506,13 @@ def sample_external_source(n_samples=1, prn_seed=None): _dll.openmc_sample_external_source(c_size_t(n_samples), c_uint64(prn_seed), sites_array) # Convert to list of SourceParticle and return - return [ - openmc.SourceParticle( + return openmc.ParticleList([openmc.SourceParticle( r=site.r, u=site.u, E=site.E, time=site.time, wgt=site.wgt, delayed_group=site.delayed_group, surf_id=site.surf_id, particle=openmc.ParticleType(site.particle) ) for site in sites_array - ] + ]) def simulation_init(): diff --git a/openmc/lib/dagmc.py b/openmc/lib/dagmc.py new file mode 100644 index 00000000000..18ec81a4be8 --- /dev/null +++ b/openmc/lib/dagmc.py @@ -0,0 +1,43 @@ +from ctypes import c_int, c_int32, POINTER, c_size_t + +import numpy as np + +from . import _dll +from .error import _error_handler + + +__all__ = [ + 'dagmc_universe_cell_ids' +] + +# DAGMC functions +_dll.openmc_dagmc_universe_get_cell_ids.argtypes = [c_int32, POINTER(c_int32), POINTER(c_size_t)] +_dll.openmc_dagmc_universe_get_cell_ids.restype = c_int +_dll.openmc_dagmc_universe_get_cell_ids.errcheck = _error_handler +_dll.openmc_dagmc_universe_get_num_cells.argtypes = [c_int32, POINTER(c_size_t)] +_dll.openmc_dagmc_universe_get_num_cells.restype = c_int +_dll.openmc_dagmc_universe_get_num_cells.errcheck = _error_handler + + +def dagmc_universe_cell_ids(universe_id: int) -> np.ndarray: + """Return an array of cell IDs for a DAGMC universe. + + Parameters + ---------- + dagmc_id : int + ID of the DAGMC universe to get cell IDs from. + + Returns + ------- + numpy.ndarray + DAGMC cell IDs for the universe. + + """ + n = c_size_t() + _dll.openmc_dagmc_universe_get_num_cells(universe_id, n) + cell_ids = np.empty(n.value, dtype=np.int32) + + _dll.openmc_dagmc_universe_get_cell_ids( + universe_id, cell_ids.ctypes.data_as(POINTER(c_int32)), n + ) + return cell_ids diff --git a/openmc/lib/filter.py b/openmc/lib/filter.py index 340c2fa3448..b30f5e66282 100644 --- a/openmc/lib/filter.py +++ b/openmc/lib/filter.py @@ -21,9 +21,9 @@ 'CellInstanceFilter', 'CollisionFilter', 'DistribcellFilter', 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'LegendreFilter', 'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshBornFilter', - 'MeshSurfaceFilter', 'MuFilter', 'ParticleFilter', - 'PolarFilter', 'SphericalHarmonicsFilter', 'SpatialLegendreFilter', 'SurfaceFilter', - 'UniverseFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' + 'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter', 'ParticleFilter', 'PolarFilter', + 'SphericalHarmonicsFilter', 'SpatialLegendreFilter', 'SurfaceFilter', 'UniverseFilter', + 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' ] # Tally functions @@ -540,6 +540,10 @@ class MuFilter(Filter): filter_type = 'mu' +class MuSurfaceFilter(Filter): + filter_type = 'musurface' + + class ParticleFilter(Filter): filter_type = 'particle' @@ -642,6 +646,7 @@ class ZernikeRadialFilter(ZernikeFilter): 'meshborn': MeshBornFilter, 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, + 'musurface': MuSurfaceFilter, 'particle': ParticleFilter, 'polar': PolarFilter, 'sphericalharmonics': SphericalHarmonicsFilter, diff --git a/openmc/lib/math.py b/openmc/lib/math.py index 029f1c4c9ff..8c62f241624 100644 --- a/openmc/lib/math.py +++ b/openmc/lib/math.py @@ -1,5 +1,4 @@ -from ctypes import c_int, c_double, POINTER, c_uint64 -from random import getrandbits +from ctypes import c_int, c_double import numpy as np from numpy.ctypeslib import ndpointer @@ -7,137 +6,18 @@ from . import _dll -_dll.t_percentile.restype = c_double -_dll.t_percentile.argtypes = [c_double, c_int] - -_dll.calc_pn_c.restype = None -_dll.calc_pn_c.argtypes = [c_int, c_double, ndpointer(c_double)] - -_dll.evaluate_legendre.restype = c_double -_dll.evaluate_legendre.argtypes = [c_int, POINTER(c_double), c_double] - -_dll.calc_rn_c.restype = None -_dll.calc_rn_c.argtypes = [c_int, ndpointer(c_double), ndpointer(c_double)] - _dll.calc_zn.restype = None _dll.calc_zn.argtypes = [c_int, c_double, c_double, ndpointer(c_double)] _dll.calc_zn_rad.restype = None _dll.calc_zn_rad.argtypes = [c_int, c_double, ndpointer(c_double)] -_dll.rotate_angle_c.restype = None -_dll.rotate_angle_c.argtypes = [ndpointer(c_double), c_double, - POINTER(c_double), POINTER(c_uint64)] -_dll.maxwell_spectrum.restype = c_double -_dll.maxwell_spectrum.argtypes = [c_double, POINTER(c_uint64)] - -_dll.watt_spectrum.restype = c_double -_dll.watt_spectrum.argtypes = [c_double, c_double, POINTER(c_uint64)] - -_dll.broaden_wmp_polynomials.restype = None -_dll.broaden_wmp_polynomials.argtypes = [c_double, c_double, c_int, - ndpointer(c_double)] - -_dll.normal_variate.restype = c_double -_dll.normal_variate.argtypes = [c_double, c_double, POINTER(c_uint64)] - -def t_percentile(p, df): - """ Calculate the percentile of the Student's t distribution with a - specified probability level and number of degrees of freedom - - Parameters - ---------- - p : float - Probability level - df : int - Degrees of freedom - - Returns - ------- - float - Corresponding t-value - - """ - - return _dll.t_percentile(p, df) - - -def calc_pn(n, x): - """ Calculate the n-th order Legendre polynomial at the value of x. - - Parameters - ---------- - n : int - Legendre order - x : float - Independent variable to evaluate the Legendre at - - Returns - ------- - float - Corresponding Legendre polynomial result - - """ - - pnx = np.empty(n + 1, dtype=np.float64) - _dll.calc_pn_c(n, x, pnx) - return pnx - - -def evaluate_legendre(data, x): - """ Finds the value of f(x) given a set of Legendre coefficients - and the value of x. - - Parameters - ---------- - data : iterable of float - Legendre coefficients - x : float - Independent variable to evaluate the Legendre at - - Returns - ------- - float - Corresponding Legendre expansion result - - """ - - data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre(len(data)-1, - data_arr.ctypes.data_as(POINTER(c_double)), x) - - -def calc_rn(n, uvw): - """ Calculate the n-th order real Spherical Harmonics for a given angle; - all Rn,m values are provided for all n (where -n <= m <= n). - - Parameters - ---------- - n : int - Harmonics order - uvw : iterable of float - Independent variable to evaluate the Legendre at - - Returns - ------- - numpy.ndarray - Corresponding real harmonics value - - """ - - num_nm = (n + 1) * (n + 1) - rn = np.empty(num_nm, dtype=np.float64) - uvw_arr = np.array(uvw, dtype=np.float64) - _dll.calc_rn_c(n, uvw_arr, rn) - return rn - def calc_zn(n, rho, phi): """ Calculate the n-th order modified Zernike polynomial moment for a given angle (rho, theta) location in the unit disk. The normalization of the polynomials is such that the integral of Z_pq*Z_pq over the unit disk is exactly pi - Parameters ---------- n : int @@ -146,12 +26,10 @@ def calc_zn(n, rho, phi): Radial location in the unit disk phi : float Theta (radians) location in the unit disk - Returns ------- numpy.ndarray Corresponding resulting list of coefficients - """ num_bins = ((n + 1) * (n + 2)) // 2 @@ -165,161 +43,19 @@ def calc_zn_rad(n, rho): moment with no azimuthal dependency (m=0) for a given radial location in the unit disk. The normalization of the polynomials is such that the integral of Z_pq*Z_pq over the unit disk is exactly pi. - Parameters ---------- n : int Maximum order rho : float Radial location in the unit disk - Returns ------- numpy.ndarray Corresponding resulting list of coefficients - """ num_bins = n // 2 + 1 zn_rad = np.zeros(num_bins, dtype=np.float64) _dll.calc_zn_rad(n, rho, zn_rad) return zn_rad - - -def rotate_angle(uvw0, mu, phi, prn_seed=None): - """ Rotates direction cosines through a polar angle whose cosine is - mu and through an azimuthal angle sampled uniformly. - - Parameters - ---------- - uvw0 : iterable of float - Original direction cosine - mu : float - Polar angle cosine to rotate - phi : float - Azimuthal angle; if None, one will be sampled uniformly - prn_seed : int - Pseudorandom number generator (PRNG) seed; if None, one will be - generated randomly. - - Returns - ------- - numpy.ndarray - Rotated direction cosine - - """ - - if prn_seed is None: - prn_seed = getrandbits(63) - - uvw0_arr = np.array(uvw0, dtype=np.float64) - if phi is None: - _dll.rotate_angle_c(uvw0_arr, mu, None, c_uint64(prn_seed)) - else: - _dll.rotate_angle_c(uvw0_arr, mu, c_double(phi), c_uint64(prn_seed)) - - uvw = uvw0_arr - - return uvw - - -def maxwell_spectrum(T, prn_seed=None): - """ Samples an energy from the Maxwell fission distribution based - on a direct sampling scheme. - - Parameters - ---------- - T : float - Spectrum parameter - prn_seed : int - Pseudorandom number generator (PRNG) seed; if None, one will be - generated randomly. - - Returns - ------- - float - Sampled outgoing energy - - """ - - if prn_seed is None: - prn_seed = getrandbits(63) - - return _dll.maxwell_spectrum(T, c_uint64(prn_seed)) - - -def watt_spectrum(a, b, prn_seed=None): - """ Samples an energy from the Watt energy-dependent fission spectrum. - - Parameters - ---------- - a : float - Spectrum parameter a - b : float - Spectrum parameter b - prn_seed : int - Pseudorandom number generator (PRNG) seed; if None, one will be - generated randomly. - - Returns - ------- - float - Sampled outgoing energy - - """ - - if prn_seed is None: - prn_seed = getrandbits(63) - - return _dll.watt_spectrum(a, b, c_uint64(prn_seed)) - - -def normal_variate(mean_value, std_dev, prn_seed=None): - """ Samples an energy from the Normal distribution. - - Parameters - ---------- - mean_value : float - Mean of the Normal distribution - std_dev : float - Standard deviation of the normal distribution - prn_seed : int - Pseudorandom number generator (PRNG) seed; if None, one will be - generated randomly. - - Returns - ------- - float - Sampled outgoing normally distributed value - - """ - - if prn_seed is None: - prn_seed = getrandbits(63) - - return _dll.normal_variate(mean_value, std_dev, c_uint64(prn_seed)) - - -def broaden_wmp_polynomials(E, dopp, n): - """ Doppler broadens the windowed multipole curvefit. The curvefit is a - polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... - - Parameters - ---------- - E : float - Energy to evaluate at - dopp : float - sqrt(atomic weight ratio / kT), with kT given in eV - n : int - Number of components to the polynomial - - Returns - ------- - numpy.ndarray - Resultant leading coefficients - - """ - - factors = np.zeros(n, dtype=np.float64) - _dll.broaden_wmp_polynomials(E, dopp, n, factors) - return factors diff --git a/openmc/lib/mesh.py b/openmc/lib/mesh.py index 4da7baba771..16bec019863 100644 --- a/openmc/lib/mesh.py +++ b/openmc/lib/mesh.py @@ -1,8 +1,8 @@ -from collections.abc import Mapping +from collections.abc import Mapping, Sequence from ctypes import (c_int, c_int32, c_char_p, c_double, POINTER, Structure, create_string_buffer, c_uint64, c_size_t) from random import getrandbits -from typing import Optional, List, Tuple, Sequence +import sys from weakref import WeakValueDictionary import numpy as np @@ -14,6 +14,7 @@ from .error import _error_handler from .material import Material from .plot import _Position +from ..bounding_box import BoundingBox __all__ = [ 'Mesh', 'RegularMesh', 'RectilinearMesh', 'CylindricalMesh', @@ -45,6 +46,10 @@ class _MaterialVolume(Structure): _dll.openmc_mesh_get_volumes.argtypes = [c_int32, POINTER(c_double)] _dll.openmc_mesh_get_volumes.restype = c_int _dll.openmc_mesh_get_volumes.errcheck = _error_handler +_dll.openmc_mesh_bounding_box.argtypes = [ + c_int32, POINTER(c_double), POINTER(c_double)] +_dll.openmc_mesh_bounding_box.restype = c_int +_dll.openmc_mesh_bounding_box.errcheck = _error_handler _dll.openmc_mesh_material_volumes.argtypes = [ c_int32, c_int, c_int, c_int, POINTER(_MaterialVolume), POINTER(c_int), POINTER(c_uint64)] @@ -167,14 +172,30 @@ def volumes(self) -> np.ndarray: self._index, volumes.ctypes.data_as(POINTER(c_double))) return volumes + @property + def bounding_box(self) -> BoundingBox: + inf = sys.float_info.max + ll = np.zeros(3) + ur = np.zeros(3) + _dll.openmc_mesh_bounding_box( + self._index, + ll.ctypes.data_as(POINTER(c_double)), + ur.ctypes.data_as(POINTER(c_double)) + ) + ll[ll == inf] = np.inf + ur[ur == inf] = np.inf + ll[ll == -inf] = -np.inf + ur[ur == -inf] = -np.inf + return BoundingBox(ll, ur) + def material_volumes( self, n_samples: int = 10_000, - prn_seed: Optional[int] = None - ) -> List[List[Tuple[Material, float]]]: + prn_seed: int | None = None + ) -> list[list[tuple[Material, float]]]: """Determine volume of materials in each mesh element - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -231,7 +252,7 @@ def get_plot_bins( ) -> np.ndarray: """Get mesh bin indices for a rasterized plot. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -293,6 +314,8 @@ class RegularMesh(Mesh): Total number of mesh elements. volumes : numpy.ndarray Volume of each mesh element in [cm^3] + bounding_box : openmc.BoundingBox + Axis-aligned bounding box of the mesh """ mesh_type = 'regular' @@ -379,6 +402,8 @@ class RectilinearMesh(Mesh): Total number of mesh elements. volumes : numpy.ndarray Volume of each mesh element in [cm^3] + bounding_box : openmc.BoundingBox + Axis-aligned bounding box of the mesh """ mesh_type = 'rectilinear' @@ -482,6 +507,8 @@ class CylindricalMesh(Mesh): Total number of mesh elements. volumes : numpy.ndarray Volume of each mesh element in [cm^3] + bounding_box : openmc.BoundingBox + Axis-aligned bounding box of the mesh """ mesh_type = 'cylindrical' @@ -585,6 +612,8 @@ class SphericalMesh(Mesh): Total number of mesh elements. volumes : numpy.ndarray Volume of each mesh element in [cm^3] + bounding_box : openmc.BoundingBox + Axis-aligned bounding box of the mesh """ mesh_type = 'spherical' diff --git a/openmc/material.py b/openmc/material.py index 6edc372161c..a6401216adb 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -5,9 +5,8 @@ from numbers import Real from pathlib import Path import re -import typing # imported separately as py3.8 requires typing.Iterable +import sys import warnings -from typing import Optional, List, Union, Dict import lxml.etree as ET import numpy as np @@ -18,14 +17,19 @@ import openmc.checkvalue as cv from ._xml import clean_indentation, reorder_attributes from .mixin import IDManagerMixin +from .utility_funcs import input_path from openmc.checkvalue import PathLike from openmc.stats import Univariate, Discrete, Mixture +from openmc.data.data import _get_element_symbol # Units for density supported by OpenMC DENSITY_UNITS = ('g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro') +# Smallest normalized floating point number +_SMALLEST_NORMAL = sys.float_info.min + NuclideTuple = namedtuple('NuclideTuple', ['name', 'percent', 'percent_type']) @@ -161,11 +165,11 @@ def __repr__(self) -> str: return string @property - def name(self) -> Optional[str]: + def name(self) -> str | None: return self._name @name.setter - def name(self, name: Optional[str]): + def name(self, name: str | None): if name is not None: cv.check_type(f'name for Material ID="{self._id}"', name, str) @@ -174,17 +178,17 @@ def name(self, name: Optional[str]): self._name = '' @property - def temperature(self) -> Optional[float]: + def temperature(self) -> float | None: return self._temperature @temperature.setter - def temperature(self, temperature: Optional[Real]): + def temperature(self, temperature: Real | None): cv.check_type(f'Temperature for Material ID="{self._id}"', temperature, (Real, type(None))) self._temperature = temperature @property - def density(self) -> Optional[float]: + def density(self) -> float | None: return self._density @property @@ -202,7 +206,7 @@ def depletable(self, depletable: bool): self._depletable = depletable @property - def paths(self) -> List[str]: + def paths(self) -> list[str]: if self._paths is None: raise ValueError('Material instance paths have not been determined. ' 'Call the Geometry.determine_paths() method.') @@ -217,15 +221,15 @@ def num_instances(self) -> int: return self._num_instances @property - def nuclides(self) -> List[namedtuple]: + def nuclides(self) -> list[namedtuple]: return self._nuclides @property - def isotropic(self) -> List[str]: + def isotropic(self) -> list[str]: return self._isotropic @isotropic.setter - def isotropic(self, isotropic: typing.Iterable[str]): + def isotropic(self, isotropic: Iterable[str]): cv.check_iterable_type('Isotropic scattering nuclides', isotropic, str) self._isotropic = list(isotropic) @@ -248,7 +252,7 @@ def average_molar_mass(self) -> float: return mass / moles @property - def volume(self) -> Optional[float]: + def volume(self) -> float | None: return self._volume @volume.setter @@ -258,7 +262,7 @@ def volume(self, volume: Real): self._volume = volume @property - def ncrystal_cfg(self) -> Optional[str]: + def ncrystal_cfg(self) -> str | None: return self._ncrystal_cfg @property @@ -274,7 +278,7 @@ def fissionable_mass(self) -> float: return density*self.volume @property - def decay_photon_energy(self) -> Optional[Univariate]: + def decay_photon_energy(self) -> Univariate | None: warnings.warn( "The 'decay_photon_energy' property has been replaced by the " "get_decay_photon_energy() method and will be removed in a future " @@ -285,8 +289,8 @@ def get_decay_photon_energy( self, clip_tolerance: float = 1e-6, units: str = 'Bq', - volume: Optional[float] = None - ) -> Optional[Univariate]: + volume: float | None = None + ) -> Univariate | None: r"""Return energy distribution of decay photons from unstable nuclides. .. versionadded:: 0.14.0 @@ -322,7 +326,7 @@ def get_decay_photon_energy( probs = [] for nuc, atoms_per_bcm in self.get_nuclide_atom_densities().items(): source_per_atom = openmc.data.decay_photon_energy(nuc) - if source_per_atom is not None: + if source_per_atom is not None and atoms_per_bcm > 0.0: dists.append(source_per_atom) probs.append(1e24 * atoms_per_bcm * multiplier) @@ -335,6 +339,11 @@ def get_decay_photon_energy( if isinstance(combined, (Discrete, Mixture)): combined.clip(clip_tolerance, inplace=True) + # If clipping resulted in a single distribution within a mixture, pick + # out that single distribution + if isinstance(combined, Mixture) and len(combined.distribution) == 1: + combined = combined.distribution[0] + return combined @classmethod @@ -471,7 +480,7 @@ def add_volume_information(self, volume_calc): else: raise ValueError(f'No volume information found for material ID={self.id}.') - def set_density(self, units: str, density: Optional[float] = None): + def set_density(self, units: str, density: float | None = None): """Set the density of the material Parameters @@ -519,6 +528,7 @@ def add_nuclide(self, nuclide: str, percent: float, percent_type: str = 'ao'): cv.check_type('nuclide', nuclide, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) + cv.check_greater_than('percent', percent, 0, equality=True) if self._macroscopic is not None: msg = 'Unable to add a Nuclide to Material ID="{}" as a ' \ @@ -684,10 +694,10 @@ def remove_macroscopic(self, macroscopic: str): self._macroscopic = None def add_element(self, element: str, percent: float, percent_type: str = 'ao', - enrichment: Optional[float] = None, - enrichment_target: Optional[str] = None, - enrichment_type: Optional[str] = None, - cross_sections: Optional[str] = None): + enrichment: float | None = None, + enrichment_target: str | None = None, + enrichment_type: str | None = None, + cross_sections: str | None = None): """Add a natural element to the material Parameters @@ -727,6 +737,7 @@ def add_element(self, element: str, percent: float, percent_type: str = 'ao', cv.check_type('nuclide', element, str) cv.check_type('percent', percent, Real) + cv.check_greater_than('percent', percent, 0, equality=True) cv.check_value('percent type', percent_type, {'ao', 'wo'}) # Make sure element name is just that @@ -797,9 +808,9 @@ def add_element(self, element: str, percent: float, percent_type: str = 'ao', self.add_nuclide(*nuclide) def add_elements_from_formula(self, formula: str, percent_type: str = 'ao', - enrichment: Optional[float] = None, - enrichment_target: Optional[str] = None, - enrichment_type: Optional[str] = None): + enrichment: float | None = None, + enrichment_target: str | None = None, + enrichment_type: str | None = None): """Add a elements from a chemical formula to the material. .. versionadded:: 0.12 @@ -928,7 +939,7 @@ def add_s_alpha_beta(self, name: str, fraction: float = 1.0): def make_isotropic_in_lab(self): self.isotropic = [x.name for x in self._nuclides] - def get_elements(self) -> List[str]: + def get_elements(self) -> list[str]: """Returns all elements in the material .. versionadded:: 0.12 @@ -942,7 +953,7 @@ def get_elements(self) -> List[str]: return sorted({re.split(r'(\d+)', i)[0] for i in self.get_nuclides()}) - def get_nuclides(self, element: Optional[str] = None) -> List[str]: + def get_nuclides(self, element: str | None = None) -> list[str]: """Returns a list of all nuclides in the material, if the element argument is specified then just nuclides of that element are returned. @@ -972,7 +983,7 @@ def get_nuclides(self, element: Optional[str] = None) -> List[str]: return matching_nuclides - def get_nuclide_densities(self) -> Dict[str, tuple]: + def get_nuclide_densities(self) -> dict[str, tuple]: """Returns all nuclides in the material and their densities Returns @@ -990,7 +1001,7 @@ def get_nuclide_densities(self) -> Dict[str, tuple]: return nuclides - def get_nuclide_atom_densities(self, nuclide: Optional[str] = None) -> Dict[str, float]: + def get_nuclide_atom_densities(self, nuclide: str | None = None) -> dict[str, float]: """Returns one or all nuclides in the material and their atomic densities in units of atom/b-cm @@ -1075,8 +1086,50 @@ def get_nuclide_atom_densities(self, nuclide: Optional[str] = None) -> Dict[str, return nuclides + def get_element_atom_densities(self, element: str | None = None) -> dict[str, float]: + """Returns one or all elements in the material and their atomic + densities in units of atom/b-cm + + .. versionadded:: 0.15.1 + + Parameters + ---------- + element : str, optional + Element for which atom density is desired. If not specified, the + atom density for each element in the material is given. + + Returns + ------- + elements : dict + Dictionary whose keys are element names and values are densities in + [atom/b-cm] + + """ + if element is not None: + element = _get_element_symbol(element) + + nuc_densities = self.get_nuclide_atom_densities() + + # Initialize an empty dictionary for summed values + densities = {} + + # Accumulate densities for each nuclide + for nuclide, density in nuc_densities.items(): + nuc_element = openmc.data.ATOMIC_SYMBOL[openmc.data.zam(nuclide)[0]] + if element is None or element == nuc_element: + if nuc_element not in densities: + densities[nuc_element] = 0.0 + densities[nuc_element] += float(density) + + # If specific element was requested, make sure it is present + if element is not None and element not in densities: + raise ValueError(f'Element {element} not found in material.') + + return densities + + def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False, - volume: Optional[float] = None) -> Union[Dict[str, float], float]: + volume: float | None = None) -> dict[str, float] | float: """Returns the activity of the material or for each nuclide in the material in units of [Bq], [Bq/g] or [Bq/cm3]. @@ -1099,7 +1152,7 @@ def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False, Returns ------- - typing.Union[dict, float] + Union[dict, float] If by_nuclide is True then a dictionary whose keys are nuclide names and values are activity is returned. Otherwise the activity of the material is returned as a float. @@ -1123,7 +1176,7 @@ def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False, return activity if by_nuclide else sum(activity.values()) def get_decay_heat(self, units: str = 'W', by_nuclide: bool = False, - volume: Optional[float] = None) -> Union[Dict[str, float], float]: + volume: float | None = None) -> dict[str, float] | float: """Returns the decay heat of the material or for each nuclide in the material in units of [W], [W/g] or [W/cm3]. @@ -1171,7 +1224,7 @@ def get_decay_heat(self, units: str = 'W', by_nuclide: bool = False, return decayheat if by_nuclide else sum(decayheat.values()) - def get_nuclide_atoms(self, volume: Optional[float] = None) -> Dict[str, float]: + def get_nuclide_atoms(self, volume: float | None = None) -> dict[str, float]: """Return number of atoms of each nuclide in the material .. versionadded:: 0.13.1 @@ -1200,7 +1253,7 @@ def get_nuclide_atoms(self, volume: Optional[float] = None) -> Dict[str, float]: atoms[nuclide] = 1.0e24 * atom_per_bcm * volume return atoms - def get_mass_density(self, nuclide: Optional[str] = None) -> float: + def get_mass_density(self, nuclide: str | None = None) -> float: """Return mass density of one or all nuclides Parameters @@ -1222,7 +1275,7 @@ def get_mass_density(self, nuclide: Optional[str] = None) -> float: mass_density += density_i return mass_density - def get_mass(self, nuclide: Optional[str] = None, volume: Optional[float] = None) -> float: + def get_mass(self, nuclide: str | None = None, volume: float | None = None) -> float: """Return mass of one or all nuclides. Note that this method requires that the :attr:`Material.volume` has @@ -1252,7 +1305,7 @@ def get_mass(self, nuclide: Optional[str] = None, volume: Optional[float] = None raise ValueError("Volume must be set in order to determine mass.") return volume*self.get_mass_density(nuclide) - def clone(self, memo: Optional[dict] = None) -> Material: + def clone(self, memo: dict | None = None) -> Material: """Create a copy of this material with a new unique ID. Parameters @@ -1295,10 +1348,16 @@ def _get_nuclide_xml(self, nuclide: NuclideTuple) -> ET.Element: xml_element = ET.Element("nuclide") xml_element.set("name", nuclide.name) + # Prevent subnormal numbers from being written to XML, which causes an + # exception on the C++ side when calling std::stod + val = nuclide.percent + if abs(val) < _SMALLEST_NORMAL: + val = 0.0 + if nuclide.percent_type == 'ao': - xml_element.set("ao", str(nuclide.percent)) + xml_element.set("ao", str(val)) else: - xml_element.set("wo", str(nuclide.percent)) + xml_element.set("wo", str(val)) return xml_element @@ -1309,8 +1368,8 @@ def _get_macroscopic_xml(self, macroscopic: str) -> ET.Element: return xml_element def _get_nuclides_xml( - self, nuclides: typing.Iterable[NuclideTuple], - nuclides_to_ignore: Optional[typing.Iterable[str]] = None)-> List[ET.Element]: + self, nuclides: Iterable[NuclideTuple], + nuclides_to_ignore: Iterable[str] | None = None)-> list[ET.Element]: xml_elements = [] # Remove any nuclides to ignore from the XML export @@ -1322,7 +1381,7 @@ def _get_nuclides_xml( return xml_elements def to_xml_element( - self, nuclides_to_ignore: Optional[typing.Iterable[str]] = None) -> ET.Element: + self, nuclides_to_ignore: Iterable[str] | None = None) -> ET.Element: """Return XML representation of the material Parameters @@ -1396,8 +1455,8 @@ def to_xml_element( return element @classmethod - def mix_materials(cls, materials, fracs: typing.Iterable[float], - percent_type: str = 'ao', name: Optional[str] = None) -> Material: + def mix_materials(cls, materials, fracs: Iterable[float], + percent_type: str = 'ao', name: str | None = None) -> Material: """Mix materials together based on atom, weight, or volume fractions .. versionadded:: 0.12 @@ -1477,7 +1536,7 @@ def mix_materials(cls, materials, fracs: typing.Iterable[float], if name is None: name = '-'.join([f'{m.name}({f})' for m, f in zip(materials, fracs)]) - new_mat = openmc.Material(name=name) + new_mat = cls(name=name) # Compute atom fractions of nuclides and add them to the new material tot_nuclides_per_cc = np.sum([dens for dens in nuclides_per_cc.values()]) @@ -1594,13 +1653,13 @@ def __init__(self, materials=None): self += materials @property - def cross_sections(self) -> Optional[Path]: + def cross_sections(self) -> Path | None: return self._cross_sections @cross_sections.setter def cross_sections(self, cross_sections): if cross_sections is not None: - self._cross_sections = Path(cross_sections) + self._cross_sections = input_path(cross_sections) def append(self, material): """Append material to collection @@ -1684,7 +1743,7 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2, file.write(indentation) def export_to_xml(self, path: PathLike = 'materials.xml', - nuclides_to_ignore: Optional[typing.Iterable[str]] = None): + nuclides_to_ignore: Iterable[str] | None = None): """Export material collection to an XML file. Parameters diff --git a/openmc/mesh.py b/openmc/mesh.py index 48263c2055c..e4d83d81d3d 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -1,14 +1,10 @@ from __future__ import annotations -import typing import warnings from abc import ABC, abstractmethod -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from functools import wraps from math import pi, sqrt, atan2 from numbers import Integral, Real -from pathlib import Path -import tempfile -from typing import Optional, Sequence, Tuple, List import h5py import lxml.etree as ET @@ -21,6 +17,7 @@ from ._xml import get_text from .mixin import IDManagerMixin from .surface import _BOUNDARY_TYPES +from .utility_funcs import input_path class MeshBase(IDManagerMixin, ABC): @@ -49,7 +46,7 @@ class MeshBase(IDManagerMixin, ABC): next_id = 1 used_ids = set() - def __init__(self, mesh_id: Optional[int] = None, name: str = ''): + def __init__(self, mesh_id: int | None = None, name: str = ''): # Initialize Mesh class attributes self.id = mesh_id self.name = name @@ -102,21 +99,40 @@ def from_hdf5(cls, group: h5py.Group): Instance of a MeshBase subclass """ + mesh_type = 'regular' if 'type' not in group.keys() else group['type'][()].decode() + mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) + mesh_name = '' if not 'name' in group else group['name'][()].decode() - mesh_type = group['type'][()].decode() if mesh_type == 'regular': - return RegularMesh.from_hdf5(group) + return RegularMesh.from_hdf5(group, mesh_id, mesh_name) elif mesh_type == 'rectilinear': - return RectilinearMesh.from_hdf5(group) + return RectilinearMesh.from_hdf5(group, mesh_id, mesh_name) elif mesh_type == 'cylindrical': - return CylindricalMesh.from_hdf5(group) + return CylindricalMesh.from_hdf5(group, mesh_id, mesh_name) elif mesh_type == 'spherical': - return SphericalMesh.from_hdf5(group) + return SphericalMesh.from_hdf5(group, mesh_id, mesh_name) elif mesh_type == 'unstructured': - return UnstructuredMesh.from_hdf5(group) + return UnstructuredMesh.from_hdf5(group, mesh_id, mesh_name) else: raise ValueError('Unrecognized mesh type: "' + mesh_type + '"') + def to_xml_element(self): + """Return XML representation of the mesh + + Returns + ------- + element : lxml.etree._Element + XML element containing mesh data + + """ + elem = ET.Element("mesh") + + elem.set("id", str(self._id)) + if self.name: + elem.set("name", self.name) + + return elem + @classmethod def from_xml_element(cls, elem: ET.Element): """Generates a mesh from an XML element @@ -135,28 +151,32 @@ def from_xml_element(cls, elem: ET.Element): mesh_type = get_text(elem, 'type') if mesh_type == 'regular' or mesh_type is None: - return RegularMesh.from_xml_element(elem) + mesh = RegularMesh.from_xml_element(elem) elif mesh_type == 'rectilinear': - return RectilinearMesh.from_xml_element(elem) + mesh = RectilinearMesh.from_xml_element(elem) elif mesh_type == 'cylindrical': - return CylindricalMesh.from_xml_element(elem) + mesh = CylindricalMesh.from_xml_element(elem) elif mesh_type == 'spherical': - return SphericalMesh.from_xml_element(elem) + mesh = SphericalMesh.from_xml_element(elem) elif mesh_type == 'unstructured': - return UnstructuredMesh.from_xml_element(elem) + mesh = UnstructuredMesh.from_xml_element(elem) else: raise ValueError(f'Unrecognized mesh type "{mesh_type}" found.') + mesh.name = get_text(elem, 'name', default='') + return mesh + def get_homogenized_materials( self, model: openmc.Model, n_samples: int = 10_000, - prn_seed: Optional[int] = None, + prn_seed: int | None = None, + include_void: bool = True, **kwargs - ) -> List[openmc.Material]: + ) -> list[openmc.Material]: """Generate homogenized materials over each element in a mesh. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -168,6 +188,8 @@ def get_homogenized_materials( prn_seed : int, optional Pseudorandom number generator (PRNG) seed; if None, one will be generated randomly. + include_void : bool, optional + Whether homogenization should include voids. **kwargs Keyword-arguments passed to :func:`openmc.lib.init`. @@ -208,6 +230,18 @@ def get_homogenized_materials( # Create homogenized material for each element materials = model.geometry.get_all_materials() + + # Account for materials in DAGMC universes + # TODO: This should really get incorporated in lower-level calls to + # get_all_materials, but right now it requires information from the + # Model object + for cell in model.geometry.get_all_cells().values(): + if isinstance(cell.fill, openmc.DAGMCUniverse): + names = cell.fill.material_names + materials.update({ + mat.id: mat for mat in model.materials if mat.name in names + }) + homogenized_materials = [] for mat_volume_list in mat_volume_by_element: material_ids, volumes = [list(x) for x in zip(*mat_volume_list)] @@ -222,6 +256,10 @@ def get_homogenized_materials( material_ids.pop(index_void) volumes.pop(index_void) + # If void should be excluded, adjust total volume + if not include_void: + total_volume = sum(volumes) + # Compute volume fractions volume_fracs = np.array(volumes) / total_volume @@ -374,7 +412,7 @@ def num_mesh_cells(self): def write_data_to_vtk(self, filename: PathLike, - datasets: Optional[dict] = None, + datasets: dict | None = None, volume_normalization: bool = True, curvilinear: bool = False): """Creates a VTK object of the mesh @@ -623,7 +661,7 @@ class RegularMesh(StructuredMesh): """ - def __init__(self, mesh_id: Optional[int] = None, name: str = ''): + def __init__(self, mesh_id: int | None = None, name: str = ''): super().__init__(mesh_id, name) self._dimension = None @@ -636,7 +674,7 @@ def dimension(self): return tuple(self._dimension) @dimension.setter - def dimension(self, dimension: typing.Iterable[int]): + def dimension(self, dimension: Iterable[int]): cv.check_type('mesh dimension', dimension, Iterable, Integral) cv.check_length('mesh dimension', dimension, 1, 3) self._dimension = dimension @@ -653,7 +691,7 @@ def lower_left(self): return self._lower_left @lower_left.setter - def lower_left(self, lower_left: typing.Iterable[Real]): + def lower_left(self, lower_left: Iterable[Real]): cv.check_type('mesh lower_left', lower_left, Iterable, Real) cv.check_length('mesh lower_left', lower_left, 1, 3) self._lower_left = lower_left @@ -673,7 +711,7 @@ def upper_right(self): return [l + w * d for l, w, d in zip(ls, ws, dims)] @upper_right.setter - def upper_right(self, upper_right: typing.Iterable[Real]): + def upper_right(self, upper_right: Iterable[Real]): cv.check_type('mesh upper_right', upper_right, Iterable, Real) cv.check_length('mesh upper_right', upper_right, 1, 3) self._upper_right = upper_right @@ -697,7 +735,7 @@ def width(self): return [(u - l) / d for u, l, d in zip(us, ls, dims)] @width.setter - def width(self, width: typing.Iterable[Real]): + def width(self, width: Iterable[Real]): cv.check_type('mesh width', width, Iterable, Real) cv.check_length('mesh width', width, 1, 3) self._width = width @@ -770,16 +808,14 @@ def __repr__(self): string += '{0: <16}{1}{2}\n'.format('\tDimensions', '=\t', self.n_dimension) string += '{0: <16}{1}{2}\n'.format('\tVoxels', '=\t', self._dimension) string += '{0: <16}{1}{2}\n'.format('\tLower left', '=\t', self._lower_left) - string += '{0: <16}{1}{2}\n'.format('\tUpper Right', '=\t', self._upper_right) - string += '{0: <16}{1}{2}\n'.format('\tWidth', '=\t', self._width) + string += '{0: <16}{1}{2}\n'.format('\tUpper Right', '=\t', self.upper_right) + string += '{0: <16}{1}{2}\n'.format('\tWidth', '=\t', self.width) return string @classmethod - def from_hdf5(cls, group: h5py.Group): - mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) - + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): # Read and assign mesh properties - mesh = cls(mesh_id) + mesh = cls(mesh_id=mesh_id, name=name) mesh.dimension = group['dimension'][()] mesh.lower_left = group['lower_left'][()] if 'width' in group: @@ -796,7 +832,7 @@ def from_rect_lattice( cls, lattice: 'openmc.RectLattice', division: int = 1, - mesh_id: Optional[int] = None, + mesh_id: int | None = None, name: str = '' ): """Create mesh from an existing rectangular lattice @@ -834,9 +870,9 @@ def from_rect_lattice( @classmethod def from_domain( cls, - domain: typing.Union['openmc.Cell', 'openmc.Region', 'openmc.Universe', 'openmc.Geometry'], + domain: 'openmc.Cell' | 'openmc.Region' | 'openmc.Universe' | 'openmc.Geometry', dimension: Sequence[int] = (10, 10, 10), - mesh_id: Optional[int] = None, + mesh_id: int | None = None, name: str = '' ): """Create mesh from an existing openmc cell, region, universe or @@ -883,9 +919,7 @@ def to_xml_element(self): XML element containing mesh data """ - - element = ET.Element("mesh") - element.set("id", str(self._id)) + element = super().to_xml_element() if self._dimension is not None: subelement = ET.SubElement(element, "dimension") @@ -921,10 +955,6 @@ def from_xml_element(cls, elem: ET.Element): mesh_id = int(get_text(elem, 'id')) mesh = cls(mesh_id=mesh_id) - mesh_type = get_text(elem, 'type') - if mesh_type is not None: - mesh.type = mesh_type - dimension = get_text(elem, 'dimension') if dimension is not None: mesh.dimension = [int(x) for x in dimension.split()] @@ -943,7 +973,7 @@ def from_xml_element(cls, elem: ET.Element): return mesh - def build_cells(self, bc: Optional[str] = None): + def build_cells(self, bc: str | None = None): """Generates a lattice of universes with the same dimensionality as the mesh object. The individual cells/universes produced will not have material definitions applied and so downstream code @@ -1219,11 +1249,9 @@ def __repr__(self): return string @classmethod - def from_hdf5(cls, group: h5py.Group): - mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) - + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): # Read and assign mesh properties - mesh = cls(mesh_id=mesh_id) + mesh = cls(mesh_id=mesh_id, name=name) mesh.x_grid = group['x_grid'][()] mesh.y_grid = group['y_grid'][()] mesh.z_grid = group['z_grid'][()] @@ -1263,8 +1291,7 @@ def to_xml_element(self): """ - element = ET.Element("mesh") - element.set("id", str(self._id)) + element = super().to_xml_element() element.set("type", "rectilinear") subelement = ET.SubElement(element, "x_grid") @@ -1344,7 +1371,7 @@ def __init__( z_grid: Sequence[float], phi_grid: Sequence[float] = (0, 2*pi), origin: Sequence[float] = (0., 0., 0.), - mesh_id: Optional[int] = None, + mesh_id: int | None = None, name: str = '', ): super().__init__(mesh_id, name) @@ -1381,6 +1408,8 @@ def r_grid(self): @r_grid.setter def r_grid(self, grid): cv.check_type('mesh r_grid', grid, Iterable, Real) + cv.check_length('mesh r_grid', grid, 2) + cv.check_increasing('mesh r_grid', grid) self._r_grid = np.asarray(grid, dtype=float) @property @@ -1390,7 +1419,12 @@ def phi_grid(self): @phi_grid.setter def phi_grid(self, grid): cv.check_type('mesh phi_grid', grid, Iterable, Real) - self._phi_grid = np.asarray(grid, dtype=float) + cv.check_length('mesh phi_grid', grid, 2) + cv.check_increasing('mesh phi_grid', grid) + grid = np.asarray(grid, dtype=float) + if np.any((grid < 0.0) | (grid > 2*pi)): + raise ValueError("phi_grid values must be in [0, 2π].") + self._phi_grid = grid @property def z_grid(self): @@ -1399,6 +1433,8 @@ def z_grid(self): @z_grid.setter def z_grid(self, grid): cv.check_type('mesh z_grid', grid, Iterable, Real) + cv.check_length('mesh z_grid', grid, 2) + cv.check_increasing('mesh z_grid', grid) self._z_grid = np.asarray(grid, dtype=float) @property @@ -1456,9 +1492,11 @@ def __repr__(self): def get_indices_at_coords( self, coords: Sequence[float] - ) -> Tuple[int, int, int]: + ) -> tuple[int, int, int]: """Finds the index of the mesh voxel at the specified x,y,z coordinates. + .. versionadded:: 0.15.0 + Parameters ---------- coords : Sequence[float] @@ -1466,7 +1504,7 @@ def get_indices_at_coords( Returns ------- - Tuple[int, int, int] + tuple[int, int, int] The r, phi, z indices """ @@ -1514,12 +1552,11 @@ def get_indices_at_coords( return (r_index, phi_index, z_index) @classmethod - def from_hdf5(cls, group: h5py.Group): - mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) - + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): # Read and assign mesh properties mesh = cls( mesh_id=mesh_id, + name=name, r_grid = group['r_grid'][()], phi_grid = group['phi_grid'][()], z_grid = group['z_grid'][()], @@ -1532,9 +1569,9 @@ def from_hdf5(cls, group: h5py.Group): @classmethod def from_domain( cls, - domain: typing.Union['openmc.Cell', 'openmc.Region', 'openmc.Universe', 'openmc.Geometry'], + domain: 'openmc.Cell' | 'openmc.Region' | 'openmc.Universe' | 'openmc.Geometry', dimension: Sequence[int] = (10, 10, 10), - mesh_id: Optional[int] = None, + mesh_id: int | None = None, phi_grid_bounds: Sequence[float] = (0.0, 2*pi), name: str = '' ): @@ -1620,8 +1657,7 @@ def to_xml_element(self): """ - element = ET.Element("mesh") - element.set("id", str(self._id)) + element = super().to_xml_element() element.set("type", "cylindrical") subelement = ET.SubElement(element, "r_grid") @@ -1783,7 +1819,7 @@ def __init__( phi_grid: Sequence[float] = (0, 2*pi), theta_grid: Sequence[float] = (0, pi), origin: Sequence[float] = (0., 0., 0.), - mesh_id: Optional[int] = None, + mesh_id: int | None = None, name: str = '', ): super().__init__(mesh_id, name) @@ -1833,7 +1869,10 @@ def theta_grid(self, grid): cv.check_type('mesh theta_grid', grid, Iterable, Real) cv.check_length('mesh theta_grid', grid, 2) cv.check_increasing('mesh theta_grid', grid) - self._theta_grid = np.asarray(grid, dtype=float) + grid = np.asarray(grid, dtype=float) + if np.any((grid < 0.0) | (grid > pi)): + raise ValueError("theta_grid values must be in [0, π].") + self._theta_grid = grid @property def phi_grid(self): @@ -1844,7 +1883,10 @@ def phi_grid(self, grid): cv.check_type('mesh phi_grid', grid, Iterable, Real) cv.check_length('mesh phi_grid', grid, 2) cv.check_increasing('mesh phi_grid', grid) - self._phi_grid = np.asarray(grid, dtype=float) + grid = np.asarray(grid, dtype=float) + if np.any((grid < 0.0) | (grid > 2*pi)): + raise ValueError("phi_grid values must be in [0, 2π].") + self._phi_grid = grid @property def _grids(self): @@ -1893,15 +1935,14 @@ def __repr__(self): return string @classmethod - def from_hdf5(cls, group: h5py.Group): - mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) - + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): # Read and assign mesh properties mesh = cls( r_grid = group['r_grid'][()], theta_grid = group['theta_grid'][()], phi_grid = group['phi_grid'][()], mesh_id=mesh_id, + name=name ) if 'origin' in group: mesh.origin = group['origin'][()] @@ -1918,8 +1959,7 @@ def to_xml_element(self): """ - element = ET.Element("mesh") - element.set("id", str(self._id)) + element = super().to_xml_element() element.set("type", "spherical") subelement = ET.SubElement(element, "r_grid") @@ -2038,8 +2078,10 @@ class UnstructuredMesh(MeshBase): Parameters ---------- - filename : str or pathlib.Path - Location of the unstructured mesh file + filename : path-like + Location of the unstructured mesh file. Supported files for 'moab' + library are .h5 and .vtk. Supported files for 'libmesh' library are + exodus mesh files .exo. library : {'moab', 'libmesh'} Mesh library used for the unstructured mesh tally mesh_id : int @@ -2103,9 +2145,9 @@ class UnstructuredMesh(MeshBase): _LINEAR_TET = 0 _LINEAR_HEX = 1 - def __init__(self, filename: PathLike, library: str, mesh_id: Optional[int] = None, + def __init__(self, filename: PathLike, library: str, mesh_id: int | None = None, name: str = '', length_multiplier: float = 1.0, - options: Optional[str] = None): + options: str | None = None): super().__init__(mesh_id, name) self.filename = filename self._volumes = None @@ -2124,8 +2166,8 @@ def filename(self): @filename.setter def filename(self, filename): - cv.check_type('Unstructured Mesh filename', filename, (str, Path)) - self._filename = filename + cv.check_type('Unstructured Mesh filename', filename, PathLike) + self._filename = input_path(filename) @property def library(self): @@ -2137,11 +2179,11 @@ def library(self, lib: str): self._library = lib @property - def options(self) -> Optional[str]: + def options(self) -> str | None: return self._options @options.setter - def options(self, options: Optional[str]): + def options(self, options: str | None): cv.check_type('options', options, (str, type(None))) self._options = options @@ -2179,7 +2221,7 @@ def volumes(self): return self._volumes @volumes.setter - def volumes(self, volumes: typing.Iterable[Real]): + def volumes(self, volumes: Iterable[Real]): cv.check_type("Unstructured mesh volumes", volumes, Iterable, Real) self._volumes = volumes @@ -2317,8 +2359,8 @@ def write_vtk_mesh(self, **kwargs): def write_data_to_vtk( self, - filename: Optional[PathLike] = None, - datasets: Optional[dict] = None, + filename: PathLike | None = None, + datasets: dict | None = None, volume_normalization: bool = True ): """Map data to unstructured VTK mesh elements. @@ -2409,8 +2451,7 @@ def write_data_to_vtk( writer.Write() @classmethod - def from_hdf5(cls, group: h5py.Group): - mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): filename = group['filename'][()].decode() library = group['library'][()].decode() if 'options' in group.attrs: @@ -2418,7 +2459,7 @@ def from_hdf5(cls, group: h5py.Group): else: options = None - mesh = cls(filename=filename, library=library, mesh_id=mesh_id, options=options) + mesh = cls(filename=filename, library=library, mesh_id=mesh_id, name=name, options=options) mesh._has_statepoint_data = True vol_data = group['volumes'][()] mesh.volumes = np.reshape(vol_data, (vol_data.shape[0],)) @@ -2445,9 +2486,9 @@ def to_xml_element(self): """ - element = ET.Element("mesh") - element.set("id", str(self._id)) + element = super().to_xml_element() element.set("type", "unstructured") + element.set("library", self._library) if self.options is not None: element.set('options', self.options) diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index 642da83d170..b840563cffc 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -3,7 +3,7 @@ import h5py import numpy as np -from scipy.integrate import simps +import scipy.integrate from scipy.interpolate import interp1d from scipy.special import eval_legendre @@ -18,21 +18,17 @@ # Supported incoming particle MGXS angular treatment representations REPRESENTATION_ISOTROPIC = 'isotropic' REPRESENTATION_ANGLE = 'angle' -_REPRESENTATIONS = [ +_REPRESENTATIONS = { REPRESENTATION_ISOTROPIC, REPRESENTATION_ANGLE -] +} # Supported scattering angular distribution representations -_SCATTER_TYPES = [ +_SCATTER_TYPES = { SCATTER_TABULAR, SCATTER_LEGENDRE, SCATTER_HISTOGRAM -] - -# List of MGXS indexing schemes -_XS_SHAPES = ["[G][G'][Order]", "[G]", "[G']", "[G][G']", "[DG]", "[DG][G]", - "[DG][G']", "[DG][G][G']"] +} # Number of mu points for conversion between scattering formats _NMU = 257 @@ -1823,6 +1819,12 @@ def convert_scatter_format(self, target_format, target_order=None): # Reset and re-generate XSdata.xs_shapes with the new scattering format xsdata._xs_shapes = None + # scipy 1.11+ prefers 'simpson', whereas older versions use 'simps' + if hasattr(scipy.integrate, 'simpson'): + integrate = scipy.integrate.simpson + else: + integrate = scipy.integrate.simps + for i, temp in enumerate(xsdata.temperatures): orig_data = self._scatter_matrix[i] new_shape = orig_data.shape[:-1] + (xsdata.num_orders,) @@ -1860,7 +1862,7 @@ def convert_scatter_format(self, target_format, target_order=None): table_fine[..., imu] += ((l + 0.5) * eval_legendre(l, mu_fine[imu]) * orig_data[..., l]) - new_data[..., h_bin] = simps(table_fine, mu_fine) + new_data[..., h_bin] = integrate(table_fine, x=mu_fine) elif self.scatter_format == SCATTER_TABULAR: # Calculate the mu points of the current data @@ -1874,7 +1876,7 @@ def convert_scatter_format(self, target_format, target_order=None): for l in range(xsdata.num_orders): y = (interp1d(mu_self, orig_data)(mu_fine) * eval_legendre(l, mu_fine)) - new_data[..., l] = simps(y, mu_fine) + new_data[..., l] = integrate(y, x=mu_fine) elif target_format == SCATTER_TABULAR: # Simply use an interpolating function to get the new data @@ -1893,7 +1895,7 @@ def convert_scatter_format(self, target_format, target_order=None): interp = interp1d(mu_self, orig_data) for h_bin in range(xsdata.num_orders): mu_fine = np.linspace(mu[h_bin], mu[h_bin + 1], _NMU) - new_data[..., h_bin] = simps(interp(mu_fine), mu_fine) + new_data[..., h_bin] = integrate(interp(mu_fine), x=mu_fine) elif self.scatter_format == SCATTER_HISTOGRAM: # The histogram format does not have enough information to @@ -1919,7 +1921,7 @@ def convert_scatter_format(self, target_format, target_order=None): mu_fine = np.linspace(-1, 1, _NMU) for l in range(xsdata.num_orders): y = interp(mu_fine) * norm * eval_legendre(l, mu_fine) - new_data[..., l] = simps(y, mu_fine) + new_data[..., l] = integrate(y, x=mu_fine) elif target_format == SCATTER_TABULAR: # Simply use an interpolating function to get the new data @@ -1938,7 +1940,7 @@ def convert_scatter_format(self, target_format, target_order=None): for h_bin in range(xsdata.num_orders): mu_fine = np.linspace(mu[h_bin], mu[h_bin + 1], _NMU) new_data[..., h_bin] = \ - norm * simps(interp(mu_fine), mu_fine) + norm * integrate(interp(mu_fine), x=mu_fine) # Remove small values resulting from numerical precision issues new_data[..., np.abs(new_data) < 1.E-10] = 0. diff --git a/openmc/mixin.py b/openmc/mixin.py index 31c26ec7603..0bc4128b0bf 100644 --- a/openmc/mixin.py +++ b/openmc/mixin.py @@ -72,12 +72,17 @@ def id(self, uid): cls.used_ids.add(uid) self._id = uid + @classmethod + def reset_ids(cls): + """Reset counters""" + cls.used_ids.clear() + cls.next_id = 1 + def reset_auto_ids(): """Reset counters for all auto-generated IDs""" for cls in IDManagerMixin.__subclasses__(): - cls.used_ids.clear() - cls.next_id = 1 + cls.reset_ids() def reserve_ids(ids, cls=None): diff --git a/openmc/model/model.py b/openmc/model/model.py index 2160c97e6cc..402d6c0610f 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,21 +1,20 @@ from __future__ import annotations from collections.abc import Iterable from functools import lru_cache -import os from pathlib import Path from numbers import Integral from tempfile import NamedTemporaryFile import warnings -from typing import Optional, Dict import h5py import lxml.etree as ET +import numpy as np import openmc import openmc._xml as xml from openmc.dummy_comm import DummyCommunicator from openmc.executor import _process_CLI_arguments -from openmc.checkvalue import check_type, check_value, PathLike +from openmc.checkvalue import check_type, check_value from openmc.exceptions import InvalidIDError from openmc.utility_funcs import change_directory @@ -83,7 +82,7 @@ def __init__(self, geometry=None, materials=None, settings=None, self.plots = plots @property - def geometry(self) -> Optional[openmc.Geometry]: + def geometry(self) -> openmc.Geometry | None: return self._geometry @geometry.setter @@ -92,7 +91,7 @@ def geometry(self, geometry): self._geometry = geometry @property - def materials(self) -> Optional[openmc.Materials]: + def materials(self) -> openmc.Materials | None: return self._materials @materials.setter @@ -106,7 +105,7 @@ def materials(self, materials): self._materials.append(mat) @property - def settings(self) -> Optional[openmc.Settings]: + def settings(self) -> openmc.Settings | None: return self._settings @settings.setter @@ -115,7 +114,7 @@ def settings(self, settings): self._settings = settings @property - def tallies(self) -> Optional[openmc.Tallies]: + def tallies(self) -> openmc.Tallies | None: return self._tallies @tallies.setter @@ -129,7 +128,7 @@ def tallies(self, tallies): self._tallies.append(tally) @property - def plots(self) -> Optional[openmc.Plots]: + def plots(self) -> openmc.Plots | None: return self._plots @plots.setter @@ -169,7 +168,7 @@ def _cells_by_id(self) -> dict: @property @lru_cache(maxsize=None) - def _cells_by_name(self) -> Dict[int, openmc.Cell]: + def _cells_by_name(self) -> dict[int, openmc.Cell]: # Get the names maps, but since names are not unique, store a set for # each name key. In this way when the user requests a change by a name, # the change will be applied to all of the same name. @@ -182,7 +181,7 @@ def _cells_by_name(self) -> Dict[int, openmc.Cell]: @property @lru_cache(maxsize=None) - def _materials_by_name(self) -> Dict[int, openmc.Material]: + def _materials_by_name(self) -> dict[int, openmc.Material]: if self.materials is None: mats = self.geometry.get_all_materials().values() else: @@ -324,6 +323,28 @@ def init_lib(self, threads=None, geometry_debug=False, restart_file=None, # communicator openmc.lib.init(args=args, intracomm=intracomm, output=output) + def sync_dagmc_universes(self): + """Synchronize all DAGMC universes in the current geometry. + + This method iterates over all DAGMC universes in the geometry and + synchronizes their cells with the current material assignments. Requires + that the model has been initialized via :meth:`Model.init_lib`. + + .. versionadded:: 0.15.1 + + """ + if self.is_initialized: + if self.materials: + materials = self.materials + else: + materials = list(self.geometry.get_all_materials().values()) + for univ in self.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + univ.sync_dagmc_cells(materials) + else: + raise ValueError("The model must be initialized before calling " + "this method") + def finalize_lib(self): """Finalize simulation and free memory allocated for the C API @@ -719,7 +740,8 @@ def run(self, particles=None, threads=None, geometry_debug=False, def calculate_volumes(self, threads=None, output=True, cwd='.', openmc_exec='openmc', mpi_args=None, - apply_volumes=True): + apply_volumes=True, export_model_xml=True, + **export_kwargs): """Runs an OpenMC stochastic volume calculation and, if requested, applies volumes to the model @@ -748,6 +770,13 @@ def calculate_volumes(self, threads=None, output=True, cwd='.', apply_volumes : bool, optional Whether apply the volume calculation results from this calculation to the model. Defaults to applying the volumes. + export_model_xml : bool, optional + Exports a single model.xml file rather than separate files. Defaults + to True. + **export_kwargs + Keyword arguments passed to either :meth:`Model.export_to_model_xml` + or :meth:`Model.export_to_xml`. + """ if len(self.settings.volume_calculations) == 0: @@ -769,10 +798,15 @@ def calculate_volumes(self, threads=None, output=True, cwd='.', openmc.lib.calculate_volumes(output) else: - self.export_to_xml() - openmc.calculate_volumes(threads=threads, output=output, - openmc_exec=openmc_exec, - mpi_args=mpi_args) + if export_model_xml: + self.export_to_model_xml(**export_kwargs) + else: + self.export_to_xml(**export_kwargs) + path_input = export_kwargs.get("path", None) + openmc.calculate_volumes( + threads=threads, output=output, openmc_exec=openmc_exec, + mpi_args=mpi_args, path_input=path_input + ) # Now we apply the volumes if apply_volumes: @@ -794,7 +828,123 @@ def calculate_volumes(self, threads=None, output=True, cwd='.', openmc.lib.materials[domain_id].volume = \ vol_calc.volumes[domain_id].n - def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc'): + def plot( + self, + n_samples: int | None = None, + plane_tolerance: float = 1., + source_kwargs: dict | None = None, + **kwargs, + ): + """Display a slice plot of the geometry. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + n_samples : int, optional + The number of source particles to sample and add to plot. Defaults + to None which doesn't plot any particles on the plot. + plane_tolerance: float + When plotting a plane the source locations within the plane +/- + the plane_tolerance will be included and those outside of the + plane_tolerance will not be shown + source_kwargs : dict, optional + Keyword arguments passed to :func:`matplotlib.pyplot.scatter`. + **kwargs + Keyword arguments passed to :func:`openmc.Universe.plot` + + Returns + ------- + matplotlib.axes.Axes + Axes containing resulting image + """ + + check_type('n_samples', n_samples, int | None) + check_type('plane_tolerance', plane_tolerance, float) + if source_kwargs is None: + source_kwargs = {} + source_kwargs.setdefault('marker', 'x') + + ax = self.geometry.plot(**kwargs) + if n_samples: + # Sample external source particles + particles = self.sample_external_source(n_samples) + + # Determine plotting parameters and bounding box of geometry + bbox = self.geometry.bounding_box + origin = kwargs.get('origin', None) + basis = kwargs.get('basis', 'xy') + indices = {'xy': (0, 1, 2), 'xz': (0, 2, 1), 'yz': (1, 2, 0)}[basis] + + # Infer origin if not provided + if np.isinf(bbox.extent[basis]).any(): + if origin is None: + origin = (0, 0, 0) + else: + if origin is None: + # if nan values in the bbox.center they get replaced with 0.0 + # this happens when the bounding_box contains inf values + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + origin = np.nan_to_num(bbox.center) + + slice_index = indices[2] + slice_value = origin[slice_index] + + xs = [] + ys = [] + tol = plane_tolerance + for particle in particles: + if (slice_value - tol < particle.r[slice_index] < slice_value + tol): + xs.append(particle.r[indices[0]]) + ys.append(particle.r[indices[1]]) + ax.scatter(xs, ys, **source_kwargs) + return ax + + def sample_external_source( + self, + n_samples: int = 1000, + prn_seed: int | None = None, + **init_kwargs + ) -> openmc.ParticleList: + """Sample external source and return source particles. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + n_samples : int + Number of samples + prn_seed : int + Pseudorandom number generator (PRNG) seed; if None, one will be + generated randomly. + **init_kwargs + Keyword arguments passed to :func:`openmc.lib.init` + + Returns + ------- + openmc.ParticleList + List of samples source particles + """ + import openmc.lib + + # Silence output by default. Also set arguments to start in volume + # calculation mode to avoid loading cross sections + init_kwargs.setdefault('output', False) + init_kwargs.setdefault('args', ['-c']) + + with change_directory(tmpdir=True): + # Export model within temporary directory + self.export_to_model_xml() + + # Sample external source sites + with openmc.lib.run_in_memory(**init_kwargs): + return openmc.lib.sample_external_source( + n_samples=n_samples, prn_seed=prn_seed + ) + + def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc', + export_model_xml=True, **export_kwargs): """Creates plot images as specified by the Model.plots attribute .. versionadded:: 0.13.0 @@ -809,6 +959,12 @@ def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc'): openmc_exec : str, optional Path to OpenMC executable. Defaults to 'openmc'. This only applies to the case when not using the C API. + export_model_xml : bool, optional + Exports a single model.xml file rather than separate files. Defaults + to True. + **export_kwargs + Keyword arguments passed to either :meth:`Model.export_to_model_xml` + or :meth:`Model.export_to_xml`. """ @@ -822,8 +978,13 @@ def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc'): # Compute the volumes openmc.lib.plot_geometry(output) else: - self.export_to_xml() - openmc.plot_geometry(output=output, openmc_exec=openmc_exec) + if export_model_xml: + self.export_to_model_xml(**export_kwargs) + else: + self.export_to_xml(**export_kwargs) + path_input = export_kwargs.get("path", None) + openmc.plot_geometry(output=output, openmc_exec=openmc_exec, + path_input=path_input) def _change_py_lib_attribs(self, names_or_ids, value, obj_type, attrib_name, density_units='atom/b-cm'): @@ -1014,51 +1175,86 @@ def update_material_volumes(self, names_or_ids, volume): self._change_py_lib_attribs(names_or_ids, volume, 'material', 'volume') - def differentiate_depletable_mats(self, diff_volume_method: str): + def differentiate_depletable_mats(self, diff_volume_method: str = None): """Assign distribmats for each depletable material .. versionadded:: 0.14.0 + .. versionchanged:: 0.15.1 + diff_volume_method default is None, do not set volumes on the new + material ovjects. Is now a convenience method for + differentiate_mats(diff_volume_method, depletable_only=True) + Parameters ---------- diff_volume_method : str Specifies how the volumes of the new materials should be found. - Default is to 'divide equally' which divides the original material - volume equally between the new materials, 'match cell' sets the - volume of the material to volume of the cell they fill. + - None: Do not assign volumes to the new materials (Default) + - 'divide_equally': Divide the original material volume equally between the new materials + - 'match cell': Set the volume of the material to the volume of the cell they fill """ + self.differentiate_mats(diff_volume_method, depletable_only=True) + + def differentiate_mats(self, diff_volume_method: str = None, depletable_only: bool = True): + """Assign distribmats for each material + + .. versionadded:: 0.15.1 + + Parameters + ---------- + diff_volume_method : str + Specifies how the volumes of the new materials should be found. + - None: Do not assign volumes to the new materials (Default) + - 'divide_equally': Divide the original material volume equally between the new materials + - 'match cell': Set the volume of the material to the volume of the cell they fill + depletable_only : bool + Default is True, only depletable materials will be differentiated. If False, all materials will be + differentiated. + """ + check_value('volume differentiation method', diff_volume_method, ("divide equally", "match cell", None)) + # Count the number of instances for each cell and material self.geometry.determine_paths(instances_only=True) - # Extract all depletable materials which have multiple instances - distribmats = set( - [mat for mat in self.materials - if mat.depletable and mat.num_instances > 1]) - - if diff_volume_method == 'divide equally': - for mat in distribmats: - if mat.volume is None: - raise RuntimeError("Volume not specified for depletable " - f"material with ID={mat.id}.") - mat.volume /= mat.num_instances - - if distribmats: - # Assign distribmats to cells - for cell in self.geometry.get_all_material_cells().values(): - if cell.fill in distribmats: - mat = cell.fill - if diff_volume_method == 'divide equally': - cell.fill = [mat.clone() for _ in range(cell.num_instances)] - elif diff_volume_method == 'match cell': - for _ in range(cell.num_instances): - cell.fill = mat.clone() + # Find all or depletable_only materials which have multiple instance + distribmats = set() + for mat in self.materials: + # Differentiate all materials with multiple instances + diff_mat = mat.num_instances > 1 + # If depletable_only is True, differentiate only depletable materials + if depletable_only: + diff_mat = diff_mat and mat.depletable + if diff_mat: + # Assign volumes to the materials according to requirements + if diff_volume_method == "divide equally": + if mat.volume is None: + raise RuntimeError( + "Volume not specified for " + f"material with ID={mat.id}.") + else: + mat.volume /= mat.num_instances + elif diff_volume_method == "match cell": + for cell in self.geometry.get_all_material_cells().values(): + if cell.fill == mat: if not cell.volume: raise ValueError( f"Volume of cell ID={cell.id} not specified. " "Set volumes of cells prior to using " - "diff_volume_method='match cell'." - ) - cell.fill.volume = cell.volume + "diff_volume_method='match cell'.") + distribmats.add(mat) + + if not distribmats: + return + + # Assign distribmats to cells + for cell in self.geometry.get_all_material_cells().values(): + if cell.fill in distribmats: + mat = cell.fill + if diff_volume_method != 'match cell': + cell.fill = [mat.clone() for _ in range(cell.num_instances)] + elif diff_volume_method == 'match cell': + cell.fill = mat.clone() + cell.fill.volume = cell.volume if self.materials is not None: self.materials = openmc.Materials( diff --git a/openmc/model/surface_composite.py b/openmc/model/surface_composite.py index 1fa4234fbf7..c7655694d3c 100644 --- a/openmc/model/surface_composite.py +++ b/openmc/model/surface_composite.py @@ -1,12 +1,12 @@ +from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from copy import copy from functools import partial from math import sqrt, pi, sin, cos, isclose from numbers import Real import warnings import operator -from typing import Sequence import numpy as np from scipy.spatial import ConvexHull, Delaunay @@ -27,7 +27,7 @@ def translate(self, vector, inplace=False): return surf def rotate(self, rotation, pivot=(0., 0., 0.), order='xyz', inplace=False): - surf = copy(self) + surf = self if inplace else copy(self) for name in self._surface_names: s = getattr(surf, name) setattr(surf, name, s.rotate(rotation, pivot, order, inplace)) @@ -41,9 +41,11 @@ def boundary_type(self): def boundary_type(self, boundary_type): # Set boundary type on underlying surfaces, but not for ambiguity plane # on one-sided cones + classes = (XConeOneSided, YConeOneSided, ZConeOneSided, Vessel) for name in self._surface_names: - if name != 'plane': - getattr(self, name).boundary_type = boundary_type + if isinstance(self, classes) and name.startswith('plane'): + continue + getattr(self, name).boundary_type = boundary_type def __repr__(self): return f"<{type(self).__name__} at 0x{id(self):x}>" @@ -90,8 +92,8 @@ class CylinderSector(CompositeSurface): counterclockwise direction with respect to the first basis axis (+y, +z, or +x). Must be greater than :attr:`theta1`. center : iterable of float - Coordinate for central axes of cylinders in the (y, z), (x, z), or (x, y) - basis. Defaults to (0,0). + Coordinate for central axes of cylinders in the (y, z), (x, z), or (x, y) + basis. Defaults to (0,0). axis : {'x', 'y', 'z'} Central axis of the cylinders defining the inner and outer surfaces of the sector. Defaults to 'z'. @@ -119,7 +121,7 @@ def __init__(self, r2, theta1, theta2, - center=(0.,0.), + center=(0., 0.), axis='z', **kwargs): @@ -233,7 +235,7 @@ class IsogonalOctagon(CompositeSurface): r"""Infinite isogonal octagon composite surface An isogonal octagon is composed of eight planar surfaces. The prism is - parallel to the x, y, or z axis. The remaining two axes (y and z, z and x, + parallel to the x, y, or z axis. The remaining two axes (y and z, x and z, or x and y) serve as a basis for constructing the surfaces. Two surfaces are parallel to the first basis axis, two surfaces are parallel to the second basis axis, and the remaining four surfaces intersect both @@ -241,7 +243,7 @@ class IsogonalOctagon(CompositeSurface): This class acts as a proper surface, meaning that unary `+` and `-` operators applied to it will produce a half-space. The negative side is - defined to be the region inside of the octogonal prism. + defined to be the region inside of the octagonal prism. .. versionadded:: 0.13.1 @@ -249,7 +251,7 @@ class IsogonalOctagon(CompositeSurface): ---------- center : iterable of float Coordinate for the central axis of the octagon in the - (y, z), (z, x), or (x, y) basis. + (y, z), (x, z), or (x, y) basis depending on the axis parameter. r1 : float Half-width of octagon across its basis axis-parallel sides in units of cm. Must be less than :math:`r_2\sqrt{2}`. @@ -290,7 +292,7 @@ class IsogonalOctagon(CompositeSurface): def __init__(self, center, r1, r2, axis='z', **kwargs): c1, c2 = center - # Coords for axis-perpendicular planes + # Coordinates for axis-perpendicular planes cright = c1 + r1 cleft = c1 - r1 @@ -307,7 +309,7 @@ def __init__(self, center, r1, r2, axis='z', **kwargs): L_basis_ax = (r2 * sqrt(2) - r1) - # Coords for quadrant planes + # Coordinates for quadrant planes p1_ur = np.array([L_basis_ax, r1, 0.]) p2_ur = np.array([r1, L_basis_ax, 0.]) p3_ur = np.array([r1, L_basis_ax, 1.]) @@ -335,17 +337,18 @@ def __init__(self, center, r1, r2, axis='z', **kwargs): self.right = openmc.XPlane(cright, **kwargs) self.left = openmc.XPlane(cleft, **kwargs) elif axis == 'y': - coord_map = [1, 2, 0] - self.top = openmc.XPlane(ctop, **kwargs) - self.bottom = openmc.XPlane(cbottom, **kwargs) - self.right = openmc.ZPlane(cright, **kwargs) - self.left = openmc.ZPlane(cleft, **kwargs) + coord_map = [0, 2, 1] + self.top = openmc.ZPlane(ctop, **kwargs) + self.bottom = openmc.ZPlane(cbottom, **kwargs) + self.right = openmc.XPlane(cright, **kwargs) + self.left = openmc.XPlane(cleft, **kwargs) elif axis == 'x': coord_map = [2, 0, 1] self.top = openmc.ZPlane(ctop, **kwargs) self.bottom = openmc.ZPlane(cbottom, **kwargs) self.right = openmc.YPlane(cright, **kwargs) self.left = openmc.YPlane(cleft, **kwargs) + self.axis = axis # Put our coordinates in (x,y,z) order and add the offset for p in points: @@ -363,14 +366,27 @@ def __init__(self, center, r1, r2, axis='z', **kwargs): **kwargs) def __neg__(self): - return -self.top & +self.bottom & -self.right & +self.left & \ - +self.upper_right & +self.lower_right & -self.lower_left & \ - -self.upper_left + if self.axis == 'y': + region = -self.top & +self.bottom & -self.right & +self.left & \ + -self.upper_right & -self.lower_right & +self.lower_left & \ + +self.upper_left + else: + region = -self.top & +self.bottom & -self.right & +self.left & \ + +self.upper_right & +self.lower_right & -self.lower_left & \ + -self.upper_left + + return region def __pos__(self): - return +self.top | -self.bottom | +self.right | -self.left | \ - -self.upper_right | -self.lower_right | +self.lower_left | \ - +self.upper_left + if self.axis == 'y': + region = +self.top | -self.bottom | +self.right | -self.left | \ + +self.upper_right | +self.lower_right | -self.lower_left | \ + -self.upper_left + else: + region = +self.top | -self.bottom | +self.right | -self.left | \ + -self.upper_right | -self.lower_right | +self.lower_left | \ + +self.upper_left + return region class RightCircularCylinder(CompositeSurface): @@ -632,6 +648,86 @@ def __pos__(self): return +self.xmax | -self.xmin | +self.ymax | -self.ymin | +self.zmax | -self.zmin +class OrthogonalBox(CompositeSurface): + """Arbitrarily oriented orthogonal box + + This composite surface is composed of four or six planar surfaces that form + an arbitrarily oriented orthogonal box when combined. + + Parameters + ---------- + v : iterable of float + (x,y,z) coordinates of a corner of the box + a1 : iterable of float + Vector of first side starting from ``v`` + a2 : iterable of float + Vector of second side starting from ``v`` + a3 : iterable of float, optional + Vector of third side starting from ``v``. When not specified, it is + assumed that the box will be infinite along the vector normal to the + plane specified by ``a1`` and ``a2``. + **kwargs + Keyword arguments passed to underlying plane classes + + Attributes + ---------- + ax1_min, ax1_max : openmc.Plane + Planes representing minimum and maximum along first axis + ax2_min, ax2_max : openmc.Plane + Planes representing minimum and maximum along second axis + ax3_min, ax3_max : openmc.Plane + Planes representing minimum and maximum along third axis + + """ + _surface_names = ('ax1_min', 'ax1_max', 'ax2_min', 'ax2_max', 'ax3_min', 'ax3_max') + + def __init__(self, v, a1, a2, a3=None, **kwargs): + v = np.array(v) + a1 = np.array(a1) + a2 = np.array(a2) + if has_a3 := a3 is not None: + a3 = np.array(a3) + else: + a3 = np.cross(a1, a2) # normal to plane specified by a1 and a2 + + # Generate corners of box + p1 = v + p2 = v + a1 + p3 = v + a2 + p4 = v + a3 + p5 = v + a1 + a2 + p6 = v + a2 + a3 + p7 = v + a1 + a3 + + # Generate 6 planes of box + self.ax1_min = openmc.Plane.from_points(p1, p3, p4, **kwargs) + self.ax1_max = openmc.Plane.from_points(p2, p5, p7, **kwargs) + self.ax2_min = openmc.Plane.from_points(p1, p4, p2, **kwargs) + self.ax2_max = openmc.Plane.from_points(p3, p6, p5, **kwargs) + if has_a3: + self.ax3_min = openmc.Plane.from_points(p1, p2, p3, **kwargs) + self.ax3_max = openmc.Plane.from_points(p4, p7, p6, **kwargs) + + # Make sure a point inside the box produces the correct senses. If not, + # flip the plane coefficients so it does. + mid_point = v + (a1 + a2 + a3)/2 + nums = (1, 2, 3) if has_a3 else (1, 2) + for num in nums: + min_surf = getattr(self, f'ax{num}_min') + max_surf = getattr(self, f'ax{num}_max') + if mid_point in -min_surf: + min_surf.flip_normal() + if mid_point in +max_surf: + max_surf.flip_normal() + + def __neg__(self): + region = (+self.ax1_min & -self.ax1_max & + +self.ax2_min & -self.ax2_max) + if hasattr(self, 'ax3_min'): + region &= (+self.ax3_min & -self.ax3_max) + return region + + class XConeOneSided(CompositeSurface): """One-sided cone parallel the x-axis @@ -685,12 +781,6 @@ def __init__(self, x0=0., y0=0., z0=0., r2=1., up=True, **kwargs): def __neg__(self): return -self.cone & (+self.plane if self.up else -self.plane) - def __pos__(self): - if self.up: - return (+self.cone & +self.plane) | -self.plane - else: - return (+self.cone & -self.plane) | +self.plane - class YConeOneSided(CompositeSurface): """One-sided cone parallel the y-axis @@ -743,7 +833,6 @@ def __init__(self, x0=0., y0=0., z0=0., r2=1., up=True, **kwargs): self.up = up __neg__ = XConeOneSided.__neg__ - __pos__ = XConeOneSided.__pos__ class ZConeOneSided(CompositeSurface): @@ -797,7 +886,6 @@ def __init__(self, x0=0., y0=0., z0=0., r2=1., up=True, **kwargs): self.up = up __neg__ = XConeOneSided.__neg__ - __pos__ = XConeOneSided.__pos__ class Polygon(CompositeSurface): @@ -958,19 +1046,19 @@ def _validate_points(self, points): # Check if polygon is self-intersecting by comparing edges pairwise n = len(points) for i in range(n): - p0 = points[i, :] - p1 = points[(i + 1) % n, :] + p0 = np.append(points[i, :], 0) + p1 = np.append(points[(i + 1) % n, :], 0) for j in range(i + 1, n): - p2 = points[j, :] - p3 = points[(j + 1) % n, :] + p2 = np.append(points[j, :], 0) + p3 = np.append(points[(j + 1) % n, :], 0) # Compute orientation of p0 wrt p2->p3 line segment - cp0 = np.cross(p3-p0, p2-p0) + cp0 = np.cross(p3-p0, p2-p0)[-1] # Compute orientation of p1 wrt p2->p3 line segment - cp1 = np.cross(p3-p1, p2-p1) + cp1 = np.cross(p3-p1, p2-p1)[-1] # Compute orientation of p2 wrt p0->p1 line segment - cp2 = np.cross(p1-p2, p0-p2) + cp2 = np.cross(p1-p2, p0-p2)[-1] # Compute orientation of p3 wrt p0->p1 line segment - cp3 = np.cross(p1-p3, p0-p3) + cp3 = np.cross(p1-p3, p0-p3)[-1] # Group cross products in an array and find out how many are 0 cross_products = np.array([[cp0, cp1], [cp2, cp3]]) @@ -1164,11 +1252,11 @@ def _get_convex_hull_surfs(self, qhull): else: op = operator.neg if basis == 'xy': - surf = openmc.Plane(a=dx, b=dy, d=-c) + surf = openmc.Plane(a=dx, b=dy, c=0.0, d=-c) elif basis == 'yz': - surf = openmc.Plane(b=dx, c=dy, d=-c) + surf = openmc.Plane(a=0.0, b=dx, c=dy, d=-c) elif basis == 'xz': - surf = openmc.Plane(a=dx, c=dy, d=-c) + surf = openmc.Plane(a=dx, b=0.0, c=dy, d=-c) else: y0 = -c/dy r2 = dy**2 / dx**2 @@ -1231,25 +1319,43 @@ def _decompose_polygon_into_convex_sets(self): surfsets.append(surf_ops) return surfsets - def offset(self, distance): + def offset(self, distance: float | Sequence[float] | np.ndarray) -> Polygon: """Offset this polygon by a set distance Parameters ---------- - distance : float + distance : float or sequence of float or np.ndarray The distance to offset the polygon by. Positive is outward - (expanding) and negative is inward (shrinking). + (expanding) and negative is inward (shrinking). If a float is + provided, the same offset is applied to all vertices. If a list or + tuple is provided, each vertex gets a different offset. If an + iterable or numpy array is provided, each vertex gets a different + offset. Returns ------- offset_polygon : openmc.model.Polygon """ + + if isinstance(distance, float): + distance = np.full(len(self.points), distance) + elif isinstance(distance, Sequence): + distance = np.array(distance) + elif not isinstance(distance, np.ndarray): + raise TypeError("Distance must be a float or sequence of float.") + + if len(distance) != len(self.points): + raise ValueError( + f"Length of distance {len(distance)} array must " + f"match number of polygon points {len(self.points)}" + ) + normals = np.insert(self._normals, 0, self._normals[-1, :], axis=0) cos2theta = np.sum(normals[1:, :]*normals[:-1, :], axis=-1, keepdims=True) costheta = np.cos(np.arccos(cos2theta) / 2) nvec = (normals[1:, :] + normals[:-1, :]) unit_nvec = nvec / np.linalg.norm(nvec, axis=-1, keepdims=True) - disp_vec = distance / costheta * unit_nvec + disp_vec = distance[:, np.newaxis] / costheta * unit_nvec return type(self)(self.points + disp_vec, basis=self.basis) @@ -1632,3 +1738,216 @@ def __neg__(self) -> openmc.Region: prism &= ~corners return prism + + +def _rotation_matrix(v1, v2): + """Compute rotation matrix that would rotate v1 into v2. + + Parameters + ---------- + v1 : numpy.ndarray + Unrotated vector + v2 : numpy.ndarray + Rotated vector + + Returns + ------- + 3x3 rotation matrix + + """ + # Normalize vectors and compute cosine + u1 = v1 / np.linalg.norm(v1) + u2 = v2 / np.linalg.norm(v2) + cos_angle = np.dot(u1, u2) + + I = np.identity(3) + + # Handle special case where vectors are parallel or anti-parallel + if isclose(abs(cos_angle), 1.0, rel_tol=1e-8): + return np.sign(cos_angle)*I + else: + # Calculate rotation angle + sin_angle = np.sqrt(1 - cos_angle*cos_angle) + + # Calculate axis of rotation + axis = np.cross(u1, u2) + axis /= np.linalg.norm(axis) + + # Create cross-product matrix K + kx, ky, kz = axis + K = np.array([ + [0.0, -kz, ky], + [kz, 0.0, -kx], + [-ky, kx, 0.0] + ]) + + # Create rotation matrix using Rodrigues' rotation formula + return I + K * sin_angle + (K @ K) * (1 - cos_angle) + + +class ConicalFrustum(CompositeSurface): + """Conical frustum. + + A conical frustum, also known as a right truncated cone, is a cone that is + truncated by two parallel planes that are perpendicular to the axis of the + cone. The lower and upper base of the conical frustum are circular faces. + This surface is equivalent to the TRC macrobody in MCNP. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + center_base : iterable of float + Cartesian coordinates of the center of the bottom planar face. + axis : iterable of float + Vector from the center of the bottom planar face to the center of the + top planar face that defines the axis of the cone. The length of this + vector is the height of the conical frustum. + r1 : float + Radius of the lower cone base + r2 : float + Radius of the upper cone base + **kwargs + Keyword arguments passed to underlying plane classes + + Attributes + ---------- + cone : openmc.Cone + Cone surface + plane_bottom : openmc.Plane + Plane surface defining the bottom of the frustum + plane_top : openmc.Plane + Plane surface defining the top of the frustum + + """ + _surface_names = ('cone', 'plane_bottom', 'plane_top') + + def __init__(self, center_base: Sequence[float], axis: Sequence[float], + r1: float, r2: float, **kwargs): + center_base = np.array(center_base) + axis = np.array(axis) + + # Determine length of axis height vector + h = np.linalg.norm(axis) + + # To create the frustum oriented with the correct axis, first we will + # create a cone along the z axis and then rotate it according to the + # given axis. Thus, we first need to determine the apex using the z axis + # as a reference. + x0, y0, z0 = center_base + if r1 != r2: + apex = z0 + r1*h/(r1 - r2) + r_sq = ((r1 - r2)/h)**2 + cone = openmc.ZCone(x0, y0, apex, r2=r_sq, **kwargs) + else: + # In the degenerate case r1 == r2, the cone becomes a cylinder + cone = openmc.ZCylinder(x0, y0, r1, **kwargs) + + # Create the parallel planes + plane_bottom = openmc.ZPlane(z0, **kwargs) + plane_top = openmc.ZPlane(z0 + h, **kwargs) + + # Determine rotation matrix corresponding to specified axis + u = np.array([0., 0., 1.]) + rotation = _rotation_matrix(u, axis) + + # Rotate the surfaces + self.cone = cone.rotate(rotation, pivot=center_base) + self.plane_bottom = plane_bottom.rotate(rotation, pivot=center_base) + self.plane_top = plane_top.rotate(rotation, pivot=center_base) + + def __neg__(self) -> openmc.Region: + return +self.plane_bottom & -self.plane_top & -self.cone + + +class Vessel(CompositeSurface): + """Vessel composed of cylinder with semi-ellipsoid top and bottom. + + This composite surface is represented by a finite cylinder with ellipsoidal + top and bottom surfaces. This surface is equivalent to the 'vesesl' surface + in Serpent. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + r : float + Radius of vessel. + p1 : float + Minimum coordinate for cylindrical part of vessel. + p2 : float + Maximum coordinate for cylindrical part of vessel. + h1 : float + Height of bottom ellipsoidal part of vessel. + h2 : float + Height of top ellipsoidal part of vessel. + center : 2-tuple of float + Coordinate for central axis of the cylinder in the (y, z), (x, z), or + (x, y) basis. Defaults to (0,0). + axis : {'x', 'y', 'z'} + Central axis of the cylinder. + + """ + + _surface_names = ('cyl', 'plane_bottom', 'plane_top', 'bottom', 'top') + + def __init__(self, r: float, p1: float, p2: float, h1: float, h2: float, + center: Sequence[float] = (0., 0.), axis: str = 'z', **kwargs): + if p1 >= p2: + raise ValueError('p1 must be less than p2') + check_value('axis', axis, {'x', 'y', 'z'}) + + c1, c2 = center + cyl_class = getattr(openmc, f'{axis.upper()}Cylinder') + plane_class = getattr(openmc, f'{axis.upper()}Plane') + self.cyl = cyl_class(c1, c2, r, **kwargs) + self.plane_bottom = plane_class(p1) + self.plane_top = plane_class(p2) + + # General equation for an ellipsoid: + # (x-x₀)²/r² + (y-y₀)²/r² + (z-z₀)²/h² = 1 + # (x-x₀)² + (y-y₀)² + (z-z₀)²s² = r² + # Let s = r/h: + # (x² - 2x₀x + x₀²) + (y² - 2y₀y + y₀²) + (z² - 2z₀z + z₀²)s² = r² + # x² + y² + s²z² - 2x₀x - 2y₀y - 2s²z₀z + (x₀² + y₀² + z₀²s² - r²) = 0 + + sb = (r/h1) + st = (r/h2) + kwargs['a'] = kwargs['b'] = kwargs['c'] = 1.0 + kwargs_bottom = kwargs + kwargs_top = kwargs.copy() + + sb2 = sb*sb + st2 = st*st + kwargs_bottom['k'] = c1*c1 + c2*c2 + p1*p1*sb2 - r*r + kwargs_top['k'] = c1*c1 + c2*c2 + p2*p2*st2 - r*r + + if axis == 'x': + kwargs_bottom['a'] *= sb2 + kwargs_top['a'] *= st2 + kwargs_bottom['g'] = -2*p1*sb2 + kwargs_top['g'] = -2*p2*st2 + kwargs_top['h'] = kwargs_bottom['h'] = -2*c1 + kwargs_top['j'] = kwargs_bottom['j'] = -2*c2 + elif axis == 'y': + kwargs_bottom['b'] *= sb2 + kwargs_top['b'] *= st2 + kwargs_top['g'] = kwargs_bottom['g'] = -2*c1 + kwargs_bottom['h'] = -2*p1*sb2 + kwargs_top['h'] = -2*p2*st2 + kwargs_top['j'] = kwargs_bottom['j'] = -2*c2 + elif axis == 'z': + kwargs_bottom['c'] *= sb2 + kwargs_top['c'] *= st2 + kwargs_top['g'] = kwargs_bottom['g'] = -2*c1 + kwargs_top['h'] = kwargs_bottom['h'] = -2*c2 + kwargs_bottom['j'] = -2*p1*sb2 + kwargs_top['j'] = -2*p2*st2 + + self.bottom = openmc.Quadric(**kwargs_bottom) + self.top = openmc.Quadric(**kwargs_top) + + def __neg__(self): + return ((-self.cyl & +self.plane_bottom & -self.plane_top) | + (-self.bottom & -self.plane_bottom) | + (-self.top & +self.plane_top)) diff --git a/openmc/plots.py b/openmc/plots.py index df4a7663310..7532d9d5cb1 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -1,7 +1,6 @@ from collections.abc import Iterable, Mapping from numbers import Integral, Real from pathlib import Path -from typing import Optional import h5py import lxml.etree as ET @@ -14,7 +13,7 @@ from ._xml import clean_indentation, get_elem_tuple, reorder_attributes, get_text from .mixin import IDManagerMixin -_BASES = ['xy', 'xz', 'yz'] +_BASES = {'xy', 'xz', 'yz'} _SVG_COLORS = { 'aliceblue': (240, 248, 255), @@ -171,8 +170,14 @@ def _get_plot_image(plot, cwd): from IPython.display import Image # Make sure .png file was created - stem = plot.filename if plot.filename is not None else f'plot_{plot.id}' - png_file = Path(cwd) / f'{stem}.png' + png_filename = plot.filename if plot.filename is not None else f'plot_{plot.id}' + + # Add file extension if not already present. The C++ code added it + # automatically if it wasn't present. + if Path(png_filename).suffix != ".png": + png_filename += ".png" + + png_file = Path(cwd) / png_filename if not png_file.exists(): raise FileNotFoundError( f"Could not find .png image for plot {plot.id}. Your version of " @@ -630,7 +635,7 @@ def meshlines(self, meshlines): cv.check_type('plot meshlines', meshlines, dict) if 'type' not in meshlines: msg = f'Unable to set the meshlines to "{meshlines}" which ' \ - 'does not have a "type" key' + 'does not have a "type" key' raise ValueError(msg) elif meshlines['type'] not in ['tally', 'entropy', 'ufs', 'cmfd']: @@ -936,7 +941,7 @@ def to_ipython_image(self, openmc_exec='openmc', cwd='.'): # Return produced image return _get_plot_image(self, cwd) - def to_vtk(self, output: Optional[PathLike] = None, + def to_vtk(self, output: PathLike | None = None, openmc_exec: str = 'openmc', cwd: str = '.'): """Render plot as an voxel image @@ -968,8 +973,13 @@ def to_vtk(self, output: Optional[PathLike] = None, # Run OpenMC in geometry plotting mode and produces a h5 file openmc.plot_geometry(False, openmc_exec, cwd) - stem = self.filename if self.filename is not None else f'plot_{self.id}' - h5_voxel_file = Path(cwd) / f'{stem}.h5' + h5_voxel_filename = self.filename if self.filename is not None else f'plot_{self.id}' + + # Add file extension if not already present + if Path(h5_voxel_filename).suffix != ".h5": + h5_voxel_filename += ".h5" + + h5_voxel_file = Path(cwd) / h5_voxel_filename if output is None: output = h5_voxel_file.with_suffix('.vti') diff --git a/openmc/plotter.py b/openmc/plotter.py index 97ca5f92975..85c4963a76f 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -1,5 +1,7 @@ +from __future__ import annotations from itertools import chain from numbers import Integral, Real +from typing import Dict, Iterable, List import numpy as np @@ -7,15 +9,15 @@ import openmc.data # Supported keywords for continuous-energy cross section plotting -PLOT_TYPES = ['total', 'scatter', 'elastic', 'inelastic', 'fission', +PLOT_TYPES = {'total', 'scatter', 'elastic', 'inelastic', 'fission', 'absorption', 'capture', 'nu-fission', 'nu-scatter', 'unity', - 'slowing-down power', 'damage'] + 'slowing-down power', 'damage'} # Supported keywords for multi-group cross section plotting -PLOT_TYPES_MGXS = ['total', 'absorption', 'scatter', 'fission', +PLOT_TYPES_MGXS = {'total', 'absorption', 'scatter', 'fission', 'kappa-fission', 'nu-fission', 'prompt-nu-fission', 'deleyed-nu-fission', 'chi', 'chi-prompt', 'chi-delayed', - 'inverse-velocity', 'beta', 'decay-rate', 'unity'] + 'inverse-velocity', 'beta', 'decay-rate', 'unity'} # Create a dictionary which can be used to convert PLOT_TYPES_MGXS to the # openmc.XSdata attribute name needed to access the data _PLOT_MGXS_ATTR = {line: line.replace(' ', '_').replace('-', '_') @@ -59,10 +61,15 @@ def _get_legend_label(this, type): """Gets a label for the element or nuclide or material and reaction plotted""" if isinstance(this, str): if type in openmc.data.DADZ: - z, a, m = openmc.data.zam(this) - da, dz = openmc.data.DADZ[type] - gnds_name = openmc.data.gnds_name(z + dz, a + da, m) - return f'{this} {type} {gnds_name}' + if this in ELEMENT_NAMES: + return f'{this} {type}' + else: # this is a nuclide so the legend can contain more information + z, a, m = openmc.data.zam(this) + da, dz = openmc.data.DADZ[type] + gnds_name = openmc.data.gnds_name(z + dz, a + da, m) + # makes a string with nuclide reaction and new nuclide + # For example "Be9 (n,2n) Be8" + return f'{this} {type} {gnds_name}' return f'{this} {type}' elif this.name == '': return f'Material {this.id} {type}' @@ -115,18 +122,29 @@ def _get_title(reactions): return 'Cross Section Plot' -def plot_xs(reactions, divisor_types=None, temperature=294., axis=None, - sab_name=None, ce_cross_sections=None, mg_cross_sections=None, - enrichment=None, plot_CE=True, orders=None, divisor_orders=None, - energy_axis_units="eV", **kwargs): +def plot_xs( + reactions: Dict[str | openmc.Material, List[str]], + divisor_types: Iterable[str] | None = None, + temperature: float = 294.0, + axis: "plt.Axes" | None = None, + sab_name: str | None = None, + ce_cross_sections: str | None = None, + mg_cross_sections: str | None = None, + enrichment: float | None = None, + plot_CE: bool = True, + orders: Iterable[int] | None = None, + divisor_orders: Iterable[int] | None = None, + energy_axis_units: str = "eV", + **kwargs, +) -> "plt.Figure" | None: """Creates a figure of continuous-energy cross sections for this item. Parameters ---------- reactions : dict keys can be either a nuclide or element in string form or an - openmc.Material object. Values are the type of cross sections to - include in the plot. + openmc.Material object. Values are a list of the types of + cross sections to include in the plot. divisor_types : Iterable of values of PLOT_TYPES, optional Cross section types which will divide those produced by types before plotting. A type of 'unity' can be used to effectively not @@ -164,7 +182,7 @@ def plot_xs(reactions, divisor_types=None, temperature=294., axis=None, energy_axis_units : {'eV', 'keV', 'MeV'} Units used on the plot energy axis - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Returns ------- @@ -270,7 +288,6 @@ def plot_xs(reactions, divisor_types=None, temperature=294., axis=None, return fig - def calculate_cexs(this, types, temperature=294., sab_name=None, cross_sections=None, enrichment=None, ncrystal_cfg=None): """Calculates continuous-energy cross sections of a requested type. diff --git a/openmc/polynomial.py b/openmc/polynomial.py index 1259c61ca26..341cdff476d 100644 --- a/openmc/polynomial.py +++ b/openmc/polynomial.py @@ -92,7 +92,7 @@ class Zernike(Polynomial): Parameters ---------- coef : Iterable of float - A list of coefficients of each term in radial only Zernike polynomials + A list of coefficients of each term in Zernike polynomials radius : float Domain of Zernike polynomials to be applied on. Default is 1. diff --git a/openmc/region.py b/openmc/region.py index f679129c1fc..e1cb834757a 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -1,3 +1,4 @@ +from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import MutableSequence from copy import deepcopy @@ -30,8 +31,9 @@ def __and__(self, other): def __or__(self, other): return Union((self, other)) - def __invert__(self): - return Complement(self) + @abstractmethod + def __invert__(self) -> Region: + pass @abstractmethod def __contains__(self, point): @@ -344,7 +346,7 @@ def rotate(self, rotation, pivot=(0., 0., 0.), order='xyz', inplace=False, def plot(self, *args, **kwargs): """Display a slice plot of the region. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -442,6 +444,9 @@ def __iand__(self, other): self.append(other) return self + def __invert__(self) -> Union: + return Union(~n for n in self) + # Implement mutable sequence protocol by delegating to list def __getitem__(self, key): return self._nodes[key] @@ -530,6 +535,9 @@ def __ior__(self, other): self.append(other) return self + def __invert__(self) -> Intersection: + return Intersection(~n for n in self) + # Implement mutable sequence protocol by delegating to list def __getitem__(self, key): return self._nodes[key] @@ -603,7 +611,7 @@ class Complement(Region): """ - def __init__(self, node): + def __init__(self, node: Region): self.node = node def __contains__(self, point): @@ -622,6 +630,9 @@ def __contains__(self, point): """ return point not in self.node + def __invert__(self) -> Region: + return self.node + def __str__(self): return '~' + str(self.node) @@ -637,18 +648,7 @@ def node(self, node): @property def bounding_box(self) -> BoundingBox: - # Use De Morgan's laws to distribute the complement operator so that it - # only applies to surface half-spaces, thus allowing us to calculate the - # bounding box in the usual recursive manner. - if isinstance(self.node, Union): - temp_region = Intersection(~n for n in self.node) - elif isinstance(self.node, Intersection): - temp_region = Union(~n for n in self.node) - elif isinstance(self.node, Complement): - temp_region = self.node.node - else: - temp_region = ~self.node - return temp_region.bounding_box + return (~self.node).bounding_box def get_surfaces(self, surfaces=None): """Recursively find and return all the surfaces referenced by the node diff --git a/openmc/search.py b/openmc/search.py index 7f6b3b5b9a4..70ce011b634 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -8,7 +8,7 @@ import openmc.checkvalue as cv -_SCALAR_BRACKETED_METHODS = ['brentq', 'brenth', 'ridder', 'bisect'] +_SCALAR_BRACKETED_METHODS = {'brentq', 'brenth', 'ridder', 'bisect'} def _search_keff(guess, target, model_builder, model_args, print_iterations, diff --git a/openmc/settings.py b/openmc/settings.py index 63124602908..77598b204fc 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1,21 +1,21 @@ -from collections.abc import Iterable, Mapping, MutableSequence +from collections.abc import Iterable, Mapping, MutableSequence, Sequence from enum import Enum import itertools from math import ceil from numbers import Integral, Real from pathlib import Path -import typing # required to prevent typing.Union namespace overwriting Union -from typing import Optional import lxml.etree as ET import openmc.checkvalue as cv +from openmc.checkvalue import PathLike from openmc.stats.multivariate import MeshSpatial -from . import (RegularMesh, SourceBase, MeshSource, IndependentSource, - VolumeCalculation, WeightWindows, WeightWindowGenerator) from ._xml import clean_indentation, get_text, reorder_attributes -from openmc.checkvalue import PathLike -from .mesh import _read_meshes +from .mesh import _read_meshes, RegularMesh +from .source import SourceBase, MeshSource, IndependentSource +from .utility_funcs import input_path +from .volume import VolumeCalculation +from .weight_windows import WeightWindows, WeightWindowGenerator class RunMode(Enum): @@ -26,7 +26,7 @@ class RunMode(Enum): PARTICLE_RESTART = 'particle restart' -_RES_SCAT_METHODS = ['dbrc', 'rvs'] +_RES_SCAT_METHODS = {'dbrc', 'rvs'} class Settings: @@ -113,10 +113,10 @@ class Settings: max_particle_events : int Maximum number of allowed particle events per source particle. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 max_order : None or int Maximum scattering order to apply globally when in multi-group mode. - max_splits : int + max_history_splits : int Maximum number of times a particle can split during a history .. versionadded:: 0.13 @@ -157,8 +157,24 @@ class Settings: :ray_source: Starting ray distribution (must be uniform in space and angle) as specified by a :class:`openmc.SourceBase` object. - - .. versionadded:: 0.14.1 + :volume_estimator: + Choice of volume estimator for the random ray solver. Options are + 'naive', 'simulation_averaged', or 'hybrid'. + The default is 'hybrid'. + :source_shape: + Assumed shape of the source distribution within each source + region. Options are 'flat' (default), 'linear', or 'linear_xy'. + :volume_normalized_flux_tallies: + Whether to normalize flux tallies by volume (bool). The default + is 'False'. When enabled, flux tallies will be reported in units of + cm/cm^3. When disabled, flux tallies will be reported in units + of cm (i.e., total distance traveled by neutrons in the spatial + tally region). + :adjoint: + Whether to run the random ray solver in adjoint mode (bool). The + default is 'False'. + + .. versionadded:: 0.15.0 resonance_scattering : dict Settings for resonance elastic scattering. Accepted keys are 'enable' (bool), 'method' (str), 'energy_min' (float), 'energy_max' (float), and @@ -200,7 +216,17 @@ class Settings: banked (int) :max_particles: Maximum number of particles to be banked on surfaces per process (int) + :max_source_files: Maximum number of surface source files to be created (int) :mcpl: Output in the form of an MCPL-file (bool) + :cell: Cell ID used to determine if particles crossing identified + surfaces are to be banked. Particles coming from or going to this + declared cell will be banked (int) + :cellfrom: Cell ID used to determine if particles crossing identified + surfaces are to be banked. Particles coming from this + declared cell will be banked (int) + :cellto: Cell ID used to determine if particles crossing identified + surfaces are to be banked. Particles going to this declared + cell will be banked (int) survival_biasing : bool Indicate whether survival biasing is to be used tabular_legendre : dict @@ -240,6 +266,9 @@ class Settings: Maximum number of batches simulated. If this is set, the number of batches specified via ``batches`` is interpreted as the minimum number of batches + uniform_source_sampling : bool + Whether to sampling among multiple sources uniformly, applying their + strengths as weights to sampled particles. ufs_mesh : openmc.RegularMesh Mesh to be used for redistributing source sites via the uniform fission site (UFS) method. @@ -302,6 +331,7 @@ def __init__(self, **kwargs): self._photon_transport = None self._plot_seed = None self._ptables = None + self._uniform_source_sampling = None self._seed = None self._survival_biasing = None @@ -358,7 +388,7 @@ def __init__(self, **kwargs): self._weight_windows_on = None self._weight_windows_file = None self._weight_window_checkpoints = {} - self._max_splits = None + self._max_history_splits = None self._max_tracks = None self._random_ray = {} @@ -496,7 +526,7 @@ def max_order(self) -> int: return self._max_order @max_order.setter - def max_order(self, max_order: Optional[int]): + def max_order(self, max_order: int | None): if max_order is not None: cv.check_type('maximum scattering order', max_order, Integral) cv.check_greater_than('maximum scattering order', max_order, 0, @@ -504,11 +534,11 @@ def max_order(self, max_order: Optional[int]): self._max_order = max_order @property - def source(self) -> typing.List[SourceBase]: + def source(self) -> list[SourceBase]: return self._source @source.setter - def source(self, source: typing.Union[SourceBase, typing.Iterable[SourceBase]]): + def source(self, source: SourceBase | Iterable[SourceBase]): if not isinstance(source, MutableSequence): source = [source] self._source = cv.CheckedList(SourceBase, 'source distributions', source) @@ -549,6 +579,15 @@ def photon_transport(self, photon_transport: bool): cv.check_type('photon transport', photon_transport, bool) self._photon_transport = photon_transport + @property + def uniform_source_sampling(self) -> bool: + return self._uniform_source_sampling + + @uniform_source_sampling.setter + def uniform_source_sampling(self, uniform_source_sampling: bool): + cv.check_type('strength as weights', uniform_source_sampling, bool) + self._uniform_source_sampling = uniform_source_sampling + @property def plot_seed(self): return self._plot_seed @@ -678,14 +717,18 @@ def surf_source_read(self) -> dict: return self._surf_source_read @surf_source_read.setter - def surf_source_read(self, surf_source_read: dict): - cv.check_type('surface source reading options', surf_source_read, Mapping) - for key, value in surf_source_read.items(): + def surf_source_read(self, ssr: dict): + cv.check_type('surface source reading options', ssr, Mapping) + for key, value in ssr.items(): cv.check_value('surface source reading key', key, ('path')) if key == 'path': - cv.check_type('path to surface source file', value, str) - self._surf_source_read = surf_source_read + cv.check_type('path to surface source file', value, PathLike) + self._surf_source_read = dict(ssr) + + # Resolve path to surface source file + if 'path' in ssr: + self._surf_source_read['path'] = input_path(ssr['path']) @property def surf_source_write(self) -> dict: @@ -693,23 +736,32 @@ def surf_source_write(self) -> dict: @surf_source_write.setter def surf_source_write(self, surf_source_write: dict): - cv.check_type('surface source writing options', surf_source_write, Mapping) + cv.check_type("surface source writing options", surf_source_write, Mapping) for key, value in surf_source_write.items(): - cv.check_value('surface source writing key', key, - ('surface_ids', 'max_particles', 'mcpl')) - if key == 'surface_ids': - cv.check_type('surface ids for source banking', value, - Iterable, Integral) + cv.check_value( + "surface source writing key", + key, + ("surface_ids", "max_particles", "max_source_files", "mcpl", "cell", "cellfrom", "cellto"), + ) + if key == "surface_ids": + cv.check_type( + "surface ids for source banking", value, Iterable, Integral + ) for surf_id in value: - cv.check_greater_than('surface id for source banking', - surf_id, 0) - elif key == 'max_particles': - cv.check_type('maximum particle banks on surfaces per process', - value, Integral) - cv.check_greater_than('maximum particle banks on surfaces per process', - value, 0) - elif key == 'mcpl': - cv.check_type('write to an MCPL-format file', value, bool) + cv.check_greater_than("surface id for source banking", surf_id, 0) + + elif key == "mcpl": + cv.check_type("write to an MCPL-format file", value, bool) + elif key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"): + name = { + "max_particles": "maximum particle banks on surfaces per process", + "max_source_files": "maximun surface source files to be written", + "cell": "Cell ID for source banking (from or to)", + "cellfrom": "Cell ID for source banking (from only)", + "cellto": "Cell ID for source banking (to only)", + }[key] + cv.check_type(name, value, Integral) + cv.check_greater_than(name, value, 0) self._surf_source_write = surf_source_write @@ -779,7 +831,7 @@ def temperature(self, temperature: dict): self._temperature = temperature @property - def trace(self) -> typing.Iterable: + def trace(self) -> Iterable: return self._trace @trace.setter @@ -792,12 +844,12 @@ def trace(self, trace: Iterable): self._trace = trace @property - def track(self) -> typing.Iterable[typing.Iterable[int]]: + def track(self) -> Iterable[Iterable[int]]: return self._track @track.setter - def track(self, track: typing.Iterable[typing.Iterable[int]]): - cv.check_type('track', track, Iterable) + def track(self, track: Iterable[Iterable[int]]): + cv.check_type('track', track, Sequence) for t in track: if len(t) != 3: msg = f'Unable to set the track to "{t}" since its length is not 3' @@ -879,12 +931,12 @@ def resonance_scattering(self, res: dict): self._resonance_scattering = res @property - def volume_calculations(self) -> typing.List[VolumeCalculation]: + def volume_calculations(self) -> list[VolumeCalculation]: return self._volume_calculations @volume_calculations.setter def volume_calculations( - self, vol_calcs: typing.Union[VolumeCalculation, typing.Iterable[VolumeCalculation]] + self, vol_calcs: VolumeCalculation | Iterable[VolumeCalculation] ): if not isinstance(vol_calcs, MutableSequence): vol_calcs = [vol_calcs] @@ -978,11 +1030,11 @@ def write_initial_source(self, value: bool): self._write_initial_source = value @property - def weight_windows(self) -> typing.List[WeightWindows]: + def weight_windows(self) -> list[WeightWindows]: return self._weight_windows @weight_windows.setter - def weight_windows(self, value: typing.Union[WeightWindows, typing.Iterable[WeightWindows]]): + def weight_windows(self, value: WeightWindows | Iterable[WeightWindows]): if not isinstance(value, MutableSequence): value = [value] self._weight_windows = cv.CheckedList(WeightWindows, 'weight windows', value) @@ -1007,14 +1059,18 @@ def weight_window_checkpoints(self, weight_window_checkpoints: dict): self._weight_window_checkpoints = weight_window_checkpoints @property - def max_splits(self) -> int: - return self._max_splits + def max_splits(self): + raise AttributeError('max_splits has been deprecated. Please use max_history_splits instead') + + @property + def max_history_splits(self) -> int: + return self._max_history_splits - @max_splits.setter - def max_splits(self, value: int): + @max_history_splits.setter + def max_history_splits(self, value: int): cv.check_type('maximum particle splits', value, Integral) cv.check_greater_than('max particle splits', value, 0) - self._max_splits = value + self._max_history_splits = value @property def max_tracks(self) -> int: @@ -1027,16 +1083,16 @@ def max_tracks(self, value: int): self._max_tracks = value @property - def weight_windows_file(self) -> Optional[PathLike]: + def weight_windows_file(self) -> PathLike | None: return self._weight_windows_file @weight_windows_file.setter def weight_windows_file(self, value: PathLike): - cv.check_type('weight windows file', value, (str, Path)) - self._weight_windows_file = value + cv.check_type('weight windows file', value, PathLike) + self._weight_windows_file = input_path(value) @property - def weight_window_generators(self) -> typing.List[WeightWindowGenerator]: + def weight_window_generators(self) -> list[WeightWindowGenerator]: return self._weight_window_generators @weight_window_generators.setter @@ -1064,6 +1120,17 @@ def random_ray(self, random_ray: dict): random_ray[key], 0.0, True) elif key == 'ray_source': cv.check_type('random ray source', random_ray[key], SourceBase) + elif key == 'volume_estimator': + cv.check_value('volume estimator', random_ray[key], + ('naive', 'simulation_averaged', + 'hybrid')) + elif key == 'source_shape': + cv.check_value('source shape', random_ray[key], + ('flat', 'linear', 'linear_xy')) + elif key == 'volume_normalized_flux_tallies': + cv.check_type('volume normalized flux tallies', random_ray[key], bool) + elif key == 'adjoint': + cv.check_type('adjoint', random_ray[key], bool) else: raise ValueError(f'Unable to set random ray to "{key}" which is ' 'unsupported by OpenMC') @@ -1167,6 +1234,11 @@ def _create_statepoint_subelement(self, root): subelement.text = ' '.join( str(x) for x in self._statepoint['batches']) + def _create_uniform_source_sampling_subelement(self, root): + if self._uniform_source_sampling is not None: + element = ET.SubElement(root, "uniform_source_sampling") + element.text = str(self._uniform_source_sampling).lower() + def _create_sourcepoint_subelement(self, root): if self._sourcepoint: element = ET.SubElement(root, "source_point") @@ -1198,21 +1270,23 @@ def _create_surf_source_read_subelement(self, root): element = ET.SubElement(root, "surf_source_read") if 'path' in self._surf_source_read: subelement = ET.SubElement(element, "path") - subelement.text = self._surf_source_read['path'] + subelement.text = str(self._surf_source_read['path']) def _create_surf_source_write_subelement(self, root): if self._surf_source_write: element = ET.SubElement(root, "surf_source_write") - if 'surface_ids' in self._surf_source_write: + if "surface_ids" in self._surf_source_write: subelement = ET.SubElement(element, "surface_ids") - subelement.text = ' '.join( - str(x) for x in self._surf_source_write['surface_ids']) - if 'max_particles' in self._surf_source_write: - subelement = ET.SubElement(element, "max_particles") - subelement.text = str(self._surf_source_write['max_particles']) - if 'mcpl' in self._surf_source_write: + subelement.text = " ".join( + str(x) for x in self._surf_source_write["surface_ids"] + ) + if "mcpl" in self._surf_source_write: subelement = ET.SubElement(element, "mcpl") - subelement.text = str(self._surf_source_write['mcpl']).lower() + subelement.text = str(self._surf_source_write["mcpl"]).lower() + for key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"): + if key in self._surf_source_write: + subelement = ET.SubElement(element, key) + subelement.text = str(self._surf_source_write[key]) def _create_confidence_intervals(self, root): if self._confidence_intervals is not None: @@ -1377,9 +1451,9 @@ def _create_create_fission_neutrons_subelement(self, root): elem.text = str(self._create_fission_neutrons).lower() def _create_create_delayed_neutrons_subelement(self, root): - if self._create_delayed_neutrons is not None: - elem = ET.SubElement(root, "create_delayed_neutrons") - elem.text = str(self._create_delayed_neutrons).lower() + if self._create_delayed_neutrons is not None: + elem = ET.SubElement(root, "create_delayed_neutrons") + elem.text = str(self._create_delayed_neutrons).lower() def _create_delayed_photon_scaling_subelement(self, root): if self._delayed_photon_scaling is not None: @@ -1456,7 +1530,7 @@ def _create_weight_window_generators_subelement(self, root, mesh_memo=None): def _create_weight_windows_file_element(self, root): if self.weight_windows_file is not None: element = ET.Element("weight_windows_file") - element.text = self.weight_windows_file + element.text = str(self.weight_windows_file) root.append(element) def _create_weight_window_checkpoints_subelement(self, root): @@ -1472,10 +1546,10 @@ def _create_weight_window_checkpoints_subelement(self, root): subelement = ET.SubElement(element, "surface") subelement.text = str(self._weight_window_checkpoints['surface']).lower() - def _create_max_splits_subelement(self, root): - if self._max_splits is not None: - elem = ET.SubElement(root, "max_splits") - elem.text = str(self._max_splits) + def _create_max_history_splits_subelement(self, root): + if self._max_history_splits is not None: + elem = ET.SubElement(root, "max_history_splits") + elem.text = str(self._max_history_splits) def _create_max_tracks_subelement(self, root): if self._max_tracks is not None: @@ -1600,23 +1674,26 @@ def _sourcepoint_from_xml_element(self, root): def _surf_source_read_from_xml_element(self, root): elem = root.find('surf_source_read') if elem is not None: + ssr = {} value = get_text(elem, 'path') if value is not None: - self.surf_source_read['path'] = value + ssr['path'] = value + self.surf_source_read = ssr def _surf_source_write_from_xml_element(self, root): elem = root.find('surf_source_write') - if elem is not None: - for key in ('surface_ids', 'max_particles','mcpl'): - value = get_text(elem, key) - if value is not None: - if key == 'surface_ids': - value = [int(x) for x in value.split()] - elif key in ('max_particles'): - value = int(value) - elif key == 'mcpl': - value = value in ('true', '1') - self.surf_source_write[key] = value + if elem is None: + return + for key in ('surface_ids', 'max_particles', 'max_source_files', 'mcpl', 'cell', 'cellto', 'cellfrom'): + value = get_text(elem, key) + if value is not None: + if key == 'surface_ids': + value = [int(x) for x in value.split()] + elif key == 'mcpl': + value = value in ('true', '1') + elif key in ('max_particles', 'max_source_files', 'cell', 'cellfrom', 'cellto'): + value = int(value) + self.surf_source_write[key] = value def _confidence_intervals_from_xml_element(self, root): text = get_text(root, 'confidence_intervals') @@ -1643,6 +1720,11 @@ def _photon_transport_from_xml_element(self, root): if text is not None: self.photon_transport = text in ('true', '1') + def _uniform_source_sampling_from_xml_element(self, root): + text = get_text(root, 'uniform_source_sampling') + if text is not None: + self.uniform_source_sampling = text in ('true', '1') + def _plot_seed_from_xml_element(self, root): text = get_text(root, 'plot_seed') if text is not None: @@ -1834,10 +1916,10 @@ def _weight_window_checkpoints_from_xml_element(self, root): value = value in ('true', '1') self.weight_window_checkpoints[key] = value - def _max_splits_from_xml_element(self, root): - text = get_text(root, 'max_splits') + def _max_history_splits_from_xml_element(self, root): + text = get_text(root, 'max_history_splits') if text is not None: - self.max_splits = int(text) + self.max_history_splits = int(text) def _max_tracks_from_xml_element(self, root): text = get_text(root, 'max_tracks') @@ -1854,6 +1936,18 @@ def _random_ray_from_xml_element(self, root): elif child.tag == 'source': source = SourceBase.from_xml_element(child) self.random_ray['ray_source'] = source + elif child.tag == 'volume_estimator': + self.random_ray['volume_estimator'] = child.text + elif child.tag == 'source_shape': + self.random_ray['source_shape'] = child.text + elif child.tag == 'volume_normalized_flux_tallies': + self.random_ray['volume_normalized_flux_tallies'] = ( + child.text in ('true', '1') + ) + elif child.tag == 'adjoint': + self.random_ray['adjoint'] = ( + child.text in ('true', '1') + ) def to_xml_element(self, mesh_memo=None): """Create a 'settings' element to be written to an XML file. @@ -1886,6 +1980,7 @@ def to_xml_element(self, mesh_memo=None): self._create_energy_mode_subelement(element) self._create_max_order_subelement(element) self._create_photon_transport_subelement(element) + self._create_uniform_source_sampling_subelement(element) self._create_plot_seed_subelement(element) self._create_ptables_subelement(element) self._create_seed_subelement(element) @@ -1915,7 +2010,7 @@ def to_xml_element(self, mesh_memo=None): self._create_weight_window_generators_subelement(element, mesh_memo) self._create_weight_windows_file_element(element) self._create_weight_window_checkpoints_subelement(element) - self._create_max_splits_subelement(element) + self._create_max_history_splits_subelement(element) self._create_max_tracks_subelement(element) self._create_random_ray_subelement(element) @@ -1992,6 +2087,7 @@ def from_xml_element(cls, elem, meshes=None): settings._energy_mode_from_xml_element(elem) settings._max_order_from_xml_element(elem) settings._photon_transport_from_xml_element(elem) + settings._uniform_source_sampling_from_xml_element(elem) settings._plot_seed_from_xml_element(elem) settings._ptables_from_xml_element(elem) settings._seed_from_xml_element(elem) @@ -2019,7 +2115,7 @@ def from_xml_element(cls, elem, meshes=None): settings._weight_windows_from_xml_element(elem, meshes) settings._weight_window_generators_from_xml_element(elem, meshes) settings._weight_window_checkpoints_from_xml_element(elem) - settings._max_splits_from_xml_element(elem) + settings._max_history_splits_from_xml_element(elem) settings._max_tracks_from_xml_element(elem) settings._random_ray_from_xml_element(elem) diff --git a/openmc/source.py b/openmc/source.py index 91318f8e9ad..878f52c3b22 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -1,16 +1,17 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from enum import IntEnum from numbers import Real +from pathlib import Path import warnings -import typing # imported separately as py3.8 requires typing.Iterable -# also required to prevent typing.Union namespace overwriting Union -from typing import Optional, Sequence, Dict, Any +from typing import Any +from pathlib import Path import lxml.etree as ET import numpy as np import h5py +import pandas as pd import openmc import openmc.checkvalue as cv @@ -19,6 +20,7 @@ from openmc.stats.univariate import Univariate from ._xml import get_text from .mesh import MeshBase, StructuredMesh, UnstructuredMesh +from .utility_funcs import input_path class SourceBase(ABC): @@ -57,8 +59,8 @@ class SourceBase(ABC): def __init__( self, - strength: Optional[float] = 1.0, - constraints: Optional[Dict[str, Any]] = None + strength: float | None = 1.0, + constraints: dict[str, Any] | None = None ): self.strength = strength self.constraints = constraints @@ -75,11 +77,11 @@ def strength(self, strength): self._strength = strength @property - def constraints(self) -> Dict[str, Any]: + def constraints(self) -> dict[str, Any]: return self._constraints @constraints.setter - def constraints(self, constraints: Optional[Dict[str, Any]]): + def constraints(self, constraints: dict[str, Any] | None): self._constraints = {} if constraints is None: return @@ -200,7 +202,7 @@ def from_xml_element(cls, elem: ET.Element, meshes=None) -> SourceBase: raise ValueError(f'Source type {source_type} is not recognized') @staticmethod - def _get_constraints(elem: ET.Element) -> Dict[str, Any]: + def _get_constraints(elem: ET.Element) -> dict[str, Any]: # Find element containing constraints constraints_elem = elem.find("constraints") elem = constraints_elem if constraints_elem is not None else elem @@ -264,7 +266,7 @@ class IndependentSource(SourceBase): Domains to reject based on, i.e., if a sampled spatial location is not within one of these domains, it will be rejected. - .. deprecated:: 0.14.1 + .. deprecated:: 0.15.0 Use the `constraints` argument instead. constraints : dict Constraints on sampled source particles. Valid keys include 'domains', @@ -308,14 +310,14 @@ class IndependentSource(SourceBase): def __init__( self, - space: Optional[openmc.stats.Spatial] = None, - angle: Optional[openmc.stats.UnitSphere] = None, - energy: Optional[openmc.stats.Univariate] = None, - time: Optional[openmc.stats.Univariate] = None, + space: openmc.stats.Spatial | None = None, + angle: openmc.stats.UnitSphere | None = None, + energy: openmc.stats.Univariate | None = None, + time: openmc.stats.Univariate | None = None, strength: float = 1.0, particle: str = 'neutron', - domains: Optional[Sequence[typing.Union[openmc.Cell, openmc.Material, openmc.Universe]]] = None, - constraints: Optional[Dict[str, Any]] = None + domains: Sequence[openmc.Cell | openmc.Material | openmc.Universe] | None = None, + constraints: dict[str, Any] | None = None ): if domains is not None: warnings.warn("The 'domains' arguments has been replaced by the " @@ -482,7 +484,7 @@ class MeshSource(SourceBase): strength of the mesh source as a whole is the sum of all source strengths applied to the elements. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -528,7 +530,7 @@ def __init__( self, mesh: MeshBase, sources: Sequence[SourceBase], - constraints: Optional[Dict[str, Any]] = None, + constraints: dict[str, Any] | None = None, ): super().__init__(strength=None, constraints=constraints) self.mesh = mesh @@ -664,7 +666,7 @@ class CompiledSource(SourceBase): Parameters ---------- - library : str or None + library : path-like Path to a compiled shared library parameters : str Parameters to be provided to the compiled shared library function @@ -686,7 +688,7 @@ class CompiledSource(SourceBase): Attributes ---------- - library : str or None + library : pathlib.Path Path to a compiled shared library parameters : str Parameters to be provided to the compiled shared library function @@ -702,17 +704,13 @@ class CompiledSource(SourceBase): """ def __init__( self, - library: Optional[str] = None, - parameters: Optional[str] = None, + library: PathLike, + parameters: str | None = None, strength: float = 1.0, - constraints: Optional[Dict[str, Any]] = None + constraints: dict[str, Any] | None = None ) -> None: super().__init__(strength=strength, constraints=constraints) - - self._library = None - if library is not None: - self.library = library - + self.library = library self._parameters = None if parameters is not None: self.parameters = parameters @@ -722,13 +720,13 @@ def type(self) -> str: return "compiled" @property - def library(self) -> str: + def library(self) -> Path: return self._library @library.setter - def library(self, library_name): - cv.check_type('library', library_name, str) - self._library = library_name + def library(self, library_name: PathLike): + cv.check_type('library', library_name, PathLike) + self._library = input_path(library_name) @property def parameters(self) -> str: @@ -748,7 +746,7 @@ def populate_xml_element(self, element): XML element containing source data """ - element.set("library", self.library) + element.set("library", str(self.library)) if self.parameters is not None: element.set("parameters", self.parameters) @@ -794,7 +792,7 @@ class FileSource(SourceBase): Parameters ---------- - path : str or pathlib.Path + path : path-like Path to the source file from which sites should be sampled strength : float Strength of the source (default is 1.0) @@ -829,14 +827,12 @@ class FileSource(SourceBase): def __init__( self, - path: Optional[PathLike] = None, + path: PathLike, strength: float = 1.0, - constraints: Optional[Dict[str, Any]] = None + constraints: dict[str, Any] | None = None ): super().__init__(strength=strength, constraints=constraints) - self._path = None - if path is not None: - self.path = path + self.path = path @property def type(self) -> str: @@ -848,8 +844,8 @@ def path(self) -> PathLike: @path.setter def path(self, p: PathLike): - cv.check_type('source file', p, str) - self._path = p + cv.check_type('source file', p, PathLike) + self._path = input_path(p) def populate_xml_element(self, element): """Add necessary file source information to an XML element @@ -861,7 +857,7 @@ def populate_xml_element(self, element): """ if self.path is not None: - element.set("file", self.path) + element.set("file", str(self.path)) @classmethod def from_xml_element(cls, elem: ET.Element) -> openmc.FileSource: @@ -919,6 +915,34 @@ def from_string(cls, value: str): except KeyError: raise ValueError(f"Invalid string for creation of {cls.__name__}: {value}") + @classmethod + def from_pdg_number(cls, pdg_number: int) -> ParticleType: + """Constructs a ParticleType instance from a PDG number. + + The Particle Data Group at LBNL publishes a Monte Carlo particle + numbering scheme as part of the `Review of Particle Physics + <10.1103/PhysRevD.110.030001>`_. This method maps PDG numbers to the + corresponding :class:`ParticleType`. + + Parameters + ---------- + pdg_number : int + The PDG number of the particle type. + + Returns + ------- + The corresponding ParticleType instance. + """ + try: + return { + 2112: ParticleType.NEUTRON, + 22: ParticleType.PHOTON, + 11: ParticleType.ELECTRON, + -11: ParticleType.POSITRON, + }[pdg_number] + except KeyError: + raise ValueError(f"Unrecognized PDG number: {pdg_number}") + def __repr__(self) -> str: """ Returns a string representation of the ParticleType instance. @@ -932,11 +956,6 @@ def __repr__(self) -> str: def __str__(self) -> str: return self.__repr__() - # needed for <= 3.7, IntEnum will use the mixed-in type's `__format__` method otherwise - # this forces it to default to the standard object format, relying on __str__ under the hood - def __format__(self, spec): - return object.__format__(self, spec) - class SourceParticle: """Source particle @@ -966,8 +985,8 @@ class SourceParticle: """ def __init__( self, - r: typing.Iterable[float] = (0., 0., 0.), - u: typing.Iterable[float] = (0., 0., 1.), + r: Iterable[float] = (0., 0., 0.), + u: Iterable[float] = (0., 0., 1.), E: float = 1.0e6, time: float = 0.0, wgt: float = 1.0, @@ -1003,7 +1022,7 @@ def to_tuple(self) -> tuple: def write_source_file( - source_particles: typing.Iterable[SourceParticle], + source_particles: Iterable[SourceParticle], filename: PathLike, **kwargs ): """Write a source file using a collection of source particles @@ -1022,34 +1041,182 @@ def write_source_file( openmc.SourceParticle """ - # Create compound datatype for source particles - pos_dtype = np.dtype([('x', ' typing.List[SourceParticle]: + Parameters + ---------- + particles : list of SourceParticle + Particles to collect into the list + + """ + @classmethod + def from_hdf5(cls, filename: PathLike) -> ParticleList: + """Create particle list from an HDF5 file. + + Parameters + ---------- + filename : path-like + Path to source file to read. + + Returns + ------- + ParticleList instance + + """ + with h5py.File(filename, 'r') as fh: + filetype = fh.attrs['filetype'] + arr = fh['source_bank'][...] + + if filetype != b'source': + raise ValueError(f'File {filename} is not a source file') + + source_particles = [ + SourceParticle(*params, ParticleType(particle)) + for *params, particle in arr + ] + return cls(source_particles) + + @classmethod + def from_mcpl(cls, filename: PathLike) -> ParticleList: + """Create particle list from an MCPL file. + + Parameters + ---------- + filename : path-like + Path to MCPL file to read. + + Returns + ------- + ParticleList instance + + """ + import mcpl + # Process .mcpl file + particles = [] + with mcpl.MCPLFile(filename) as f: + for particle in f.particles: + # Determine particle type based on the PDG number + try: + particle_type = ParticleType.from_pdg_number(particle.pdgcode) + except ValueError: + particle_type = "UNKNOWN" + + # Create a source particle instance. Note that MCPL stores + # energy in MeV and time in ms. + source_particle = SourceParticle( + r=tuple(particle.position), + u=tuple(particle.direction), + E=1.0e6*particle.ekin, + time=1.0e-3*particle.time, + wgt=particle.weight, + particle=particle_type + ) + particles.append(source_particle) + + return cls(particles) + + def __getitem__(self, index): + """ + Return a new ParticleList object containing the particle(s) + at the specified index or slice. + + Parameters + ---------- + index : int, slice or list + The index, slice or list to select from the list of particles + + Returns + ------- + openmc.ParticleList or openmc.SourceParticle + A new object with the selected particle(s) + """ + if isinstance(index, int): + # If it's a single integer, return the corresponding particle + return super().__getitem__(index) + elif isinstance(index, slice): + # If it's a slice, return a new ParticleList object with the + # sliced particles + return ParticleList(super().__getitem__(index)) + elif isinstance(index, list): + # If it's a list of integers, return a new ParticleList object with + # the selected particles. Note that Python 3.10 gets confused if you + # use super() here, so we call list.__getitem__ directly. + return ParticleList([list.__getitem__(self, i) for i in index]) + else: + raise TypeError(f"Invalid index type: {type(index)}. Must be int, " + "slice, or list of int.") + + def to_dataframe(self) -> pd.DataFrame: + """A dataframe representing the source particles + + Returns + ------- + pandas.DataFrame + DataFrame containing the source particles attributes. + """ + # Extract the attributes of the source particles into a list of tuples + data = [(sp.r[0], sp.r[1], sp.r[2], sp.u[0], sp.u[1], sp.u[2], + sp.E, sp.time, sp.wgt, sp.delayed_group, sp.surf_id, + sp.particle.name.lower()) for sp in self] + + # Define the column names for the DataFrame + columns = ['x', 'y', 'z', 'u_x', 'u_y', 'u_z', 'E', 'time', 'wgt', + 'delayed_group', 'surf_id', 'particle'] + + # Create the pandas DataFrame from the data + return pd.DataFrame(data, columns=columns) + + def export_to_hdf5(self, filename: PathLike, **kwargs): + """Export particle list to an HDF5 file. + + This method write out an .h5 file that can be used as a source file in + conjunction with the :class:`openmc.FileSource` class. + + Parameters + ---------- + filename : path-like + Path to source file to write + **kwargs + Keyword arguments to pass to :class:`h5py.File` + + See Also + -------- + openmc.FileSource + + """ + # Create compound datatype for source particles + pos_dtype = np.dtype([('x', ' ParticleList: """Read a source file and return a list of source particles. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Parameters ---------- @@ -1058,23 +1225,18 @@ def read_source_file(filename: PathLike) -> typing.List[SourceParticle]: Returns ------- - list of SourceParticle - Source particles read from file + openmc.ParticleList See Also -------- openmc.SourceParticle """ - with h5py.File(filename, 'r') as fh: - filetype = fh.attrs['filetype'] - arr = fh['source_bank'][...] - - if filetype != b'source': - raise ValueError(f'File {filename} is not a source file') - - source_particles = [] - for *params, particle in arr: - source_particles.append(SourceParticle(*params, ParticleType(particle))) - - return source_particles + filename = Path(filename) + if filename.suffix not in ('.h5', '.mcpl'): + raise ValueError('Source file must have a .h5 or .mcpl extension.') + + if filename.suffix == '.h5': + return ParticleList.from_hdf5(filename) + else: + return ParticleList.from_mcpl(filename) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index f6df01bd4f1..13aac0caf7e 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -235,7 +235,7 @@ def global_tallies(self): if self._global_tallies is None: data = self._f['global_tallies'][()] gt = np.zeros(data.shape[0], dtype=[ - ('name', 'a14'), ('sum', 'f8'), ('sum_sq', 'f8'), + ('name', 'S14'), ('sum', 'f8'), ('sum_sq', 'f8'), ('mean', 'f8'), ('std_dev', 'f8')]) gt['name'] = ['k-collision', 'k-absorption', 'k-tracklength', 'leakage'] diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 212083c3cff..cd474fa9b28 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -1,7 +1,6 @@ from __future__ import annotations -import typing from abc import ABC, abstractmethod -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from math import cos, pi from numbers import Real from warnings import warn @@ -9,6 +8,7 @@ import lxml.etree as ET import numpy as np +import openmc import openmc.checkvalue as cv from .._xml import get_text from ..mesh import MeshBase @@ -212,7 +212,7 @@ class Monodirectional(UnitSphere): """ - def __init__(self, reference_uvw: typing.Sequence[float] = [1., 0., 0.]): + def __init__(self, reference_uvw: Sequence[float] = [1., 0., 0.]): super().__init__(reference_uvw) def to_xml_element(self): @@ -279,6 +279,8 @@ def from_xml_element(cls, elem, meshes=None): return Point.from_xml_element(elem) elif distribution == 'mesh': return MeshSpatial.from_xml_element(elem, meshes) + elif distribution == 'cloud': + return PointCloud.from_xml_element(elem) class CartesianIndependent(Spatial): @@ -756,6 +758,118 @@ def from_xml_element(cls, elem, meshes): return cls(meshes[mesh_id], strengths, volume_normalized) +class PointCloud(Spatial): + """Spatial distribution from a point cloud. + + This distribution specifies a discrete list of points, with corresponding + relative probabilities. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + positions : iterable of 3-tuples + The points in space to be sampled + strengths : iterable of float, optional + An iterable of values that represents the relative probabilty of each + point. + + Attributes + ---------- + positions : numpy.ndarray + The points in space to be sampled with shape (N, 3) + strengths : numpy.ndarray or None + An array of relative probabilities for each mesh point + """ + + def __init__( + self, + positions: Sequence[Sequence[float]], + strengths: Sequence[float] | None = None + ): + self.positions = positions + self.strengths = strengths + + @property + def positions(self) -> np.ndarray: + return self._positions + + @positions.setter + def positions(self, positions): + positions = np.array(positions, dtype=float) + if positions.ndim != 2: + raise ValueError('positions must be a 2D array') + elif positions.shape[1] != 3: + raise ValueError('Each position must have 3 values') + self._positions = positions + + @property + def strengths(self) -> np.ndarray: + return self._strengths + + @strengths.setter + def strengths(self, strengths): + if strengths is not None: + strengths = np.array(strengths, dtype=float) + if strengths.ndim != 1: + raise ValueError('strengths must be a 1D array') + elif strengths.size != self.positions.shape[0]: + raise ValueError('strengths must have the same length as positions') + self._strengths = strengths + + @property + def num_strength_bins(self) -> int: + if self.strengths is None: + raise ValueError('Strengths are not set') + return self.strengths.size + + def to_xml_element(self) -> ET.Element: + """Return XML representation of the spatial distribution + + Returns + ------- + element : lxml.etree._Element + XML element containing spatial distribution data + + """ + element = ET.Element('space') + element.set('type', 'cloud') + + subelement = ET.SubElement(element, 'coords') + subelement.text = ' '.join(str(e) for e in self.positions.flatten()) + + if self.strengths is not None: + subelement = ET.SubElement(element, 'strengths') + subelement.text = ' '.join(str(e) for e in self.strengths) + + return element + + @classmethod + def from_xml_element(cls, elem: ET.Element) -> PointCloud: + """Generate spatial distribution from an XML element + + Parameters + ---------- + elem : lxml.etree._Element + XML element + + Returns + ------- + openmc.stats.PointCloud + Spatial distribution generated from XML element + + + """ + coord_data = get_text(elem, 'coords') + positions = np.array([float(b) for b in coord_data.split()]).reshape((-1, 3)) + + strengths = get_text(elem, 'strengths') + if strengths is not None: + strengths = [float(b) for b in strengths.split()] + + return cls(positions, strengths) + + class Box(Spatial): """Uniform distribution of coordinates in a rectangular cuboid. @@ -769,7 +883,7 @@ class Box(Spatial): Whether spatial sites should only be accepted if they occur in fissionable materials - .. deprecated:: 0.14.1 + .. deprecated:: 0.15.0 Use the `constraints` argument when defining a source object instead. Attributes @@ -782,15 +896,15 @@ class Box(Spatial): Whether spatial sites should only be accepted if they occur in fissionable materials - .. deprecated:: 0.14.1 + .. deprecated:: 0.15.0 Use the `constraints` argument when defining a source object instead. """ def __init__( self, - lower_left: typing.Sequence[float], - upper_right: typing.Sequence[float], + lower_left: Sequence[float], + upper_right: Sequence[float], only_fissionable: bool = False ): self.lower_left = lower_left @@ -889,7 +1003,7 @@ class Point(Spatial): """ - def __init__(self, xyz: typing.Sequence[float] = (0., 0., 0.)): + def __init__(self, xyz: Sequence[float] = (0., 0., 0.)): self.xyz = xyz @property @@ -939,9 +1053,9 @@ def from_xml_element(cls, elem: ET.Element): def spherical_uniform( r_outer: float, r_inner: float = 0.0, - thetas: typing.Sequence[float] = (0., pi), - phis: typing.Sequence[float] = (0., 2*pi), - origin: typing.Sequence[float] = (0., 0., 0.) + thetas: Sequence[float] = (0., pi), + phis: Sequence[float] = (0., 2*pi), + origin: Sequence[float] = (0., 0., 0.) ): """Return a uniform spatial distribution over a spherical shell. diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index f44bce67cee..0dc6f385685 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -1,9 +1,8 @@ from __future__ import annotations import math -import typing from abc import ABC, abstractmethod from collections import defaultdict -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from copy import deepcopy from numbers import Real from warnings import warn @@ -16,13 +15,13 @@ from .._xml import get_text from ..mixin import EqualityMixin -_INTERPOLATION_SCHEMES = [ +_INTERPOLATION_SCHEMES = { 'histogram', 'linear-linear', 'linear-log', 'log-linear', 'log-log' -] +} class Univariate(EqualityMixin, ABC): @@ -68,7 +67,7 @@ def from_xml_element(cls, elem): return Mixture.from_xml_element(elem) @abstractmethod - def sample(n_samples: int = 1, seed: typing.Optional[int] = None): + def sample(n_samples: int = 1, seed: int | None = None): """Sample the univariate distribution Parameters @@ -98,6 +97,45 @@ def integral(self): return 1.0 +def _intensity_clip(intensity: Sequence[float], tolerance: float = 1e-6) -> np.ndarray: + """Clip low-importance points from an array of intensities. + + Given an array of intensities, this function returns an array of indices for + points that contribute non-negligibly to the total sum of intensities. + + Parameters + ---------- + intensity : sequence of float + Intensities in arbitrary units. + tolerance : float + Maximum fraction of intensities that will be discarded. + + Returns + ------- + Array of indices + + """ + # Get indices of intensities from largest to smallest + index_sort = np.argsort(intensity)[::-1] + + # Get intensities from largest to smallest + sorted_intensity = np.asarray(intensity)[index_sort] + + # Determine cumulative sum of probabilities + cumsum = np.cumsum(sorted_intensity) + cumsum /= cumsum[-1] + + # Find index that satisfies cutoff + index_cutoff = np.searchsorted(cumsum, 1.0 - tolerance) + + # Now get indices up to cutoff + new_indices = index_sort[:index_cutoff + 1] + + # Put back in the order of the original array and return + new_indices.sort() + return new_indices + + class Discrete(Univariate): """Distribution characterized by a probability mass function. @@ -210,8 +248,8 @@ def from_xml_element(cls, elem: ET.Element): @classmethod def merge( cls, - dists: typing.Sequence[Discrete], - probs: typing.Sequence[int] + dists: Sequence[Discrete], + probs: Sequence[int] ): """Merge multiple discrete distributions into a single distribution @@ -284,35 +322,44 @@ def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Discrete: cv.check_less_than("tolerance", tolerance, 1.0, equality=True) cv.check_greater_than("tolerance", tolerance, 0.0, equality=True) - # Determine (reversed) sorted order of probabilities + # Compute intensities intensity = self.p * self.x - index_sort = np.argsort(intensity)[::-1] - - # Get probabilities in above order - sorted_intensity = intensity[index_sort] - # Determine cumulative sum of probabilities - cumsum = np.cumsum(sorted_intensity) - cumsum /= cumsum[-1] - - # Find index which satisfies cutoff - index_cutoff = np.searchsorted(cumsum, 1.0 - tolerance) - - # Now get indices up to cutoff - new_indices = index_sort[:index_cutoff + 1] - new_indices.sort() + # Get indices for intensities above threshold + indices = _intensity_clip(intensity, tolerance=tolerance) # Create new discrete distribution if inplace: - self.x = self.x[new_indices] - self.p = self.p[new_indices] + self.x = self.x[indices] + self.p = self.p[indices] return self else: - new_x = self.x[new_indices] - new_p = self.p[new_indices] + new_x = self.x[indices] + new_p = self.p[indices] return type(self)(new_x, new_p) +def delta_function(value: float, intensity: float = 1.0) -> Discrete: + """Return a discrete distribution with a single point. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + value : float + Value of the random variable. + intensity : float, optional + When used for an energy distribution, this can be used to assign an + intensity. + + Returns + ------- + Discrete distribution with a single point + + """ + return Discrete([value], [intensity]) + + class Uniform(Univariate): """Distribution with constant probability over a finite interval [a,b] @@ -838,7 +885,8 @@ class Tabular(Univariate): Tabulated values of the random variable p : Iterable of float Tabulated probabilities. For histogram interpolation, if the length of - `p` is the same as `x`, the last value is ignored. + `p` is the same as `x`, the last value is ignored. Probabilities `p` are + given per unit of `x`. interpolation : {'histogram', 'linear-linear', 'linear-log', 'log-linear', 'log-log'}, optional Indicates how the density function is interpolated between tabulated points. Defaults to 'linear-linear'. @@ -855,12 +903,21 @@ class Tabular(Univariate): Indicates how the density function is interpolated between tabulated points. Defaults to 'linear-linear'. + Notes + ----- + The probabilities `p` are interpreted per unit of the corresponding + independent variable `x`. This follows the definition of a probability + density function (PDF) in probability theory, where the PDF represents the + relative likelihood of the random variable taking on a particular value per + unit of the variable. For example, if `x` represents energy in eV, then `p` + should represent probabilities per eV. + """ def __init__( self, - x: typing.Sequence[float], - p: typing.Sequence[float], + x: Sequence[float], + p: Sequence[float], interpolation: str = 'linear-linear', ignore_negative: bool = False ): @@ -958,7 +1015,7 @@ def normalize(self): """Normalize the probabilities stored on the distribution""" self._p /= self.cdf().max() - def sample(self, n_samples: int = 1, seed: typing.Optional[int] = None): + def sample(self, n_samples: int = 1, seed: int | None = None): rng = np.random.RandomState(seed) xi = rng.random(n_samples) @@ -1100,7 +1157,7 @@ class Legendre(Univariate): """ - def __init__(self, coefficients: typing.Sequence[float]): + def __init__(self, coefficients: Sequence[float]): self.coefficients = coefficients self._legendre_poly = None @@ -1156,8 +1213,8 @@ class Mixture(Univariate): def __init__( self, - probability: typing.Sequence[float], - distribution: typing.Sequence[Univariate] + probability: Sequence[float], + distribution: Sequence[Univariate] ): self.probability = probability self.distribution = distribution @@ -1176,7 +1233,7 @@ def probability(self, probability): for p in probability: cv.check_greater_than('mixture distribution probabilities', p, 0.0, True) - self._probability = probability + self._probability = np.array(probability, dtype=float) @property def distribution(self): @@ -1282,45 +1339,68 @@ def integral(self): ]) def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture: - r"""Remove low-importance points from contained discrete distributions. + r"""Remove low-importance points / distributions - Given a probability mass function :math:`p(x)` with :math:`\{x_1, x_2, - x_3, \dots\}` the possible values of the random variable with - corresponding probabilities :math:`\{p_1, p_2, p_3, \dots\}`, this - function will remove any low-importance points such that :math:`\sum_i - x_i p_i` is preserved to within some threshold. + Like :meth:`Discrete.clip`, this method will remove low-importance + points from discrete distributions contained within the mixture but it + will also clip any distributions that have negligible contributions to + the overall intensity. .. versionadded:: 0.14.0 Parameters ---------- tolerance : float - Maximum fraction of :math:`\sum_i x_i p_i` that will be discarded - for any discrete distributions within the mixture distribution. + Maximum fraction of intensities that will be discarded. inplace : bool Whether to modify the current object in-place or return a new one. Returns ------- - Discrete distribution with low-importance points removed + Distribution with low-importance points / distributions removed """ + # Determine integral of original distribution to compare later + original_integral = self.integral() + + # Determine indices for any distributions that contribute non-negligibly + # to overall intensity + intensities = [prob*dist.integral() for prob, dist in + zip(self.probability, self.distribution)] + indices = _intensity_clip(intensities, tolerance=tolerance) + + # Clip mixture of distributions + probability = self.probability[indices] + distribution = [self.distribution[i] for i in indices] + + # Clip points from Discrete distributions + distribution = [ + dist.clip(tolerance, inplace) if isinstance(dist, Discrete) else dist + for dist in distribution + ] + if inplace: - for dist in self.distribution: - if isinstance(dist, Discrete): - dist.clip(tolerance, inplace=True) - return self + # Set attributes of current object and return + self.probability = probability + self.distribution = distribution + new_dist = self else: - distribution = [ - dist.clip(tolerance) if isinstance(dist, Discrete) else dist - for dist in self.distribution - ] - return type(self)(self.probability, distribution) + # Create new distribution + new_dist = type(self)(probability, distribution) + + # Show warning if integral of new distribution is not within + # tolerance of original + diff = (original_integral - new_dist.integral())/original_integral + if diff > tolerance: + warn("Clipping mixture distribution resulted in an integral that is " + f"lower by a fraction of {diff} when tolerance={tolerance}.") + + return new_dist def combine_distributions( - dists: typing.Sequence[Univariate], - probs: typing.Sequence[float] + dists: Sequence[Univariate], + probs: Sequence[float] ): """Combine distributions with specified probabilities diff --git a/openmc/surface.py b/openmc/surface.py index 806331024e2..c95025f949b 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1,3 +1,4 @@ +from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import Iterable from copy import deepcopy @@ -14,8 +15,8 @@ from .bounding_box import BoundingBox -_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white'] -_ALBEDO_BOUNDARIES = ['reflective', 'periodic', 'white'] +_BOUNDARY_TYPES = {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} +_ALBEDO_BOUNDARIES = {'reflective', 'periodic', 'white'} _WARNING_UPPER = """\ "{}(...) accepts an argument named '{}', not '{}'. Future versions of OpenMC \ @@ -118,7 +119,7 @@ class Surface(IDManagerMixin, ABC): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Note that periodic boundary conditions @@ -134,7 +135,7 @@ class Surface(IDManagerMixin, ABC): Attributes ---------- - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -682,7 +683,7 @@ class Plane(PlaneMixin, Surface): The 'C' parameter for the plane. Defaults to 0. d : float, optional The 'D' parameter for the plane. Defaults to 0. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -706,7 +707,7 @@ class Plane(PlaneMixin, Surface): The 'C' parameter for the plane d : float The 'D' parameter for the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -802,6 +803,13 @@ def from_points(cls, p1, p2, p3, **kwargs): d = np.dot(n, p1) return cls(a=a, b=b, c=c, d=d, **kwargs) + def flip_normal(self): + """Modify plane coefficients to reverse the normal vector.""" + self.a = -self.a + self.b = -self.b + self.c = -self.c + self.d = -self.d + class XPlane(PlaneMixin, Surface): """A plane perpendicular to the x axis of the form :math:`x - x_0 = 0` @@ -810,7 +818,7 @@ class XPlane(PlaneMixin, Surface): ---------- x0 : float, optional Location of the plane in [cm]. Defaults to 0. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -829,7 +837,7 @@ class XPlane(PlaneMixin, Surface): ---------- x0 : float Location of the plane in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -875,7 +883,7 @@ class YPlane(PlaneMixin, Surface): ---------- y0 : float, optional Location of the plane in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -894,7 +902,7 @@ class YPlane(PlaneMixin, Surface): ---------- y0 : float Location of the plane in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -940,7 +948,7 @@ class ZPlane(PlaneMixin, Surface): ---------- z0 : float, optional Location of the plane in [cm]. Defaults to 0. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -959,7 +967,7 @@ class ZPlane(PlaneMixin, Surface): ---------- z0 : float Location of the plane in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1189,7 +1197,7 @@ class Cylinder(QuadricMixin, Surface): dz : float, optional z-component of the vector representing the axis of the cylinder. Defaults to 1. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1220,7 +1228,7 @@ class Cylinder(QuadricMixin, Surface): y-component of the vector representing the axis of the cylinder dz : float z-component of the vector representing the axis of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1364,7 +1372,7 @@ class XCylinder(QuadricMixin, Surface): z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional Radius of the cylinder in [cm]. Defaults to 1. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1387,7 +1395,7 @@ class XCylinder(QuadricMixin, Surface): z-coordinate for the origin of the Cylinder in [cm] r : float Radius of the cylinder in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1462,7 +1470,7 @@ class YCylinder(QuadricMixin, Surface): z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional Radius of the cylinder in [cm]. Defaults to 1. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1485,7 +1493,7 @@ class YCylinder(QuadricMixin, Surface): z-coordinate for the origin of the Cylinder in [cm] r : float Radius of the cylinder in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1560,7 +1568,7 @@ class ZCylinder(QuadricMixin, Surface): y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0 r : float, optional Radius of the cylinder in [cm]. Defaults to 1. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1583,7 +1591,7 @@ class ZCylinder(QuadricMixin, Surface): y-coordinate for the origin of the Cylinder in [cm] r : float Radius of the cylinder in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1659,7 +1667,7 @@ class Sphere(QuadricMixin, Surface): z-coordinate of the center of the sphere in [cm]. Defaults to 0. r : float, optional Radius of the sphere in [cm]. Defaults to 1. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1683,7 +1691,7 @@ class Sphere(QuadricMixin, Surface): z-coordinate of the center of the sphere in [cm] r : float Radius of the sphere in [cm] - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1762,7 +1770,7 @@ class Cone(QuadricMixin, Surface): z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperture [:math:`\\rm cm^2`]. - It can be interpreted as the increase in the radius squared per cm along + It can be interpreted as the increase in the radius squared per cm along the cone's axis of revolution. dx : float, optional x-component of the vector representing the axis of the cone. @@ -1776,7 +1784,7 @@ class Cone(QuadricMixin, Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1804,7 +1812,7 @@ class Cone(QuadricMixin, Surface): y-component of the vector representing the axis of the cone. dz : float z-component of the vector representing the axis of the cone. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -1920,9 +1928,9 @@ class XCone(QuadricMixin, Surface): z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperture [:math:`\\rm cm^2`]. - It can be interpreted as the increase in the radius squared per cm along + It can be interpreted as the increase in the radius squared per cm along the cone's axis of revolution. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1946,7 +1954,7 @@ class XCone(QuadricMixin, Surface): z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2021,9 +2029,9 @@ class YCone(QuadricMixin, Surface): z-coordinate of the apex in [cm]. Defaults to 0. r2 : float, optional Parameter related to the aperture [:math:`\\rm cm^2`]. - It can be interpreted as the increase in the radius squared per cm along + It can be interpreted as the increase in the radius squared per cm along the cone's axis of revolution. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -2047,7 +2055,7 @@ class YCone(QuadricMixin, Surface): z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2124,7 +2132,7 @@ class ZCone(QuadricMixin, Surface): Parameter related to the aperature [cm^2]. This is the square of the radius of the cone 1 cm from. This can also be treated as the square of the slope of the cone relative to its axis. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -2148,7 +2156,7 @@ class ZCone(QuadricMixin, Surface): z-coordinate of the apex in [cm] r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2213,7 +2221,7 @@ class Quadric(QuadricMixin, Surface): ---------- a, b, c, d, e, f, g, h, j, k : float, optional coefficients for the surface. All default to 0. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -2231,7 +2239,7 @@ class Quadric(QuadricMixin, Surface): ---------- a, b, c, d, e, f, g, h, j, k : float coefficients for the surface - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2383,7 +2391,7 @@ class XTorus(TorusMixin, Surface): Minor radius of the torus in [cm] (parallel to axis of revolution) c : float Minor radius of the torus in [cm] (perpendicular to axis of revolution) - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2458,7 +2466,7 @@ class YTorus(TorusMixin, Surface): Minor radius of the torus in [cm] (parallel to axis of revolution) c : float Minor radius of the torus (perpendicular to axis of revolution) - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2533,7 +2541,7 @@ class ZTorus(TorusMixin, Surface): Minor radius of the torus in [cm] (parallel to axis of revolution) c : float Minor radius of the torus in [cm] (perpendicular to axis of revolution) - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + boundary_type : {'transmission', 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. albedo : float @@ -2624,7 +2632,7 @@ def __or__(self, other): else: return Union((self, other)) - def __invert__(self): + def __invert__(self) -> Halfspace: return -self.surface if self.side == '+' else +self.surface def __contains__(self, point): diff --git a/openmc/trigger.py b/openmc/trigger.py index 79f5ea5af89..be2537e3ac8 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -22,7 +22,7 @@ class Trigger(EqualityMixin): can cause the trigger to fire prematurely if there are zero scores in any bin at the first evaluation. - .. versionadded:: 0.14.1 + .. versionadded:: 0.15.0 Attributes ---------- diff --git a/openmc/universe.py b/openmc/universe.py index 9fab9ae51b6..85ce6fd9656 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,21 +1,17 @@ +from __future__ import annotations import math from abc import ABC, abstractmethod from collections.abc import Iterable -from numbers import Integral, Real +from numbers import Real from pathlib import Path from tempfile import TemporaryDirectory import warnings -import h5py -import lxml.etree as ET import numpy as np import openmc import openmc.checkvalue as cv -from ._xml import get_text -from .checkvalue import check_type, check_value from .mixin import IDManagerMixin -from .surface import _BOUNDARY_TYPES class UniverseBase(ABC, IDManagerMixin): @@ -53,6 +49,10 @@ def __repr__(self): def name(self): return self._name + @property + def cells(self): + return self._cells + @name.setter def name(self, name): if name is not None: @@ -133,6 +133,130 @@ def create_xml_subelement(self, xml_element, memo=None): """ + def _determine_paths(self, path='', instances_only=False): + """Count the number of instances for each cell in the universe, and + record the count in the :attr:`Cell.num_instances` properties.""" + + univ_path = path + f'u{self.id}' + + for cell in self.cells.values(): + cell_path = f'{univ_path}->c{cell.id}' + fill = cell._fill + fill_type = cell.fill_type + + # If universe-filled, recursively count cells in filling universe + if fill_type == 'universe': + fill._determine_paths(cell_path + '->', instances_only) + # If lattice-filled, recursively call for all universes in lattice + elif fill_type == 'lattice': + latt = fill + + # Count instances in each universe in the lattice + for index in latt._natural_indices: + latt_path = '{}->l{}({})->'.format( + cell_path, latt.id, ",".join(str(x) for x in index)) + univ = latt.get_universe(index) + univ._determine_paths(latt_path, instances_only) + + else: + if fill_type == 'material': + mat = fill + elif fill_type == 'distribmat': + mat = fill[cell._num_instances] + else: + mat = None + + if mat is not None: + mat._num_instances += 1 + if not instances_only: + mat._paths.append(f'{cell_path}->m{mat.id}') + + # Append current path + cell._num_instances += 1 + if not instances_only: + cell._paths.append(cell_path) + + def add_cells(self, cells): + """Add multiple cells to the universe. + + Parameters + ---------- + cells : Iterable of openmc.Cell + Cells to add + + """ + + if not isinstance(cells, Iterable): + msg = f'Unable to add Cells to Universe ID="{self._id}" since ' \ + f'"{cells}" is not iterable' + raise TypeError(msg) + + for cell in cells: + self.add_cell(cell) + + @abstractmethod + def add_cell(self, cell): + pass + + @abstractmethod + def remove_cell(self, cell): + pass + + def clear_cells(self): + """Remove all cells from the universe.""" + + self._cells.clear() + + def get_all_cells(self, memo=None): + """Return all cells that are contained within the universe + + Returns + ------- + cells : dict + Dictionary whose keys are cell IDs and values are :class:`Cell` + instances + + """ + + if memo is None: + memo = set() + elif self in memo: + return {} + memo.add(self) + + # Add this Universe's cells to the dictionary + cells = {} + cells.update(self._cells) + + # Append all Cells in each Cell in the Universe to the dictionary + for cell in self._cells.values(): + cells.update(cell.get_all_cells(memo)) + + return cells + + def get_all_materials(self, memo=None): + """Return all materials that are contained within the universe + + Returns + ------- + materials : dict + Dictionary whose keys are material IDs and values are + :class:`Material` instances + + """ + + if memo is None: + memo = set() + + materials = {} + + # Append all Cells in each Cell in the Universe to the dictionary + cells = self.get_all_cells(memo) + for cell in cells.values(): + materials.update(cell.get_all_materials(memo)) + + return materials + @abstractmethod def _partial_deepcopy(self): """Deepcopy all parameters of an openmc.UniverseBase object except its cells. @@ -180,93 +304,6 @@ def clone(self, clone_materials=True, clone_regions=True, memo=None): return memo[self] - -class Universe(UniverseBase): - """A collection of cells that can be repeated. - - Parameters - ---------- - universe_id : int, optional - Unique identifier of the universe. If not specified, an identifier will - automatically be assigned - name : str, optional - Name of the universe. If not specified, the name is the empty string. - cells : Iterable of openmc.Cell, optional - Cells to add to the universe. By default no cells are added. - - Attributes - ---------- - id : int - Unique identifier of the universe - name : str - Name of the universe - cells : dict - Dictionary whose keys are cell IDs and values are :class:`Cell` - instances - volume : float - Volume of the universe in cm^3. This can either be set manually or - calculated in a stochastic volume calculation and added via the - :meth:`Universe.add_volume_information` method. - bounding_box : openmc.BoundingBox - Lower-left and upper-right coordinates of an axis-aligned bounding box - of the universe. - - """ - - def __init__(self, universe_id=None, name='', cells=None): - super().__init__(universe_id, name) - - if cells is not None: - self.add_cells(cells) - - def __repr__(self): - string = super().__repr__() - string += '{: <16}=\t{}\n'.format('\tGeom', 'CSG') - string += '{: <16}=\t{}\n'.format('\tCells', list(self._cells.keys())) - return string - - @property - def cells(self): - return self._cells - - @property - def bounding_box(self): - regions = [c.region for c in self.cells.values() - if c.region is not None] - if regions: - return openmc.Union(regions).bounding_box - else: - return openmc.BoundingBox.infinite() - - @classmethod - def from_hdf5(cls, group, cells): - """Create universe from HDF5 group - - Parameters - ---------- - group : h5py.Group - Group in HDF5 file - cells : dict - Dictionary mapping cell IDs to instances of :class:`openmc.Cell`. - - Returns - ------- - openmc.Universe - Universe instance - - """ - universe_id = int(group.name.split('/')[-1].lstrip('universe ')) - cell_ids = group['cells'][()] - - # Create this Universe - universe = cls(universe_id) - - # Add each Cell to the Universe - for cell_id in cell_ids: - universe.add_cell(cells[cell_id]) - - return universe - def find(self, point): """Find cells/universes/lattices which contain a given point @@ -526,98 +563,37 @@ def plot(self, origin=None, width=None, pixels=40000, axes.imshow(img, extent=(x_min, x_max, y_min, y_max), **kwargs) return axes - def add_cell(self, cell): - """Add a cell to the universe. + def get_nuclides(self): + """Returns all nuclides in the universe - Parameters - ---------- - cell : openmc.Cell - Cell to add + Returns + ------- + nuclides : list of str + List of nuclide names """ - if not isinstance(cell, openmc.Cell): - msg = f'Unable to add a Cell to Universe ID="{self._id}" since ' \ - f'"{cell}" is not a Cell' - raise TypeError(msg) + nuclides = [] - cell_id = cell.id + # Append all Nuclides in each Cell in the Universe to the dictionary + for cell in self.cells.values(): + for nuclide in cell.get_nuclides(): + if nuclide not in nuclides: + nuclides.append(nuclide) - if cell_id not in self._cells: - self._cells[cell_id] = cell + return nuclides - def add_cells(self, cells): - """Add multiple cells to the universe. + def get_nuclide_densities(self): + """Return all nuclides contained in the universe - Parameters - ---------- - cells : Iterable of openmc.Cell - Cells to add + Returns + ------- + nuclides : dict + Dictionary whose keys are nuclide names and values are 2-tuples of + (nuclide, density) """ - - if not isinstance(cells, Iterable): - msg = f'Unable to add Cells to Universe ID="{self._id}" since ' \ - f'"{cells}" is not iterable' - raise TypeError(msg) - - for cell in cells: - self.add_cell(cell) - - def remove_cell(self, cell): - """Remove a cell from the universe. - - Parameters - ---------- - cell : openmc.Cell - Cell to remove - - """ - - if not isinstance(cell, openmc.Cell): - msg = f'Unable to remove a Cell from Universe ID="{self._id}" ' \ - f'since "{cell}" is not a Cell' - raise TypeError(msg) - - # If the Cell is in the Universe's list of Cells, delete it - self._cells.pop(cell.id, None) - - def clear_cells(self): - """Remove all cells from the universe.""" - - self._cells.clear() - - def get_nuclides(self): - """Returns all nuclides in the universe - - Returns - ------- - nuclides : list of str - List of nuclide names - - """ - - nuclides = [] - - # Append all Nuclides in each Cell in the Universe to the dictionary - for cell in self.cells.values(): - for nuclide in cell.get_nuclides(): - if nuclide not in nuclides: - nuclides.append(nuclide) - - return nuclides - - def get_nuclide_densities(self): - """Return all nuclides contained in the universe - - Returns - ------- - nuclides : dict - Dictionary whose keys are nuclide names and values are 2-tuples of - (nuclide, density) - - """ - nuclides = {} + nuclides = {} if self._atoms: volume = self.volume @@ -634,150 +610,20 @@ def get_nuclide_densities(self): return nuclides - def get_all_cells(self, memo=None): - """Return all cells that are contained within the universe - - Returns - ------- - cells : dict - Dictionary whose keys are cell IDs and values are :class:`Cell` - instances - - """ - - if memo is None: - memo = set() - elif self in memo: - return {} - memo.add(self) - - # Add this Universe's cells to the dictionary - cells = {} - cells.update(self._cells) - - # Append all Cells in each Cell in the Universe to the dictionary - for cell in self._cells.values(): - cells.update(cell.get_all_cells(memo)) - - return cells - - def get_all_materials(self, memo=None): - """Return all materials that are contained within the universe - - Returns - ------- - materials : dict - Dictionary whose keys are material IDs and values are - :class:`Material` instances - - """ - - if memo is None: - memo = set() - - materials = {} - - # Append all Cells in each Cell in the Universe to the dictionary - cells = self.get_all_cells(memo) - for cell in cells.values(): - materials.update(cell.get_all_materials(memo)) - - return materials - - def create_xml_subelement(self, xml_element, memo=None): - if memo is None: - memo = set() - - # Iterate over all Cells - for cell in self._cells.values(): - # If the cell was already written, move on - if cell in memo: - continue - memo.add(cell) - - # Create XML subelement for this Cell - cell_element = cell.create_xml_subelement(xml_element, memo) - - # Append the Universe ID to the subelement and add to Element - cell_element.set("universe", str(self._id)) - xml_element.append(cell_element) - - def _determine_paths(self, path='', instances_only=False): - """Count the number of instances for each cell in the universe, and - record the count in the :attr:`Cell.num_instances` properties.""" - - univ_path = path + f'u{self.id}' - - for cell in self.cells.values(): - cell_path = f'{univ_path}->c{cell.id}' - fill = cell._fill - fill_type = cell.fill_type - - # If universe-filled, recursively count cells in filling universe - if fill_type == 'universe': - fill._determine_paths(cell_path + '->', instances_only) - - # If lattice-filled, recursively call for all universes in lattice - elif fill_type == 'lattice': - latt = fill - - # Count instances in each universe in the lattice - for index in latt._natural_indices: - latt_path = '{}->l{}({})->'.format( - cell_path, latt.id, ",".join(str(x) for x in index)) - univ = latt.get_universe(index) - univ._determine_paths(latt_path, instances_only) - - else: - if fill_type == 'material': - mat = fill - elif fill_type == 'distribmat': - mat = fill[cell._num_instances] - else: - mat = None - - if mat is not None: - mat._num_instances += 1 - if not instances_only: - mat._paths.append(f'{cell_path}->m{mat.id}') - - # Append current path - cell._num_instances += 1 - if not instances_only: - cell._paths.append(cell_path) - - def _partial_deepcopy(self): - """Clone all of the openmc.Universe object's attributes except for its cells, - as they are copied within the clone function. This should only to be - used within the openmc.UniverseBase.clone() context. - """ - clone = openmc.Universe(name=self.name) - clone.volume = self.volume - return clone - - -class DAGMCUniverse(UniverseBase): - """A reference to a DAGMC file to be used in the model. - - .. versionadded:: 0.13.0 +class Universe(UniverseBase): + """A collection of cells that can be repeated. Parameters ---------- - filename : str - Path to the DAGMC file used to represent this universe. universe_id : int, optional Unique identifier of the universe. If not specified, an identifier will - automatically be assigned. + automatically be assigned name : str, optional Name of the universe. If not specified, the name is the empty string. - auto_geom_ids : bool - Set IDs automatically on initialization (True) or report overlaps in ID - space between CSG and DAGMC (False) - auto_mat_ids : bool - Set IDs automatically on initialization (True) or report overlaps in ID - space between OpenMC and UWUW materials (False) + cells : Iterable of openmc.Cell, optional + Cells to add to the universe. By default no cells are added. Attributes ---------- @@ -785,335 +631,133 @@ class DAGMCUniverse(UniverseBase): Unique identifier of the universe name : str Name of the universe - filename : str - Path to the DAGMC file used to represent this universe. - auto_geom_ids : bool - Set IDs automatically on initialization (True) or report overlaps in ID - space between CSG and DAGMC (False) - auto_mat_ids : bool - Set IDs automatically on initialization (True) or report overlaps in ID - space between OpenMC and UWUW materials (False) + cells : dict + Dictionary whose keys are cell IDs and values are :class:`Cell` + instances + volume : float + Volume of the universe in cm^3. This can either be set manually or + calculated in a stochastic volume calculation and added via the + :meth:`Universe.add_volume_information` method. bounding_box : openmc.BoundingBox Lower-left and upper-right coordinates of an axis-aligned bounding box of the universe. - .. versionadded:: 0.13.1 - material_names : list of str - Return a sorted list of materials names that are contained within the - DAGMC h5m file. This is useful when naming openmc.Material() objects - as each material name present in the DAGMC h5m file must have a - matching openmc.Material() with the same name. - - .. versionadded:: 0.13.2 - n_cells : int - The number of cells in the DAGMC model. This is the number of cells at - runtime and accounts for the implicit complement whether or not is it - present in the DAGMC file. - - .. versionadded:: 0.13.2 - n_surfaces : int - The number of surfaces in the model. - - .. versionadded:: 0.13.2 - """ - def __init__(self, - filename, - universe_id=None, - name='', - auto_geom_ids=False, - auto_mat_ids=False): + def __init__(self, universe_id=None, name='', cells=None): super().__init__(universe_id, name) - # Initialize class attributes - self.filename = filename - self.auto_geom_ids = auto_geom_ids - self.auto_mat_ids = auto_mat_ids + + if cells is not None: + self.add_cells(cells) def __repr__(self): string = super().__repr__() - string += '{: <16}=\t{}\n'.format('\tGeom', 'DAGMC') - string += '{: <16}=\t{}\n'.format('\tFile', self.filename) + string += '{: <16}=\t{}\n'.format('\tGeom', 'CSG') + string += '{: <16}=\t{}\n'.format('\tCells', list(self._cells.keys())) return string @property - def bounding_box(self): - with h5py.File(self.filename) as dagmc_file: - coords = dagmc_file['tstt']['nodes']['coordinates'][()] - lower_left_corner = coords.min(axis=0) - upper_right_corner = coords.max(axis=0) - return openmc.BoundingBox(lower_left_corner, upper_right_corner) - - @property - def filename(self): - return self._filename - - @filename.setter - def filename(self, val): - cv.check_type('DAGMC filename', val, (Path, str)) - self._filename = val - - @property - def auto_geom_ids(self): - return self._auto_geom_ids - - @auto_geom_ids.setter - def auto_geom_ids(self, val): - cv.check_type('DAGMC automatic geometry ids', val, bool) - self._auto_geom_ids = val - - @property - def auto_mat_ids(self): - return self._auto_mat_ids - - @auto_mat_ids.setter - def auto_mat_ids(self, val): - cv.check_type('DAGMC automatic material ids', val, bool) - self._auto_mat_ids = val - - @property - def material_names(self): - dagmc_file_contents = h5py.File(self.filename) - material_tags_hex = dagmc_file_contents['/tstt/tags/NAME'].get( - 'values') - material_tags_ascii = [] - for tag in material_tags_hex: - candidate_tag = tag.tobytes().decode().replace('\x00', '') - # tags might be for temperature or reflective surfaces - if candidate_tag.startswith('mat:'): - # removes first 4 characters as openmc.Material name should be - # set without the 'mat:' part of the tag - material_tags_ascii.append(candidate_tag[4:]) - - return sorted(set(material_tags_ascii)) - - def get_all_cells(self, memo=None): - return {} - - def get_all_materials(self, memo=None): - return {} + def bounding_box(self) -> openmc.BoundingBox: + regions = [c.region for c in self.cells.values() + if c.region is not None] + if regions: + return openmc.Union(regions).bounding_box + else: + return openmc.BoundingBox.infinite() - def _n_geom_elements(self, geom_type): - """ - Helper function for retrieving the number geometric entities in a DAGMC - file + @classmethod + def from_hdf5(cls, group, cells): + """Create universe from HDF5 group Parameters ---------- - geom_type : str - The type of geometric entity to count. One of {'Volume', 'Surface'}. Returns - the runtime number of voumes in the DAGMC model (includes implicit complement). + group : h5py.Group + Group in HDF5 file + cells : dict + Dictionary mapping cell IDs to instances of :class:`openmc.Cell`. Returns ------- - int - Number of geometry elements of the specified type - """ - cv.check_value('geometry type', geom_type, ('volume', 'surface')) - - def decode_str_tag(tag_val): - return tag_val.tobytes().decode().replace('\x00', '') - - dagmc_filepath = Path(self.filename).resolve() - with h5py.File(dagmc_filepath) as dagmc_file: - category_data = dagmc_file['tstt/tags/CATEGORY/values'] - category_strs = map(decode_str_tag, category_data) - n = sum([v == geom_type.capitalize() for v in category_strs]) - - # check for presence of an implicit complement in the file and - # increment the number of cells if it doesn't exist - if geom_type == 'volume': - name_data = dagmc_file['tstt/tags/NAME/values'] - name_strs = map(decode_str_tag, name_data) - if not sum(['impl_complement' in n for n in name_strs]): - n += 1 - return n + openmc.Universe + Universe instance - @property - def n_cells(self): - return self._n_geom_elements('volume') + """ + universe_id = int(group.name.split('/')[-1].lstrip('universe ')) + cell_ids = group['cells'][()] - @property - def n_surfaces(self): - return self._n_geom_elements('surface') + # Create this Universe + universe = cls(universe_id) - def create_xml_subelement(self, xml_element, memo=None): - if memo is None: - memo = set() + # Add each Cell to the Universe + for cell_id in cell_ids: + universe.add_cell(cells[cell_id]) - if self in memo: - return + return universe - memo.add(self) - # Set xml element values - dagmc_element = ET.Element('dagmc_universe') - dagmc_element.set('id', str(self.id)) - - if self.auto_geom_ids: - dagmc_element.set('auto_geom_ids', 'true') - if self.auto_mat_ids: - dagmc_element.set('auto_mat_ids', 'true') - dagmc_element.set('filename', str(self.filename)) - xml_element.append(dagmc_element) - - def bounding_region( - self, - bounded_type: str = 'box', - boundary_type: str = 'vacuum', - starting_id: int = 10000, - padding_distance: float = 0. - ): - """Creates a either a spherical or box shaped bounding region around - the DAGMC geometry. - - .. versionadded:: 0.13.1 + def add_cell(self, cell): + """Add a cell to the universe. Parameters ---------- - bounded_type : str - The type of bounding surface(s) to use when constructing the region. - Options include a single spherical surface (sphere) or a rectangle - made from six planes (box). - boundary_type : str - Boundary condition that defines the behavior for particles hitting - the surface. Defaults to vacuum boundary condition. Passed into the - surface construction. - starting_id : int - Starting ID of the surface(s) used in the region. For bounded_type - 'box', the next 5 IDs will also be used. Defaults to 10000 to reduce - the chance of an overlap of surface IDs with the DAGMC geometry. - padding_distance : float - Distance between the bounding region surfaces and the minimal - bounding box. Allows for the region to be larger than the DAGMC - geometry. + cell : openmc.Cell + Cell to add - Returns - ------- - openmc.Region - Region instance """ - check_type('boundary type', boundary_type, str) - check_value('boundary type', boundary_type, _BOUNDARY_TYPES) - check_type('starting surface id', starting_id, Integral) - check_type('bounded type', bounded_type, str) - check_value('bounded type', bounded_type, ('box', 'sphere')) - - bbox = self.bounding_box.expand(padding_distance, True) - - if bounded_type == 'sphere': - radius = np.linalg.norm(bbox.upper_right - bbox.center) - bounding_surface = openmc.Sphere( - surface_id=starting_id, - x0=bbox.center[0], - y0=bbox.center[1], - z0=bbox.center[2], - boundary_type=boundary_type, - r=radius, - ) - - return -bounding_surface - - if bounded_type == 'box': - # defines plane surfaces for all six faces of the bounding box - lower_x = openmc.XPlane(bbox[0][0], surface_id=starting_id) - upper_x = openmc.XPlane(bbox[1][0], surface_id=starting_id+1) - lower_y = openmc.YPlane(bbox[0][1], surface_id=starting_id+2) - upper_y = openmc.YPlane(bbox[1][1], surface_id=starting_id+3) - lower_z = openmc.ZPlane(bbox[0][2], surface_id=starting_id+4) - upper_z = openmc.ZPlane(bbox[1][2], surface_id=starting_id+5) - - region = +lower_x & -upper_x & +lower_y & -upper_y & +lower_z & -upper_z - - for surface in region.get_surfaces().values(): - surface.boundary_type = boundary_type - - return region - - def bounded_universe(self, bounding_cell_id=10000, **kwargs): - """Returns an openmc.Universe filled with this DAGMCUniverse and bounded - with a cell. Defaults to a box cell with a vacuum surface however this - can be changed using the kwargs which are passed directly to - DAGMCUniverse.bounding_region(). + if not isinstance(cell, openmc.Cell): + msg = f'Unable to add a Cell to Universe ID="{self._id}" since ' \ + f'"{cell}" is not a Cell' + raise TypeError(msg) - Parameters - ---------- - bounding_cell_id : int - The cell ID number to use for the bounding cell, defaults to 10000 to reduce - the chance of overlapping ID numbers with the DAGMC geometry. + cell_id = cell.id - Returns - ------- - openmc.Universe - Universe instance - """ - bounding_cell = openmc.Cell( - fill=self, cell_id=bounding_cell_id, region=self.bounding_region(**kwargs)) - return openmc.Universe(cells=[bounding_cell]) + if cell_id not in self._cells: + self._cells[cell_id] = cell - @classmethod - def from_hdf5(cls, group): - """Create DAGMC universe from HDF5 group + def remove_cell(self, cell): + """Remove a cell from the universe. Parameters ---------- - group : h5py.Group - Group in HDF5 file - - Returns - ------- - openmc.DAGMCUniverse - DAGMCUniverse instance + cell : openmc.Cell + Cell to remove """ - id = int(group.name.split('/')[-1].lstrip('universe ')) - fname = group['filename'][()].decode() - name = group['name'][()].decode() if 'name' in group else None - out = cls(fname, universe_id=id, name=name) - - out.auto_geom_ids = bool(group.attrs['auto_geom_ids']) - out.auto_mat_ids = bool(group.attrs['auto_mat_ids']) - - return out - - @classmethod - def from_xml_element(cls, elem): - """Generate DAGMC universe from XML element + if not isinstance(cell, openmc.Cell): + msg = f'Unable to remove a Cell from Universe ID="{self._id}" ' \ + f'since "{cell}" is not a Cell' + raise TypeError(msg) - Parameters - ---------- - elem : lxml.etree._Element - `` element + # If the Cell is in the Universe's list of Cells, delete it + self._cells.pop(cell.id, None) - Returns - ------- - openmc.DAGMCUniverse - DAGMCUniverse instance + def create_xml_subelement(self, xml_element, memo=None): + if memo is None: + memo = set() - """ - id = int(get_text(elem, 'id')) - fname = get_text(elem, 'filename') + # Iterate over all Cells + for cell in self._cells.values(): - out = cls(fname, universe_id=id) + # If the cell was already written, move on + if cell in memo: + continue - name = get_text(elem, 'name') - if name is not None: - out.name = name + memo.add(cell) - out.auto_geom_ids = bool(elem.get('auto_geom_ids')) - out.auto_mat_ids = bool(elem.get('auto_mat_ids')) + # Create XML subelement for this Cell + cell_element = cell.create_xml_subelement(xml_element, memo) - return out + # Append the Universe ID to the subelement and add to Element + cell_element.set("universe", str(self._id)) + xml_element.append(cell_element) def _partial_deepcopy(self): - """Clone all of the openmc.DAGMCUniverse object's attributes except for - its cells, as they are copied within the clone function. This should - only to be used within the openmc.UniverseBase.clone() context. + """Clone all of the openmc.Universe object's attributes except for its cells, + as they are copied within the clone function. This should only to be + used within the openmc.UniverseBase.clone() context. """ - clone = openmc.DAGMCUniverse(name=self.name, filename=self.filename) + clone = openmc.Universe(name=self.name) clone.volume = self.volume - clone.auto_geom_ids = self.auto_geom_ids - clone.auto_mat_ids = self.auto_mat_ids return clone diff --git a/openmc/utility_funcs.py b/openmc/utility_funcs.py index 4eb307c9303..da9f73b1651 100644 --- a/openmc/utility_funcs.py +++ b/openmc/utility_funcs.py @@ -2,12 +2,13 @@ import os from pathlib import Path from tempfile import TemporaryDirectory -from typing import Optional +import openmc from .checkvalue import PathLike + @contextmanager -def change_directory(working_dir: Optional[PathLike] = None, *, tmpdir: bool = False): +def change_directory(working_dir: PathLike | None = None, *, tmpdir: bool = False): """Context manager for executing in a provided working directory Parameters @@ -36,3 +37,23 @@ def change_directory(working_dir: Optional[PathLike] = None, *, tmpdir: bool = F os.chdir(orig_dir) if tmpdir: tmp.cleanup() + + +def input_path(filename: PathLike) -> Path: + """Return a path object for an input file based on global configuration + + Parameters + ---------- + filename : PathLike + Path to input file + + Returns + ------- + pathlib.Path + Path object + + """ + if openmc.config['resolve_paths']: + return Path(filename).resolve() + else: + return Path(filename) diff --git a/openmc/weight_windows.py b/openmc/weight_windows.py index 96f1db89282..5df9f71dcc7 100644 --- a/openmc/weight_windows.py +++ b/openmc/weight_windows.py @@ -1,6 +1,6 @@ from __future__ import annotations from numbers import Real, Integral -from typing import Iterable, List, Optional, Dict, Sequence +from collections.abc import Iterable, Sequence import warnings import lxml.etree as ET @@ -110,15 +110,15 @@ def __init__( self, mesh: MeshBase, lower_ww_bounds: Iterable[float], - upper_ww_bounds: Optional[Iterable[float]] = None, - upper_bound_ratio: Optional[float] = None, - energy_bounds: Optional[Iterable[Real]] = None, + upper_ww_bounds: Iterable[float] | None = None, + upper_bound_ratio: float | None = None, + energy_bounds: Iterable[Real] | None = None, particle_type: str = 'neutron', survival_ratio: float = 3, - max_lower_bound_ratio: Optional[float] = None, + max_lower_bound_ratio: float | None = None, max_split: int = 10, weight_cutoff: float = 1.e-38, - id: Optional[int] = None + id: int | None = None ): self.mesh = mesh self.id = id @@ -328,8 +328,9 @@ def to_xml_element(self) -> ET.Element: subelement = ET.SubElement(element, 'particle_type') subelement.text = self.particle_type - subelement = ET.SubElement(element, 'energy_bounds') - subelement.text = ' '.join(str(e) for e in self.energy_bounds) + if self.energy_bounds is not None: + subelement = ET.SubElement(element, 'energy_bounds') + subelement.text = ' '.join(str(e) for e in self.energy_bounds) subelement = ET.SubElement(element, 'lower_ww_bounds') subelement.text = ' '.join(str(b) for b in self.lower_ww_bounds.ravel('F')) @@ -353,7 +354,7 @@ def to_xml_element(self) -> ET.Element: return element @classmethod - def from_xml_element(cls, elem: ET.Element, meshes: Dict[int, MeshBase]) -> WeightWindows: + def from_xml_element(cls, elem: ET.Element, meshes: dict[int, MeshBase]) -> WeightWindows: """Generate weight window settings from an XML element Parameters @@ -407,7 +408,7 @@ def from_xml_element(cls, elem: ET.Element, meshes: Dict[int, MeshBase]) -> Weig ) @classmethod - def from_hdf5(cls, group: h5py.Group, meshes: Dict[int, MeshBase]) -> WeightWindows: + def from_hdf5(cls, group: h5py.Group, meshes: dict[int, MeshBase]) -> WeightWindows: """Create weight windows from HDF5 group Parameters @@ -457,7 +458,7 @@ def from_hdf5(cls, group: h5py.Group, meshes: Dict[int, MeshBase]) -> WeightWind ) -def wwinp_to_wws(path: PathLike) -> List[WeightWindows]: +def wwinp_to_wws(path: PathLike) -> list[WeightWindows]: """Create WeightWindows instances from a wwinp file .. versionadded:: 0.13.1 @@ -698,7 +699,7 @@ class WeightWindowGenerator: def __init__( self, mesh: openmc.MeshBase, - energy_bounds: Optional[Sequence[float]] = None, + energy_bounds: Sequence[float] | None = None, particle_type: str = 'neutron', method: str = 'magic', max_realizations: int = 1, diff --git a/pyproject.toml b/pyproject.toml index d5970617a74..e4e8472f1aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,77 @@ [build-system] -requires = ["setuptools", "wheel", "numpy", "cython"] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "openmc" +authors = [ + {name = "The OpenMC Development Team", email = "openmc@anl.gov"}, +] +description = "OpenMC" +version = "0.15.1-dev" +requires-python = ">=3.11" +license = {file = "LICENSE"} +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Topic :: Scientific/Engineering", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "numpy", + "h5py", + "scipy", + "ipython", + "matplotlib", + "pandas", + "lxml", + "uncertainties", + "setuptools", + "endf", +] + +[project.optional-dependencies] +depletion-mpi = ["mpi4py"] +docs = [ + "sphinx==5.0.2", + "sphinxcontrib-katex", + "sphinx-numfig", + "jupyter", + "sphinxcontrib-svg2pdfconverter", + "sphinx-rtd-theme==1.0.0" +] +test = ["packaging", "pytest", "pytest-cov", "colorama", "openpyxl"] +ci = ["cpp-coveralls", "coveralls"] +vtk = ["vtk"] + +[project.urls] +Homepage = "https://openmc.org" +Documentation = "https://docs.openmc.org" +Repository = "https://github.com/openmc-dev/openmc" +Issues = "https://github.com/openmc-dev/openmc/issues" + +[tool.setuptools.packages.find] +include = ['openmc*', 'scripts*'] +exclude = ['tests*'] + +[tool.setuptools.package-data] +"openmc.data.effective_dose" = ["**/*.txt"] +"openmc.data" = ["*.txt", "*.DAT", "*.json", "*.h5"] +"openmc.lib" = ["libopenmc.dylib", "libopenmc.so"] + +[project.scripts] +openmc-ace-to-hdf5 = "scripts.openmc_ace_to_hdf5:main" +openmc-plot-mesh-tally = "scripts.openmc_plot_mesh_tally:main" +openmc-track-combine = "scripts.openmc_track_combine:main" +openmc-track-to-vtk = "scripts.openmc_track_to_vtk:main" +openmc-update-inputs = "scripts.openmc_update_inputs:main" +openmc-update-mgxs = "scripts.openmc_update_mgxs:main" +openmc-voxel-to-vtk = "scripts.openmc_voxel_to_vtk:main" diff --git a/scripts/openmc-update-inputs b/scripts/openmc-update-inputs index 2d49c0626e2..5ee1f1ca0bc 100755 --- a/scripts/openmc-update-inputs +++ b/scripts/openmc-update-inputs @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -"""Update OpenMC's input XML files to the latest format. - -""" +"""Update OpenMC's input XML files to the latest format.""" import argparse from itertools import chain @@ -201,6 +199,16 @@ def update_geometry(geometry_root): was_updated = True + for surface in root.findall('surface'): + for attribute in ['type', 'coeffs', 'boundary']: + if surface.find(attribute) is not None: + value = surface.find(attribute).text + surface.attrib[attribute] = value + + child_element = surface.find(attribute) + surface.remove(child_element) + was_updated = True + # Remove 'type' from lattice definitions. for lat in root.iter('lattice'): elem = lat.find('type') @@ -296,4 +304,4 @@ if __name__ == '__main__': move(fname, fname + '.original') # Write a new geometry file. - tree.write(fname, xml_declaration=True) + tree.write(fname, xml_declaration=True) \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index a33037ad3e6..00000000000 --- a/setup.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python - -import glob -import sys -import numpy as np - -from setuptools import setup, find_packages -from Cython.Build import cythonize - - -# Determine shared library suffix -if sys.platform == 'darwin': - suffix = 'dylib' -else: - suffix = 'so' - -# Get version information from __init__.py. This is ugly, but more reliable than -# using an import. -with open('openmc/__init__.py', 'r') as f: - version = f.readlines()[-1].split()[-1].strip("'") - -kwargs = { - 'name': 'openmc', - 'version': version, - 'packages': find_packages(exclude=['tests*']), - 'scripts': glob.glob('scripts/openmc-*'), - - # Data files and libraries - 'package_data': { - 'openmc.lib': ['libopenmc.{}'.format(suffix)], - 'openmc.data': ['mass_1.mas20.txt', 'BREMX.DAT', 'half_life.json', '*.h5'], - 'openmc.data.effective_dose': ['*.txt'] - }, - - # Metadata - 'author': 'The OpenMC Development Team', - 'author_email': 'openmc@anl.gov', - 'description': 'OpenMC', - 'url': 'https://openmc.org', - 'download_url': 'https://github.com/openmc-dev/openmc/releases', - 'project_urls': { - 'Issue Tracker': 'https://github.com/openmc-dev/openmc/issues', - 'Documentation': 'https://docs.openmc.org', - 'Source Code': 'https://github.com/openmc-dev/openmc', - }, - 'classifiers': [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Topic :: Scientific/Engineering' - 'Programming Language :: C++', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - ], - - # Dependencies - 'python_requires': '>=3.8', - 'install_requires': [ - 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', - 'pandas', 'lxml', 'uncertainties', 'setuptools' - ], - 'extras_require': { - 'depletion-mpi': ['mpi4py'], - 'docs': ['sphinx', 'sphinxcontrib-katex', 'sphinx-numfig', 'jupyter', - 'sphinxcontrib-svg2pdfconverter', 'sphinx-rtd-theme'], - 'test': ['pytest', 'pytest-cov', 'colorama', 'openpyxl'], - 'vtk': ['vtk'], - }, - # Cython is used to add resonance reconstruction and fast float_endf - 'ext_modules': cythonize('openmc/data/*.pyx'), - 'include_dirs': [np.get_include()] -} - -setup(**kwargs) diff --git a/src/cell.cpp b/src/cell.cpp index a46f05687eb..d4d28fb70e6 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -252,12 +252,12 @@ void Cell::to_hdf5(hid_t cell_group) const // default constructor CSGCell::CSGCell() { - geom_type_ = GeometryType::CSG; + geom_type() = GeometryType::CSG; } CSGCell::CSGCell(pugi::xml_node cell_node) { - geom_type_ = GeometryType::CSG; + geom_type() = GeometryType::CSG; if (check_for_node(cell_node, "id")) { id_ = std::stoi(get_node_value(cell_node, "id")); @@ -578,17 +578,14 @@ void Region::apply_demorgan( //! precedence than unions using parentheses. //============================================================================== -std::vector::iterator Region::add_parentheses( - std::vector::iterator start) +gsl::index Region::add_parentheses(gsl::index start) { - int32_t start_token = *start; - // Add left parenthesis - if (start_token == OP_INTERSECTION) { - start = expression_.insert(start - 1, OP_LEFT_PAREN); - } else { - start = expression_.insert(start + 1, OP_LEFT_PAREN); + int32_t start_token = expression_[start]; + // Add left parenthesis and set new position to be after parenthesis + if (start_token == OP_UNION) { + start += 2; } - start++; + expression_.insert(expression_.begin() + start - 1, OP_LEFT_PAREN); // Keep track of return iterator distance. If we don't encounter a left // parenthesis, we return an iterator corresponding to wherever the right @@ -600,23 +597,23 @@ std::vector::iterator Region::add_parentheses( // Add right parenthesis // While the start iterator is within the bounds of infix - while (start < expression_.end()) { + while (start + 1 < expression_.size()) { start++; // If the current token is an operator and is different than the start token - if (*start >= OP_UNION && *start != start_token) { + if (expression_[start] >= OP_UNION && expression_[start] != start_token) { // Skip wrapped regions but save iterator position to check precedence and // add right parenthesis, right parenthesis position depends on the // operator, when the operator is a union then do not include the operator // in the region, when the operator is an intersection then include the // operator and next surface - if (*start == OP_LEFT_PAREN) { - return_it_dist = std::distance(expression_.begin(), start); + if (expression_[start] == OP_LEFT_PAREN) { + return_it_dist = start; int depth = 1; do { start++; - if (*start > OP_COMPLEMENT) { - if (*start == OP_RIGHT_PAREN) { + if (expression_[start] > OP_COMPLEMENT) { + if (expression_[start] == OP_RIGHT_PAREN) { depth--; } else { depth++; @@ -624,10 +621,12 @@ std::vector::iterator Region::add_parentheses( } } while (depth > 0); } else { - start = expression_.insert( - start_token == OP_UNION ? start - 1 : start, OP_RIGHT_PAREN); + if (start_token == OP_UNION) { + --start; + } + expression_.insert(expression_.begin() + start, OP_RIGHT_PAREN); if (return_it_dist > 0) { - return expression_.begin() + return_it_dist; + return return_it_dist; } else { return start - 1; } @@ -638,7 +637,7 @@ std::vector::iterator Region::add_parentheses( // return iterator expression_.push_back(OP_RIGHT_PAREN); if (return_it_dist > 0) { - return expression_.begin() + return_it_dist; + return return_it_dist; } else { return start - 1; } @@ -651,21 +650,21 @@ void Region::add_precedence() int32_t current_op = 0; std::size_t current_dist = 0; - for (auto it = expression_.begin(); it != expression_.end(); it++) { - int32_t token = *it; + for (gsl::index i = 0; i < expression_.size(); i++) { + int32_t token = expression_[i]; if (token == OP_UNION || token == OP_INTERSECTION) { if (current_op == 0) { // Set the current operator if is hasn't been set current_op = token; - current_dist = std::distance(expression_.begin(), it); + current_dist = i; } else if (token != current_op) { // If the current operator doesn't match the token, add parenthesis to // assert precedence if (current_op == OP_INTERSECTION) { - it = add_parentheses(expression_.begin() + current_dist); + i = add_parentheses(current_dist); } else { - it = add_parentheses(it); + i = add_parentheses(i); } current_op = 0; current_dist = 0; diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index a7bd86095b3..248ae9019d9 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -283,6 +283,10 @@ void read_ce_cross_sections_xml() { // Check if cross_sections.xml exists const auto& filename = settings::path_cross_sections; + if (dir_exists(filename)) { + fatal_error("OPENMC_CROSS_SECTIONS is set to a directory. " + "It should be set to an XML file."); + } if (!file_exists(filename)) { // Could not find cross_sections.xml file fatal_error("Cross sections XML file '" + filename + "' does not exist."); diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 2f2502f6ea1..134e31ecf3c 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -11,7 +11,7 @@ #include "openmc/settings.h" #include "openmc/string_utils.h" -#ifdef UWUW +#ifdef OPENMC_UWUW #include "uwuw.hpp" #endif #include @@ -29,7 +29,7 @@ const bool DAGMC_ENABLED = true; const bool DAGMC_ENABLED = false; #endif -#ifdef UWUW +#ifdef OPENMC_UWUW const bool UWUW_ENABLED = true; #else const bool UWUW_ENABLED = false; @@ -72,6 +72,23 @@ DAGUniverse::DAGUniverse(pugi::xml_node node) adjust_material_ids_ = get_node_value_bool(node, "auto_mat_ids"); } + // get material assignment overloading + if (check_for_node(node, "material_overrides")) { + auto mat_node = node.child("material_overrides"); + // loop over all subelements (each subelement corresponds to a material) + for (pugi::xml_node cell_node : mat_node.children("cell_override")) { + // Store assignment reference name + int32_t ref_assignment = std::stoi(get_node_value(cell_node, "id")); + + // Get mat name for each assignement instances + vector instance_mats = + get_node_array(cell_node, "material_ids"); + + // Store mat name for each instances + material_overrides_.emplace(ref_assignment, instance_mats); + } + } + initialize(); } @@ -112,6 +129,11 @@ void DAGUniverse::initialize() { geom_type() = GeometryType::DAG; +#ifdef OPENMC_UWUW + // read uwuw materials from the .h5m file if present + read_uwuw_materials(); +#endif + init_dagmc(); init_metadata(); @@ -206,12 +228,13 @@ void DAGUniverse::init_geometry() if (mat_str == "graveyard") { graveyard = vol_handle; } - // material void checks if (mat_str == "void" || mat_str == "vacuum" || mat_str == "graveyard") { c->material_.push_back(MATERIAL_VOID); } else { - if (uses_uwuw()) { + if (material_overrides_.count(c->id_)) { + override_assign_material(c); + } else if (uses_uwuw()) { uwuw_assign_material(vol_handle, c); } else { legacy_assign_material(mat_str, c); @@ -271,8 +294,10 @@ void DAGUniverse::init_geometry() : dagmc_instance_->id_by_index(2, i + 1); // set surface source attribute if needed - if (contains(settings::source_write_surf_id, s->id_)) + if (contains(settings::source_write_surf_id, s->id_) || + settings::source_write_surf_id.empty()) { s->surf_source_ = true; + } // set BCs std::string bc_value = @@ -429,16 +454,16 @@ void DAGUniverse::to_hdf5(hid_t universes_group) const bool DAGUniverse::uses_uwuw() const { -#ifdef UWUW +#ifdef OPENMC_UWUW return uwuw_ && !uwuw_->material_library.empty(); #else return false; -#endif // UWUW +#endif // OPENMC_UWUW } std::string DAGUniverse::get_uwuw_materials_xml() const { -#ifdef UWUW +#ifdef OPENMC_UWUW if (!uses_uwuw()) { throw std::runtime_error("This DAGMC Universe does not use UWUW materials"); } @@ -458,12 +483,12 @@ std::string DAGUniverse::get_uwuw_materials_xml() const return ss.str(); #else fatal_error("DAGMC was not configured with UWUW."); -#endif // UWUW +#endif // OPENMC_UWUW } void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const { -#ifdef UWUW +#ifdef OPENMC_UWUW if (!uses_uwuw()) { throw std::runtime_error( "This DAGMC universe does not use UWUW materials."); @@ -476,7 +501,7 @@ void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const mats_xml.close(); #else fatal_error("DAGMC was not configured with UWUW."); -#endif +#endif // OPENMC_UWUW } void DAGUniverse::legacy_assign_material( @@ -538,7 +563,7 @@ void DAGUniverse::legacy_assign_material( void DAGUniverse::read_uwuw_materials() { -#ifdef UWUW +#ifdef OPENMC_UWUW // If no filename was provided, don't read UWUW materials if (filename_ == "") return; @@ -578,16 +603,13 @@ void DAGUniverse::read_uwuw_materials() } #else fatal_error("DAGMC was not configured with UWUW."); -#endif +#endif // OPENMC_UWUW } void DAGUniverse::uwuw_assign_material( moab::EntityHandle vol_handle, std::unique_ptr& c) const { -#ifdef UWUW - // read materials from uwuw material file - read_uwuw_materials(); - +#ifdef OPENMC_UWUW // lookup material in uwuw if present std::string uwuw_mat = dmd_ptr->volume_material_property_data_eh[vol_handle]; if (uwuw_->material_library.count(uwuw_mat) != 0) { @@ -599,12 +621,39 @@ void DAGUniverse::uwuw_assign_material( } else { fatal_error(fmt::format("Material with value '{}' not found in the " "UWUW material library", - mat_str)); + uwuw_mat)); } #else fatal_error("DAGMC was not configured with UWUW."); -#endif +#endif // OPENMC_UWUW } + +void DAGUniverse::override_assign_material(std::unique_ptr& c) const +{ + // if Cell ID matches an override key, use it to override the material + // assignment else if UWUW is used, get the material assignment from the DAGMC + // metadata + // Notify User that an override is being applied on a DAGMCCell + write_message(fmt::format("Applying override for DAGMCCell {}", c->id_), 8); + + if (settings::verbosity >= 10) { + auto msg = fmt::format("Assigning DAGMC cell {} material(s) based on " + "override information (see input XML).", + c->id_); + write_message(msg, 10); + } + + // Override the material assignment for each cell instance using the legacy + // assignement + for (auto mat_id : material_overrides_.at(c->id_)) { + if (model::material_map.find(mat_id) == model::material_map.end()) { + fatal_error(fmt::format( + "Material with ID '{}' not found for DAGMC cell {}", mat_id, c->id_)); + } + c->material_.push_back(mat_id); + } +} + //============================================================================== // DAGMC Cell implementation //============================================================================== @@ -612,7 +661,7 @@ void DAGUniverse::uwuw_assign_material( DAGCell::DAGCell(std::shared_ptr dag_ptr, int32_t dag_idx) : Cell {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx) { - geom_type_ = GeometryType::DAG; + geom_type() = GeometryType::DAG; }; std::pair DAGCell::distance( @@ -715,7 +764,7 @@ BoundingBox DAGCell::bounding_box() const DAGSurface::DAGSurface(std::shared_ptr dag_ptr, int32_t dag_idx) : Surface {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx) { - geom_type_ = GeometryType::DAG; + geom_type() = GeometryType::DAG; } // empty constructor moab::EntityHandle DAGSurface::mesh_handle() const @@ -814,12 +863,61 @@ int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ) return univp->cell_index(new_vol); } +extern "C" int openmc_dagmc_universe_get_cell_ids( + int32_t univ_id, int32_t* ids, size_t* n) +{ + // make sure the universe id is a DAGMC Universe + const auto& univ = model::universes[model::universe_map[univ_id]]; + if (univ->geom_type() != GeometryType::DAG) { + set_errmsg(fmt::format("Universe {} is not a DAGMC Universe", univ_id)); + return OPENMC_E_INVALID_TYPE; + } + + std::vector dag_cell_ids; + for (const auto& cell_index : univ->cells_) { + const auto& cell = model::cells[cell_index]; + if (cell->geom_type() == GeometryType::CSG) { + set_errmsg(fmt::format("Cell {} is not a DAGMC Cell", cell->id_)); + return OPENMC_E_INVALID_TYPE; + } + dag_cell_ids.push_back(cell->id_); + } + std::copy(dag_cell_ids.begin(), dag_cell_ids.end(), ids); + *n = dag_cell_ids.size(); + return 0; +} + +extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n) +{ + // make sure the universe id is a DAGMC Universe + const auto& univ = model::universes[model::universe_map[univ_id]]; + if (univ->geom_type() != GeometryType::DAG) { + set_errmsg(fmt::format("Universe {} is not a DAGMC universe", univ_id)); + return OPENMC_E_INVALID_TYPE; + } + *n = univ->cells_.size(); + return 0; +} + } // namespace openmc #else namespace openmc { +extern "C" int openmc_dagmc_universe_get_cell_ids( + int32_t univ_id, int32_t* ids, size_t* n) +{ + set_errmsg("OpenMC was not configured with DAGMC"); + return OPENMC_E_UNASSIGNED; +}; + +extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n) +{ + set_errmsg("OpenMC was not configured with DAGMC"); + return OPENMC_E_UNASSIGNED; +}; + void read_dagmc_universes(pugi::xml_node node) { if (check_for_node(node, "dagmc_universe")) { diff --git a/src/distribution.cpp b/src/distribution.cpp index 3026630b335..a6b4acd58b1 100644 --- a/src/distribution.cpp +++ b/src/distribution.cpp @@ -394,6 +394,9 @@ Mixture::Mixture(pugi::xml_node node) distribution_.push_back(std::make_pair(cumsum, std::move(dist))); } + // Save integral of distribution + integral_ = cumsum; + // Normalize cummulative probabilities to 1 for (auto& pair : distribution_) { pair.first /= cumsum; diff --git a/src/distribution_spatial.cpp b/src/distribution_spatial.cpp index 8dbd7d88706..5c193a95d29 100644 --- a/src/distribution_spatial.cpp +++ b/src/distribution_spatial.cpp @@ -26,6 +26,8 @@ unique_ptr SpatialDistribution::create(pugi::xml_node node) return UPtrSpace {new SphericalIndependent(node)}; } else if (type == "mesh") { return UPtrSpace {new MeshSpatial(node)}; + } else if (type == "cloud") { + return UPtrSpace {new PointCloud(node)}; } else if (type == "box") { return UPtrSpace {new SpatialBox(node)}; } else if (type == "fission") { @@ -298,6 +300,49 @@ Position MeshSpatial::sample(uint64_t* seed) const return this->sample_mesh(seed).second; } +//============================================================================== +// PointCloud implementation +//============================================================================== + +PointCloud::PointCloud(pugi::xml_node node) +{ + if (check_for_node(node, "coords")) { + point_cloud_ = get_node_position_array(node, "coords"); + } else { + fatal_error("No coordinates were provided for the PointCloud " + "spatial distribution"); + } + + std::vector strengths; + + if (check_for_node(node, "strengths")) + strengths = get_node_array(node, "strengths"); + else + strengths.resize(point_cloud_.size(), 1.0); + + if (strengths.size() != point_cloud_.size()) { + fatal_error( + fmt::format("Number of entries for the strengths array {} does " + "not match the number of spatial points provided {}.", + strengths.size(), point_cloud_.size())); + } + + point_idx_dist_.assign(strengths); +} + +PointCloud::PointCloud( + std::vector point_cloud, gsl::span strengths) +{ + point_cloud_.assign(point_cloud.begin(), point_cloud.end()); + point_idx_dist_.assign(strengths); +} + +Position PointCloud::sample(uint64_t* seed) const +{ + int32_t index = point_idx_dist_.sample(seed); + return point_cloud_[index]; +} + //============================================================================== // SpatialBox implementation //============================================================================== diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index d5410094b18..8669d76f947 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -556,7 +556,7 @@ void shannon_entropy() double H = 0.0; for (auto p_i : p) { if (p_i > 0.0) { - H -= p_i * std::log(p_i) / std::log(2.0); + H -= p_i * std::log2(p_i); } } diff --git a/src/external/quartic_solver.cpp b/src/external/quartic_solver.cpp index 0b280e83c29..915020ffaa3 100644 --- a/src/external/quartic_solver.cpp +++ b/src/external/quartic_solver.cpp @@ -1,4 +1,5 @@ #include +#define _USE_MATH_DEFINES // to make M_PI declared in Intel and MSVC compilers #include #include #include diff --git a/src/finalize.cpp b/src/finalize.cpp index 26efc9723a5..981ec5cbaf1 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -91,8 +91,8 @@ int openmc_finalize() settings::max_lost_particles = 10; settings::max_order = 0; settings::max_particles_in_flight = 100000; - settings::max_particle_events = 1000000; - settings::max_splits = 1000; + settings::max_particle_events = 1'000'000; + settings::max_history_splits = 10'000'000; settings::max_tracks = 1000; settings::max_write_lost_particles = -1; settings::n_log_bins = 8000; @@ -120,6 +120,10 @@ int openmc_finalize() settings::source_latest = false; settings::source_separate = false; settings::source_write = true; + settings::ssw_cell_id = C_NONE; + settings::ssw_cell_type = SSWCellType::None; + settings::ssw_max_particles = 0; + settings::ssw_max_files = 1; settings::survival_biasing = false; settings::temperature_default = 293.6; settings::temperature_method = TemperatureMethod::NEAREST; @@ -129,6 +133,7 @@ int openmc_finalize() settings::trigger_on = false; settings::trigger_predict = false; settings::trigger_batch_interval = 1; + settings::uniform_source_sampling = false; settings::ufs_on = false; settings::urr_ptables_on = true; settings::verbosity = 7; @@ -141,6 +146,7 @@ int openmc_finalize() simulation::keff = 1.0; simulation::need_depletion_rx = false; + simulation::ssw_current_file = 1; simulation::total_gen = 0; simulation::entropy_mesh = nullptr; diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 10b239be838..050d4db968c 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -530,7 +530,8 @@ std::string distribcell_path_inner(int32_t target_cell, int32_t map, if (c.type_ != Fill::MATERIAL) { int32_t temp_offset; if (c.type_ == Fill::UNIVERSE) { - temp_offset = offset + c.offset_[map]; + temp_offset = + offset + c.offset_[map]; // TODO: should also apply to lattice fills? } else { Lattice& lat = *model::lattices[c.fill_]; int32_t indx = lat.universes_.size() * map + lat.begin().indx_; diff --git a/src/lattice.cpp b/src/lattice.cpp index fa2e2828ecf..73005ad096c 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -58,6 +58,11 @@ LatticeIter Lattice::end() return LatticeIter(*this, universes_.size()); } +int32_t& Lattice::back() +{ + return universes_.back(); +} + ReverseLatticeIter Lattice::rbegin() { return ReverseLatticeIter(*this, universes_.size() - 1); @@ -106,9 +111,10 @@ int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, // offsets_ array doesn't actually include the offset accounting for the last // universe, so we get the before-last offset for the given map and then // explicitly add the count for the last universe. - if (offsets_[map * universes_.size()] != C_NONE) { - int last_offset = offsets_[(map + 1) * universes_.size() - 1]; - int last_univ = universes_.back(); + if (offsets_[map * universes_.size() + this->begin().indx_] != C_NONE) { + int last_offset = + offsets_[(map + 1) * universes_.size() - this->begin().indx_ - 1]; + int last_univ = this->back(); return last_offset + count_universe_instances(last_univ, target_univ_id, univ_count_memo); } @@ -117,6 +123,7 @@ int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, offsets_[map * universes_.size() + it.indx_] = offset; offset += count_universe_instances(*it, target_univ_id, univ_count_memo); } + return offset; } @@ -225,14 +232,14 @@ RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node} //============================================================================== -int32_t const& RectLattice::operator[](array const& i_xyz) +const int32_t& RectLattice::operator[](const array& i_xyz) { return universes_[get_flat_index(i_xyz)]; } //============================================================================== -bool RectLattice::are_valid_indices(array const& i_xyz) const +bool RectLattice::are_valid_indices(const array& i_xyz) const { return ((i_xyz[0] >= 0) && (i_xyz[0] < n_cells_[0]) && (i_xyz[1] >= 0) && (i_xyz[1] < n_cells_[1]) && (i_xyz[2] >= 0) && @@ -354,7 +361,7 @@ Position RectLattice::get_local_position( //============================================================================== -int32_t& RectLattice::offset(int map, array const& i_xyz) +int32_t& RectLattice::offset(int map, const array& i_xyz) { return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + n_cells_[0] * n_cells_[1] * i_xyz[2] + @@ -676,13 +683,19 @@ void HexLattice::fill_lattice_y(const vector& univ_words) //============================================================================== -int32_t const& HexLattice::operator[](array const& i_xyz) +const int32_t& HexLattice::operator[](const array& i_xyz) { return universes_[get_flat_index(i_xyz)]; } //============================================================================== +// The HexLattice iterators need their own versions b/c the universes array is +// "square", meaning that it is allocated with entries that are intentionally +// left empty. As such, the iterator indices need to skip the empty entries to +// get cell instances and geometry paths correct. See the image in the Theory +// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where +// the empty positions are. LatticeIter HexLattice::begin() { return LatticeIter(*this, n_rings_ - 1); @@ -693,9 +706,24 @@ ReverseLatticeIter HexLattice::rbegin() return ReverseLatticeIter(*this, universes_.size() - n_rings_); } +int32_t& HexLattice::back() +{ + return universes_[universes_.size() - n_rings_]; +} + +LatticeIter HexLattice::end() +{ + return LatticeIter(*this, universes_.size() - n_rings_ + 1); +} + +ReverseLatticeIter HexLattice::rend() +{ + return ReverseLatticeIter(*this, n_rings_ - 2); +} + //============================================================================== -bool HexLattice::are_valid_indices(array const& i_xyz) const +bool HexLattice::are_valid_indices(const array& i_xyz) const { // Check if (x, alpha, z) indices are valid, accounting for number of rings return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) && @@ -992,7 +1020,7 @@ bool HexLattice::is_valid_index(int indx) const //============================================================================== -int32_t& HexLattice::offset(int map, array const& i_xyz) +int32_t& HexLattice::offset(int map, const array& i_xyz) { int nx {2 * n_rings_ - 1}; int ny {2 * n_rings_ - 1}; diff --git a/src/mesh.cpp b/src/mesh.cpp index c57dd5dcc31..97bf710caa6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,7 +1,8 @@ #include "openmc/mesh.h" -#include // for copy, equal, min, min_element -#include // for ceil -#include // for size_t +#include // for copy, equal, min, min_element +#define _USE_MATH_DEFINES // to make M_PI declared in Intel and MSVC compilers +#include // for ceil +#include // for size_t #include #include @@ -109,6 +110,8 @@ Mesh::Mesh(pugi::xml_node node) { // Read mesh id id_ = std::stoi(get_node_value(node, "id")); + if (check_for_node(node, "name")) + name_ = get_node_value(node, "name"); } void Mesh::set_id(int32_t id) @@ -236,6 +239,28 @@ vector Mesh::material_volumes( return result; } +void Mesh::to_hdf5(hid_t group) const +{ + // Create group for mesh + std::string group_name = fmt::format("mesh {}", id_); + hid_t mesh_group = create_group(group, group_name.c_str()); + + // Write mesh type + write_dataset(mesh_group, "type", this->get_mesh_type()); + + // Write mesh ID + write_attribute(mesh_group, "id", id_); + + // Write mesh name + write_dataset(mesh_group, "name", name_); + + // Write mesh data + this->to_hdf5_inner(mesh_group); + + // Close group + close_group(mesh_group); +} + //============================================================================== // Structured Mesh implementation //============================================================================== @@ -283,7 +308,6 @@ Position StructuredMesh::sample_element( UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) { - // check the mesh type if (check_for_node(node, "type")) { auto temp = get_node_value(node, "type", true, true); @@ -319,6 +343,28 @@ UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) } } +void UnstructuredMesh::determine_bounds() +{ + double xmin = INFTY; + double ymin = INFTY; + double zmin = INFTY; + double xmax = -INFTY; + double ymax = -INFTY; + double zmax = -INFTY; + int n = this->n_vertices(); + for (int i = 0; i < n; ++i) { + auto v = this->vertex(i); + xmin = std::min(v.x, xmin); + ymin = std::min(v.y, ymin); + zmin = std::min(v.z, zmin); + xmax = std::max(v.x, xmax); + ymax = std::max(v.y, ymax); + zmax = std::max(v.z, zmax); + } + lower_left_ = {xmin, ymin, zmin}; + upper_right_ = {xmax, ymax, zmax}; +} + Position UnstructuredMesh::sample_tet( std::array coords, uint64_t* seed) const { @@ -367,11 +413,8 @@ std::string UnstructuredMesh::bin_label(int bin) const return fmt::format("Mesh Index ({})", bin); }; -void UnstructuredMesh::to_hdf5(hid_t group) const +void UnstructuredMesh::to_hdf5_inner(hid_t mesh_group) const { - hid_t mesh_group = create_group(group, fmt::format("mesh {}", id_)); - - write_dataset(mesh_group, "type", mesh_type); write_dataset(mesh_group, "filename", filename_); write_dataset(mesh_group, "library", this->library()); if (!options_.empty()) { @@ -431,8 +474,6 @@ void UnstructuredMesh::to_hdf5(hid_t group) const write_dataset(mesh_group, "volumes", volumes); write_dataset(mesh_group, "connectivity", connectivity); write_dataset(mesh_group, "element_types", elem_types); - - close_group(mesh_group); } void UnstructuredMesh::set_length_multiplier(double length_multiplier) @@ -926,17 +967,12 @@ std::pair, vector> RegularMesh::plot( return {axis_lines[0], axis_lines[1]}; } -void RegularMesh::to_hdf5(hid_t group) const +void RegularMesh::to_hdf5_inner(hid_t mesh_group) const { - hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); - - write_dataset(mesh_group, "type", "regular"); write_dataset(mesh_group, "dimension", get_x_shape()); write_dataset(mesh_group, "lower_left", lower_left_); write_dataset(mesh_group, "upper_right", upper_right_); write_dataset(mesh_group, "width", width_); - - close_group(mesh_group); } xt::xtensor RegularMesh::count_sites( @@ -1116,16 +1152,11 @@ std::pair, vector> RectilinearMesh::plot( return {axis_lines[0], axis_lines[1]}; } -void RectilinearMesh::to_hdf5(hid_t group) const +void RectilinearMesh::to_hdf5_inner(hid_t mesh_group) const { - hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); - - write_dataset(mesh_group, "type", "rectilinear"); write_dataset(mesh_group, "x_grid", grid_[0]); write_dataset(mesh_group, "y_grid", grid_[1]); write_dataset(mesh_group, "z_grid", grid_[2]); - - close_group(mesh_group); } double RectilinearMesh::volume(const MeshIndex& ijk) const @@ -1372,8 +1403,10 @@ int CylindricalMesh::set_grid() full_phi_ = (grid_[1].front() == 0.0) && (grid_[1].back() == 2.0 * PI); - lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()}; - upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()}; + lower_left_ = {origin_[0] - grid_[0].back(), origin_[1] - grid_[0].back(), + origin_[2] + grid_[2].front()}; + upper_right_ = {origin_[0] + grid_[0].back(), origin_[1] + grid_[0].back(), + origin_[2] + grid_[2].back()}; return 0; } @@ -1393,17 +1426,12 @@ std::pair, vector> CylindricalMesh::plot( return {axis_lines[0], axis_lines[1]}; } -void CylindricalMesh::to_hdf5(hid_t group) const +void CylindricalMesh::to_hdf5_inner(hid_t mesh_group) const { - hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); - - write_dataset(mesh_group, "type", "cylindrical"); write_dataset(mesh_group, "r_grid", grid_[0]); write_dataset(mesh_group, "phi_grid", grid_[1]); write_dataset(mesh_group, "z_grid", grid_[2]); write_dataset(mesh_group, "origin", origin_); - - close_group(mesh_group); } double CylindricalMesh::volume(const MeshIndex& ijk) const @@ -1687,8 +1715,9 @@ int SphericalMesh::set_grid() full_theta_ = (grid_[1].front() == 0.0) && (grid_[1].back() == PI); full_phi_ = (grid_[2].front() == 0.0) && (grid_[2].back() == 2 * PI); - lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()}; - upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()}; + double r = grid_[0].back(); + lower_left_ = {origin_[0] - r, origin_[1] - r, origin_[2] - r}; + upper_right_ = {origin_[0] + r, origin_[1] + r, origin_[2] + r}; return 0; } @@ -1708,17 +1737,12 @@ std::pair, vector> SphericalMesh::plot( return {axis_lines[0], axis_lines[1]}; } -void SphericalMesh::to_hdf5(hid_t group) const +void SphericalMesh::to_hdf5_inner(hid_t mesh_group) const { - hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_)); - - write_dataset(mesh_group, "type", SphericalMesh::mesh_type); write_dataset(mesh_group, "r_grid", grid_[0]); write_dataset(mesh_group, "theta_grid", grid_[1]); write_dataset(mesh_group, "phi_grid", grid_[2]); write_dataset(mesh_group, "origin", origin_); - - close_group(mesh_group); } double SphericalMesh::volume(const MeshIndex& ijk) const @@ -1899,6 +1923,26 @@ extern "C" int openmc_mesh_get_volumes(int32_t index, double* volumes) return 0; } +//! Get the bounding box of a mesh +extern "C" int openmc_mesh_bounding_box(int32_t index, double* ll, double* ur) +{ + if (int err = check_mesh(index)) + return err; + + BoundingBox bbox = model::meshes[index]->bounding_box(); + + // set lower left corner values + ll[0] = bbox.xmin; + ll[1] = bbox.ymin; + ll[2] = bbox.zmin; + + // set upper right corner values + ur[0] = bbox.xmax; + ur[1] = bbox.ymax; + ur[2] = bbox.zmax; + return 0; +} + extern "C" int openmc_mesh_material_volumes(int32_t index, int n_sample, int bin, int result_size, void* result, int* hits, uint64_t* seed) { @@ -2265,9 +2309,12 @@ void MOABMesh::initialize() } } } + + // Determine bounds of mesh + this->determine_bounds(); } -void MOABMesh::prepare_for_tallies() +void MOABMesh::prepare_for_point_location() { // if the KDTree has already been constructed, do nothing if (kdtree_) @@ -2317,7 +2364,8 @@ void MOABMesh::build_kdtree(const moab::Range& all_tets) all_tets_and_tris.merge(all_tris); // create a kd-tree instance - write_message("Building adaptive k-d tree for tet mesh...", 7); + write_message( + 7, "Building adaptive k-d tree for tet mesh with ID {}...", id_); kdtree_ = make_unique(mbi_.get()); // Determine what options to use @@ -2873,7 +2921,7 @@ void MOABMesh::write(const std::string& base_filename) const const std::string LibMesh::mesh_lib_type = "libmesh"; -LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node) +LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node), adaptive_(false) { // filename_ and length_multiplier_ will already be set by the // UnstructuredMesh constructor @@ -2884,7 +2932,13 @@ LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node) // create the mesh from a pointer to a libMesh Mesh LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) + : adaptive_(input_mesh.n_active_elem() != input_mesh.n_elem()) { + if (!dynamic_cast(&input_mesh)) { + fatal_error("At present LibMesh tallies require a replicated mesh. Please " + "ensure 'input_mesh' is a libMesh::ReplicatedMesh."); + } + m_ = &input_mesh; set_length_multiplier(length_multiplier); initialize(); @@ -2892,6 +2946,7 @@ LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier) // create the mesh from an input file LibMesh::LibMesh(const std::string& filename, double length_multiplier) + : adaptive_(false) { set_mesh_pointer_from_filename(filename); set_length_multiplier(length_multiplier); @@ -2901,11 +2956,21 @@ LibMesh::LibMesh(const std::string& filename, double length_multiplier) void LibMesh::set_mesh_pointer_from_filename(const std::string& filename) { filename_ = filename; - unique_m_ = make_unique(*settings::libmesh_comm, n_dimension_); + unique_m_ = + make_unique(*settings::libmesh_comm, n_dimension_); m_ = unique_m_.get(); m_->read(filename_); } +// build a libMesh equation system for storing values +void LibMesh::build_eqn_sys() +{ + eq_system_name_ = fmt::format("mesh_{}_system", id_); + equation_systems_ = make_unique(*m_); + libMesh::ExplicitSystem& eq_sys = + equation_systems_->add_system(eq_system_name_); +} + // intialize from mesh file void LibMesh::initialize() { @@ -2933,13 +2998,6 @@ void LibMesh::initialize() filename_)); } - // create an equation system for storing values - eq_system_name_ = fmt::format("mesh_{}_system", id_); - - equation_systems_ = make_unique(*m_); - libMesh::ExplicitSystem& eq_sys = - equation_systems_->add_system(eq_system_name_); - for (int i = 0; i < num_threads(); i++) { pl_.emplace_back(m_->sub_point_locator()); pl_.back()->set_contains_point_tol(FP_COINCIDENT); @@ -2950,8 +3008,27 @@ void LibMesh::initialize() auto first_elem = *m_->elements_begin(); first_element_id_ = first_elem->id(); + // if the mesh is adaptive elements aren't guaranteed by libMesh to be + // contiguous in ID space, so we need to map from bin indices (defined over + // active elements) to global dof ids + if (adaptive_) { + bin_to_elem_map_.reserve(m_->n_active_elem()); + elem_to_bin_map_.resize(m_->n_elem(), -1); + for (auto it = m_->active_elements_begin(); it != m_->active_elements_end(); + it++) { + auto elem = *it; + + bin_to_elem_map_.push_back(elem->id()); + elem_to_bin_map_[elem->id()] = bin_to_elem_map_.size() - 1; + } + } + // bounding box for the mesh for quick rejection checks bbox_ = libMesh::MeshTools::create_bounding_box(*m_); + libMesh::Point ll = bbox_.min(); + libMesh::Point ur = bbox_.max(); + lower_left_ = {ll(0), ll(1), ll(2)}; + upper_right_ = {ur(0), ur(1), ur(2)}; } // Sample position within a tet for LibMesh type tets @@ -3003,7 +3080,7 @@ std::string LibMesh::library() const int LibMesh::n_bins() const { - return m_->n_elem(); + return m_->n_active_elem(); } int LibMesh::n_surface_bins() const @@ -3026,6 +3103,18 @@ int LibMesh::n_surface_bins() const void LibMesh::add_score(const std::string& var_name) { + if (adaptive_) { + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); + + return; + } + + if (!equation_systems_) { + build_eqn_sys(); + } + // check if this is a new variable std::string value_name = var_name + "_mean"; if (!variable_map_.count(value_name)) { @@ -3047,14 +3136,28 @@ void LibMesh::add_score(const std::string& var_name) void LibMesh::remove_scores() { - auto& eqn_sys = equation_systems_->get_system(eq_system_name_); - eqn_sys.clear(); - variable_map_.clear(); + if (equation_systems_) { + auto& eqn_sys = equation_systems_->get_system(eq_system_name_); + eqn_sys.clear(); + variable_map_.clear(); + } } void LibMesh::set_score_data(const std::string& var_name, const vector& values, const vector& std_dev) { + if (adaptive_) { + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); + + return; + } + + if (!equation_systems_) { + build_eqn_sys(); + } + auto& eqn_sys = equation_systems_->get_system(eq_system_name_); if (!eqn_sys.is_initialized()) { @@ -3072,6 +3175,10 @@ void LibMesh::set_score_data(const std::string& var_name, for (auto it = m_->local_elements_begin(); it != m_->local_elements_end(); it++) { + if (!(*it)->active()) { + continue; + } + auto bin = get_bin_from_element(*it); // set value @@ -3090,6 +3197,14 @@ void LibMesh::set_score_data(const std::string& var_name, void LibMesh::write(const std::string& filename) const { + if (adaptive_) { + warning(fmt::format( + "Exodus output cannot be provided as unstructured mesh {} is adaptive.", + this->id_)); + + return; + } + write_message(fmt::format( "Writing file: {}.e for unstructured mesh {}", filename, this->id_)); libMesh::ExodusII_IO exo(*m_); @@ -3123,7 +3238,8 @@ int LibMesh::get_bin(Position r) const int LibMesh::get_bin_from_element(const libMesh::Elem* elem) const { - int bin = elem->id() - first_element_id_; + int bin = + adaptive_ ? elem_to_bin_map_[elem->id()] : elem->id() - first_element_id_; if (bin >= n_bins() || bin < 0) { fatal_error(fmt::format("Invalid bin: {}", bin)); } @@ -3138,7 +3254,7 @@ std::pair, vector> LibMesh::plot( const libMesh::Elem& LibMesh::get_element_from_bin(int bin) const { - return m_->elem_ref(bin); + return adaptive_ ? m_->elem_ref(bin_to_elem_map_.at(bin)) : m_->elem_ref(bin); } double LibMesh::volume(int bin) const diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 9eae5a7da7f..eae3e5817ce 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -72,18 +72,18 @@ void Mgxs::metadata_from_hdf5(hid_t xs_id, const vector& temperature, } get_datasets(kT_group, dset_names); vector shape = {num_temps}; - xt::xarray available_temps(shape); + xt::xarray temps_available(shape); for (int i = 0; i < num_temps; i++) { - read_double(kT_group, dset_names[i], &available_temps[i], true); + read_double(kT_group, dset_names[i], &temps_available[i], true); // convert eV to Kelvin - available_temps[i] /= K_BOLTZMANN; + temps_available[i] = std::round(temps_available[i] / K_BOLTZMANN); // Done with dset_names, so delete it delete[] dset_names[i]; } delete[] dset_names; - std::sort(available_temps.begin(), available_temps.end()); + std::sort(temps_available.begin(), temps_available.end()); // If only one temperature is available, lets just use nearest temperature // interpolation @@ -99,17 +99,20 @@ void Mgxs::metadata_from_hdf5(hid_t xs_id, const vector& temperature, case TemperatureMethod::NEAREST: // Determine actual temperatures to read for (const auto& T : temperature) { - auto i_closest = xt::argmin(xt::abs(available_temps - T))[0]; - double temp_actual = available_temps[i_closest]; + auto i_closest = xt::argmin(xt::abs(temps_available - T))[0]; + double temp_actual = temps_available[i_closest]; if (std::fabs(temp_actual - T) < settings::temperature_tolerance) { if (std::find(temps_to_read.begin(), temps_to_read.end(), std::round(temp_actual)) == temps_to_read.end()) { temps_to_read.push_back(std::round(temp_actual)); } } else { - fatal_error(fmt::format("MGXS library does not contain cross sections " - "for {} at or near {} K.", - in_name, std::round(T))); + fatal_error(fmt::format( + "MGXS library does not contain cross sections " + "for {} at or near {} K. Available temperatures " + "are {} K. Consider making use of openmc.Settings.temperature " + "to specify how intermediate temperatures are treated.", + in_name, std::round(T), concatenate(temps_available))); } } break; @@ -122,16 +125,16 @@ void Mgxs::metadata_from_hdf5(hid_t xs_id, const vector& temperature, in_name + " at temperatures that bound " + std::to_string(std::round(temperature[i]))); } - if ((available_temps[j] <= temperature[i]) && - (temperature[i] < available_temps[j + 1])) { + if ((temps_available[j] <= temperature[i]) && + (temperature[i] < temps_available[j + 1])) { if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(available_temps[j])) == temps_to_read.end()) { - temps_to_read.push_back(std::round((int)available_temps[j])); + temps_available[j]) == temps_to_read.end()) { + temps_to_read.push_back(temps_available[j]); } if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(available_temps[j + 1])) == temps_to_read.end()) { - temps_to_read.push_back(std::round((int)available_temps[j + 1])); + temps_available[j + 1]) == temps_to_read.end()) { + temps_to_read.push_back(temps_available[j + 1]); } break; } diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index d54211ecaf1..ed734d401ea 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -15,6 +15,7 @@ #include "openmc/material.h" #include "openmc/math_functions.h" #include "openmc/nuclide.h" +#include "openmc/search.h" #include "openmc/settings.h" namespace openmc { @@ -145,7 +146,7 @@ void MgxsInterface::create_macro_xs() num_energy_groups_, num_delayed_groups_); } else { // Preserve the ordering of materials by including a blank entry - macro_xs_.emplace_back(); + macro_xs_.emplace_back(false); } } } @@ -183,6 +184,15 @@ vector> MgxsInterface::get_mat_kTs() //============================================================================== +int MgxsInterface::get_group_index(double E) +{ + int g = + lower_bound_index(rev_energy_bins_.begin(), rev_energy_bins_.end(), E); + return num_energy_groups_ - g - 1.; +} + +//============================================================================== + void MgxsInterface::read_header(const std::string& path_cross_sections) { // Save name of HDF5 file to be read to struct data diff --git a/src/nuclide.cpp b/src/nuclide.cpp index 91adc077799..f720f848bc2 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -63,6 +63,19 @@ Nuclide::Nuclide(hid_t group, const vector& temperature) read_attribute(group, "atomic_weight_ratio", awr_); if (settings::run_mode == RunMode::VOLUME) { + // Determine whether nuclide is fissionable and then exit + int mt; + hid_t rxs_group = open_group(group, "reactions"); + for (auto name : group_names(rxs_group)) { + if (starts_with(name, "reaction_")) { + hid_t rx_group = open_group(rxs_group, name.c_str()); + read_attribute(rx_group, "mt", mt); + if (is_fission(mt)) { + fissionable_ = true; + break; + } + } + } return; } @@ -73,7 +86,7 @@ Nuclide::Nuclide(hid_t group, const vector& temperature) for (const auto& name : dset_names) { double T; read_dataset(kT_group, name.c_str(), T); - temps_available.push_back(T / K_BOLTZMANN); + temps_available.push_back(std::round(T / K_BOLTZMANN)); } std::sort(temps_available.begin(), temps_available.end()); @@ -145,9 +158,12 @@ Nuclide::Nuclide(hid_t group, const vector& temperature) } } } else { - fatal_error( - "Nuclear data library does not contain cross sections for " + name_ + - " at or near " + std::to_string(T_desired) + " K."); + fatal_error(fmt::format( + "Nuclear data library does not contain cross sections " + "for {} at or near {} K. Available temperatures " + "are {} K. Consider making use of openmc.Settings.temperature " + "to specify how intermediate temperatures are treated.", + name_, std::to_string(T_desired), concatenate(temps_available))); } } break; @@ -160,8 +176,8 @@ Nuclide::Nuclide(hid_t group, const vector& temperature) for (int j = 0; j < temps_available.size() - 1; ++j) { if (temps_available[j] <= T_desired && T_desired < temps_available[j + 1]) { - int T_j = std::round(temps_available[j]); - int T_j1 = std::round(temps_available[j + 1]); + int T_j = temps_available[j]; + int T_j1 = temps_available[j + 1]; if (!contains(temps_to_read, T_j)) { temps_to_read.push_back(T_j); } @@ -178,14 +194,14 @@ Nuclide::Nuclide(hid_t group, const vector& temperature) if (std::abs(T_desired - temps_available.front()) <= settings::temperature_tolerance) { if (!contains(temps_to_read, temps_available.front())) { - temps_to_read.push_back(std::round(temps_available.front())); + temps_to_read.push_back(temps_available.front()); } continue; } if (std::abs(T_desired - temps_available.back()) <= settings::temperature_tolerance) { if (!contains(temps_to_read, temps_available.back())) { - temps_to_read.push_back(std::round(temps_available.back())); + temps_to_read.push_back(temps_available.back()); } continue; } diff --git a/src/output.cpp b/src/output.cpp index 5fdbea1304e..a430fe9a6c6 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -347,7 +347,7 @@ void print_build_info() #ifdef COVERAGEBUILD coverage = y; #endif -#ifdef UWUW +#ifdef OPENMC_UWUW uwuw = y; #endif diff --git a/src/particle.cpp b/src/particle.cpp index a91113c61ad..0ea8650143d 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -168,6 +168,12 @@ void Particle::event_calculate_xs() // Set birth cell attribute if (cell_born() == C_NONE) cell_born() = lowest_coord().cell; + + // Initialize last cells from current cell + for (int j = 0; j < n_coord(); ++j) { + cell_last(j) = coord(j).cell; + } + n_coord_last() = n_coord(); } // Write particle track. @@ -264,16 +270,16 @@ void Particle::event_advance() void Particle::event_cross_surface() { - // Set surface that particle is on and adjust coordinate levels - surface() = boundary().surface_index; - n_coord() = boundary().coord_level; - // Saving previous cell data for (int j = 0; j < n_coord(); ++j) { cell_last(j) = coord(j).cell; } n_coord_last() = n_coord(); + // Set surface that particle is on and adjust coordinate levels + surface() = boundary().surface_index; + n_coord() = boundary().coord_level; + if (boundary().lattice_translation[0] != 0 || boundary().lattice_translation[1] != 0 || boundary().lattice_translation[2] != 0) { @@ -284,7 +290,17 @@ void Particle::event_cross_surface() event() = TallyEvent::LATTICE; } else { // Particle crosses surface - cross_surface(); + // TODO: off-by-one + const auto& surf {model::surfaces[std::abs(surface()) - 1].get()}; + // If BC, add particle to surface source before crossing surface + if (surf->surf_source_ && surf->bc_) { + add_surf_source_to_bank(*this, *surf); + } + this->cross_surface(*surf); + // If no BC, add particle to surface source after crossing surface + if (surf->surf_source_ && !surf->bc_) { + add_surf_source_to_bank(*this, *surf); + } if (settings::weight_window_checkpoint_surface) { apply_weight_windows(*this); } @@ -418,6 +434,12 @@ void Particle::event_revive_from_secondary() // Set birth cell attribute if (cell_born() == C_NONE) cell_born() = lowest_coord().cell; + + // Initialize last cells from current cell + for (int j = 0; j < n_coord(); ++j) { + cell_last(j) = coord(j).cell; + } + n_coord_last() = n_coord(); } pht_secondary_particles(); } @@ -502,40 +524,22 @@ void Particle::pht_secondary_particles() } } -void Particle::cross_surface() +void Particle::cross_surface(const Surface& surf) { - int i_surface = std::abs(surface()); - // TODO: off-by-one - const auto& surf {model::surfaces[i_surface - 1].get()}; - if (settings::verbosity >= 10 || trace()) { - write_message(1, " Crossing surface {}", surf->id_); - } - if (surf->surf_source_ && simulation::current_batch > settings::n_inactive && - !simulation::surf_source_bank.full()) { - SourceSite site; - site.r = r(); - site.u = u(); - site.E = E(); - site.time = time(); - site.wgt = wgt(); - site.delayed_group = delayed_group(); - site.surf_id = surf->id_; - site.particle = type(); - site.parent_id = id(); - site.progeny_id = n_progeny(); - int64_t idx = simulation::surf_source_bank.thread_safe_append(site); + if (settings::verbosity >= 10 || trace()) { + write_message(1, " Crossing surface {}", surf.id_); } // if we're crossing a CSG surface, make sure the DAG history is reset #ifdef DAGMC - if (surf->geom_type_ == GeometryType::CSG) + if (surf.geom_type() == GeometryType::CSG) history().reset(); #endif // Handle any applicable boundary conditions. - if (surf->bc_ && settings::run_mode != RunMode::PLOTTING) { - surf->bc_->handle_particle(*this, *surf); + if (surf.bc_ && settings::run_mode != RunMode::PLOTTING) { + surf.bc_->handle_particle(*this, surf); return; } @@ -544,25 +548,31 @@ void Particle::cross_surface() #ifdef DAGMC // in DAGMC, we know what the next cell should be - if (surf->geom_type_ == GeometryType::DAG) { - int32_t i_cell = - next_cell(i_surface, cell_last(n_coord() - 1), lowest_coord().universe) - - 1; + if (surf.geom_type() == GeometryType::DAG) { + int32_t i_cell = next_cell(std::abs(surface()), cell_last(n_coord() - 1), + lowest_coord().universe) - + 1; // save material and temp material_last() = material(); sqrtkT_last() = sqrtkT(); // set new cell value lowest_coord().cell = i_cell; + auto& cell = model::cells[i_cell]; + cell_instance() = 0; - material() = model::cells[i_cell]->material_[0]; - sqrtkT() = model::cells[i_cell]->sqrtkT_[0]; + if (cell->distribcell_index_ >= 0) + cell_instance() = cell_instance_at_level(*this, n_coord() - 1); + + material() = cell->material(cell_instance()); + sqrtkT() = cell->sqrtkT(cell_instance()); return; } #endif bool verbose = settings::verbosity >= 10 || trace(); - if (neighbor_list_find_cell(*this, verbose)) + if (neighbor_list_find_cell(*this, verbose)) { return; + } // ========================================================================== // COULDN'T FIND PARTICLE IN NEIGHBORING CELLS, SEARCH ALL CELLS @@ -586,7 +596,7 @@ void Particle::cross_surface() if (!exhaustive_find_cell(*this, verbose)) { mark_as_lost("After particle " + std::to_string(id()) + - " crossed surface " + std::to_string(surf->id_) + + " crossed surface " + std::to_string(surf.id_) + " it could not be located in any cell and it did not leak."); return; } @@ -650,7 +660,7 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) u() = new_u; // Reassign particle's cell and surface - coord(0).cell = cell_last(n_coord_last() - 1); + coord(0).cell = cell_last(0); surface() = -surface(); // If a reflective surface is coincident with a lattice or universe @@ -658,7 +668,8 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) // the lower universes. // (unless we're using a dagmc model, which has exactly one universe) n_coord() = 1; - if (surf.geom_type_ != GeometryType::DAG && !neighbor_list_find_cell(*this)) { + if (surf.geom_type() != GeometryType::DAG && + !neighbor_list_find_cell(*this)) { mark_as_lost("Couldn't find particle after reflecting from surface " + std::to_string(surf.id_) + "."); return; @@ -873,4 +884,90 @@ ParticleType str_to_particle_type(std::string str) } } +void add_surf_source_to_bank(Particle& p, const Surface& surf) +{ + if (simulation::current_batch <= settings::n_inactive || + simulation::surf_source_bank.full()) { + return; + } + + // If a cell/cellfrom/cellto parameter is defined + if (settings::ssw_cell_id != C_NONE) { + + // Retrieve cell index and storage type + int cell_idx = model::cell_map[settings::ssw_cell_id]; + + if (surf.bc_) { + // Leave if cellto with vacuum boundary condition + if (surf.bc_->type() == "vacuum" && + settings::ssw_cell_type == SSWCellType::To) { + return; + } + + // Leave if other boundary condition than vacuum + if (surf.bc_->type() != "vacuum") { + return; + } + } + + // Check if the cell of interest has been exited + bool exited = false; + for (int i = 0; i < p.n_coord_last(); ++i) { + if (p.cell_last(i) == cell_idx) { + exited = true; + } + } + + // Check if the cell of interest has been entered + bool entered = false; + for (int i = 0; i < p.n_coord(); ++i) { + if (p.coord(i).cell == cell_idx) { + entered = true; + } + } + + // Vacuum boundary conditions: return if cell is not exited + if (surf.bc_) { + if (surf.bc_->type() == "vacuum" && !exited) { + return; + } + } else { + + // If we both enter and exit the cell of interest + if (entered && exited) { + return; + } + + // If we did not enter nor exit the cell of interest + if (!entered && !exited) { + return; + } + + // If cellfrom and the cell before crossing is not the cell of + // interest + if (settings::ssw_cell_type == SSWCellType::From && !exited) { + return; + } + + // If cellto and the cell after crossing is not the cell of interest + if (settings::ssw_cell_type == SSWCellType::To && !entered) { + return; + } + } + } + + SourceSite site; + site.r = p.r(); + site.u = p.u(); + site.E = p.E(); + site.time = p.time(); + site.wgt = p.wgt(); + site.delayed_group = p.delayed_group(); + site.surf_id = surf.id_; + site.particle = p.type(); + site.parent_id = p.id(); + site.progeny_id = p.n_progeny(); + int64_t idx = simulation::surf_source_bank.thread_safe_append(site); +} + } // namespace openmc diff --git a/src/photon.cpp b/src/photon.cpp index 08062a2e9f5..4b2fb41978f 100644 --- a/src/photon.cpp +++ b/src/photon.cpp @@ -328,15 +328,19 @@ PhotonInteraction::PhotonInteraction(hid_t group) } // Take logarithm of energies and cross sections since they are log-log - // interpolated + // interpolated. Note that cross section libraries converted from ACE files + // represent zero as exp(-500) to avoid log-log interpolation errors. For + // values below exp(-499) we store the log as -900, for which exp(-900) + // evaluates to zero. + double limit = std::exp(-499.0); energy_ = xt::log(energy_); - coherent_ = xt::where(coherent_ > 0.0, xt::log(coherent_), -500.0); - incoherent_ = xt::where(incoherent_ > 0.0, xt::log(incoherent_), -500.0); + coherent_ = xt::where(coherent_ > limit, xt::log(coherent_), -900.0); + incoherent_ = xt::where(incoherent_ > limit, xt::log(incoherent_), -900.0); photoelectric_total_ = xt::where( - photoelectric_total_ > 0.0, xt::log(photoelectric_total_), -500.0); + photoelectric_total_ > limit, xt::log(photoelectric_total_), -900.0); pair_production_total_ = xt::where( - pair_production_total_ > 0.0, xt::log(pair_production_total_), -500.0); - heating_ = xt::where(heating_ > 0.0, xt::log(heating_), -500.0); + pair_production_total_ > limit, xt::log(pair_production_total_), -900.0); + heating_ = xt::where(heating_ > limit, xt::log(heating_), -900.0); } PhotonInteraction::~PhotonInteraction() diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp index 361cf5affcf..3cc0532d2b1 100644 --- a/src/physics_mg.cpp +++ b/src/physics_mg.cpp @@ -19,6 +19,7 @@ #include "openmc/settings.h" #include "openmc/simulation.h" #include "openmc/tallies/tally.h" +#include "openmc/weight_windows.h" namespace openmc { @@ -27,6 +28,9 @@ void collision_mg(Particle& p) // Add to the collision counter for the particle p.n_collision()++; + if (settings::weight_window_checkpoint_collision) + apply_weight_windows(p); + // Sample the reaction type sample_reaction(p); diff --git a/src/plot.cpp b/src/plot.cpp index bf733cff48f..85131d67a01 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1,6 +1,8 @@ #include "openmc/plot.h" #include +#define _USE_MATH_DEFINES // to make M_PI declared in Intel and MSVC compilers +#include #include #include #include @@ -315,6 +317,11 @@ void Plot::set_output_path(pugi::xml_node plot_node) } else { filename = fmt::format("plot_{}", id()); } + const std::string dir_if_present = + filename.substr(0, filename.find_last_of("/") + 1); + if (dir_if_present.size() > 0 && !dir_exists(dir_if_present)) { + fatal_error(fmt::format("Directory '{}' does not exist!", dir_if_present)); + } // add appropriate file extension to name switch (type_) { case PlotType::slice: @@ -966,10 +973,6 @@ void Plot::create_voxel() const ProgressBar pb; for (int z = 0; z < pixels_[2]; z++) { - // update progress bar - pb.set_value( - 100. * static_cast(z) / static_cast((pixels_[2] - 1))); - // update z coordinate pltbase.origin_.z = ll.z + z * vox[2]; @@ -984,6 +987,10 @@ void Plot::create_voxel() const // Write to HDF5 dataset voxel_write_slice(z, dspace, dset, memspace, data_flipped.data()); + + // update progress bar + pb.set_value( + 100. * static_cast(z + 1) / static_cast((pixels_[2]))); } voxel_finalize(dspace, dset, memspace); @@ -1296,7 +1303,7 @@ void ProjectionPlot::create_output() const int32_t i_surface = std::abs(p.surface()) - 1; if (i_surface > 0 && - model::surfaces[i_surface]->geom_type_ == GeometryType::DAG) { + model::surfaces[i_surface]->geom_type() == GeometryType::DAG) { #ifdef DAGMC int32_t i_cell = next_cell(i_surface, p.cell_last(p.n_coord() - 1), p.lowest_coord().universe); diff --git a/src/random_lcg.cpp b/src/random_lcg.cpp index 581d696176f..ca4467719c4 100644 --- a/src/random_lcg.cpp +++ b/src/random_lcg.cpp @@ -17,7 +17,8 @@ constexpr uint64_t prn_stride {152917LL}; // stride between particles //============================================================================== // 64 bit implementation of the PCG-RXS-M-XS 64-bit state / 64-bit output -// geneator Adapted from: https://github.com/imneme/pcg-c +// geneator Adapted from: https://github.com/imneme/pcg-c, in particular +// https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/include/pcg_variants.h#L188-L192 // @techreport{oneill:pcg2014, // title = "PCG: A Family of Simple Fast Space-Efficient Statistically Good // Algorithms for Random Number Generation", author = "Melissa E. O'Neill", diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index 5e1194aa92a..19913c03c16 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -1,7 +1,9 @@ #include "openmc/random_ray/flat_source_domain.h" #include "openmc/cell.h" +#include "openmc/eigenvalue.h" #include "openmc/geometry.h" +#include "openmc/material.h" #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" #include "openmc/output.h" @@ -21,6 +23,12 @@ namespace openmc { // FlatSourceDomain implementation //============================================================================== +// Static Variable Declarations +RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ { + RandomRayVolumeEstimator::HYBRID}; +bool FlatSourceDomain::volume_normalized_flux_tallies_ {false}; +bool FlatSourceDomain::adjoint_ {false}; + FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) { // Count the number of source regions, compute the cell offset @@ -44,14 +52,26 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) position_.resize(n_source_regions_); volume_.assign(n_source_regions_, 0.0); volume_t_.assign(n_source_regions_, 0.0); - was_hit_.assign(n_source_regions_, 0); + volume_naive_.assign(n_source_regions_, 0.0); // Initialize element-wise arrays scalar_flux_new_.assign(n_source_elements_, 0.0); - scalar_flux_old_.assign(n_source_elements_, 1.0); scalar_flux_final_.assign(n_source_elements_, 0.0); source_.resize(n_source_elements_); + tally_task_.resize(n_source_elements_); + volume_task_.resize(n_source_regions_); + + if (settings::run_mode == RunMode::EIGENVALUE) { + // If in eigenvalue mode, set starting flux to guess of unity + scalar_flux_old_.assign(n_source_elements_, 1.0); + } else { + // If in fixed source mode, set starting flux to guess of zero + // and initialize external source arrays + scalar_flux_old_.assign(n_source_elements_, 0.0); + external_source_.assign(n_source_elements_, 0.0); + external_source_present_.assign(n_source_regions_, false); + } // Initialize material array int64_t source_region_id = 0; @@ -68,15 +88,35 @@ FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_) if (source_region_id != n_source_regions_) { fatal_error("Unexpected number of source regions"); } + + // Initialize tally volumes + if (volume_normalized_flux_tallies_) { + tally_volumes_.resize(model::tallies.size()); + for (int i = 0; i < model::tallies.size(); i++) { + // Get the shape of the 3D result tensor + auto shape = model::tallies[i]->results().shape(); + + // Create a new 2D tensor with the same size as the first + // two dimensions of the 3D tensor + tally_volumes_[i] = + xt::xtensor::from_shape({shape[0], shape[1]}); + } + } + + // Compute simulation domain volume based on ray source + auto* is = dynamic_cast(RandomRay::ray_source_.get()); + SpatialDistribution* space_dist = is->space(); + SpatialBox* sb = dynamic_cast(space_dist); + Position dims = sb->upper_right() - sb->lower_left(); + simulation_volume_ = dims.x * dims.y * dims.z; } void FlatSourceDomain::batch_reset() { // Reset scalar fluxes, iteration volume tallies, and region hit flags to // zero - parallel_fill(scalar_flux_new_, 0.0f); + parallel_fill(scalar_flux_new_, 0.0); parallel_fill(volume_, 0.0); - parallel_fill(was_hit_, 0); } void FlatSourceDomain::accumulate_iteration_flux() @@ -95,38 +135,51 @@ void FlatSourceDomain::update_neutron_source(double k_eff) double inverse_k_eff = 1.0 / k_eff; - // Temperature and angle indices, if using multiple temperature - // data sets and/or anisotropic data sets. - // TODO: Currently assumes we are only using single temp/single - // angle data. - const int t = 0; - const int a = 0; - + // Add scattering source #pragma omp parallel for for (int sr = 0; sr < n_source_regions_; sr++) { int material = material_[sr]; - for (int e_out = 0; e_out < negroups_; e_out++) { - float sigma_t = data::mg.macro_xs_[material].get_xs( - MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a); - float scatter_source = 0.0f; - float fission_source = 0.0f; - - for (int e_in = 0; e_in < negroups_; e_in++) { - float scalar_flux = scalar_flux_old_[sr * negroups_ + e_in]; - float sigma_s = data::mg.macro_xs_[material].get_xs( - MgxsType::NU_SCATTER, e_in, &e_out, nullptr, nullptr, t, a); - float nu_sigma_f = data::mg.macro_xs_[material].get_xs( - MgxsType::NU_FISSION, e_in, nullptr, nullptr, nullptr, t, a); - float chi = data::mg.macro_xs_[material].get_xs( - MgxsType::CHI_PROMPT, e_in, &e_out, nullptr, nullptr, t, a); + for (int g_out = 0; g_out < negroups_; g_out++) { + double sigma_t = sigma_t_[material * negroups_ + g_out]; + double scatter_source = 0.0; + + for (int g_in = 0; g_in < negroups_; g_in++) { + double scalar_flux = scalar_flux_old_[sr * negroups_ + g_in]; + double sigma_s = + sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in]; scatter_source += sigma_s * scalar_flux; + } + + source_[sr * negroups_ + g_out] = scatter_source / sigma_t; + } + } + + // Add fission source +#pragma omp parallel for + for (int sr = 0; sr < n_source_regions_; sr++) { + int material = material_[sr]; + + for (int g_out = 0; g_out < negroups_; g_out++) { + double sigma_t = sigma_t_[material * negroups_ + g_out]; + double fission_source = 0.0; + + for (int g_in = 0; g_in < negroups_; g_in++) { + double scalar_flux = scalar_flux_old_[sr * negroups_ + g_in]; + double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in]; + double chi = chi_[material * negroups_ + g_out]; fission_source += nu_sigma_f * scalar_flux * chi; } + source_[sr * negroups_ + g_out] += + fission_source * inverse_k_eff / sigma_t; + } + } - fission_source *= inverse_k_eff; - float new_isotropic_source = (scatter_source + fission_source) / sigma_t; - source_[sr * negroups_ + e_out] = new_isotropic_source; + // Add external source if in fixed source mode + if (settings::run_mode == RunMode::FIXED_SOURCE) { +#pragma omp parallel for + for (int64_t se = 0; se < n_source_elements_; se++) { + source_[se] += external_source_[se]; } } @@ -137,14 +190,14 @@ void FlatSourceDomain::update_neutron_source(double k_eff) void FlatSourceDomain::normalize_scalar_flux_and_volumes( double total_active_distance_per_iteration) { - float normalization_factor = 1.0 / total_active_distance_per_iteration; + double normalization_factor = 1.0 / total_active_distance_per_iteration; double volume_normalization_factor = 1.0 / (total_active_distance_per_iteration * simulation::current_batch); // Normalize scalar flux to total distance travelled by all rays this iteration #pragma omp parallel for - for (int64_t e = 0; e < scalar_flux_new_.size(); e++) { - scalar_flux_new_[e] *= normalization_factor; + for (int64_t se = 0; se < scalar_flux_new_.size(); se++) { + scalar_flux_new_[se] *= normalization_factor; } // Accumulate cell-wise ray length tallies collected this iteration, then @@ -152,58 +205,105 @@ void FlatSourceDomain::normalize_scalar_flux_and_volumes( #pragma omp parallel for for (int64_t sr = 0; sr < n_source_regions_; sr++) { volume_t_[sr] += volume_[sr]; + volume_naive_[sr] = volume_[sr] * normalization_factor; volume_[sr] = volume_t_[sr] * volume_normalization_factor; } } +void FlatSourceDomain::set_flux_to_flux_plus_source( + int64_t idx, double volume, int material, int g) +{ + double sigma_t = sigma_t_[material * negroups_ + g]; + scalar_flux_new_[idx] /= (sigma_t * volume); + scalar_flux_new_[idx] += source_[idx]; +} + +void FlatSourceDomain::set_flux_to_old_flux(int64_t idx) +{ + scalar_flux_new_[idx] = scalar_flux_old_[idx]; +} + +void FlatSourceDomain::set_flux_to_source(int64_t idx) +{ + scalar_flux_new_[idx] = source_[idx]; +} + // Combine transport flux contributions and flat source contributions from the // previous iteration to generate this iteration's estimate of scalar flux. int64_t FlatSourceDomain::add_source_to_scalar_flux() { int64_t n_hits = 0; - // Temperature and angle indices, if using multiple temperature - // data sets and/or anisotropic data sets. - // TODO: Currently assumes we are only using single temp/single - // angle data. - const int t = 0; - const int a = 0; - #pragma omp parallel for reduction(+ : n_hits) for (int sr = 0; sr < n_source_regions_; sr++) { - // Check if this cell was hit this iteration - int was_cell_hit = was_hit_[sr]; - if (was_cell_hit) { + double volume_simulation_avg = volume_[sr]; + double volume_iteration = volume_naive_[sr]; + + // Increment the number of hits if cell was hit this iteration + if (volume_iteration) { n_hits++; } - double volume = volume_[sr]; + // Check if an external source is present in this source region + bool external_source_present = + external_source_present_.size() && external_source_present_[sr]; + + // The volume treatment depends on the volume estimator type + // and whether or not an external source is present in the cell. + double volume; + switch (volume_estimator_) { + case RandomRayVolumeEstimator::NAIVE: + volume = volume_iteration; + break; + case RandomRayVolumeEstimator::SIMULATION_AVERAGED: + volume = volume_simulation_avg; + break; + case RandomRayVolumeEstimator::HYBRID: + if (external_source_present) { + volume = volume_iteration; + } else { + volume = volume_simulation_avg; + } + break; + default: + fatal_error("Invalid volume estimator type"); + } + int material = material_[sr]; for (int g = 0; g < negroups_; g++) { int64_t idx = (sr * negroups_) + g; // There are three scenarios we need to consider: - if (was_cell_hit) { + if (volume_iteration > 0.0) { // 1. If the FSR was hit this iteration, then the new flux is equal to // the flat source from the previous iteration plus the contributions // from rays passing through the source region (computed during the // transport sweep) - float sigma_t = data::mg.macro_xs_[material].get_xs( - MgxsType::TOTAL, g, nullptr, nullptr, nullptr, t, a); - scalar_flux_new_[idx] /= (sigma_t * volume); - scalar_flux_new_[idx] += source_[idx]; - } else if (volume > 0.0) { + set_flux_to_flux_plus_source(idx, volume, material, g); + } else if (volume_simulation_avg > 0.0) { // 2. If the FSR was not hit this iteration, but has been hit some - // previous iteration, then we simply set the new scalar flux to be - // equal to the contribution from the flat source alone. - scalar_flux_new_[idx] = source_[idx]; - } else { - // If the FSR was not hit this iteration, and it has never been hit in - // any iteration (i.e., volume is zero), then we want to set this to 0 - // to avoid dividing anything by a zero volume. - scalar_flux_new_[idx] = 0.0f; + // previous iteration, then we need to make a choice about what + // to do. Naively we will usually want to set the flux to be equal + // to the reduced source. However, in fixed source problems where + // there is a strong external source present in the cell, and where + // the cell has a very low cross section, this approximation will + // cause a huge upward bias in the flux estimate of the cell (in these + // conditions, the flux estimate can be orders of magnitude too large). + // Thus, to avoid this bias, if any external source is present + // in the cell we will use the previous iteration's flux estimate. This + // injects a small degree of correlation into the simulation, but this + // is going to be trivial when the miss rate is a few percent or less. + if (external_source_present) { + set_flux_to_old_flux(idx); + } else { + set_flux_to_source(idx); + } } + // If the FSR was not hit this iteration, and it has never been hit in + // any iteration (i.e., volume is zero), then we want to set this to 0 + // to avoid dividing anything by a zero volume. This happens implicitly + // given that the new scalar flux arrays are set to zero each iteration. } } @@ -218,12 +318,8 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const double fission_rate_old = 0; double fission_rate_new = 0; - // Temperature and angle indices, if using multiple temperature - // data sets and/or anisotropic data sets. - // TODO: Currently assumes we are only using single temp/single - // angle data. - const int t = 0; - const int a = 0; + // Vector for gathering fission source terms for Shannon entropy calculation + vector p(n_source_regions_, 0.0f); #pragma omp parallel for reduction(+ : fission_rate_old, fission_rate_new) for (int sr = 0; sr < n_source_regions_; sr++) { @@ -241,18 +337,43 @@ double FlatSourceDomain::compute_k_eff(double k_eff_old) const for (int g = 0; g < negroups_; g++) { int64_t idx = (sr * negroups_) + g; - double nu_sigma_f = data::mg.macro_xs_[material].get_xs( - MgxsType::NU_FISSION, g, nullptr, nullptr, nullptr, t, a); + double nu_sigma_f = nu_sigma_f_[material * negroups_ + g]; sr_fission_source_old += nu_sigma_f * scalar_flux_old_[idx]; sr_fission_source_new += nu_sigma_f * scalar_flux_new_[idx]; } - fission_rate_old += sr_fission_source_old * volume; - fission_rate_new += sr_fission_source_new * volume; + // Compute total fission rates in FSR + sr_fission_source_old *= volume; + sr_fission_source_new *= volume; + + // Accumulate totals + fission_rate_old += sr_fission_source_old; + fission_rate_new += sr_fission_source_new; + + // Store total fission rate in the FSR for Shannon calculation + p[sr] = sr_fission_source_new; } double k_eff_new = k_eff_old * (fission_rate_new / fission_rate_old); + double H = 0.0; + // defining an inverse sum for better performance + double inverse_sum = 1 / fission_rate_new; + +#pragma omp parallel for reduction(+ : H) + for (int sr = 0; sr < n_source_regions_; sr++) { + // Only if FSR has non-negative and non-zero fission source + if (p[sr] > 0.0f) { + // Normalize to total weight of bank sites. p_i for better performance + float p_i = p[sr] * inverse_sum; + // Sum values to obtain Shannon entropy. + H -= p_i * std::log2(p_i); + } + } + + // Adds entropy value to shared entropy vector in openmc namespace. + simulation::entropy.push_back(H); + return k_eff_new; } @@ -355,10 +476,14 @@ void FlatSourceDomain::convert_source_regions_to_tallies() for (auto score_index = 0; score_index < tally.scores_.size(); score_index++) { auto score_bin = tally.scores_[score_index]; - // If a valid tally, filter, and score cobination has been found, + // If a valid tally, filter, and score combination has been found, // then add it to the list of tally tasks for this source element. - tally_task_[source_element].emplace_back( - i_tally, filter_index, score_index, score_bin); + TallyTask task(i_tally, filter_index, score_index, score_bin); + tally_task_[source_element].push_back(task); + + // Also add this task to the list of volume tasks for this source + // region. + volume_task_[sr].insert(task); } } } @@ -372,6 +497,62 @@ void FlatSourceDomain::convert_source_regions_to_tallies() mapped_all_tallies_ = all_source_regions_mapped; } +// Set the volume accumulators to zero for all tallies +void FlatSourceDomain::reset_tally_volumes() +{ + if (volume_normalized_flux_tallies_) { +#pragma omp parallel for + for (int i = 0; i < tally_volumes_.size(); i++) { + auto& tensor = tally_volumes_[i]; + tensor.fill(0.0); // Set all elements of the tensor to 0.0 + } + } +} + +// In fixed source mode, due to the way that volumetric fixed sources are +// converted and applied as volumetric sources in one or more source regions, +// we need to perform an additional normalization step to ensure that the +// reported scalar fluxes are in units per source neutron. This allows for +// direct comparison of reported tallies to Monte Carlo flux results. +// This factor needs to be computed at each iteration, as it is based on the +// volume estimate of each FSR, which improves over the course of the +// simulation +double FlatSourceDomain::compute_fixed_source_normalization_factor() const +{ + // If we are not in fixed source mode, then there are no external sources + // so no normalization is needed. + if (settings::run_mode != RunMode::FIXED_SOURCE || adjoint_) { + return 1.0; + } + + // Step 1 is to sum over all source regions and energy groups to get the + // total external source strength in the simulation. + double simulation_external_source_strength = 0.0; +#pragma omp parallel for reduction(+ : simulation_external_source_strength) + for (int sr = 0; sr < n_source_regions_; sr++) { + int material = material_[sr]; + double volume = volume_[sr] * simulation_volume_; + for (int g = 0; g < negroups_; g++) { + double sigma_t = sigma_t_[material * negroups_ + g]; + simulation_external_source_strength += + external_source_[sr * negroups_ + g] * sigma_t * volume; + } + } + + // Step 2 is to determine the total user-specified external source strength + double user_external_source_strength = 0.0; + for (auto& ext_source : model::external_sources) { + user_external_source_strength += ext_source->strength(); + } + + // The correction factor is the ratio of the user-specified external source + // strength to the simulation external source strength. + double source_normalization_factor = + user_external_source_strength / simulation_external_source_strength; + + return source_normalization_factor; +} + // Tallying in random ray is not done directly during transport, rather, // it is done only once after each power iteration. This is made possible // by way of a mapping data structure that relates spatial source regions @@ -381,48 +562,56 @@ void FlatSourceDomain::convert_source_regions_to_tallies() // tally function simply traverses the mapping data structure and executes // the scoring operations to OpenMC's native tally result arrays. -void FlatSourceDomain::random_ray_tally() const +void FlatSourceDomain::random_ray_tally() { openmc::simulation::time_tallies.start(); - // Temperature and angle indices, if using multiple temperature - // data sets and/or anisotropic data sets. - // TODO: Currently assumes we are only using single temp/single - // angle data. - const int t = 0; - const int a = 0; + // Reset our tally volumes to zero + reset_tally_volumes(); + + double source_normalization_factor = + compute_fixed_source_normalization_factor(); // We loop over all source regions and energy groups. For each // element, we check if there are any scores needed and apply // them. #pragma omp parallel for for (int sr = 0; sr < n_source_regions_; sr++) { - double volume = volume_[sr]; + // The fsr.volume_ is the unitless fractional simulation averaged volume + // (i.e., it is the FSR's fraction of the overall simulation volume). The + // simulation_volume_ is the total 3D physical volume in cm^3 of the + // entire global simulation domain (as defined by the ray source box). + // Thus, the FSR's true 3D spatial volume in cm^3 is found by multiplying + // its fraction of the total volume by the total volume. Not important in + // eigenvalue solves, but useful in fixed source solves for returning the + // flux shape with a magnitude that makes sense relative to the fixed + // source strength. + double volume = volume_[sr] * simulation_volume_; + double material = material_[sr]; for (int g = 0; g < negroups_; g++) { int idx = sr * negroups_ + g; - double flux = scalar_flux_new_[idx] * volume; + double flux = scalar_flux_new_[idx] * source_normalization_factor; + + // Determine numerical score value for (auto& task : tally_task_[idx]) { double score; switch (task.score_type) { case SCORE_FLUX: - score = flux; + score = flux * volume; break; case SCORE_TOTAL: - score = flux * data::mg.macro_xs_[material].get_xs( - MgxsType::TOTAL, g, NULL, NULL, NULL, t, a); + score = flux * volume * sigma_t_[material * negroups_ + g]; break; case SCORE_FISSION: - score = flux * data::mg.macro_xs_[material].get_xs( - MgxsType::FISSION, g, NULL, NULL, NULL, t, a); + score = flux * volume * sigma_f_[material * negroups_ + g]; break; case SCORE_NU_FISSION: - score = flux * data::mg.macro_xs_[material].get_xs( - MgxsType::NU_FISSION, g, NULL, NULL, NULL, t, a); + score = flux * volume * nu_sigma_f_[material * negroups_ + g]; break; case SCORE_EVENTS: @@ -435,13 +624,53 @@ void FlatSourceDomain::random_ray_tally() const "random ray mode."); break; } + + // Apply score to the appropriate tally bin Tally& tally {*model::tallies[task.tally_idx]}; #pragma omp atomic tally.results_(task.filter_idx, task.score_idx, TallyResult::VALUE) += score; } } + + // For flux tallies, the total volume of the spatial region is needed + // for normalizing the flux. We store this volume in a separate tensor. + // We only contribute to each volume tally bin once per FSR. + if (volume_normalized_flux_tallies_) { + for (const auto& task : volume_task_[sr]) { + if (task.score_type == SCORE_FLUX) { +#pragma omp atomic + tally_volumes_[task.tally_idx](task.filter_idx, task.score_idx) += + volume; + } + } + } + } // end FSR loop + + // Normalize any flux scores by the total volume of the FSRs scoring to that + // bin. To do this, we loop over all tallies, and then all filter bins, + // and then scores. For each score, we check the tally data structure to + // see what index that score corresponds to. If that score is a flux score, + // then we divide it by volume. + if (volume_normalized_flux_tallies_) { + for (int i = 0; i < model::tallies.size(); i++) { + Tally& tally {*model::tallies[i]}; +#pragma omp parallel for + for (int bin = 0; bin < tally.n_filter_bins(); bin++) { + for (int score_idx = 0; score_idx < tally.n_scores(); score_idx++) { + auto score_type = tally.scores_[score_idx]; + if (score_type == SCORE_FLUX) { + double vol = tally_volumes_[i](bin, score_idx); + if (vol > 0.0) { + tally.results_(bin, score_idx, TallyResult::VALUE) /= vol; + } + } + } + } + } } + + openmc::simulation::time_tallies.stop(); } void FlatSourceDomain::all_reduce_replicated_source_regions() @@ -527,16 +756,20 @@ void FlatSourceDomain::all_reduce_replicated_source_regions() MPI_Allreduce(MPI_IN_PLACE, volume_.data(), n_source_regions_, MPI_DOUBLE, MPI_SUM, mpi::intracomm); - MPI_Allreduce(MPI_IN_PLACE, was_hit_.data(), n_source_regions_, MPI_INT, - MPI_SUM, mpi::intracomm); - MPI_Allreduce(MPI_IN_PLACE, scalar_flux_new_.data(), n_source_elements_, - MPI_FLOAT, MPI_SUM, mpi::intracomm); + MPI_DOUBLE, MPI_SUM, mpi::intracomm); simulation::time_bank_sendrecv.stop(); #endif } +double FlatSourceDomain::evaluate_flux_at_point( + Position r, int64_t sr, int g) const +{ + return scalar_flux_final_[sr * negroups_ + g] / + (settings::n_batches - settings::n_inactive); +} + // Outputs all basic material, FSR ID, multigroup flux, and // fission source data to .vtk file that can be directly // loaded and displayed by Paraview. Note that .vtk binary @@ -597,6 +830,7 @@ void FlatSourceDomain::output_to_vtk() const // Relate voxel spatial locations to random ray source regions vector voxel_indices(Nx * Ny * Nz); + vector voxel_positions(Nx * Ny * Nz); #pragma omp parallel for collapse(3) for (int z = 0; z < Nz; z++) { @@ -613,10 +847,14 @@ void FlatSourceDomain::output_to_vtk() const int64_t source_region_idx = source_region_offsets_[i_cell] + p.cell_instance(); voxel_indices[z * Ny * Nx + y * Nx + x] = source_region_idx; + voxel_positions[z * Ny * Nx + y * Nx + x] = sample; } } } + double source_normalization_factor = + compute_fixed_source_normalization_factor(); + // Open file for writing std::FILE* plot = std::fopen(filename.c_str(), "wb"); @@ -634,10 +872,10 @@ void FlatSourceDomain::output_to_vtk() const for (int g = 0; g < negroups_; g++) { std::fprintf(plot, "SCALARS flux_group_%d float\n", g); std::fprintf(plot, "LOOKUP_TABLE default\n"); - for (int fsr : voxel_indices) { + for (int i = 0; i < Nx * Ny * Nz; i++) { + int64_t fsr = voxel_indices[i]; int64_t source_element = fsr * negroups_ + g; - float flux = scalar_flux_final_[source_element]; - flux /= (settings::n_batches - settings::n_inactive); + float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g); flux = convert_to_big_endian(flux); std::fwrite(&flux, sizeof(float), 1, plot); } @@ -664,16 +902,16 @@ void FlatSourceDomain::output_to_vtk() const // Plot fission source std::fprintf(plot, "SCALARS total_fission_source float\n"); std::fprintf(plot, "LOOKUP_TABLE default\n"); - for (int fsr : voxel_indices) { + for (int i = 0; i < Nx * Ny * Nz; i++) { + int64_t fsr = voxel_indices[i]; + float total_fission = 0.0; int mat = material_[fsr]; for (int g = 0; g < negroups_; g++) { int64_t source_element = fsr * negroups_ + g; - float flux = scalar_flux_final_[source_element]; - flux /= (settings::n_batches - settings::n_inactive); - float Sigma_f = data::mg.macro_xs_[mat].get_xs( - MgxsType::FISSION, g, nullptr, nullptr, nullptr, 0, 0); - total_fission += Sigma_f * flux; + float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g); + double sigma_f = sigma_f_[mat * negroups_ + g]; + total_fission += sigma_f * flux; } total_fission = convert_to_big_endian(total_fission); std::fwrite(&total_fission, sizeof(float), 1, plot); @@ -683,4 +921,212 @@ void FlatSourceDomain::output_to_vtk() const } } +void FlatSourceDomain::apply_external_source_to_source_region( + Discrete* discrete, double strength_factor, int64_t source_region) +{ + external_source_present_[source_region] = true; + + const auto& discrete_energies = discrete->x(); + const auto& discrete_probs = discrete->prob(); + + for (int i = 0; i < discrete_energies.size(); i++) { + int g = data::mg.get_group_index(discrete_energies[i]); + external_source_[source_region * negroups_ + g] += + discrete_probs[i] * strength_factor; + } +} + +void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell, + Discrete* discrete, double strength_factor, int target_material_id, + const vector& instances) +{ + Cell& cell = *model::cells[i_cell]; + + if (cell.type_ != Fill::MATERIAL) + return; + + for (int j : instances) { + int cell_material_idx = cell.material(j); + int cell_material_id = model::materials[cell_material_idx]->id(); + if (target_material_id == C_NONE || + cell_material_id == target_material_id) { + int64_t source_region = source_region_offsets_[i_cell] + j; + apply_external_source_to_source_region( + discrete, strength_factor, source_region); + } + } +} + +void FlatSourceDomain::apply_external_source_to_cell_and_children( + int32_t i_cell, Discrete* discrete, double strength_factor, + int32_t target_material_id) +{ + Cell& cell = *model::cells[i_cell]; + + if (cell.type_ == Fill::MATERIAL) { + vector instances(cell.n_instances_); + std::iota(instances.begin(), instances.end(), 0); + apply_external_source_to_cell_instances( + i_cell, discrete, strength_factor, target_material_id, instances); + } else if (target_material_id == C_NONE) { + std::unordered_map> cell_instance_list = + cell.get_contained_cells(0, nullptr); + for (const auto& pair : cell_instance_list) { + int32_t i_child_cell = pair.first; + apply_external_source_to_cell_instances(i_child_cell, discrete, + strength_factor, target_material_id, pair.second); + } + } +} + +void FlatSourceDomain::count_external_source_regions() +{ + n_external_source_regions_ = 0; +#pragma omp parallel for reduction(+ : n_external_source_regions_) + for (int sr = 0; sr < n_source_regions_; sr++) { + if (external_source_present_[sr]) { + n_external_source_regions_++; + } + } +} + +void FlatSourceDomain::convert_external_sources() +{ + // Loop over external sources + for (int es = 0; es < model::external_sources.size(); es++) { + Source* s = model::external_sources[es].get(); + IndependentSource* is = dynamic_cast(s); + Discrete* energy = dynamic_cast(is->energy()); + const std::unordered_set& domain_ids = is->domain_ids(); + + double strength_factor = is->strength(); + + if (is->domain_type() == Source::DomainType::MATERIAL) { + for (int32_t material_id : domain_ids) { + for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) { + apply_external_source_to_cell_and_children( + i_cell, energy, strength_factor, material_id); + } + } + } else if (is->domain_type() == Source::DomainType::CELL) { + for (int32_t cell_id : domain_ids) { + int32_t i_cell = model::cell_map[cell_id]; + apply_external_source_to_cell_and_children( + i_cell, energy, strength_factor, C_NONE); + } + } else if (is->domain_type() == Source::DomainType::UNIVERSE) { + for (int32_t universe_id : domain_ids) { + int32_t i_universe = model::universe_map[universe_id]; + Universe& universe = *model::universes[i_universe]; + for (int32_t i_cell : universe.cells_) { + apply_external_source_to_cell_and_children( + i_cell, energy, strength_factor, C_NONE); + } + } + } + } // End loop over external sources + +// Divide the fixed source term by sigma t (to save time when applying each +// iteration) +#pragma omp parallel for + for (int sr = 0; sr < n_source_regions_; sr++) { + int material = material_[sr]; + for (int g = 0; g < negroups_; g++) { + double sigma_t = sigma_t_[material * negroups_ + g]; + external_source_[sr * negroups_ + g] /= sigma_t; + } + } +} + +void FlatSourceDomain::flux_swap() +{ + scalar_flux_old_.swap(scalar_flux_new_); +} + +void FlatSourceDomain::flatten_xs() +{ + // Temperature and angle indices, if using multiple temperature + // data sets and/or anisotropic data sets. + // TODO: Currently assumes we are only using single temp/single angle data. + const int t = 0; + const int a = 0; + + n_materials_ = data::mg.macro_xs_.size(); + for (auto& m : data::mg.macro_xs_) { + for (int g_out = 0; g_out < negroups_; g_out++) { + if (m.exists_in_model) { + double sigma_t = + m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a); + sigma_t_.push_back(sigma_t); + + double nu_Sigma_f = + m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a); + nu_sigma_f_.push_back(nu_Sigma_f); + + double sigma_f = + m.get_xs(MgxsType::FISSION, g_out, NULL, NULL, NULL, t, a); + sigma_f_.push_back(sigma_f); + + double chi = + m.get_xs(MgxsType::CHI_PROMPT, g_out, &g_out, NULL, NULL, t, a); + chi_.push_back(chi); + + for (int g_in = 0; g_in < negroups_; g_in++) { + double sigma_s = + m.get_xs(MgxsType::NU_SCATTER, g_in, &g_out, NULL, NULL, t, a); + sigma_s_.push_back(sigma_s); + } + } else { + sigma_t_.push_back(0); + nu_sigma_f_.push_back(0); + sigma_f_.push_back(0); + chi_.push_back(0); + for (int g_in = 0; g_in < negroups_; g_in++) { + sigma_s_.push_back(0); + } + } + } + } +} + +void FlatSourceDomain::set_adjoint_sources(const vector& forward_flux) +{ + // Set the external source to 1/forward_flux + // The forward flux is given in terms of total for the forward simulation + // so we must convert it to a "per batch" quantity +#pragma omp parallel for + for (int64_t se = 0; se < n_source_elements_; se++) { + external_source_[se] = 1.0 / forward_flux[se]; + } + + // Divide the fixed source term by sigma t (to save time when applying each + // iteration) +#pragma omp parallel for + for (int sr = 0; sr < n_source_regions_; sr++) { + int material = material_[sr]; + for (int g = 0; g < negroups_; g++) { + double sigma_t = sigma_t_[material * negroups_ + g]; + external_source_[sr * negroups_ + g] /= sigma_t; + } + } +} + +void FlatSourceDomain::transpose_scattering_matrix() +{ + // Transpose the inner two dimensions for each material + for (int m = 0; m < n_materials_; ++m) { + int material_offset = m * negroups_ * negroups_; + for (int i = 0; i < negroups_; ++i) { + for (int j = i + 1; j < negroups_; ++j) { + // Calculate indices of the elements to swap + int idx1 = material_offset + i * negroups_ + j; + int idx2 = material_offset + j * negroups_ + i; + + // Swap the elements to transpose the matrix + std::swap(sigma_s_[idx1], sigma_s_[idx2]); + } + } + } +} + } // namespace openmc diff --git a/src/random_ray/linear_source_domain.cpp b/src/random_ray/linear_source_domain.cpp new file mode 100644 index 00000000000..f3f6fa0687d --- /dev/null +++ b/src/random_ray/linear_source_domain.cpp @@ -0,0 +1,225 @@ +#include "openmc/random_ray/linear_source_domain.h" + +#include "openmc/cell.h" +#include "openmc/geometry.h" +#include "openmc/material.h" +#include "openmc/message_passing.h" +#include "openmc/mgxs_interface.h" +#include "openmc/output.h" +#include "openmc/plot.h" +#include "openmc/random_ray/random_ray.h" +#include "openmc/simulation.h" +#include "openmc/tallies/filter.h" +#include "openmc/tallies/tally.h" +#include "openmc/tallies/tally_scoring.h" +#include "openmc/timer.h" + +namespace openmc { + +//============================================================================== +// LinearSourceDomain implementation +//============================================================================== + +LinearSourceDomain::LinearSourceDomain() : FlatSourceDomain() +{ + // First order spatial moment of the scalar flux + flux_moments_old_.assign(n_source_elements_, {0.0, 0.0, 0.0}); + flux_moments_new_.assign(n_source_elements_, {0.0, 0.0, 0.0}); + flux_moments_t_.assign(n_source_elements_, {0.0, 0.0, 0.0}); + // Source gradients given by M inverse multiplied by source moments + source_gradients_.assign(n_source_elements_, {0.0, 0.0, 0.0}); + + centroid_.assign(n_source_regions_, {0.0, 0.0, 0.0}); + centroid_iteration_.assign(n_source_regions_, {0.0, 0.0, 0.0}); + centroid_t_.assign(n_source_regions_, {0.0, 0.0, 0.0}); + mom_matrix_.assign(n_source_regions_, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); + mom_matrix_t_.assign(n_source_regions_, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}); +} + +void LinearSourceDomain::batch_reset() +{ + FlatSourceDomain::batch_reset(); +#pragma omp parallel for + for (int64_t se = 0; se < n_source_elements_; se++) { + flux_moments_new_[se] = {0.0, 0.0, 0.0}; + } +#pragma omp parallel for + for (int64_t sr = 0; sr < n_source_regions_; sr++) { + centroid_iteration_[sr] = {0.0, 0.0, 0.0}; + mom_matrix_[sr] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + } +} + +void LinearSourceDomain::update_neutron_source(double k_eff) +{ + simulation::time_update_src.start(); + + double inverse_k_eff = 1.0 / k_eff; + +#pragma omp parallel for + for (int sr = 0; sr < n_source_regions_; sr++) { + + int material = material_[sr]; + MomentMatrix invM = mom_matrix_[sr].inverse(); + + for (int g_out = 0; g_out < negroups_; g_out++) { + double sigma_t = sigma_t_[material * negroups_ + g_out]; + + double scatter_flat = 0.0f; + double fission_flat = 0.0f; + MomentArray scatter_linear = {0.0, 0.0, 0.0}; + MomentArray fission_linear = {0.0, 0.0, 0.0}; + + for (int g_in = 0; g_in < negroups_; g_in++) { + // Handles for the flat and linear components of the flux + double flux_flat = scalar_flux_old_[sr * negroups_ + g_in]; + MomentArray flux_linear = flux_moments_old_[sr * negroups_ + g_in]; + + // Handles for cross sections + double sigma_s = + sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in]; + double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in]; + double chi = chi_[material * negroups_ + g_out]; + + // Compute source terms for flat and linear components of the flux + scatter_flat += sigma_s * flux_flat; + fission_flat += nu_sigma_f * flux_flat * chi; + scatter_linear += sigma_s * flux_linear; + fission_linear += nu_sigma_f * flux_linear * chi; + } + + // Compute the flat source term + source_[sr * negroups_ + g_out] = + (scatter_flat + fission_flat * inverse_k_eff) / sigma_t; + + // Compute the linear source terms + // In the first 10 iterations when the centroids and spatial moments + // are not well known, we will leave the source gradients as zero + // so as to avoid causing any numerical instability. + if (simulation::current_batch > 10) { + source_gradients_[sr * negroups_ + g_out] = + invM * ((scatter_linear + fission_linear * inverse_k_eff) / sigma_t); + } + } + } + + if (settings::run_mode == RunMode::FIXED_SOURCE) { +// Add external source to flat source term if in fixed source mode +#pragma omp parallel for + for (int64_t se = 0; se < n_source_elements_; se++) { + source_[se] += external_source_[se]; + } + } + + simulation::time_update_src.stop(); +} + +void LinearSourceDomain::normalize_scalar_flux_and_volumes( + double total_active_distance_per_iteration) +{ + double normalization_factor = 1.0 / total_active_distance_per_iteration; + double volume_normalization_factor = + 1.0 / (total_active_distance_per_iteration * simulation::current_batch); + +// Normalize flux to total distance travelled by all rays this iteration +#pragma omp parallel for + for (int64_t se = 0; se < scalar_flux_new_.size(); se++) { + scalar_flux_new_[se] *= normalization_factor; + flux_moments_new_[se] *= normalization_factor; + } + +// Accumulate cell-wise ray length tallies collected this iteration, then +// update the simulation-averaged cell-wise volume estimates +#pragma omp parallel for + for (int64_t sr = 0; sr < n_source_regions_; sr++) { + centroid_t_[sr] += centroid_iteration_[sr]; + mom_matrix_t_[sr] += mom_matrix_[sr]; + volume_t_[sr] += volume_[sr]; + volume_naive_[sr] = volume_[sr] * normalization_factor; + volume_[sr] = volume_t_[sr] * volume_normalization_factor; + if (volume_t_[sr] > 0.0) { + double inv_volume = 1.0 / volume_t_[sr]; + centroid_[sr] = centroid_t_[sr]; + centroid_[sr] *= inv_volume; + mom_matrix_[sr] = mom_matrix_t_[sr]; + mom_matrix_[sr] *= inv_volume; + } + } +} + +void LinearSourceDomain::set_flux_to_flux_plus_source( + int64_t idx, double volume, int material, int g) +{ + scalar_flux_new_[idx] /= volume; + scalar_flux_new_[idx] += source_[idx]; + flux_moments_new_[idx] *= (1.0 / volume); +} + +void LinearSourceDomain::set_flux_to_old_flux(int64_t idx) +{ + scalar_flux_new_[idx] = scalar_flux_old_[idx]; + flux_moments_new_[idx] = flux_moments_old_[idx]; +} + +void LinearSourceDomain::flux_swap() +{ + FlatSourceDomain::flux_swap(); + flux_moments_old_.swap(flux_moments_new_); +} + +void LinearSourceDomain::accumulate_iteration_flux() +{ + // Accumulate scalar flux + FlatSourceDomain::accumulate_iteration_flux(); + + // Accumulate scalar flux moments +#pragma omp parallel for + for (int64_t se = 0; se < n_source_elements_; se++) { + flux_moments_t_[se] += flux_moments_new_[se]; + } +} + +void LinearSourceDomain::all_reduce_replicated_source_regions() +{ +#ifdef OPENMC_MPI + FlatSourceDomain::all_reduce_replicated_source_regions(); + simulation::time_bank_sendrecv.start(); + + // We are going to assume we can safely cast Position, MomentArray, + // and MomentMatrix to contiguous arrays of doubles for the MPI + // allreduce operation. This is a safe assumption as typically + // compilers will at most pad to 8 byte boundaries. If a new FP32 MomentArray + // type is introduced, then there will likely be padding, in which case this + // function will need to become more complex. + if (sizeof(MomentArray) != 3 * sizeof(double) || + sizeof(MomentMatrix) != 6 * sizeof(double)) { + fatal_error("Unexpected buffer padding in linear source domain reduction."); + } + + MPI_Allreduce(MPI_IN_PLACE, static_cast(flux_moments_new_.data()), + n_source_elements_ * 3, MPI_DOUBLE, MPI_SUM, mpi::intracomm); + MPI_Allreduce(MPI_IN_PLACE, static_cast(mom_matrix_.data()), + n_source_regions_ * 6, MPI_DOUBLE, MPI_SUM, mpi::intracomm); + MPI_Allreduce(MPI_IN_PLACE, static_cast(centroid_iteration_.data()), + n_source_regions_ * 3, MPI_DOUBLE, MPI_SUM, mpi::intracomm); + + simulation::time_bank_sendrecv.stop(); +#endif +} + +double LinearSourceDomain::evaluate_flux_at_point( + Position r, int64_t sr, int g) const +{ + double phi_flat = FlatSourceDomain::evaluate_flux_at_point(r, sr, g); + + Position local_r = r - centroid_[sr]; + MomentArray phi_linear = flux_moments_t_[sr * negroups_ + g]; + phi_linear *= 1.0 / (settings::n_batches - settings::n_inactive); + + MomentMatrix invM = mom_matrix_[sr].inverse(); + MomentArray phi_solved = invM * phi_linear; + + return phi_flat + phi_solved.dot(local_r); +} + +} // namespace openmc diff --git a/src/random_ray/moment_matrix.cpp b/src/random_ray/moment_matrix.cpp new file mode 100644 index 00000000000..0324a14943b --- /dev/null +++ b/src/random_ray/moment_matrix.cpp @@ -0,0 +1,84 @@ +#include "openmc/random_ray/moment_matrix.h" +#include "openmc/error.h" + +#include + +namespace openmc { + +//============================================================================== +// UpperTriangular implementation +//============================================================================== + +// Inverts a 3x3 smmetric matrix labeled as: +// +// | a b c | +// | b d e | +// | c e f | +// +// We first check the determinant to ensure it is non-zero before proceeding +// with the inversion. If the determinant is zero, we return a matrix of zeros. +// Inversion is calculated by computing the adjoint matrix first, and then the +// inverse can be computed as: A^-1 = 1/det(A) * adj(A) +MomentMatrix MomentMatrix::inverse() const +{ + MomentMatrix inv; + + // Check if the determinant is zero + double det = determinant(); + if (det < std::abs(1.0e-10)) { + // Set the inverse to zero. In effect, this will + // result in all the linear terms of the source becoming + // zero, leaving just the flat source. + inv.set_to_zero(); + return inv; + } + + // Compute the adjoint matrix + inv.a = d * f - e * e; + inv.b = c * e - b * f; + inv.c = b * e - c * d; + inv.d = a * f - c * c; + inv.e = b * c - a * e; + inv.f = a * d - b * b; + + // A^-1 = 1/det(A) * adj(A) + inv *= 1.0 / det; + + return inv; +} + +// Computes the determinant of a 3x3 symmetric +// matrix, with elements labeled as follows: +// +// | a b c | +// | b d e | +// | c e f | +double MomentMatrix::determinant() const +{ + return a * (d * f - e * e) - b * (b * f - c * e) + c * (b * e - c * d); +} + +// Compute a 3x3 spatial moment matrix based on a single ray crossing. +// The matrix is symmetric, and is defined as: +// +// | a b c | +// | b d e | +// | c e f | +// +// The estimate of the obect's spatial moments matrix is computed based on the +// midpoint of the ray's crossing, the direction of the ray, and the distance +// the ray traveled through the 3D object. +void MomentMatrix::compute_spatial_moments_matrix( + const Position& r, const Direction& u, const double& distance) +{ + constexpr double one_over_twelve = 1.0 / 12.0; + const double distance2_12 = distance * distance * one_over_twelve; + a = r[0] * r[0] + u[0] * u[0] * distance2_12; + b = r[0] * r[1] + u[0] * u[1] * distance2_12; + c = r[0] * r[2] + u[0] * u[2] * distance2_12; + d = r[1] * r[1] + u[1] * u[1] * distance2_12; + e = r[1] * r[2] + u[1] * u[2] * distance2_12; + f = r[2] * r[2] + u[2] * u[2] * distance2_12; +} + +} // namespace openmc \ No newline at end of file diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 07899836dd9..42e6ea670b8 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -1,9 +1,11 @@ #include "openmc/random_ray/random_ray.h" +#include "openmc/constants.h" #include "openmc/geometry.h" #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" #include "openmc/random_ray/flat_source_domain.h" +#include "openmc/random_ray/linear_source_domain.h" #include "openmc/search.h" #include "openmc/settings.h" #include "openmc/simulation.h" @@ -63,6 +65,118 @@ float cjosey_exponential(float tau) return num / den; } +// The below two functions (exponentialG and exponentialG2) were developed +// by Colin Josey. The implementation of these functions is closely based +// on the OpenMOC versions of these functions. The OpenMOC license is given +// below: + +// Copyright (C) 2012-2023 Massachusetts Institute of Technology and OpenMOC +// contributors +// +// 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. + +// Computes y = 1/x-(1-exp(-x))/x**2 using a 5/6th order rational +// approximation. It is accurate to 2e-7 over [0, 1e5]. Developed by Colin +// Josey using Remez's algorithm, with original implementation in OpenMOC at: +// https://github.com/mit-crpg/OpenMOC/blob/develop/src/exponentials.h +float exponentialG(float tau) +{ + // Numerator coefficients in rational approximation for 1/x - (1 - exp(-x)) / + // x^2 + constexpr float d0n = 0.5f; + constexpr float d1n = 0.176558112351595f; + constexpr float d2n = 0.04041584305811143f; + constexpr float d3n = 0.006178333902037397f; + constexpr float d4n = 0.0006429894635552992f; + constexpr float d5n = 0.00006064409107557148f; + + // Denominator coefficients in rational approximation for 1/x - (1 - exp(-x)) + // / x^2 + constexpr float d0d = 1.0f; + constexpr float d1d = 0.6864462055546078f; + constexpr float d2d = 0.2263358514260129f; + constexpr float d3d = 0.04721469893686252f; + constexpr float d4d = 0.006883236664917246f; + constexpr float d5d = 0.0007036272419147752f; + constexpr float d6d = 0.00006064409107557148f; + + float x = tau; + + float num = d5n; + num = num * x + d4n; + num = num * x + d3n; + num = num * x + d2n; + num = num * x + d1n; + num = num * x + d0n; + + float den = d6d; + den = den * x + d5d; + den = den * x + d4d; + den = den * x + d3d; + den = den * x + d2d; + den = den * x + d1d; + den = den * x + d0d; + + return num / den; +} + +// Computes G2 : y = 2/3 - (1 + 2/x) * (1/x + 0.5 - (1 + 1/x) * (1-exp(-x)) / +// x) using a 5/5th order rational approximation. It is accurate to 1e-6 over +// [0, 1e6]. Developed by Colin Josey using Remez's algorithm, with original +// implementation in OpenMOC at: +// https://github.com/mit-crpg/OpenMOC/blob/develop/src/exponentials.h +float exponentialG2(float tau) +{ + + // Coefficients for numerator in rational approximation + constexpr float g1n = -0.08335775885589858f; + constexpr float g2n = -0.003603942303847604f; + constexpr float g3n = 0.0037673183263550827f; + constexpr float g4n = 0.00001124183494990467f; + constexpr float g5n = 0.00016837426505799449f; + + // Coefficients for denominator in rational approximation + constexpr float g1d = 0.7454048371823628f; + constexpr float g2d = 0.23794300531408347f; + constexpr float g3d = 0.05367250964303789f; + constexpr float g4d = 0.006125197988351906f; + constexpr float g5d = 0.0010102514456857377f; + + float x = tau; + + float num = g5n; + num = num * x + g4n; + num = num * x + g3n; + num = num * x + g2n; + num = num * x + g1n; + num = num * x; + + float den = g5d; + den = den * x + g4d; + den = den * x + g3d; + den = den * x + g2d; + den = den * x + g1d; + den = den * x + 1.0f; + + return num / den; +} + // Implementation of the Fisher-Yates shuffle algorithm. // Algorithm adapted from: // https://en.cppreference.com/w/cpp/algorithm/random_shuffle#Version_3 @@ -126,7 +240,6 @@ vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) } return halton; -} //============================================================================== // RandomRay implementation @@ -136,12 +249,18 @@ vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) double RandomRay::distance_inactive_; double RandomRay::distance_active_; unique_ptr RandomRay::ray_source_; +RandomRaySourceShape RandomRay::source_shape_ {RandomRaySourceShape::FLAT}; RandomRay::RandomRay() : angular_flux_(data::mg.num_energy_groups_), delta_psi_(data::mg.num_energy_groups_), negroups_(data::mg.num_energy_groups_) -{} +{ + if (source_shape_ == RandomRaySourceShape::LINEAR || + source_shape_ == RandomRaySourceShape::LINEAR_XY) { + delta_moments_.resize(negroups_); + } +} RandomRay::RandomRay(uint64_t ray_id, FlatSourceDomain* domain) : RandomRay() { @@ -220,6 +339,21 @@ void RandomRay::event_advance_ray() } } +void RandomRay::attenuate_flux(double distance, bool is_active) +{ + switch (source_shape_) { + case RandomRaySourceShape::FLAT: + attenuate_flux_flat_source(distance, is_active); + break; + case RandomRaySourceShape::LINEAR: + case RandomRaySourceShape::LINEAR_XY: + attenuate_flux_linear_source(distance, is_active); + break; + default: + fatal_error("Unknown source shape for random ray transport."); + } +} + // This function forms the inner loop of the random ray transport process. // It is responsible for several tasks. Based on the incoming angular flux // of the ray and the source term in the region, the outgoing angular flux @@ -233,7 +367,7 @@ void RandomRay::event_advance_ray() // than use of many atomic operations corresponding to each energy group // individually (at least on CPU). Several other bookkeeping tasks are also // performed when inside the lock. -void RandomRay::attenuate_flux(double distance, bool is_active) +void RandomRay::attenuate_flux_flat_source(double distance, bool is_active) { // The number of geometric intersections is counted for reporting purposes n_event()++; @@ -249,17 +383,9 @@ void RandomRay::attenuate_flux(double distance, bool is_active) int64_t source_element = source_region * negroups_; int material = this->material(); - // Temperature and angle indices, if using multiple temperature - // data sets and/or anisotropic data sets. - // TODO: Currently assumes we are only using single temp/single - // angle data. - const int t = 0; - const int a = 0; - // MOC incoming flux attenuation + source contribution/attenuation equation for (int g = 0; g < negroups_; g++) { - float sigma_t = data::mg.macro_xs_[material].get_xs( - MgxsType::TOTAL, g, NULL, NULL, NULL, t, a); + float sigma_t = domain_->sigma_t_[material * negroups_ + g]; float tau = sigma_t * distance; float exponential = cjosey_exponential(tau); // exponential = 1 - exp(-tau) float new_delta_psi = @@ -281,12 +407,6 @@ void RandomRay::attenuate_flux(double distance, bool is_active) domain_->scalar_flux_new_[source_element + g] += delta_psi_[g]; } - // If the source region hasn't been hit yet this iteration, - // indicate that it now has - if (domain_->was_hit_[source_region] == 0) { - domain_->was_hit_[source_region] = 1; - } - // Accomulate volume (ray distance) into this iteration's estimate // of the source region's volume domain_->volume_[source_region] += distance; @@ -304,6 +424,144 @@ void RandomRay::attenuate_flux(double distance, bool is_active) } } +void RandomRay::attenuate_flux_linear_source(double distance, bool is_active) +{ + // Cast domain to LinearSourceDomain + LinearSourceDomain* domain = dynamic_cast(domain_); + if (!domain) { + fatal_error("RandomRay::attenuate_flux_linear_source() called with " + "non-LinearSourceDomain domain."); + } + + // The number of geometric intersections is counted for reporting purposes + n_event()++; + + // Determine source region index etc. + int i_cell = lowest_coord().cell; + + // The source region is the spatial region index + int64_t source_region = + domain_->source_region_offsets_[i_cell] + cell_instance(); + + // The source element is the energy-specific region index + int64_t source_element = source_region * negroups_; + int material = this->material(); + + Position& centroid = domain->centroid_[source_region]; + Position midpoint = r() + u() * (distance / 2.0); + + // Determine the local position of the midpoint and the ray origin + // relative to the source region's centroid + Position rm_local; + Position r0_local; + + // In the first few iterations of the simulation, the source region + // may not yet have had any ray crossings, in which case there will + // be no estimate of its centroid. We detect this by checking if it has + // any accumulated volume. If its volume is zero, just use the midpoint + // of the ray as the region's centroid. + if (domain->volume_t_[source_region]) { + rm_local = midpoint - centroid; + r0_local = r() - centroid; + } else { + rm_local = {0.0, 0.0, 0.0}; + r0_local = -u() * 0.5 * distance; + } + double distance_2 = distance * distance; + + // Linear Source MOC incoming flux attenuation + source + // contribution/attenuation equation + for (int g = 0; g < negroups_; g++) { + + // Compute tau, the optical thickness of the ray segment + float sigma_t = domain_->sigma_t_[material * negroups_ + g]; + float tau = sigma_t * distance; + + // If tau is very small, set it to zero to avoid numerical issues. + // The following computations will still work with tau = 0. + if (tau < 1.0e-8f) { + tau = 0.0f; + } + + // Compute linear source terms, spatial and directional (dir), + // calculated from the source gradients dot product with local centroid + // and direction, respectively. + float spatial_source = + domain_->source_[source_element + g] + + rm_local.dot(domain->source_gradients_[source_element + g]); + float dir_source = u().dot(domain->source_gradients_[source_element + g]); + + float gn = exponentialG(tau); + float f1 = 1.0f - tau * gn; + float f2 = (2.0f * gn - f1) * distance_2; + float new_delta_psi = (angular_flux_[g] - spatial_source) * f1 * distance - + 0.5 * dir_source * f2; + + float h1 = f1 - gn; + float g1 = 0.5f - h1; + float g2 = exponentialG2(tau); + g1 = g1 * spatial_source; + g2 = g2 * dir_source * distance * 0.5f; + h1 = h1 * angular_flux_[g]; + h1 = (g1 + g2 + h1) * distance_2; + spatial_source = spatial_source * distance + new_delta_psi; + + // Store contributions for this group into arrays, so that they can + // be accumulated into the source region's estimates inside of the locked + // region. + delta_psi_[g] = new_delta_psi; + delta_moments_[g] = r0_local * spatial_source + u() * h1; + + // Update the angular flux for this group + angular_flux_[g] -= new_delta_psi * sigma_t; + + // If 2D mode is enabled, the z-component of the flux moments is forced + // to zero + if (source_shape_ == RandomRaySourceShape::LINEAR_XY) { + delta_moments_[g].z = 0.0; + } + } + + // If ray is in the active phase (not in dead zone), make contributions to + // source region bookkeeping + if (is_active) { + // Compute an estimate of the spatial moments matrix for the source + // region based on parameters from this ray's crossing + MomentMatrix moment_matrix_estimate; + moment_matrix_estimate.compute_spatial_moments_matrix( + rm_local, u(), distance); + + // Aquire lock for source region + domain_->lock_[source_region].lock(); + + // Accumulate deltas into the new estimate of source region flux for this + // iteration + for (int g = 0; g < negroups_; g++) { + domain_->scalar_flux_new_[source_element + g] += delta_psi_[g]; + domain->flux_moments_new_[source_element + g] += delta_moments_[g]; + } + + // Accumulate the volume (ray segment distance), centroid, and spatial + // momement estimates into the running totals for the iteration for this + // source region. The centroid and spatial momements estimates are scaled by + // the ray segment length as part of length averaging of the estimates. + domain_->volume_[source_region] += distance; + domain->centroid_iteration_[source_region] += midpoint * distance; + moment_matrix_estimate *= distance; + domain->mom_matrix_[source_region] += moment_matrix_estimate; + + // Tally valid position inside the source region (e.g., midpoint of + // the ray) if not done already + if (!domain_->position_recorded_[source_region]) { + domain_->position_[source_region] = midpoint; + domain_->position_recorded_[source_region] = 1; + } + + // Release lock + domain_->lock_[source_region].unlock(); + } +} + void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) { domain_ = domain; diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index b9fd93a48f6..a9180c68e7b 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -6,6 +6,7 @@ #include "openmc/mgxs_interface.h" #include "openmc/output.h" #include "openmc/plot.h" +#include "openmc/random_ray/flat_source_domain.h" #include "openmc/random_ray/random_ray.h" #include "openmc/simulation.h" #include "openmc/source.h" @@ -22,6 +23,22 @@ namespace openmc { void openmc_run_random_ray() { + ////////////////////////////////////////////////////////// + // Run forward simulation + ////////////////////////////////////////////////////////// + + // Check if adjoint calculation is needed. If it is, we will run the forward + // calculation first and then the adjoint calculation later. + bool adjoint_needed = FlatSourceDomain::adjoint_; + + // Configure the domain for forward simulation + FlatSourceDomain::adjoint_ = false; + + // If we're going to do an adjoint simulation afterwards, report that this is + // the initial forward flux solve. + if (adjoint_needed && mpi::master) + header("FORWARD FLUX SOLVE", 3); + // Initialize OpenMC general data structures openmc_simulation_init(); @@ -29,26 +46,93 @@ void openmc_run_random_ray() if (mpi::master) validate_random_ray_inputs(); - // Initialize Random Ray Simulation Object - RandomRaySimulation sim; + // Declare forward flux so that it can be saved for later adjoint simulation + vector forward_flux; + + { + // Initialize Random Ray Simulation Object + RandomRaySimulation sim; + + // Initialize fixed sources, if present + sim.prepare_fixed_sources(); + + // Begin main simulation timer + simulation::time_total.start(); + + // Execute random ray simulation + sim.simulate(); + + // End main simulation timer + simulation::time_total.stop(); + + // Normalize and save the final forward flux + forward_flux = sim.domain()->scalar_flux_final_; + + double source_normalization_factor = + sim.domain()->compute_fixed_source_normalization_factor() / + (settings::n_batches - settings::n_inactive); + +#pragma omp parallel for + for (uint64_t i = 0; i < forward_flux.size(); i++) { + forward_flux[i] *= source_normalization_factor; + } + + // Finalize OpenMC + openmc_simulation_finalize(); + + // Reduce variables across MPI ranks + sim.reduce_simulation_statistics(); + + // Output all simulation results + sim.output_simulation_results(); + } + + ////////////////////////////////////////////////////////// + // Run adjoint simulation (if enabled) + ////////////////////////////////////////////////////////// + + if (adjoint_needed) { + reset_timers(); + + // Configure the domain for adjoint simulation + FlatSourceDomain::adjoint_ = true; + + if (mpi::master) + header("ADJOINT FLUX SOLVE", 3); - // Begin main simulation timer - simulation::time_total.start(); + // Initialize OpenMC general data structures + openmc_simulation_init(); - // Execute random ray simulation - sim.simulate(); + // Initialize Random Ray Simulation Object + RandomRaySimulation adjoint_sim; - // End main simulation timer - openmc::simulation::time_total.stop(); + // Initialize adjoint fixed sources, if present + adjoint_sim.prepare_fixed_sources_adjoint(forward_flux); - // Finalize OpenMC - openmc_simulation_finalize(); + // Transpose scattering matrix + adjoint_sim.domain()->transpose_scattering_matrix(); - // Reduce variables across MPI ranks - sim.reduce_simulation_statistics(); + // Swap nu_sigma_f and chi + adjoint_sim.domain()->nu_sigma_f_.swap(adjoint_sim.domain()->chi_); - // Output all simulation results - sim.output_simulation_results(); + // Begin main simulation timer + simulation::time_total.start(); + + // Execute random ray simulation + adjoint_sim.simulate(); + + // End main simulation timer + simulation::time_total.stop(); + + // Finalize OpenMC + openmc_simulation_finalize(); + + // Reduce variables across MPI ranks + adjoint_sim.reduce_simulation_statistics(); + + // Output all simulation results + adjoint_sim.output_simulation_results(); + } } // Enforces restrictions on inputs in random ray mode. While there are @@ -111,13 +195,6 @@ void validate_random_ray_inputs() } } - // Validate solver mode - /////////////////////////////////////////////////////////////////// - if (settings::run_mode == RunMode::FIXED_SOURCE) { - fatal_error( - "Invalid run mode. Fixed source not yet supported in random ray mode."); - } - // Validate ray source /////////////////////////////////////////////////////////////////// @@ -125,8 +202,8 @@ void validate_random_ray_inputs() IndependentSource* is = dynamic_cast(RandomRay::ray_source_.get()); if (!is) { - fatal_error( - "Invalid ray source definition. Ray source must be IndependentSource."); + fatal_error("Invalid ray source definition. Ray source must provided and " + "be of type IndependentSource."); } // Check for box source @@ -134,24 +211,68 @@ void validate_random_ray_inputs() SpatialBox* sb = dynamic_cast(space_dist); if (!sb) { fatal_error( - "Invalid source definition -- only box sources are allowed in random " - "ray " - "mode. If no source is specified, OpenMC default is an isotropic point " - "source at the origin, which is invalid in random ray mode."); + "Invalid ray source definition -- only box sources are allowed."); } // Check that box source is not restricted to fissionable areas if (sb->only_fissionable()) { - fatal_error("Invalid source definition -- fissionable spatial distribution " - "not allowed for random ray source."); + fatal_error( + "Invalid ray source definition -- fissionable spatial distribution " + "not allowed."); } // Check for isotropic source UnitSphereDistribution* angle_dist = is->angle(); Isotropic* id = dynamic_cast(angle_dist); if (!id) { - fatal_error("Invalid source definition -- only isotropic sources are " - "allowed for random ray source."); + fatal_error("Invalid ray source definition -- only isotropic sources are " + "allowed."); + } + + // Validate external sources + /////////////////////////////////////////////////////////////////// + if (settings::run_mode == RunMode::FIXED_SOURCE) { + if (model::external_sources.size() < 1) { + fatal_error("Must provide a particle source (in addition to ray source) " + "in fixed source random ray mode."); + } + + for (int i = 0; i < model::external_sources.size(); i++) { + Source* s = model::external_sources[i].get(); + + // Check for independent source + IndependentSource* is = dynamic_cast(s); + + if (!is) { + fatal_error( + "Only IndependentSource external source types are allowed in " + "random ray mode"); + } + + // Check for isotropic source + UnitSphereDistribution* angle_dist = is->angle(); + Isotropic* id = dynamic_cast(angle_dist); + if (!id) { + fatal_error( + "Invalid source definition -- only isotropic external sources are " + "allowed in random ray mode."); + } + + // Validate that a domain ID was specified + if (is->domain_ids().size() == 0) { + fatal_error("Fixed sources must be specified by domain " + "id (cell, material, or universe) in random ray mode."); + } + + // Check that a discrete energy distribution was used + Distribution* d = is->energy(); + Discrete* dd = dynamic_cast(d); + if (!dd) { + fatal_error( + "Only discrete (multigroup) energy distributions are allowed for " + "external sources in random ray mode."); + } + } } // Validate plotting files @@ -204,6 +325,39 @@ RandomRaySimulation::RandomRaySimulation() // Random ray mode does not have an inner loop over generations within a // batch, so set the current gen to 1 simulation::current_gen = 1; + + switch (RandomRay::source_shape_) { + case RandomRaySourceShape::FLAT: + domain_ = make_unique(); + break; + case RandomRaySourceShape::LINEAR: + case RandomRaySourceShape::LINEAR_XY: + domain_ = make_unique(); + break; + default: + fatal_error("Unknown random ray source shape"); + } + + // Convert OpenMC native MGXS into a more efficient format + // internal to the random ray solver + domain_->flatten_xs(); +} + +void RandomRaySimulation::prepare_fixed_sources() +{ + if (settings::run_mode == RunMode::FIXED_SOURCE) { + // Transfer external source user inputs onto random ray source regions + domain_->convert_external_sources(); + domain_->count_external_source_regions(); + } +} + +void RandomRaySimulation::prepare_fixed_sources_adjoint( + vector& forward_flux) +{ + if (settings::run_mode == RunMode::FIXED_SOURCE) { + domain_->set_adjoint_sources(forward_flux); + } } void RandomRaySimulation::simulate() @@ -219,11 +373,11 @@ void RandomRaySimulation::simulate() simulation::total_weight = 1.0; // Update source term (scattering + fission) - domain_.update_neutron_source(k_eff_); + domain_->update_neutron_source(k_eff_); // Reset scalar fluxes, iteration volume tallies, and region hit flags to // zero - domain_.batch_reset(); + domain_->batch_reset(); // Start timer for transport simulation::time_transport.start(); @@ -232,7 +386,7 @@ void RandomRaySimulation::simulate() #pragma omp parallel for schedule(dynamic) \ reduction(+ : total_geometric_intersections_) for (int i = 0; i < simulation::work_per_rank; i++) { - RandomRay ray(i, &domain_); + RandomRay ray(i, domain_.get()); total_geometric_intersections_ += ray.transport_history_based_single_ray(); } @@ -240,38 +394,42 @@ void RandomRaySimulation::simulate() simulation::time_transport.stop(); // If using multiple MPI ranks, perform all reduce on all transport results - domain_.all_reduce_replicated_source_regions(); + domain_->all_reduce_replicated_source_regions(); // Normalize scalar flux and update volumes - domain_.normalize_scalar_flux_and_volumes( + domain_->normalize_scalar_flux_and_volumes( settings::n_particles * RandomRay::distance_active_); // Add source to scalar flux, compute number of FSR hits - int64_t n_hits = domain_.add_source_to_scalar_flux(); + int64_t n_hits = domain_->add_source_to_scalar_flux(); - // Compute random ray k-eff - k_eff_ = domain_.compute_k_eff(k_eff_); + if (settings::run_mode == RunMode::EIGENVALUE) { + // Compute random ray k-eff + k_eff_ = domain_->compute_k_eff(k_eff_); - // Store random ray k-eff into OpenMC's native k-eff variable - global_tally_tracklength = k_eff_; + // Store random ray k-eff into OpenMC's native k-eff variable + global_tally_tracklength = k_eff_; + } // Execute all tallying tasks, if this is an active batch - if (simulation::current_batch > settings::n_inactive && mpi::master) { + if (simulation::current_batch > settings::n_inactive) { - // Generate mapping between source regions and tallies - if (!domain_.mapped_all_tallies_) { - domain_.convert_source_regions_to_tallies(); - } + // Add this iteration's scalar flux estimate to final accumulated estimate + domain_->accumulate_iteration_flux(); - // Use above mapping to contribute FSR flux data to appropriate tallies - domain_.random_ray_tally(); + if (mpi::master) { + // Generate mapping between source regions and tallies + if (!domain_->mapped_all_tallies_) { + domain_->convert_source_regions_to_tallies(); + } - // Add this iteration's scalar flux estimate to final accumulated estimate - domain_.accumulate_iteration_flux(); + // Use above mapping to contribute FSR flux data to appropriate tallies + domain_->random_ray_tally(); + } } // Set phi_old = phi_new - domain_.scalar_flux_old_.swap(domain_.scalar_flux_new_); + domain_->flux_swap(); // Check for any obvious insabilities/nans/infs instability_check(n_hits, k_eff_, avg_miss_rate_); @@ -302,9 +460,9 @@ void RandomRaySimulation::output_simulation_results() const if (mpi::master) { print_results_random_ray(total_geometric_intersections_, avg_miss_rate_ / settings::n_batches, negroups_, - domain_.n_source_regions_); + domain_->n_source_regions_, domain_->n_external_source_regions_); if (model::plots.size() > 0) { - domain_.output_to_vtk(); + domain_->output_to_vtk(); } } } @@ -314,32 +472,35 @@ void RandomRaySimulation::output_simulation_results() const void RandomRaySimulation::instability_check( int64_t n_hits, double k_eff, double& avg_miss_rate) const { - double percent_missed = ((domain_.n_source_regions_ - n_hits) / - static_cast(domain_.n_source_regions_)) * + double percent_missed = ((domain_->n_source_regions_ - n_hits) / + static_cast(domain_->n_source_regions_)) * 100.0; avg_miss_rate += percent_missed; - if (percent_missed > 10.0) { - warning(fmt::format( - "Very high FSR miss rate detected ({:.3f}%). Instability may occur. " - "Increase ray density by adding more rays and/or active distance.", - percent_missed)); - } else if (percent_missed > 0.01) { - warning(fmt::format("Elevated FSR miss rate detected ({:.3f}%). Increasing " - "ray density by adding more rays and/or active " - "distance may improve simulation efficiency.", - percent_missed)); - } + if (mpi::master) { + if (percent_missed > 10.0) { + warning(fmt::format( + "Very high FSR miss rate detected ({:.3f}%). Instability may occur. " + "Increase ray density by adding more rays and/or active distance.", + percent_missed)); + } else if (percent_missed > 1.0) { + warning( + fmt::format("Elevated FSR miss rate detected ({:.3f}%). Increasing " + "ray density by adding more rays and/or active " + "distance may improve simulation efficiency.", + percent_missed)); + } - if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) { - fatal_error("Instability detected"); + if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) { + fatal_error("Instability detected"); + } } } // Print random ray simulation results void RandomRaySimulation::print_results_random_ray( uint64_t total_geometric_intersections, double avg_miss_rate, int negroups, - int64_t n_source_regions) const + int64_t n_source_regions, int64_t n_external_source_regions) const { using namespace simulation; @@ -355,6 +516,8 @@ void RandomRaySimulation::print_results_random_ray( fmt::print( " Total Iterations = {}\n", settings::n_batches); fmt::print(" Flat Source Regions (FSRs) = {}\n", n_source_regions); + fmt::print( + " FSRs Containing External Sources = {}\n", n_external_source_regions); fmt::print(" Total Geometric Intersections = {:.4e}\n", static_cast(total_geometric_intersections)); fmt::print(" Avg per Iteration = {:.4e}\n", @@ -369,6 +532,25 @@ void RandomRaySimulation::print_results_random_ray( fmt::print(" Avg per Iteration = {:.4e}\n", total_integrations / settings::n_batches); + std::string estimator; + switch (domain_->volume_estimator_) { + case RandomRayVolumeEstimator::SIMULATION_AVERAGED: + estimator = "Simulation Averaged"; + break; + case RandomRayVolumeEstimator::NAIVE: + estimator = "Naive"; + break; + case RandomRayVolumeEstimator::HYBRID: + estimator = "Hybrid"; + break; + default: + fatal_error("Invalid volume estimator type"); + } + fmt::print(" Volume Estimator Type = {}\n", estimator); + + std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF"; + fmt::print(" Adjoint Flux Mode = {}\n", adjoint_true); + header("Timing Statistics", 4); show_time("Total time for initialization", time_initialize.elapsed()); show_time("Reading cross sections", time_read_xs.elapsed(), 1); @@ -387,7 +569,7 @@ void RandomRaySimulation::print_results_random_ray( show_time("Time per integration", time_per_integration); } - if (settings::verbosity >= 4) { + if (settings::verbosity >= 4 && settings::run_mode == RunMode::EIGENVALUE) { header("Results", 4); fmt::print(" k-effective = {:.5f} +/- {:.5f}\n", simulation::keff, simulation::keff_std); diff --git a/src/settings.cpp b/src/settings.cpp index 6790f2cc0f7..61eda79967a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -72,6 +72,7 @@ bool survival_biasing {false}; bool temperature_multipole {false}; bool trigger_on {false}; bool trigger_predict {false}; +bool uniform_source_sampling {false}; bool ufs_on {false}; bool urr_ptables_on {true}; bool weight_windows_on {false}; @@ -107,7 +108,7 @@ int max_order {0}; int n_log_bins {8000}; int n_batches; int n_max_batches; -int max_splits {1000}; +int max_history_splits {10'000'000}; int max_tracks {1000}; ResScatMethod res_scat_method {ResScatMethod::rvs}; double res_scat_energy_min {0.01}; @@ -118,7 +119,10 @@ SolverType solver_type {SolverType::MONTE_CARLO}; std::unordered_set sourcepoint_batch; std::unordered_set statepoint_batch; std::unordered_set source_write_surf_id; -int64_t max_surface_particles; +int64_t ssw_max_particles; +int64_t ssw_max_files; +int64_t ssw_cell_id {C_NONE}; +SSWCellType ssw_cell_type {SSWCellType::None}; TemperatureMethod temperature_method {TemperatureMethod::NEAREST}; double temperature_tolerance {10.0}; double temperature_default {293.6}; @@ -191,7 +195,8 @@ void get_run_parameters(pugi::xml_node node_base) } // Get number of inactive batches - if (run_mode == RunMode::EIGENVALUE) { + if (run_mode == RunMode::EIGENVALUE || + solver_type == SolverType::RANDOM_RAY) { if (check_for_node(node_base, "inactive")) { n_inactive = std::stoi(get_node_value(node_base, "inactive")); } @@ -266,6 +271,41 @@ void get_run_parameters(pugi::xml_node node_base) } else { fatal_error("Specify random ray source in settings XML"); } + if (check_for_node(random_ray_node, "volume_estimator")) { + std::string temp_str = + get_node_value(random_ray_node, "volume_estimator", true, true); + if (temp_str == "simulation_averaged") { + FlatSourceDomain::volume_estimator_ = + RandomRayVolumeEstimator::SIMULATION_AVERAGED; + } else if (temp_str == "naive") { + FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::NAIVE; + } else if (temp_str == "hybrid") { + FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID; + } else { + fatal_error("Unrecognized volume estimator: " + temp_str); + } + } + if (check_for_node(random_ray_node, "source_shape")) { + std::string temp_str = + get_node_value(random_ray_node, "source_shape", true, true); + if (temp_str == "flat") { + RandomRay::source_shape_ = RandomRaySourceShape::FLAT; + } else if (temp_str == "linear") { + RandomRay::source_shape_ = RandomRaySourceShape::LINEAR; + } else if (temp_str == "linear_xy") { + RandomRay::source_shape_ = RandomRaySourceShape::LINEAR_XY; + } else { + fatal_error("Unrecognized source shape: " + temp_str); + } + } + if (check_for_node(random_ray_node, "volume_normalized_flux_tallies")) { + FlatSourceDomain::volume_normalized_flux_tallies_ = + get_node_value_bool(random_ray_node, "volume_normalized_flux_tallies"); + } + if (check_for_node(random_ray_node, "adjoint")) { + FlatSourceDomain::adjoint_ = + get_node_value_bool(random_ray_node, "adjoint"); + } } } @@ -523,9 +563,17 @@ void read_settings_xml(pugi::xml_node root) model::external_sources.push_back(make_unique(path)); } + // Build probability mass function for sampling external sources + vector source_strengths; + for (auto& s : model::external_sources) { + source_strengths.push_back(s->strength()); + } + model::external_sources_probability.assign(source_strengths); + // If no source specified, default to isotropic point source at origin with - // Watt spectrum - if (model::external_sources.empty()) { + // Watt spectrum. No default source is needed in random ray mode. + if (model::external_sources.empty() && + settings::solver_type != SolverType::RANDOM_RAY) { double T[] {0.0}; double p[] {1.0}; model::external_sources.push_back(make_unique( @@ -623,30 +671,37 @@ void read_settings_xml(pugi::xml_node root) } } - // Shannon Entropy mesh - if (check_for_node(root, "entropy_mesh")) { - int temp = std::stoi(get_node_value(root, "entropy_mesh")); - if (model::mesh_map.find(temp) == model::mesh_map.end()) { - fatal_error(fmt::format( - "Mesh {} specified for Shannon entropy does not exist.", temp)); + // Shannon entropy + if (solver_type == SolverType::RANDOM_RAY) { + if (check_for_node(root, "entropy_mesh")) { + fatal_error("Random ray uses FSRs to compute the Shannon entropy. " + "No user-defined entropy mesh is supported."); } + entropy_on = true; + } else if (solver_type == SolverType::MONTE_CARLO) { + if (check_for_node(root, "entropy_mesh")) { + int temp = std::stoi(get_node_value(root, "entropy_mesh")); + if (model::mesh_map.find(temp) == model::mesh_map.end()) { + fatal_error(fmt::format( + "Mesh {} specified for Shannon entropy does not exist.", temp)); + } - auto* m = - dynamic_cast(model::meshes[model::mesh_map.at(temp)].get()); - if (!m) - fatal_error("Only regular meshes can be used as an entropy mesh"); - simulation::entropy_mesh = m; + auto* m = dynamic_cast( + model::meshes[model::mesh_map.at(temp)].get()); + if (!m) + fatal_error("Only regular meshes can be used as an entropy mesh"); + simulation::entropy_mesh = m; - // Turn on Shannon entropy calculation - entropy_on = true; + // Turn on Shannon entropy calculation + entropy_on = true; - } else if (check_for_node(root, "entropy")) { - fatal_error( - "Specifying a Shannon entropy mesh via the element " - "is deprecated. Please create a mesh using and then reference " - "it by specifying its ID in an element."); + } else if (check_for_node(root, "entropy")) { + fatal_error( + "Specifying a Shannon entropy mesh via the element " + "is deprecated. Please create a mesh using and then reference " + "it by specifying its ID in an element."); + } } - // Uniform fission source weighting mesh if (check_for_node(root, "ufs_mesh")) { auto temp = std::stoi(get_node_value(root, "ufs_mesh")); @@ -739,13 +794,21 @@ void read_settings_xml(pugi::xml_node root) sourcepoint_batch = statepoint_batch; } + // Check is the user specified to convert strength to statistical weight + if (check_for_node(root, "uniform_source_sampling")) { + uniform_source_sampling = + get_node_value_bool(root, "uniform_source_sampling"); + } + // Check if the user has specified to write surface source if (check_for_node(root, "surf_source_write")) { surf_source_write = true; // Get surface source write node xml_node node_ssw = root.child("surf_source_write"); - // Determine surface ids at which crossing particles are to be banked + // Determine surface ids at which crossing particles are to be banked. + // If no surfaces are specified, all surfaces in the model will be used + // to bank source points. if (check_for_node(node_ssw, "surface_ids")) { auto temp = get_node_array(node_ssw, "surface_ids"); for (const auto& b : temp) { @@ -755,9 +818,20 @@ void read_settings_xml(pugi::xml_node root) // Get maximum number of particles to be banked per surface if (check_for_node(node_ssw, "max_particles")) { - max_surface_particles = - std::stoll(get_node_value(node_ssw, "max_particles")); + ssw_max_particles = std::stoll(get_node_value(node_ssw, "max_particles")); + } else { + fatal_error("A maximum number of particles needs to be specified " + "using the 'max_particles' parameter to store surface " + "source points."); + } + + // Get maximum number of surface source files to be created + if (check_for_node(node_ssw, "max_source_files")) { + ssw_max_files = std::stoll(get_node_value(node_ssw, "max_source_files")); + } else { + ssw_max_files = 1; } + if (check_for_node(node_ssw, "mcpl")) { surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl"); @@ -767,6 +841,27 @@ void read_settings_xml(pugi::xml_node root) "surface source files."); } } + // Get cell information + if (check_for_node(node_ssw, "cell")) { + ssw_cell_id = std::stoll(get_node_value(node_ssw, "cell")); + ssw_cell_type = SSWCellType::Both; + } + if (check_for_node(node_ssw, "cellfrom")) { + if (ssw_cell_id != C_NONE) { + fatal_error( + "'cell', 'cellfrom' and 'cellto' cannot be used at the same time."); + } + ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellfrom")); + ssw_cell_type = SSWCellType::From; + } + if (check_for_node(node_ssw, "cellto")) { + if (ssw_cell_id != C_NONE) { + fatal_error( + "'cell', 'cellfrom' and 'cellto' cannot be used at the same time."); + } + ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellto")); + ssw_cell_type = SSWCellType::To; + } } // If source is not separate and is to be written out in the statepoint file, @@ -979,8 +1074,9 @@ void read_settings_xml(pugi::xml_node root) weight_windows_on = get_node_value_bool(root, "weight_windows_on"); } - if (check_for_node(root, "max_splits")) { - settings::max_splits = std::stoi(get_node_value(root, "max_splits")); + if (check_for_node(root, "max_history_splits")) { + settings::max_history_splits = + std::stoi(get_node_value(root, "max_history_splits")); } if (check_for_node(root, "max_tracks")) { diff --git a/src/simulation.cpp b/src/simulation.cpp index 527d98db4f1..09b3764732b 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -117,6 +117,7 @@ int openmc_simulation_init() // Reset global variables -- this is done before loading state point (as that // will potentially populate k_generation and entropy) simulation::current_batch = 0; + simulation::ssw_current_file = 1; simulation::k_generation.clear(); simulation::entropy.clear(); openmc_reset(); @@ -137,7 +138,11 @@ int openmc_simulation_init() // Display header if (mpi::master) { if (settings::run_mode == RunMode::FIXED_SOURCE) { - header("FIXED SOURCE TRANSPORT SIMULATION", 3); + if (settings::solver_type == SolverType::MONTE_CARLO) { + header("FIXED SOURCE TRANSPORT SIMULATION", 3); + } else if (settings::solver_type == SolverType::RANDOM_RAY) { + header("FIXED SOURCE TRANSPORT SIMULATION (RANDOM RAY SOLVER)", 3); + } } else if (settings::run_mode == RunMode::EIGENVALUE) { if (settings::solver_type == SolverType::MONTE_CARLO) { header("K EIGENVALUE SIMULATION", 3); @@ -304,6 +309,7 @@ int n_lost_particles {0}; bool need_depletion_rx {false}; int restart_batch; bool satisfy_triggers {false}; +int ssw_current_file; int total_gen {0}; double total_weight; int64_t work_per_rank; @@ -333,7 +339,7 @@ void allocate_banks() if (settings::surf_source_write) { // Allocate surface source bank - simulation::surf_source_bank.reserve(settings::max_surface_particles); + simulation::surf_source_bank.reserve(settings::ssw_max_particles); } } @@ -341,9 +347,14 @@ void initialize_batch() { // Increment current batch ++simulation::current_batch; - if (settings::run_mode == RunMode::FIXED_SOURCE) { - write_message(6, "Simulating batch {}", simulation::current_batch); + if (settings::solver_type == SolverType::RANDOM_RAY && + simulation::current_batch < settings::n_inactive + 1) { + write_message( + 6, "Simulating batch {:<4} (inactive)", simulation::current_batch); + } else { + write_message(6, "Simulating batch {}", simulation::current_batch); + } } // Reset total starting particle weight used for normalizing tallies @@ -429,42 +440,49 @@ void finalize_batch() std::string source_point_filename = fmt::format("{0}source.{1:0{2}}", settings::path_output, simulation::current_batch, w); gsl::span bankspan(simulation::source_bank); - if (settings::source_mcpl_write) { - write_mcpl_source_point( - source_point_filename.c_str(), bankspan, simulation::work_index); - } else { - write_source_point( - source_point_filename.c_str(), bankspan, simulation::work_index); - } + write_source_point(source_point_filename, bankspan, + simulation::work_index, settings::source_mcpl_write); } // Write a continously-overwritten source point if requested. if (settings::source_latest) { - // note: correct file extension appended automatically auto filename = settings::path_output + "source"; gsl::span bankspan(simulation::source_bank); - if (settings::source_mcpl_write) { - write_mcpl_source_point( - filename.c_str(), bankspan, simulation::work_index); - } else { - write_source_point(filename.c_str(), bankspan, simulation::work_index); - } + write_source_point(filename.c_str(), bankspan, simulation::work_index, + settings::source_mcpl_write); } } // Write out surface source if requested. if (settings::surf_source_write && - simulation::current_batch == settings::n_batches) { - auto filename = settings::path_output + "surface_source"; - auto surf_work_index = - mpi::calculate_parallel_index_vector(simulation::surf_source_bank.size()); - gsl::span surfbankspan(simulation::surf_source_bank.begin(), - simulation::surf_source_bank.size()); - if (settings::surf_mcpl_write) { - write_mcpl_source_point(filename.c_str(), surfbankspan, surf_work_index); - } else { - write_source_point(filename.c_str(), surfbankspan, surf_work_index); + simulation::ssw_current_file <= settings::ssw_max_files) { + bool last_batch = (simulation::current_batch == settings::n_batches); + if (simulation::surf_source_bank.full() || last_batch) { + // Determine appropriate filename + auto filename = fmt::format("{}surface_source.{}", settings::path_output, + simulation::current_batch); + if (settings::ssw_max_files == 1 || + (simulation::ssw_current_file == 1 && last_batch)) { + filename = settings::path_output + "surface_source"; + } + + // Get span of source bank and calculate parallel index vector + auto surf_work_index = mpi::calculate_parallel_index_vector( + simulation::surf_source_bank.size()); + gsl::span surfbankspan(simulation::surf_source_bank.begin(), + simulation::surf_source_bank.size()); + + // Write surface source file + write_source_point( + filename, surfbankspan, surf_work_index, settings::surf_mcpl_write); + + // Reset surface source bank and increment counter + simulation::surf_source_bank.clear(); + if (!last_batch && settings::ssw_max_files >= 1) { + simulation::surf_source_bank.reserve(settings::ssw_max_particles); + } + ++simulation::ssw_current_file; } } } @@ -521,7 +539,8 @@ void finalize_generation() if (settings::run_mode == RunMode::EIGENVALUE) { // Calculate shannon entropy - if (settings::entropy_on) + if (settings::entropy_on && + settings::solver_type == SolverType::MONTE_CARLO) shannon_entropy(); // Collect results and statistics @@ -752,13 +771,15 @@ void transport_history_based_single_particle(Particle& p) { while (p.alive()) { p.event_calculate_xs(); - if (!p.alive()) - break; - p.event_advance(); - if (p.collision_distance() > p.boundary().distance) { - p.event_cross_surface(); - } else { - p.event_collide(); + if (p.alive()) { + p.event_advance(); + } + if (p.alive()) { + if (p.collision_distance() > p.boundary().distance) { + p.event_cross_surface(); + } else if (p.alive()) { + p.event_collide(); + } } p.event_revive_from_secondary(); } diff --git a/src/source.cpp b/src/source.cpp index 15fe8433ba5..2116809f2dd 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -44,7 +44,10 @@ namespace openmc { namespace model { vector> external_sources; -} + +DiscreteIndex external_sources_probability; + +} // namespace model //============================================================================== // Source implementation @@ -608,26 +611,24 @@ void initialize_source() SourceSite sample_external_source(uint64_t* seed) { - // Determine total source strength - double total_strength = 0.0; - for (auto& s : model::external_sources) - total_strength += s->strength(); - // Sample from among multiple source distributions int i = 0; if (model::external_sources.size() > 1) { - double xi = prn(seed) * total_strength; - double c = 0.0; - for (; i < model::external_sources.size(); ++i) { - c += model::external_sources[i]->strength(); - if (xi < c) - break; + if (settings::uniform_source_sampling) { + i = prn(seed) * model::external_sources.size(); + } else { + i = model::external_sources_probability.sample(seed); } } // Sample source site from i-th source distribution SourceSite site {model::external_sources[i]->sample_with_constraints(seed)}; + // Set particle creation weight + if (settings::uniform_source_sampling) { + site.wgt *= model::external_sources[i]->strength(); + } + // If running in MG, convert site.E to group if (!settings::run_CE) { site.E = lower_bound_index(data::mg.rev_energy_bins_.begin(), diff --git a/src/state_point.cpp b/src/state_point.cpp index c7b7d6ad85c..adc026fa3c2 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -15,6 +15,7 @@ #include "openmc/error.h" #include "openmc/file_utils.h" #include "openmc/hdf5_interface.h" +#include "openmc/mcpl_interface.h" #include "openmc/mesh.h" #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" @@ -568,8 +569,23 @@ hid_t h5banktype() return banktype; } -void write_source_point(const char* filename, gsl::span source_bank, - const vector& bank_index) +void write_source_point(std::string filename, gsl::span source_bank, + const vector& bank_index, bool use_mcpl) +{ + std::string ext = use_mcpl ? "mcpl" : "h5"; + write_message("Creating source file {}.{} with {} particles ...", filename, + ext, source_bank.size(), 5); + + // Dispatch to appropriate function based on file type + if (use_mcpl) { + write_mcpl_source_point(filename.c_str(), source_bank, bank_index); + } else { + write_h5_source_point(filename.c_str(), source_bank, bank_index); + } +} + +void write_h5_source_point(const char* filename, + gsl::span source_bank, const vector& bank_index) { // When using parallel HDF5, the file is written to collectively by all // processes. With MPI-only, the file is opened and written by the master diff --git a/src/string_utils.cpp b/src/string_utils.cpp index 3b1c1b4a678..74f048e8d24 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -2,7 +2,6 @@ #include // for equal #include // for tolower, isspace -#include namespace openmc { @@ -36,7 +35,7 @@ void to_lower(std::string& str) str[i] = std::tolower(str[i]); } -int word_count(std::string const& str) +int word_count(const std::string& str) { std::stringstream stream(str); std::string dum; diff --git a/src/surface.cpp b/src/surface.cpp index 12fef070e18..dbcaf849848 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -63,7 +63,8 @@ Surface::Surface(pugi::xml_node surf_node) { if (check_for_node(surf_node, "id")) { id_ = std::stoi(get_node_value(surf_node, "id")); - if (contains(settings::source_write_surf_id, id_)) { + if (contains(settings::source_write_surf_id, id_) || + settings::source_write_surf_id.empty()) { surf_source_ = true; } } else { @@ -164,9 +165,9 @@ void Surface::to_hdf5(hid_t group_id) const { hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_)); - if (geom_type_ == GeometryType::DAG) { + if (geom_type() == GeometryType::DAG) { write_string(surf_group, "geom_type", "dagmc", false); - } else if (geom_type_ == GeometryType::CSG) { + } else if (geom_type() == GeometryType::CSG) { write_string(surf_group, "geom_type", "csg", false); if (bc_) { @@ -188,11 +189,11 @@ void Surface::to_hdf5(hid_t group_id) const CSGSurface::CSGSurface() : Surface {} { - geom_type_ = GeometryType::CSG; + geom_type() = GeometryType::CSG; }; CSGSurface::CSGSurface(pugi::xml_node surf_node) : Surface {surf_node} { - geom_type_ = GeometryType::CSG; + geom_type() = GeometryType::CSG; }; //============================================================================== diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index ff7a3416b90..074212db44d 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -26,6 +26,7 @@ #include "openmc/tallies/filter_meshborn.h" #include "openmc/tallies/filter_meshsurface.h" #include "openmc/tallies/filter_mu.h" +#include "openmc/tallies/filter_musurface.h" #include "openmc/tallies/filter_particle.h" #include "openmc/tallies/filter_polar.h" #include "openmc/tallies/filter_sph_harm.h" @@ -133,6 +134,8 @@ Filter* Filter::create(const std::string& type, int32_t id) return Filter::create(id); } else if (type == "mu") { return Filter::create(id); + } else if (type == "musurface") { + return Filter::create(id); } else if (type == "particle") { return Filter::create(id); } else if (type == "polar") { diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index 5b01da1f65a..03f7da97847 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -80,7 +80,7 @@ void MeshFilter::set_mesh(int32_t mesh) // perform any additional perparation for mesh tallies here mesh_ = mesh; n_bins_ = model::meshes[mesh_]->n_bins(); - model::meshes[mesh_]->prepare_for_tallies(); + model::meshes[mesh_]->prepare_for_point_location(); } void MeshFilter::set_translation(const Position& translation) diff --git a/src/tallies/filter_musurface.cpp b/src/tallies/filter_musurface.cpp new file mode 100644 index 00000000000..58fe39b9113 --- /dev/null +++ b/src/tallies/filter_musurface.cpp @@ -0,0 +1,36 @@ +#include "openmc/tallies/filter_musurface.h" + +#include // for abs, copysign + +#include "openmc/search.h" +#include "openmc/surface.h" +#include "openmc/tallies/tally_scoring.h" + +namespace openmc { + +void MuSurfaceFilter::get_all_bins( + const Particle& p, TallyEstimator estimator, FilterMatch& match) const +{ + // Get surface normal (and make sure it is a unit vector) + const auto surf {model::surfaces[std::abs(p.surface()) - 1].get()}; + auto n = surf->normal(p.r()); + n /= n.norm(); + + // Determine whether normal should be pointing in or out + if (p.surface() < 0) + n *= -1; + + // Determine cosine of angle between normal and particle direction + double mu = p.u().dot(n); + if (std::abs(mu) > 1.0) + mu = std::copysign(1.0, mu); + + // Find matching bin + if (mu >= bins_.front() && mu <= bins_.back()) { + auto bin = lower_bound_index(bins_.begin(), bins_.end(), mu); + match.bins_.push_back(bin); + match.weights_.push_back(1.0); + } +} + +} // namespace openmc diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 674987b8f13..4f33abf6beb 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -714,7 +714,7 @@ void Tally::init_triggers(pugi::xml_node node) } else { int i_score = 0; for (; i_score < this->scores_.size(); ++i_score) { - if (reaction_name(this->scores_[i_score]) == score_str) + if (this->scores_[i_score] == reaction_type(score_str)) break; } if (i_score == this->scores_.size()) { @@ -751,7 +751,8 @@ void Tally::accumulate() if (mpi::master || !settings::reduce_tallies) { // Calculate total source strength for normalization double total_source = 0.0; - if (settings::run_mode == RunMode::FIXED_SOURCE) { + if (settings::run_mode == RunMode::FIXED_SOURCE && + !settings::uniform_source_sampling) { for (const auto& s : model::external_sources) { total_source += s->strength(); } diff --git a/src/thermal.cpp b/src/thermal.cpp index 741f89ed104..cbe0983ed65 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -19,6 +19,7 @@ #include "openmc/secondary_correlated.h" #include "openmc/secondary_thermal.h" #include "openmc/settings.h" +#include "openmc/string_utils.h" namespace openmc { @@ -59,7 +60,7 @@ ThermalScattering::ThermalScattering( // Read temperature value double T; read_dataset(kT_group, dset_names[i].data(), T); - temps_available[i] = T / K_BOLTZMANN; + temps_available[i] = std::round(T / K_BOLTZMANN); } std::sort(temps_available.begin(), temps_available.end()); @@ -89,9 +90,12 @@ ThermalScattering::ThermalScattering( temps_to_read.push_back(std::round(temp_actual)); } } else { - fatal_error(fmt::format("Nuclear data library does not contain cross " - "sections for {} at or near {} K.", - name_, std::round(T))); + fatal_error(fmt::format( + "Nuclear data library does not contain cross sections " + "for {} at or near {} K. Available temperatures " + "are {} K. Consider making use of openmc.Settings.temperature " + "to specify how intermediate temperatures are treated.", + name_, std::round(T), concatenate(temps_available))); } } break; @@ -103,8 +107,8 @@ ThermalScattering::ThermalScattering( bool found = false; for (int j = 0; j < temps_available.size() - 1; ++j) { if (temps_available[j] <= T && T < temps_available[j + 1]) { - int T_j = std::round(temps_available[j]); - int T_j1 = std::round(temps_available[j + 1]); + int T_j = temps_available[j]; + int T_j1 = temps_available[j + 1]; if (std::find(temps_to_read.begin(), temps_to_read.end(), T_j) == temps_to_read.end()) { temps_to_read.push_back(T_j); @@ -122,14 +126,14 @@ ThermalScattering::ThermalScattering( if (std::abs(T - temps_available[0]) <= settings::temperature_tolerance) { if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(temps_available[0])) == temps_to_read.end()) { - temps_to_read.push_back(std::round(temps_available[0])); + temps_available[0]) == temps_to_read.end()) { + temps_to_read.push_back(temps_available[0]); } } else if (std::abs(T - temps_available[n - 1]) <= settings::temperature_tolerance) { if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(temps_available[n - 1])) == temps_to_read.end()) { - temps_to_read.push_back(std::round(temps_available[n - 1])); + temps_available[n - 1]) == temps_to_read.end()) { + temps_to_read.push_back(temps_available[n - 1]); } } else { fatal_error( diff --git a/src/weight_windows.cpp b/src/weight_windows.cpp index 1ca83a2bd79..e798a2f7abd 100644 --- a/src/weight_windows.cpp +++ b/src/weight_windows.cpp @@ -102,7 +102,7 @@ void apply_weight_windows(Particle& p) // the window if (weight > weight_window.upper_weight) { // do not further split the particle if above the limit - if (p.n_split() >= settings::max_splits) + if (p.n_split() >= settings::max_history_splits) return; double n_split = std::ceil(weight / weight_window.upper_weight); @@ -147,8 +147,8 @@ WeightWindows::WeightWindows(int32_t id) WeightWindows::WeightWindows(pugi::xml_node node) { // Make sure required elements are present - const vector required_elems {"id", "particle_type", - "energy_bounds", "lower_ww_bounds", "upper_ww_bounds"}; + const vector required_elems { + "id", "particle_type", "lower_ww_bounds", "upper_ww_bounds"}; for (const auto& elem : required_elems) { if (!check_for_node(node, elem.c_str())) { fatal_error(fmt::format("Must specify <{}> for weight windows.", elem)); @@ -165,7 +165,7 @@ WeightWindows::WeightWindows(pugi::xml_node node) // Determine associated mesh int32_t mesh_id = std::stoi(get_node_value(node, "mesh")); - mesh_idx_ = model::mesh_map.at(mesh_id); + set_mesh(model::mesh_map.at(mesh_id)); // energy bounds if (check_for_node(node, "energy_bounds")) @@ -340,6 +340,7 @@ void WeightWindows::set_mesh(int32_t mesh_idx) fatal_error(fmt::format("Could not find a mesh for index {}", mesh_idx)); mesh_idx_ = mesh_idx; + model::meshes[mesh_idx_]->prepare_for_point_location(); allocate_ww_bounds(); } @@ -736,7 +737,7 @@ WeightWindowsGenerator::WeightWindowsGenerator(pugi::xml_node node) int32_t mesh_idx = model::mesh_map[mesh_id]; max_realizations_ = std::stoi(get_node_value(node, "max_realizations")); - int active_batches = settings::n_batches - settings::n_inactive; + int32_t active_batches = settings::n_batches - settings::n_inactive; if (max_realizations_ > active_batches) { auto msg = fmt::format("The maximum number of specified tally realizations ({}) is " diff --git a/src/xml_interface.cpp b/src/xml_interface.cpp index 6ce465bafb9..840d3f5b871 100644 --- a/src/xml_interface.cpp +++ b/src/xml_interface.cpp @@ -4,6 +4,7 @@ #include "openmc/error.h" #include "openmc/string_utils.h" +#include "openmc/vector.h" namespace openmc { @@ -48,6 +49,24 @@ bool get_node_value_bool(pugi::xml_node node, const char* name) return false; } +vector get_node_position_array( + pugi::xml_node node, const char* name, bool lowercase) +{ + vector coords = get_node_array(node, name, lowercase); + if (coords.size() % 3 != 0) { + fatal_error(fmt::format( + "Incorect number of coordinates in Position array ({}) for \"{}\"", + coords.size(), name)); + } + vector positions; + positions.reserve(coords.size() / 3); + auto it = coords.begin(); + for (size_t i = 0; i < coords.size(); i += 3) { + positions.push_back({coords[i], coords[i + 1], coords[i + 2]}); + } + return positions; +} + Position get_node_position( pugi::xml_node node, const char* name, bool lowercase) { diff --git a/tests/conftest.py b/tests/conftest.py index 639d669f3a8..cd86da53900 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import pytest +import openmc from tests.regression_tests import config as regression_config @@ -27,3 +28,9 @@ def run_in_tmpdir(tmpdir): yield finally: orig.chdir() + + +@pytest.fixture(scope='session', autouse=True) +def resolve_paths(): + with openmc.config.patch('resolve_paths', False): + yield diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index 24ec1b7a90e..f0f5f2853ad 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -3,6 +3,7 @@ set(TEST_NAMES test_file_utils test_tally test_interpolate + test_math # Add additional unit test files here ) diff --git a/tests/cpp_unit_tests/test_math.cpp b/tests/cpp_unit_tests/test_math.cpp new file mode 100644 index 00000000000..1ad7c4b7097 --- /dev/null +++ b/tests/cpp_unit_tests/test_math.cpp @@ -0,0 +1,357 @@ +#include +#include + +#include +#include +#include + +#include "openmc/math_functions.h" +#include "openmc/random_dist.h" +#include "openmc/random_lcg.h" +#include "openmc/wmp.h" + +TEST_CASE("Test t_percentile") +{ + // The reference solutions come from scipy.stats.t.ppf + std::vector> ref_ts { + {-15.894544844102773, -0.32491969623407446, 0.000000000000000, + 0.32491969623407446, 15.894544844102759}, + {-4.848732214442601, -0.2886751346880066, 0.000000000000000, + 0.2886751346880066, 4.848732214442598}, + {-2.756508521909475, -0.2671808657039658, 0.000000000000000, + 0.2671808657039658, 2.7565085219094745}}; + + // Permutations include 1 DoF, 2 DoF, and > 2 DoF + // We will test 5 p-values at 3-DoF values + std::vector test_ps {0.02, 0.4, 0.5, 0.6, 0.98}; + std::vector test_dfs {1, 2, 5}; + + for (int i = 0; i < test_dfs.size(); i++) { + int df = test_dfs[i]; + + std::vector test_ts; + + for (double p : test_ps) { + double test_t = openmc::t_percentile(p, df); + test_ts.push_back(test_t); + } + + // The 5 DoF approximation in openmc.lib.math.t_percentile is off by up to + // 8e-3 from the scipy solution, so test that one separately with looser + // tolerance + double tolerance = (df > 2) ? 1e-2 : 1e-6; + + REQUIRE_THAT( + ref_ts[i], Catch::Matchers::Approx(test_ts).epsilon(tolerance)); + } +} + +TEST_CASE("Test calc_pn") +{ + // The reference solutions come from scipy.special.eval_legendre + std::vector> ref_vals { + {1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1}, + {1, -0.5, -0.125, 0.4375, -0.289062, -0.0898438, 0.323242, -0.223145, + -0.0736389, 0.267899, -0.188229}, + {1, 0, -0.5, -0, 0.375, 0, -0.3125, -0, 0.273438, 0, -0.246094}, + {1, 0.5, -0.125, -0.4375, -0.289062, 0.0898438, 0.323242, 0.223145, + -0.0736389, -0.267899, -0.188229}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; + + int max_order = 10; + std::vector test_xs = {-1.0, -0.5, 0.0, 0.5, 1.0}; + + std::vector> test_vals; + for (double x : test_xs) { + std::vector test_val(max_order + 1); + openmc::calc_pn_c(max_order, x, test_val.data()); + test_vals.push_back(test_val); + } + + for (int i = 0; i < ref_vals.size(); i++) { + REQUIRE_THAT(ref_vals[i], Catch::Matchers::Approx(test_vals[i])); + } +} + +TEST_CASE("Test evaluate_legendre") +{ + // The reference solutions come from numpy.polynomial.legendre.legval + std::vector ref_vals { + 5.5, -0.45597649, -1.35351562, -2.7730999, 60.5}; + + int max_order = 10; + std::vector test_xs = {-1.0, -0.5, 0.0, 0.5, 1.0}; + + // Set the coefficients back to 1s for the test values since + // evaluate legendre incorporates the (2l+1)/2 term on its own + std::vector test_coeffs(max_order + 1, 1.0); + + std::vector test_vals; + for (double x : test_xs) { + test_vals.push_back( + openmc::evaluate_legendre(test_coeffs.size() - 1, test_coeffs.data(), x)); + } + + REQUIRE_THAT(ref_vals, Catch::Matchers::Approx(test_vals)); +} + +TEST_CASE("Test calc_rn") +{ + std::vector ref_vals {1.000000000000000, -0.019833838076210, + 0.980066577841242, -0.197676811654084, 0.006790834062088, + -0.033668438114859, 0.940795745502164, -0.335561350977312, + 0.033500236162691, -0.001831975566765, 0.014882082223994, + -0.046185860057145, 0.883359726009014, -0.460318044571973, + 0.073415616482180, -0.005922278973373, 0.000448625292461, + -0.004750335422039, 0.025089695062177, -0.057224052171859, + 0.809468042300133, -0.570331780454957, 0.123771351522967, + -0.015356543011155, 0.001061098599927, -0.000104097571795, + 0.001319047965347, -0.009263463267120, 0.037043163155191, + -0.066518621473934, 0.721310852552881, -0.662967447756079, + 0.182739660926192, -0.029946258412359, 0.003119841820746, + -0.000190549327031, 0.000023320052630, -0.000338370521658, + 0.002878809439524, -0.015562587450914, 0.050271226423217, + -0.073829294593737, 0.621486505922182, -0.735830327235834, + 0.247995745731425, -0.050309614442385, 0.006809024629381, + -0.000619383085285, 0.000034086826414, -0.000005093626712, + 0.000082405610567, -0.000809532012556, 0.005358034016708, + -0.023740240859138, 0.064242405926477, -0.078969918083157, + 0.512915839160049, -0.787065093668736, 0.316917738015632, + -0.076745744765114, 0.012672942183651, -0.001481838409317, + 0.000120451946983, -0.000006047366709, 0.000001091052697, + -0.000019334294214, 0.000213051604838, -0.001640234119608, + 0.008982263900105, -0.033788039035668, 0.078388909900756, + -0.081820779415058, 0.398746190829636, -0.815478614863816, + 0.386704633068855, -0.109227544713261, 0.021245051959237, + -0.003002428416676, 0.000311416667310, -0.000022954482885, + 0.000001059646310, -0.000000230023931, 0.000004408854505, + -0.000053457925526, 0.000464152759861, -0.002976305522860, + 0.013958017448970, -0.045594791382625, 0.092128969315914, + -0.082334538374971, 0.282248459574595, -0.820599067736528, + 0.454486474163594, -0.147395565311743, 0.033013815809602, + -0.005448090715661, 0.000678450207914, -0.000063467485444, + 0.000004281943868, -0.000000182535754, 0.000000047847775, + -0.000000982664801, 0.000012933320414, -0.000124076425457, + 0.000901739739837, -0.004982323311961, 0.020457776068931, + -0.058948376674391, 0.104888993733747, -0.080538298991650, + 0.166710763818175, -0.802696588503912, 0.517433650833039, + -0.190564076304612, 0.048387190622376, -0.009120081648146, + 0.001318069323039, -0.000147308722683, 0.000012561029621, + -0.000000779794781, 0.000000030722703}; + + int max_order = 10; + + double azi = 0.1; // Longitude + double pol = 0.2; // Latitude + double mu = std::cos(pol); + + std::vector test_uvw {std::sin(pol) * std::cos(azi), + std::sin(pol) * std::sin(azi), std::cos(pol)}; + + std::vector test_vals((max_order + 1) * (max_order + 1), 0); + openmc::calc_rn_c(max_order, test_uvw.data(), test_vals.data()); + + REQUIRE_THAT(ref_vals, Catch::Matchers::Approx(test_vals)); +} + +TEST_CASE("Test calc_zn") +{ + std::vector ref_vals {1.00000000e+00, 2.39712769e-01, 4.38791281e-01, + 2.10367746e-01, -5.00000000e-01, 1.35075576e-01, 1.24686873e-01, + -2.99640962e-01, -5.48489101e-01, 8.84215021e-03, 5.68310892e-02, + -4.20735492e-01, -1.25000000e-01, -2.70151153e-01, -2.60091773e-02, + 1.87022545e-02, -3.42888902e-01, 1.49820481e-01, 2.74244551e-01, + -2.43159131e-02, -2.50357380e-02, 2.20500013e-03, -1.98908812e-01, + 4.07587508e-01, 4.37500000e-01, 2.61708929e-01, 9.10321205e-02, + -1.54686328e-02, -2.74049397e-03, -7.94845816e-02, 4.75368705e-01, + 7.11647284e-02, 1.30266162e-01, 3.37106977e-02, 1.06401886e-01, + -7.31606787e-03, -2.95625975e-03, -1.10250006e-02, 3.55194307e-01, + -1.44627826e-01, -2.89062500e-01, -9.28644588e-02, -1.62557358e-01, + 7.73431638e-02, -2.55329539e-03, -1.90923851e-03, 1.57578403e-02, + 1.72995854e-01, -3.66267690e-01, -1.81657333e-01, -3.32521518e-01, + -2.59738162e-02, -2.31580576e-01, 4.20673902e-02, -4.11710546e-04, + -9.36449487e-04, 1.92156884e-02, 2.82515641e-02, -3.90713738e-01, + -1.69280296e-01, -8.98437500e-02, -1.08693628e-01, 1.78813094e-01, + -1.98191857e-01, 1.65964201e-02, 2.77013853e-04}; + + int n = 10; + double rho = 0.5; + double phi = 0.5; + + int nums = ((n + 1) * (n + 2)) / 2; + + std::vector test_vals(nums, 0); + openmc::calc_zn(n, rho, phi, test_vals.data()); + + REQUIRE_THAT(ref_vals, Catch::Matchers::Approx(test_vals)); +} + +TEST_CASE("Test calc_zn_rad") +{ + std::vector ref_vals {1.00000000e+00, -5.00000000e-01, + -1.25000000e-01, 4.37500000e-01, -2.89062500e-01, -8.98437500e-02}; + + int n = 10; + double rho = 0.5; + + int nums = n / 2 + 1; + std::vector test_vals(nums, 0); + openmc::calc_zn_rad(n, rho, test_vals.data()); + + REQUIRE_THAT(ref_vals, Catch::Matchers::Approx(test_vals)); +} + +TEST_CASE("Test rotate_angle") +{ + std::vector uvw0 {1.0, 0.0, 0.0}; + double phi = 0.0; + + uint64_t prn_seed = 1; + openmc::prn(&prn_seed); + + SECTION("Test rotate_angle mu is 0") + { + std::vector ref_uvw {0.0, 0.0, -1.0}; + + double mu = 0.0; + + std::vector test_uvw(uvw0); + openmc::rotate_angle_c(test_uvw.data(), mu, &phi, &prn_seed); + + REQUIRE_THAT(ref_uvw, Catch::Matchers::Approx(test_uvw)); + } + + SECTION("Test rotate_angle mu is 1") + { + std::vector ref_uvw = {1.0, 0.0, 0.0}; + + double mu = 1.0; + + std::vector test_uvw(uvw0); + openmc::rotate_angle_c(test_uvw.data(), mu, &phi, &prn_seed); + + REQUIRE_THAT(ref_uvw, Catch::Matchers::Approx(test_uvw)); + } + + // Now to test phi is None + SECTION("Test rotate_angle no phi") + { + // When seed = 1, phi will be sampled as 1.9116495709698769 + // The resultant reference is from hand-calculations given the above + std::vector ref_uvw = { + 0.9, -0.422746750548505, 0.10623175090659095}; + + double mu = 0.9; + prn_seed = 1; + + std::vector test_uvw(uvw0); + openmc::rotate_angle_c(test_uvw.data(), mu, NULL, &prn_seed); + + REQUIRE_THAT(ref_uvw, Catch::Matchers::Approx(test_uvw)); + } +} + +TEST_CASE("Test maxwell_spectrum") +{ + double ref_val = 0.27767406743161277; + + double T = 0.5; + uint64_t prn_seed = 1; + + double test_val = openmc::maxwell_spectrum(T, &prn_seed); + + REQUIRE(ref_val == test_val); +} + +TEST_CASE("Test watt_spectrum") +{ + double ref_val = 0.30957476387766697; + + double a = 0.5; + double b = 0.75; + uint64_t prn_seed = 1; + + double test_val = openmc::watt_spectrum(a, b, &prn_seed); + + REQUIRE(ref_val == test_val); +} + +TEST_CASE("Test normal_variate") +{ + + // Generate a series of normally distributed random numbers and test + // whether their mean and standard deviation are close to the expected value + SECTION("Test with non-zero standard deviation") + { + uint64_t seed = 1; + + double mean = 0.0; + double standard_deviation = 1.0; + + int num_samples = 10000; + double sum = 0.0; + double sum_squared_difference = 0.0; + + for (int i = 0; i < num_samples; ++i) { + double sample = openmc::normal_variate(mean, standard_deviation, &seed); + sum += sample; + sum_squared_difference += (sample - mean) * (sample - mean); + } + + double actual_mean = sum / num_samples; + double actual_standard_deviation = + std::sqrt(sum_squared_difference / num_samples); + + REQUIRE_THAT(mean, Catch::Matchers::WithinAbs(actual_mean, 0.1)); + REQUIRE_THAT(standard_deviation, + Catch::Matchers::WithinAbs(actual_standard_deviation, 0.1)); + } + + // When the standard deviation is zero + // the generated random number should always be equal to the mean + SECTION("Test with zero standard deviation") + { + uint64_t seed = 1; + double mean = 5.0; + double standard_deviation = 0.0; + + for (int i = 0; i < 10; ++i) { + double sample = openmc::normal_variate(mean, standard_deviation, &seed); + REQUIRE(sample == mean); + } + } +} + +TEST_CASE("Test broaden_wmp_polynomials") +{ + double test_E = 0.5; + int n = 6; + + // Two branches of the code to worry about, beta > 6 and otherwise + // beta = sqrtE * dopp + SECTION("Test broaden_wmp_polynomials beta > 6") + { + std::vector ref_val { + 2., 1.41421356, 1.0001, 0.70731891, 0.50030001, 0.353907}; + + double test_dopp = 100.0; // approximately U235 at room temperature + + std::vector test_val(n, 0); + openmc::broaden_wmp_polynomials(test_E, test_dopp, n, test_val.data()); + + REQUIRE_THAT(ref_val, Catch::Matchers::Approx(test_val)); + } + + SECTION("Test broaden_wmp_polynomials beta < 6") + { + std::vector ref_val = { + 1.99999885, 1.41421356, 1.04, 0.79195959, 0.6224, 0.50346003}; + + double test_dopp = 5.0; + + std::vector test_val(n, 0); + openmc::broaden_wmp_polynomials(test_E, test_dopp, n, test_val.data()); + + REQUIRE_THAT(ref_val, Catch::Matchers::Approx(test_val)); + } +} diff --git a/tests/regression_tests/__init__.py b/tests/regression_tests/__init__.py index ba48fc01e9e..e1cb56f1dd8 100644 --- a/tests/regression_tests/__init__.py +++ b/tests/regression_tests/__init__.py @@ -12,6 +12,17 @@ } +def assert_same_mats(res_ref, res_test): + for mat in res_ref[0].index_mat: + assert mat in res_test[0].index_mat, f"Material {mat} not in new results." + for nuc in res_ref[0].index_nuc: + assert nuc in res_test[0].index_nuc, f"Nuclide {nuc} not in new results." + for mat in res_test[0].index_mat: + assert mat in res_ref[0].index_mat, f"Material {mat} not in old results." + for nuc in res_test[0].index_nuc: + assert nuc in res_ref[0].index_nuc, f"Nuclide {nuc} not in old results." + + def assert_atoms_equal(res_ref, res_test, tol=1e-5): for mat in res_test[0].index_mat: for nuc in res_test[0].index_nuc: diff --git a/tests/regression_tests/conftest.py b/tests/regression_tests/conftest.py index b4a7644762c..1cdf414e767 100644 --- a/tests/regression_tests/conftest.py +++ b/tests/regression_tests/conftest.py @@ -1,12 +1,12 @@ import numpy as np import openmc -from pkg_resources import parse_version +from packaging.version import parse import pytest @pytest.fixture(scope='module', autouse=True) def numpy_version_requirement(): - assert parse_version(np.__version__) >= parse_version("1.14"), \ + assert parse(np.__version__) >= parse("1.14"), \ "Regression tests require NumPy 1.14 or greater" diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index 884264f30ef..b6b1e376b00 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -104,5 +104,4 @@ def test_surf_source(model): def test_dagmc(model): harness = PyAPITestHarness('statepoint.5.h5', model) - harness.main() - + harness.main() \ No newline at end of file diff --git a/tests/regression_tests/dagmc/universes/inputs_true.dat b/tests/regression_tests/dagmc/universes/inputs_true.dat index babf43ff1d8..4b5be3612bd 100644 --- a/tests/regression_tests/dagmc/universes/inputs_true.dat +++ b/tests/regression_tests/dagmc/universes/inputs_true.dat @@ -47,9 +47,18 @@ eigenvalue 100 10 - 2 + 5 false - + + + 4 0 4 1 4 2 4 3 4 4 + + + 1 + scatter + + + diff --git a/tests/regression_tests/dagmc/universes/results_true.dat b/tests/regression_tests/dagmc/universes/results_true.dat index f7bd016c155..76cbc9db0cf 100644 --- a/tests/regression_tests/dagmc/universes/results_true.dat +++ b/tests/regression_tests/dagmc/universes/results_true.dat @@ -1,2 +1,13 @@ k-combined: -9.156561E-01 4.398617E-02 +9.887663E-01 1.510336E-02 +tally 1: +4.340758E+00 +4.265459E+00 +4.712319E+00 +4.654778E+00 +4.151897E+00 +3.588090E+00 +2.965925E+00 +1.852746E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/dagmc/universes/test.py b/tests/regression_tests/dagmc/universes/test.py index a318275cd01..057a7b0d477 100644 --- a/tests/regression_tests/dagmc/universes/test.py +++ b/tests/regression_tests/dagmc/universes/test.py @@ -72,13 +72,20 @@ def __init__(self, *args, **kwargs): self._model.geometry = openmc.Geometry([bounding_cell]) + # add a cell instance tally + tally = openmc.Tally(name='cell instance tally') + # using scattering + cell_instance_filter = openmc.CellInstanceFilter(((4, 0), (4, 1), (4, 2), (4, 3), (4, 4))) + tally.filters = [cell_instance_filter] + tally.scores = ['scatter'] + self._model.tallies = [tally] + # settings self._model.settings.particles = 100 self._model.settings.batches = 10 - self._model.settings.inactive = 2 + self._model.settings.inactive = 5 self._model.settings.output = {'summary' : False} - def test_univ(): harness = DAGMCUniverseTest('statepoint.10.h5', model=openmc.Model()) harness.main() diff --git a/tests/regression_tests/deplete_no_transport/test.py b/tests/regression_tests/deplete_no_transport/test.py index 54c29cd5b70..63ae584e116 100644 --- a/tests/regression_tests/deplete_no_transport/test.py +++ b/tests/regression_tests/deplete_no_transport/test.py @@ -10,7 +10,7 @@ from openmc.deplete import IndependentOperator, MicroXS from tests.regression_tests import config, assert_atoms_equal, \ - assert_reaction_rates_equal + assert_reaction_rates_equal, assert_same_mats @pytest.fixture(scope="module") @@ -101,7 +101,7 @@ def test_against_self(run_in_tmpdir, res_ref = openmc.deplete.Results(path_reference) # Assert same mats - _assert_same_mats(res_test, res_ref) + assert_same_mats(res_ref, res_test) tol = 1.0e-14 assert_atoms_equal(res_ref, res_test, tol) @@ -155,7 +155,7 @@ def test_against_coupled(run_in_tmpdir, res_ref = openmc.deplete.Results(path_reference) # Assert same mats - _assert_same_mats(res_test, res_ref) + assert_same_mats(res_test, res_ref) assert_atoms_equal(res_ref, res_test, atom_tol) assert_reaction_rates_equal(res_ref, res_test, rx_tol) @@ -172,6 +172,7 @@ def _create_operator(from_nuclides, for nuc, dens in fuel.get_nuclide_atom_densities().items(): nuclides[nuc] = dens + openmc.reset_auto_ids() op = IndependentOperator.from_nuclides(fuel.volume, nuclides, flux, @@ -187,20 +188,3 @@ def _create_operator(from_nuclides, normalization_mode=normalization_mode) return op - - -def _assert_same_mats(res_ref, res_test): - for mat in res_ref[0].index_mat: - assert mat in res_test[0].index_mat, \ - f"Material {mat} not in new results." - for nuc in res_ref[0].index_nuc: - assert nuc in res_test[0].index_nuc, \ - f"Nuclide {nuc} not in new results." - - for mat in res_test[0].index_mat: - assert mat in res_ref[0].index_mat, \ - f"Material {mat} not in old results." - for nuc in res_test[0].index_nuc: - assert nuc in res_ref[0].index_nuc, \ - f"Nuclide {nuc} not in old results." - diff --git a/tests/regression_tests/deplete_with_transfer_rates/test.py b/tests/regression_tests/deplete_with_transfer_rates/test.py index 669fd4bea01..10d60866a69 100644 --- a/tests/regression_tests/deplete_with_transfer_rates/test.py +++ b/tests/regression_tests/deplete_with_transfer_rates/test.py @@ -11,11 +11,12 @@ from openmc.deplete import CoupledOperator from tests.regression_tests import config, assert_reaction_rates_equal, \ - assert_atoms_equal + assert_atoms_equal, assert_same_mats @pytest.fixture def model(): + openmc.reset_auto_ids() f = openmc.Material(name="f") f.add_element("U", 1, percent_type="ao", enrichment=4.25) f.add_element("O", 2) @@ -46,7 +47,6 @@ def model(): return openmc.Model(geometry, materials, settings) -@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+") @pytest.mark.parametrize("rate, dest_mat, power, ref_result", [ (1e-5, None, 0.0, 'no_depletion_only_removal'), (-1e-5, None, 0.0, 'no_depletion_only_feed'), @@ -67,7 +67,7 @@ def test_transfer_rates(run_in_tmpdir, model, rate, dest_mat, power, ref_result) integrator = openmc.deplete.PredictorIntegrator( op, [1], power, timestep_units = 'd') integrator.add_transfer_rate('f', transfer_elements, rate, - destination_material=dest_mat) + destination_material=dest_mat) integrator.integrate() # Get path to test and reference results @@ -83,5 +83,6 @@ def test_transfer_rates(run_in_tmpdir, model, rate, dest_mat, power, ref_result) res_ref = openmc.deplete.Results(path_reference) res_test = openmc.deplete.Results(path_test) + assert_same_mats(res_ref, res_test) assert_atoms_equal(res_ref, res_test, 1e-6) assert_reaction_rates_equal(res_ref, res_test) diff --git a/tests/regression_tests/random_ray_basic/__init__.py b/tests/regression_tests/filter_cellfrom/__init__.py similarity index 100% rename from tests/regression_tests/random_ray_basic/__init__.py rename to tests/regression_tests/filter_cellfrom/__init__.py diff --git a/tests/regression_tests/filter_cellfrom/inputs_true.dat b/tests/regression_tests/filter_cellfrom/inputs_true.dat new file mode 100644 index 00000000000..b85f63c6e18 --- /dev/null +++ b/tests/regression_tests/filter_cellfrom/inputs_true.dat @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 2000 + 15 + 5 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + 1 + + + + 1 + + + 1 + + + 2 + + + 3 + + + 4 + + + 2 + + + 3 + + + 4 + + + 5 1 + total + + + 5 2 + total + + + 5 3 + total + + + 5 4 + total + + + 6 1 + total + + + 6 2 + total + + + 6 3 + total + + + 6 4 + total + + + 7 1 + total + + + 7 2 + total + + + 7 3 + total + + + 7 4 + total + + + 8 1 + total + + + 8 2 + total + + + 8 3 + total + + + 8 4 + total + + + total + + + diff --git a/tests/regression_tests/filter_cellfrom/results_true.dat b/tests/regression_tests/filter_cellfrom/results_true.dat new file mode 100644 index 00000000000..5dd43e1f33b --- /dev/null +++ b/tests/regression_tests/filter_cellfrom/results_true.dat @@ -0,0 +1,53 @@ +k-combined: +9.640806E-02 2.655206E-03 +tally 1: +6.025999E+00 +3.635317E+00 +tally 2: +4.523606E-04 +2.051522E-08 +tally 3: +6.026452E+00 +3.635862E+00 +tally 4: +0.000000E+00 +0.000000E+00 +tally 5: +1.530903E+00 +2.357048E-01 +tally 6: +4.992646E-05 +2.600391E-10 +tally 7: +1.530953E+00 +2.357201E-01 +tally 8: +1.889115E+01 +3.574727E+01 +tally 9: +7.556902E+00 +5.717680E+00 +tally 10: +5.022871E-04 +2.531352E-08 +tally 11: +7.557405E+00 +5.718440E+00 +tally 12: +1.889115E+01 +3.574727E+01 +tally 13: +0.000000E+00 +0.000000E+00 +tally 14: +2.663407E-04 +7.161725E-09 +tally 15: +2.663407E-04 +7.161725E-09 +tally 16: +8.025710E+01 +6.459305E+02 +tally 17: +1.067059E+02 +1.140939E+03 diff --git a/tests/regression_tests/filter_cellfrom/test.py b/tests/regression_tests/filter_cellfrom/test.py new file mode 100644 index 00000000000..5559b4c8171 --- /dev/null +++ b/tests/regression_tests/filter_cellfrom/test.py @@ -0,0 +1,316 @@ +"""This test ensures that the CellFromFilter works correctly even if the level of +coordinates (number of encapsulated universes) is different in the cell from +where the particle originates compared to the cell where the particle is going. + +A matrix of reaction rates based on where the particle is coming from and +where it goes to is calculated and compared to the total reaction rate of the problem. +The components of this matrix are also compared to other components using symmetric +properties. + +TODO: + +- Test with a lattice, +- Test with mesh, +- Test with reflective boundary conditions, +- Test with periodic boundary conditions. + +""" + +from numpy.testing import assert_allclose, assert_equal +import numpy as np +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +RTOL = 1.0e-7 +ATOL = 0.0 + + +@pytest.fixture +def model(): + """Cylindrical core contained in a first box which is contained in a larger box. + A lower universe is used to describe the interior of the first box which + contains the core and its surrounding space.""" + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material() + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 10.97) + + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + air = openmc.Material() + air.add_element("O", 0.2) + air.add_element("N", 0.8) + air.set_density("g/cm3", 0.001225) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(z0=-core_height / 2.0) + core_upper_plane = openmc.ZPlane(z0=core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell(fill=air, region=outside_core_region) + + # Universe + inside_box1_universe = openmc.Universe(cells=[core, outside_core]) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 4.1 + + # Surfaces + box1_rpp = openmc.model.RectangularParallelepiped( + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=-box1_rpp) + + # ----------------------------------------------------------------------------- + # Box 2 + # ----------------------------------------------------------------------------- + + # Parameters + box2_size = 12 + + # Surfaces + box2_rpp = openmc.model.RectangularParallelepiped( + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + boundary_type="vacuum" + ) + + # Cell + box2 = openmc.Cell(fill=water, region=-box2_rpp & +box1_rpp) + + # Register geometry + model.geometry = openmc.Geometry([box1, box2]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 2000 + model.settings.batches = 15 + model.settings.inactive = 5 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + # ============================================================================= + # Tallies + # ============================================================================= + + in_core_filter = openmc.CellFilter([core]) + in_outside_core_filter = openmc.CellFilter([outside_core]) + in_box1_filter = openmc.CellFilter([box1]) + in_box2_filter = openmc.CellFilter([box2]) + + from_core_filter = openmc.CellFromFilter([core]) + from_outside_core_filter = openmc.CellFromFilter([outside_core]) + from_box1_filter = openmc.CellFromFilter([box1]) + from_box2_filter = openmc.CellFromFilter([box2]) + + t1_1 = openmc.Tally(name="total from 1 in 1") + t1_1.filters = [from_core_filter, in_core_filter] + t1_1.scores = ["total"] + + t1_2 = openmc.Tally(name="total from 1 in 2") + t1_2.filters = [from_core_filter, in_outside_core_filter] + t1_2.scores = ["total"] + + t1_3 = openmc.Tally(name="total from 1 in 3") + t1_3.filters = [from_core_filter, in_box1_filter] + t1_3.scores = ["total"] + + t1_4 = openmc.Tally(name="total from 1 in 4") + t1_4.filters = [from_core_filter, in_box2_filter] + t1_4.scores = ["total"] + + t2_1 = openmc.Tally(name="total from 2 in 1") + t2_1.filters = [from_outside_core_filter, in_core_filter] + t2_1.scores = ["total"] + + t2_2 = openmc.Tally(name="total from 2 in 2") + t2_2.filters = [from_outside_core_filter, in_outside_core_filter] + t2_2.scores = ["total"] + + t2_3 = openmc.Tally(name="total from 2 in 3") + t2_3.filters = [from_outside_core_filter, in_box1_filter] + t2_3.scores = ["total"] + + t2_4 = openmc.Tally(name="total from 2 in 4") + t2_4.filters = [from_outside_core_filter, in_box2_filter] + t2_4.scores = ["total"] + + t3_1 = openmc.Tally(name="total from 3 in 1") + t3_1.filters = [from_box1_filter, in_core_filter] + t3_1.scores = ["total"] + + t3_2 = openmc.Tally(name="total from 3 in 2") + t3_2.filters = [from_box1_filter, in_outside_core_filter] + t3_2.scores = ["total"] + + t3_3 = openmc.Tally(name="total from 3 in 3") + t3_3.filters = [from_box1_filter, in_box1_filter] + t3_3.scores = ["total"] + + t3_4 = openmc.Tally(name="total from 3 in 4") + t3_4.filters = [from_box1_filter, in_box2_filter] + t3_4.scores = ["total"] + + t4_1 = openmc.Tally(name="total from 4 in 1") + t4_1.filters = [from_box2_filter, in_core_filter] + t4_1.scores = ["total"] + + t4_2 = openmc.Tally(name="total from 4 in 2") + t4_2.filters = [from_box2_filter, in_outside_core_filter] + t4_2.scores = ["total"] + + t4_3 = openmc.Tally(name="total from 4 in 3") + t4_3.filters = [from_box2_filter, in_box1_filter] + t4_3.scores = ["total"] + + t4_4 = openmc.Tally(name="total from 4 in 4") + t4_4.filters = [from_box2_filter, in_box2_filter] + t4_4.scores = ["total"] + + tglobal = openmc.Tally(name="total") + tglobal.scores = ["total"] + + model.tallies += [ + t1_1, + t1_2, + t1_3, + t1_4, + t2_1, + t2_2, + t2_3, + t2_4, + t3_1, + t3_2, + t3_3, + t3_4, + t4_1, + t4_2, + t4_3, + t4_4, + tglobal, + ] + return model + + +class CellFromFilterTest(PyAPITestHarness): + + def _compare_results(self): + """Additional unit tests on the tally results to check + consistency of CellFromFilter.""" + with openmc.StatePoint(self.statepoint_name) as sp: + + t1_1 = sp.get_tally(name="total from 1 in 1").mean + t1_2 = sp.get_tally(name="total from 1 in 2").mean + t1_3 = sp.get_tally(name="total from 1 in 3").mean + t1_4 = sp.get_tally(name="total from 1 in 4").mean + + t2_1 = sp.get_tally(name="total from 2 in 1").mean + t2_2 = sp.get_tally(name="total from 2 in 2").mean + t2_3 = sp.get_tally(name="total from 2 in 3").mean + t2_4 = sp.get_tally(name="total from 2 in 4").mean + + t3_1 = sp.get_tally(name="total from 3 in 1").mean + t3_2 = sp.get_tally(name="total from 3 in 2").mean + t3_3 = sp.get_tally(name="total from 3 in 3").mean + t3_4 = sp.get_tally(name="total from 3 in 4").mean + + t4_1 = sp.get_tally(name="total from 4 in 1").mean + t4_2 = sp.get_tally(name="total from 4 in 2").mean + t4_3 = sp.get_tally(name="total from 4 in 3").mean + t4_4 = sp.get_tally(name="total from 4 in 4").mean + + tglobal = sp.get_tally(name="total").mean + + # From 1 and 2 is equivalent to from 3 + assert_allclose(t1_1 + t2_1, t3_1, rtol=RTOL, atol=ATOL) + assert_allclose(t1_2 + t2_2, t3_2, rtol=RTOL, atol=ATOL) + assert_allclose(t1_3 + t2_3, t3_3, rtol=RTOL, atol=ATOL) + assert_allclose(t1_4 + t2_4, t3_4, rtol=RTOL, atol=ATOL) + + # In 1 and 2 equivalent to in 3 + assert_allclose(t1_1 + t1_2, t1_3, rtol=RTOL, atol=ATOL) + assert_allclose(t2_1 + t2_2, t2_3, rtol=RTOL, atol=ATOL) + assert_allclose(t3_1 + t3_2, t3_3, rtol=RTOL, atol=ATOL) + assert_allclose(t4_1 + t4_2, t4_3, rtol=RTOL, atol=ATOL) + + # Comparison to global from 3 + assert_allclose(t3_3 + t3_4 + t4_3 + t4_4, tglobal, rtol=RTOL, atol=ATOL) + + # Comparison to global from 1 and 2 + t_from_1_wo_3 = t1_1 + t1_2 + t1_4 + t_from_2_wo_3 = t2_1 + t2_2 + t2_4 + t_from_4_wo_3 = t4_1 + t4_2 + t4_4 + assert_allclose( + t_from_1_wo_3 + t_from_2_wo_3 + t_from_4_wo_3, + tglobal, + rtol=RTOL, + atol=ATOL, + ) + + # 1 cannot contribute to 4 and 4 cannot contribute to 1 by symmetry + assert_equal(t1_4, np.zeros_like(t1_4)) + assert_equal(t4_1, np.zeros_like(t4_1)) + + return super()._compare_results() + + +def test_filter_cellfrom(model): + harness = CellFromFilterTest("statepoint.15.h5", model) + harness.main() diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index a58d58bab95..0667c034148 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/random_ray_vacuum/__init__.py b/tests/regression_tests/filter_musurface/__init__.py similarity index 100% rename from tests/regression_tests/random_ray_vacuum/__init__.py rename to tests/regression_tests/filter_musurface/__init__.py diff --git a/tests/regression_tests/filter_musurface/inputs_true.dat b/tests/regression_tests/filter_musurface/inputs_true.dat new file mode 100644 index 00000000000..031f62159ab --- /dev/null +++ b/tests/regression_tests/filter_musurface/inputs_true.dat @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 5 + 0 + + + + 1 + + + -1.0 -0.5 0.0 0.5 1.0 + + + 1 2 + current + + + diff --git a/tests/regression_tests/filter_musurface/results_true.dat b/tests/regression_tests/filter_musurface/results_true.dat new file mode 100644 index 00000000000..39c9f2b925a --- /dev/null +++ b/tests/regression_tests/filter_musurface/results_true.dat @@ -0,0 +1,11 @@ +k-combined: +1.157005E-01 7.587090E-03 +tally 1: +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.770000E-01 +1.608710E-01 +3.909000E+00 +3.063035E+00 diff --git a/tests/regression_tests/filter_musurface/test.py b/tests/regression_tests/filter_musurface/test.py new file mode 100644 index 00000000000..f2ec96b495d --- /dev/null +++ b/tests/regression_tests/filter_musurface/test.py @@ -0,0 +1,43 @@ +import numpy as np +from math import pi + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def model(): + model = openmc.Model() + fuel = openmc.Material() + fuel.set_density('g/cm3', 10.0) + fuel.add_nuclide('U235', 1.0) + zr = openmc.Material() + zr.set_density('g/cm3', 1.0) + zr.add_nuclide('Zr90', 1.0) + + cyl1 = openmc.ZCylinder(r=1.0) + cyl2 = openmc.ZCylinder(r=3.0, boundary_type='vacuum') + cell1 = openmc.Cell(fill=fuel, region=-cyl1) + cell2 = openmc.Cell(fill=zr, region=+cyl1 & -cyl2) + model.geometry = openmc.Geometry([cell1, cell2]) + + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 + + # Create a tally for current through the first surface binned by mu + surf_filter = openmc.SurfaceFilter([cyl1]) + mu_filter = openmc.MuSurfaceFilter([-1.0, -0.5, 0.0, 0.5, 1.0]) + tally = openmc.Tally() + tally.filters = [surf_filter, mu_filter] + tally.scores = ['current'] + model.tallies.append(tally) + + return model + + +def test_filter_musurface(model): + harness = PyAPITestHarness('statepoint.5.h5', model) + harness.main() diff --git a/tests/regression_tests/filter_translations/inputs_true.dat b/tests/regression_tests/filter_translations/inputs_true.dat index a80ccd87675..5004c3217ab 100644 --- a/tests/regression_tests/filter_translations/inputs_true.dat +++ b/tests/regression_tests/filter_translations/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/mg_temperature/build_2g.py b/tests/regression_tests/mg_temperature/build_2g.py index f236031a727..1fb7234499b 100644 --- a/tests/regression_tests/mg_temperature/build_2g.py +++ b/tests/regression_tests/mg_temperature/build_2g.py @@ -289,8 +289,9 @@ def build_inf_model(xsnames, xslibname, temperature, tempmethod='nearest'): settings_file.output = {'summary': False} # Create an initial uniform spatial source distribution over fissionable zones bounds = [-INF, -INF, -INF, INF, INF, INF] - uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) + uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:]) settings_file.temperature = {'method': tempmethod} - settings_file.source = openmc.IndependentSource(space=uniform_dist) + settings_file.source = openmc.IndependentSource( + space=uniform_dist, constraints={'fissionable': True}) model.settings = settings_file model.export_to_model_xml() diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index bb9c99026d0..1ecb7a2d3b4 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/model_xml/photon_production_inputs_true.dat b/tests/regression_tests/model_xml/photon_production_inputs_true.dat index ee6cb88622b..10f0bad9841 100644 --- a/tests/regression_tests/model_xml/photon_production_inputs_true.dat +++ b/tests/regression_tests/model_xml/photon_production_inputs_true.dat @@ -9,7 +9,7 @@ - + diff --git a/tests/regression_tests/photon_production/inputs_true.dat b/tests/regression_tests/photon_production/inputs_true.dat index ee6cb88622b..10f0bad9841 100644 --- a/tests/regression_tests/photon_production/inputs_true.dat +++ b/tests/regression_tests/photon_production/inputs_true.dat @@ -9,7 +9,7 @@ - + diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/__init__.py b/tests/regression_tests/random_ray_adjoint_fixed_source/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat new file mode 100644 index 00000000000..686987512a0 --- /dev/null +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat b/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat new file mode 100644 index 00000000000..a216fa9fcc3 --- /dev/null +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +-7.235364E+03 +3.367109E+09 +tally 2: +4.818311E+05 +6.269371E+10 +tally 3: +1.515641E+06 +4.598791E+11 diff --git a/tests/regression_tests/random_ray_adjoint_fixed_source/test.py b/tests/regression_tests/random_ray_adjoint_fixed_source/test.py new file mode 100644 index 00000000000..0295e36e9ac --- /dev/null +++ b/tests/regression_tests/random_ray_adjoint_fixed_source/test.py @@ -0,0 +1,20 @@ +import os + +from openmc.examples import random_ray_three_region_cube + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_adjoint_fixed_source(): + model = random_ray_three_region_cube() + model.settings.random_ray['adjoint'] = True + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_adjoint_k_eff/__init__.py b/tests/regression_tests/random_ray_adjoint_k_eff/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_vacuum/inputs_true.dat b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat similarity index 90% rename from tests/regression_tests/random_ray_vacuum/inputs_true.dat rename to tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat index 4ef10942005..725702a4912 100644 --- a/tests/regression_tests/random_ray_vacuum/inputs_true.dat +++ b/tests/regression_tests/random_ray_adjoint_k_eff/inputs_true.dat @@ -66,10 +66,10 @@ - - - - + + + + eigenvalue @@ -85,6 +85,8 @@ -1.26 -1.26 -1 1.26 1.26 1 + True + True diff --git a/tests/regression_tests/random_ray_adjoint_k_eff/results_true.dat b/tests/regression_tests/random_ray_adjoint_k_eff/results_true.dat new file mode 100644 index 00000000000..1690d46e966 --- /dev/null +++ b/tests/regression_tests/random_ray_adjoint_k_eff/results_true.dat @@ -0,0 +1,171 @@ +k-combined: +1.006640E+00 1.812967E-03 +tally 1: +6.684129E+00 +8.939821E+00 +2.685967E+00 +1.443592E+00 +0.000000E+00 +0.000000E+00 +6.358774E+00 +8.091444E+00 +9.687217E-01 +1.878029E-01 +0.000000E+00 +0.000000E+00 +5.963160E+00 +7.117108E+00 +1.932332E-01 +7.473914E-03 +0.000000E+00 +0.000000E+00 +5.137593E+00 +5.283310E+00 +1.714616E-01 +5.884834E-03 +1.086218E-06 +2.361752E-13 +4.857253E+00 +4.719856E+00 +5.689580E-02 +6.476286E-04 +2.989356E-03 +1.787808E-06 +4.830516E+00 +4.666801E+00 +7.203015E-03 +1.037676E-05 +3.620020E+00 +2.620927E+00 +5.161382E+00 +5.328124E+00 +6.786255E-02 +9.210763E-04 +5.531943E+00 +6.120553E+00 +5.414034E+00 +5.864661E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.632338E+00 +6.347626E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.682608E+00 +6.462382E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.310716E+00 +5.645180E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.945409E+00 +4.893171E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.842688E+00 +4.690352E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.117198E+00 +5.237280E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.938711E+00 +9.633345E+00 +2.835258E+00 +1.608212E+00 +0.000000E+00 +0.000000E+00 +6.549505E+00 +8.584036E+00 +1.015138E+00 +2.061993E-01 +0.000000E+00 +0.000000E+00 +6.050651E+00 +7.327711E+00 +1.992816E-01 +7.948424E-03 +0.000000E+00 +0.000000E+00 +5.113981E+00 +5.234801E+00 +1.732323E-01 +6.006619E-03 +1.097435E-06 +2.410627E-13 +4.837033E+00 +4.680541E+00 +5.760042E-02 +6.637112E-04 +3.026377E-03 +1.832205E-06 +4.827049E+00 +4.660105E+00 +7.319913E-03 +1.071647E-05 +3.678770E+00 +2.706730E+00 +5.175337E+00 +5.356957E+00 +6.923046E-02 +9.586177E-04 +5.643451E+00 +6.370016E+00 +6.693323E+00 +8.964322E+00 +2.753307E+00 +1.516683E+00 +0.000000E+00 +0.000000E+00 +6.358384E+00 +8.090233E+00 +9.912008E-01 +1.965868E-01 +0.000000E+00 +0.000000E+00 +5.957484E+00 +7.103246E+00 +1.974033E-01 +7.798286E-03 +0.000000E+00 +0.000000E+00 +5.130744E+00 +5.268844E+00 +1.749233E-01 +6.123348E-03 +1.108148E-06 +2.457474E-13 +4.857340E+00 +4.720019E+00 +5.816659E-02 +6.768049E-04 +3.056125E-03 +1.868351E-06 +4.830629E+00 +4.667018E+00 +7.366289E-03 +1.085264E-05 +3.702077E+00 +2.741125E+00 +5.164864E+00 +5.335279E+00 +6.947917E-02 +9.655086E-04 +5.663725E+00 +6.415806E+00 diff --git a/tests/regression_tests/random_ray_adjoint_k_eff/test.py b/tests/regression_tests/random_ray_adjoint_k_eff/test.py new file mode 100644 index 00000000000..44cf1182ae6 --- /dev/null +++ b/tests/regression_tests/random_ray_adjoint_k_eff/test.py @@ -0,0 +1,20 @@ +import os + +from openmc.examples import random_ray_lattice + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_basic(): + model = random_ray_lattice() + model.settings.random_ray['adjoint'] = True + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_basic/results_true.dat b/tests/regression_tests/random_ray_basic/results_true.dat deleted file mode 100644 index 802e78a828b..00000000000 --- a/tests/regression_tests/random_ray_basic/results_true.dat +++ /dev/null @@ -1,171 +0,0 @@ -k-combined: -8.400322E-01 8.023349E-03 -tally 1: -1.260220E+00 -3.179889E-01 -1.484289E-01 -4.411066E-03 -3.612463E-01 -2.612843E-02 -7.086707E-01 -1.006119E-01 -3.342483E-02 -2.238499E-04 -8.134936E-02 -1.325949E-03 -4.194328E-01 -3.558669E-02 -4.287776E-03 -3.717447E-06 -1.043559E-02 -2.201986E-05 -5.878720E-01 -7.045887E-02 -6.147757E-03 -7.701173E-06 -1.496241E-02 -4.561699E-05 -1.768113E+00 -6.356917E-01 -6.513486E-03 -8.628535E-06 -1.585272E-02 -5.111136E-05 -5.063704E+00 -5.152401E+00 -2.440293E-03 -1.196869E-06 -6.038334E-03 -7.328193E-06 -3.253717E+00 -2.117655E+00 -1.389120E-02 -3.859385E-05 -3.863767E-02 -2.985798E-04 -1.876994E+00 -7.046366E-01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -8.390875E-01 -1.408791E-01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.513839E-01 -4.139640E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -6.682186E-01 -9.116003E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.849034E+00 -6.944337E-01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.523425E+00 -4.112118E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -2.821432E+00 -1.592568E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.159618E+00 -2.694138E-01 -1.354028E-01 -3.672094E-03 -3.295432E-01 -2.175122E-02 -6.880334E-01 -9.491215E-02 -3.234611E-02 -2.097782E-04 -7.872396E-02 -1.242596E-03 -4.184841E-01 -3.536436E-02 -4.274305E-03 -3.687209E-06 -1.040280E-02 -2.184074E-05 -5.810180E-01 -6.872944E-02 -6.060273E-03 -7.476500E-06 -1.474949E-02 -4.428617E-05 -1.782580E+00 -6.457892E-01 -6.552384E-03 -8.730345E-06 -1.594739E-02 -5.171444E-05 -5.278155E+00 -5.596601E+00 -2.546878E-03 -1.303010E-06 -6.302072E-03 -7.978072E-06 -3.420419E+00 -2.340454E+00 -1.465798E-02 -4.299061E-05 -4.077042E-02 -3.325951E-04 -1.279417E+00 -3.278133E-01 -1.509073E-01 -4.561836E-03 -3.672782E-01 -2.702150E-02 -7.212777E-01 -1.042487E-01 -3.411552E-02 -2.332877E-04 -8.303035E-02 -1.381852E-03 -4.269473E-01 -3.685202E-02 -4.378540E-03 -3.872997E-06 -1.065649E-02 -2.294124E-05 -5.973530E-01 -7.266946E-02 -6.260881E-03 -7.976490E-06 -1.523773E-02 -4.724780E-05 -1.795373E+00 -6.547440E-01 -6.635941E-03 -8.945067E-06 -1.615075E-02 -5.298634E-05 -5.161876E+00 -5.353441E+00 -2.505311E-03 -1.261399E-06 -6.199218E-03 -7.723296E-06 -3.344042E+00 -2.236603E+00 -1.443089E-02 -4.166228E-05 -4.013879E-02 -3.223186E-04 diff --git a/tests/regression_tests/random_ray_basic/test.py b/tests/regression_tests/random_ray_basic/test.py deleted file mode 100644 index 1727a63716c..00000000000 --- a/tests/regression_tests/random_ray_basic/test.py +++ /dev/null @@ -1,232 +0,0 @@ -import os - -import numpy as np -import openmc - -from tests.testing_harness import TolerantPyAPITestHarness - - -class MGXSTestHarness(TolerantPyAPITestHarness): - def _cleanup(self): - super()._cleanup() - f = 'mgxs.h5' - if os.path.exists(f): - os.remove(f) - - -def random_ray_model() -> openmc.Model: - ############################################################################### - # Create multigroup data - - # Instantiate the energy group data - group_edges = [1e-5, 0.0635, 10.0, 1.0e2, 1.0e3, 0.5e6, 1.0e6, 20.0e6] - groups = openmc.mgxs.EnergyGroups(group_edges) - - # Instantiate the 7-group (C5G7) cross section data - uo2_xsdata = openmc.XSdata('UO2', groups) - uo2_xsdata.order = 0 - uo2_xsdata.set_total( - [0.1779492, 0.3298048, 0.4803882, 0.5543674, 0.3118013, 0.3951678, - 0.5644058]) - uo2_xsdata.set_absorption([8.0248e-03, 3.7174e-03, 2.6769e-02, 9.6236e-02, - 3.0020e-02, 1.1126e-01, 2.8278e-01]) - scatter_matrix = np.array( - [[[0.1275370, 0.0423780, 0.0000094, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.3244560, 0.0016314, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.4509400, 0.0026792, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.4525650, 0.0055664, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0001253, 0.2714010, 0.0102550, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0012968, 0.2658020, 0.0168090], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0085458, 0.2730800]]]) - scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) - uo2_xsdata.set_scatter_matrix(scatter_matrix) - uo2_xsdata.set_fission([7.21206e-03, 8.19301e-04, 6.45320e-03, - 1.85648e-02, 1.78084e-02, 8.30348e-02, - 2.16004e-01]) - uo2_xsdata.set_nu_fission([2.005998e-02, 2.027303e-03, 1.570599e-02, - 4.518301e-02, 4.334208e-02, 2.020901e-01, - 5.257105e-01]) - uo2_xsdata.set_chi([5.8791e-01, 4.1176e-01, 3.3906e-04, 1.1761e-07, 0.0000e+00, - 0.0000e+00, 0.0000e+00]) - - h2o_xsdata = openmc.XSdata('LWTR', groups) - h2o_xsdata.order = 0 - h2o_xsdata.set_total([0.15920605, 0.412969593, 0.59030986, 0.58435, - 0.718, 1.2544497, 2.650379]) - h2o_xsdata.set_absorption([6.0105e-04, 1.5793e-05, 3.3716e-04, - 1.9406e-03, 5.7416e-03, 1.5001e-02, - 3.7239e-02]) - scatter_matrix = np.array( - [[[0.0444777, 0.1134000, 0.0007235, 0.0000037, 0.0000001, 0.0000000, 0.0000000], - [0.0000000, 0.2823340, 0.1299400, 0.0006234, 0.0000480, 0.0000074, 0.0000010], - [0.0000000, 0.0000000, 0.3452560, 0.2245700, 0.0169990, 0.0026443, 0.0005034], - [0.0000000, 0.0000000, 0.0000000, 0.0910284, 0.4155100, 0.0637320, 0.0121390], - [0.0000000, 0.0000000, 0.0000000, 0.0000714, 0.1391380, 0.5118200, 0.0612290], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0022157, 0.6999130, 0.5373200], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.1324400, 2.4807000]]]) - scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) - h2o_xsdata.set_scatter_matrix(scatter_matrix) - - mg_cross_sections = openmc.MGXSLibrary(groups) - mg_cross_sections.add_xsdatas([uo2_xsdata, h2o_xsdata]) - mg_cross_sections.export_to_hdf5() - - ############################################################################### - # Create materials for the problem - - # Instantiate some Materials and register the appropriate macroscopic data - uo2 = openmc.Material(name='UO2 fuel') - uo2.set_density('macro', 1.0) - uo2.add_macroscopic('UO2') - - water = openmc.Material(name='Water') - water.set_density('macro', 1.0) - water.add_macroscopic('LWTR') - - # Instantiate a Materials collection and export to XML - materials = openmc.Materials([uo2, water]) - materials.cross_sections = "mgxs.h5" - - ############################################################################### - # Define problem geometry - - ######################################## - # Define an unbounded pincell universe - - pitch = 1.26 - - # Create a surface for the fuel outer radius - fuel_or = openmc.ZCylinder(r=0.54, name='Fuel OR') - inner_ring_a = openmc.ZCylinder(r=0.33, name='inner ring a') - inner_ring_b = openmc.ZCylinder(r=0.45, name='inner ring b') - outer_ring_a = openmc.ZCylinder(r=0.60, name='outer ring a') - outer_ring_b = openmc.ZCylinder(r=0.69, name='outer ring b') - - # Instantiate Cells - fuel_a = openmc.Cell(fill=uo2, region=-inner_ring_a, name='fuel inner a') - fuel_b = openmc.Cell(fill=uo2, region=+inner_ring_a & -inner_ring_b, name='fuel inner b') - fuel_c = openmc.Cell(fill=uo2, region=+inner_ring_b & -fuel_or, name='fuel inner c') - moderator_a = openmc.Cell(fill=water, region=+fuel_or & -outer_ring_a, name='moderator inner a') - moderator_b = openmc.Cell(fill=water, region=+outer_ring_a & -outer_ring_b, name='moderator outer b') - moderator_c = openmc.Cell(fill=water, region=+outer_ring_b, name='moderator outer c') - - # Create pincell universe - pincell_base = openmc.Universe() - - # Register Cells with Universe - pincell_base.add_cells([fuel_a, fuel_b, fuel_c, moderator_a, moderator_b, moderator_c]) - - # Create planes for azimuthal sectors - azimuthal_planes = [] - for i in range(8): - angle = 2 * i * openmc.pi / 8 - normal_vector = (-openmc.sin(angle), openmc.cos(angle), 0) - azimuthal_planes.append(openmc.Plane(a=normal_vector[0], b=normal_vector[1], c=normal_vector[2], d=0)) - - # Create a cell for each azimuthal sector - azimuthal_cells = [] - for i in range(8): - azimuthal_cell = openmc.Cell(name=f'azimuthal_cell_{i}') - azimuthal_cell.fill = pincell_base - azimuthal_cell.region = +azimuthal_planes[i] & -azimuthal_planes[(i+1) % 8] - azimuthal_cells.append(azimuthal_cell) - - # Create a geometry with the azimuthal universes - pincell = openmc.Universe(cells=azimuthal_cells) - - ######################################## - # Define a moderator lattice universe - - moderator_infinite = openmc.Cell(fill=water, name='moderator infinite') - mu = openmc.Universe() - mu.add_cells([moderator_infinite]) - - lattice = openmc.RectLattice() - lattice.lower_left = [-pitch/2.0, -pitch/2.0] - lattice.pitch = [pitch/10.0, pitch/10.0] - lattice.universes = np.full((10, 10), mu) - - mod_lattice_cell = openmc.Cell(fill=lattice) - - mod_lattice_uni = openmc.Universe() - - mod_lattice_uni.add_cells([mod_lattice_cell]) - - ######################################## - # Define 2x2 outer lattice - lattice2x2 = openmc.RectLattice() - lattice2x2.lower_left = (-pitch, -pitch) - lattice2x2.pitch = (pitch, pitch) - lattice2x2.universes = [ - [pincell, pincell], - [pincell, mod_lattice_uni] - ] - - ######################################## - # Define cell containing lattice and other stuff - box = openmc.model.RectangularPrism(pitch*2, pitch*2, boundary_type='reflective') - - assembly = openmc.Cell(fill=lattice2x2, region=-box, name='assembly') - - # Create a geometry with the top-level cell - geometry = openmc.Geometry([assembly]) - - ############################################################################### - # Define problem settings - - # Instantiate a Settings object, set all runtime parameters, and export to XML - settings = openmc.Settings() - settings.energy_mode = "multi-group" - settings.batches = 10 - settings.inactive = 5 - settings.particles = 100 - - # Create an initial uniform spatial source distribution over fissionable zones - lower_left = (-pitch, -pitch, -1) - upper_right = (pitch, pitch, 1) - uniform_dist = openmc.stats.Box(lower_left, upper_right) - rr_source = openmc.IndependentSource(space=uniform_dist) - - settings.random_ray['distance_active'] = 100.0 - settings.random_ray['distance_inactive'] = 20.0 - settings.random_ray['ray_source'] = rr_source - - ############################################################################### - # Define tallies - - # Create a mesh that will be used for tallying - mesh = openmc.RegularMesh() - mesh.dimension = (2, 2) - mesh.lower_left = (-pitch, -pitch) - mesh.upper_right = (pitch, pitch) - - # Create a mesh filter that can be used in a tally - mesh_filter = openmc.MeshFilter(mesh) - - # Create an energy group filter as well - energy_filter = openmc.EnergyFilter(group_edges) - - # Now use the mesh filter in a tally and indicate what scores are desired - tally = openmc.Tally(name="Mesh tally") - tally.filters = [mesh_filter, energy_filter] - tally.scores = ['flux', 'fission', 'nu-fission'] - tally.estimator = 'analog' - - # Instantiate a Tallies collection and export to XML - tallies = openmc.Tallies([tally]) - - ############################################################################### - # Exporting to OpenMC model - ############################################################################### - - model = openmc.Model() - model.geometry = geometry - model.materials = materials - model.settings = settings - model.tallies = tallies - return model - - -def test_random_ray_basic(): - harness = MGXSTestHarness('statepoint.10.h5', random_ray_model()) - harness.main() diff --git a/tests/regression_tests/random_ray_entropy/__init__.py b/tests/regression_tests/random_ray_entropy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_entropy/geometry.xml b/tests/regression_tests/random_ray_entropy/geometry.xml new file mode 100644 index 00000000000..4c87bbbfb9a --- /dev/null +++ b/tests/regression_tests/random_ray_entropy/geometry.xml @@ -0,0 +1,88 @@ + + + + + + 12.5 12.5 12.5 + 8 8 8 + 0.0 0.0 0.0 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 + + + + + + + + diff --git a/tests/regression_tests/random_ray_entropy/materials.xml b/tests/regression_tests/random_ray_entropy/materials.xml new file mode 100644 index 00000000000..5a6f93414bb --- /dev/null +++ b/tests/regression_tests/random_ray_entropy/materials.xml @@ -0,0 +1,8 @@ + + + mgxs.h5 + + + + + diff --git a/tests/regression_tests/random_ray_entropy/mgxs.h5 b/tests/regression_tests/random_ray_entropy/mgxs.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6ce80bf713a8d5ff29700ba59e982e18f99b5403 GIT binary patch literal 10664 zcmeHN%}*0S6o1<)ELx=~2P1yeL;JB5M`%OA4UJMuu{yU;m}}jFYub= zpCp>Wc%w?`qzL4JLLk-KJs<`=jH9M_TERFYFm72kyJxt}HjSLnQCudeLWQygKO6SV(mu^{#vDtpJMgnd zH{3=&5sl;2%Eu!cjybM9GwIIQ3|eA|^>K9`h0dQi?=_$Ct!2L!-+%vhQ~3XQ!s+(o zk0k{bjN?Gc@yAL;^%%{l`++9`bj_V4+@MK6%Q@D1c^N!HbJV{c83gv_Q{XH7@ zLUFe!NP%DEcLnpS4N4!}|Be9huotfg$Yp`!8G*|jVO6cwN1#3`^AJf<+$ z;db}k2Yj;uLxT^6kOlNSNdC@?g5Pacu$ja1uHg#02Ep%5%2c{A9m}hQ&q)QJ zb5gOo1FX=yIu|vppSS1FZtb80ix}W%XQ$2Y2?A0_%E1fc90x*l}8~J3RX;`i{jvLfPglKxS;(OKWW1?go4)haasERZ?)r} zg}UG$wGRwK45412!0~}z?V16K?LHMyO*|C8v~|7xQd)UjRl3eE`ovRdPeNFIJeKMT Fk6-_XIk5l$ literal 0 HcmV?d00001 diff --git a/tests/regression_tests/random_ray_entropy/results_true.dat b/tests/regression_tests/random_ray_entropy/results_true.dat new file mode 100644 index 00000000000..25425453871 --- /dev/null +++ b/tests/regression_tests/random_ray_entropy/results_true.dat @@ -0,0 +1,13 @@ +k-combined: +1.000000E+00 0.000000E+00 +entropy: +8.863421E+00 +8.933584E+00 +8.960553E+00 +8.967921E+00 +8.976016E+00 +8.981856E+00 +8.983670E+00 +8.986584E+00 +8.987732E+00 +8.988186E+00 diff --git a/tests/regression_tests/random_ray_entropy/settings.xml b/tests/regression_tests/random_ray_entropy/settings.xml new file mode 100644 index 00000000000..81deaa7751d --- /dev/null +++ b/tests/regression_tests/random_ray_entropy/settings.xml @@ -0,0 +1,17 @@ + + + eigenvalue + 100 + 10 + 5 + multi-group + + + + 0.0 0.0 0.0 100.0 100.0 100.0 + + + 40.0 + 400.0 + + diff --git a/tests/regression_tests/random_ray_entropy/test.py b/tests/regression_tests/random_ray_entropy/test.py new file mode 100644 index 00000000000..a3cba65ad0b --- /dev/null +++ b/tests/regression_tests/random_ray_entropy/test.py @@ -0,0 +1,33 @@ +import glob +import os + +from openmc import StatePoint + +from tests.testing_harness import TestHarness + + +class EntropyTestHarness(TestHarness): + def _get_results(self): + """Digest info in the statepoint and return as a string.""" + # Read the statepoint file. + statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] + with StatePoint(statepoint) as sp: + # Write out k-combined. + outstr = 'k-combined:\n' + outstr += '{:12.6E} {:12.6E}\n'.format(sp.keff.n, sp.keff.s) + + # Write out entropy data. + outstr += 'entropy:\n' + results = ['{:12.6E}'.format(x) for x in sp.entropy] + outstr += '\n'.join(results) + '\n' + + return outstr + +''' +# This test is adapted from "Monte Carlo power iteration: Entropy and spatial correlations," +M. Nowak et al. The cross sections are defined explicitly so that the value for entropy +is exactly 9 and the eigenvalue is exactly 1. +''' +def test_entropy(): + harness = EntropyTestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/regression_tests/random_ray_fixed_source_domain/__init__.py b/tests/regression_tests/random_ray_fixed_source_domain/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat new file mode 100644 index 00000000000..d6ca8918cd0 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/cell/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + cell + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_domain/cell/results_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/cell/results_true.dat new file mode 100644 index 00000000000..6da51a711bf --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/cell/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.934460E-01 +7.058894E-02 +tally 2: +3.206214E-02 +2.063370E-04 +tally 3: +2.096411E-03 +8.804924E-07 diff --git a/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat new file mode 100644 index 00000000000..7fae491aea4 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/material/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + material + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_domain/material/results_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/material/results_true.dat new file mode 100644 index 00000000000..6da51a711bf --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/material/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.934460E-01 +7.058894E-02 +tally 2: +3.206214E-02 +2.063370E-04 +tally 3: +2.096411E-03 +8.804924E-07 diff --git a/tests/regression_tests/random_ray_fixed_source_domain/test.py b/tests/regression_tests/random_ray_fixed_source_domain/test.py new file mode 100644 index 00000000000..5885a92009a --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/test.py @@ -0,0 +1,51 @@ +import os + +import openmc +from openmc.utility_funcs import change_directory +from openmc.examples import random_ray_three_region_cube +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("domain_type", ["cell", "material", "universe"]) +def test_random_ray_fixed_source(domain_type): + with change_directory(domain_type): + openmc.reset_auto_ids() + model = random_ray_three_region_cube() + + # Based on the parameter, we need to adjust + # the particle source constraints + source = model.settings.source[0] + constraints = source.constraints + + if domain_type == 'cell': + cells = model.geometry.get_all_cells() + for key, cell in cells.items(): + print(cell.name) + if cell.name == 'infinite source region': + constraints['domain_type'] = 'cell' + constraints['domain_ids'] = [cell.id] + elif domain_type == 'material': + materials = model.materials + for material in materials: + if material.name == 'source': + constraints['domain_type'] = 'material' + constraints['domain_ids'] = [material.id] + elif domain_type == 'universe': + universes = model.geometry.get_all_universes() + for key, universe in universes.items(): + if universe.name == 'source universe': + constraints['domain_type'] = 'universe' + constraints['domain_ids'] = [universe.id] + + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat new file mode 100644 index 00000000000..75d84efe978 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/universe/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_domain/universe/results_true.dat b/tests/regression_tests/random_ray_fixed_source_domain/universe/results_true.dat new file mode 100644 index 00000000000..6da51a711bf --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_domain/universe/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.934460E-01 +7.058894E-02 +tally 2: +3.206214E-02 +2.063370E-04 +tally 3: +2.096411E-03 +8.804924E-07 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/__init__.py b/tests/regression_tests/random_ray_fixed_source_linear/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat new file mode 100644 index 00000000000..6ef3f08713b --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 40 + 20 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + linear + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat new file mode 100644 index 00000000000..46200b19c8d --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.339084E+00 +2.747299E-01 +tally 2: +1.090051E-01 +6.073265E-04 +tally 3: +7.300117E-03 +2.715425E-06 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat new file mode 100644 index 00000000000..805c53fe674 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 40 + 20 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + linear_xy + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/results_true.dat b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/results_true.dat new file mode 100644 index 00000000000..ea711a92567 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_linear/linear_xy/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.335701E+00 +2.742860E-01 +tally 2: +1.081600E-01 +5.980902E-04 +tally 3: +7.295015E-03 +2.711589E-06 diff --git a/tests/regression_tests/random_ray_fixed_source_linear/test.py b/tests/regression_tests/random_ray_fixed_source_linear/test.py new file mode 100644 index 00000000000..99211024e6e --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_linear/test.py @@ -0,0 +1,29 @@ +import os + +import numpy as np +import openmc +from openmc.utility_funcs import change_directory +from openmc.examples import random_ray_three_region_cube +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("shape", ["linear", "linear_xy"]) +def test_random_ray_fixed_source_linear(shape): + with change_directory(shape): + openmc.reset_auto_ids() + model = random_ray_three_region_cube() + model.settings.random_ray['source_shape'] = shape + model.settings.inactive = 20 + model.settings.batches = 40 + harness = MGXSTestHarness('statepoint.40.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat new file mode 100644 index 00000000000..559cff0f0df --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_normalization/False/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + False + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/False/results_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/False/results_true.dat new file mode 100644 index 00000000000..d8f78493ce3 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_normalization/False/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +6.840321E+01 +9.376316E+02 +tally 2: +4.976182E+02 +4.970407E+04 +tally 3: +2.382441E+01 +1.137148E+02 diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat new file mode 100644 index 00000000000..75d84efe978 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_normalization/True/inputs_true.dat @@ -0,0 +1,244 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/True/results_true.dat b/tests/regression_tests/random_ray_fixed_source_normalization/True/results_true.dat new file mode 100644 index 00000000000..6da51a711bf --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_normalization/True/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.934460E-01 +7.058894E-02 +tally 2: +3.206214E-02 +2.063370E-04 +tally 3: +2.096411E-03 +8.804924E-07 diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/__init__.py b/tests/regression_tests/random_ray_fixed_source_normalization/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_fixed_source_normalization/test.py b/tests/regression_tests/random_ray_fixed_source_normalization/test.py new file mode 100644 index 00000000000..3fa4ba2a63f --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_normalization/test.py @@ -0,0 +1,27 @@ +import os + +import openmc +from openmc.utility_funcs import change_directory +from openmc.examples import random_ray_three_region_cube +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("normalize", [True, False]) +def test_random_ray_fixed_source(normalize): + with change_directory(str(normalize)): + openmc.reset_auto_ids() + model = random_ray_three_region_cube() + model.settings.random_ray['volume_normalized_flux_tallies'] = normalize + + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/__init__.py b/tests/regression_tests/random_ray_fixed_source_subcritical/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat new file mode 100644 index 00000000000..0bf4cfdc90d --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/inputs_true.dat @@ -0,0 +1,140 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 9 + + + 1.26 1.26 + 2 2 + -1.26 -1.26 + +2 12 +12 13 + + + + + + + + + + + + + + + + + + + + + fixed source + 30 + 125 + 100 + + + 1.134 -1.26 -1.0 1.26 -1.134 1.0 + + + 2e-05 0.0735 20.0 200.0 2000.0 750000.0 2000000.0 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 + + + universe + 9 + + + multi-group + + 40.0 + 40.0 + + + -1.26 -1.26 -1 1.26 1.26 1 + + + False + flat + + + + + 2 2 + -1.26 -1.26 + 1.26 1.26 + + + 1 + + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 2 + flux fission nu-fission + analog + + + diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat new file mode 100644 index 00000000000..831eac5019d --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/flat/results_true.dat @@ -0,0 +1,169 @@ +tally 1: +1.591301E+02 +1.016825E+03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.916980E+01 +1.404518E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.036992E+01 +1.662864E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.464728E+01 +2.434669E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.260923E+01 +1.109616E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.443775E+01 +3.580658E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.708323E+01 +1.318058E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.004495E+02 +1.610586E+03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.321594E+01 +2.147152E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.573858E+01 +2.655227E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.944079E+01 +3.474938E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.758548E+01 +1.328297E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.536577E+01 +3.644652E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.824884E+01 +1.368347E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.073356E+02 +4.630895E+02 +1.263975E+01 +6.427678E+00 +3.076263E+01 +3.807359E+01 +4.995302E+01 +1.001379E+02 +2.355060E+00 +2.226283E-01 +5.731746E+00 +1.318712E+00 +1.959843E+01 +1.538887E+01 +2.005479E-01 +1.611455E-03 +4.880935E-01 +9.545263E-03 +2.315398E+01 +2.148264E+01 +2.441408E-01 +2.388612E-03 +5.941898E-01 +1.414866E-02 +5.319555E+01 +1.134034E+02 +1.967517E-01 +1.551313E-03 +4.788601E-01 +9.189243E-03 +1.192041E+02 +5.698491E+02 +5.811654E-02 +1.356369E-04 +1.438053E-01 +8.304781E-04 +8.044764E+01 +2.602455E+02 +3.474828E-01 +4.908567E-03 +9.665057E-01 +3.797493E-02 +1.561720E+02 +9.788256E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.840869E+01 +1.368656E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.029506E+01 +1.650985E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.467632E+01 +2.440891E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.301033E+01 +1.126277E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.608414E+01 +3.702737E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.881578E+01 +1.396463E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat new file mode 100644 index 00000000000..f7572a07281 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/inputs_true.dat @@ -0,0 +1,140 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 8 +8 8 8 8 8 8 8 8 8 9 + + + 1.26 1.26 + 2 2 + -1.26 -1.26 + +2 12 +12 13 + + + + + + + + + + + + + + + + + + + + + fixed source + 30 + 125 + 100 + + + 1.134 -1.26 -1.0 1.26 -1.134 1.0 + + + 2e-05 0.0735 20.0 200.0 2000.0 750000.0 2000000.0 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 0.14285714285714285 + + + universe + 9 + + + multi-group + + 40.0 + 40.0 + + + -1.26 -1.26 -1 1.26 1.26 1 + + + False + linear_xy + + + + + 2 2 + -1.26 -1.26 + 1.26 1.26 + + + 1 + + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 2 + flux fission nu-fission + analog + + + diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat new file mode 100644 index 00000000000..43a6d825816 --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/linear_xy/results_true.dat @@ -0,0 +1,169 @@ +tally 1: +1.583461E+02 +1.007023E+03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.891511E+01 +1.392800E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.028333E+01 +1.649217E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.453681E+01 +2.413486E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.235799E+01 +1.099237E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.394382E+01 +3.543494E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.676269E+01 +1.303097E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.007853E+02 +1.616208E+03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.319692E+01 +2.146291E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.566725E+01 +2.640687E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.934697E+01 +3.453012E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.737202E+01 +1.318574E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.495251E+01 +3.613355E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.799236E+01 +1.356172E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.066411E+02 +4.570264E+02 +1.254011E+01 +6.325603E+00 +3.052011E+01 +3.746896E+01 +4.977289E+01 +9.940494E+01 +2.345784E+00 +2.208432E-01 +5.709169E+00 +1.308139E+00 +1.953078E+01 +1.528285E+01 +1.998229E-01 +1.599785E-03 +4.863290E-01 +9.476135E-03 +2.307190E+01 +2.133049E+01 +2.432475E-01 +2.371063E-03 +5.920158E-01 +1.404471E-02 +5.299849E+01 +1.125570E+02 +1.959909E-01 +1.539183E-03 +4.770085E-01 +9.117394E-03 +1.186130E+02 +5.641029E+02 +5.781214E-02 +1.341752E-04 +1.430521E-01 +8.215282E-04 +7.995712E+01 +2.570085E+02 +3.452925E-01 +4.844032E-03 +9.604136E-01 +3.747567E-02 +1.556790E+02 +9.726171E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.816710E+01 +1.357523E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.018475E+01 +1.633543E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.453470E+01 +2.413604E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.269366E+01 +1.113096E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.550190E+01 +3.658245E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.846305E+01 +1.379618E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/random_ray_fixed_source_subcritical/test.py b/tests/regression_tests/random_ray_fixed_source_subcritical/test.py new file mode 100644 index 00000000000..6c2c569c65e --- /dev/null +++ b/tests/regression_tests/random_ray_fixed_source_subcritical/test.py @@ -0,0 +1,133 @@ +import os + +import openmc +from openmc.examples import random_ray_lattice +from openmc.utility_funcs import change_directory +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("shape", ["flat", "linear_xy"]) +def test_random_ray_source(shape): + with change_directory(shape): + openmc.reset_auto_ids() + + # The general strategy is to reuse the random_ray_lattice model, + # but redfine some of the geometry to make it a good + # subcritical multiplication problem. We then also add in + # a fixed source term. + + model = random_ray_lattice() + + # Begin by updating the random ray settings for fixed source + settings = model.settings + settings.random_ray['source_shape'] = shape + settings.run_mode = 'fixed source' + settings.particles = 30 + settings.random_ray['distance_active'] = 40.0 + settings.random_ray['distance_inactive'] = 40.0 + settings.random_ray['volume_normalized_flux_tallies'] = False + + # This problem needs about 2k iterations to converge, + # but for regression testing we only need a few hundred + # to ensure things are working as expected. With + # only 100 inactive batches, tallies will still be off + # by 3x or more. For validation against MGMC, be sure + # to increase the batch counts. + settings.batches = 125 + settings.inactive = 100 + + ######################################## + # Define the alternative geometry + + pitch = 1.26 + + for material in model.materials: + if material.name == 'Water': + water = material + + # The new geometry replaces two of the fuel pins with + # moderator, reducing k-eff to around 0.84. We also + # add a special universe in the corner of one of the moderator + # regions to use as a domain constraint for the source + moderator_infinite = openmc.Cell(fill=water, name='moderator infinite') + mu = openmc.Universe(cells=[moderator_infinite]) + + moderator_infinite2 = openmc.Cell(fill=water, name='moderator infinite 2') + mu2 = openmc.Universe(cells=[moderator_infinite2]) + + n_sub = 10 + + lattice = openmc.RectLattice() + lattice.lower_left = [-pitch/2.0, -pitch/2.0] + lattice.pitch = [pitch/n_sub, pitch/n_sub] + lattice.universes = [[mu] * n_sub for _ in range(n_sub)] + + lattice2 = openmc.RectLattice() + lattice2.lower_left = [-pitch/2.0, -pitch/2.0] + lattice2.pitch = [pitch/n_sub, pitch/n_sub] + lattice2.universes = [[mu] * n_sub for _ in range(n_sub)] + lattice2.universes[n_sub-1][n_sub-1] = mu2 + + mod_lattice_cell = openmc.Cell(fill=lattice) + mod_lattice_uni = openmc.Universe(cells=[mod_lattice_cell]) + + mod_lattice_cell2 = openmc.Cell(fill=lattice2) + mod_lattice_uni2 = openmc.Universe(cells=[mod_lattice_cell2]) + + lattice2x2 = openmc.RectLattice() + lattice2x2.lower_left = [-pitch, -pitch] + lattice2x2.pitch = [pitch, pitch] + + universes = model.geometry.get_all_universes() + for universe in universes.values(): + if universe.name == 'pincell': + pincell = universe + + lattice2x2.universes = [ + [pincell, mod_lattice_uni], + [mod_lattice_uni, mod_lattice_uni2] + ] + + box = openmc.model.RectangularPrism( + pitch*2, pitch*2, boundary_type='reflective') + + assembly = openmc.Cell(fill=lattice2x2, region=-box, name='assembly') + + root = openmc.Universe(name='root universe', cells=[assembly]) + model.geometry = openmc.Geometry(root) + + ######################################## + # Define the fixed source term + + s = 1.0 / 7.0 + strengths = [s, s, s, s, s, s, s] + midpoints = [2.0e-5, 0.0735, 20.0, 2.0e2, 2.0e3, 0.75e6, 2.0e6] + energy_distribution = openmc.stats.Discrete(x=midpoints, p=strengths) + + lower_left_src = [pitch - pitch/10.0, -pitch, -1.0] + upper_right_src = [pitch, -pitch + pitch/10.0, 1.0] + spatial_distribution = openmc.stats.Box( + lower_left_src, upper_right_src, only_fissionable=False) + + settings.source = openmc.IndependentSource( + space=spatial_distribution, + energy=energy_distribution, + constraints={'domains': [mu2]}, + strength=1.0 + ) + + ######################################## + # Run test + + harness = MGXSTestHarness('statepoint.125.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_k_eff/__init__.py b/tests/regression_tests/random_ray_k_eff/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_basic/inputs_true.dat b/tests/regression_tests/random_ray_k_eff/inputs_true.dat similarity index 98% rename from tests/regression_tests/random_ray_basic/inputs_true.dat rename to tests/regression_tests/random_ray_k_eff/inputs_true.dat index 97b7906f7b2..a2b058f2bdb 100644 --- a/tests/regression_tests/random_ray_basic/inputs_true.dat +++ b/tests/regression_tests/random_ray_k_eff/inputs_true.dat @@ -85,6 +85,7 @@ -1.26 -1.26 -1 1.26 1.26 1 + True diff --git a/tests/regression_tests/random_ray_k_eff/results_true.dat b/tests/regression_tests/random_ray_k_eff/results_true.dat new file mode 100644 index 00000000000..a21929d53ba --- /dev/null +++ b/tests/regression_tests/random_ray_k_eff/results_true.dat @@ -0,0 +1,171 @@ +k-combined: +8.400321E-01 8.023358E-03 +tally 1: +5.086559E+00 +5.180935E+00 +1.885166E+00 +7.115503E-01 +4.588116E+00 +4.214784E+00 +2.860400E+00 +1.639328E+00 +4.245221E-01 +3.610929E-02 +1.033202E+00 +2.138892E-01 +1.692631E+00 +5.793966E-01 +5.445818E-02 +5.996625E-04 +1.325403E-01 +3.552030E-03 +2.372248E+00 +1.146944E+00 +7.808142E-02 +1.242278E-03 +1.900346E-01 +7.358490E-03 +7.134948E+00 +1.034824E+01 +8.272647E-02 +1.391871E-03 +2.013421E-01 +8.244788E-03 +2.043539E+01 +8.389902E+01 +3.099367E-02 +1.930673E-04 +7.669167E-02 +1.182113E-03 +1.313212E+01 +3.449537E+01 +1.764293E-01 +6.225586E-03 +4.907292E-01 +4.816400E-02 +7.567715E+00 +1.145439E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.383194E+00 +2.290468E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.819672E+00 +6.726158E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.693683E+00 +1.480961E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.453758E+00 +1.128171E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.823561E+01 +6.681652E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.137517E+01 +2.588512E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.601916E+00 +4.242624E+00 +1.719723E+00 +5.923465E-01 +4.185462E+00 +3.508695E+00 +2.730305E+00 +1.494324E+00 +4.108214E-01 +3.383938E-02 +9.998572E-01 +2.004436E-01 +1.660852E+00 +5.570432E-01 +5.428709E-02 +5.947848E-04 +1.321239E-01 +3.523137E-03 +2.306069E+00 +1.082855E+00 +7.697031E-02 +1.206036E-03 +1.873303E-01 +7.143815E-03 +7.075194E+00 +1.017519E+01 +8.322052E-02 +1.408294E-03 +2.025446E-01 +8.342070E-03 +2.094832E+01 +8.816715E+01 +3.234739E-02 +2.101889E-04 +8.004135E-02 +1.286945E-03 +1.357413E+01 +3.685983E+01 +1.861680E-01 +6.934827E-03 +5.178169E-01 +5.365102E-02 +5.072149E+00 +5.151429E+00 +1.916643E+00 +7.358710E-01 +4.664726E+00 +4.358845E+00 +2.859464E+00 +1.638250E+00 +4.332944E-01 +3.763170E-02 +1.054552E+00 +2.229070E-01 +1.693008E+00 +5.796671E-01 +5.561096E-02 +6.247543E-04 +1.353459E-01 +3.700658E-03 +2.368860E+00 +1.143296E+00 +7.951819E-02 +1.286690E-03 +1.935314E-01 +7.621556E-03 +7.119586E+00 +1.030023E+01 +8.428175E-02 +1.442931E-03 +2.051274E-01 +8.547243E-03 +2.046758E+01 +8.418767E+01 +3.181946E-02 +2.034766E-04 +7.873502E-02 +1.245847E-03 +1.325834E+01 +3.515919E+01 +1.832838E-01 +6.720555E-03 +5.097947E-01 +5.199331E-02 diff --git a/tests/regression_tests/random_ray_k_eff/test.py b/tests/regression_tests/random_ray_k_eff/test.py new file mode 100644 index 00000000000..8dd0dd9155c --- /dev/null +++ b/tests/regression_tests/random_ray_k_eff/test.py @@ -0,0 +1,19 @@ +import os + +from openmc.examples import random_ray_lattice + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_basic(): + model = random_ray_lattice() + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_linear/__init__.py b/tests/regression_tests/random_ray_linear/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_linear/linear/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat new file mode 100644 index 00000000000..4df51bad5ba --- /dev/null +++ b/tests/regression_tests/random_ray_linear/linear/inputs_true.dat @@ -0,0 +1,110 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 + + + 1.26 1.26 + 2 2 + -1.26 -1.26 + +2 2 +2 5 + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 40 + 20 + multi-group + + 100.0 + 20.0 + + + -1.26 -1.26 -1 1.26 1.26 1 + + + True + linear + + + + + 2 2 + -1.26 -1.26 + 1.26 1.26 + + + 1 + + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 2 + flux fission nu-fission + analog + + + diff --git a/tests/regression_tests/random_ray_linear/linear/results_true.dat b/tests/regression_tests/random_ray_linear/linear/results_true.dat new file mode 100644 index 00000000000..6617c782164 --- /dev/null +++ b/tests/regression_tests/random_ray_linear/linear/results_true.dat @@ -0,0 +1,171 @@ +k-combined: +1.095967E+00 1.543581E-02 +tally 1: +2.548108E+01 +3.269093E+01 +9.271804E+00 +4.327275E+00 +2.256572E+01 +2.563210E+01 +1.816107E+01 +1.653421E+01 +2.659203E+00 +3.544951E-01 +6.471969E+00 +2.099810E+00 +1.364193E+01 +9.308675E+00 +4.362828E-01 +9.521133E-03 +1.061825E+00 +5.639730E-02 +1.746102E+01 +1.524680E+01 +5.733016E-01 +1.643671E-02 +1.395301E+00 +9.736091E-02 +4.539598E+01 +1.030472E+02 +5.263055E-01 +1.385088E-02 +1.280938E+00 +8.204609E-02 +9.945736E+01 +4.946716E+02 +1.505228E-01 +1.133424E-03 +3.724582E-01 +6.939732E-03 +5.324914E+01 +1.418809E+02 +7.219589E-01 +2.614875E-02 +2.008092E+00 +2.022988E-01 +4.188246E+01 +8.843468E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.224363E+01 +2.481131E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.436097E+01 +1.031496E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.901248E+01 +1.807657E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.559337E+01 +1.039424E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.802992E+01 +3.874925E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.506602E+01 +1.016497E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.207833E+01 +2.455068E+01 +8.139772E+00 +3.337974E+00 +1.981058E+01 +1.977210E+01 +1.710407E+01 +1.466648E+01 +2.542287E+00 +3.240467E-01 +6.187418E+00 +1.919453E+00 +1.342690E+01 +9.017908E+00 +4.362263E-01 +9.518695E-03 +1.061688E+00 +5.638286E-02 +1.713924E+01 +1.469065E+01 +5.714028E-01 +1.632839E-02 +1.390680E+00 +9.671931E-02 +4.539193E+01 +1.030326E+02 +5.345253E-01 +1.428719E-02 +1.300944E+00 +8.463057E-02 +1.013270E+02 +5.134168E+02 +1.555517E-01 +1.210145E-03 +3.849017E-01 +7.409480E-03 +5.377836E+01 +1.446537E+02 +7.374053E-01 +2.722908E-02 +2.051056E+00 +2.106567E-01 +2.522726E+01 +3.202007E+01 +9.366659E+00 +4.412278E+00 +2.279657E+01 +2.613561E+01 +1.803368E+01 +1.629756E+01 +2.696490E+00 +3.643066E-01 +6.562716E+00 +2.157928E+00 +1.357447E+01 +9.216150E+00 +4.437305E-01 +9.847287E-03 +1.079951E+00 +5.832923E-02 +1.735848E+01 +1.506775E+01 +5.825173E-01 +1.696803E-02 +1.417731E+00 +1.005081E-01 +4.519297E+01 +1.021280E+02 +5.357712E-01 +1.435322E-02 +1.303976E+00 +8.502167E-02 +9.934508E+01 +4.935314E+02 +1.538761E-01 +1.184229E-03 +3.807556E-01 +7.250804E-03 +5.335839E+01 +1.424221E+02 +7.404823E-01 +2.747396E-02 +2.059614E+00 +2.125512E-01 diff --git a/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat new file mode 100644 index 00000000000..113771438c4 --- /dev/null +++ b/tests/regression_tests/random_ray_linear/linear_xy/inputs_true.dat @@ -0,0 +1,110 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 + + + 1.26 1.26 + 2 2 + -1.26 -1.26 + +2 2 +2 5 + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 40 + 20 + multi-group + + 100.0 + 20.0 + + + -1.26 -1.26 -1 1.26 1.26 1 + + + True + linear_xy + + + + + 2 2 + -1.26 -1.26 + 1.26 1.26 + + + 1 + + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 2 + flux fission nu-fission + analog + + + diff --git a/tests/regression_tests/random_ray_linear/linear_xy/results_true.dat b/tests/regression_tests/random_ray_linear/linear_xy/results_true.dat new file mode 100644 index 00000000000..abfd03c0678 --- /dev/null +++ b/tests/regression_tests/random_ray_linear/linear_xy/results_true.dat @@ -0,0 +1,171 @@ +k-combined: +1.104727E+00 1.593303E-02 +tally 1: +2.566934E+01 +3.317503E+01 +9.417202E+00 +4.465518E+00 +2.291958E+01 +2.645097E+01 +1.823903E+01 +1.667438E+01 +2.679931E+00 +3.600420E-01 +6.522415E+00 +2.132667E+00 +1.365623E+01 +9.327448E+00 +4.370682E-01 +9.554968E-03 +1.063736E+00 +5.659772E-02 +1.750634E+01 +1.532609E+01 +5.762870E-01 +1.660889E-02 +1.402567E+00 +9.838082E-02 +4.543609E+01 +1.032286E+02 +5.271287E-01 +1.389456E-02 +1.282941E+00 +8.230483E-02 +9.881678E+01 +4.882586E+02 +1.487616E-01 +1.106634E-03 +3.681003E-01 +6.775702E-03 +5.260781E+01 +1.384126E+02 +7.018594E-01 +2.464953E-02 +1.952187E+00 +1.907001E-01 +4.184779E+01 +8.826530E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.226932E+01 +2.486554E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.441107E+01 +1.038682E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.909194E+01 +1.822767E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.579319E+01 +1.048559E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.833276E+01 +3.901410E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.528911E+01 +1.025740E+02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.218278E+01 +2.477434E+01 +8.203467E+00 +3.389915E+00 +1.996560E+01 +2.007976E+01 +1.714818E+01 +1.473927E+01 +2.551034E+00 +3.262450E-01 +6.208707E+00 +1.932474E+00 +1.343470E+01 +9.027502E+00 +4.363259E-01 +9.522121E-03 +1.061930E+00 +5.640315E-02 +1.717034E+01 +1.474381E+01 +5.729720E-01 +1.641841E-02 +1.394499E+00 +9.725251E-02 +4.542715E+01 +1.031896E+02 +5.348128E-01 +1.430232E-02 +1.301643E+00 +8.472019E-02 +1.009076E+02 +5.091264E+02 +1.543547E-01 +1.191320E-03 +3.819398E-01 +7.294216E-03 +5.342751E+01 +1.427427E+02 +7.255554E-01 +2.633014E-02 +2.018096E+00 +2.037021E-01 +2.540053E+01 +3.247261E+01 +9.483956E+00 +4.526477E+00 +2.308205E+01 +2.681205E+01 +1.812760E+01 +1.646855E+01 +2.717374E+00 +3.700301E-01 +6.613545E+00 +2.191830E+00 +1.362672E+01 +9.286907E+00 +4.458292E-01 +9.940508E-03 +1.085059E+00 +5.888142E-02 +1.744511E+01 +1.521876E+01 +5.866632E-01 +1.721091E-02 +1.427821E+00 +1.019468E-01 +4.538426E+01 +1.029941E+02 +5.385934E-01 +1.450495E-02 +1.310845E+00 +8.592045E-02 +9.921424E+01 +4.921945E+02 +1.532444E-01 +1.174272E-03 +3.791926E-01 +7.189835E-03 +5.301633E+01 +1.405571E+02 +7.285745E-01 +2.655093E-02 +2.026493E+00 +2.054102E-01 diff --git a/tests/regression_tests/random_ray_linear/test.py b/tests/regression_tests/random_ray_linear/test.py new file mode 100644 index 00000000000..510c57de8c4 --- /dev/null +++ b/tests/regression_tests/random_ray_linear/test.py @@ -0,0 +1,28 @@ +import os + +import openmc +from openmc.examples import random_ray_lattice +from openmc.utility_funcs import change_directory +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("shape", ["linear", "linear_xy"]) +def test_random_ray_source(shape): + with change_directory(shape): + openmc.reset_auto_ids() + model = random_ray_lattice() + model.settings.random_ray['source_shape'] = shape + model.settings.inactive = 20 + model.settings.batches = 40 + harness = MGXSTestHarness('statepoint.40.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_vacuum/results_true.dat b/tests/regression_tests/random_ray_vacuum/results_true.dat deleted file mode 100644 index 744ce6cef28..00000000000 --- a/tests/regression_tests/random_ray_vacuum/results_true.dat +++ /dev/null @@ -1,171 +0,0 @@ -k-combined: -1.010455E-01 1.585558E-02 -tally 1: -1.849176E-01 -7.634332E-03 -2.181815E-02 -1.062861E-04 -5.310100E-02 -6.295730E-04 -4.048251E-02 -3.851890E-04 -1.893676E-03 -8.448769E-07 -4.608828E-03 -5.004529E-06 -4.063643E-03 -4.022442E-06 -4.112970E-05 -4.186661E-10 -1.001015E-04 -2.479919E-09 -7.467029E-03 -1.178864E-05 -7.688748E-05 -1.266903E-09 -1.871288E-04 -7.504350E-09 -3.870644E-02 -3.010745E-04 -1.375240E-04 -3.807356E-09 -3.347099E-04 -2.255298E-08 -4.524967E-01 -4.098857E-02 -2.437418E-04 -1.190325E-08 -6.031220E-04 -7.288126E-08 -4.989226E-01 -4.993728E-02 -2.374296E-03 -1.135824E-06 -6.603983E-03 -8.787258E-06 -3.899991E-01 -3.308783E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -7.108982E-02 -1.144390E-03 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -5.295259E-03 -6.352159E-06 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -9.852001E-03 -1.984406E-05 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.414391E-02 -3.905201E-04 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -2.571668E-01 -1.323140E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -2.752932E-01 -1.517930E-02 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.465446E-01 -4.901884E-03 -1.700791E-02 -6.587674E-05 -4.139385E-02 -3.902131E-04 -3.424032E-02 -2.841338E-04 -1.598985E-03 -6.216312E-07 -3.891610E-03 -3.682159E-06 -4.067582E-03 -4.209468E-06 -4.152829E-05 -4.494649E-10 -1.010715E-04 -2.662352E-09 -7.526712E-03 -1.225032E-05 -7.769969E-05 -1.328443E-09 -1.891055E-04 -7.868877E-09 -4.008649E-02 -3.246821E-04 -1.417944E-04 -4.070719E-09 -3.451035E-04 -2.411301E-08 -4.859902E-01 -4.747592E-02 -2.606214E-04 -1.369749E-08 -6.448895E-04 -8.386705E-08 -5.475198E-01 -6.061269E-02 -2.625477E-03 -1.405458E-06 -7.302631E-03 -1.087327E-05 -1.909660E-01 -8.147906E-03 -2.269063E-02 -1.149570E-04 -5.522446E-02 -6.809342E-04 -4.196583E-02 -4.141620E-04 -1.980406E-03 -9.227119E-07 -4.819913E-03 -5.465576E-06 -4.247004E-03 -4.420116E-06 -4.341806E-05 -4.691518E-10 -1.056709E-04 -2.778965E-09 -7.742814E-03 -1.272112E-05 -8.039606E-05 -1.389209E-09 -1.956679E-04 -8.228817E-09 -3.982370E-02 -3.190931E-04 -1.427171E-04 -4.103942E-09 -3.473492E-04 -2.430981E-08 -4.849535E-01 -4.707014E-02 -2.678327E-04 -1.438540E-08 -6.627333E-04 -8.807897E-08 -5.493457E-01 -6.069440E-02 -2.717400E-03 -1.501450E-06 -7.558312E-03 -1.161591E-05 diff --git a/tests/regression_tests/random_ray_vacuum/test.py b/tests/regression_tests/random_ray_vacuum/test.py deleted file mode 100644 index e9ca2252144..00000000000 --- a/tests/regression_tests/random_ray_vacuum/test.py +++ /dev/null @@ -1,235 +0,0 @@ -import os - -import numpy as np -import openmc - -from tests.testing_harness import TolerantPyAPITestHarness - - -class MGXSTestHarness(TolerantPyAPITestHarness): - def _cleanup(self): - super()._cleanup() - f = 'mgxs.h5' - if os.path.exists(f): - os.remove(f) - - -def random_ray_model() -> openmc.Model: - ############################################################################### - # Create multigroup data - - # Instantiate the energy group data - group_edges = [1e-5, 0.0635, 10.0, 1.0e2, 1.0e3, 0.5e6, 1.0e6, 20.0e6] - groups = openmc.mgxs.EnergyGroups(group_edges) - - # Instantiate the 7-group (C5G7) cross section data - uo2_xsdata = openmc.XSdata('UO2', groups) - uo2_xsdata.order = 0 - uo2_xsdata.set_total( - [0.1779492, 0.3298048, 0.4803882, 0.5543674, 0.3118013, 0.3951678, - 0.5644058]) - uo2_xsdata.set_absorption([8.0248e-03, 3.7174e-03, 2.6769e-02, 9.6236e-02, - 3.0020e-02, 1.1126e-01, 2.8278e-01]) - scatter_matrix = np.array( - [[[0.1275370, 0.0423780, 0.0000094, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.3244560, 0.0016314, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.4509400, 0.0026792, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.4525650, 0.0055664, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0001253, 0.2714010, 0.0102550, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0012968, 0.2658020, 0.0168090], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0085458, 0.2730800]]]) - scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) - uo2_xsdata.set_scatter_matrix(scatter_matrix) - uo2_xsdata.set_fission([7.21206e-03, 8.19301e-04, 6.45320e-03, - 1.85648e-02, 1.78084e-02, 8.30348e-02, - 2.16004e-01]) - uo2_xsdata.set_nu_fission([2.005998e-02, 2.027303e-03, 1.570599e-02, - 4.518301e-02, 4.334208e-02, 2.020901e-01, - 5.257105e-01]) - uo2_xsdata.set_chi([5.8791e-01, 4.1176e-01, 3.3906e-04, 1.1761e-07, 0.0000e+00, - 0.0000e+00, 0.0000e+00]) - - h2o_xsdata = openmc.XSdata('LWTR', groups) - h2o_xsdata.order = 0 - h2o_xsdata.set_total([0.15920605, 0.412969593, 0.59030986, 0.58435, - 0.718, 1.2544497, 2.650379]) - h2o_xsdata.set_absorption([6.0105e-04, 1.5793e-05, 3.3716e-04, - 1.9406e-03, 5.7416e-03, 1.5001e-02, - 3.7239e-02]) - scatter_matrix = np.array( - [[[0.0444777, 0.1134000, 0.0007235, 0.0000037, 0.0000001, 0.0000000, 0.0000000], - [0.0000000, 0.2823340, 0.1299400, 0.0006234, 0.0000480, 0.0000074, 0.0000010], - [0.0000000, 0.0000000, 0.3452560, 0.2245700, 0.0169990, 0.0026443, 0.0005034], - [0.0000000, 0.0000000, 0.0000000, 0.0910284, 0.4155100, 0.0637320, 0.0121390], - [0.0000000, 0.0000000, 0.0000000, 0.0000714, 0.1391380, 0.5118200, 0.0612290], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0022157, 0.6999130, 0.5373200], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.1324400, 2.4807000]]]) - scatter_matrix = np.rollaxis(scatter_matrix, 0, 3) - h2o_xsdata.set_scatter_matrix(scatter_matrix) - - mg_cross_sections = openmc.MGXSLibrary(groups) - mg_cross_sections.add_xsdatas([uo2_xsdata, h2o_xsdata]) - mg_cross_sections.export_to_hdf5() - - ############################################################################### - # Create materials for the problem - - # Instantiate some Materials and register the appropriate Macroscopic objects - uo2 = openmc.Material(name='UO2 fuel') - uo2.set_density('macro', 1.0) - uo2.add_macroscopic('UO2') - - water = openmc.Material(name='Water') - water.set_density('macro', 1.0) - water.add_macroscopic('LWTR') - - # Instantiate a Materials collection and export to XML - materials = openmc.Materials([uo2, water]) - materials.cross_sections = "mgxs.h5" - - ############################################################################### - # Define problem geometry - - ######################################## - # Define an unbounded pincell universe - - pitch = 1.26 - - # Create a surface for the fuel outer radius - fuel_or = openmc.ZCylinder(r=0.54, name='Fuel OR') - inner_ring_a = openmc.ZCylinder(r=0.33, name='inner ring a') - inner_ring_b = openmc.ZCylinder(r=0.45, name='inner ring b') - outer_ring_a = openmc.ZCylinder(r=0.60, name='outer ring a') - outer_ring_b = openmc.ZCylinder(r=0.69, name='outer ring b') - - # Instantiate Cells - fuel_a = openmc.Cell(fill=uo2, region=-inner_ring_a, name='fuel inner a') - fuel_b = openmc.Cell(fill=uo2, region=+inner_ring_a & -inner_ring_b, name='fuel inner b') - fuel_c = openmc.Cell(fill=uo2, region=+inner_ring_b & -fuel_or, name='fuel inner c') - moderator_a = openmc.Cell(fill=water, region=+fuel_or & -outer_ring_a, name='moderator inner a') - moderator_b = openmc.Cell(fill=water, region=+outer_ring_a & -outer_ring_b, name='moderator outer b') - moderator_c = openmc.Cell(fill=water, region=+outer_ring_b, name='moderator outer c') - - # Create pincell universe - pincell_base = openmc.Universe() - - # Register Cells with Universe - pincell_base.add_cells([fuel_a, fuel_b, fuel_c, moderator_a, moderator_b, moderator_c]) - - # Create planes for azimuthal sectors - azimuthal_planes = [] - for i in range(8): - angle = 2 * i * openmc.pi / 8 - normal_vector = (-openmc.sin(angle), openmc.cos(angle), 0) - azimuthal_planes.append(openmc.Plane(a=normal_vector[0], b=normal_vector[1], c=normal_vector[2], d=0)) - - # Create a cell for each azimuthal sector - azimuthal_cells = [] - for i in range(8): - azimuthal_cell = openmc.Cell(name=f'azimuthal_cell_{i}') - azimuthal_cell.fill = pincell_base - azimuthal_cell.region = +azimuthal_planes[i] & -azimuthal_planes[(i+1) % 8] - azimuthal_cells.append(azimuthal_cell) - - # Create a geometry with the azimuthal universes - pincell = openmc.Universe(cells=azimuthal_cells) - - ######################################## - # Define a moderator lattice universe - - moderator_infinite = openmc.Cell(fill=water, name='moderator infinite') - mu = openmc.Universe() - mu.add_cells([moderator_infinite]) - - lattice = openmc.RectLattice() - lattice.lower_left = [-pitch/2.0, -pitch/2.0] - lattice.pitch = [pitch/10.0, pitch/10.0] - lattice.universes = np.full((10, 10), mu) - - mod_lattice_cell = openmc.Cell(fill=lattice) - - mod_lattice_uni = openmc.Universe() - - mod_lattice_uni.add_cells([mod_lattice_cell]) - - ######################################## - # Define 2x2 outer lattice - lattice2x2 = openmc.RectLattice() - lattice2x2.lower_left = [-pitch, -pitch] - lattice2x2.pitch = [pitch, pitch] - lattice2x2.universes = [ - [pincell, pincell], - [pincell, mod_lattice_uni] - ] - - ######################################## - # Define cell containing lattice and other stuff - box = openmc.model.RectangularPrism(pitch*2, pitch*2, boundary_type='vacuum') - - assembly = openmc.Cell(fill=lattice2x2, region=-box, name='assembly') - - root = openmc.Universe(name='root universe') - root.add_cell(assembly) - - # Create a geometry with the two cells and export to XML - geometry = openmc.Geometry(root) - - ############################################################################### - # Define problem settings - - # Instantiate a Settings object, set all runtime parameters, and export to XML - settings = openmc.Settings() - settings.energy_mode = "multi-group" - settings.batches = 10 - settings.inactive = 5 - settings.particles = 100 - - # Create an initial uniform spatial source distribution over fissionable zones - lower_left = (-pitch, -pitch, -1) - upper_right = (pitch, pitch, 1) - uniform_dist = openmc.stats.Box(lower_left, upper_right) - rr_source = openmc.IndependentSource(space=uniform_dist) - - settings.random_ray['distance_active'] = 100.0 - settings.random_ray['distance_inactive'] = 20.0 - settings.random_ray['ray_source'] = rr_source - - ############################################################################### - # Define tallies - - # Create a mesh that will be used for tallying - mesh = openmc.RegularMesh() - mesh.dimension = (2, 2) - mesh.lower_left = (-pitch, -pitch) - mesh.upper_right = (pitch, pitch) - - # Create a mesh filter that can be used in a tally - mesh_filter = openmc.MeshFilter(mesh) - - # Create an energy group filter as well - energy_filter = openmc.EnergyFilter(group_edges) - - # Now use the mesh filter in a tally and indicate what scores are desired - tally = openmc.Tally(name="Mesh tally") - tally.filters = [mesh_filter, energy_filter] - tally.scores = ['flux', 'fission', 'nu-fission'] - tally.estimator = 'analog' - - # Instantiate a Tallies collection and export to XML - tallies = openmc.Tallies([tally]) - - ############################################################################### - # Exporting to OpenMC model - ############################################################################### - - model = openmc.Model() - model.geometry = geometry - model.materials = materials - model.settings = settings - model.tallies = tallies - return model - - -def test_random_ray_vacuum(): - harness = MGXSTestHarness('statepoint.10.h5', random_ray_model()) - harness.main() diff --git a/tests/regression_tests/random_ray_volume_estimator/__init__.py b/tests/regression_tests/random_ray_volume_estimator/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat new file mode 100644 index 00000000000..9c15ec97db0 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + hybrid + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat new file mode 100644 index 00000000000..6da51a711bf --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/hybrid/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.934460E-01 +7.058894E-02 +tally 2: +3.206214E-02 +2.063370E-04 +tally 3: +2.096411E-03 +8.804924E-07 diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat new file mode 100644 index 00000000000..7d05f0978da --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/naive/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + naive + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat new file mode 100644 index 00000000000..f8d6d10b001 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/naive/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +5.935538E-01 +7.061433E-02 +tally 2: +3.263210E-02 +2.134164E-04 +tally 3: +2.107977E-03 +8.905227E-07 diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat new file mode 100644 index 00000000000..301671e6d45 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/inputs_true.dat @@ -0,0 +1,245 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 10 + 5 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + simulation_averaged + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat new file mode 100644 index 00000000000..5f297586075 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/simulation_averaged/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +-5.745886E+02 +9.758367E+04 +tally 2: +2.971927E-02 +1.827222E-04 +tally 3: +1.978393E-03 +7.951531E-07 diff --git a/tests/regression_tests/random_ray_volume_estimator/test.py b/tests/regression_tests/random_ray_volume_estimator/test.py new file mode 100644 index 00000000000..fba4bbbbe6d --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator/test.py @@ -0,0 +1,30 @@ +import os + +import openmc +from openmc.utility_funcs import change_directory +from openmc.examples import random_ray_three_region_cube +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("estimator", ["hybrid", + "simulation_averaged", + "naive" + ]) +def test_random_ray_volume_estimator(estimator): + with change_directory(estimator): + openmc.reset_auto_ids() + model = random_ray_three_region_cube() + model.settings.random_ray['volume_estimator'] = estimator + + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/__init__.py b/tests/regression_tests/random_ray_volume_estimator_linear/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat new file mode 100644 index 00000000000..6440cca04a0 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/inputs_true.dat @@ -0,0 +1,246 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 40 + 20 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + linear + hybrid + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat new file mode 100644 index 00000000000..46200b19c8d --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/hybrid/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.339084E+00 +2.747299E-01 +tally 2: +1.090051E-01 +6.073265E-04 +tally 3: +7.300117E-03 +2.715425E-06 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat new file mode 100644 index 00000000000..fcf93b046d0 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/inputs_true.dat @@ -0,0 +1,246 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 40 + 20 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + linear + naive + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat new file mode 100644 index 00000000000..02bfc526626 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/naive/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.339575E+00 +2.748441E-01 +tally 2: +1.086360E-01 +6.030557E-04 +tally 3: +7.298937E-03 +2.741185E-06 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat new file mode 100644 index 00000000000..1df3204a370 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/inputs_true.dat @@ -0,0 +1,246 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 12 12 12 + 0.0 0.0 0.0 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 +1 1 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 +2 2 2 2 2 2 2 2 2 2 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 3 3 + + + + + + + + + + fixed source + 90 + 40 + 20 + + + 100.0 1.0 + + + universe + 1 + + + multi-group + + 500.0 + 100.0 + + + 0.0 0.0 0.0 30.0 30.0 30.0 + + + True + linear + simulation_averaged + + + + + 1 + + + 2 + + + 3 + + + 3 + flux + tracklength + + + 2 + flux + tracklength + + + 1 + flux + tracklength + + + diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat new file mode 100644 index 00000000000..93e54feea99 --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/simulation_averaged/results_true.dat @@ -0,0 +1,9 @@ +tally 1: +2.670874E+02 +4.432876E+05 +tally 2: +1.117459E-01 +6.497788E-04 +tally 3: +7.563753E-03 +2.947210E-06 diff --git a/tests/regression_tests/random_ray_volume_estimator_linear/test.py b/tests/regression_tests/random_ray_volume_estimator_linear/test.py new file mode 100644 index 00000000000..94a14f3ad3b --- /dev/null +++ b/tests/regression_tests/random_ray_volume_estimator_linear/test.py @@ -0,0 +1,32 @@ +import os + +import openmc +from openmc.utility_funcs import change_directory +from openmc.examples import random_ray_three_region_cube +import pytest + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +@pytest.mark.parametrize("estimator", ["hybrid", + "simulation_averaged", + "naive" + ]) +def test_random_ray_volume_estimator_linear(estimator): + with change_directory(estimator): + openmc.reset_auto_ids() + model = random_ray_three_region_cube() + model.settings.random_ray['source_shape'] = 'linear' + model.settings.random_ray['volume_estimator'] = estimator + model.settings.inactive = 20 + model.settings.batches = 40 + harness = MGXSTestHarness('statepoint.40.h5', model) + harness.main() diff --git a/tests/regression_tests/score_current/inputs_true.dat b/tests/regression_tests/score_current/inputs_true.dat index 9ea8e5e4ae1..ddbd6c24b05 100644 --- a/tests/regression_tests/score_current/inputs_true.dat +++ b/tests/regression_tests/score_current/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat index c1a616dd109..e787f24d413 100644 --- a/tests/regression_tests/source/inputs_true.dat +++ b/tests/regression_tests/source/inputs_true.dat @@ -85,13 +85,13 @@ - + - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 @@ -108,13 +108,13 @@ - + - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 @@ -132,13 +132,13 @@ - + - + - + 1.0 1.3894954943731377 1.93069772888325 2.6826957952797255 3.72759372031494 5.17947467923121 7.196856730011519 10.0 13.894954943731374 19.306977288832496 26.826957952797247 37.2759372031494 51.7947467923121 71.96856730011518 100.0 138.94954943731375 193.06977288832496 268.26957952797244 372.7593720314938 517.9474679231207 719.6856730011514 1000.0 1389.4954943731375 1930.6977288832495 2682.6957952797247 3727.593720314938 5179.474679231207 7196.856730011514 10000.0 13894.95494373136 19306.977288832495 26826.95795279722 37275.93720314938 51794.74679231213 71968.56730011514 100000.0 138949.5494373136 193069.77288832495 268269.5795279722 372759.3720314938 517947.4679231202 719685.6730011514 1000000.0 1389495.494373136 1930697.7288832497 2682695.7952797217 3727593.720314938 5179474.679231202 7196856.730011513 10000000.0 0.0 2.9086439299358713e-08 5.80533561806147e-08 8.67817193689187e-08 1.1515347785771536e-07 1.4305204600565115e-07 1.7036278261198208e-07 1.9697346200185813e-07 2.227747351856934e-07 2.4766057919761985e-07 2.715287327665956e-07 2.9428111652990295e-07 3.1582423606228735e-07 3.360695660646056e-07 3.549339141332686e-07 3.723397626156721e-07 3.882155871468592e-07 4.024961505584776e-07 4.151227709522976e-07 4.260435628367196e-07 4.3521365033538783e-07 4.4259535159179273e-07 4.4815833361210174e-07 4.5187973690993757e-07 4.5374426944091084e-07 4.5374426944091084e-07 4.5187973690993757e-07 4.4815833361210174e-07 4.4259535159179273e-07 4.352136503353879e-07 4.2604356283671966e-07 4.1512277095229767e-07 4.0249615055847764e-07 3.8821558714685926e-07 3.723397626156722e-07 3.5493391413326864e-07 3.360695660646057e-07 3.158242360622874e-07 2.942811165299031e-07 2.715287327665957e-07 2.4766057919762e-07 2.2277473518569352e-07 1.9697346200185819e-07 1.7036278261198226e-07 1.4305204600565126e-07 1.1515347785771556e-07 8.678171936891881e-08 5.805335618061493e-08 2.9086439299358858e-08 5.559621115282002e-23 diff --git a/tests/regression_tests/source/results_true.dat b/tests/regression_tests/source/results_true.dat index 94d9ced1551..673d27c8af8 100644 --- a/tests/regression_tests/source/results_true.dat +++ b/tests/regression_tests/source/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.054101E-01 1.167865E-03 +2.959436E-01 2.782384E-03 diff --git a/tests/regression_tests/source_dlopen/test.py b/tests/regression_tests/source_dlopen/test.py index 88ff9dd8509..0581d6deec4 100644 --- a/tests/regression_tests/source_dlopen/test.py +++ b/tests/regression_tests/source_dlopen/test.py @@ -72,8 +72,7 @@ def model(): model.tallies = openmc.Tallies([tally]) # custom source from shared library - source = openmc.CompiledSource() - source.library = 'build/libsource.so' + source = openmc.CompiledSource('build/libsource.so') model.settings.source = source return model diff --git a/tests/regression_tests/source_parameterized_dlopen/test.py b/tests/regression_tests/source_parameterized_dlopen/test.py index 1cc253528cb..151fb37356e 100644 --- a/tests/regression_tests/source_parameterized_dlopen/test.py +++ b/tests/regression_tests/source_parameterized_dlopen/test.py @@ -71,8 +71,7 @@ def model(): model.tallies = openmc.Tallies([tally]) # custom source from shared library - source = openmc.CompiledSource() - source.library = 'build/libsource.so' + source = openmc.CompiledSource('build/libsource.so') source.parameters = '1e3' model.settings.source = source diff --git a/tests/regression_tests/surface_source/test.py b/tests/regression_tests/surface_source/test.py index a25b40064d8..251e9e9ad4a 100644 --- a/tests/regression_tests/surface_source/test.py +++ b/tests/regression_tests/surface_source/test.py @@ -118,15 +118,13 @@ def _cleanup(self): @pytest.mark.surf_source_op('write') -def test_surface_source_write(model): +def test_surface_source_write(model, monkeypatch): # Test result is based on 1 MPI process - np = config['mpi_np'] - config['mpi_np'] = '1' + monkeypatch.setitem(config, "mpi_np", "1") harness = SurfaceSourceTestHarness('statepoint.10.h5', model, 'inputs_true_write.dat') harness.main() - config['mpi_np'] = np @pytest.mark.surf_source_op('read') @@ -134,4 +132,4 @@ def test_surface_source_read(model): harness = SurfaceSourceTestHarness('statepoint.10.h5', model, 'inputs_true_read.dat') - harness.main() + harness.main() \ No newline at end of file diff --git a/tests/regression_tests/surface_source_write/__init__.py b/tests/regression_tests/surface_source_write/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/surface_source_write/_visualize.py b/tests/regression_tests/surface_source_write/_visualize.py new file mode 100644 index 00000000000..73340cae06f --- /dev/null +++ b/tests/regression_tests/surface_source_write/_visualize.py @@ -0,0 +1,66 @@ +"""Helper script to visualize the surface_source.h5 files created with this test. +""" + +import h5py +import matplotlib.pyplot as plt + + +if __name__ == "__main__": + + # Select an option + # "show": 3D visualization using matplotlib + # "savefig": 2D representation using matplotlib and storing the fig under plot_2d.png + option = "show" + # option = "savefig" + + # Select the case from its folder name + folder = "case-20" + + # Reading the surface source file + with h5py.File(f"{folder}/surface_source_true.h5", "r") as fp: + source_bank = fp["source_bank"][()] + r_xs = source_bank['r']['x'] + r_ys = source_bank['r']['y'] + r_zs = source_bank['r']['z'] + + print("Size of the source bank: ", len(source_bank)) + + # Select data range to visualize + idx_1 = 0 + idx_2 = -1 + + # Show 3D representation + if option == "show": + + fig = plt.figure(figsize=(10, 10)) + ax1 = fig.add_subplot(projection="3d", proj_type="ortho") + ax1.scatter(r_xs[idx_1:idx_2], r_ys[idx_1:idx_2], r_zs[idx_1:idx_2], marker=".") + ax1.view_init(0, 0) + ax1.xaxis.set_ticklabels([]) + ax1.set_ylabel("y-axis [cm]") + ax1.set_zlabel("z-axis [cm]") + ax1.set_aspect("equal", "box") + + plt.show() + + # Save 2D representations + elif option == "savefig": + + fig = plt.figure(figsize=(14, 5)) + ax1 = fig.add_subplot(121, projection="3d", proj_type="ortho") + ax1.scatter(r_xs[idx_1:idx_2], r_ys[idx_1:idx_2], r_zs[idx_1:idx_2], marker=".") + ax1.view_init(0, 0) + ax1.xaxis.set_ticklabels([]) + ax1.set_ylabel("y-axis [cm]") + ax1.set_zlabel("z-axis [cm]") + ax1.set_aspect("equal", "box") + + ax2 = fig.add_subplot(122, projection="3d", proj_type="ortho") + ax2.scatter(r_xs[idx_1:idx_2], r_ys[idx_1:idx_2], r_zs[idx_1:idx_2], marker=".") + ax2.view_init(90, -90) + ax2.zaxis.set_ticklabels([]) + ax2.set_xlabel("x-axis [cm]") + ax2.set_ylabel("y-axis [cm]") + ax2.set_aspect("equal", "box") + + plt.savefig("plot_2d.png") diff --git a/tests/regression_tests/surface_source_write/case-01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-01/inputs_true.dat new file mode 100644 index 00000000000..7368debd5f7 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-01/inputs_true.dat @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-01/results_true.dat b/tests/regression_tests/surface_source_write/case-01/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-01/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-01/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..82dca4d032eec7395fe8697247c7dc4fb01f51e1 GIT binary patch literal 33344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 8 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-02/results_true.dat b/tests/regression_tests/surface_source_write/case-02/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-02/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-02/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-02/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..872efa070701d0a052aa84f2f1980145fb59519b GIT binary patch literal 12752 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 fIbtUfR=hm1DWH4{y87gfrX|1|kOEHDC< literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-03/inputs_true.dat b/tests/regression_tests/surface_source_write/case-03/inputs_true.dat new file mode 100644 index 00000000000..cd0f7deda59 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-03/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-03/results_true.dat b/tests/regression_tests/surface_source_write/case-03/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-03/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-03/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..04a6ac9a387d5bd34e4d46fc533e90171d779238 GIT binary patch literal 33344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-04/results_true.dat b/tests/regression_tests/surface_source_write/case-04/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-04/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-04/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-04/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..75c309eb4f43740be530493ea10863478d06a525 GIT binary patch literal 33344 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 GIsXqD9JFl! literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-05/inputs_true.dat b/tests/regression_tests/surface_source_write/case-05/inputs_true.dat new file mode 100644 index 00000000000..143e8520074 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-05/inputs_true.dat @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-05/results_true.dat b/tests/regression_tests/surface_source_write/case-05/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-05/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-05/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-05/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..1a2f54d765811bd10a2758e20a11b7ea1f242ae5 GIT binary patch literal 33344 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 F{{Rq!v~K_a literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-06/inputs_true.dat b/tests/regression_tests/surface_source_write/case-06/inputs_true.dat new file mode 100644 index 00000000000..f2dc7ea35d6 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-06/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-06/results_true.dat b/tests/regression_tests/surface_source_write/case-06/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-06/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-06/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-06/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..bef135e5ace081b17fa1400cddb095fda62df652 GIT binary patch literal 33344 zcmeI52{;vP`0$T?DO(GYvM-e)*_CHTq#}w`QYcFc740gt5Ro?RDMCe(k}O%Cqiori z>`V52%bxY)b*A^6`G@}3^`5TlyT0$dSMSW3GiQE&bKlQB&#dR#MvV<@tbDA5$rm#- zfu6wm&nWi4ancL9JT(P-k9}@N8lfQ#-AKbg+Nlv50v%!UGhWhkPSWxP>82)ZB~9P1 zuc=9xm=jw*HY9ya;ANT`W5y=@|Ka~rYha@$`cJCtB3WbkluF7oY-OA{cIvpfrRn~C zN3H*vgykiI&G5e*a*^^CpVxB+1{>jV}^Kc1* z35&@WLXew7kQ<-)zWqP%C&~h8INjiP-#tf~nX;Pv&n5isyQfIAVKM%n-Jxlv!B?kCzlysm?702df<~7c+$89PoYyY~i9i{Gm;-r1N^lnVogOPHdRZW#=-qDtUDD zTEgl-=F|_$H;;x5fKJ-o$oi625{E!kl0CwE@<}xu+|bSTcufQFRqIcPj;#QR!uhwJ z`-q`k)~O16YLzHB*e3FKJ^```_Ton0A#cc&hf4-ye{mj3?e$6w>wuxk2 z53g+aa$!~3qW(8m#n6G%fg4$WAb-rk;_;Gc_jefr5uB1!$XWQ1fjApy#}{~lEmf0( zGgVW5p9j>+H*6hZ`DR$BxkZ+<2Vh=-nrEA14C#rp<@M*-Z?OvngeZI_`w5e;$@t(o z)bhcXGiv$ZIn?sOa$;&tPOQf=`(S<3@4w=6nqN9s@HH)QJVz{jr$Q+-I92J^XHW&l zL&0*PHBHdyhG_oN4Xe-q-m6O#+oz8Yo&Up`6BG%0>sG(>YT znf3ii&EQ&*x677$r7+EKL8}Kx8LV~@?RXZagwh|||NKbI^gehFwS2G~$F!)GZ+H&1 zd`P>am=0l{bU=0RxBg%8aj_q)8%Y{~x5IMe-VE1+#iDGSd8eykv_r!=t&f$kHcg#w zYmq8i>=tCf&M7c$J-~CQ<%4bWFd=Hi2hX9F4+|zj8dA##&!Lvj)B&1*rKshD=TOUM z&c7=Ej8V%6&!Lvj+`r_5=TOUs4d>G`{ALWEr~}^|HhpdTHUy_WW+w6-F>h2f%z?U% zeb@K5bO86o2TT|X{b9q>4OtRQ3(-Z5PjyS5uAr!ISPhu$@8CHJJI~x@(*FuX4Ymip zZEE;cPov(P6o6tCAl2jB;jZ`=Fk&8g*3l;)d??mhC22*6#_F^4B6+HR%t3a4UaIbN z7+XH#(QdxZE}1~&v8`uN8LWaEuI$eZ5o-dDKJ;NdF~u;tdC~Z#Z`DX;%1a|&)h!ep ztVaLN#}4`UGxTN)w!V-sYe{h#{xLX@Y2zu0`aRvV5`D@*9v}na@mxV)cokp!f-p^j>Cu3#&F{c@9jz4SFEm;9N zPwc8xTv!a1cp0_aoGL&x!R_GAf{%a9F@Ld0&-i99T(@zRhzI_BCtxT<{B|6oq{vddW*hyZ;%*V5xi`!wMX0_7XO_}i8vahd$r3>MyVKcV~oBLqA=C;#`pXC&s znd0M>k=NC3G6)x5>ogAE(~RXektOg{yGUIHkd8Ub81pU#+)`$`ygaxSCL517pEnEy z(#czXN@h1vaAv9p)T(dnxcb&OFQMk@Krg`RTH@ukPagRbi1aBBXg*0cfE2{`jE-0} z{LWdgW6)X&*StR@a;HHR-C?R7$7C-^;e+)-C)*F6W6ZIgmc?@fEMwDdO}tr;VAlbYvU-qj1S(Y*lcU=QFdD5=TZZ<8tbNC zobBeX*iJ5Ng;H{>6ppE6!;laaVuVg59Hb4sDiGd>NNto(l1ShD#~iHPJf?d#a_)9D z2uk*2u6aX`%*HvilFre3uO0;l+sFQ$54G}*Ynt}6t~X&`MOQauJ#e2`ox*_d23}TP zbG{LjkBb=QIg~=p?zbCPtg8m1aDl3O0uc>gbH+WEU5)|Hs#c{`PW17A43sm(nZma zr5DY@&r$Z@uw_oh2hX|4pJPyxWeRz@ZCgGNx_@!x2HB-Ye}0F_^02SFpbWedvhS-K zYy@kY)Ka&g!f3#@@Sj@rG8F4=CeEcN_72w%)I;LBPlJh@he;e}WHyeg_|~R&GkFRQ zdHccpP%Gai&kz337xRB!ukjpe`7HRKG80q&dmlW9T0RT^k`JCkEg$abd`{lsSiybsbj#6}BopvXX&2l~5T=Er4n+l%udh{CXOTYTgVMrcbYfIa^IduB|0iHuG zAD-!a{^S16bR4r+{yohnyg-J$-`SP~CPZ_G@J~J0dg$V}DYoKD16XzYb=RhinLu() zr+Z`08npK7+02ULl;?lQw+ncVR9s%a^(H2yHC*zALQL$h`ZgOUU-6`~Wi};;yuRT% z)QZodzZ4%lhgv>-)A)3ys>g}w(h`UFZZJF7*$GViN>wftRs&bb3o47Z=R;Zl$U+s^ z1PP7J2|`9ANGyC@_B2YFV*5j$2Y3#(d=^jR^Iz{T;5pRt;r~lMc+T0S>8XB2ePHLE z7Q{x9i1l}f9mZqNYDF7CX4;Os{rl>n#Iw#%ZrhS!T3&LPox46N?;PU$Orx7356F)n zESbhfro1&ISgaq)^||{?OVG{AG56{Fl8fVz=Nq0wEuW>+_z1@*?p?xK4O{Mb)uI1l*xofr($@lLXJ z6J-6u2Rp;6ppKE+u7?+N&~%f%o+UH1A3R5vCzfeMatv%W+mz>MUJozwZ}+-!cMNV5 zA5vF5Q4N-ut3`MRwm{3<*IPVcBna%`b;)iR-H0LrM!lIhAM)d)cn-CE#Be@XeE#eD zhUZYrNBl4O;5pRtk@!nKcn-CEB>$2Ro;>>=Ui4vyew?f2;9B-3ck2ZUDrCnL^^!EMwBJvp!f2j9R^QR zfPEeK%TX-Vw(kKRzmj^La-juTqi~0p1Zr>qFaqU|z`e)AFBW zm0O`#f^p34&A~7)qs4MCvc7v#yBH$-$~ul-TdbqIqqxw2Jel*T8;AYoSYhX(aor_^S&o= z9vnE$@xB(Sy3t*~ER_fsHEQjvi)ey3tt|K-c{(FXXV=W4&ay6k7*4`ZWSXg-W0WPFt%k<2dB)M-JyRv|N%S zp>qtJ7$kB=G5oqt1euM)xiP&z9>*cyp5i$-PcKn4nAkt8j?>Z{R~mz7y@uCbwaNi} zYkJ&!{Xf71$A#d2l}@O7*SYbo@M7wd9d-68Mq;5h`xrd6`lqu`7J>(a1^>-maHGDbNPJ^?qDQ;sb+ zI)J4^l0+UuG?36M^Ox+*1ugj;WoM+s(cq^a9295pQN;O7y=6w^9jyfQpS6Ic8yp@j z>nod;lj)lF>H>~Ko^ND55WFu-EjoMy12{^!u$BNAw_mdd)DI1YJy@b~h)^c4J_=Fki1qwV9=6B!ZAZ#Is_ za(eeZ9EUvL@SLwHpqo>p9}pwBWh00C!Kj#T<<`+3K>mZ2jhuA@@Z)M_*f3lJ=FTtw zaeIFW9DgMeP}wewO7rcHzKi3K`zTKnpO-eC*J~LV5Se&7i<@85p<4c;c%ocA*dDcU z4Q#6f#(MT!xb+{pGEtXWN`UL9v?i1TKzW_oDa47Z+Onbj38Q}E5oos z%=W|Eon1iZ*gQk`!sWfQU~m2yJdT5&nsY=ny`Ja_#2$J%=2l=4uCEfkFN^Y zAwqF}fjmB{Bp+;DlJbk_P6XF6a6BZrSu?{oNewMD$)$$Xf)dXaC&gb(t> z^K|E<>3!Bt=VQwrA<)?d?$pR|>78tyl@oT&EOrGty$_!A`tbOTNcUl=7PaGj_2w>M zwPI6R$h#tVrTNw>A+AnH?BXH{b36ij3Td~4t64C)O0ItXPJre(uV6IZKE2O6T)tuP zdG!6WSAA9&VBNj;EmB`UE9bTy{iw|9Lu`jOxY_F{MhVi$-ed$bXLYb=Qvn9`-v+GDvya z+FG|BBq1T7yoU>=;E?x6@f?ReF-L_|L6{GLUAJGPyA^Dxn$MyZ zKtr7Rn0Z!?N>lT~GJVSXT*-a#9BQ>6b&?O|;JWn?x6V$T`*Q{ zojs(f0%pQ0v|4uYfbZ;*nlHCH;CV}-W|rN#VA$gckCwMqoIYoRt*%UI z>dOWYnZ;`Z73|jkF~|Mo^^{iaF>vs>i;&LaI(U;w`Fegq1JFRTax(Xp0a+pGYkf+M zFz%C&^AS)D4$DMJ`+e_6vfdl)53%0x$DEUzeI4I3`hY_4y~aHB4%!sm+*f;0V zQh1bwMa}V59c(u9P_H$s19F5Pub%hiAW@s$((Z>CQ*g-h4Zq%~m2Vo9iAH+IOmxS~fvu6~=Gp@20?zPBTv8 zJub9yeMUf3{o4OEC!0sEQ0+23(J`OB$0Z>J76bQlM@~1x0)^u=^)UsYI&gokCP zyL?n{4&`1hP2%IEQ-2fkS?c;gD$zB&63Cb}N*Dn*4iq zMRjeUKQ@Vfbw?=N-j^O%8>)!jIZs=(`Xgl>C9l_-q=;bk?XBmUJ!40H?av|IXD^KE zdM3}x8Qie+eIHmw;X{7>4bP!gzHOK$-}Kctok!W;!5%lR1!d~PaJ_cA(1O#YFuS7M z(#0tYd^__hhF72szPZuj(H(mn5fDM+nf-TA_>kuto;4X&(Z{+#55tnZ()`}?0?)gI6UNa*HZ+xxR+}SlNr!yx%SY!g`QSMhieD~{O&bSajt*LH5zB?+$p`D3Ouc1Bw&{hH|1bIAIn?qo_)9)` z4z+x?PvcV(A8y8y@D4be#uT148HLG3Y2WnX>%pV(;&+c3OM&nXkCJ%zT6p)B*k zHPqfhNsF6>bJ}{1=TOVXa2lWgy8YleqLv z%|3fcYHK67$nfKlw@4lAOS0Q_kxvIbnS9fqA(Zm@eB{SRcjA0p^nPV}$cr#jSOiuWT4RynAZ#@UdU_ z(-NEc(=^0~YJnL`Qd5v}F_6h=V-u~chy5!&vUXffMS7CYxrd}s-nUC0A3VqPT~ z%O-HHQD3aVbrj6K_ulhx!zlDEx)UTXTncvdTXx0rv;&(TcJS6rDT;E?a1;yKsnzrR0t zbqs5#i0Ouop`H2@*RP+_Qo5|153ij5{N(M#c`fz3vB)~b5XfrDqq66LI{GA4H>rPv z=pS>+mt^Q(t!)J!+GhA-EJ#OFGEGTHyO9Y&n%#3Wk zo%h}4J}psMX|uXoP&2HnWaJ8ZR|!&9iru`HRS4=K!&TP6ji@&=vdrS;l40;gd8x`K5$S?TXsmKjz z4kK4m<(lcwXPMx9o*MR6xo-J&9*fvqwV|{mD7D6N_Ch$czZ?S|+WM%}Cs5FTpI9 z^$l1v-g>y0uLES~&Tr@|i-HfAa);$PYoo^mf_)i17foB=@EmISSm1o9RS)o-)Q$t$ znao4L5B*-I|D+r6#?7fXQ#}k?EJqDmj=h6}XGiw>zw88ZS})8O4-%p3+ZWC67{$>R z>nk4v9<8KUZ{+I89*1`dHn50@QujX1kD~J8Zz4_jXYbe$m zR%3q02@)pWxM?y9?%qDT|L~z^*c;k3bm!Oz5MDQ?$J|o@N^Cx4hlN%`p6BQsg_34? zFnf7SgQEz_@LKbl&_c@X9Jvo2CPEr&6rWGu+WYVJ)NL+t#n}Qz;_LTEZ{FZ*h1FHW zqz7(!(B{dmrp0dMa7VC{{^NjL#Qv4Nn)vQ*)B4~!)~fQ^vUNki$4=*7$mV~amxe@_ z|2(7^0!l1jZhZ0OGxXkDP(paz31VDoZbat^qh*I*#LsP`e6B9G?8)_pofE{gB84q3 zSLFfk}QZSv(FHBk6d_r0UL1kt^7;#+RdojyL;F>}mkYRHVV zyvbcG=K39Ggtf8r1ogr9iu`Bv7u0~&kIuhJsILJ)-AKpNwg$fBpzq^P976i)4(qKy zLwP+Cw(LnCY#ie#_xM?N!#WV5BG7AEHB8EH0?|OYOD)zZ25hR74avai1Z{P8-Zv1I ztCN}$s({u?xkNapO#eJCtOj8`EIxeo#S4;LDq+U0=GCi{=w{_qYl_&KET=qwip6Ad zz2P|_5p#{(^83JEo#ATM_5VJf;=X(`OYY%1nB*Ywx_NmONV+YtlmB52+;{M7pn;ht zdX7!*=}gb3z}6v_2ez~arS(~I0iT|5QCMv!z~VC-M_WNEd#3vV@tnvPi%q_*nb?oF z7S*dsPW#2Fy!q~?;^r#2REtI;$)^!=-Z=f#Q@Z#x~|$2`W?8Od-Esfg=UE_aNq(2FM>9y06i;~?I`KOe zm(Ra-^jM9KU1?vziQ|yh16Gm9DCAh!>$H8Jh z86P}n?Q@IIIz?k3_EOA;3tZK}S*dL;&>M&HhTR*U*Ea(-DIc%%?`r_pbIrnUKB1t7 z+c!Hrb1ur+E0|V4Lwx3td?*LZ$dOYaX5-$2K(=ws?z^iA#LrQ7UE+});9hLsGn0wu z_ZgP4cAvTX0e%TT*BGWef&?bcdvP7-LmnSIhpv-Fe6{IUtlc0w+WmGPRolSk2A$MC z-bBd#_QR#r1x>KxN^a@;Gj-sll6Fw<2~i|&S%I-=KjnQ>Sgn~{Z+MPlo53m$>j6lL z3!?AGB<&bXgM`;tS9uSYK=(J+PO84ufVU*&ZL?t`ux*K9`nt3b#0x#`XIJT?=pW3) zdATD{MYZAwH2&~qVfYXeg7Mhv9Bmf9dkkZ$=D%p!+Yb37c|V(&l)${};=xBwJOagB z(V?xEM=3a1F8|JlR$$D^<-t>w%w1Kl?->qgJhk=FOr{>Dt4?<-ccRTlJtn!atSUb_F*>#Qfcxzr3Qfro@e zfcuLUKzqOF3H^>dxaGbflkGf_YsIb z;sF=3w=~1(PZ!oAxz%v3Icvs2tsHPM%7AHm$TGCK|DmWJF5k%8DW0PzJTCiq#W>W> ze$k`dR}SAsc}X(dse^3H55D^Fw?Ml=hW8~5uiyfyy*^9rYJjuz-qF=O%g{b%>F#kH z2W$J2@xgOQaVL8B`Aa((RzamA!sX1T(O@=?@5U!S8*ulRV116?aUQna%pLyL2XYP= zImmEqrn0Tm3wFU35Gq#(SpiDRvz!7A6HY z(?>CDN5Hw8#Z{8#SRG|1&c@-(c3PE!t2^ZN4bNFh*R8p35k2y-+%fF)lYfu<*N6)? zwWLfO_mANqtd}c-GB4MA)Fch=CbmNqC2b8zvN~@6dg?3txOcsb2KtWqW(RMBbZ7+PC#E;{U z=Nmf~7>Xe?@$21w;%>cq5W|r0ps|%eoQ)$+%KGia8P(m z{7xVH&HG4bLg6zc>H+vk~AGyYIBp^ES{*DBt(u zLO^?6-& z9I*o|@QPX^+%PsP$L!F@Co}aA@EkwO)lXA9d%@h+r_P(!41&J*N@e%obb-%854bJ4 zYrwX2i#ugITEO*?7@|7g8}QL?Vd1V)NmR3ZPOa?>KJ#!skLyws%KT{&%!|DuQNK#P zdsa?N@CUnZI1YKf;W;P$8M*l;u7gDuh>Xb^JcCyI)qA@|hQSKYt4BWdGywOiIRU~4 ztKq||yTgRPmjS02vD|}ot5C6nw)?$s9P)gdkMqGi(9ZVMN5UhJxOm%%yYB5E?(A`I znYGnGF^Hq#)=(yRzl*&TX=sMA9Np^3OD`ldMkg+FD`mfmy#I#hcyAYu6S2>U%dPHxf(F1wQz8J^3w}Ghoxa6GyYfxRjq2zp=5Bc$s1vsB8 zCx)ea;UGx5cJCD%JH}z2l_Oiby^S8%e#rYfc#eQdNLfc!2h48RBTRsAX`>3_keh&?;UkE!3MhzvLXDGR#q4 zOX4siWx}ZOTc&*Idry4hoppt<(I4GHd#Vjct}8rz>2wbwIRD+-tuy%GIT|}16^`@% zfU+6`%Hwl~;l)r$e|aL`jOcw_3d?GsK5_mFp|&PS6Z|qW81k(77DV_Pnb37jGzvvIDc-0=U5L&OZr4IRV$8SO<2dB`#*6d8 zJOn?wJ(uOJ233S0J?BTXNX2}K@Dq*gP<5Y}gZr8Sc*;E0EIF76-=`WkM4Bj~1qSo$ z@!JLR?J1sPrIt1J=3)=H#oc!(@j=o$eeTnVvUxd!4@#2ri zo&pt3K(L>eM}wF)E9aQ u_%Hvh!@xgP7RVvqLR}Mk041;K~jVPyzebUQ|FkOFo*j-=e&pg1r4K#`&~cGLPGB z8-&rZ7j8vo^a40vIjD)g7jO{H?_M9(30juZu>{n8fafk$vvqHefzJ~alQpbaP=zZC zo;?quyq;nvj>D6pK%edokjA=8{|4;{NUGl)r2MoM__NFT)5KIm;m-|QJRDoVW&d5o zt`!k5viecGe*!(~$hdO-eK*SAy_|_dn45U^XCk&g!i;dne4F>^XydFLt*#?4v=y}} z@|t{mieGP60*ya8xR1h*ouB91J#GR^a*oR;&HDxoLKdwsF>Qojmr68QC)UI5x~>_l z0ihH7sjAw$SFoXN{Jb90TPTl{ll$=F;xqM@nV6M%v$W$zK0I2!)wt_r`m7wgcY$`1 zp_J!;$n$MFPF?2(56_8yz|H;-sO7!^7^VL8uJ4^Tz@c=KLGxHS*vBBL^6EnvK$7L} zYDu9^6j{%X|%;B(J7YaLC&aKHoMK z@@ZXT?)}9fRDE%6CaS=DUP>7>qgBB7!H3%ln{#1!s&5v1cr(0pIk;f0<9k5s$hKNQ zJnxS=EyaY$4=Y>X$qFvD9cxB`ZGWTD+50tM%(v~0=+7MZHTj->ns^yxntP>nIJ^~% YCj~A4wCW)cI{o0XzX)Z2bf$d!KQh5CSO5S3 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-07/inputs_true.dat b/tests/regression_tests/surface_source_write/case-07/inputs_true.dat new file mode 100644 index 00000000000..a6107d16faa --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-07/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-07/results_true.dat b/tests/regression_tests/surface_source_write/case-07/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-07/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-07/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-07/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f84a5c4c5b6d8cb4ad3f720e40580f6309998d57 GIT binary patch literal 33344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-08/results_true.dat b/tests/regression_tests/surface_source_write/case-08/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-08/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-08/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-08/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..1fd302a7cd429beaee0a203ec8e5f1da09cb09d9 GIT binary patch literal 33344 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 ud%f^f literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-09/inputs_true.dat b/tests/regression_tests/surface_source_write/case-09/inputs_true.dat new file mode 100644 index 00000000000..f4f6ebd87cf --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-09/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-09/results_true.dat b/tests/regression_tests/surface_source_write/case-09/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-09/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-09/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-09/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..af832f060cbfbfd6f25fd6cd2049a5bfef55e307 GIT binary patch literal 33344 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-10/inputs_true.dat b/tests/regression_tests/surface_source_write/case-10/inputs_true.dat new file mode 100644 index 00000000000..372e5c0116f --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-10/inputs_true.dat @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-10/results_true.dat b/tests/regression_tests/surface_source_write/case-10/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-10/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-10/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-10/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..1512d869298f2a8a0c86d1218646be2abe1ac77b GIT binary patch literal 33344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-11/results_true.dat b/tests/regression_tests/surface_source_write/case-11/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-11/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-11/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-11/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..0edb06c88e9a4ba368a72bb0a16b698553dee244 GIT binary patch literal 6408 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-12/inputs_true.dat b/tests/regression_tests/surface_source_write/case-12/inputs_true.dat new file mode 100644 index 00000000000..cb8f877416c --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-12/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-12/results_true.dat b/tests/regression_tests/surface_source_write/case-12/results_true.dat new file mode 100644 index 00000000000..d793a7e4216 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-12/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +4.752377E-02 6.773395E-03 diff --git a/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-12/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..ea81a8250956ba2f3da07bde3332e13963cd26d7 GIT binary patch literal 33344 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 S)+dnMzTq}Zl@F~h?f(H)kZg?r literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-13/inputs_true.dat b/tests/regression_tests/surface_source_write/case-13/inputs_true.dat new file mode 100644 index 00000000000..23bbf0455eb --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-13/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-13/results_true.dat b/tests/regression_tests/surface_source_write/case-13/results_true.dat new file mode 100644 index 00000000000..d793a7e4216 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-13/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +4.752377E-02 6.773395E-03 diff --git a/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-13/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..08bd809852152200e5449745616feef691f46778 GIT binary patch literal 33344 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 Q0?F+gZo^dh(CX6uAE<_K5&!@I literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-14/inputs_true.dat b/tests/regression_tests/surface_source_write/case-14/inputs_true.dat new file mode 100644 index 00000000000..29acd94f250 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-14/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-14/results_true.dat b/tests/regression_tests/surface_source_write/case-14/results_true.dat new file mode 100644 index 00000000000..d793a7e4216 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-14/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +4.752377E-02 6.773395E-03 diff --git a/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-14/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..50cddcc70cd27df73f37a288d86a906d7e7694fe GIT binary patch literal 33344 zcmeIb2{cu0{P%52CzT8(W0?x6h(f}(HyH~Js8ogs4N9d*lO#onR8morBuR>d=-QRJ z$UM*UJWnBBZuV*K^S{0C^R89*TF-i(bFb`c@3YVKIltfayXNmcx4YJ6jfE_{EOb*Z z3Wbh|j_sc${%2z5g}6*#!O!vc&1MqxGso*Q$54jp1U(%i-PC*BGuPQ>@)u;BzOZfP zy1@>0b-Kwr@%-`Q%-eL_bEi`j{KEe|{vYK5Ep_ytP}x0WH6BlCGjWDHd?`H_Gj%5;@NXfCYUGx;r!|E zHFZAcpKE?I@lQWfGwA83?_53e0sP2FC(Sr@iEA?OZ6=Q=C;$DgytpRwpYoC8|H{pi z_pl3sNh_va2;KVmbn7Q3KY#Y0^T}pm<~ZBo-#+U!b2GU$`JXxb+hoNVm=c}+*r%*nqT{>?x3hiw0eGlhS-pLprtKI>p@|IaN5@y35i|J!FR4<0o? zd(hI<`sDG`c9Wmc`j^iUFR@Jq4%0su1nEwlK51oo$dYa=@rHj%6F*~Te$xJs#ZhXV z+V(H^6E6`zJ7+R#r|Jy@1H(TxiZWBr80pPs!nqm`?|-u3n2GYKn@#*|taj?@Z>FEj z0grvVnfglP{onNew+A}s9$)_P)({Z%I#pqnF$$+YLBV1^8@j6>&jStjMLM}y8o(UB zu=pOfMp&6S?%A9zg({rbc2Iqtn9B71-C*j7m&N#{-|g!3`*d`M_3FAZY<>USH+d%( zs_xkG(U1u{SyEWNX+bHlskwOGu&V^RZG2`RD$@$@XdaU5u|v@>3Hb?2mJ5+MQ|&x=jqDp13RnX#2c_mNVXa7 zxh0wxps^MW=5`a1@0i`5>H0Wr-_&2TZ7~4?Ux%rjWLNssAWeGAVuiHYU~&s^FY>>t zd#@B`7%pzRv8W8zTovtnoTz{@9Y65Y_T%iFIlmGAr2ba<;AMs5IHZDWUgZx`?YVk( zxPB~o2;P2|y*_-j0q}_~WXnBM1LH0UYlHv_7`>A$kAq}UA z<8!UsP(L-UlU$S@J$OckF{R$8j~8zQsfeA!))h6dimhSm&bCS@`}UBCPva)k$aG8M z+_Qot4qj)bIIJ|BzvZ_@{}G3Fyr~sm%pk4POZ6M?`H$h_u8dgTIVlbC*gT-f>?rko zQ#0Iom0nK6wid3JWXk;Nq=d$o+MUu!pWSclH1^n>Z}+|=|BK4031Q>**};S@wCxXi z@wgVWn8qd7^A^FY7KIWlJDT8XeKXJ1ll{*gil^rI5#d?Ohhv6=x2J!re5U3r#Mj^A zEG9yzX$m4N6UHomrWHPBK4f@u4kI$id(KX9*JQm3vWkni^9i~f-=d*jnhPF#EuUj? zRUR$Kb>-7~y^`cN+(%P!y@Z-KF)_)p_hDntYlHHsYN&H3J(8^u7h1oi3&><$JKR!| z4y8;5o$fpc1Loh{A}@?=K$Ywm`gPvS&fy|vPfh=MdzMo3CMNzohliLwHBCXf(^L~h z^cb+w{TgOYU0uK=s8sQMVGXz@eqNE!AP>qs`%tI|n;~6O%V(iIV@LvgSQZewan|zT zrRGgc{CRu$h}l!qf1bln&6}9`^Be(U_S7^5k@ZR!j0A=y@&9A0$61LD3JzYfwPyz5tHSGKi-Mdc589m7hYdYkvNQRX;^ z*#$F7Etf%0o<&(}R+HyH#PSiQ=1ojc5cVt+;UE3EP-VO!AqA< z%a>~8f@@n>Zr9QL40=UV=x;g8qv7Gd!eRLA?VHF<_T+iPpZA+6(F|((&vRB#^Cl+# zJZB{_dusa6bHu256BB=)BTmeIGVL9w>{{*D1YHXK9&i{8QO7+Gqv)jN=i~z6s~8mCYwE&w=yXDVKBrsj95(3@f`&7FNx(@vi$hIR&e`(;`h6co1y;smOZh|HLwi^Zj%gYg2mbb3@Il{VNmS6 zOHEr>qATof>9Sm%owIrt&V#BicN(&~0gJIx1k%s|ue5lt6=Ls#ST{Q+yyyYgSIA%h z+_GRw_4)?RT>w4hUi;(we2H1@S@R!p*3xiX*Dc%bDO(=x>_)#Fu_()m=6V=-Q}{j0(9|L{^*+vSCH(%=kinKBQ?Vz&mSqsLWR9| z*Y`GnRfmxSOC5?pt)++%{f}m_xFG$pSwby*rB?l{)gTMDuF3GyRV+g;=e_n7+PsOx zA;ufN&Wm5e?YP-09KYJH22|=~ckZVQ!s^JKr!OhB0Is6_tM2M2L&YykE~IqV!Y{s| z_EJmaQDf190-^5N+v{~R_TcmTlgNur{OgzyoF}mPZLeOy1a#f)mgTb}7wik>v51Q= z0zk>M>Y;i&^lh~+3jQjNVyi_KER80|8_{nvGaUR7P_fa*?W1nTIu5c}2~;IyLfi&eiOY9Gd}Vx=%UM-~T=jucGNGfRIkycgVQ z+|2Vfp#fg1D3j4V(F(oR_9-i})qvbWC5{Z4RG6Cb7VTatkKPfNc+9OAgeHA2@U zR-d%4ZJIT3;{ZZ-QjW7^4hheIR3m`$@9p(4 z+Bkc>ZJOcGv6ht0{LoBZt&;C1I(EH z1x3>z=q{Ix8UVXd#m=vhucQ}{;sF2dQ|+kozx(Zvb7sB(4UO{0Ie>;ReL6V~5$A1P z{iF!W%eRiHCilTPeyfznygGr3O5OWUUZFtVRwdc(LoSf9UE};qc{jT1lGxSr1Hb(? zRdW!y)gf{&~Nt&Tzi>|IkJw4BR97|%GHLf?$!uiwn zM|~DfaKOTwhfjBdAJNWph1=;cExz}wmnYW(Gw;khaVp$XX|3@61|A@2YKjQrRem@0aJzM-#zPtnk_#eC5 z*)jnQO6dd3SlfZZTHOr-zbe3z`%uXt;3G8An0r2G({42Eh4!->8mzO{Q{5SR$ooxy zzTMILkNl=T3&)$2X)m}L5BH{B_tr)Gq3%N7%$VMG&{7(>N9$t-h*QW~BA8kP#?|V0 zF0<@LU+)+ZlDxWbR=?3|Dz51#sO(wU2_8R>Pz>#ygG_%73f7&KaZyeB8`ysKhd^3e z6L6l>^T1!E9u6d**zL@_6_tBl)E+IyP2v#8quXihdBUX-+$qF>&E$`y#8B>wj4@zO zyMq}`va_K^R9xhH;dT(d{m7AqV{M~;`%bsIkqMDXPg$QVY0~JO&vK7i2aWd4d-v!v+F4Mj0iD;|d}=D31E()fju&s|R$cXFz&0xAsH(hdf%TQl z>@OoLLF$?nUhY|ipaITtV+qwl{gE*V%MgKC{kETm6JXe1eNFfGx@f0kcjqqU0T_G+ z+n91K8{7}%Mqf5|!e-}s&RZ4!peX2-`{@rFs6u><#eARH_k&GnIDe~rO#dT}84YL2 zE78NwEuDD1K}sJ9v=?$QVLy5wn7v++3FbuTFMP499V9J%wc|m33FKI`bdM?bR&=qs zB}+)_>~U>A!@<`nj`*%~HB@N=z6;j>Sc)>*)a+d+a>og0mB*^=p&5G(>Q>gp}eBX3YGo7-dCP zJ~+bc?Z?O434Y{oHV%}VD=99li!3a=L41PU9YYhK~O`%mUMgZp&S>hjdN?K?kxs|wj(xii)(>l zSi#$O#x;;mco*ZRhB_#vdg$As2m#b?!k=eZ_j-~&xD8VrywAfq9X)%MA4lfE*CAO~ z*iESW4A^L2<~hpw8n|J=@Y8*|I^eSKz`A{nwa|H=?BRue06pNt7FwS_`+gSgL%f{K zCin&)%nbmzT}fgW-dyw=2g|Mm@0s_Z60*IJVy_-<1J!MEhLA4`(D%td-Nj9hh6FiI z$okDb?!|qG$DMXvcB7>iBZ6~mLtE{&J}@FLA}h6)N;UyW=@MjgX$eqRl3K(gQ485_ zD;#?MOcZ@xS9ba`B1iHY?yD)kk>^*F=QUnULP~w$iB!;!<%_!^&Wk?3jN;*60o#?^ zRykDUzzRc#?F=2QK-?`ND_KJpRUNbWZjwme$G~lv;*j@argNQYKmC=BzXeF=gZ1;2 zP1_0>5Xvf}72(SDAnhQX#Ieoc(EY)yLzS`3;G*>Mq@&M*kX2WYANcuAmt+qy4ruj! z@>XxwNE){QN8z<|>5tdp_F%k%akseAzd`}`P)TL96kfYtu2%M?9k%Lx?i1$TfQo8) zx4Ir9ufyVHKV=WCo^QranBeU_>eq5dVZGN=c<<2!bib0OEO@^GM(iu$f48q1o_@c- z?KY(b2DX=H59~EZFZ#x~ppVG9TRgU=IJA1cewh!MhH7*O&RaOaaWM7gIAAFB0R@1XS_Lj_0(w8Q21g}`4Hoc zR?nxeC@JInwG$joj^mTPLG8DZqQ)dmJ{YA z%Mt}A_3?{GKCQXWQVPUF`X5_Mw!&kt$1{1yIZz{5Ty^-fBB^|caZRh|+rB8{`D)e) zY82`p4bhz_pt%LYU>dfLFTQy#B`v z&}4&mM`Rw!k~qY6hgQ!QqVw_8{jNEf%Fg*uuW*#%9IV47>cyh5M(CI9c|F=T5o%XE ztuY&Dfbp!JV=*cQ=otyy2vrYq{UQ2|R?inUZ{8WEH62io_uC_PpB5^|%l+O)BP14B z&sW|0DZCbjo3D~=%xZxNj@29!bJWrIv3}R?>5=$iT)jg~QT|3Ttj?A^FCn(0w7Nkf z4);fy#!SeOyjbDbP1NTYkf5;YPP=;*;HSna-7t@@(EaWLo$q~ZAYP5K$g+47x?r3B znTaBDTocP@Hr?Q!rTn}-9yQ=iW7^8&g01i+$9l&m*;E*? ztWi`b?=y1CRZr?0wv`kIM0;p;gL00R*~07TkhXPRF75=q3vV5ho@-hP*iNUD!j_!@ za=rNTk0f=%2es7PQWEmbp5JWSCLP%w>z`w1~KW-Bj+eZ_0UpM zrsZRA5*%&6G9}qzUllEXx`WEJkFI5Rj&-zv$gsncZwdouDNYb@*CaA-5U^xvZ%QR5K<6Q?x<{shA?xyzJh=4Ko8MDMBSUa?7rPR9&ApM!A zN~od}EUaQA=-OR>3Bu1KhBG{`A0nb<{QV9%gcCHOX&8ju4GK)29?{_uIKPYUh%{ zcjXVCv7cq2xXy3miGX$}oX!%bYgP$anoLkIA|KV)+Qu zaF~Z%2ldw1!WkZ7H`ZplBzpq(1e*PHSX2bg7Vg|$9n%5MMs^$tON)X224C(?%;QAQ zd>HkqEg-j}#PSiL;Y=9nEo3(u0wLR6wlwxopOZj*O+q)UecA!Ag^A&6OEDN0^^C>i zTOp7Mk@uNnt%i1r>ND%Fn!S8PXE?ZxxE(=jZU$N9w!#T%l?By4-5|8sM{jOiJGh#; zlh1X1IdqcA?mo5F4PMY&5&2~o3o43sKFQ)GkFSaC!3r9CW;mF)kAjJ&eirl->E*gL zJ^`<8&TKB)+yGh5?}~YFrxdO?daHv}*TH=@d-EcAQ1pAA&aoH4@fO?Q`i>t`I(!&>nF%MVqzSK({Kde3g}cz#K3yPbuFg*Xf7h_9*gjazC zuIm0q`9?Umi~HbwzD6*|T?N^Zmk#R9pWH~vS3*yp&)>jmH@iKwy1~_q=M#f&(IMq? z^B*SV)&QK>t+~yy6xM?zzFd|aj2~g0`LEJVg3aKSkAm}iZgo^M^gF%m6Y@F*(H>ge zpkd8kx)V%MyqvI-n%rXO#n!zRb|SSef^RI4s)-41y#N!0FCP(bII z$DX}XM2-Vu|AXrW@fZxU-Ph>v+yeyTudyv{9>MLwxX_(E!iTEB@IHmm#_V(;5%beF zXn7m-S^J@RcbOWhWuYz2c$w@sB8OHtXk3KpVU0P|{AIE}Ck{ViM6zO3KUp~B0#Vr{ z|7zDJ=)B_a7{jwVpcL|Jt*ydl^!mG_+V?YLNbM+bUV`feakia@!!OP1-{mqv=cigc z%#5Tzlw6u~r3NOPmbGUd&H@IbD>iI=)&jzp96!6HKpri0LOZ2ClkFkKHLmBw`vsS- z^z4T#fA9Z{M+gRdie*54==+^@QK|#X_qZ}1$~FPFHRV@XT?)YX(o@WPJ*83G5dO6h zLgeuqk%Q~`$l*=FjGwVLXLC2eP@d9r+r)>#_?`1Rg4sKu%j#P!mt)^S8P;Rs4!cXi zdH0VUUp2*1X`=&)+F?p0zu{#*)$ZVWKJxk)1rzt{r4)>H15Nklcd>`t!MC7L#;tap zP%Xe29qNq(LHj9l-iPD@mzdgRCR}3Z;}Hwnwe!jGM&!(*=R@v4jF5hbP5|jOt*ZLj z?VynE;PyucYQd|f3k9u@OTe?+JkCnRjlfWiF(_ZQ7AcQn_7=-TNcIrh>-A)Z&74s% z=bne2%PX3J;2`(X%~%Fpf64Al_VF&*vr|%*V`(zrec*AvrWcN`J+gHMIr-$-DzDt(iwI5@kZRkyY z_o5c?P|I6<$od=T(f-xP8c_>`hga&43aX&?h7ZnMGa$zSv3wL}0Jx34BlH*46er;H zWeT!y^`Zn}{RHsTGjw$~UEAc7MNIY|x~jpiCGv0f`^3QaR_mC$ECCvJT1NNFm$kFn zgX;!yj)~%V473fv%vBX{FRWz1aGqb;Xu-?oKyZ1#A>DJ$61YDu^67|K2XNxq>crP0 zfM!Ruus^#*ex8ZgUeoFZ9pAMavwclZ&3}EWM%a^_5n$NBAS!fuavU;T6seqF0#YnG z)wE#-;QslYr9ff?RI3|J+?f|Rt39}GaQb`We<@g|_!ZWlL)B1m^i|UJ(*xj5)0T(} z?M?8y^cRERyOnVH=sOYNs}(>xEs*D9nI_6Yr_uT833;A{`)H~?z;%Q8wcU#y+3qZe zhaY#|i)30*iI)?$y+%%8Ahi?zERfvu@O%om-}7au+>Z>XS+jcH4mk$&jy!vWogjJM zh_}sC99rGrK112D_$9sI?z}^N%3{?0P0Sg3*syq<2B^a)ZkJuH!2Ho2bBbgw{CUA~ zzuQ&>J!?f_7`nkl@*9ye^9Js)nIi=;KKUr@=%l{NF}UDN>JkRb&(5{$jBpc>sr=yM zc&;8aUyR^e{yZB*eCqPa%|Ot8%#_hrlDyAEjB7wcP#ON5#y-CX3@>sI5k5g(kHwB^ zU$Wg6eH-o_>DPMe*8y^(s-GL{7sH25PL{#k2zpR6J6QP|`FV7rJqQiQB7g5LLz6P# zPR~#kev>*rKptkKR2TYVll#Kv+qPd%g3qkh?Tyjz0v4O(8_h9x^hYrILX?5rFA(Dz zrQy&$&@JA);5*#UX?*?14(dD$JL$bwZy>b^#>=nn+ujoepI7Db2~3WIuf&UMRI_TL zx*GJm=A37rwR}`*IPJUX3qrfcV1G`}Sa!)AZ2D~q7E+4_*w7V%uC==-9?xrn`ro!J z+PI|-%F9UJcfDqYWcvCxzPs!-D@TonGm|gU{cNKA$j>e)mhxcz*y>t{`9~_RWoiZd zE}Vk=$1>p&CbrL_%W44sKypgn4pnrJGM~M5&+KuqnTDe^swU}CdQ*@oYzfFuc z4H}N)LW!>f-Au@_%*4mF6a7?s4vxrUHk0F=%A=hcv5h4#NjCJRctH(xR85Fo!zzp- zl7XiLkBZD%KAJNed|b81em!Unhc|sxc_ED&(7$qeWxS^eF}L_JC0`aST|@nGj=6lW`b5=zF$4q7e*L z<=k6vvk_jmsy==*D-iZCmTkmJwxjN^9S3*KBd=c*+XL-cIDTd;*wTH!z>}>ymHG>Q z!F^oI%}@TQ1?{2QnM3y{pJV!V%n;Qmf$@RveKJYa$XMvEANs7?BzuUQ*>r<8Hw&^J zseFOU)QflS51oTqZU_2h1Z?!KE4E5(N%kS%epXipqN!LeF zmOU39(OZ-Ce8hO8)eYux#&ph4ras4yNWB(Ge{02n1;*Dt8j-yX^mQ(}?ThFF>&t5| z1~q(!t#w**3K44PcZX{V9}39rC~>@l>jufiL_wNg?6`H)wE(nmSeP%ETt9HRbWicZ z`Wny|65Es;X}oVsG%}dl+`#_cHD%#PLB7{Q~fEe zZje%y%aj|;jNtJd`XRM;>ybufu3nzgNG za-7mARLohs5#YH;ZxU(FPD<{gNgRg>IP^0g~i@ja7wJJ0}81aCim#~ zfWrZY->#c1r_UcN=w@`0YdY&se}w%Up++t9lYuek zTA|n3-0001AF*xX<7m@VA|{)THPRS$9dmqEbJIP5|hj4xqo8~w2RFiqCeFP zl`^%l{%w=L=V749cxquD+>m%r7gOzdb$ZLQr{Cj&w|b$gIa@oNmlqU?Fjm5m>n5_% z$`eSoWr)hyV)A^BSWjv7eEUBJjC$_R2D;4c8+pur|GrdA3yUCYR(VvNyIJky&e_L1w0gd7Uk~q6ki2> zcRXNO?9~L%?$GkJI9CB5dY|#=0i5WTe7{=mo8eoAGTapGetFQa-HD4$y0ag zyc=6EdhL0LaXHL8;pABOsT4kl8`5+zZ-E)gh=gx~9ID?E_RyAtpA>IIj>&W&Q~$Ep zY`^H1Le&i-q9$+cU)$z`7p7Yl=ZD3^BP^nd?U^-TjrL)81@l%A(|JEs_bmmD%>Vh> zZSL&44O1$Cn4n;;68vnQ*BOw+VT~Q zDL?-z;jtU-7>|5yltH$KSbxlj*-xhbRvehqaHdZwm|gx5kPa^extV7EcW#eEH#1dd z?$Z_Uut|TigKsnV>YT~Nbfz4B-43*F#+4(Y->hF%ts<}c6YV)b!;ujfbT}=N3*v2L zB~1935S-h5^JkBqc>-)@3gfyt-m&OW}jpyABq zi(FWGBk+v^Bl2oO|DxoBad^(2Qz&d8ANX=cV~JW{p~NE>%?%|TaNWT)IQLi`ViwPaJE&`(e4;$JxI?u52?Uaobi< zDqdTuo-2l?1=KpceL~)EB+eV@X*jq&MbFFSw<#4tCV_C%d+UclBFnXHXhaJT4d9Ma z^2i7LvS!y;aaRL}H*Pt{T(+YD&o;eUH9~$qm}n1qy%L8~kU!1A=OZ`)x2Ml`Zj!>Q zW~f+KlYBL%6XYfw%+{*t0E-W+s&Alt0adqIM{cbs0N)!9+tp@Equ&Ls&W(4F_p^xh z;IW5uxKC?+ZmlkYFFSa1#Eww+8!@Lx^1$J4Ej%%hqF-v53)K&u^A)vdhRi*O`-?3( z&@_?FTi0un*ZGL;4$j2ODJZGv0l#(|n6V3E=0Eg+$Dj_bFy&j3wzm)_NUEQq>}iLa zzYjKgyXV15&1j{CNsH0u%GXI2XUK6t93S9m+@8VAN{8JCsQm%LY~prDVaFU~^JqHr z*zq9P`XFS^2jMP|JD~JTP&yNoSsyucv>c!(qmL=xyF=Dn68kyaHk{*f_xV}(BTaze z8NF9z`d^`8;<^*d`@^&WW;h(e;|-|h5qg} z1YPA=3YZp?_ZM(qO^xGeb#{M?gWG}oZKnK@xDLMFcI{&5kafw*?#noM*_8b{!l?@! zTV>z;@bMRT>VvRV%|tvf`fM_sr8pNgvEn>yet&j71+KHhZNxdjO!KAJ>o8!cj^jr& zkIX@`4ldu;Z`2Oxq#}Rr>?#E>=4(vM7fAY=Q^)GQ?PNc8qE2Xe7?rG0qCqfHves6HIU%Uuv2}lfZntU z-8VKujyJsQr#Q4ayTBD62Q(g2>nGwhVSXsLW)8O4i)CK*xoWWUeu#XeXAM|fzw7n} z&IaheajWNry)x)!maqkRmgI3SF|Ki)-SqdSqjS}L5N)uX0h_S{`?*8FFXCc7khRFa zc;`whbgjPZf@Rl0ZUJYDFLn{&a7D`zHWhDhf%BSfoj+InLj^wnY5<#9yk1o;?1jZb_o9DH>X+Z8ySm7Y7K7bO3^&?) zmchQWU7r3%s%Wf})!Og3X5X);)!9)7Y)+I6x4slboEX_4yg{Z-h~)%i>EW9{tdUAuxzG`;rU~aO0LA; z7wCh>Pql6fFKB|%Yvyx5d|3@|g;kjFMVA7Je*YMEwO>fy%8BYS6_hj{CHjq4XUALf z&`_eF7)E`#=g7O0suRKd-W{oo5vqVA((B@H50?U;bo&#>d0L?0F&B>U#6W}vaQN`K zpCWPaHhL=FXmxgUb=>1T1%A{x5I?d);8`q$`xLa7#l#jtzF>!}0NqAt{iGwbHnu-GOiDrTaa3P1eC2(1PUYrKcq63%)bOW(bDgo?`|sqy#364RxuF3Bd3>rxU|5Az zK6ss+iUV4m-Hbih^S+0t1P=~@bAn+={+mu{xjIvoZG98`es`ttjmdqpBddS?sxPg9 z9XqW#TeUW!?V6?gd{54?vDr{R9avepTPtsd`If^VRg7tijcwT&C8x z<5>=q-C4q0^k*FytEYqH;n^^{g;ry6sx^O{>Z5PadV-L2&k9^c5s z+zoO3>1~ilrABXqL^j;x6ZstMECW|m983Hc)*{{FMea`<$Z<{NETgsr!~_M|6Ju1l zq@xdhR}GCl`MDNEh33%hv1kG|l67n7q9KS3xx7i&sR4>AJN`W9%YX{$q%I3hBDV*` zI1nUePfb&h+!vTecEC7XVzTnJVKozG=)T5j&$@Pih@1F3Hk89Iw~}MboRjO$D>ky{ zakL_119Vyjx#pxeAaaDLc@q;9?9|u=y22+tAXy#Nh)67%)B|1O91LlM%RlOT34h%H zik0F{V7}>K!F7=@uVYrCx0ZtoyH1n)ZDPC$6SJqLDOls4YG_g!1B#=aeGh!^0hL}B zt`A+BpqcNB<5znFK~J=nRzXAq;E|Ow_4>R4^}&h@lrNC=J4B8MHSg)f;piYUe)xNz zB$>bVT%p(isC9367@nR2#?N?NKQXEQ6|TEKoD@+67{c$n^K))TbMCX;+kJvOUm&(` zqBNW)pRow7^tsr_pP$2HIe)Li8r)uVRC;d#yl8$sH+D@Lta(=Gl<(OA$8QO%b65(Y zmdEpUF#jZv5AeD&RZmyYaLx>e3EeC$g(n4-)#Y{H;_WC_EL4HLhs_TaTbeQ#fjX(#AENpL)umf}w)2ruAm@anXs-<}BoXff(1~{}D&x-#J** z{>SFo6eeW;w+;vGy(56>atg!x(pGp$+S$f!pc)+DPCR632w_ggf`IZ{>ru|i{{EI? zavTuj8rQ$!^TrsK*XJHz$id4W(eW1D#QS;}Ea{s_JyR48@2&A4dg9jwSSRLOF6#LL zuc~cylbldNvqzn|);uHEQ{uQ+lExlK`snS7-xx4vfg-o%6O-$$|GX&3H}L`%L-ksy zp?K6TM<5;+NPjKXWp4%fl4~uJ&*`8IiY>!wCFFWaY)5hZ8;+|n7VwXlDMdwlraHrN}G z{!M~!IohOidA=yu?CXhAIAG?urgiQG-O>)Q;aO;3&zC}+hv_c7$Lyb34=!xI&l(aE z2@-W~+ZAnWhLyc9GE>!`BH!qSZIGwr{)gBe;5s`TXZ9qzp~Afz_ZN2P5gi(svNBIpkv(Fc1(b*wuL%;e;E@}ix zbTQ{-&oX0G4*Z9+&QIzmYCi5sFnSA|JhCno@+N}?_a`iE7QR5R{8K+hERW6F&*3^d zyiU!O3+ALAecU)C6_zZrPhc!##>$pEWEe45LKdkxv6*w8!GjwZ?usk5!-p|>bPL|A zpyw2rvlEY!$02y0Q|&dbv%|0XZbFnelw05`-{R2f><*j6Ro3Rc2QdyBN?z8#*UQVT zbsRQ(mIB>x{!w>Lvw@D#8uht7ZNTc?mgb;yQfOXUT@0Jiiwc*MHk;1A zzJu%RaL!B|ARg|l(mjnSz~T9i?VFvxgN}%pJG_b=K$JehXu>re+OOuu=EW64$$~vu zTJ``f+r*+idV{>*NbG-bogH3(nlFFQF_;hG(gwGpx`n^}*0bvLro?SU@cxaxbASBG zgHAiD!g)B`L0DtLBJ-o&$euUnrG5Wj#-q5-4(HsmivQZ^+5wMpA6%j)@>_>;{V8@% z^i>{|+*;}?5Ks$Ko{ldM;%SDj25mcyE`LW-pGv7r=#a;W#P)zz|JJD%*8BZrBaqe^ z;#z!p@;TstUKB(=N>SFc{|!hzAzRgBo(6_HZY9O<=>q234;QQL=tGbjgVt}_j7asA z7zeoi4Yvo+mvx|n!hf46x0A0#pp^-g^Qc&UV7PG6@=w`X<-G{&`U_y8SD*Y*MHO?J6SI z-@E+)_v0EK86Iziw%a|*WKea~vAJRBjRLtnAo^`4BPyrc@Y8Y8iZN)9na6uvVZ?CG znpJg=9`$9zYr0ZfHS{WA(NV{vgJNy4r1e?K_RGp>*#e_Dz)kMwh#Z7Um`*s{^7KFG zO)4dAS zg>Yk>{iX-y8&Kt%0@sQaWP6C?5Y-tD`9Q(A&X`0!K1qkL*%qY6Kj?*%N7Jlg9@{zip=B zXy-iLrQ+HL51+5|*R}k;fBMCFUh5!V0UTBswYNX_8Z1*jdUUl|Cw!djdUN2Ms7E3K3*5C^-&p6b9goZ5>Pj9qA)=%FSiA5Ix@#qGd zw#jv8Ea~yhZR=K`8D0hf)edC85z9w|#-8}9OZStFhhWGEms9}X@AazE&zeuaUaN%n zJ{;MSBUJ$vgWMwCu{6UZH}-0=dM)&;t>zvfda~b$_Gry;@G_e|rC_3QCCOngs-UPl zD^h7P7fY5j;^U)hfPp@rcnuzuz>$a67gn9EhcBHB(Mty;P&&TTJ5E&3KEBqbvFB5i zrVm%>TzK{8d3~D@-<~TQB<4=T~H@+P0KhOax{K~w-)QVxM6cy4F0eUeYfeyBS&L$zfJTTt{21G@ELoM zE2ed|yEb$Jts{%e_}G~+@12sp?w?u#U8Ho0TAMr+2of7#=TifB&6BrvezF}c);<_u zc8kox^PKYAEP65Ir5KPa{qPHvd8Jg2R34v}^AZ?k{`R@!>|$UV5= zOr^ot8g2Cb&4@tLwAneddNJl8o)wF&sQ(`uWPNVP%D4p?;OT*&-lP@HAdtTy@>Xg+ z*dAT83=@q7pR88C;>guPUGMDKS;I%(rzO@?TD=&b*s$@Z{f!X!-@`z&D+xBE;74%E zA+|*o;A4HO)JygTVDHoS=+>mJ>t>wkwar`o5$saTjR$;|q_`$>X!T-83O!=vHV?w8 z_xJCO9U8;q09mQ=IPD5r4<9OR-KdkF2pZ&GAWtqdgT&?x^BUo6$Qg&%k@YSXBo1+W zK&uyHV)UiVWn{qAcDakXb21>fT>~)oqUPl9hTLPG>}i_jB%90o~jvJ(f~(Gr%;B8Jpo?hZ?vn0#lly$lf(>s%vZE zyQAk-T#9qx%POs>%1=@Bw&q!{1@SBkaBjkH2^g{*v2(Ze^dp zpY1qV=S}47o+%&lfr32IioAZ@tp%(KJO2X)WJ1BBm)0KNT7lA8Ap_et72tiD;Nrcm zxqxfxWR|tuh7)1~yEfhH zf~P-s3;7Kxp>|B}Y#J*$NF1WyOvr&o_vk*R*`6^07>=cBid-(Ca(=`t%PlP{1)mJO zhB`Lp0*-@Q3@W zeAtJq(*C9Iw17N*BlbTBXgGf>4lHOmb;+Vz4&LkqtlD-K9p*z+zYXq?o3nlE4|v({ zk^=YMB=~E?(%Z?CzbEl#-SMLyvLdM7Nxwz@9<%$+l7?fDEfMs&p&U3T8XlD^hyv4Z zQxGG!`gxDyCZEqZcWG^PR}SDhJpY;Lp*HZU{Gnb`%qH}#f8(}erR4c3v3&4x8yQDI zhMo;B9@#MfUAivBg)FIomuoIxxn$P?qW2yuI6(gj4tjocVmgpu@pR{%WBd zSa+|-rYjJjBF#u9w=&sp#PKNJU*nt%b)R0>yT8CbVgGY3BlO7hbqc1vXr0Es6~&-M z{{f4`#XMMcZg;@du{LO@QeaS#VUM(H2WhW!JUnZAfHQFp%DZ)4`sDXF%9B7AWLT3H(~1nu-Dn5R%236US?rFaY`~Ak-Od& z?O9<=vIoz5YTS#@RdIWc+b{XPM4Sn6^of)xzD(UeL)a^wE-QGK0Jc>c`%5->!8>lN zkMbCH0GoYR7Va9`hL#N0busSX;1cNy3IJ$(D%pEzk4!FP@v#9^7B#_@-d#h7RrXdYrO%3TEF&$8E!X zx$$UBPp}Fz5*`e6jP-xVTc)2G|0&S|aEJXv!kwj)x+mGj=EILCe}{IC)r6|@8nlse TdaX|&xqZWJm?|GyUE2QxR|Ib8 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-15/inputs_true.dat b/tests/regression_tests/surface_source_write/case-15/inputs_true.dat new file mode 100644 index 00000000000..c775f2f5276 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-15/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-15/results_true.dat b/tests/regression_tests/surface_source_write/case-15/results_true.dat new file mode 100644 index 00000000000..d793a7e4216 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-15/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +4.752377E-02 6.773395E-03 diff --git a/tests/regression_tests/surface_source_write/case-15/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-15/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..3e15017a16626a9a66d903daf1721a97dc3c4058 GIT binary patch literal 2144 zcmeHI!A`kb>e1ZM}pWvtXS2(*n zV-{FqJQ`U7GyCSvv^#xM-kbG{!`w*@nD9$mAib{Y`T#0ae-AwT^utk*OrwCyAEUE_U%tM+FS*YuY)<+YG#NrL zlPhTJLdBJlA_{+qHeL>I+!e9D?9uB+#&V!gKEqSURfFn2R2P^(C5pyrV==be8S$3i zChN(z+>~hijMuX?@|VwuFFUogifCgow*DmHlhWC#g>OQ(+?PS3C^eI4@ol-iVAPog zJ%137(+G2#mOK;6qi`|`D8nR;`+nGiOOs&n&77zcCt-K=JyR_U--OKEEUH<~jpI0K nusoVGSFHZC=n6-qeo6M{!~X5nbzNWu)_z3uYF=;a|J;Euu@!3E literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-16/inputs_true.dat b/tests/regression_tests/surface_source_write/case-16/inputs_true.dat new file mode 100644 index 00000000000..969a8f31ea8 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-16/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-16/results_true.dat b/tests/regression_tests/surface_source_write/case-16/results_true.dat new file mode 100644 index 00000000000..a97be70caec --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-16/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.496403E+00 3.891283E-02 diff --git a/tests/regression_tests/surface_source_write/case-16/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-16/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..49f67e8205608e848f0130ed65b472ff98eee046 GIT binary patch literal 33344 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 NWw4>&FGk7_{y#M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-17/results_true.dat b/tests/regression_tests/surface_source_write/case-17/results_true.dat new file mode 100644 index 00000000000..a97be70caec --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-17/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.496403E+00 3.891283E-02 diff --git a/tests/regression_tests/surface_source_write/case-17/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-17/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..35878eecae7d7fcf08cbd53e5db8b2d63e3177d3 GIT binary patch literal 2144 zcmeHI%}&BV5T0#8D{2Co5aNkP;K0F~n1JcQC{YjI_$|FTVm3ulb>*LW9kI~t|FJE8Zm)vI+HYfcHnhYVB z$rZG9q2kI&5rscO8!v}9?uuAn_UL6JV>wVLpW!LwszG%hste4Y5k=#)u^3zKEAf`! zChN(z+>~hijMuX?@|VwuFFUogifCgow*DmHqte-_g>OQ(+~;AUC^eI4@ol;NVBDDn z{njv^q!H%$mOK;6qi`|`D8n?32d%IVmnOmDn>kS@PQu>!d#0Kez6qJRSyZ!}8^>|f oV0koWu2}tN(G`wJ{gT|Bj|R6_*L8svSo;ypt9iYx|8obv0JQmP-v9sr literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-18/inputs_true.dat b/tests/regression_tests/surface_source_write/case-18/inputs_true.dat new file mode 100644 index 00000000000..89df27b475b --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-18/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-18/results_true.dat b/tests/regression_tests/surface_source_write/case-18/results_true.dat new file mode 100644 index 00000000000..a97be70caec --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-18/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.496403E+00 3.891283E-02 diff --git a/tests/regression_tests/surface_source_write/case-18/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-18/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..36da9e7a7f7307e5af5221f380b58750742fcc41 GIT binary patch literal 2144 zcmeHI!A`kb>e1ZM}pWvtXS2(*n zV-{FqJQ`U7GyCSvv^#xM-kbG{!`w*@4&2rnMMJZKSpN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-19/results_true.dat b/tests/regression_tests/surface_source_write/case-19/results_true.dat new file mode 100644 index 00000000000..a97be70caec --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-19/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.496403E+00 3.891283E-02 diff --git a/tests/regression_tests/surface_source_write/case-19/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-19/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f4261451dd4832b3ebf2e4f014543c850f8f35de GIT binary patch literal 2144 zcmeHI!A`kb>e1ZM}pWvtXS2(*n zV-{FqJQ`U7GyCSvv^#xM-kbG{!`w*@I+!e9D?9uB+#&V!gKEqSURfFn2R2P^(C5pyrV==be8S$3i zChN(z+>~hijMuX?@|VwuFFUogifCgow*DmHlhWC#g>OQ(+?PS3C^eI4@ol+%f7G4& z{q`UprxE5fEqNxCN8w}?P=-kw2ko#AmnOmDn>kT8PQu>kd!||zz6qJRSyZ!}8^>|f nV0koWu2}tN(G`wJ{gNEahr#XDbzNWu)_z3uYF=;a|J;Eux+!Yo literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-20/inputs_true.dat b/tests/regression_tests/surface_source_write/case-20/inputs_true.dat new file mode 100644 index 00000000000..c3c768a3da9 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-20/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-20/results_true.dat b/tests/regression_tests/surface_source_write/case-20/results_true.dat new file mode 100644 index 00000000000..7ccce7cc3a6 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-20/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.149925E+00 2.542255E-01 diff --git a/tests/regression_tests/surface_source_write/case-20/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-20/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..fa8a4e628728deb4c4d75b03cd31f0aefe6d132c GIT binary patch literal 33344 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

l11eivG9^A2FF@o(kK8lbM?n}(nd+t7C#e>`?H zC6C)gdGK=|xNVf6k)K$De&^fTD6DwFa;qE2&u7ybxseT<>-PJ~OtzUXh^T>XbIBxI74S-f2E^v>Q+dKd( z-#@rNdTB^j%8i$t=S}3$pUa_fFekOBW2V6=uy}!eJX0wPR=U(7 z-GrqAvaX#KlQAn0?px1zPh6=DK8ntz%zLklo>gGUN;pEc1HA7~%7f?O-XZe%u0Ozo*!suD$6BDRzI&-Os)jl?)epW= zAajWQhgQ*4brXM0B%Lphy-S|1Um@@I;ti|1A@|)`%!c%ldU<||3O|?Dx6d>(8*WqOWqHTV=3xy;CZ86jVurT+b7$HKF`5_`Nw|s zCnLDt5RY-ogE`f+u!SD1bE?i(0mBEu@)5VH!NR)jch+;&L*ETsZ=K&Ijb366otJAp zb3et;mrOSht=${GHkjXWR}q0>(fFnYN|~IQ2FaS-E9c_x}14*rw?z z_1rEB$ff2O)h%oXo<3{uJkAtEAAkAf(63CM{}BBFeofv_r(&0SeRb0`8L_nrx=$i| zi{LBIGqWl-cfcFT`Jh@L8Jz8i@!WGa8zu~T9*`UCN028cn+1oINp?V#ho7u4+8q@u zi7rN4EcyV0Yowc!Tou%aKSXt(h}%aSBCI7>rGYt>#|0UmPt5=8Uwkt9)`+BzTOMvS zA?J@0IrzU%f$Ob9HMHmZi3T90J;=TA(nS8|KQAgGAE_vFtM3g+IWAM#ZJ7#&+HWVu z?d$}WT8|d08uTK_je-4dT1`mu;JTW$H~f4FSu83N+s@z9HnINcka^L@?#mc>*_ib@ z+^G{BU1{I+=*bs&@`H#?^>`dG`D`|nsW=-ov*9{p`C#V!H$3kNkAXg9AK=U0HogCY z%<1VCe%Cn);+gElTQ}vwRK3W`+wyH7*3UF4+PVPTDhl8%PZmS>MO>C*yGV}Th~v+0 zvLF;@CU5F&CPqwkyPK#R7b7zDHWlfIF&8u@^8D|+RPs}BorJmj|~wGUAq`g5W9?{BFuj6gZNL9d#G^VNu9$kVCl znUv551WJRnm@Wl?TgJBTL#$;$&y;OVjVw?lj4xaTEwQ7Pa-G};$tU1xtWt+FkYLL%o5&bC6 z#O<1|`jN3jei4lPaNm*7@b@}}&%47F(Zc0$SZZC|ouLxom1ckZ7;iHaI(nINEWsaP z1)N^|ZYN24BXa1^N#J^06Y|wtazO(~q(q;UIm3cgItU!fJU4NUp!(y^c$2rl$vyL8 z0bdfB_h8)m!2A~omUr^Uu=P8|47r?AMd+3C+7vGp5-?N7n!YiZCu5S8N01^ z+PSU`AmV1ej`d}*)3x|03)e(`-0}?^xtuM?Xg@{MD2F`$5IMp$4qiCizU8~mb2@&* zGPjn#mv}!4mWfFK_7kxncbA*=tNv_Q&A_^X9}xQw-u}2ec)f#x0}F=@`r+ly^RdB;s^O*TOII%1wS%Z#2lMwb zyn+L_zDu7dErLud9+YH7uR)U^-@i5cEqR@fs5g8)jE;jjGOoN3S;>Ow$aZcOIMNMI z^hc*3C}@MthxXQnX6HkPeH8*#!r8F)e)oY+e}FD)LNa(Zk>}fZn@{Qu*B8!NaOwJm zScB>F=-5|?2DNql1krQ#mKR@3g=pAuYpPl&%*ruecKOr?sM+yqVPmcw>Ma%akohoq z9hTTW!~b=-c=JgcN z!aP`ZTCW_J2gCZp`^%LaL03V$$jjm{V8y9JjBdf5P;p}8r2m2(TDrw5?4<~KzCh%N z;{aNDCZaV0p;&S}6o$Tpo@z5LHUfGpNxulVi;;Mmynf?c2iyzKWiRID;V zZ&Me~MeG57{;! edE5?HrP?A#ixAW#=z`r^N%DD3qCE8bS^o!D&vB6e literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/test.py b/tests/regression_tests/surface_source_write/test.py new file mode 100644 index 00000000000..f144eb82a73 --- /dev/null +++ b/tests/regression_tests/surface_source_write/test.py @@ -0,0 +1,1125 @@ +"""Test the 'surface_source_write' setting. + +Results +------- + +All results are generated using only 1 MPI process. + +All results are generated using 1 thread except for "test_consistency_low_realization_number". +This specific test verifies that when the number of realization (i.e., point being candidate +to be stored) is lower than the capacity, results are reproducible even with multiple +threads (i.e., there is no potential thread competition that would produce different +results in that case). + +All results are generated using the history-based mode except for cases e01 to e03. + +All results are visually verified using the '_visualize.py' script in the regression test folder. + +OpenMC models +------------- + +Four OpenMC models with CSG-only geometries are used to cover the transmission, vacuum, +reflective and periodic Boundary Conditions (BC): + +- model_1: cylindrical core in 2 boxes (vacuum and transmission BC), +- model_2: cylindrical core in 1 box (vacuum BC), +- model_3: cylindrical core in 1 box (reflective BC), +- model_4: cylindrical core in 1 box (periodic BC). + +Two models including DAGMC geometries are also used, based on the mesh file 'dagmc.h5m' +available from tests/regression_tests/dagmc/legacy: + +- model_dagmc_1: model adapted from tests/regression_tests/dagmc/legacy, +- model_dagmc_2: model_dagmc_1 contained in two CSG boxes to introduce multiple level of coordinates. + +Test cases +---------- + +Test cases using CSG-only geometries: + +======== ======= ========= ========================= ===== =================================== +Folder Model Surface Cell BC* Expected particles +======== ======= ========= ========================= ===== =================================== +case-01 model_1 No No T+V Particles crossing any surface in + the model +case-02 model_1 1 No T Particles crossing this surface + only +case-03 model_1 Multiple No T Particles crossing the declared + surfaces +case-04 model_1 Multiple cell (lower universe) T Particles crossing the declared + surfaces that come from or are + coming to the cell +case-05 model_1 Multiple cell (root universe) T Particles crossing the declared + surfaces that come from or are + coming to the cell +case-06 model_1 No cell (lower universe) T Particles crossing any surface that + come from or are coming to the cell +case-07 model_1 No cell (root universe) T Particles crossing any surface that + come from or are coming to the cell +case-08 model_1 No cellfrom (lower universe) T Particles crossing any surface that + come from the cell +case-09 model_1 No cellto (lower universe) T Particles crossing any surface that + are coming to the cell +case-10 model_1 No cellfrom (root universe) T Particles crossing any surface that + come from the cell +case-11 model_1 No cellto (root universe) T Particles crossing any surface that + are coming to the cell +case-12 model_2 Multiple No V Particles crossing the declared + surfaces +case-13 model_2 Multiple cell (root universe) V Particles crossing any surface that + come from or are coming to the cell +case-14 model_2 Multiple cellfrom (root universe) V Particles crossing any surface that + are coming to the cell +case-15 model_2 Multiple cellto (root universe) V None +case-16 model_3 Multiple No R Particles crossing the declared + surfaces +case-17 model_3 Multiple cell (root universe) R None +case-18 model_3 Multiple cellfrom (root universe) R None +case-19 model_3 Multiple cellto (root universe) R None +case-20 model_4 1 No P+R Particles crossing the declared + periodic surface +case-21 model_4 1 cell (root universe) P+R None +======== ======= ========= ========================= ===== =================================== + +*: BC stands for Boundary Conditions, T for Transmission, R for Reflective, and V for Vacuum. + +An additional case, called 'case-a01', is used to check that the results are comparable when +the number of threads is set to 2 if the number of realization is lower than the capacity. + +Cases e01 to e03 are the event-based cases corresponding to the history-based cases 04, 07 and 13, +respectively. + +Test cases using DAGMC geometries: + +======== ============= ========= ===================== ===== =================================== +Folder Model Surface Cell BC* Expected particles +======== ============= ========= ===================== ===== =================================== +case-d01 model_dagmc_1 No No T+V Particles crossing any surface in + the model +case-d02 model_dagmc_1 1 No T Particles crossing this surface + only +case-d03 model_dagmc_1 No cell T Particles crossing any surface that + come from or are coming to the cell +case-d04 model_dagmc_1 1 cell T Particles crossing the declared + surface that come from or are + coming to the cell +case-d05 model_dagmc_1 No cellfrom T Particles crossing any surface that + come from the cell +case-d06 model_dagmc_1 No cellto T Particles crossing any surface that + are coming to the cell +case-d07 model_dagmc_2 Multiple cell (lower universe) T Particles crossing the declared + surfaces that come from or are + coming to the cell +case-d08 model_dagmc_2 Multiple cell (root universe) T Particles crossing the declared + surfaces that come from or are + coming to the cell +======== ============= ========= ===================== ===== =================================== + +*: BC stands for Boundary Conditions, T for Transmission, and V for Vacuum. + +Notes: + +- The test cases list is non-exhaustive compared to the number of possible combinations. + Test cases have been selected based on use and internal code logic. +- Cases 08 to 11 are testing that the feature still works even if the level of coordinates + before and after crossing a surface is different, +- Tests on boundary conditions are not performed on DAGMC models as the logic is shared + with CSG-only models, +- Cases that should return an error are tested in the 'test_exceptions' unit test + from 'unit_tests/surface_source_write/test.py'. + +TODO: + +- Test with a lattice. + +""" + +import os +import shutil +from pathlib import Path + +import h5py +import numpy as np +import openmc +import openmc.lib +import pytest + +from tests.testing_harness import PyAPITestHarness +from tests.regression_tests import config + + +@pytest.fixture(scope="function") +def single_thread(monkeypatch): + """Set the number of OMP threads to 1 for the test.""" + monkeypatch.setenv("OMP_NUM_THREADS", "1") + + +@pytest.fixture(scope="function") +def two_threads(monkeypatch): + """Set the number of OMP threads to 2 for the test.""" + monkeypatch.setenv("OMP_NUM_THREADS", "2") + + +@pytest.fixture(scope="function") +def single_process(monkeypatch): + """Set the number of MPI process to 1 for the test.""" + monkeypatch.setitem(config, "mpi_np", "1") + + +@pytest.fixture(scope="module") +def model_1(): + """Cylindrical core contained in a first box which is contained in a larger box. + A lower universe is used to describe the interior of the first box which + contains the core and its surrounding space. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material() + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 11.0) + + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(-core_height / 2.0) + core_upper_plane = openmc.ZPlane(core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell(fill=water, region=outside_core_region) + + # Universe + inside_box1_universe = openmc.Universe(cells=[core, outside_core]) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 6.0 + + # Surfaces + box1_rpp = openmc.model.RectangularParallelepiped( + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=-box1_rpp) + + # ----------------------------------------------------------------------------- + # Box 2 + # ----------------------------------------------------------------------------- + + # Parameters + box2_size = 8 + + # Surfaces + box2_rpp = openmc.model.RectangularParallelepiped( + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + -box2_size / 2.0, box2_size / 2.0, + boundary_type="vacuum" + ) + + # Cell + box2 = openmc.Cell(fill=water, region=-box2_rpp & +box1_rpp) + + # Register geometry + model.geometry = openmc.Geometry([box1, box2]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + return model + + +@pytest.fixture +def model_2(): + """Cylindrical core contained in a box. + A lower universe is used to describe the interior of the box which + contains the core and its surrounding space. + + The box is defined with vacuum boundary conditions. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material() + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 11.0) + + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(-core_height / 2.0) + core_upper_plane = openmc.ZPlane(core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell(fill=water, region=outside_core_region) + + # Universe + inside_box1_universe = openmc.Universe(cells=[core, outside_core]) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 6.0 + + # Surfaces + box1_rpp = openmc.model.RectangularParallelepiped( + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + boundary_type="vacuum" + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=-box1_rpp) + + # Register geometry + model.geometry = openmc.Geometry([box1]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + return model + + +@pytest.fixture +def model_3(): + """Cylindrical core contained in a box. + A lower universe is used to describe the interior of the box which + contains the core and its surrounding space. + + The box is defined with reflective boundary conditions. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material() + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 11.0) + + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(-core_height / 2.0) + core_upper_plane = openmc.ZPlane(core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell(fill=water, region=outside_core_region) + + # Universe + inside_box1_universe = openmc.Universe(cells=[core, outside_core]) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 6.0 + + # Surfaces + box1_rpp = openmc.model.RectangularParallelepiped( + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + -box1_size / 2.0, box1_size / 2.0, + boundary_type="reflective" + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=-box1_rpp) + + # Register geometry + model.geometry = openmc.Geometry([box1]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + return model + + +@pytest.fixture +def model_4(): + """Cylindrical core contained in a box. + A lower universe is used to describe the interior of the box which + contains the core and its surrounding space. + + The box is defined with a pair of periodic boundary with reflective + boundaries. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + fuel = openmc.Material() + fuel.add_nuclide("U234", 0.0004524) + fuel.add_nuclide("U235", 0.0506068) + fuel.add_nuclide("U238", 0.9487090) + fuel.add_nuclide("U236", 0.0002318) + fuel.add_nuclide("O16", 2.0) + fuel.set_density("g/cm3", 11.0) + + water = openmc.Material() + water.add_nuclide("H1", 2.0) + water.add_nuclide("O16", 1.0) + water.set_density("g/cm3", 1.0) + + # ============================================================================= + # Geometry + # ============================================================================= + + # ----------------------------------------------------------------------------- + # Cylindrical core + # ----------------------------------------------------------------------------- + + # Parameters + core_radius = 2.0 + core_height = 4.0 + + # Surfaces + core_cylinder = openmc.ZCylinder(r=core_radius) + core_lower_plane = openmc.ZPlane(-core_height / 2.0) + core_upper_plane = openmc.ZPlane(core_height / 2.0) + + # Region + core_region = -core_cylinder & +core_lower_plane & -core_upper_plane + + # Cells + core = openmc.Cell(fill=fuel, region=core_region) + outside_core_region = +core_cylinder | -core_lower_plane | +core_upper_plane + outside_core = openmc.Cell(fill=water, region=outside_core_region) + + # Universe + inside_box1_universe = openmc.Universe(cells=[core, outside_core]) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 6.0 + + # Surfaces + box1_lower_plane = openmc.ZPlane(-box1_size / 2.0, boundary_type="periodic") + box1_upper_plane = openmc.ZPlane(box1_size / 2.0, boundary_type="periodic") + box1_left_plane = openmc.XPlane(-box1_size / 2.0, boundary_type="reflective") + box1_right_plane = openmc.XPlane(box1_size / 2.0, boundary_type="reflective") + box1_rear_plane = openmc.YPlane(-box1_size / 2.0, boundary_type="reflective") + box1_front_plane = openmc.YPlane(box1_size / 2.0, boundary_type="reflective") + + # Region + box1_region = ( + +box1_lower_plane + & -box1_upper_plane + & +box1_left_plane + & -box1_right_plane + & +box1_rear_plane + & -box1_front_plane + ) + + # Cell + box1 = openmc.Cell(fill=inside_box1_universe, region=box1_region) + + # Register geometry + model.geometry = openmc.Geometry([box1]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + bounds = [ + -core_radius, + -core_radius, + -core_height / 2.0, + core_radius, + core_radius, + core_height / 2.0, + ] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource( + space=distribution, constraints={'fissionable': True}) + + return model + + +def return_surface_source_data(filepath): + """Read a surface source file and return a sorted array composed + of flatten arrays of source data for each surface source point. + + Parameters + ---------- + filepath : str + Path to the surface source file + + Returns + ------- + data : np.array + Sorted array composed of flatten arrays of source data for + each surface source point + + """ + data = [] + keys = [] + + # Read source file + source = openmc.read_source_file(filepath) + + for point in source: + r = point.r + u = point.u + e = point.E + time = point.time + wgt = point.wgt + delayed_group = point.delayed_group + surf_id = point.surf_id + particle = point.particle + key = ( + f"{r[0]:.10e} {r[1]:.10e} {r[2]:.10e} {u[0]:.10e} {u[1]:.10e} {u[2]:.10e}" + f"{e:.10e} {time:.10e} {wgt:.10e} {delayed_group} {surf_id} {particle}" + ) + keys.append(key) + values = [*r, *u, e, time, wgt, delayed_group, surf_id, particle] + assert len(values) == 12 + data.append(values) + + data = np.array(data) + keys = np.array(keys) + sorted_idx = np.argsort(keys) + + return data[sorted_idx] + + +class SurfaceSourceWriteTestHarness(PyAPITestHarness): + def __init__(self, statepoint_name, model=None, inputs_true=None, workdir=None): + super().__init__(statepoint_name, model, inputs_true) + self.workdir = workdir + + def _test_output_created(self): + """Make sure surface_source.h5 has also been created.""" + super()._test_output_created() + if self._model.settings.surf_source_write: + assert os.path.exists( + "surface_source.h5" + ), "Surface source file has not been created." + + def _compare_output(self): + """Compare surface_source.h5 files.""" + if self._model.settings.surf_source_write: + source_true = return_surface_source_data("surface_source_true.h5") + source_test = return_surface_source_data("surface_source.h5") + np.testing.assert_allclose(source_true, source_test, rtol=1e-07) + + def main(self): + """Accept commandline arguments and either run or update tests.""" + if config["build_inputs"]: + self.build_inputs() + elif config["update"]: + self.update_results() + else: + self.execute_test() + + def build_inputs(self): + """Build inputs.""" + base_dir = os.getcwd() + try: + os.chdir(self.workdir) + self._build_inputs() + finally: + os.chdir(base_dir) + + def execute_test(self): + """Build inputs, run OpenMC, and verify correct results.""" + base_dir = os.getcwd() + try: + os.chdir(self.workdir) + self._build_inputs() + inputs = self._get_inputs() + self._write_inputs(inputs) + self._compare_inputs() + self._run_openmc() + self._test_output_created() + self._compare_output() + results = self._get_results() + self._write_results(results) + self._compare_results() + finally: + self._cleanup() + os.chdir(base_dir) + + def update_results(self): + """Update results_true.dat and inputs_true.dat""" + base_dir = os.getcwd() + try: + os.chdir(self.workdir) + self._build_inputs() + inputs = self._get_inputs() + self._write_inputs(inputs) + self._overwrite_inputs() + self._run_openmc() + self._test_output_created() + results = self._get_results() + self._write_results(results) + self._overwrite_results() + finally: + self._cleanup() + os.chdir(base_dir) + + def _overwrite_results(self): + """Also add the 'surface_source.h5' file during overwriting.""" + super()._overwrite_results() + if os.path.exists("surface_source.h5"): + shutil.copyfile("surface_source.h5", "surface_source_true.h5") + + def _cleanup(self): + """Also remove the 'surface_source.h5' file while cleaning.""" + super()._cleanup() + fs = "surface_source.h5" + if os.path.exists(fs): + os.remove(fs) + + +@pytest.mark.skipif(config["event"] is True, reason="Results from history-based mode.") +@pytest.mark.parametrize( + "folder, model_name, parameter", + [ + ("case-01", "model_1", {"max_particles": 300}), + ("case-02", "model_1", {"max_particles": 300, "surface_ids": [8]}), + ( + "case-03", + "model_1", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9]}, + ), + ( + "case-04", + "model_1", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 2}, + ), + ( + "case-05", + "model_1", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 3}, + ), + ("case-06", "model_1", {"max_particles": 300, "cell": 2}), + ("case-07", "model_1", {"max_particles": 300, "cell": 3}), + ("case-08", "model_1", {"max_particles": 300, "cellfrom": 2}), + ("case-09", "model_1", {"max_particles": 300, "cellto": 2}), + ("case-10", "model_1", {"max_particles": 300, "cellfrom": 3}), + ("case-11", "model_1", {"max_particles": 300, "cellto": 3}), + ( + "case-12", + "model_2", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9]}, + ), + ( + "case-13", + "model_2", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 3}, + ), + ( + "case-14", + "model_2", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cellfrom": 3}, + ), + ( + "case-15", + "model_2", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cellto": 3}, + ), + ( + "case-16", + "model_3", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9]}, + ), + ( + "case-17", + "model_3", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 3}, + ), + ( + "case-18", + "model_3", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cellfrom": 3}, + ), + ( + "case-19", + "model_3", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cellto": 3}, + ), + ( + "case-20", + "model_4", + {"max_particles": 300, "surface_ids": [4]}, + ), + ( + "case-21", + "model_4", + {"max_particles": 300, "surface_ids": [4], "cell": 3}, + ), + ], +) +def test_surface_source_cell_history_based( + folder, model_name, parameter, single_thread, single_process, request +): + """Test on history-based results for CSG-only geometries.""" + assert os.environ["OMP_NUM_THREADS"] == "1" + assert config["mpi_np"] == "1" + model = request.getfixturevalue(model_name) + model.settings.surf_source_write = parameter + harness = SurfaceSourceWriteTestHarness( + "statepoint.5.h5", model=model, workdir=folder + ) + harness.main() + + +@pytest.mark.skipif(config["event"] is True, reason="Results from history-based mode.") +def test_consistency_low_realization_number(model_1, two_threads, single_process): + """The objective is to test that the results produced, in a case where + the number of potential realization (particle storage) is low + compared to the capacity of storage, are still consistent. + + This configuration ensures that the competition between threads does not + occur and that the content of the source file created can be compared. + + """ + assert os.environ["OMP_NUM_THREADS"] == "2" + assert config["mpi_np"] == "1" + model_1.settings.surf_source_write = { + "max_particles": 200, + "surface_ids": [1, 2, 3], + "cellfrom": 2, + } + harness = SurfaceSourceWriteTestHarness( + "statepoint.5.h5", model=model_1, workdir="case-a01" + ) + harness.main() + + +@pytest.mark.skipif(config["event"] is False, reason="Results from event-based mode.") +@pytest.mark.parametrize( + "folder, model_name, parameter", + [ + ( + "case-e01", + "model_1", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 2}, + ), + ("case-e02", "model_1", {"max_particles": 300, "cell": 3}), + ( + "case-e03", + "model_2", + {"max_particles": 300, "surface_ids": [4, 5, 6, 7, 8, 9], "cell": 3}, + ), + ], +) +def test_surface_source_cell_event_based( + folder, model_name, parameter, single_thread, single_process, request +): + """Test on event-based results for CSG-only geometries.""" + assert os.environ["OMP_NUM_THREADS"] == "1" + assert config["mpi_np"] == "1" + model = request.getfixturevalue(model_name) + model.settings.surf_source_write = parameter + harness = SurfaceSourceWriteTestHarness( + "statepoint.5.h5", model=model, workdir=folder + ) + harness.main() + + +@pytest.fixture(scope="module") +def model_dagmc_1(): + """Model based on the mesh file 'dagmc.h5m' available from + tests/regression_tests/dagmc/legacy. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + u235 = openmc.Material(name="no-void fuel") + u235.add_nuclide("U235", 1.0, "ao") + u235.set_density("g/cc", 11) + u235.id = 40 + + water = openmc.Material(name="water") + water.add_nuclide("H1", 2.0, "ao") + water.add_nuclide("O16", 1.0, "ao") + water.set_density("g/cc", 1.0) + water.add_s_alpha_beta("c_H_in_H2O") + water.id = 41 + + materials = openmc.Materials([u235, water]) + model.materials = materials + + # ============================================================================= + # Geometry + # ============================================================================= + + dagmc_univ = openmc.DAGMCUniverse(Path("../../dagmc/legacy/dagmc.h5m")) + model.geometry = openmc.Geometry(dagmc_univ) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + source_box = openmc.stats.Box([-4, -4, -20], [4, 4, 20]) + model.settings.source = openmc.IndependentSource( + space=source_box, constraints={'fissionable': True}) + + return model + + +@pytest.fixture(scope="module") +def model_dagmc_2(): + """Model based on the mesh file 'dagmc.h5m' available from + tests/regression_tests/dagmc/legacy. + + This model corresponds to the model_dagmc_1 contained in two boxes to introduce + multiple level of coordinates from CSG geometry. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + u235 = openmc.Material(name="no-void fuel") + u235.add_nuclide("U235", 1.0, "ao") + u235.set_density("g/cc", 11) + u235.id = 40 + + water = openmc.Material(name="water") + water.add_nuclide("H1", 2.0, "ao") + water.add_nuclide("O16", 1.0, "ao") + water.set_density("g/cc", 1.0) + water.add_s_alpha_beta("c_H_in_H2O") + water.id = 41 + + materials = openmc.Materials([u235, water]) + model.materials = materials + + # ============================================================================= + # Geometry + # ============================================================================= + + dagmc_univ = openmc.DAGMCUniverse(Path("../../dagmc/legacy/dagmc.h5m")) + + # ----------------------------------------------------------------------------- + # Box 1 + # ----------------------------------------------------------------------------- + + # Parameters + box1_size = 44 + + # Surfaces + box1_lower_plane = openmc.ZPlane(-box1_size / 2.0, surface_id=101) + box1_upper_plane = openmc.ZPlane(box1_size / 2.0, surface_id=102) + box1_left_plane = openmc.XPlane(-box1_size / 2.0, surface_id=103) + box1_right_plane = openmc.XPlane(box1_size / 2.0, surface_id=104) + box1_rear_plane = openmc.YPlane(-box1_size / 2.0, surface_id=105) + box1_front_plane = openmc.YPlane(box1_size / 2.0, surface_id=106) + + # Region + box1_region = ( + +box1_lower_plane + & -box1_upper_plane + & +box1_left_plane + & -box1_right_plane + & +box1_rear_plane + & -box1_front_plane + ) + + # Cell + box1 = openmc.Cell(fill=dagmc_univ, region=box1_region, cell_id=8) + + # ----------------------------------------------------------------------------- + # Box 2 + # ----------------------------------------------------------------------------- + + # Parameters + box2_size = 48 + + # Surfaces + box2_lower_plane = openmc.ZPlane( + -box2_size / 2.0, boundary_type="vacuum", surface_id=107 + ) + box2_upper_plane = openmc.ZPlane( + box2_size / 2.0, boundary_type="vacuum", surface_id=108 + ) + box2_left_plane = openmc.XPlane( + -box2_size / 2.0, boundary_type="vacuum", surface_id=109 + ) + box2_right_plane = openmc.XPlane( + box2_size / 2.0, boundary_type="vacuum", surface_id=110 + ) + box2_rear_plane = openmc.YPlane( + -box2_size / 2.0, boundary_type="vacuum", surface_id=111 + ) + box2_front_plane = openmc.YPlane( + box2_size / 2.0, boundary_type="vacuum", surface_id=112 + ) + + # Region + inside_box2 = ( + +box2_lower_plane + & -box2_upper_plane + & +box2_left_plane + & -box2_right_plane + & +box2_rear_plane + & -box2_front_plane + ) + outside_box1 = ( + -box1_lower_plane + | +box1_upper_plane + | -box1_left_plane + | +box1_right_plane + | -box1_rear_plane + | +box1_front_plane + ) + + box2_region = inside_box2 & outside_box1 + + # Cell + box2 = openmc.Cell(fill=water, region=box2_region, cell_id=9) + + # Register geometry + model.geometry = openmc.Geometry([box1, box2]) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 100 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + source_box = openmc.stats.Box([-4, -4, -20], [4, 4, 20]) + model.settings.source = openmc.IndependentSource( + space=source_box, constraints={'fissionable': True}) + + return model + + +@pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." +) +@pytest.mark.skipif(config["event"] is True, reason="Results from history-based mode.") +@pytest.mark.parametrize( + "folder, model_name, parameter", + [ + ("case-d01", "model_dagmc_1", {"max_particles": 300}), + ("case-d02", "model_dagmc_1", {"max_particles": 300, "surface_ids": [1]}), + ("case-d03", "model_dagmc_1", {"max_particles": 300, "cell": 2}), + ( + "case-d04", + "model_dagmc_1", + {"max_particles": 300, "surface_ids": [1], "cell": 2}, + ), + ("case-d05", "model_dagmc_1", {"max_particles": 300, "cellfrom": 2}), + ("case-d06", "model_dagmc_1", {"max_particles": 300, "cellto": 2}), + ( + "case-d07", + "model_dagmc_2", + { + "max_particles": 300, + "surface_ids": [101, 102, 103, 104, 105, 106], + "cell": 7, + }, + ), + ( + "case-d08", + "model_dagmc_2", + { + "max_particles": 300, + "surface_ids": [101, 102, 103, 104, 105, 106], + "cell": 8, + }, + ), + ], +) +def test_surface_source_cell_dagmc( + folder, model_name, parameter, single_thread, single_process, request +): + """Test on models with DAGMC geometries.""" + assert os.environ["OMP_NUM_THREADS"] == "1" + assert config["mpi_np"] == "1" + model = request.getfixturevalue(model_name) + model.settings.surf_source_write = parameter + harness = SurfaceSourceWriteTestHarness( + "statepoint.5.h5", model=model, workdir=folder + ) + harness.main() diff --git a/tests/regression_tests/tally_slice_merge/inputs_true.dat b/tests/regression_tests/tally_slice_merge/inputs_true.dat index 356962f8e4f..7159d833a5a 100644 --- a/tests/regression_tests/tally_slice_merge/inputs_true.dat +++ b/tests/regression_tests/tally_slice_merge/inputs_true.dat @@ -308,7 +308,7 @@ - + 2 2 -50.0 -50.0 50.0 50.0 diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index 090e3144838..aec73979b91 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -149,6 +149,9 @@ def _get_results(self, hash_output=False): sum2 = mesh_tally.summation(filter_type=openmc.MeshFilter, filter_bins=[(2, 1), (2, 2)]) + mesh = mesh_tally.find_filter(openmc.MeshFilter).mesh + assert mesh.name == 'mesh' + # Merge the mesh tally slices merge_tally = sum1.merge(sum2) diff --git a/tests/regression_tests/weightwindows/generators/test.py b/tests/regression_tests/weightwindows/generators/test.py index 1d516db8dde..d6a40b44f01 100644 --- a/tests/regression_tests/weightwindows/generators/test.py +++ b/tests/regression_tests/weightwindows/generators/test.py @@ -22,7 +22,7 @@ def test_ww_generator(run_in_tmpdir): model.settings.particles = 500 model.settings.batches = 5 model.settings.run_mode = 'fixed source' - model.settings.max_splits = 100 + model.settings.max_history_splits = 100 mesh = openmc.RegularMesh.from_domain(model.geometry.root_universe) energy_bounds = np.linspace(0.0, 1e6, 70) diff --git a/tests/regression_tests/weightwindows/inputs_true.dat b/tests/regression_tests/weightwindows/inputs_true.dat index c40f4b7615e..dcc41ae8417 100644 --- a/tests/regression_tests/weightwindows/inputs_true.dat +++ b/tests/regression_tests/weightwindows/inputs_true.dat @@ -70,7 +70,7 @@ 10 1e-38 - 200 + 200 diff --git a/tests/regression_tests/weightwindows/test.py b/tests/regression_tests/weightwindows/test.py index 9ed8559760f..864f4f062dc 100644 --- a/tests/regression_tests/weightwindows/test.py +++ b/tests/regression_tests/weightwindows/test.py @@ -48,7 +48,7 @@ def model(): settings.run_mode = 'fixed source' settings.particles = 200 settings.batches = 2 - settings.max_splits = 200 + settings.max_history_splits = 200 settings.photon_transport = True space = Point((0.001, 0.001, 0.001)) energy = Discrete([14E6], [1.0]) diff --git a/tests/unit_tests/cell_instances/test_hex_multilattice.py b/tests/unit_tests/cell_instances/test_hex_multilattice.py new file mode 100644 index 00000000000..3f503fe9b84 --- /dev/null +++ b/tests/unit_tests/cell_instances/test_hex_multilattice.py @@ -0,0 +1,119 @@ +from math import sqrt + +import pytest +import numpy as np +import openmc +import openmc.lib + +from tests import cdtemp + + +@pytest.fixture(scope='module', autouse=True) +def double_hex_lattice_model(): + openmc.reset_auto_ids() + radius = 0.9 + pin_lattice_pitch = 2.0 + # make the hex prism a little larger to make sure test + # locations are definitively in the model + hex_prism_edge = 1.2 * pin_lattice_pitch + + model = openmc.Model() + + # materials + nat_u = openmc.Material() + nat_u.set_density('g/cm3', 12.0) + nat_u.add_element('U', 1.0) + + graphite = openmc.Material() + graphite.set_density('g/cm3', 1.1995) + graphite.add_element('C', 1.0) + + # zplanes to define lower and upper region + z_low = openmc.ZPlane(-10, boundary_type='vacuum') + z_mid = openmc.ZPlane(0) + z_high = openmc.ZPlane(10, boundary_type='vacuum') + hex_prism = openmc.model.HexagonalPrism( + edge_length=hex_prism_edge, boundary_type='reflective') + + # geometry + cyl = openmc.ZCylinder(r=radius) + univ = openmc.model.pin([cyl], [nat_u, graphite]) + + # create a hexagonal lattice of compacts + hex_lattice = openmc.HexLattice() + hex_lattice.orientation = 'y' + hex_lattice.pitch = (pin_lattice_pitch,) + hex_lattice.center = (0., 0.) + center = [univ] + ring = [univ, univ, univ, univ, univ, univ] + hex_lattice.universes = [ring, center] + lower_hex_cell = openmc.Cell(fill=hex_lattice, region=-hex_prism & +z_low & -z_mid) + upper_hex_cell = openmc.Cell(fill=hex_lattice, region=-hex_prism & +z_mid & -z_high) + hex_cells = [lower_hex_cell, upper_hex_cell] + model.geometry = openmc.Geometry(hex_cells) + + # moderator + cell = next(iter(univ.get_all_cells().values())) + tally = openmc.Tally(tally_id=1) + filter = openmc.DistribcellFilter(cell) + tally.filters = [filter] + tally.scores = ['flux'] + model.tallies = [tally] + + # settings + # source definition. fission source given bounding box of graphite active region + system_LL = (-pin_lattice_pitch*sqrt(3)/2, -pin_lattice_pitch, -5) + system_UR = (pin_lattice_pitch*sqrt(3)/2, pin_lattice_pitch, 5) + source_dist = openmc.stats.Box(system_LL, system_UR) + model.settings.source = openmc.IndependentSource(space=source_dist) + model.settings.particles = 100 + model.settings.inactive = 2 + model.settings.batches = 10 + + with cdtemp(): + model.export_to_xml() + openmc.lib.init() + yield + openmc.lib.finalize() + + +# Lower cell instances +# 6 +# 5 4 +# 3 +# 2 1 +# 0 +# Upper cell instances +# 13 +# 12 11 +# 10 +# 9 8 +# 7 +hex_expected_results = [ + ((0.0, -2.0, -5.0), 0), + ((1.732, -1.0, -5.0), 1), + ((-1.732, -1.0, -5.0), 2), + ((0.0, 0.0, -0.1), 3), + ((1.732, 1.0, -5.0), 4), + ((-1.732, 1.0, -5.0), 5), + ((0.0, 2.0, -0.1), 6), + ((0.0, -2.0, 5.0), 7), + ((1.732, -1.0, 5.0), 8), + ((-1.732, -1.0, 5.0), 9), + ((0.0, 0.0, 5.0), 10), + ((1.732, 1.0, 5.0), 11), + ((-1.732, 1.0, 5.0), 12), + ((0.0, 2.0, 5.0), 13), +] + + +@pytest.mark.parametrize("r,expected_cell_instance", hex_expected_results, ids=str) +def test_cell_instance_hex_multilattice(r, expected_cell_instance): + _, cell_instance = openmc.lib.find_cell(r) + assert cell_instance == expected_cell_instance + + +def test_cell_instance_multilattice_results(): + openmc.lib.run() + tally_results = openmc.lib.tallies[1].mean + assert (tally_results != 0.0).all() diff --git a/tests/unit_tests/test_cell_instance.py b/tests/unit_tests/cell_instances/test_rect_multilattice.py similarity index 84% rename from tests/unit_tests/test_cell_instance.py rename to tests/unit_tests/cell_instances/test_rect_multilattice.py index 25c20cfef22..aaecb3bdac5 100644 --- a/tests/unit_tests/test_cell_instance.py +++ b/tests/unit_tests/cell_instances/test_rect_multilattice.py @@ -1,5 +1,5 @@ -import numpy as np import pytest +import numpy as np import openmc import openmc.lib @@ -8,7 +8,7 @@ @pytest.fixture(scope='module', autouse=True) -def double_lattice_model(): +def double_rect_lattice_model(): openmc.reset_auto_ids() model = openmc.Model() @@ -40,8 +40,9 @@ def double_lattice_model(): cell_with_lattice2.translation = (2., 0., 0.) model.geometry = openmc.Geometry([cell_with_lattice1, cell_with_lattice2]) - tally = openmc.Tally() - tally.filters = [openmc.DistribcellFilter(c)] + tally = openmc.Tally(tally_id=1) + dcell_filter = openmc.DistribcellFilter(c) + tally.filters = [dcell_filter] tally.scores = ['flux'] model.tallies = [tally] @@ -50,7 +51,8 @@ def double_lattice_model(): bbox[0][2] = -0.5 bbox[1][2] = 0.5 space = openmc.stats.Box(*bbox) - model.settings.source = openmc.IndependentSource(space=space) + source = openmc.IndependentSource(space=space) + model.settings.source = source # Add necessary settings and export model.settings.batches = 10 @@ -63,14 +65,13 @@ def double_lattice_model(): yield openmc.lib.finalize() - # This shows the expected cell instance numbers for each lattice position: # ┌─┬─┬─┬─┐ # │2│3│6│7│ # ├─┼─┼─┼─┤ # │0│1│4│5│ # └─┴─┴─┴─┘ -expected_results = [ +rect_expected_results = [ ((0.5, 0.5, 0.0), 0), ((1.5, 0.5, 0.0), 1), ((0.5, 1.5, 0.0), 2), @@ -80,13 +81,16 @@ def double_lattice_model(): ((2.5, 1.5, 0.0), 6), ((3.5, 1.5, 0.0), 7), ] -@pytest.mark.parametrize("r,expected_cell_instance", expected_results) -def test_cell_instance_multilattice(r, expected_cell_instance): + + +@pytest.mark.parametrize("r,expected_cell_instance", rect_expected_results, ids=lambda p : f'{p}') +def test_cell_instance_rect_multilattice(r, expected_cell_instance): _, cell_instance = openmc.lib.find_cell(r) assert cell_instance == expected_cell_instance def test_cell_instance_multilattice_results(): + openmc.run() openmc.lib.run() tally_results = openmc.lib.tallies[1].mean assert (tally_results != 0.0).all() diff --git a/tests/unit_tests/dagmc/test_model.py b/tests/unit_tests/dagmc/test_model.py new file mode 100644 index 00000000000..9fdfbcebc68 --- /dev/null +++ b/tests/unit_tests/dagmc/test_model.py @@ -0,0 +1,256 @@ +from pathlib import Path + +import lxml.etree as ET +import numpy as np +import pytest +import openmc +from openmc.utility_funcs import change_directory + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC CAD geometry is not enabled.") + + +@pytest.fixture() +def model(request): + pitch = 1.26 + + mats = {} + mats["no-void fuel"] = openmc.Material(1, name="no-void fuel") + mats["no-void fuel"].add_nuclide("U235", 0.03) + mats["no-void fuel"].add_nuclide("U238", 0.97) + mats["no-void fuel"].add_nuclide("O16", 2.0) + mats["no-void fuel"].set_density("g/cm3", 10.0) + + mats["41"] = openmc.Material(name="41") + mats["41"].add_nuclide("H1", 2.0) + mats["41"].add_element("O", 1.0) + mats["41"].set_density("g/cm3", 1.0) + mats["41"].add_s_alpha_beta("c_H_in_H2O") + + p = Path(request.fspath).parent / "dagmc.h5m" + + daguniv = openmc.DAGMCUniverse(p, auto_geom_ids=True) + + lattice = openmc.RectLattice() + lattice.dimension = [2, 2] + lattice.lower_left = [-pitch, -pitch] + lattice.pitch = [pitch, pitch] + lattice.universes = [ + [daguniv, daguniv], + [daguniv, daguniv]] + + box = openmc.model.RectangularParallelepiped(-pitch, pitch, -pitch, pitch, -5, 5) + + root = openmc.Universe(cells=[openmc.Cell(region=-box, fill=lattice)]) + + settings = openmc.Settings() + settings.batches = 100 + settings.inactive = 10 + settings.particles = 1000 + + ll, ur = root.bounding_box + mat_vol = openmc.VolumeCalculation([mats["no-void fuel"]], 1000000, ll, ur) + cell_vol = openmc.VolumeCalculation(list(root.cells.values()), 1000000, ll, ur) + settings.volume_calculations = [mat_vol, cell_vol] + + model = openmc.Model() + model.materials = openmc.Materials(mats.values()) + model.geometry = openmc.Geometry(root=root) + model.settings = settings + + with change_directory(tmpdir=True): + try: + model.init_lib() + model.sync_dagmc_universes() + yield model + finally: + model.finalize_lib() + openmc.reset_auto_ids() + + +def test_dagmc_replace_material_assignment(model): + mats = {} + + mats["foo"] = openmc.Material(name="foo") + mats["foo"].add_nuclide("H1", 2.0) + mats["foo"].add_element("O", 1.0) + mats["foo"].set_density("g/cm3", 1.0) + mats["foo"].add_s_alpha_beta("c_H_in_H2O") + + for univ in model.geometry.get_all_universes().values(): + if not isinstance(univ, openmc.DAGMCUniverse): + break + + cells_with_41 = [] + for cell in univ.cells.values(): + if cell.fill is None: + continue + if cell.fill.name == "41": + cells_with_41.append(cell.id) + univ.replace_material_assignment("41", mats["foo"]) + for cell_id in cells_with_41: + assert univ.cells[cell_id] == mats["foo"] + + +def test_dagmc_add_material_override_with_id(model): + mats = {} + mats["foo"] = openmc.Material(name="foo") + mats["foo"].add_nuclide("H1", 2.0) + mats["foo"].add_element("O", 1.0) + mats["foo"].set_density("g/cm3", 1.0) + mats["foo"].add_s_alpha_beta("c_H_in_H2O") + + for univ in model.geometry.get_all_universes().values(): + if not isinstance(univ, openmc.DAGMCUniverse): + break + + cells_with_41 = [] + for cell in univ.cells.values(): + if cell.fill is None: + continue + if cell.fill.name == "41": + cells_with_41.append(cell.id) + univ.add_material_override(cell.id, mats["foo"]) + for cell_id in cells_with_41: + assert univ.cells[cell_id] == mats["foo"] + + +def test_dagmc_add_material_override_with_cell(model): + mats = {} + mats["foo"] = openmc.Material(name="foo") + mats["foo"].add_nuclide("H1", 2.0) + mats["foo"].add_element("O", 1.0) + mats["foo"].set_density("g/cm3", 1.0) + mats["foo"].add_s_alpha_beta("c_H_in_H2O") + + for univ in model.geometry.get_all_universes().values(): + if not isinstance(univ, openmc.DAGMCUniverse): + break + + cells_with_41 = [] + for cell in univ.cells.values(): + if cell.fill is None: + continue + if cell.fill.name == "41": + cells_with_41.append(cell.id) + univ.add_material_override(cell, mats["foo"]) + for cell_id in cells_with_41: + assert univ.cells[cell_id] == mats["foo"] + + +def test_model_differentiate_depletable_with_dagmc(model, run_in_tmpdir): + model.calculate_volumes() + + # Get the volume of the no-void fuel material before differentiation + volume_before = np.sum([m.volume for m in model.materials if m.name == "no-void fuel"]) + + # Differentiate the depletable materials + model.differentiate_depletable_mats(diff_volume_method="divide equally") + # Get the volume of the no-void fuel material after differentiation + volume_after = np.sum([m.volume for m in model.materials if "fuel" in m.name]) + assert np.isclose(volume_before, volume_after) + assert len(model.materials) == 4*2 +1 + + +def test_model_differentiate_with_dagmc(model): + root = model.geometry.root_universe + ll, ur = root.bounding_box + model.calculate_volumes() + # Get the volume of the no-void fuel material before differentiation + volume_before = np.sum([m.volume for m in model.materials if m.name == "no-void fuel"]) + + # Differentiate all the materials + model.differentiate_mats(depletable_only=False) + + # Get the volume of the no-void fuel material after differentiation + mat_vol = openmc.VolumeCalculation(model.materials, 1000000, ll, ur) + model.settings.volume_calculations = [mat_vol] + model.init_lib() # need to reinitialize the lib after differentiating the materials + model.calculate_volumes() + volume_after = np.sum([m.volume for m in model.materials if "fuel" in m.name]) + assert np.isclose(volume_before, volume_after) + assert len(model.materials) == 4*2 + 4 + + +def test_bad_override_cell_id(model): + for univ in model.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + break + with pytest.raises(ValueError, match="Cell ID '1' not found in DAGMC universe"): + univ.material_overrides = {1: model.materials[0]} + + +def test_bad_override_type(model): + not_a_dag_cell = openmc.Cell() + for univ in model.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + break + with pytest.raises(ValueError, match="Unrecognized key type. Must be an integer or openmc.DAGMCCell object"): + univ.material_overrides = {not_a_dag_cell: model.materials[0]} + + +def test_bad_replacement_mat_name(model): + for univ in model.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + break + with pytest.raises(ValueError, match="No material with name 'not_a_mat' found in the DAGMC universe"): + univ.replace_material_assignment("not_a_mat", model.materials[0]) + + +def test_dagmc_xml(model): + # Set the environment + mats = {} + mats["no-void fuel"] = openmc.Material(1, name="no-void fuel") + mats["no-void fuel"].add_nuclide("U235", 0.03) + mats["no-void fuel"].add_nuclide("U238", 0.97) + mats["no-void fuel"].add_nuclide("O16", 2.0) + mats["no-void fuel"].set_density("g/cm3", 10.0) + + mats[5] = openmc.Material(name="41") + mats[5].add_nuclide("H1", 2.0) + mats[5].add_element("O", 1.0) + mats[5].set_density("g/cm3", 1.0) + mats[5].add_s_alpha_beta("c_H_in_H2O") + + for univ in model.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + dag_univ = univ + break + + for k, v in mats.items(): + if isinstance(k, int): + dag_univ.add_material_override(k, v) + model.materials.append(v) + elif isinstance(k, str): + dag_univ.replace_material_assignment(k, v) + + # Tesing the XML subelement generation + root = ET.Element('dagmc_universe') + dag_univ.create_xml_subelement(root) + dagmc_ele = root.find('dagmc_universe') + + assert dagmc_ele.get('id') == str(dag_univ.id) + assert dagmc_ele.get('filename') == str(dag_univ.filename) + assert dagmc_ele.get('auto_geom_ids') == str(dag_univ.auto_geom_ids).lower() + + override_eles = dagmc_ele.find('material_overrides').findall('cell_override') + assert len(override_eles) == 4 + + for i, override_ele in enumerate(override_eles): + cell_id = override_ele.get('id') + assert dag_univ.material_overrides[int(cell_id)][0].id == int(override_ele.find('material_ids').text) + + model.export_to_model_xml() + + xml_model = openmc.Model.from_model_xml() + + for univ in xml_model.geometry.get_all_universes().values(): + if isinstance(univ, openmc.DAGMCUniverse): + xml_dagmc_univ = univ + break + + assert xml_dagmc_univ._material_overrides.keys() == dag_univ._material_overrides.keys() + + for xml_mats, model_mats in zip(xml_dagmc_univ._material_overrides.values(), dag_univ._material_overrides.values()): + assert all([xml_mat.id == orig_mat.id for xml_mat, orig_mat in zip(xml_mats, model_mats)]) diff --git a/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py b/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py index baa54498438..0b6acf5aabf 100644 --- a/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py +++ b/tests/unit_tests/mesh_to_vtk/test_vtk_dims.py @@ -153,7 +153,7 @@ def test_write_data_to_vtk_round_trip(run_in_tmpdir): smesh = openmc.SphericalMesh( r_grid=(0.0, 1.0, 2.0), - theta_grid=(0.0, 2.0, 4.0, 5.0), + theta_grid=(0.0, 0.5, 1.0, 2.0), phi_grid=(0.0, 3.0, 6.0), ) rmesh = openmc.RegularMesh() diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 1d3c0f173b0..9d3f53a7403 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -19,7 +19,10 @@ def test_config_basics(): assert isinstance(openmc.config, Mapping) for key, value in openmc.config.items(): assert isinstance(key, str) - assert isinstance(value, os.PathLike) + if key == 'resolve_paths': + assert isinstance(value, bool) + else: + assert isinstance(value, os.PathLike) # Set and delete openmc.config['cross_sections'] = '/path/to/cross_sections.xml' @@ -32,6 +35,13 @@ def test_config_basics(): openmc.config['🐖'] = '/like/to/eat/bacon' +def test_config_patch(): + openmc.config['cross_sections'] = '/path/to/cross_sections.xml' + with openmc.config.patch('cross_sections', '/path/to/other.xml'): + assert str(openmc.config['cross_sections']) == '/path/to/other.xml' + assert str(openmc.config['cross_sections']) == '/path/to/cross_sections.xml' + + def test_config_set_envvar(): openmc.config['cross_sections'] = '/path/to/cross_sections.xml' assert os.environ['OPENMC_CROSS_SECTIONS'] == '/path/to/cross_sections.xml' diff --git a/tests/unit_tests/test_data_dose.py b/tests/unit_tests/test_data_dose.py index 348143e0b0a..2d80cf8384f 100644 --- a/tests/unit_tests/test_data_dose.py +++ b/tests/unit_tests/test_data_dose.py @@ -22,8 +22,22 @@ def test_dose_coefficients(): assert energy[-1] == approx(10e9) assert dose[-1] == approx(699.0) + energy, dose = dose_coefficients('photon', data_source='icrp74') + assert energy[0] == approx(0.01e6) + assert dose[0] == approx(7.43*0.00653) + assert energy[-1] == approx(10.0e6) + assert dose[-1] == approx(24.0*0.990) + + energy, dose = dose_coefficients('neutron', 'LLAT', data_source='icrp74') + assert energy[0] == approx(1e-3) + assert dose[0] == approx(1.68) + assert energy[-1] == approx(20.0e6) + assert dose[-1] == approx(338.0) + # Invalid particle/geometry should raise an exception with raises(ValueError): dose_coefficients('slime', 'LAT') with raises(ValueError): dose_coefficients('neutron', 'ZZ') + with raises(ValueError): + dose_coefficients('neutron', data_source='icrp7000') diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index c0d6a1f1547..db6ae1eb850 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -282,10 +282,6 @@ def test_slbw(xe135): s = resolved.parameters.iloc[0] assert s['energy'] == pytest.approx(0.084) - xs = resolved.reconstruct([10., 30., 100.]) - assert sorted(xs.keys()) == [2, 18, 102] - assert np.all(xs[18] == 0.0) - def test_mlbw(sm150): resolved = sm150.resonances.resolved @@ -294,10 +290,6 @@ def test_mlbw(sm150): assert resolved.energy_max == pytest.approx(1570.) assert resolved.target_spin == 0.0 - xs = resolved.reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] - assert np.all(xs[18] == 0.0) - def test_reichmoore(gd154): res = gd154.resonances @@ -319,7 +311,6 @@ def test_reichmoore(gd154): elastic = gd154.reactions[2].xs['0K'] assert isinstance(elastic, openmc.data.ResonancesWithBackground) - assert elastic(0.0253) == pytest.approx(5.7228949796394524) def test_rml(cl35): @@ -347,8 +338,6 @@ def test_mlbw_cov_lcomp0(cf252): assert not subset.parameters.empty assert (subset.file2res.parameters['energy'] < 100).all() samples = cov.sample(1) - xs = samples[0].reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] def test_mlbw_cov_lcomp1(ti50): @@ -365,9 +354,7 @@ def test_mlbw_cov_lcomp1(ti50): subset = cov.subset('L', [1, 1]) assert not subset.parameters.empty assert (subset.file2res.parameters['L'] == 1).all() - samples = cov.sample(1) - xs = samples[0].reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] + cov.sample(1) def test_mlbw_cov_lcomp2(na23): @@ -384,9 +371,7 @@ def test_mlbw_cov_lcomp2(na23): subset = cov.subset('L', [1, 1]) assert not subset.parameters.empty assert (subset.file2res.parameters['L'] == 1).all() - samples = cov.sample(1) - xs = samples[0].reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] + cov.sample(1) def test_rmcov_lcomp1(gd154): @@ -403,9 +388,7 @@ def test_rmcov_lcomp1(gd154): subset = cov.subset('energy', [0, 100]) assert not subset.parameters.empty assert (subset.file2res.parameters['energy'] < 100).all() - samples = cov.sample(1) - xs = samples[0].reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] + cov.sample(1) def test_rmcov_lcomp2(th232): @@ -422,9 +405,7 @@ def test_rmcov_lcomp2(th232): subset = cov.subset('energy', [0, 100]) assert not subset.parameters.empty assert (subset.file2res.parameters['energy'] < 100).all() - samples = cov.sample(1) - xs = samples[0].reconstruct([10., 100., 1000.]) - assert sorted(xs.keys()) == [2, 18, 102] + cov.sample(1) def test_madland_nix(am241): diff --git a/tests/unit_tests/test_data_photon.py b/tests/unit_tests/test_data_photon.py index f7274e9c195..010ac1f353e 100644 --- a/tests/unit_tests/test_data_photon.py +++ b/tests/unit_tests/test_data_photon.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from collections.abc import Mapping, Callable import os from pathlib import Path @@ -146,8 +144,9 @@ def test_export_to_hdf5(tmpdir, element): # Export to hdf5 again element2.export_to_hdf5(filename, 'w') + def test_photodat_only(run_in_tmpdir): endf_dir = Path(os.environ['OPENMC_ENDF_DATA']) photoatomic_file = endf_dir / 'photoat' / 'photoat-001_H_000.endf' data = openmc.data.IncidentPhoton.from_endf(photoatomic_file) - data.export_to_hdf5('tmp.h5', 'w') \ No newline at end of file + data.export_to_hdf5('tmp.h5', 'w') diff --git a/tests/unit_tests/test_deplete_decay.py b/tests/unit_tests/test_deplete_decay.py index aca812560c7..6e7b0b101ec 100644 --- a/tests/unit_tests/test_deplete_decay.py +++ b/tests/unit_tests/test_deplete_decay.py @@ -20,7 +20,7 @@ def test_deplete_decay_products(run_in_tmpdir): """) # Create MicroXS object with no cross sections - micro_xs = openmc.deplete.MicroXS(np.empty((0, 0)), [], []) + micro_xs = openmc.deplete.MicroXS(np.empty((0, 0, 0)), [], []) # Create depletion operator with no reactions op = openmc.deplete.IndependentOperator.from_nuclides( @@ -40,8 +40,9 @@ def test_deplete_decay_products(run_in_tmpdir): # Get concentration of H1 and He4 results = openmc.deplete.Results('depletion_results.h5') - _, h1 = results.get_atoms("1", "H1") - _, he4 = results.get_atoms("1", "He4") + mat_id = op.materials[0].id + _, h1 = results.get_atoms(f"{mat_id}", "H1") + _, he4 = results.get_atoms(f"{mat_id}", "He4") # Since we started with 1e24 atoms of Li5, we should have 1e24 atoms of both # H1 and He4 @@ -58,7 +59,7 @@ def test_deplete_decay_step_fissionable(run_in_tmpdir): """ # Set up a pure decay operator - micro_xs = openmc.deplete.MicroXS(np.empty((0, 0)), [], []) + micro_xs = openmc.deplete.MicroXS(np.empty((0, 0, 0)), [], []) mat = openmc.Material() mat.name = 'I do not decay.' mat.add_nuclide('U238', 1.0, 'ao') @@ -78,6 +79,6 @@ def test_deplete_decay_step_fissionable(run_in_tmpdir): # Get concentration of U238. It should be unchanged since this chain has no U238 decay. results = openmc.deplete.Results('depletion_results.h5') - _, u238 = results.get_atoms("1", "U238") + _, u238 = results.get_atoms(f"{mat.id}", "U238") assert u238[1] == pytest.approx(original_atoms) diff --git a/tests/unit_tests/test_deplete_independent_operator.py b/tests/unit_tests/test_deplete_independent_operator.py index 9129cf0642f..c765d065009 100644 --- a/tests/unit_tests/test_deplete_independent_operator.py +++ b/tests/unit_tests/test_deplete_independent_operator.py @@ -6,7 +6,7 @@ import pytest -from openmc import Material, Materials +from openmc import Material from openmc.deplete import IndependentOperator, MicroXS CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" @@ -34,7 +34,7 @@ def test_operator_init(): fuel.set_density("g/cc", 10.4) fuel.depletable = True fuel.volume = 1 - materials = Materials([fuel]) + materials = [fuel] fluxes = [1.0] micros = [micro_xs] IndependentOperator(materials, fluxes, micros, CHAIN_PATH) @@ -47,7 +47,7 @@ def test_error_handling(): fuel.set_density("g/cc", 1) fuel.depletable = True fuel.volume = 1 - materials = Materials([fuel]) + materials = [fuel] fluxes = [1.0, 2.0] micros = [micro_xs] with pytest.raises(ValueError, match=r"The length of fluxes \(2\)"): diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index ad54026f014..073b3f162d1 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -46,9 +46,7 @@ def test_from_array(): data.shape = (12, 2, 1) MicroXS(data, nuclides, reactions) - with pytest.raises(ValueError, match=r'Nuclides list of length \d* and ' - r'reactions array of length \d* do not ' - r'match dimensions of data array of shape \(\d*\, \d*\)'): + with pytest.raises(ValueError, match='Data array must be 3D'): MicroXS(data[:, 0], nuclides, reactions) @@ -96,9 +94,13 @@ def test_multigroup_flux_same(): energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7] flux_per_ev = [0.3, 0.3, 1.0, 1.0] flux = flux_per_ev * np.diff(energies) + flux_sum = flux.sum() microxs_4g = MicroXS.from_multigroup_flux( energies=energies, multigroup_flux=flux, chain_file=chain_file) + # from_multigroup_flux should not modify the flux + assert flux.sum() == flux_sum + # Generate micro XS based on 2-group flux, where the boundaries line up with # the 4 group flux and have the same flux per eV across the full energy # range diff --git a/tests/unit_tests/test_deplete_transfer_rates.py b/tests/unit_tests/test_deplete_transfer_rates.py index 847606be435..140777cd6f9 100644 --- a/tests/unit_tests/test_deplete_transfer_rates.py +++ b/tests/unit_tests/test_deplete_transfer_rates.py @@ -71,6 +71,7 @@ def model(): def test_get_set(model, case_name, transfer_rates): """Tests the get/set methods""" + openmc.reset_auto_ids() op = CoupledOperator(model, CHAIN_PATH) transfer = TransferRates(op, model) diff --git a/tests/unit_tests/test_filter_musurface.py b/tests/unit_tests/test_filter_musurface.py new file mode 100644 index 00000000000..ca0db71f0c6 --- /dev/null +++ b/tests/unit_tests/test_filter_musurface.py @@ -0,0 +1,38 @@ +import openmc + + +def test_musurface(run_in_tmpdir): + sphere = openmc.Sphere(r=1.0, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + model.settings.particles = 1000 + model.settings.batches = 10 + E = 1.0 + model.settings.source = openmc.IndependentSource( + space=openmc.stats.Point(), + angle=openmc.stats.Isotropic(), + energy=openmc.stats.delta_function(E), + ) + model.settings.run_mode = "fixed source" + + filter1 = openmc.MuSurfaceFilter(200) + filter2 = openmc.SurfaceFilter(sphere) + tally = openmc.Tally() + tally.filters = [filter1, filter2] + tally.scores = ['current'] + model.tallies = openmc.Tallies([tally]) + + # Run OpenMC + sp_filename = model.run() + + # Get current binned by mu + with openmc.StatePoint(sp_filename) as sp: + current_mu = sp.tallies[tally.id].mean.ravel() + + # All contributions should show up in last bin + assert current_mu[-1] == 1.0 + for element in current_mu[:-1]: + assert element == 0.0 + + diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 009a9096831..64c16c238ea 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -570,6 +570,12 @@ def test_regular_mesh(lib_init): np.testing.assert_allclose(mesh.volumes, 1.0) + # bounding box + mesh.set_parameters(lower_left=ll, upper_right=ur) + bbox = mesh.bounding_box + np.testing.assert_allclose(bbox.lower_left, ll) + np.testing.assert_allclose(bbox.upper_right, ur) + meshes = openmc.lib.meshes assert isinstance(meshes, Mapping) assert len(meshes) == 1 @@ -650,6 +656,11 @@ def test_rectilinear_mesh(lib_init): np.testing.assert_allclose(mesh.volumes, 1000.0) + # bounding box + bbox = mesh.bounding_box + np.testing.assert_allclose(bbox.lower_left, (-10., 0., 10.)) + np.testing.assert_allclose(bbox.upper_right, (10., 20., 30.)) + with pytest.raises(exc.AllocationError): mesh2 = openmc.lib.RectilinearMesh(mesh.id) @@ -697,6 +708,11 @@ def test_cylindrical_mesh(lib_init): np.testing.assert_allclose(mesh.volumes[::2], 10/360 * pi * 5**2 * 10) np.testing.assert_allclose(mesh.volumes[1::2], 10/360 * pi * (10**2 - 5**2) * 10) + # bounding box + bbox = mesh.bounding_box + np.testing.assert_allclose(bbox.lower_left, (-10., -10., 10.)) + np.testing.assert_allclose(bbox.upper_right, (10., 10., 30.)) + with pytest.raises(exc.AllocationError): mesh2 = openmc.lib.CylindricalMesh(mesh.id) @@ -750,6 +766,11 @@ def test_spherical_mesh(lib_init): np.testing.assert_allclose(mesh.volumes[2::4], f * 5**3 * dtheta(10., 20.)) np.testing.assert_allclose(mesh.volumes[3::4], f * (10**3 - 5**3) * dtheta(10., 20.)) + # bounding box + bbox = mesh.bounding_box + np.testing.assert_allclose(bbox.lower_left, (-10., -10., -10.)) + np.testing.assert_allclose(bbox.upper_right, (10., 10., 10.)) + with pytest.raises(exc.AllocationError): mesh2 = openmc.lib.SphericalMesh(mesh.id) @@ -963,7 +984,8 @@ def test_sample_external_source(run_in_tmpdir, mpi_intracomm): model.settings.source = openmc.IndependentSource( space=openmc.stats.Box([-5., -5., -5.], [5., 5., 5.]), angle=openmc.stats.Monodirectional((0., 0., 1.)), - energy=openmc.stats.Discrete([1.0e5], [1.0]) + energy=openmc.stats.Discrete([1.0e5], [1.0]), + constraints={'fissionable': True} ) model.settings.particles = 1000 model.settings.batches = 10 @@ -993,3 +1015,8 @@ def test_sample_external_source(run_in_tmpdir, mpi_intracomm): assert p1.wgt == p2.wgt openmc.lib.finalize() + + # Make sure sampling works in volume calculation mode + openmc.lib.init(["-c"]) + openmc.lib.sample_external_source(100) + openmc.lib.finalize() diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 44c0730fbd2..c6a07cff977 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -380,6 +380,28 @@ def test_get_nuclide_atom_densities_specific(uo2): assert all_nuc['O16'] == one_nuc['O16'] +def test_get_element_atom_densities(uo2): + for element, density in uo2.get_element_atom_densities().items(): + assert element in ('U', 'O') + assert density > 0 + + +def test_get_element_atom_densities_specific(uo2): + one_nuc = uo2.get_element_atom_densities('O') + assert list(one_nuc.keys()) == ['O'] + assert list(one_nuc.values())[0] > 0 + + one_nuc = uo2.get_element_atom_densities('uranium') + assert list(one_nuc.keys()) == ['U'] + assert list(one_nuc.values())[0] > 0 + + with pytest.raises(ValueError, match='not found'): + uo2.get_element_atom_densities('Li') + + with pytest.raises(ValueError, match='not recognized'): + uo2.get_element_atom_densities('proximium') + + def test_get_nuclide_atoms(): mat = openmc.Material() mat.add_nuclide('Li6', 1.0) @@ -661,3 +683,17 @@ def intensity(src): stable.add_nuclide('Gd156', 1.0) stable.volume = 1.0 assert stable.get_decay_photon_energy() is None + + +def test_avoid_subnormal(run_in_tmpdir): + # Write a materials.xml with a material that has a nuclide density that is + # represented as a subnormal floating point value + mat = openmc.Material() + mat.add_nuclide('H1', 1.0) + mat.add_nuclide('H2', 1.0e-315) + mats = openmc.Materials([mat]) + mats.export_to_xml() + + # When read back in, the density should be zero + mats = openmc.Materials.from_xml() + assert mats[0].get_nuclide_atom_densities()['H2'] == 0.0 diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py deleted file mode 100644 index c50057fcd6f..00000000000 --- a/tests/unit_tests/test_math.py +++ /dev/null @@ -1,247 +0,0 @@ -import numpy as np -import pytest -import scipy as sp -from scipy.stats import shapiro - -import openmc -import openmc.lib - - -def test_t_percentile(): - # Permutations include 1 DoF, 2 DoF, and > 2 DoF - # We will test 5 p-values at 3-DoF values - test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] - test_dfs = [1, 2, 5] - - # The reference solutions come from Scipy - ref_ts = [[sp.stats.t.ppf(p, df) for p in test_ps] for df in test_dfs] - - test_ts = [[openmc.lib.math.t_percentile(p, df) for p in test_ps] - for df in test_dfs] - - # The 5 DoF approximation in openmc.lib.math.t_percentile is off by up to - # 8e-3 from the scipy solution, so test that one separately with looser - # tolerance - assert np.allclose(ref_ts[:-1], test_ts[:-1]) - assert np.allclose(ref_ts[-1], test_ts[-1], atol=1e-2) - - -def test_calc_pn(): - max_order = 10 - test_xs = np.linspace(-1., 1., num=5, endpoint=True) - - # Reference solutions from scipy - ref_vals = np.array([sp.special.eval_legendre(n, test_xs) - for n in range(0, max_order + 1)]) - - test_vals = [] - for x in test_xs: - test_vals.append(openmc.lib.math.calc_pn(max_order, x).tolist()) - - test_vals = np.swapaxes(np.array(test_vals), 0, 1) - - assert np.allclose(ref_vals, test_vals) - - -def test_evaluate_legendre(): - max_order = 10 - # Coefficients are set to 1, but will incorporate the (2l+1)/2 norm factor - # for the reference solution - test_coeffs = [0.5 * (2. * l + 1.) for l in range(max_order + 1)] - test_xs = np.linspace(-1., 1., num=5, endpoint=True) - - ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) - - # Set the coefficients back to 1s for the test values since - # evaluate legendre incorporates the (2l+1)/2 term on its own - test_coeffs = [1. for l in range(max_order + 1)] - - test_vals = np.array([openmc.lib.math.evaluate_legendre(test_coeffs, x) - for x in test_xs]) - - assert np.allclose(ref_vals, test_vals) - - -def test_calc_rn(): - max_order = 10 - test_ns = np.array([i for i in range(0, max_order + 1)]) - azi = 0.1 # Longitude - pol = 0.2 # Latitude - test_uvw = np.array([np.sin(pol) * np.cos(azi), - np.sin(pol) * np.sin(azi), - np.cos(pol)]) - - # Reference solutions from the equations - ref_vals = [] - - def coeff(n, m): - return np.sqrt((2. * n + 1) * sp.special.factorial(n - m) / - (sp.special.factorial(n + m))) - - def pnm_bar(n, m, mu): - val = coeff(n, m) - if m != 0: - val *= np.sqrt(2.) - val *= sp.special.lpmv([m], [n], [mu]) - return val[0] - - ref_vals = [] - for n in test_ns: - for m in range(-n, n + 1): - if m < 0: - ylm = pnm_bar(n, np.abs(m), np.cos(pol)) * \ - np.sin(np.abs(m) * azi) - else: - ylm = pnm_bar(n, m, np.cos(pol)) * np.cos(m * azi) - - # Un-normalize for comparison - ylm /= np.sqrt(2. * n + 1.) - ref_vals.append(ylm) - - test_vals = [] - test_vals = openmc.lib.math.calc_rn(max_order, test_uvw) - - assert np.allclose(ref_vals, test_vals) - - -def test_calc_zn(): - n = 10 - rho = 0.5 - phi = 0.5 - - # Reference solution from running the C++ implementation - ref_vals = np.array([ - 1.00000000e+00, 2.39712769e-01, 4.38791281e-01, - 2.10367746e-01, -5.00000000e-01, 1.35075576e-01, - 1.24686873e-01, -2.99640962e-01, -5.48489101e-01, - 8.84215021e-03, 5.68310892e-02, -4.20735492e-01, - -1.25000000e-01, -2.70151153e-01, -2.60091773e-02, - 1.87022545e-02, -3.42888902e-01, 1.49820481e-01, - 2.74244551e-01, -2.43159131e-02, -2.50357380e-02, - 2.20500013e-03, -1.98908812e-01, 4.07587508e-01, - 4.37500000e-01, 2.61708929e-01, 9.10321205e-02, - -1.54686328e-02, -2.74049397e-03, -7.94845816e-02, - 4.75368705e-01, 7.11647284e-02, 1.30266162e-01, - 3.37106977e-02, 1.06401886e-01, -7.31606787e-03, - -2.95625975e-03, -1.10250006e-02, 3.55194307e-01, - -1.44627826e-01, -2.89062500e-01, -9.28644588e-02, - -1.62557358e-01, 7.73431638e-02, -2.55329539e-03, - -1.90923851e-03, 1.57578403e-02, 1.72995854e-01, - -3.66267690e-01, -1.81657333e-01, -3.32521518e-01, - -2.59738162e-02, -2.31580576e-01, 4.20673902e-02, - -4.11710546e-04, -9.36449487e-04, 1.92156884e-02, - 2.82515641e-02, -3.90713738e-01, -1.69280296e-01, - -8.98437500e-02, -1.08693628e-01, 1.78813094e-01, - -1.98191857e-01, 1.65964201e-02, 2.77013853e-04]) - - test_vals = openmc.lib.math.calc_zn(n, rho, phi) - - assert np.allclose(ref_vals, test_vals) - - -def test_calc_zn_rad(): - n = 10 - rho = 0.5 - - # Reference solution from running the C++ implementation - ref_vals = np.array([ - 1.00000000e+00, -5.00000000e-01, -1.25000000e-01, - 4.37500000e-01, -2.89062500e-01,-8.98437500e-02]) - - test_vals = openmc.lib.math.calc_zn_rad(n, rho) - - assert np.allclose(ref_vals, test_vals) - - -def test_rotate_angle(): - uvw0 = np.array([1., 0., 0.]) - phi = 0. - mu = 0. - - # reference: mu of 0 pulls the vector the bottom, so: - ref_uvw = np.array([0., 0., -1.]) - - test_uvw = openmc.lib.math.rotate_angle(uvw0, mu, phi) - - assert np.array_equal(ref_uvw, test_uvw) - - # Repeat for mu = 1 (no change) - mu = 1. - ref_uvw = np.array([1., 0., 0.]) - - test_uvw = openmc.lib.math.rotate_angle(uvw0, mu, phi) - - assert np.array_equal(ref_uvw, test_uvw) - - # Now to test phi is None - mu = 0.9 - phi = None - prn_seed = 1 - - # When seed = 1, phi will be sampled as 1.9116495709698769 - # The resultant reference is from hand-calculations given the above - ref_uvw = [0.9, -0.422746750548505, 0.10623175090659095] - test_uvw = openmc.lib.math.rotate_angle(uvw0, mu, phi, prn_seed) - - assert np.allclose(ref_uvw, test_uvw) - - -def test_maxwell_spectrum(): - prn_seed = 1 - T = 0.5 - ref_val = 0.27767406743161277 - test_val = openmc.lib.math.maxwell_spectrum(T, prn_seed) - - assert ref_val == test_val - - -def test_watt_spectrum(): - prn_seed = 1 - a = 0.5 - b = 0.75 - ref_val = 0.30957476387766697 - test_val = openmc.lib.math.watt_spectrum(a, b, prn_seed) - - assert ref_val == test_val - - -def test_normal_dist(): - # When standard deviation is zero, sampled value should be mean - prn_seed = 1 - mean = 14.08 - stdev = 0.0 - ref_val = 14.08 - test_val = openmc.lib.math.normal_variate(mean, stdev, prn_seed) - assert ref_val == pytest.approx(test_val) - - # Use Shapiro-Wilk test to ensure normality of sampled vairates - stdev = 1.0 - samples = [] - num_samples = 10000 - for _ in range(num_samples): - # sample the normal distribution from openmc - samples.append(openmc.lib.math.normal_variate(mean, stdev, prn_seed)) - prn_seed += 1 - stat, p = shapiro(samples) - assert p > 0.05 - - -def test_broaden_wmp_polynomials(): - # Two branches of the code to worry about, beta > 6 and otherwise - # beta = sqrtE * dopp - # First lets do beta > 6 - test_E = 0.5 - test_dopp = 100. # approximately U235 at room temperature - n = 6 - - ref_val = [2., 1.41421356, 1.0001, 0.70731891, 0.50030001, 0.353907] - test_val = openmc.lib.math.broaden_wmp_polynomials(test_E, test_dopp, n) - - assert np.allclose(ref_val, test_val) - - # now beta < 6 - test_dopp = 5. - ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959, 0.6224, 0.50346003] - test_val = openmc.lib.math.broaden_wmp_polynomials(test_E, test_dopp, n) - - assert np.allclose(ref_val, test_val) diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 5bacd5fc693..27322165f64 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -189,6 +189,42 @@ def test_CylindricalMesh_initiation(): openmc.SphericalMesh(('🧇', '🥞')) +def test_invalid_cylindrical_mesh_errors(): + # Test invalid r_grid values + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[5, 1], phi_grid=[0, pi], z_grid=[0, 10]) + + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[1, 2, 4, 3], phi_grid=[0, pi], z_grid=[0, 10]) + + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[1], phi_grid=[0, pi], z_grid=[0, 10]) + + # Test invalid phi_grid values + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[-1, 3], z_grid=[0, 10]) + + with pytest.raises(ValueError): + openmc.CylindricalMesh( + r_grid=[0, 1, 2], + phi_grid=[0, 2*pi + 0.1], + z_grid=[0, 10] + ) + + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[pi], z_grid=[0, 10]) + + # Test invalid z_grid values + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[0, pi], z_grid=[5]) + + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[0, pi], z_grid=[5, 1]) + + with pytest.raises(ValueError): + openmc.CylindricalMesh(r_grid=[1, 2, 4, 3], phi_grid=[0, pi], z_grid=[0, 10, 5]) + + def test_centroids(): # regular mesh mesh = openmc.RegularMesh() @@ -321,6 +357,27 @@ def test_CylindricalMesh_get_indices_at_coords(): assert mesh.get_indices_at_coords([102, 199.1, 299]) == (0, 3, 0) # forth angle quadrant +def test_mesh_name_roundtrip(run_in_tmpdir): + + mesh = openmc.RegularMesh() + mesh.name = 'regular-mesh' + mesh.lower_left = (-1, -1, -1) + mesh.width = (1, 1, 1) + mesh.dimension = (1, 1, 1) + + mesh_filter = openmc.MeshFilter(mesh) + tally = openmc.Tally() + tally.filters = [mesh_filter] + tally.scores = ['flux'] + + openmc.Tallies([tally]).export_to_xml() + + xml_tallies = openmc.Tallies.from_xml() + + mesh = xml_tallies[0].find_filter(openmc.MeshFilter).mesh + assert mesh.name == 'regular-mesh' + + def test_umesh_roundtrip(run_in_tmpdir, request): umesh = openmc.UnstructuredMesh(request.path.parent / 'test_mesh_tets.e', 'moab') umesh.output = True @@ -386,3 +443,9 @@ def test_mesh_get_homogenized_materials(): # Mesh element that overlaps void should have half density assert m4.get_mass_density('H1') == pytest.approx(0.5, rel=1e-2) + + # If not including void, density of homogenized material should be same as + # original material + m5, = mesh_void.get_homogenized_materials( + model, n_samples=1000, include_void=False) + assert m5.get_mass_density('H1') == pytest.approx(1.0) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 404235bef1f..4b567c56d62 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -591,3 +591,32 @@ def test_single_xml_exec(run_in_tmpdir): os.mkdir('subdir') pincell_model.run(path='subdir') + + +def test_model_plot(): + # plots the geometry with source location and checks the resulting + # matplotlib includes the correct coordinates for the scatter plot for all + # basis. + + surface = openmc.Sphere(r=600, boundary_type="vacuum") + cell = openmc.Cell(region=-surface) + geometry = openmc.Geometry([cell]) + source = openmc.IndependentSource(space=openmc.stats.Point((1, 2, 3))) + settings = openmc.Settings(particles=1, batches=1, source=source) + model = openmc.Model(geometry, settings=settings) + + plot = model.plot(n_samples=1, plane_tolerance=4.0, basis="xy") + coords = plot.axes.collections[0].get_offsets().data.flatten() + assert (coords == np.array([1.0, 2.0])).all() + + plot = model.plot(n_samples=1, plane_tolerance=4.0, basis="xz") + coords = plot.axes.collections[0].get_offsets().data.flatten() + assert (coords == np.array([1.0, 3.0])).all() + + plot = model.plot(n_samples=1, plane_tolerance=4.0, basis="yz") + coords = plot.axes.collections[0].get_offsets().data.flatten() + assert (coords == np.array([2.0, 3.0])).all() + + plot = model.plot(n_samples=1, plane_tolerance=0.1, basis="xy") + coords = plot.axes.collections[0].get_offsets().data.flatten() + assert (coords == np.array([])).all() diff --git a/tests/unit_tests/test_plots.py b/tests/unit_tests/test_plots.py index 922e4d9dc95..a1e15016a2e 100644 --- a/tests/unit_tests/test_plots.py +++ b/tests/unit_tests/test_plots.py @@ -11,7 +11,7 @@ def myplot(): plot.width = (100., 100.) plot.origin = (2., 3., -10.) plot.pixels = (500, 500) - plot.filename = 'myplot' + plot.filename = './not-a-dir/myplot' plot.type = 'slice' plot.basis = 'yz' plot.background = 'black' @@ -221,3 +221,26 @@ def test_voxel_plot_roundtrip(): assert new_plot.origin == plot.origin assert new_plot.width == plot.width assert new_plot.color_by == plot.color_by + + +def test_plot_directory(run_in_tmpdir): + pwr_pin = openmc.examples.pwr_pin_cell() + + # create a standard plot, expected to work + plot = openmc.Plot() + plot.filename = 'plot_1' + plot.type = 'slice' + plot.pixels = (10, 10) + plot.color_by = 'material' + plot.width = (100., 100.) + pwr_pin.plots = [plot] + pwr_pin.plot_geometry() + + # use current directory, also expected to work + plot.filename = './plot_1' + pwr_pin.plot_geometry() + + # use a non-existent directory, should raise an error + plot.filename = './not-a-dir/plot_1' + with pytest.raises(RuntimeError, match='does not exist'): + pwr_pin.plot_geometry() diff --git a/tests/unit_tests/test_plotter.py b/tests/unit_tests/test_plotter.py index 2c195c5e120..0220cec3e71 100644 --- a/tests/unit_tests/test_plotter.py +++ b/tests/unit_tests/test_plotter.py @@ -75,7 +75,7 @@ def test_calculate_cexs_with_materials(test_mat): @pytest.mark.parametrize("this", ["Be", "Be9"]) def test_plot_xs(this): from matplotlib.figure import Figure - assert isinstance(openmc.plot_xs({this: ['total', 'elastic']}), Figure) + assert isinstance(openmc.plot_xs({this: ['total', 'elastic', 16, '(n,2n)']}), Figure) def test_plot_xs_mat(test_mat): diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py index 8c9e0afe4d9..cbcd1983125 100644 --- a/tests/unit_tests/test_region.py +++ b/tests/unit_tests/test_region.py @@ -106,7 +106,7 @@ def test_complement(reset): assert_unbounded(outside_equiv) # string represention - assert str(inside) == '~(1 | -2 | 3)' + assert str(inside) == '(-1 2 -3)' # evaluate method assert (0, 0, 0) in inside diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 650bfd18680..02a47625162 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -91,7 +91,7 @@ def test_export_to_xml(run_in_tmpdir): assert s.sourcepoint == {'batches': [50, 150, 500, 1000], 'separate': True, 'write': True, 'overwrite': True, 'mcpl': True} assert s.statepoint == {'batches': [50, 150, 500, 1000]} - assert s.surf_source_read == {'path': 'surface_source_1.h5'} + assert s.surf_source_read['path'].name == 'surface_source_1.h5' assert s.surf_source_write == {'surface_ids': [2], 'max_particles': 200} assert s.confidence_intervals assert s.ptables diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py index 9a19f6f24dd..c88fbcbe62d 100644 --- a/tests/unit_tests/test_source.py +++ b/tests/unit_tests/test_source.py @@ -1,3 +1,4 @@ +from collections import Counter from math import pi import openmc @@ -49,11 +50,66 @@ def test_spherical_uniform(): assert isinstance(sph_indep_function, openmc.stats.SphericalIndependent) +def test_point_cloud(): + positions = [(1, 0, 2), (0, 1, 0), (0, 0, 3), (4, 9, 2)] + strengths = [1, 2, 3, 4] + + space = openmc.stats.PointCloud(positions, strengths) + np.testing.assert_equal(space.positions, positions) + np.testing.assert_equal(space.strengths, strengths) + + src = openmc.IndependentSource(space=space) + assert src.space == space + np.testing.assert_equal(src.space.positions, positions) + np.testing.assert_equal(src.space.strengths, strengths) + + elem = src.to_xml_element() + src = openmc.IndependentSource.from_xml_element(elem) + np.testing.assert_equal(src.space.positions, positions) + np.testing.assert_equal(src.space.strengths, strengths) + + +def test_point_cloud_invalid(): + with pytest.raises(ValueError, match='2D'): + openmc.stats.PointCloud([1, 0, 2, 0, 1, 0]) + + with pytest.raises(ValueError, match='3 values'): + openmc.stats.PointCloud([(1, 0, 2, 3), (4, 5, 2, 3)]) + + with pytest.raises(ValueError, match='1D'): + openmc.stats.PointCloud([(1, 0, 2), (4, 5, 2)], [(1, 2), (3, 4)]) + + with pytest.raises(ValueError, match='same length'): + openmc.stats.PointCloud([(1, 0, 2), (4, 5, 2)], [1, 2, 4]) + + +def test_point_cloud_strengths(run_in_tmpdir, sphere_box_model): + positions = [(1., 0., 2.), (0., 1., 0.), (0., 0., 3.), (-1., -1., 2.)] + strengths = [1, 2, 3, 4] + space = openmc.stats.PointCloud(positions, strengths) + + model = sphere_box_model[0] + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.IndependentSource(space=space) + + try: + model.init_lib() + n_samples = 50_000 + sites = openmc.lib.sample_external_source(n_samples) + finally: + model.finalize_lib() + + count = Counter(s.r for s in sites) + for i, (strength, position) in enumerate(zip(strengths, positions)): + sampled_strength = count[position] / n_samples + expected_strength = pytest.approx(strength/sum(strengths), abs=0.02) + assert sampled_strength == expected_strength, f'Strength incorrect for {positions[i]}' + def test_source_file(): filename = 'source.h5' src = openmc.FileSource(path=filename) - assert src.path == filename + assert src.path.name == filename elem = src.to_xml_element() assert 'strength' in elem.attrib @@ -61,9 +117,9 @@ def test_source_file(): def test_source_dlopen(): - library = './libsource.so' - src = openmc.CompiledSource(library=library) - assert src.library == library + library = 'libsource.so' + src = openmc.CompiledSource(library) + assert src.library.name == library elem = src.to_xml_element() assert 'library' in elem.attrib diff --git a/tests/unit_tests/test_source_file.py b/tests/unit_tests/test_source_file.py index 1b5549b008e..41906c80f83 100644 --- a/tests/unit_tests/test_source_file.py +++ b/tests/unit_tests/test_source_file.py @@ -44,11 +44,9 @@ def test_source_file(run_in_tmpdir): assert np.all(arr['delayed_group'] == 0) assert np.all(arr['particle'] == 0) - # Ensure sites read in are consistent - sites = openmc.read_source_file('test_source.h5') + sites = openmc.ParticleList.from_hdf5('test_source.h5') - assert filetype == b'source' xs = np.array([site.r[0] for site in sites]) ys = np.array([site.r[1] for site in sites]) zs = np.array([site.r[2] for site in sites]) @@ -68,6 +66,32 @@ def test_source_file(run_in_tmpdir): p_types = np.array([s.particle for s in sites]) assert np.all(p_types == 0) + # Ensure a ParticleList item is a SourceParticle + site = sites[0] + assert isinstance(site, openmc.SourceParticle) + assert site.E == pytest.approx(n) + + # Ensure site slice read in and exported are consistent + sites_slice = sites[:10] + sites_slice.export_to_hdf5("test_source_slice.h5") + sites_slice = openmc.ParticleList.from_hdf5('test_source_slice.h5') + + assert isinstance(sites_slice, openmc.ParticleList) + assert len(sites_slice) == 10 + E = np.array([s.E for s in sites_slice]) + np.testing.assert_allclose(E, n - np.arange(10)) + + # Ensure site list read in and exported are consistent + df = sites.to_dataframe() + sites_filtered = sites[df[df.E <= 10.0].index.tolist()] + sites_filtered.export_to_hdf5("test_source_filtered.h5") + sites_filtered = openmc.read_source_file('test_source_filtered.h5') + + assert isinstance(sites_filtered, openmc.ParticleList) + assert len(sites_filtered) == 10 + E = np.array([s.E for s in sites_filtered]) + np.testing.assert_allclose(E, np.arange(10, 0, -1)) + def test_wrong_source_attributes(run_in_tmpdir): # Create a source file with animal attributes diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index f5b467d0380..643e115564b 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -50,6 +50,13 @@ def test_discrete(): assert_sample_mean(samples, exp_mean) +def test_delta_function(): + d = openmc.stats.delta_function(14.1e6) + assert isinstance(d, openmc.stats.Discrete) + np.testing.assert_array_equal(d.x, [14.1e6]) + np.testing.assert_array_equal(d.p, [1.0]) + + def test_merge_discrete(): x1 = [0.0, 1.0, 10.0] p1 = [0.3, 0.2, 0.5] @@ -255,7 +262,7 @@ def test_mixture(): d2 = openmc.stats.Uniform(3, 7) p = [0.5, 0.5] mix = openmc.stats.Mixture(p, [d1, d2]) - assert mix.probability == p + np.testing.assert_allclose(mix.probability, p) assert mix.distribution == [d1, d2] assert len(mix) == 4 @@ -267,7 +274,7 @@ def test_mixture(): elem = mix.to_xml_element('distribution') d = openmc.stats.Mixture.from_xml_element(elem) - assert d.probability == p + np.testing.assert_allclose(d.probability, p) assert d.distribution == [d1, d2] assert len(d) == 4 @@ -289,6 +296,20 @@ def test_mixture_clip(): mix_same = mix.clip(1e-6, inplace=True) assert mix_same is mix + # Make sure clip removes low probability distributions + d_small = openmc.stats.Uniform(0., 1.) + d_large = openmc.stats.Uniform(2., 5.) + mix = openmc.stats.Mixture([1e-10, 1.0], [d_small, d_large]) + mix_clip = mix.clip(1e-3) + assert mix_clip.distribution == [d_large] + + # Make sure warning is raised if tolerance is exceeded + d1 = openmc.stats.Discrete([1.0, 1.001], [1.0, 0.7e-6]) + d2 = openmc.stats.Tabular([0.0, 1.0], [0.7e-6], interpolation='histogram') + mix = openmc.stats.Mixture([1.0, 1.0], [d1, d2]) + with pytest.warns(UserWarning): + mix_clip = mix.clip(1e-6) + def test_polar_azimuthal(): # default polar-azimuthal should be uniform in mu and phi @@ -376,15 +397,6 @@ def test_box(): d = openmc.stats.Box.from_xml_element(elem) assert d.lower_left == pytest.approx(lower_left) assert d.upper_right == pytest.approx(upper_right) - assert not d.only_fissionable - - # only fissionable parameter - d2 = openmc.stats.Box(lower_left, upper_right, True) - assert d2.only_fissionable - elem = d2.to_xml_element() - assert elem.attrib['type'] == 'fission' - d = openmc.stats.Spatial.from_xml_element(elem) - assert isinstance(d, openmc.stats.Box) def test_point(): diff --git a/tests/unit_tests/test_surface_composite.py b/tests/unit_tests/test_surface_composite.py index 62ec18b3261..da7ffbcc470 100644 --- a/tests/unit_tests/test_surface_composite.py +++ b/tests/unit_tests/test_surface_composite.py @@ -255,7 +255,7 @@ def test_cylinder_sector_from_theta_alpha(): @pytest.mark.parametrize( "axis, plane_tb, plane_lr, axis_idx", [ ("x", "Z", "Y", 0), - ("y", "X", "Z", 1), + ("y", "Z", "X", 1), ("z", "Y", "X", 2), ] ) @@ -347,10 +347,13 @@ def test_polygon(): assert any([points_in[i] in reg for reg in star_poly.regions]) assert points_in[i] not in +star_poly assert (0, 0, 0) not in -star_poly - if basis != 'rz': - offset_star = star_poly.offset(.6) - assert (0, 0, 0) in -offset_star - assert any([(0, 0, 0) in reg for reg in offset_star.regions]) + if basis != "rz": + for offsets in [0.6, np.array([0.6] * 10), [0.6] * 10]: + offset_star = star_poly.offset(offsets) + assert (0, 0, 0) in -offset_star + assert any([(0, 0, 0) in reg for reg in offset_star.regions]) + with pytest.raises(ValueError): + star_poly.offset([0.6, 0.6]) # check invalid Polygon input points # duplicate points not just at start and end @@ -498,3 +501,150 @@ def test_cruciform_prism(axis): openmc.model.CruciformPrism([1.0, 0.5, 2.0, 3.0]) with pytest.raises(ValueError): openmc.model.CruciformPrism([3.0, 2.0, 0.5, 1.0]) + + +def test_box(): + v = (-1.0, -1.0, -2.5) + a1 = (2.0, -1.0, 0.0) + a2 = (1.0, 2.0, 0.0) + a3 = (0.0, 0.0, 5.0) + s = openmc.model.OrthogonalBox(v, a1, a2, a3) + for num in (1, 2, 3): + assert isinstance(getattr(s, f'ax{num}_min'), openmc.Plane) + assert isinstance(getattr(s, f'ax{num}_max'), openmc.Plane) + + # Make sure boundary condition propagates + s.boundary_type = 'reflective' + assert s.boundary_type == 'reflective' + for num in (1, 2, 3): + assert getattr(s, f'ax{num}_min').boundary_type == 'reflective' + assert getattr(s, f'ax{num}_max').boundary_type == 'reflective' + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll[2] == pytest.approx(-2.5) + assert ur[2] == pytest.approx(2.5) + + # __contains__ on associated half-spaces + assert (0., 0., 0.) in -s + assert (-2., 0., 0.) not in -s + assert (0., 0.9, 0.) in -s + assert (0., 0., -3.) in +s + assert (0., 0., 3.) in +s + + # translate method + s_t = s.translate((1., 1., 0.)) + assert (-0.01, 0., 0.) in +s_t + assert (0.01, 0., 0.) in -s_t + + # Make sure repr works + repr(s) + + # Version with infinite 3rd dimension + s = openmc.model.OrthogonalBox(v, a1, a2) + assert not hasattr(s, 'ax3_min') + assert not hasattr(s, 'ax3_max') + ll, ur = (-s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + assert (0., 0., 0.) in -s + assert (-2., 0., 0.) not in -s + assert (0., 0.9, 0.) in -s + assert (0., 0., -3.) not in +s + assert (0., 0., 3.) not in +s + + +def test_conical_frustum(): + center_base = (0.0, 0.0, -3) + axis = (0., 0., 3.) + r1 = 2.0 + r2 = 0.5 + s = openmc.model.ConicalFrustum(center_base, axis, r1, r2) + assert isinstance(s.cone, openmc.Cone) + assert isinstance(s.plane_bottom, openmc.Plane) + assert isinstance(s.plane_top, openmc.Plane) + + # Make sure boundary condition propagates + s.boundary_type = 'reflective' + assert s.boundary_type == 'reflective' + assert s.cone.boundary_type == 'reflective' + assert s.plane_bottom.boundary_type == 'reflective' + assert s.plane_top.boundary_type == 'reflective' + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll[2] == pytest.approx(-3.0) + assert ur[2] == pytest.approx(0.0) + + # __contains__ on associated half-spaces + assert (0., 0., -1.) in -s + assert (0., 0., -4.) not in -s + assert (0., 0., 1.) not in -s + assert (1., 1., -2.99) in -s + assert (1., 1., -0.01) in +s + + # translate method + s_t = s.translate((1., 1., 0.)) + assert (1., 1., -0.01) in -s_t + + # Make sure repr works + repr(s) + + # Denegenerate case with r1 = r2 + s = openmc.model.ConicalFrustum(center_base, axis, r1, r1) + assert (1., 1., -0.01) in -s + + +def test_vessel(): + center = (3.0, 2.0) + r = 1.0 + p1, p2 = -5.0, 5.0 + h1 = h2 = 1.0 + s = openmc.model.Vessel(r, p1, p2, h1, h2, center) + assert isinstance(s.cyl, openmc.Cylinder) + assert isinstance(s.plane_bottom, openmc.Plane) + assert isinstance(s.plane_top, openmc.Plane) + assert isinstance(s.bottom, openmc.Quadric) + assert isinstance(s.top, openmc.Quadric) + + # Make sure boundary condition propagates (but not for planes) + s.boundary_type = 'reflective' + assert s.boundary_type == 'reflective' + assert s.cyl.boundary_type == 'reflective' + assert s.bottom.boundary_type == 'reflective' + assert s.top.boundary_type == 'reflective' + assert s.plane_bottom.boundary_type == 'transmission' + assert s.plane_top.boundary_type == 'transmission' + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + + # __contains__ on associated half-spaces + assert (3., 2., 0.) in -s + assert (3., 2., -5.0) in -s + assert (3., 2., 5.0) in -s + assert (3., 2., -5.9) in -s + assert (3., 2., 5.9) in -s + assert (3., 2., -6.1) not in -s + assert (3., 2., 6.1) not in -s + assert (4.5, 2., 0.) in +s + assert (3., 3.2, 0.) in +s + assert (3., 2., 7.) in +s + + # translate method + s_t = s.translate((0., 0., 1.)) + assert (3., 2., 6.1) in -s_t + + # Make sure repr works + repr(s) diff --git a/tests/unit_tests/test_surface_source_write.py b/tests/unit_tests/test_surface_source_write.py new file mode 100644 index 00000000000..6f18d32b718 --- /dev/null +++ b/tests/unit_tests/test_surface_source_write.py @@ -0,0 +1,301 @@ +"""Test the 'surf_source_write' setting used to store particles that cross +surfaces in a file for a given simulation.""" + +from pathlib import Path + +import openmc +import openmc.lib +import pytest +import h5py +import numpy as np + + +@pytest.fixture(scope="module") +def geometry(): + """Simple hydrogen sphere geometry""" + openmc.reset_auto_ids() + material = openmc.Material(name="H1") + material.add_element("H", 1.0) + sphere = openmc.Sphere(r=1.0, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=material) + return openmc.Geometry([cell]) + + +@pytest.mark.parametrize( + "parameter", + [ + {"max_particles": 200}, + {"max_particles": 200, "cell": 1}, + {"max_particles": 200, "cellto": 1}, + {"max_particles": 200, "cellfrom": 1}, + {"max_particles": 200, "surface_ids": [2]}, + {"max_particles": 200, "surface_ids": [2], "cell": 1}, + {"max_particles": 200, "surface_ids": [2], "cellto": 1}, + {"max_particles": 200, "surface_ids": [2], "cellfrom": 1}, + {"max_particles": 200, "surface_ids": [2], "max_source_files": 1}, + ], +) +def test_xml_serialization(parameter, run_in_tmpdir): + """Check that the different use cases can be written and read in XML.""" + settings = openmc.Settings() + settings.surf_source_write = parameter + settings.export_to_xml() + + read_settings = openmc.Settings.from_xml() + assert read_settings.surf_source_write == parameter + + +@pytest.fixture(scope="module") +def model(): + """Simple hydrogen sphere geometry""" + openmc.reset_auto_ids() + model = openmc.Model() + + # Material + h1 = openmc.Material(name="H1") + h1.add_nuclide("H1", 1.0) + h1.set_density('g/cm3', 1e-7) + + # Geometry + radius = 1.0 + sphere = openmc.Sphere(r=radius, boundary_type="vacuum") + cell = openmc.Cell(region=-sphere, fill=h1) + model.geometry = openmc.Geometry([cell]) + + # Settings + model.settings = openmc.Settings() + model.settings.run_mode = "fixed source" + model.settings.particles = 100 + model.settings.batches = 3 + model.settings.seed = 1 + + distribution = openmc.stats.Point() + model.settings.source = openmc.IndependentSource(space=distribution) + return model + + +@pytest.mark.parametrize( + "max_particles, max_source_files", + [ + (100, 2), + (100, 3), + (100, 1), + ], +) +def test_number_surface_source_file_created(max_particles, max_source_files, + run_in_tmpdir, model): + """Check the number of surface source files written.""" + model.settings.surf_source_write = { + "max_particles": max_particles, + "max_source_files": max_source_files + } + model.run() + should_be_numbered = max_source_files > 1 + for i in range(1, max_source_files + 1): + if should_be_numbered: + assert Path(f"surface_source.{i}.h5").exists() + if not should_be_numbered: + assert Path("surface_source.h5").exists() + +ERROR_MSG_1 = ( + "A maximum number of particles needs to be specified " + "using the 'max_particles' parameter to store surface " + "source points." +) +ERROR_MSG_2 = "'cell', 'cellfrom' and 'cellto' cannot be used at the same time." + + +@pytest.mark.parametrize( + "parameter, error", + [ + ({"cell": 1}, ERROR_MSG_1), + ({"max_particles": 200, "cell": 1, "cellto": 1}, ERROR_MSG_2), + ({"max_particles": 200, "cell": 1, "cellfrom": 1}, ERROR_MSG_2), + ({"max_particles": 200, "cellto": 1, "cellfrom": 1}, ERROR_MSG_2), + ({"max_particles": 200, "cell": 1, "cellto": 1, "cellfrom": 1}, ERROR_MSG_2), + ], +) +def test_exceptions(parameter, error, run_in_tmpdir, geometry): + """Test parameters configuration that should return an error.""" + settings = openmc.Settings(run_mode="fixed source", batches=5, particles=100) + settings.surf_source_write = parameter + model = openmc.Model(geometry=geometry, settings=settings) + with pytest.raises(RuntimeError, match=error): + model.run() + + +@pytest.fixture(scope="module") +def model(): + """Simple hydrogen sphere divided in two hemispheres + by a z-plane to form 2 cells.""" + openmc.reset_auto_ids() + model = openmc.Model() + + # Material + material = openmc.Material(name="H1") + material.add_element("H", 1.0) + + # Geometry + radius = 1.0 + sphere = openmc.Sphere(r=radius, boundary_type="reflective") + plane = openmc.ZPlane(0.0) + cell_1 = openmc.Cell(region=-sphere & -plane, fill=material) + cell_2 = openmc.Cell(region=-sphere & +plane, fill=material) + root = openmc.Universe(cells=[cell_1, cell_2]) + model.geometry = openmc.Geometry(root) + + # Settings + model.settings = openmc.Settings() + model.settings.run_mode = "fixed source" + model.settings.particles = 100 + model.settings.batches = 3 + model.settings.seed = 1 + + bounds = [-radius, -radius, -radius, radius, radius, radius] + distribution = openmc.stats.Box(bounds[:3], bounds[3:]) + model.settings.source = openmc.IndependentSource(space=distribution) + + return model + + +@pytest.mark.parametrize( + "parameter", + [ + {"max_particles": 200, "cellto": 2, "surface_ids": [2]}, + {"max_particles": 200, "cellfrom": 2, "surface_ids": [2]}, + ], +) +def test_particle_direction(parameter, run_in_tmpdir, model): + """Test the direction of particles with the 'cellfrom' and 'cellto' parameters + on a simple model with only one surface of interest. + + Cell 2 is the upper hemisphere and surface 2 is the plane dividing the sphere + into two hemispheres. + + """ + model.settings.surf_source_write = parameter + model.run() + with h5py.File("surface_source.h5", "r") as f: + source = f["source_bank"] + + assert len(source) == 200 + + # We want to verify that the dot product of the surface's normal vector + # and the direction of the particle is either positive or negative + # depending on cellfrom or cellto. In this case, it is equivalent + # to just compare the z component of the direction of the particle. + for point in source: + if "cellto" in parameter.keys(): + assert point["u"]["z"] > 0.0 + elif "cellfrom" in parameter.keys(): + assert point["u"]["z"] < 0.0 + else: + assert False + + +@pytest.fixture +def model_dagmc(request): + """Model based on the mesh file 'dagmc.h5m' available from + tests/regression_tests/dagmc/legacy. + + """ + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + u235 = openmc.Material(name="no-void fuel") + u235.add_nuclide("U235", 1.0, "ao") + u235.set_density("g/cc", 11) + u235.id = 40 + + water = openmc.Material(name="water") + water.add_nuclide("H1", 2.0, "ao") + water.add_nuclide("O16", 1.0, "ao") + water.set_density("g/cc", 1.0) + water.add_s_alpha_beta("c_H_in_H2O") + water.id = 41 + + model.materials = openmc.Materials([u235, water]) + + # ============================================================================= + # Geometry + # ============================================================================= + dagmc_path = Path(request.fspath).parent / "../regression_tests/dagmc/legacy/dagmc.h5m" + dagmc_univ = openmc.DAGMCUniverse(dagmc_path) + model.geometry = openmc.Geometry(dagmc_univ) + + # ============================================================================= + # Settings + # ============================================================================= + + model.settings = openmc.Settings() + model.settings.particles = 300 + model.settings.batches = 5 + model.settings.inactive = 1 + model.settings.seed = 1 + + source_box = openmc.stats.Box([-4, -4, -20], [4, 4, 20]) + model.settings.source = openmc.IndependentSource(space=source_box) + + return model + + +@pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." +) +@pytest.mark.parametrize( + "parameter", + [ + {"max_particles": 200, "cellto": 1}, + {"max_particles": 200, "cellfrom": 1}, + ], +) +def test_particle_direction_dagmc(parameter, run_in_tmpdir, model_dagmc): + """Test the direction of particles with the 'cellfrom' and 'cellto' parameters + on a DAGMC model.""" + model_dagmc.settings.surf_source_write = parameter + model_dagmc.run() + + r = 7.0 + h = 20.0 + + with h5py.File("surface_source.h5", "r") as f: + source = f["source_bank"] + + assert len(source) == 200 + + for point in source: + + x, y, z = point["r"] + ux, uy, uz = point["u"] + + # If the point is on the upper or lower circle + if np.allclose(abs(z), h): + # If the point is also on the cylindrical surface + if np.allclose(np.sqrt(x**2 + y**2), r): + if "cellfrom" in parameter.keys(): + assert (uz * z > 0) or (ux * x + uy * y > 0) + elif "cellto" in parameter.keys(): + assert (uz * z < 0) or (ux * x + uy * y < 0) + else: + assert False + # If the point is not on the cylindrical surface + else: + if "cellfrom" in parameter.keys(): + assert uz * z > 0 + elif "cellto" in parameter.keys(): + assert uz * z < 0 + else: + assert False + # If the point is not on the upper or lower circle, + # meaning it is on the cylindrical surface + else: + if "cellfrom" in parameter.keys(): + assert ux * x + uy * y > 0 + elif "cellto" in parameter.keys(): + assert ux * x + uy * y < 0 + else: + assert False diff --git a/tests/unit_tests/test_tally_multiply_density.py b/tests/unit_tests/test_tally_multiply_density.py index 66ef41e0dac..552d76bd52f 100644 --- a/tests/unit_tests/test_tally_multiply_density.py +++ b/tests/unit_tests/test_tally_multiply_density.py @@ -3,7 +3,7 @@ import pytest -def test_micro_macro_compare(): +def test_micro_macro_compare(run_in_tmpdir): # Create simple sphere model with H1 and H2 mat = openmc.Material() mat.add_components({'H1': 1.0, 'H2': 1.0}) diff --git a/tests/unit_tests/test_temp_interp.py b/tests/unit_tests/test_temp_interp.py index 4c2882347b3..4566070cfd9 100644 --- a/tests/unit_tests/test_temp_interp.py +++ b/tests/unit_tests/test_temp_interp.py @@ -152,7 +152,7 @@ def model(tmp_path_factory): mat = openmc.Material() mat.add_nuclide('U235', 1.0) model.materials.append(mat) - model.materials.cross_sections = str(Path('cross_sections_fake.xml').resolve()) + model.materials.cross_sections = 'cross_sections_fake.xml' sph = openmc.Sphere(r=100.0, boundary_type='reflective') cell = openmc.Cell(fill=mat, region=-sph) @@ -257,7 +257,7 @@ def test_temperature_slightly_above(run_in_tmpdir): mat2.add_nuclide('U235', 1.0) mat2.temperature = 600.0 model.materials.extend([mat1, mat2]) - model.materials.cross_sections = str(Path('cross_sections_fake.xml').resolve()) + model.materials.cross_sections = 'cross_sections_fake.xml' sph1 = openmc.Sphere(r=1.0) sph2 = openmc.Sphere(r=4.0, boundary_type='reflective') diff --git a/tests/unit_tests/test_tracks.py b/tests/unit_tests/test_tracks.py index 3951a72c63c..fa866415984 100644 --- a/tests/unit_tests/test_tracks.py +++ b/tests/unit_tests/test_tracks.py @@ -168,7 +168,7 @@ def test_restart_track(run_in_tmpdir, sphere_model): # generate lost particle files with pytest.raises(RuntimeError, match='Maximum number of lost particles has been reached.'): - sphere_model.run(output=False) + sphere_model.run(output=False, threads=1) lost_particle_files = list(Path.cwd().glob('particle_*.h5')) assert len(lost_particle_files) > 0 diff --git a/tests/unit_tests/test_triggers.py b/tests/unit_tests/test_triggers.py index 6b9e54eeb72..14bda0cceb3 100644 --- a/tests/unit_tests/test_triggers.py +++ b/tests/unit_tests/test_triggers.py @@ -113,3 +113,34 @@ def test_tally_trigger_zero_ignored(run_in_tmpdir): total_batches = sp.n_realizations + sp.n_inactive assert total_batches < pincell.settings.trigger_max_batches + + +def test_trigger_he3_production(run_in_tmpdir): + li6 = openmc.Material() + li6.set_density('g/cm3', 1.0) + li6.add_nuclide('Li6', 1.0) + + sph = openmc.Sphere(r=20, boundary_type='vacuum') + outer_cell = openmc.Cell(fill=li6, region=-sph) + model = openmc.Model() + model.geometry = openmc.Geometry([outer_cell]) + model.settings.source = openmc.IndependentSource( + energy=openmc.stats.delta_function(14.1e6) + ) + model.settings.batches = 10 + model.settings.particles = 100 + model.settings.run_mode = 'fixed source' + model.settings.trigger_active = True + model.settings.trigger_batch_interval = 10 + model.settings.trigger_max_batches = 30 + + # Define tally with trigger + trigger = openmc.Trigger(trigger_type='rel_err', threshold=0.0001) + trigger.scores = ['He3-production'] + he3_production_tally = openmc.Tally() + he3_production_tally.scores = ['He3-production'] + he3_production_tally.triggers = [trigger] + model.tallies = openmc.Tallies([he3_production_tally]) + + # Run model to verify that trigger works + model.run() diff --git a/tests/unit_tests/test_uniform_source_sampling.py b/tests/unit_tests/test_uniform_source_sampling.py new file mode 100644 index 00000000000..7f805e37d27 --- /dev/null +++ b/tests/unit_tests/test_uniform_source_sampling.py @@ -0,0 +1,75 @@ +import openmc +import pytest + + +@pytest.fixture +def sphere_model(): + mat = openmc.Material() + mat.add_nuclide('Li6', 1.0) + mat.set_density('g/cm3', 1.0) + sphere = openmc.Sphere(r=1.0, boundary_type='vacuum') + cell = openmc.Cell(region=-sphere, fill=mat) + model = openmc.Model() + model.geometry = openmc.Geometry([cell]) + + model.settings.particles = 100 + model.settings.batches = 1 + model.settings.source = openmc.IndependentSource( + energy=openmc.stats.delta_function(1.0e3), + strength=100.0 + ) + model.settings.run_mode = "fixed source" + model.settings.surf_source_write = { + "max_particles": 100, + } + + tally = openmc.Tally() + tally.scores = ['flux'] + model.tallies = [tally] + return model + + +def test_source_weight(run_in_tmpdir, sphere_model): + # Run OpenMC without uniform source sampling and check that banked particles + # have weight 1 + sphere_model.settings.uniform_source_sampling = False + sphere_model.run() + particles = openmc.ParticleList.from_hdf5('surface_source.h5') + assert set(p.wgt for p in particles) == {1.0} + + # Run with uniform source sampling and check that banked particles have + # weight == strength + sphere_model.settings.uniform_source_sampling = True + sphere_model.run() + particles = openmc.ParticleList.from_hdf5('surface_source.h5') + strength = sphere_model.settings.source[0].strength + assert set(p.wgt for p in particles) == {strength} + + +def test_tally_mean(run_in_tmpdir, sphere_model): + # Run without uniform source sampling + sphere_model.settings.uniform_source_sampling = False + sp_file = sphere_model.run() + with openmc.StatePoint(sp_file) as sp: + reference_mean = sp.tallies[sphere_model.tallies[0].id].mean + + # Run with uniform source sampling + sphere_model.settings.uniform_source_sampling = True + sp_file = sphere_model.run() + with openmc.StatePoint(sp_file) as sp: + mean = sp.tallies[sphere_model.tallies[0].id].mean + + # Check that tally means match + assert mean == pytest.approx(reference_mean) + + +def test_multiple_sources(sphere_model): + low_strength_src = openmc.IndependentSource( + energy=openmc.stats.delta_function(1.0e6), strength=1e-7) + sphere_model.settings.source.append(low_strength_src) + sphere_model.settings.uniform_source_sampling = True + + # Sample particles from source and make sure 1 MeV shows up despite + # negligible strength + particles = sphere_model.sample_external_source(100) + assert {p.E for p in particles} == {1.0e3, 1.0e6} diff --git a/tests/unit_tests/weightwindows/test.py b/tests/unit_tests/weightwindows/test.py index 8af843cbdc0..79aadbef0de 100644 --- a/tests/unit_tests/weightwindows/test.py +++ b/tests/unit_tests/weightwindows/test.py @@ -94,7 +94,7 @@ def model(): settings.run_mode = 'fixed source' settings.particles = 500 settings.batches = 2 - settings.max_splits = 100 + settings.max_history_splits = 100 settings.photon_transport = True space = Point((0.001, 0.001, 0.001)) energy = Discrete([14E6], [1.0]) @@ -296,4 +296,40 @@ def test_ww_attrs_capi(run_in_tmpdir, model): assert wws.id == 2 assert wws.particle == openmc.ParticleType.PHOTON - openmc.lib.finalize() \ No newline at end of file + openmc.lib.finalize() + + +@pytest.mark.parametrize('library', ('libmesh', 'moab')) +def test_unstructured_mesh_applied_wws(request, run_in_tmpdir, library): + """ + Ensure that weight windows on unstructured mesh work when + they aren't part of a tally or weight window generator + """ + + if library == 'libmesh' and not openmc.lib._libmesh_enabled(): + pytest.skip('LibMesh not enabled in this build.') + if library == 'moab' and not openmc.lib._dagmc_enabled(): + pytest.skip('DAGMC (and MOAB) mesh not enabled in this build.') + + water = openmc.Material(name='water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cc', 1.0) + box = openmc.model.RectangularParallelepiped(*(3*[-10, 10]), boundary_type='vacuum') + cell = openmc.Cell(region=-box, fill=water) + + geometry = openmc.Geometry([cell]) + mesh_file = str(request.fspath.dirpath() / 'test_mesh_tets.exo') + mesh = openmc.UnstructuredMesh(mesh_file, library) + + dummy_wws = np.ones((12_000,)) + + wws = openmc.WeightWindows(mesh, dummy_wws, upper_bound_ratio=5.0) + + model = openmc.Model(geometry) + model.settings.weight_windows = wws + model.settings.weight_windows_on = True + model.settings.run_mode = 'fixed source' + model.settings.particles = 100 + model.settings.batches = 2 + model.run() diff --git a/tests/unit_tests/weightwindows/test_mesh_tets.exo b/tests/unit_tests/weightwindows/test_mesh_tets.exo new file mode 120000 index 00000000000..5bf23b369a3 --- /dev/null +++ b/tests/unit_tests/weightwindows/test_mesh_tets.exo @@ -0,0 +1 @@ +../../regression_tests/unstructured_mesh/test_mesh_tets.e \ No newline at end of file diff --git a/tests/unit_tests/weightwindows/test_ww_gen.py b/tests/unit_tests/weightwindows/test_ww_gen.py index 072e332cb75..555421461b7 100644 --- a/tests/unit_tests/weightwindows/test_ww_gen.py +++ b/tests/unit_tests/weightwindows/test_ww_gen.py @@ -55,7 +55,7 @@ def model(): run_mode='fixed source', particles=100, batches=10, - max_splits=10, + max_history_splits=10, survival_biasing=False ) @@ -101,7 +101,7 @@ def labels(params): @pytest.mark.parametrize("filters", test_cases, ids=labels) -def test_ww_gen(filters, model): +def test_ww_gen(filters, run_in_tmpdir, model): tally = openmc.Tally() tally.filters = list(filters) diff --git a/tools/ci/gha-install-libmesh.sh b/tools/ci/gha-install-libmesh.sh index cb808ae5b3b..d4557d2d3a2 100755 --- a/tools/ci/gha-install-libmesh.sh +++ b/tools/ci/gha-install-libmesh.sh @@ -16,7 +16,6 @@ else ../libmesh/configure --prefix=$HOME/LIBMESH --enable-exodus --disable-netcdf-4 --disable-eigen --disable-lapack --disable-mpi fi make -j4 install -export LIBMESH_PC=$HOME/LIBMESH/lib/pkgconfig/ rm -rf $HOME/LIBMESH/build popd diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index f4b2fbb3187..d52c4c8254c 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -2,22 +2,6 @@ import shutil import subprocess -def which(program): - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrystal=False): # Create build directory and change to it @@ -47,7 +31,9 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrys if dagmc: cmake_cmd.append('-DOPENMC_USE_DAGMC=ON') - cmake_cmd.append('-DCMAKE_PREFIX_PATH=~/DAGMC') + cmake_cmd.append('-DOPENMC_USE_UWUW=ON') + dagmc_path = os.environ.get('HOME') + '/DAGMC' + cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + dagmc_path) if libmesh: cmake_cmd.append('-DOPENMC_USE_LIBMESH=ON') @@ -56,8 +42,8 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False, ncrys if ncrystal: cmake_cmd.append('-DOPENMC_USE_NCRYSTAL=ON') - ncrystal_cmake_path = os.environ.get('HOME') + '/ncrystal_inst/lib/cmake' - cmake_cmd.append(f'-DCMAKE_PREFIX_PATH={ncrystal_cmake_path}') + ncrystal_path = os.environ.get('HOME') + '/ncrystal_inst' + cmake_cmd.append(f'-DCMAKE_PREFIX_PATH={ncrystal_path}') # Build in coverage mode for coverage testing cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index 2cef974d346..cff7dc834f5 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -40,18 +40,11 @@ if [[ $MPI == 'y' ]]; then export CC=mpicc export HDF5_MPI=ON export HDF5_DIR=/usr/lib/x86_64-linux-gnu/hdf5/mpich - pip install wheel "cython<3.0" - pip install --no-binary=h5py --no-build-isolation h5py + pip install --no-binary=h5py h5py fi # Build and install OpenMC executable python tools/ci/gha-install.py # Install Python API in editable mode -pip install -e .[test,vtk] - -# For coverage testing of the C++ source files -pip install cpp-coveralls - -# For coverage testing of the Python source files -pip install coveralls +pip install -e .[test,vtk,ci] diff --git a/vendor/fmt b/vendor/fmt index d141cdbeb0f..0c9fce2ffef 160000 --- a/vendor/fmt +++ b/vendor/fmt @@ -1 +1 @@ -Subproject commit d141cdbeb0fb422a3fb7173b285fd38e0d1772dc +Subproject commit 0c9fce2ffefecfdce794e1859584e25877b7b592 From 08abdc6456c97b9e625395bbc8b87434a5a5faca Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Sat, 11 Jan 2025 15:23:34 -0500 Subject: [PATCH 04/21] Add initial halton samples regression test --- .../random_ray_halton_samples/__init__.py | 0 .../random_ray_halton_samples/inputs_true.dat | 110 +++++++++++ .../results_true.dat | 171 ++++++++++++++++++ .../random_ray_halton_samples/test.py | 20 ++ 4 files changed, 301 insertions(+) create mode 100644 tests/regression_tests/random_ray_halton_samples/__init__.py create mode 100644 tests/regression_tests/random_ray_halton_samples/inputs_true.dat create mode 100644 tests/regression_tests/random_ray_halton_samples/results_true.dat create mode 100644 tests/regression_tests/random_ray_halton_samples/test.py diff --git a/tests/regression_tests/random_ray_halton_samples/__init__.py b/tests/regression_tests/random_ray_halton_samples/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat new file mode 100644 index 00000000000..725702a4912 --- /dev/null +++ b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat @@ -0,0 +1,110 @@ + + + + mgxs.h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.126 0.126 + 10 10 + -0.63 -0.63 + +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 +3 3 3 3 3 3 3 3 3 3 + + + 1.26 1.26 + 2 2 + -1.26 -1.26 + +2 2 +2 5 + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + multi-group + + 100.0 + 20.0 + + + -1.26 -1.26 -1 1.26 1.26 1 + + + True + True + + + + + 2 2 + -1.26 -1.26 + 1.26 1.26 + + + 1 + + + 1e-05 0.0635 10.0 100.0 1000.0 500000.0 1000000.0 20000000.0 + + + 1 2 + flux fission nu-fission + analog + + + diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat new file mode 100644 index 00000000000..006bab18ea1 --- /dev/null +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -0,0 +1,171 @@ +k-combined: +1.006387E+00 1.725019E-03 +tally 1: +6.679209E+00 +8.926547E+00 +2.658380E+00 +1.413908E+00 +0.000000E+00 +0.000000E+00 +6.352588E+00 +8.075713E+00 +9.581421E-01 +1.836980E-01 +0.000000E+00 +0.000000E+00 +5.957699E+00 +7.103986E+00 +1.910097E-01 +7.301757E-03 +0.000000E+00 +0.000000E+00 +5.140326E+00 +5.288483E+00 +1.695407E-01 +5.752606E-03 +1.074049E-06 +2.308685E-13 +4.863665E+00 +4.732328E+00 +5.633496E-02 +6.348732E-04 +2.959889E-03 +1.752596E-06 +4.832553E+00 +4.670737E+00 +7.128045E-03 +1.016201E-05 +3.582343E+00 +2.566688E+00 +5.160808E+00 +5.326902E+00 +6.715281E-02 +9.019348E-04 +5.474087E+00 +5.993358E+00 +5.407490E+00 +5.850366E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.623077E+00 +6.326677E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.675351E+00 +6.445719E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.312760E+00 +5.648965E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.951439E+00 +4.905044E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.845043E+00 +4.694914E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.114241E+00 +5.231199E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.913207E+00 +9.563332E+00 +2.801843E+00 +1.571133E+00 +0.000000E+00 +0.000000E+00 +6.534871E+00 +8.545865E+00 +1.005522E+00 +2.023834E-01 +0.000000E+00 +0.000000E+00 +6.048969E+00 +7.323575E+00 +1.980839E-01 +7.856118E-03 +0.000000E+00 +0.000000E+00 +5.125616E+00 +5.258595E+00 +1.729489E-01 +5.989157E-03 +1.095639E-06 +2.403619E-13 +4.844081E+00 +4.694230E+00 +5.735538E-02 +6.582314E-04 +3.013503E-03 +1.817078E-06 +4.829099E+00 +4.664063E+00 +7.277551E-03 +1.059286E-05 +3.657480E+00 +2.675510E+00 +5.169796E+00 +5.345504E+00 +6.866802E-02 +9.430643E-04 +5.597602E+00 +6.266663E+00 +6.699012E+00 +8.979397E+00 +2.742417E+00 +1.504937E+00 +0.000000E+00 +0.000000E+00 +6.363841E+00 +8.103951E+00 +9.874487E-01 +1.951305E-01 +0.000000E+00 +0.000000E+00 +5.961411E+00 +7.112510E+00 +1.966496E-01 +7.740114E-03 +0.000000E+00 +0.000000E+00 +5.134383E+00 +5.276328E+00 +1.742854E-01 +6.079807E-03 +1.104106E-06 +2.440000E-13 +4.860312E+00 +4.725851E+00 +5.792265E-02 +6.712060E-04 +3.043308E-03 +1.852895E-06 +4.832174E+00 +4.670003E+00 +7.332547E-03 +1.075326E-05 +3.685119E+00 +2.716023E+00 +5.163497E+00 +5.332456E+00 +6.911535E-02 +9.553967E-04 +5.634068E+00 +6.348612E+00 diff --git a/tests/regression_tests/random_ray_halton_samples/test.py b/tests/regression_tests/random_ray_halton_samples/test.py new file mode 100644 index 00000000000..44cf1182ae6 --- /dev/null +++ b/tests/regression_tests/random_ray_halton_samples/test.py @@ -0,0 +1,20 @@ +import os + +from openmc.examples import random_ray_lattice + +from tests.testing_harness import TolerantPyAPITestHarness + + +class MGXSTestHarness(TolerantPyAPITestHarness): + def _cleanup(self): + super()._cleanup() + f = 'mgxs.h5' + if os.path.exists(f): + os.remove(f) + + +def test_random_ray_basic(): + model = random_ray_lattice() + model.settings.random_ray['adjoint'] = True + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() From 528bb72584a07e69957fe3a6fd7418203524818e Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Sun, 12 Jan 2025 11:55:14 -0500 Subject: [PATCH 05/21] Add sample method for selecting prng or halton sampling --- include/openmc/constants.h | 1 + include/openmc/random_ray/random_ray.h | 12 +- openmc/settings.py | 8 + src/random_ray/random_ray.cpp | 54 ++- src/settings.cpp | 11 + .../random_ray_halton_samples/inputs_true.dat | 2 +- .../results_true.dat | 338 +++++++++--------- .../random_ray_halton_samples/test.py | 5 +- 8 files changed, 238 insertions(+), 193 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 605ae1839d8..47dabfc7ac6 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -344,6 +344,7 @@ enum class SolverType { MONTE_CARLO, RANDOM_RAY }; enum class RandomRayVolumeEstimator { NAIVE, SIMULATION_AVERAGED, HYBRID }; enum class RandomRaySourceShape { FLAT, LINEAR, LINEAR_XY }; +enum class RandomRaySampleMethod { PRNG, HALTON }; //============================================================================== // Geometry Constants diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index 9b375f42c16..b222acee06f 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -31,14 +31,16 @@ class RandomRay : public Particle { void initialize_ray(uint64_t ray_id, FlatSourceDomain* domain); uint64_t transport_history_based_single_ray(); - SourceSite sample_lds(uint64_t* seed, int64_t skip); + SourceSite sample_prng(); + SourceSite sample_halton(); //---------------------------------------------------------------------------- // Static data members - static double distance_inactive_; // Inactive (dead zone) ray length - static double distance_active_; // Active ray length - static unique_ptr ray_source_; // Starting source for ray sampling - static RandomRaySourceShape source_shape_; // Flag for linear source + static double distance_inactive_; // Inactive (dead zone) ray length + static double distance_active_; // Active ray length + static unique_ptr ray_source_; // Starting source for ray sampling + static RandomRaySourceShape source_shape_; // Flag for linear source + static RandomRaySampleMethod sample_method_; // Flag for sampling method //---------------------------------------------------------------------------- // Public data members diff --git a/openmc/settings.py b/openmc/settings.py index 77598b204fc..f2eef17258c 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -173,6 +173,9 @@ class Settings: :adjoint: Whether to run the random ray solver in adjoint mode (bool). The default is 'False'. + :sample_method: + Sampling method for the ray starting location and direction of travel. + Options are `prng` (default) or 'halton`. .. versionadded:: 0.15.0 resonance_scattering : dict @@ -1131,6 +1134,9 @@ def random_ray(self, random_ray: dict): cv.check_type('volume normalized flux tallies', random_ray[key], bool) elif key == 'adjoint': cv.check_type('adjoint', random_ray[key], bool) + elif key == 'sample_method': + cv.check_value('sample method', random_ray[key], + ('prng', 'halton')) else: raise ValueError(f'Unable to set random ray to "{key}" which is ' 'unsupported by OpenMC') @@ -1948,6 +1954,8 @@ def _random_ray_from_xml_element(self, root): self.random_ray['adjoint'] = ( child.text in ('true', '1') ) + elif child.tag == 'sample_method': + self.random_ray['sample_method'] = child.text def to_xml_element(self, mesh_memo=None): """Create a 'settings' element to be written to an XML file. diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 42e6ea670b8..0da0eafbc22 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -196,9 +196,6 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // Algorithm adapted from: // A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. // URL http:arxiv.org/abs/1706.02808 -// -// Results are not idential to Python implementation - the permutation process -// produces different results due to differences in shuffle/rng implementation. vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) { vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; @@ -240,6 +237,7 @@ vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) } return halton; +} //============================================================================== // RandomRay implementation @@ -250,6 +248,7 @@ double RandomRay::distance_inactive_; double RandomRay::distance_active_; unique_ptr RandomRay::ray_source_; RandomRaySourceShape RandomRay::source_shape_ {RandomRaySourceShape::FLAT}; +RandomRaySampleMethod RandomRay::sample_method_ {RandomRaySampleMethod::PRNG}; RandomRay::RandomRay() : angular_flux_(data::mg.num_energy_groups_), @@ -576,16 +575,21 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) // set identifier for particle id() = simulation::work_index[mpi::rank] + ray_id; - // set random number seed - int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles; - int64_t skip = id(); - init_particle_seeds(batch_seed, seeds()); - stream() = STREAM_TRACKING; + // generate source site using sample method + SourceSite site; + switch (sample_method_) { + case RandomRaySampleMethod::PRNG: + site = sample_prng(); + break; + case RandomRaySampleMethod::HALTON: + site = sample_halton(); + break; + default: + fatal_error("Unknown sample method for random ray transport."); + } - // Sample from ray source distribution - SourceSite site = sample_lds(current_seed(), skip); site.E = lower_bound_index( - data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); + data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); site.E = negroups_ - site.E - 1.; this->from_source(&site); @@ -612,14 +616,34 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) } } -SourceSite RandomRay::sample_lds(uint64_t* seed, int64_t skip) +SourceSite RandomRay::sample_prng() +{ + // set random number seed + int64_t particle_seed = + (simulation::current_batch - 1) * settings::n_particles + id(); + init_particle_seeds(particle_seed, seeds()); + stream() = STREAM_TRACKING; + + // Sample from ray source distribution + SourceSite site {ray_source_->sample(current_seed())}; + + return site; +} + +SourceSite RandomRay::sample_halton() { SourceSite site; + // Set random number seed + int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles; + int64_t skip = id(); + init_particle_seeds(batch_seed, seeds()); + stream() = STREAM_TRACKING; + // Calculate next samples in LDS - vector> samples = rhalton(1, 5, seed, skip = skip); + vector> samples = rhalton(1, 5, current_seed(), skip = skip); - // get spatial box of ray_source_ + // Get spatial box of ray_source_ SpatialBox* sb = dynamic_cast( dynamic_cast(RandomRay::ray_source_.get())->space()); @@ -633,7 +657,7 @@ SourceSite RandomRay::sample_lds(uint64_t* seed, int64_t skip) float mu = 2.0 * samples[0][3] - 1.0; float azi = 2.0 * PI * samples[0][4]; // Convert to Cartesian coordinates - float c = std::pow((1.0 - std::pow(mu, 2)), 0.5); + float c = std::pow((1.0 - mu*mu), 0.5); site.u.x = mu; site.u.y = std::cos(azi) * c; site.u.z = std::sin(azi) * c; diff --git a/src/settings.cpp b/src/settings.cpp index 61eda79967a..60263c84653 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -306,6 +306,17 @@ void get_run_parameters(pugi::xml_node node_base) FlatSourceDomain::adjoint_ = get_node_value_bool(random_ray_node, "adjoint"); } + if (check_for_node(random_ray_node, "sample_method")) { + std::string temp_str = + get_node_value(random_ray_node, "sample_method", true, true); + if (temp_str == "prng") { + RandomRay::sample_method_ = RandomRaySampleMethod::PRNG; + } else if (temp_str == "halton") { + RandomRay::sample_method_ = RandomRaySampleMethod::HALTON; + } else { + fatal_error("Unrecognized sample method: " + temp_str); + } + } } } diff --git a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat index 725702a4912..624ab495f41 100644 --- a/tests/regression_tests/random_ray_halton_samples/inputs_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/inputs_true.dat @@ -86,7 +86,7 @@ True - True + halton diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat index 006bab18ea1..f9d2309ad53 100644 --- a/tests/regression_tests/random_ray_halton_samples/results_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -1,171 +1,171 @@ k-combined: -1.006387E+00 1.725019E-03 +8.432519E-01 9.088530E-03 tally 1: -6.679209E+00 -8.926547E+00 -2.658380E+00 -1.413908E+00 -0.000000E+00 -0.000000E+00 -6.352588E+00 -8.075713E+00 -9.581421E-01 -1.836980E-01 -0.000000E+00 -0.000000E+00 -5.957699E+00 -7.103986E+00 -1.910097E-01 -7.301757E-03 -0.000000E+00 -0.000000E+00 -5.140326E+00 -5.288483E+00 -1.695407E-01 -5.752606E-03 -1.074049E-06 -2.308685E-13 -4.863665E+00 -4.732328E+00 -5.633496E-02 -6.348732E-04 -2.959889E-03 -1.752596E-06 -4.832553E+00 -4.670737E+00 -7.128045E-03 -1.016201E-05 -3.582343E+00 -2.566688E+00 -5.160808E+00 -5.326902E+00 -6.715281E-02 -9.019348E-04 -5.474087E+00 -5.993358E+00 -5.407490E+00 -5.850366E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -5.623077E+00 -6.326677E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -5.675351E+00 -6.445719E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -5.312760E+00 -5.648965E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.951439E+00 -4.905044E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.845043E+00 -4.694914E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -5.114241E+00 -5.231199E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -6.913207E+00 -9.563332E+00 -2.801843E+00 -1.571133E+00 -0.000000E+00 -0.000000E+00 -6.534871E+00 -8.545865E+00 -1.005522E+00 -2.023834E-01 -0.000000E+00 -0.000000E+00 -6.048969E+00 -7.323575E+00 -1.980839E-01 -7.856118E-03 -0.000000E+00 -0.000000E+00 -5.125616E+00 -5.258595E+00 -1.729489E-01 -5.989157E-03 -1.095639E-06 -2.403619E-13 -4.844081E+00 -4.694230E+00 -5.735538E-02 -6.582314E-04 -3.013503E-03 -1.817078E-06 -4.829099E+00 -4.664063E+00 -7.277551E-03 -1.059286E-05 -3.657480E+00 -2.675510E+00 -5.169796E+00 -5.345504E+00 -6.866802E-02 -9.430643E-04 -5.597602E+00 -6.266663E+00 -6.699012E+00 -8.979397E+00 -2.742417E+00 -1.504937E+00 -0.000000E+00 -0.000000E+00 -6.363841E+00 -8.103951E+00 -9.874487E-01 -1.951305E-01 -0.000000E+00 -0.000000E+00 -5.961411E+00 -7.112510E+00 -1.966496E-01 -7.740114E-03 -0.000000E+00 -0.000000E+00 -5.134383E+00 -5.276328E+00 -1.742854E-01 -6.079807E-03 -1.104106E-06 -2.440000E-13 -4.860312E+00 -4.725851E+00 -5.792265E-02 -6.712060E-04 -3.043308E-03 -1.852895E-06 -4.832174E+00 -4.670003E+00 -7.332547E-03 -1.075326E-05 -3.685119E+00 -2.716023E+00 -5.163497E+00 -5.332456E+00 -6.911535E-02 -9.553967E-04 -5.634068E+00 -6.348612E+00 +5.104731E+00 +5.217483E+00 +1.865256E+00 +6.967915E-01 +4.539660E+00 +4.127362E+00 +2.872400E+00 +1.653133E+00 +4.210027E-01 +3.552973E-02 +1.024636E+00 +2.104562E-01 +1.701509E+00 +5.854992E-01 +5.407557E-02 +5.910236E-04 +1.316091E-01 +3.500858E-03 +2.379486E+00 +1.152677E+00 +7.727568E-02 +1.215094E-03 +1.880735E-01 +7.197467E-03 +7.116810E+00 +1.028474E+01 +8.152037E-02 +1.349477E-03 +1.984067E-01 +7.993666E-03 +2.026820E+01 +8.253626E+01 +3.048078E-02 +1.866430E-04 +7.542256E-02 +1.142779E-03 +1.306821E+01 +3.415706E+01 +1.747690E-01 +6.109976E-03 +4.861111E-01 +4.726959E-02 +7.587358E+00 +1.151384E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.398045E+00 +2.310416E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.828891E+00 +6.791320E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.701258E+00 +1.487062E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.434718E+00 +1.121066E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.804904E+01 +6.545166E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.122548E+01 +2.520364E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.652481E+00 +4.337660E+00 +1.740208E+00 +6.065202E-01 +4.235318E+00 +3.592650E+00 +2.749346E+00 +1.515311E+00 +4.123319E-01 +3.406795E-02 +1.003533E+00 +2.017975E-01 +1.670393E+00 +5.636574E-01 +5.437391E-02 +5.976682E-04 +1.323352E-01 +3.540217E-03 +2.318934E+00 +1.094620E+00 +7.728806E-02 +1.217494E-03 +1.881037E-01 +7.211682E-03 +7.079333E+00 +1.018111E+01 +8.304540E-02 +1.403504E-03 +2.021184E-01 +8.313692E-03 +2.071523E+01 +8.619465E+01 +3.168783E-02 +2.018351E-04 +7.840932E-02 +1.235797E-03 +1.335678E+01 +3.568486E+01 +1.801059E-01 +6.488902E-03 +5.009554E-01 +5.020113E-02 +5.063471E+00 +5.133250E+00 +1.903361E+00 +7.254157E-01 +4.632401E+00 +4.296914E+00 +2.862473E+00 +1.641645E+00 +4.315352E-01 +3.731380E-02 +1.050270E+00 +2.210239E-01 +1.697725E+00 +5.830136E-01 +5.550429E-02 +6.228379E-04 +1.350863E-01 +3.689306E-03 +2.371998E+00 +1.146021E+00 +7.930853E-02 +1.280754E-03 +1.930211E-01 +7.586396E-03 +7.112194E+00 +1.027188E+01 +8.386325E-02 +1.428782E-03 +2.041089E-01 +8.463429E-03 +2.034686E+01 +8.315832E+01 +3.147111E-02 +1.990415E-04 +7.787307E-02 +1.218692E-03 +1.316796E+01 +3.467991E+01 +1.809483E-01 +6.549878E-03 +5.032986E-01 +5.067288E-02 diff --git a/tests/regression_tests/random_ray_halton_samples/test.py b/tests/regression_tests/random_ray_halton_samples/test.py index 44cf1182ae6..478b6502648 100644 --- a/tests/regression_tests/random_ray_halton_samples/test.py +++ b/tests/regression_tests/random_ray_halton_samples/test.py @@ -12,9 +12,8 @@ def _cleanup(self): if os.path.exists(f): os.remove(f) - -def test_random_ray_basic(): +def test_random_ray_halton_samples(): model = random_ray_lattice() - model.settings.random_ray['adjoint'] = True + model.settings.random_ray['sample_method'] = 'halton' harness = MGXSTestHarness('statepoint.10.h5', model) harness.main() From 808cb6cd9f1549f65b071d2585b7b8fb52e5756c Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Sun, 12 Jan 2025 20:32:02 -0500 Subject: [PATCH 06/21] Fix uniform_int_distribution bug --- include/openmc/random_dist.h | 2 +- src/random_dist.cpp | 8 +- src/random_ray/random_ray.cpp | 4 +- .../results_true.dat | 338 +++++++++--------- 4 files changed, 174 insertions(+), 178 deletions(-) diff --git a/include/openmc/random_dist.h b/include/openmc/random_dist.h index ec3f6d2a5f3..11e88ab8cce 100644 --- a/include/openmc/random_dist.h +++ b/include/openmc/random_dist.h @@ -25,7 +25,7 @@ double uniform_distribution(double a, double b, uint64_t* seed); //! \return Sampled variate //============================================================================== -double uniform_int_distribution(double a, double b, uint64_t* seed); +int64_t uniform_int_distribution(int64_t a, int64_t b, uint64_t* seed); //============================================================================== //! Samples an energy from the Maxwell fission distribution based on a direct diff --git a/src/random_dist.cpp b/src/random_dist.cpp index 253b48e4389..b05b76f99ec 100644 --- a/src/random_dist.cpp +++ b/src/random_dist.cpp @@ -12,13 +12,9 @@ double uniform_distribution(double a, double b, uint64_t* seed) return a + (b - a) * prn(seed); } -double uniform_int_distribution(double a, double b, uint64_t* seed) +int64_t uniform_int_distribution(int64_t a, int64_t b, uint64_t* seed) { - // Generate a random uint64_t value using the PRNG - uint64_t random_value = - static_cast(prn(seed) * std::numeric_limits::max()); - // Map the random value to the range [a, b] - return a + (random_value % static_cast(b - a + 1)); + return a + static_cast(prn(seed) * (b - a + 1)); } double maxwell_spectrum(double T, uint64_t* seed) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 0da0eafbc22..d04f0c0ac81 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -195,7 +195,7 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // // Algorithm adapted from: // A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. -// URL http:arxiv.org/abs/1706.02808 +// URL https://arxiv.org/abs/1706.02808 vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) { vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; @@ -657,7 +657,7 @@ SourceSite RandomRay::sample_halton() float mu = 2.0 * samples[0][3] - 1.0; float azi = 2.0 * PI * samples[0][4]; // Convert to Cartesian coordinates - float c = std::pow((1.0 - mu*mu), 0.5); + float c = std::sqrt(1.0 - mu*mu); site.u.x = mu; site.u.y = std::cos(azi) * c; site.u.z = std::sin(azi) * c; diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat index f9d2309ad53..f9fb05d15d1 100644 --- a/tests/regression_tests/random_ray_halton_samples/results_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -1,171 +1,171 @@ k-combined: -8.432519E-01 9.088530E-03 +8.388050E-01 7.383283E-03 tally 1: -5.104731E+00 -5.217483E+00 -1.865256E+00 -6.967915E-01 -4.539660E+00 -4.127362E+00 -2.872400E+00 -1.653133E+00 -4.210027E-01 -3.552973E-02 -1.024636E+00 -2.104562E-01 -1.701509E+00 -5.854992E-01 -5.407557E-02 -5.910236E-04 -1.316091E-01 -3.500858E-03 -2.379486E+00 -1.152677E+00 -7.727568E-02 -1.215094E-03 -1.880735E-01 -7.197467E-03 -7.116810E+00 -1.028474E+01 -8.152037E-02 -1.349477E-03 -1.984067E-01 -7.993666E-03 -2.026820E+01 -8.253626E+01 -3.048078E-02 -1.866430E-04 -7.542256E-02 -1.142779E-03 -1.306821E+01 -3.415706E+01 -1.747690E-01 -6.109976E-03 -4.861111E-01 -4.726959E-02 -7.587358E+00 -1.151384E+01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -3.398045E+00 -2.310416E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.828891E+00 -6.791320E-01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -2.701258E+00 -1.487062E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -7.434718E+00 -1.121066E+01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.804904E+01 -6.545166E+01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -1.122548E+01 -2.520364E+01 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -4.652481E+00 -4.337660E+00 -1.740208E+00 -6.065202E-01 -4.235318E+00 -3.592650E+00 -2.749346E+00 -1.515311E+00 -4.123319E-01 -3.406795E-02 -1.003533E+00 -2.017975E-01 -1.670393E+00 -5.636574E-01 -5.437391E-02 -5.976682E-04 -1.323352E-01 -3.540217E-03 -2.318934E+00 -1.094620E+00 -7.728806E-02 -1.217494E-03 -1.881037E-01 -7.211682E-03 -7.079333E+00 -1.018111E+01 -8.304540E-02 -1.403504E-03 -2.021184E-01 -8.313692E-03 -2.071523E+01 -8.619465E+01 -3.168783E-02 -2.018351E-04 -7.840932E-02 -1.235797E-03 -1.335678E+01 -3.568486E+01 -1.801059E-01 -6.488902E-03 -5.009554E-01 -5.020113E-02 -5.063471E+00 -5.133250E+00 -1.903361E+00 -7.254157E-01 -4.632401E+00 -4.296914E+00 -2.862473E+00 -1.641645E+00 -4.315352E-01 -3.731380E-02 -1.050270E+00 -2.210239E-01 -1.697725E+00 -5.830136E-01 -5.550429E-02 -6.228379E-04 -1.350863E-01 -3.689306E-03 -2.371998E+00 -1.146021E+00 -7.930853E-02 -1.280754E-03 -1.930211E-01 -7.586396E-03 -7.112194E+00 -1.027188E+01 -8.386325E-02 -1.428782E-03 -2.041089E-01 -8.463429E-03 -2.034686E+01 -8.315832E+01 -3.147111E-02 -1.990415E-04 -7.787307E-02 -1.218692E-03 -1.316796E+01 -3.467991E+01 -1.809483E-01 -6.549878E-03 -5.032986E-01 -5.067288E-02 +5.033306E+00 +5.072159E+00 +1.917335E+00 +7.360725E-01 +4.666410E+00 +4.360038E+00 +2.851811E+00 +1.629361E+00 +4.365591E-01 +3.818885E-02 +1.062497E+00 +2.262072E-01 +1.697620E+00 +5.829331E-01 +5.639912E-02 +6.427569E-04 +1.372642E-01 +3.807294E-03 +2.376682E+00 +1.151027E+00 +8.060903E-02 +1.323179E-03 +1.961863E-01 +7.837694E-03 +7.145451E+00 +1.037540E+01 +8.551805E-02 +1.486270E-03 +2.081364E-01 +8.803959E-03 +2.053205E+01 +8.469502E+01 +3.235620E-02 +2.102893E-04 +8.006316E-02 +1.287560E-03 +1.326546E+01 +3.519488E+01 +1.867472E-01 +6.975145E-03 +5.194280E-01 +5.396293E-02 +7.558115E+00 +1.142535E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +3.386210E+00 +2.294414E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.827274E+00 +6.782303E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.702858E+00 +1.489752E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.475537E+00 +1.133971E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.828685E+01 +6.719608E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.143600E+01 +2.615734E+01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +4.590713E+00 +4.224106E+00 +1.705847E+00 +5.831967E-01 +4.151691E+00 +3.454497E+00 +2.730853E+00 +1.495413E+00 +4.072633E-01 +3.325944E-02 +9.911973E-01 +1.970084E-01 +1.664732E+00 +5.598667E-01 +5.385644E-02 +5.858353E-04 +1.310758E-01 +3.470126E-03 +2.312239E+00 +1.088485E+00 +7.662778E-02 +1.195422E-03 +1.864967E-01 +7.080943E-03 +7.105765E+00 +1.025960E+01 +8.287513E-02 +1.396474E-03 +2.017039E-01 +8.272054E-03 +2.099024E+01 +8.854252E+01 +3.191885E-02 +2.048369E-04 +7.898097E-02 +1.254176E-03 +1.355820E+01 +3.676863E+01 +1.815102E-01 +6.590730E-03 +5.048615E-01 +5.098892E-02 +5.093659E+00 +5.192360E+00 +1.874631E+00 +7.031791E-01 +4.562477E+00 +4.165198E+00 +2.870213E+00 +1.650126E+00 +4.244068E-01 +3.608744E-02 +1.032921E+00 +2.137597E-01 +1.703400E+00 +5.873028E-01 +5.464556E-02 +6.042852E-04 +1.329964E-01 +3.579412E-03 +2.389118E+00 +1.163673E+00 +7.832235E-02 +1.251253E-03 +1.906209E-01 +7.411650E-03 +7.162706E+00 +1.042515E+01 +8.273829E-02 +1.391799E-03 +2.013709E-01 +8.244357E-03 +2.043146E+01 +8.383560E+01 +3.096158E-02 +1.924116E-04 +7.661227E-02 +1.178099E-03 +1.314148E+01 +3.454146E+01 +1.771733E-01 +6.279891E-03 +4.927985E-01 +4.858413E-02 From 25ac2092f410c6ddb23c4bf1674386c34c29d87a Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Tue, 14 Jan 2025 17:21:55 -0500 Subject: [PATCH 07/21] clang-format and updated regression test --- include/openmc/random_ray/random_ray.h | 10 +++++----- src/random_ray/random_ray.cpp | 20 +++++++++---------- .../results_true.dat | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/openmc/random_ray/random_ray.h b/include/openmc/random_ray/random_ray.h index b222acee06f..08c2a8488f2 100644 --- a/include/openmc/random_ray/random_ray.h +++ b/include/openmc/random_ray/random_ray.h @@ -36,11 +36,11 @@ class RandomRay : public Particle { //---------------------------------------------------------------------------- // Static data members - static double distance_inactive_; // Inactive (dead zone) ray length - static double distance_active_; // Active ray length - static unique_ptr ray_source_; // Starting source for ray sampling - static RandomRaySourceShape source_shape_; // Flag for linear source - static RandomRaySampleMethod sample_method_; // Flag for sampling method + static double distance_inactive_; // Inactive (dead zone) ray length + static double distance_active_; // Active ray length + static unique_ptr ray_source_; // Starting source for ray sampling + static RandomRaySourceShape source_shape_; // Flag for linear source + static RandomRaySampleMethod sample_method_; // Flag for sampling method //---------------------------------------------------------------------------- // Public data members diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index d04f0c0ac81..27b56f30bc7 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -589,7 +589,7 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) } site.E = lower_bound_index( - data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); + data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E); site.E = negroups_ - site.E - 1.; this->from_source(&site); @@ -618,16 +618,16 @@ void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain) SourceSite RandomRay::sample_prng() { - // set random number seed - int64_t particle_seed = - (simulation::current_batch - 1) * settings::n_particles + id(); - init_particle_seeds(particle_seed, seeds()); - stream() = STREAM_TRACKING; + // set random number seed + int64_t particle_seed = + (simulation::current_batch - 1) * settings::n_particles + id(); + init_particle_seeds(particle_seed, seeds()); + stream() = STREAM_TRACKING; - // Sample from ray source distribution - SourceSite site {ray_source_->sample(current_seed())}; + // Sample from ray source distribution + SourceSite site {ray_source_->sample(current_seed())}; - return site; + return site; } SourceSite RandomRay::sample_halton() @@ -657,7 +657,7 @@ SourceSite RandomRay::sample_halton() float mu = 2.0 * samples[0][3] - 1.0; float azi = 2.0 * PI * samples[0][4]; // Convert to Cartesian coordinates - float c = std::sqrt(1.0 - mu*mu); + float c = std::sqrt(1.0 - mu * mu); site.u.x = mu; site.u.y = std::cos(azi) * c; site.u.z = std::sin(azi) * c; diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat index f9fb05d15d1..651c884b5dc 100644 --- a/tests/regression_tests/random_ray_halton_samples/results_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -32,7 +32,7 @@ tally 1: 2.081364E-01 8.803959E-03 2.053205E+01 -8.469502E+01 +8.469503E+01 3.235620E-02 2.102893E-04 8.006316E-02 From 31518dc9762881378266799da05cb71ed19d2e63 Mon Sep 17 00:00:00 2001 From: Sam Pasmann Date: Sat, 18 Jan 2025 11:56:03 -0500 Subject: [PATCH 08/21] Reorganize halton function --- src/random_ray/random_ray.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 27b56f30bc7..28572e62099 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -198,16 +198,20 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // URL https://arxiv.org/abs/1706.02808 vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) { + int b; + double b2r; + vector ans(N); + vector ind(N); vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector> halton(N, vector(dim, 0.0)); + std::iota(ind.begin(), ind.end(), skip); + for (int D = 0; D < dim; ++D) { - int b = primes[D]; - vector ind(N); - std::iota(ind.begin(), ind.end(), skip); - double b2r = 1.0 / b; - vector ans(N, 0.0); + b = primes[D]; + b2r = 1.0 / b; vector res(ind); + std::fill(ans.begin(), ans.end(), 0.0); while ((1.0 - b2r) < 1.0) { vector dig(N); @@ -216,19 +220,12 @@ vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) std::iota(perm.begin(), perm.end(), 0); fisher_yates_shuffle(perm, seed); - // compute element wise remainder of division (mod) for (int i = 0; i < N; ++i) { dig[i] = res[i] % b; - } - - for (int i = 0; i < N; ++i) { ans[i] += perm[dig[i]] * b2r; - } - - b2r /= b; - for (int i = 0; i < N; ++i) { res[i] = (res[i] - dig[i]) / b; } + b2r /= b; } for (int i = 0; i < N; ++i) { From e868aeaa46be29f9bc282a24fcb4d793a52fcc03 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 27 Jan 2025 09:21:46 -0600 Subject: [PATCH 09/21] added section to userguide on Halton sampling --- docs/source/usersguide/random_ray.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index 2b9cf67240b..9b02443f3cf 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -299,6 +299,24 @@ acceptable ray source for a two-dimensional 2x2 lattice would look like: provide physical particle fixed sources in addition to the random ray source. +-------------------------- +Quasi-Monte Carlo Sampling +-------------------------- + +By default OpenMC will use a pseudorandom number generator (PRNG) to sample ray +starting locations from a uniform distribution in space and angle. +Alternatively, a randomized Halton sequence may be sampled from, which is a form +of Randomized Qusi-Monte Carlo (RQMC) sampling. RQMC sampling with random ray +has been shown to offer reduced variance as compared to regular PRNG sampling, +as the Halton sequence offers a more uniform distribution of sampled points. +Randomized halton sampling can be enabled as:: + + settings.random_ray['sample_method'] = 'halton' + +Default behavior using OpenMC's native PRNG can be manually specified as:: + + settings.random_ray['sample_method'] = 'prng' + .. _subdivision_fsr: ---------------------------------- From 2f5de56d862799d885d54ca41335329de1d3484a Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 27 Jan 2025 09:25:02 -0600 Subject: [PATCH 10/21] Halton capitalization --- docs/source/usersguide/random_ray.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index 9b02443f3cf..b838daef34d 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -309,7 +309,7 @@ Alternatively, a randomized Halton sequence may be sampled from, which is a form of Randomized Qusi-Monte Carlo (RQMC) sampling. RQMC sampling with random ray has been shown to offer reduced variance as compared to regular PRNG sampling, as the Halton sequence offers a more uniform distribution of sampled points. -Randomized halton sampling can be enabled as:: +Randomized Halton sampling can be enabled as:: settings.random_ray['sample_method'] = 'halton' From 2d7cf35425c3f5a602cef22c25de9e11deab0dfa Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 27 Jan 2025 09:36:16 -0600 Subject: [PATCH 11/21] added sample_method to XML input docs --- docs/source/io_formats/settings.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 34b73fc55c5..24a7cf22c1f 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -439,6 +439,12 @@ found in the :ref:`random ray user guide `. *Default*: None + :sample_method: + Specifies the method for sampling the starting ray distribution. This + element can be set to "prng" or "halton". + + *Default*: prng + ---------------------------------- ```` Element ---------------------------------- From df113b7a0df67935b622aa78d7461a018735f24f Mon Sep 17 00:00:00 2001 From: spasmann Date: Tue, 4 Feb 2025 12:20:02 -0800 Subject: [PATCH 12/21] update rhalton sampling to double precision --- src/random_ray/random_ray.cpp | 30 ++-- .../results_true.dat | 146 +++++++++--------- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 28572e62099..9c2c71a9b6f 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -180,12 +180,12 @@ float exponentialG2(float tau) // Implementation of the Fisher-Yates shuffle algorithm. // Algorithm adapted from: // https://en.cppreference.com/w/cpp/algorithm/random_shuffle#Version_3 -void fisher_yates_shuffle(vector& arr, uint64_t* seed) +void fisher_yates_shuffle(vector& arr, uint64_t* seed) { // Loop over the array from the last element down to the second - for (size_t i = arr.size() - 1; i > 0; --i) { + for (int i = arr.size() - 1; i > 0; --i) { // Generate a random index in the range [0, i] - size_t j = uniform_int_distribution(0, i, seed); + int j = uniform_int_distribution(0, i, seed); // Swap arr[i] with arr[j] std::swap(arr[i], arr[j]); } @@ -196,27 +196,27 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // Algorithm adapted from: // A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. // URL https://arxiv.org/abs/1706.02808 -vector> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0) +vector> rhalton(int64_t N, int dim, uint64_t* seed, int64_t skip = 0) { - int b; + int64_t b; double b2r; vector ans(N); - vector ind(N); - vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; - vector> halton(N, vector(dim, 0.0)); + vector ind(N); + vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; + vector> halton(N, vector(dim, 0.0)); std::iota(ind.begin(), ind.end(), skip); for (int D = 0; D < dim; ++D) { b = primes[D]; b2r = 1.0 / b; - vector res(ind); + vector res(ind); std::fill(ans.begin(), ans.end(), 0.0); while ((1.0 - b2r) < 1.0) { - vector dig(N); + vector dig(N); // randomaly permute a sequence from skip to skip+N - vector perm(b); + vector perm(b); std::iota(perm.begin(), perm.end(), 0); fisher_yates_shuffle(perm, seed); @@ -638,7 +638,7 @@ SourceSite RandomRay::sample_halton() stream() = STREAM_TRACKING; // Calculate next samples in LDS - vector> samples = rhalton(1, 5, current_seed(), skip = skip); + vector> samples = rhalton(1, 5, current_seed(), skip = skip); // Get spatial box of ray_source_ SpatialBox* sb = dynamic_cast( @@ -651,10 +651,10 @@ SourceSite RandomRay::sample_halton() xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); // Sample Polar cosine and azimuthal angles - float mu = 2.0 * samples[0][3] - 1.0; - float azi = 2.0 * PI * samples[0][4]; + double mu = 2.0 * samples[0][3] - 1.0; + double azi = 2.0 * PI * samples[0][4]; // Convert to Cartesian coordinates - float c = std::sqrt(1.0 - mu * mu); + double c = std::sqrt(1.0 - mu * mu); site.u.x = mu; site.u.y = std::cos(azi) * c; site.u.z = std::sin(azi) * c; diff --git a/tests/regression_tests/random_ray_halton_samples/results_true.dat b/tests/regression_tests/random_ray_halton_samples/results_true.dat index 651c884b5dc..7eb307da05b 100644 --- a/tests/regression_tests/random_ray_halton_samples/results_true.dat +++ b/tests/regression_tests/random_ray_halton_samples/results_true.dat @@ -1,62 +1,62 @@ k-combined: -8.388050E-01 7.383283E-03 +8.388051E-01 7.383265E-03 tally 1: -5.033306E+00 -5.072159E+00 +5.033308E+00 +5.072162E+00 1.917335E+00 7.360725E-01 4.666410E+00 4.360038E+00 -2.851811E+00 -1.629361E+00 -4.365591E-01 -3.818885E-02 +2.851812E+00 +1.629362E+00 +4.365590E-01 +3.818884E-02 1.062497E+00 -2.262072E-01 -1.697620E+00 -5.829331E-01 +2.262071E-01 +1.697621E+00 +5.829333E-01 5.639912E-02 -6.427569E-04 +6.427568E-04 1.372642E-01 3.807294E-03 -2.376682E+00 +2.376683E+00 1.151027E+00 -8.060903E-02 +8.060902E-02 1.323179E-03 -1.961863E-01 -7.837694E-03 -7.145451E+00 +1.961862E-01 +7.837693E-03 +7.145452E+00 1.037540E+01 -8.551805E-02 -1.486270E-03 -2.081364E-01 -8.803959E-03 +8.551803E-02 +1.486269E-03 +2.081363E-01 +8.803955E-03 2.053205E+01 -8.469503E+01 -3.235620E-02 -2.102893E-04 -8.006316E-02 -1.287560E-03 -1.326546E+01 -3.519488E+01 -1.867472E-01 -6.975145E-03 -5.194280E-01 -5.396293E-02 +8.469498E+01 +3.235618E-02 +2.102891E-04 +8.006311E-02 +1.287559E-03 +1.326545E+01 +3.519484E+01 +1.867471E-01 +6.975133E-03 +5.194275E-01 +5.396284E-02 7.558115E+00 1.142535E+01 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -3.386210E+00 +3.386211E+00 2.294414E+00 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 1.827274E+00 -6.782303E-01 +6.782305E-01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -74,7 +74,7 @@ tally 1: 0.000000E+00 0.000000E+00 1.828685E+01 -6.719608E+01 +6.719606E+01 0.000000E+00 0.000000E+00 0.000000E+00 @@ -86,7 +86,7 @@ tally 1: 0.000000E+00 0.000000E+00 4.590713E+00 -4.224106E+00 +4.224107E+00 1.705847E+00 5.831967E-01 4.151691E+00 @@ -98,8 +98,8 @@ tally 1: 9.911973E-01 1.970084E-01 1.664732E+00 -5.598667E-01 -5.385644E-02 +5.598668E-01 +5.385645E-02 5.858353E-04 1.310758E-01 3.470126E-03 @@ -111,61 +111,61 @@ tally 1: 7.080943E-03 7.105765E+00 1.025960E+01 -8.287513E-02 +8.287512E-02 1.396474E-03 2.017039E-01 -8.272054E-03 +8.272053E-03 2.099024E+01 -8.854252E+01 +8.854251E+01 3.191885E-02 -2.048369E-04 -7.898097E-02 -1.254176E-03 +2.048368E-04 +7.898095E-02 +1.254175E-03 1.355820E+01 -3.676863E+01 +3.676862E+01 1.815102E-01 -6.590730E-03 -5.048615E-01 -5.098892E-02 +6.590727E-03 +5.048614E-01 +5.098890E-02 5.093659E+00 5.192360E+00 -1.874631E+00 -7.031791E-01 -4.562477E+00 -4.165198E+00 +1.874632E+00 +7.031793E-01 +4.562478E+00 +4.165199E+00 2.870213E+00 1.650126E+00 4.244068E-01 -3.608744E-02 +3.608745E-02 1.032921E+00 -2.137597E-01 +2.137598E-01 1.703400E+00 -5.873028E-01 -5.464556E-02 -6.042852E-04 +5.873029E-01 +5.464557E-02 +6.042855E-04 1.329964E-01 -3.579412E-03 +3.579413E-03 2.389118E+00 -1.163673E+00 -7.832235E-02 -1.251253E-03 -1.906209E-01 -7.411650E-03 +1.163674E+00 +7.832237E-02 +1.251254E-03 +1.906210E-01 +7.411654E-03 7.162706E+00 1.042515E+01 -8.273829E-02 +8.273831E-02 1.391799E-03 2.013709E-01 -8.244357E-03 -2.043146E+01 -8.383560E+01 +8.244359E-03 +2.043145E+01 +8.383557E+01 3.096158E-02 1.924116E-04 -7.661227E-02 -1.178099E-03 +7.661226E-02 +1.178098E-03 1.314148E+01 -3.454146E+01 -1.771733E-01 -6.279891E-03 -4.927985E-01 -4.858413E-02 +3.454143E+01 +1.771732E-01 +6.279887E-03 +4.927984E-01 +4.858410E-02 From d919a89a792ab80e0e2f89cb90f2abfdddef0960 Mon Sep 17 00:00:00 2001 From: spasmann Date: Tue, 4 Feb 2025 12:51:12 -0800 Subject: [PATCH 13/21] reduce rhalton outputs to one array of samples --- src/random_ray/random_ray.cpp | 44 +++++++++++++---------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 9c2c71a9b6f..5ca6a8867ec 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -186,7 +186,6 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) for (int i = arr.size() - 1; i > 0; --i) { // Generate a random index in the range [0, i] int j = uniform_int_distribution(0, i, seed); - // Swap arr[i] with arr[j] std::swap(arr[i], arr[j]); } } @@ -196,41 +195,30 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // Algorithm adapted from: // A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017. // URL https://arxiv.org/abs/1706.02808 -vector> rhalton(int64_t N, int dim, uint64_t* seed, int64_t skip = 0) +vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) { - int64_t b; - double b2r; - vector ans(N); - vector ind(N); + int64_t b, res, dig; + double b2r, ans; vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; - vector> halton(N, vector(dim, 0.0)); - - std::iota(ind.begin(), ind.end(), skip); + vector halton(dim, 0.0); for (int D = 0; D < dim; ++D) { b = primes[D]; + vector perm(b); b2r = 1.0 / b; - vector res(ind); - std::fill(ans.begin(), ans.end(), 0.0); + res = skip; + ans = 0.0; while ((1.0 - b2r) < 1.0) { - vector dig(N); - // randomaly permute a sequence from skip to skip+N - vector perm(b); std::iota(perm.begin(), perm.end(), 0); fisher_yates_shuffle(perm, seed); - - for (int i = 0; i < N; ++i) { - dig[i] = res[i] % b; - ans[i] += perm[dig[i]] * b2r; - res[i] = (res[i] - dig[i]) / b; - } + dig = res % b; + ans += perm[dig] * b2r; + res = (res - dig) / b; b2r /= b; } - for (int i = 0; i < N; ++i) { - halton[i][D] = ans[i]; - } + halton[D] = ans; } return halton; @@ -637,22 +625,22 @@ SourceSite RandomRay::sample_halton() init_particle_seeds(batch_seed, seeds()); stream() = STREAM_TRACKING; - // Calculate next samples in LDS - vector> samples = rhalton(1, 5, current_seed(), skip = skip); + // Calculate next samples in LDS across 5 dimensions + vector samples = rhalton(5, current_seed(), skip = skip); // Get spatial box of ray_source_ SpatialBox* sb = dynamic_cast( dynamic_cast(RandomRay::ray_source_.get())->space()); // Sample spatial distribution - Position xi {samples[0][0], samples[0][1], samples[0][2]}; + Position xi {samples[0], samples[1], samples[2]}; Position shift {1e-9, 1e-9, 1e-9}; site.r = (sb->lower_left() + shift) + xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); // Sample Polar cosine and azimuthal angles - double mu = 2.0 * samples[0][3] - 1.0; - double azi = 2.0 * PI * samples[0][4]; + double mu = 2.0 * samples[3] - 1.0; + double azi = 2.0 * PI * samples[4]; // Convert to Cartesian coordinates double c = std::sqrt(1.0 - mu * mu); site.u.x = mu; From 3bc75629e525b2d2573b1affde580573c2429d99 Mon Sep 17 00:00:00 2001 From: spasmann Date: Tue, 4 Feb 2025 13:19:32 -0800 Subject: [PATCH 14/21] use FP_COINCIDENT to shift particle positions --- src/random_ray/random_ray.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 5ca6a8867ec..bd9aad63ac2 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -634,7 +634,8 @@ SourceSite RandomRay::sample_halton() // Sample spatial distribution Position xi {samples[0], samples[1], samples[2]}; - Position shift {1e-9, 1e-9, 1e-9}; + // make a small shift in position to avoid geometry floating point issues + Position shift {FP_COINCIDENT , FP_COINCIDENT , FP_COINCIDENT}; site.r = (sb->lower_left() + shift) + xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); From fa25fdb41eb626f90a6c33066475203bf6a59644 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 12 Feb 2025 21:51:13 +0000 Subject: [PATCH 15/21] ran git clang-format develop with LLVM15 --- include/openmc/particle_data.h | 4 ++-- src/random_ray/random_ray.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 2255d1a7d88..7f56bb159e8 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -186,8 +186,8 @@ struct CacheDataMG { struct BoundaryInfo { double distance {INFINITY}; //!< distance to nearest boundary int surface { - SURFACE_NONE}; //!< surface token, non-zero if boundary is surface - int coord_level; //!< coordinate level after crossing boundary + SURFACE_NONE}; //!< surface token, non-zero if boundary is surface + int coord_level; //!< coordinate level after crossing boundary array lattice_translation {}; //!< which way lattice indices will change diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 7adb87f142d..e277be570c8 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -631,7 +631,7 @@ SourceSite RandomRay::sample_halton() // Sample spatial distribution Position xi {samples[0], samples[1], samples[2]}; // make a small shift in position to avoid geometry floating point issues - Position shift {FP_COINCIDENT , FP_COINCIDENT , FP_COINCIDENT}; + Position shift {FP_COINCIDENT, FP_COINCIDENT, FP_COINCIDENT}; site.r = (sb->lower_left() + shift) + xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift)); From a5cb348e86b130797be09fc1b41790354750454a Mon Sep 17 00:00:00 2001 From: John Tramm Date: Wed, 12 Feb 2025 21:52:02 +0000 Subject: [PATCH 16/21] ran git clang-format develop with LLVM15 --- include/openmc/particle_data.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 7f56bb159e8..2255d1a7d88 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -186,8 +186,8 @@ struct CacheDataMG { struct BoundaryInfo { double distance {INFINITY}; //!< distance to nearest boundary int surface { - SURFACE_NONE}; //!< surface token, non-zero if boundary is surface - int coord_level; //!< coordinate level after crossing boundary + SURFACE_NONE}; //!< surface token, non-zero if boundary is surface + int coord_level; //!< coordinate level after crossing boundary array lattice_translation {}; //!< which way lattice indices will change From 10d49d0b03b6bc2cd0dcf669529aaaf4e13c1e85 Mon Sep 17 00:00:00 2001 From: Sam Pasmann <46267220+spasmann@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:06:30 -0500 Subject: [PATCH 17/21] Static prime basis array Co-authored-by: John Tramm --- src/random_ray/random_ray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index e277be570c8..ce0c2419ead 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -199,7 +199,7 @@ vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) { int64_t b, res, dig; double b2r, ans; - vector primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; +const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector halton(dim, 0.0); for (int D = 0; D < dim; ++D) { From 74ebcb1e2eb6f271f46e87d371d4772f4acc8eb5 Mon Sep 17 00:00:00 2001 From: Sam Pasmann <46267220+spasmann@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:10:58 -0500 Subject: [PATCH 18/21] Move permutation vector for optimized memory allocation Co-authored-by: John Tramm --- src/random_ray/random_ray.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index ce0c2419ead..6d94755ff2c 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -202,9 +202,10 @@ vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector halton(dim, 0.0); + std::vector perm; for (int D = 0; D < dim; ++D) { b = primes[D]; - vector perm(b); + perm.resize(b); b2r = 1.0 / b; res = skip; ans = 0.0; From b32aeaf69dbdd93dff5acf06d5727aa13920913e Mon Sep 17 00:00:00 2001 From: Sam Pasmann <46267220+spasmann@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:11:41 -0500 Subject: [PATCH 19/21] Check dimension size Co-authored-by: John Tramm --- src/random_ray/random_ray.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 6d94755ff2c..cd4df0b334f 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -197,6 +197,9 @@ void fisher_yates_shuffle(vector& arr, uint64_t* seed) // URL https://arxiv.org/abs/1706.02808 vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) { + if (dim > 10) { + fatal_error("Halton sampling dimension too large"); + } int64_t b, res, dig; double b2r, ans; const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; From 789b2429bc3f0c5cbcd586a4709c834faa97ca8d Mon Sep 17 00:00:00 2001 From: John Tramm Date: Mon, 17 Feb 2025 17:06:24 +0000 Subject: [PATCH 20/21] ran git clang-format develop --- src/random_ray/random_ray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index cd4df0b334f..3cb2d2639a3 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -202,7 +202,7 @@ vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) } int64_t b, res, dig; double b2r, ans; -const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; + const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector halton(dim, 0.0); std::vector perm; From 795507ccabe8812d5cac6a953fdadb986aa13a91 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Tue, 18 Feb 2025 19:54:10 -0600 Subject: [PATCH 21/21] replaced std::vector with openmc::vector for consistency --- src/random_ray/random_ray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index 3cb2d2639a3..efd85786c2b 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -205,7 +205,7 @@ vector rhalton(int dim, uint64_t* seed, int64_t skip = 0) const std::array primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; vector halton(dim, 0.0); - std::vector perm; + vector perm; for (int D = 0; D < dim; ++D) { b = primes[D]; perm.resize(b);

`BTVz zMB)c?1qpIG95ua!pEsVu;}p{9 z#_cYhfCb4xjcWC{`%mhpT$(D17i8z$d9tvt z8ho#&DRBQf2v1mhowJjS1A2ZLM4YaK$B*K3!aP^_q71u$(dVFjXKR|VesD>?+PAf% z31+Vb-Lp{`fxm*C`D?4&;Jz=rJ1-C=BPy>w)FW>35X>JfJiGa6O1{sx0+X7B2&*Z&Y!Xj1B^Cvgz~u>-!zsX$VxDOPUCSdE-qe@ zbpH-nsY^$`kKo#)9C9Xh_XNI!jva&i?O!Xw!|870mM0UiM&^)mm&65Gd zqpH*%i4*Pz<8x9vn=ZvX?}EJz!6oTzIL<`|p-1jP-@xMdYFPE*Y(OWje%1W?Bovbu zd}tF$gl3Yu^~oxJgrFW>JUy~(k@1Z#*%gGzd!&naQ z!J)vTqe(zW;iH)MXcPEz-u|rIt8qABlQnD2n+&+Y@b7yLKHAbQwpWHAB#U@ z!o?|w((+m+*Sew2@2LhckqlTo=UH!PFaj4$?VbchBj}OH??P+-!36W?44&Qio6guC zmHY-A{oV8r99)1Kn<+7}m0yi-8a2a%6_&Yz`Q@ZX<#u%&w5_A;z< zs7n9bL+uzF2cgBs&ZNJ}1W}K8 zTs#LG;MChF{*Eqyro9$n&lAf=(67(qaRjxgkN%(~au=#UOP41)ld?O%H zepJX+(hc8xlxs?&hTy^(i97J*1>~`9WU&AH?Q=*R4v71kEkNcntGRD#xcliPgR{dv>#X^Z{Wo?q!m}HRSP$u4 zK!RE2_Btw1hKp0sJ)hUO(>(yVK(dTjm}$y{=-3GRh#8kE#L>+M4)k?G6tF89Fdl5(FvmjjSg?^nDedmR0KxKc8tgDj3 z=5WWwyDzmE0l8-fw(m1zw6o8JgrbY#v96$++ossY$LK%6)5i@Mf$Z zRtrj>9uw+;^YsQ_=8yLwdx7iAzA<@%9P=%}X8QIj#q+DGBXCvp@VTddaDE+fO&k4j zp%}!$y1=NS=Wx<%I(W!_655}?ImmFX1OY33(}CmL=cISa7>`&kg6Jw&rj96Fzw!8! z#~jgOeQ;-@jzV-MGM0p{8lbn$IYOK?5L}r8 zPoC*fSGl}C-tMmtCFan1W9?DjX1Mp+vbfuWPT=k(^sX2+3=Oq9B`=(0MssEaAL@-f zOpvn`H`vOi1fsT&7ktK{O(MId&o$gQI}gV{^sniAf%5ycqW+{BsP<}#)@bh#+-;Gi zwWpyT@kCQRXgW){9^LZ2EzXJVrtY-aDUcjdvuJ68Yll2{a6M(3+YfGxaYzYTm&2KN zONQ0v15kWCb5t|_IZ(eYB^KGpPEZd%hxR#Z#`za-q2STsfk>%2xMep#sw!)-6A@7F zt4Gf1@8#fX&+}&iB9m}r%;`z|t3&ANW7CCUf`sEpWLrWu)7`vfjQek|&zEj@E+1)_ z#^%ql#;OZvgbIMF;=X&r5~DEZtBA8T=z@=8BH83`WB@38&C=C>`y7{_wl&Cb3-JAD z#$D(^j@iIdV#IIdc*G7(K#kSI!WpWGaHWJaSpQfx^evz@+c&_B2poUgU8g~qL%ykF zD+#F2DcyO004}h3)E`bG$84-$pYQBO%{!%$1rJaj&stUa32Pfh1n;cR7pJ>iGd|!G z380S{Gl9sJ*N?x%QNjN5Y7cYb7%&KznMRW&smO9;ufkP!PrLzTTcZ} zVEsDA%rP@lu^Ce6$ZNgoD}!9O#*Vp_jKPX8t9kks=^*2VUH5)p!sC1KIl02M;hcOZ z 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 I=Y?4Z0NP<`Z~y=R literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-21/inputs_true.dat b/tests/regression_tests/surface_source_write/case-21/inputs_true.dat new file mode 100644 index 00000000000..5dae8374d27 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-21/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-21/results_true.dat b/tests/regression_tests/surface_source_write/case-21/results_true.dat new file mode 100644 index 00000000000..7ccce7cc3a6 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-21/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.149925E+00 2.542255E-01 diff --git a/tests/regression_tests/surface_source_write/case-21/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-21/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..ed87ce849f788a346f99843aa263565fd326040c GIT binary patch literal 2144 zcmeHI!A`KklY} zzdej5Nr*X3OP&ekQ8*a|lwq1ggLcq|OOs&n&780s#X)cUJyR_U--OKEEUH<~jpI0K nusoVGSFHZC=n6-qen}4IqrvUfbzNWu)_zFyYF=;a|J;Euz71;T literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat new file mode 100644 index 00000000000..5cec1b76f73 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-a01/inputs_true.dat @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 1 2 3 + 200 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-a01/results_true.dat b/tests/regression_tests/surface_source_write/case-a01/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-a01/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-a01/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..4a86235679085a07458904653a5b63f162059dea GIT binary patch literal 6616 zcmeHMc|4Wd8s4Tzh)Ss_BuS)FqRg^FM03nw$gJ<)#vZ?NIf7mttFCmU=e4iddj1kVfN^NVv#CJgZWZbL0C z5^W?gKN0b6k_h)?i-$=3KmJcCuvLpZVHFdcHQ}fCxX%bNoKBo_w6d`{XlZXZks^GF z5d;3iD1`f!)z^F^C4BywBp%w2?U7?P&aMtN#PoA%jTy9G0)O&-(eJq?a$>+3=mg z#x^-}6+QtGIY>$z^q%=N;~%C)N4)>?&mt*PY4xsn z-(()YnI=qEdlnzZe46}w@pQGP@L|Ln|9jPB=D(RE?Xa7;CS8QzOcSQl$N9+d37@T> zW;|nRy4o{`oF|50j02}>PgiSgbIj7!#@gbLd)v>8PWiZWn#uQ^M7tPJ|eZ$VqKC!WQ@O{R?b^y1tix6Y& z9Nhi%!BM{-w%w(tugON6f$+WqzF!$@{@?z41saD`TF2rBp_|uSi5ED5}GdpWts?!D^1`t|CQ<9mP}iKBkZ{m*k1pN}+0 zCvF@X1`!fZ&OFTx{elV-hY@o*oyt7L2TqOcWo-@4dRWU8Ut$>hsA z;c~QIWX2X>_i9Kc`5rd@_<{*%!kDyq>3mpml0!mIa*@}s1R80I=59L;n(a#*oJbexJjtQI#K3l?s!>Ai8j&|I-$D>+BFhe5 zgwC|LgdKaYzeFdj5*;Bg?Vc@G4?#MG4IPnn=X($Yh~@8RsaS* z+_{@q4q{4+H$GB5slkLp=q5?~pwPf^2wrRD@5h9SeM3R{y892eR1iMEG@mco7XGXr zw#}Ac>)6-~?+!-X_P$qwikF@hM6WJle9z?q^>isrIK+FW^I47K5TQyeU(dU~9kMGV zjn}dD6S}1a`uGA=I@9nu_ajh2og$+%r)`*g2MYKXa+?AMci1nfK zF(m--SRTHuS=4$QR^KStnLo1uE{k^82Q`cUWhwWSwPraep)pOXH2pma;XbGK1a*OT z_sR_-ALPlci%Z@p>|V@-!>HR&EI0;*&c@sL8^OHIU7B}&%@m*8()Zj7cC@186t|67 zQ7zhN#hZV4OCelH+Qq&5$x3qTK$w)i7PD>yAG&UQEI45ow{0$!p@K+(x@)6UHQIjv z>7yv2ukiEo=XXcS>QELO*)IHh14^j=UUF;GS4i>`G;iO05|d3++l ztm-xI)}6=9>qF>9=d+jvCsMFsCkUq$6b1qopItED^>V|*(-VMeJLTn<|AxP^UcMji>guJVAM%yz^V!KAo zGpp-e|1nNmIVmw-z70873#so}I|dE|&3j$0*THy1M~u{PAv0okt&4KO?7ixkWiSPgO&xZHL+jb zg?`qNhzyV^f}44-_4jZ4h|PQY{TL-nmWdDJ`WUn5_Vj}Fxj)i|VXljfO!jvQl5;_? zcDRk8=pa)ot50ocxnV%d4xK7U|MgDiq5Y*u&a!`(XHGW+ZK$E7SVS}75Pax9*vo>W z5VI$HDe6V5o>Qa>B1XU>AkoIrvlU=Rc#5`sH-vP&+iB{Zg@zT{$hDklXguURl(Jmu^|a&|YmZVrsV4AUhg(t{o7 zQB9uf3eFrbv$o4yEmMw)PD`a4a#q4ntX6;e<>VjoVLf-gEwQeb;28zGC!0Rne(3`L zpmp&v+}}Z$GXE-}l-m)Rv*3p~8zyN? zy|Sn34mT%^0FS*Fw^q0G9byawXt*rz3{wu8XM87o4> zU!s1wOrGVx)gT2Odr?tE39{XX(*Y?pKlU~2xl?^bt+cI(3elye%7J2eC~I+U(6HAx zbmfrc+fz@w(1SOnM%F5^=(OFwq+?M<=)A|?q=cxKSg`Qc68WZojw2)(I`rl70E+T7 z<24N(fIja>7vEMVgZ-)Fomr{vaKJw{!B-;^`P*fdH)ecA$A$Zp2Sa(uT8Em{y_cyl z>Bi_ciK&&DY}9Remfb>m2Yi9$v&-74@Wc>k0D&O?zUj2r-RQNghuHE{NaXb(4-BGB>M94J3(NE{42Tf zLS#^)bCNH%7*vk7rpf1aVTp;0*b-kFG2swCpzj}6KBiu8cooF@p|x*?W74c4bf4EM zOgE$t8GE@sj8twx7DDY?^-4b>cNc#HZNFNi-J$l5v$_>i66TQL-JZdOL+D075B9L= z7P3BLp@Kmr_?{Fw(;ho8#YgIsnWOhe0~*q_R~0{=jvBQa7o9*Ui$Gu5bKmRs`|qYe~g z)}T%mdye3hZ->e9huF{(FGD4VAM@GAg5&a$myLf#54h{iHOjsH9emH;G6=rkhU8*A zFE$EhBA>k>m9`5zk@(S*&sCkvVWUFL%K0ihWKCh)Udgg$OneC4=Z;Jq zOgN0XvEp3icBtmOHG=jRKPb{m?}glb_TpB)O{lcUwxBY-3-%VRMq+k_uu##wtiUlD zjW@k=2>;_ACSsjaCa!44g!5N?)FOo5+nuMvLII5vhUuT6OToLNL-#va4vz`!5o$x{ ztG_;3V_F5(O)AU$`peLa^h=&RnhP<-aD9gep{!|f=zb&Chj_mJd9k)@_O1~ma~jjX zt3*Lo%}yIV4n734pCaEp!@6N)l$5Jm5f0!U{Mkn%52ftVZ~F8(6;fNx96Z;4`ytK& ze0_*>RO6#lnaGQ5NbxBP{SerT@b1()1D&_2l3j?}jg8m}eLy=UZm3-<_yA4OZy$5Y zts}>o<~Ajd3Nzsl>qF=ij07nR$0BJE#b| zoNan0brii`GB_BgI(}njp@(@*!4Hg%%l`8%aM`C11DF4ouK5X5z!>1J=)l z=vf{DTNblpsF9t5^{ypvqtj% LC5*DhS910bE;gYLeTFMBG>ducP#FN z=pYOE7`c2{G2H!|uFoDUCF$kQq)p5?jO)XSGp})T98XL?c!rhn86EsKg(L1=yM3JY zeP_GMnMfU%4B*zk$kp4L4+Xa?ReVw<$cCMt6MYPrbt7=-=fQHE5B_fjlJoV;q_h`# zpqW*-<4XY*ZHix-FR^+6jc%#SlxizMD!NZ)QjgTY++TO5iQa65`8&L0uMN*3vzc7< z8slQ-Luf?TjrIGSNj-1ue)CeKY0MM#FqDdf#geWmZTSX4Ek<2YFI&MSEZCSku@<72 g7E`WJ^I(JNZ + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d01/results_true.dat b/tests/regression_tests/surface_source_write/case-d01/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d01/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d01/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..8f8cd604dc7d685af08c32e69922749b6ad01654 GIT binary patch literal 33344 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat new file mode 100644 index 00000000000..d6ca4143c62 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d02/inputs_true.dat @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 1 + 300 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d02/results_true.dat b/tests/regression_tests/surface_source_write/case-d02/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d02/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d02/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d02/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..3abc471e043152b09e736a5f09012f5f0a95191b GIT binary patch literal 31472 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 + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d03/results_true.dat b/tests/regression_tests/surface_source_write/case-d03/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d03/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d03/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..8435e8f17f4284ea1916ca71e048265aea650f19 GIT binary patch literal 33344 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 TcuCG7%T29+iWS#a5jp + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 1 + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d04/results_true.dat b/tests/regression_tests/surface_source_write/case-d04/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d04/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d04/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d04/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6c45d92e6117a936f05040f5ce367471f98b32fa GIT binary patch literal 31472 zcmeFZc{o<<8|ZJI=b2YB%RHoHeAY5W(O^oHLKz}uXfPxpDJhjCDPt5(B#H7YB}6jM zGS6d_A&O(~ZGF%8yl1a#@AJp`<9Gee+r{%*?^^GtdwA|=xbL+tn;06iG6^wJ{PAL7 zprE1P`139PYjyqgE#R*s_}}>Fhu7awuD6lv?IWtc-cV9dQ~dFn!1{5H_3sy@{_DV= z_2X6+Mn)8CXX4+Fx9cBM2+;rajsZXL|JwgwX~4t?{nJ%!*QLh&sbbyFaAo+OIOXGf z^pKOIm&c!naKA+G6aEid&UJq!_iGjkmG$puq+nS4=i=^p)X)FqQT*$<*WS>r{owq+ z{$GFmP5bAu_;vrMT<;l_lz*Kmw0;5JQd6i<|8ag8cW1fAjwDu03+p z)6xIvkwdONCr+JQyT;^iuKVvp9BYn4^XCCk3g1&cE{EKYP?-Jq34inczppvz=;P<^ z?D?NQ^=AP1n``hx|9vg(n%Dl(Z>XrK{_If<>-~(H^6w4P_?fzvr7QmJ3<$+8ZC|av^Do=v$O-Z z-rykX={6XlnPw%aHU@W>v44%-tdE99x#rWesOkMh`3C-I|1{)UyLx`ju77>-pWoVc zGTR3IIgn$kLMDl6d>D)1kqs}@q_JA7eZkQo&q3y|Glj2D_5=M?jZwdSqac9GEhV;@ z70o{;e>J)k{YMVYwn^G)K9fp^CCt0sG)NT0;@TbhSqumgUc1HdSJHZDHpQ*aOKQE2M@yN>MmZysfGzk zr;3^%ngC^1sg<_%tw207iF9mZEfnA?vjw%CLELWtU) z+(We|sF8w#Z|Nye7Bkr6+!6Ao9msH5Dduh+17Uf=uU^Wwf&Q8J68$Cvl)vPbh7Wl$3#yURCsr7+J3MJzOFPN{)%YWcXJ4kEV=l|Z zdqI`3j>2fzR!zG|CU?E|dcXCx*!o;x)ms=9ysi4MU*M z&^tiWs|VI*PYqK#iJ~mVS6KXa82=*&*W0l%1?qFEBjB7OPa0!030tx03tq95#cG%% z?Vj*9fg}w_sc4T8XdAJ=!N0N>4mAaTjMNfE18mqUqel1sBM0|u6%#FKT}yUsa(?T? z{Vn|1g_`7Fx{}gZWYbKwTG#-bc>MmXi|YvV`OMcYrj!rH;!Zw=bK0mq*R!;#SOr23 zBd)OZR{u(@kg_8Qdo{zU7394DcX(b9`4&x&xGxpW-=v*}krrF{+|u5GvdpdnT`#KO zD0L%6U+fk%l1^XR_0u*&&R+|q|LJd5=Zroi?H0nMoa*9JWvH>oTHCJgj7# zkJ@)c94Z8`_$)qgW)3E7KABy3{|0KT?eneo8Nd5r?vFa_vAYAXzSdmUY^)0^@t3qu zJw?#03s>&$<0KL4jf0HS+|DxQ%0|L$IasovHvWPF$HQ56)BJ>nw_Kk|MU()ii!Y8W zm-YcP@Y{@C)F>8;k?15mT49r5XVBnUa2WE=rjBr(-i5MwYKV#ccIv8JtqJa$iN zp-hSEYv-V8u#oX`>0w(LJOM)HE?YRk){@!;U z!N|| zG{<-DA|8Ku{=)o+!(M*^#M?6>QFNAmKj#Iox4NId(FJf~84)s%yA69ls-_~p`O$Hh zJX?G~>dkA=5%rKkk4F*>vWjrX5h2zaFB!)?m7S|Ygc-5ZYD?RGUI05^J#IW&x(G|W zM4vDU6vH5f(TwvR)8IW{%~@%N4>0Zb;e(V{cca2=1*p_#;&Fj|jpAIO{=!0u56k*Q zD|Lg96TA9ctH;bx3M<~{A?kCk2f7%iOlOIYLnOT31%(izXL-Hdxz*7x<_P_B={v{(yL)6%rftRMawZ1}Il^Rm`+Qk5=;j;`LUVab$XpB;BDr|hC!=g55_e_xi(gg) zq-!x4IP_s0Sg5b2h0(Ub^URX>=W1n8_lmZ!4<7zA2Ok&mzTdM9`$NK` zDf8idef@C$6)*{TGzq@tNFwUVZ$RwUy2E+0EzmRK?7c9~t?2lula>qzLMYFMb$Rfn zmOmo8<){GGwcx&e$)6dsd%EZ5MKell)5N2)4vlFjoaa^78&d@-B%7?WbTZ(z=Rf55 zkD8(UWp>#m7E**95gf4IZW_IJYP^aO+a<4Ts?aWgxVdnBTt2RflpGE&)t(xKECVk7 zb!8Z>xvLMhdyAV|7AeP5hLT|@ATOdV8f057>}*%1w2Uc6X6#R zV^okEh*Fa>+6(24Jqpj=8-g4f<3e|BzQVXuc@I(1YA0E+HX*00Q88f2M?*fg)cH}*cy^KP`zX< z)SsJp-iynFb5b4J4V=%hBWyss+ggSnL%^BtpSE(ywwUx|b<|(Lro?L>Zk_Li_1Yg> zbqYqolW*zC41xkE;dP?lS6LacR; z-_c$fhQ^eS&#bsN!|T6p3jCh$g?YO&sJ%zTQH}2%q>xclLcK|@b8x@rVq85bbyEm) zqr0KF?=dA}f9qW{Yv^VyK{v=_XL%PquQcYX?>r7ZZmxP&Y+nIWTbDm<`CyHP+~Z_r z;3DS8kom!n6!`!RURtbG@=gBTZ-R)SP&sS+JyDGDc5K{ibQZX$%5v)Qr*WX5Dq)!# z(+wVU#Sf@V3!)Tm=Q7HDi2Y5LOm9aXY;|-uoClU_Q5oGQNr*%G6%Kn{DXjIwkx$ge z2Ej$sOhwbKT2Szt)Ahdg5S-$qnRvQg4SiqadqFZ+l~8YTWb#~$&{B!pDv0eLZ+Ts2 z$AEw*XI3``i(!)0_wII(Iw5nWRYrd1IFK@W>S^I#15X_aRle|E4J~MY(#(+~L&%XQ z<0y(5KZoB)m}lFNO^)JMkocP}`hcMr(x-PFQ`l7sYr0tTy>?6kt9HZ2%jZe}$IMZq z`*fUWf-o(VuXQ8jD6ezyb;E%j+b`LC;>KQ{>&$#p!;NhAYIhxQQAMWq>CThB_QNb= zS?P1Hdcn`1Lw7EG?1gH_D;CsUMbYIvn)Zuz#Onoo{|Fx!Ix5GmyzyLt5mnV3CufDQ z$;7x|-Dp+plkegk`><*#fCVUXu}neJGm`gD*wn(A@RlI1dPDTXch8$1DDgO_O6CWO z&pzu*#PK73SbI!>A{$m=f0`=wp)7XH+i#{Vz7G^H=r~9Y^+2U;;k|mb{h;&hue*5* z>gYB8$JR$MVn4w5wD5i!a8iR}iy{Z|0Zp#UZ0EyFHFemUBR61X*K-P2vZleJ?UJ}7 z!+UsEA@<9c$wc74iKnu78AZ1;UuEO|piF2VO)@{YmPGkgCXN*gWU%?!{gEF_d%tm4 z$!0YyGTOdS)$~?NJ|)((8ocZrnd&rx4Wecv(xcDskM=TIb;LfcNG@ zr2;ofWUT*@a8;rZRu;(mAf8bIlRdZXp7qyzvuDYJP-J9-M=(yDzHcH1)B~0n)i3Ey)lPrYPc346pDZSiVBELv0>pTGEyFT^fbw zrempKNFNYzQ)7Nu3D6un=f;(3T|$lynIANGi%#n87R2fqj3wTFr$>&B#~kqup~AY} z2AqxW8iFT3Dbdl-`hgjxm54gWA;|UOBqph=fd;$EqGONr2|2oC_7M{43iJ;D1&$@0 zL?AZ_nbx$F%i|P82Aobs?n^9&S=S9Eh&BQd7|juQ|#)f?Cih|_EE7e5jf?ZI%k zj|KUByYfzKIxU92R(BhxYX(kZ{}4N30xJ(V^3fXM85$IPZywi1})G>M;A$A1ZAi z^IKPbryviSE^59gGG$IE4>`xpX(+w)HWh+7?zMg6E`(InojiV|S`;gsTCkSA-V9Qn z(Ja@=OoI6QD4$#VszAW~qk%8&emNC|eU23wgq{rMK^_>Q`4T+b*j9?? z!QWV9FzeIXb;Zy20ie8@MMSwBUJrXHn|FT*s++WOsV`Wd3SyM}j8WQz9DLs&j|WqB zuc<@wnqXhlquCn^Yx^#g8p=jT#j!ot)yBnUb@q(bC=3$@+fHv_^(W0b@=ry=%@6BB2@ z3l20>S%HWzaJ%)u0%IbA7H1+4+02RkdOI2CgAvm+wMcG^&gXRGEISXj{kF^MfPo?w z=`WUJ8=e8w8V~7CzMFzFJ5_tmMAgBsby2@}D(IuuMTdo)P7u$pO~^PAUq+JWpYtH) z+V=|&b#Y-5GK-acx}u1JXilVN{@OloS$g-@7emmi{>|3Z#U}7t*&*A=UkcsIXt*o# z7_ncQl5uh@@3t5p;6e0$?%aA~8#kuVTk!-5XTcuaO#i++r4FtLbd&_Iln^%(#Up9Kv++< z60}VC^JcZI#p`}ck=d8Ma5-fokLLqcv`;d#L$8u}evNZ*e=~X5o=Z2(hgI~vw|Pb4 zK=|R1#uX)TWQu$A`QE!7FiSg+V}N}C#Fg1f>U|ghQ4|}s6A!AQl0juRcZZn}%0r&l z8R*hA@9<+l&c1tn$w);IDYNj;c4#DF)R{Hv(fK1lCY(iJMWPF2iu6TYHSK^y*@0CE ztpR$H^6R7R6}t&JyU6tRVQ`ODjgb&$xH~?dbdVaGYQF3gNI`>G@*EqPnV5j2+*ity z!fim}0}rEc)knZ+&J^8}V2S!3+n8$}OT6CRO~w&+Ka=${gBshfmT;DLn-Eg!`?*KE zP7!fpm%J^{-3beVCN8OK4+7r3W~@3#`@zmv6LLK+ThNt@b>GcGiN{lmbq*ePmcAq@ z%eAs#dj=Y{mR9)@PeDCX3!6^T%2ZkzO{il~iRb&Q$wc0s-%~KD- zh5I>DjP)Y`HhTU%IH-u;;^N58U_3x*A8Rtb&Fr8*#c-1nTM$rDV2l&ORv%wkeHb+W zE6NHM9~ljT0C`lKO|%QXDK6!=iRpu~VFjXD2XxVlv00t5y~O*L`^Y%{&$YI19a{k| zhc?8X8x=y{N@vIp@7#oZuF^8wb)+1+Jin!A@_hoZ++8v+NNEMa)tffW&YPjF_pDzO zogiK>*phLY-)n16_|Agkk0~XY9+0qu6xz;4HIf+ZRACMuQvsOpI&2f#It{Jk%7c{` z5-Q0*)+Qr6@ zH{6K0P-REZRdI~pEo9WXWgH}%sOE~F>H>}~yeFK`V$kYT29@y*GgPwlr>WPpEFtGG z8RvVA($>@|NJE3fCamkx3yreOO2B^gkbA#iFECVnxi@_+-;|Yf zus$=<9%Wy7bG-2o@jM=%@8aq+8ElSlnV>)}2Fbo?c_DG%7Oh0`YzoEe^otvK?7TP!K|dR{>*xKQ*Fx z!RppX4F~3B)@Q~fJ_(-x^uE@TRuAeG%Q6JM)I+(H33Mh^5nU)gy}?e~hEQ+h`K*UW zWE=L3b0ajYU<}Iv?LRe{|IA;Gu z9-AAe2)*S!2n+_{oEJkkbaT7ST(#T_gMNms26LIB545x%aXS*nYx4Y$F{2}Gl86v; zb_;XBoB|EDbuYK>eaSgsW02>pX)_7-OopG(rHKK|5|pc-ue5L2KIk-FpmnH+xs(**mk@e5(ZWA&=w%@i>$e@wa>RGSSiS6D{sWe%gjjnO;Jit#JhY@{Q$ zow*$12le?9s&I9G?Hdaf4=iy&C*D5!JU` z-0lOs$gZxa2-Yz^WG4B^$?M+j(31P3z{d@fQ2$4_$f!^npxM84K2JDufzGVxc{@~dGWO8>PC_EPqgrg2*f3U}m{yK=6Y$vm zds+z;_3(r><<^n3CU8j5JpR0&ChEUK=GKNvV!ds|6}H|!c$m6XR+<$XDL`L3jPN6F z`Z*r9`n3PZQ5e?H&ia0cP#$uQb*HS(f(;3KDxfdDTX7L+TWf7Gjikd$O>aTp^uL~VgN?G35+{Gw^k3sb#F$6Web{T(0d}%!1-Tu%J&EOtu`_vm4}YxQkn^1TeQzF zFC3Rf>UyC@@$Ngoj8RmN#dU2TA&8$v!DlUx@$2BPMI&R>^XIs4WD;?Gl^B`c#KLEG zW>9cSxDSb>$+WKgz z;`KVSQZaQ0?FG1bP{o_HZyIJs zQh5zEeS)jj_v&M{^ig(x1rNlTcpidt@Nv$@E%4g8yL?FGQ5xSbmpG89)#Zr?uN5)S zvYkTn{ume;@}v@a_z|cHX4QI3^uVu0X6G!TmC(a4hJQVhC)S%ZnLL)yJ_c-e<3fZ7 z!lU;I@?urJf3h=eQ?4enI&b*2y}a@cviV`1^JY=S4#Ao z8KEEGc_v(*Lv=@#@+p7A6`G%4T$o8n(oHT)eN6={T{HMa%GR$?e|u(vLE{+U5Yc+v z&)xt>mUJ$uIBZAvzM6NIiP9kCY$B7#Bw&u^)Nu|hQtaK!M^E@L+n|nWYBdRrnKDQv zH|is7O_}vCa-W91GX+|2$Ma$T*OyH|bss8Ocfd0|ig#S$QeUsC+S7KVulY>#{EzJu(RB6@Ndsm3akToPpWwLGuWv=K{6jiPv-F zoU`BR17{a`kYi7+-Q}XW5UNOisV&MREN=eyPp-TY$gMqkYya!DP2?}js4q(|;H+{p zwmc?<9!nWcdu&WxU!|}v2yUO?yV1qF-!UUsJ||xKV6i6GY3G}*{u$t(I0I!ipTw1j z;~P20ZsMk<(I5#M6V1O_V9^gcHgZ{MjIts7D@N5uFAqUaalYI3SwkQ&YSl_#@e$1O zV^s??-;6e!j9e15u^{vV#dUe`b%~FwsN6y$D?(dw{nXZ6eoWRdDfBIs401h6S0qoo z8IV#FbJe59pp+d_MR-VBcE3*ju%LjBYz(d6aPnSZ_a4wX>z|5JFn-Vdnk% z)QE^Y*EaPC8YFw*<$d+|A&}nA>wY+_5AIM1-D54@4r_M&t~l_N8&wVn?YSXxf>0hj zZ-&oDCC|USm$Ok2tJVm|o)s`)Kf4?+9H5d#ZrNr!CA}Si%uTO7IA6SjpP(m~_FO-x zDa*x1oTbs%H%cylZ6Tibs*%Yv^0Tb2XV)}9eu&>sdb1Wsb%h+x+*3u)EAlwL!3M!L zY0fb*rXj#}HcLuUycR4K&S|{0RYK!jf)7d75cku~>l|F39HH(#GrPGl(}f48B#n8H z{7GnaSX>S1xt%xbJ39?2-bw0GP9%WEc$uva6|x{78&|N8t{PerO)vLUhqz8iolKs+ zhZ^5qEhk~IUf=I{u}na}i#<$nhg7hm*F4_wg-yeEl8-ba4NHOc-2Be(-nrnxZo0;} z6=8HlY2uL4Ydu2yY+2{v{U@P=k!t+A5T+G(aWr<{3RIw|Gu^X61$)a;>^dA*1I&Bb zMi-SEL6C%nvYEvslxA@(Zt)aE^Q*Grqce8?YfhQ9a=Y%P1@Iy_+)3yt2`hU@!zA)q z4$IS84B)@r3}mjGZ||j^~B}tqNS5C&uj8h z)5j(-SI**^WF>%Vy?XydH-&h9jpsFSe>*UAVA3Fo59$0RX4G|_12b5t>6cDa!6b`x z3hpdpVBg+gX#1-kmdd;bhYY6RLandFc|KEgTKz1=eSLL8`)HHtZSMD*V;64nVS04E z3>G&yF!hx3D?jLzFq*@9H+FzAxZw2i;mO#wdRw3Ceh~~c@Cr{!7fFW&{k-Esf(#vT z-3EF7bU3T6{%+<9@G(`-F_;prM|e-d0G#M%xT7Uih`b@7jyl)}JDM*YzgU8_vP)L;7f>m%UvG$+tbDLuE;g z2{*oupn6V&)mG@=nT#I=!|}P@rv~eQc9e5Y+3y}G{Peog&fzWSsDYxvA(fMa90V6+ zy`6QEeVVgYFZOu&TRHN+9=r{aL|E9r0hac~y`GoW;@;;QCz=m4N z`E#dq(D3Iy{-3po^I0ev$3ZqaOL&155f1t|eI}D1Ss9(!I(th9u@$h{@x@{UByKBZ zqcrXVJ@w2k@~sW ze(b8|#nnt*R?JUfa_jMqRq&H4z;h4#S9o5gFr91P032OV_fKBxhdg$5JY#OssKUvB zFPzPX3GIV(@HjeBVJ~ke-U9YrEkEu$PeSBh(H%LHD~j>E%y7JC8v^vsp$bhc9e_dR z!ro_>`(VWod|hs!g5JIn9kSX5H|dO}Vq*6bb8_{ndEBQXKml zA42I$(FlWewmgW*=mftxZc$p*jKQz131#K=!*Iq1W0Q)ST+uV9m0xU=>UA2XB=ulW-Td)=J z{0-;e^2`)j`MduXMBG@j-oI+1L1t#(z1^iOj-}}>oTg})1`nyj4hwD10X%PaT$T#V z1FbsM62MX382nLi^x(O`J2qbLYOqDhWdxmA$Mqi(tipFhlnzKY@n9Oa1Tl z1Mq3u!Lxc7n!!HiQ%=2+gRn1PKK7xlDq8r+mWOK_aUIGIGQaj~aqRobMvc5#HWm@I z62g-6Y-L=^q>ynr6Yp@TQ5bTqq_W+m4}9mzl}Qn;1~*zvD;A85&;t4h`leQ5f5Y>d zI4+#i#XPc^2fGt9VDRB37v?v-pY>OQ0AhDzgWT)$ZP3t(apu|n5!jpdF%>LxWzu-u%j*c)ftg?An**(BcR}G#D?FMsRAMF)gwNX*mL=#CD;{HRP*E~BV zx12J{fP}f<(!0+sh{!eFHANaXVVbe4!v{<|fo9u5tM>CF(6DQu{QB5ekibq4g?kuL zm>gEdcAeO-aSrZpsUI^d>{DqFyX z9+9fA1Mt1&fdwV373PoA@iTM{zy!>M$fwD5Tdk+*U|Do?7CLw%#`GMz>4){s5km z_TIslNZ6eCfwa4~g|G`$Hbt(~<W1~X5ViN9K_^WLt!{Zeu_ zaemH^h9p22=>TFr#$#c zI}DObkT#85+ds0?sYeHg0rKl}tqV6T`l;G!o}Z3*zsiQJ|M)L8%RWgE#I%%`8 zFc%I#?#Ol-OtJXPvBlvYcsu*I2C(ae+1eZxlp_PM$4UIglVT~9=dNM7W;k&^i##9f z^Le0@mQ4sV^L@R_6H0@bh|J}WqcT`k%Sy~6WfP8AM6@qTyyif5T=j48Z0TG#2z@&7~N=<#$M8DaJ$QNf!nNuuP=BE zfn8HyUq$z6R=3X$96wfY!3?+m-PdF?*RZT=st;%iM%-m_7kD+7t;*-z$g83B}@ju;Qt z^z3Yd-DfVWT-Dy=NXjw{yRO=o#y<$R9gf~zGBp5&dVMDM7PkPc7_-bzBYRM1m170s zdBpkp17v`J~E+ zfWt4O@bH@s5MykUs*uwQs5{dgEiUq)lPQcpKI|l}7du2IPo%S@N!j^tAfhViZAA_V z32@#we5K7H3)*}eJm_KcD5{zU@nXE5Zyn!)R@KfxCX1bDq~}Z9$Ry%*@L@7} zmUs+?f2;^%UY@s-SijI9J-u69vvSkd>Ru%PO@2R2%|DvHBfba7X?^<8ahMJ_(_+{84hUe;Rm}hko9lCWi4wGQH4aZ2(Cb zA9{Xw_CrD5gZZDf48wxGZ!)RgGoo~pQOvJ>iTxVqP^~NO8h>C&XO;lsD{x4J<~$QJ zb6t@4q}8vrG86`RwfT?Wcdzy0bnztkn(%nat*;SAmK#i|_Rk?k`OV8+3B>t1Jb#Yc z=j_X&xGnsG$gehETcr*Lg!`sY#oZ?YSb|ebeU$e!sDCK8%>1?-+zL;v(Wwptqns?e z_s@x<{`;%lFa03)YrHN1m&Zi-_?+W?0pxkb1vj1?CPYF%&~EoCReWNh(e3Tg%WW6Q5rt6Bd7D6g{Vra(bsIkHV zxvQgV^J|aaT9cG7rXc;PTl+UlJO>VT(X=bC>)?!S;?FT&0W{gm@6 z=?$ia9#eqt-1B?4-jOf{Kwp)R*8sj()jbuWtA`ldo%n+&WT!v**_R0tY}FHY4T)#yt)SlUv3tfbI?X@^bY*cKSP|CVOtjj_v?^b&_?STJr;3Od3?o15W6aUim%my z4HI%N{TjnE3BNIF?O;`Ffyw>1pK{YxL3;Vaz9n87=+uazeCBQ9c|3U@J**_1@ti$3 zR$#Z4iC3Ek8>pN`&f9N70tIhOy)3JTVZ|}GVvC2tE3*#=mQ4G>Je>&ro!kCMa4E?o zFd>F;+{QV$zcsr5mZeqd1Ui?M?v>Awu=a-uhm%EQ5FbFVaAV8b`00ywiE#7)^u~`T zdZMEsywxJ&TZ$1H!Dc+%?MYl`hu`Ol>n-&P40*kW3puF2QM^i#7qjYO-ByBzkt2R73^6w5K?K!ZiDoQ-oQ=8)F~ z?B1WS*y6to4(+Lwnv^6VOHrS5v)nkaX#1?)&!3b+(aI}Q6U+TzbiUWU!FL2)hpitC zlXsv;l4>_@I=-9GKKT8oc>jrP+neioiXD3@&vl1IjvvwZl9hNly8}`^sNKnZc^ZDE zov#be`v549`M=S&%!My{^Fn5`sL|!q0=gdA#PI;H3&8!LA~4|v%l=llZOpCBd5(mf zEU3|6^bo*$zRg;?c|3v#!;g5#K=Ku>s%9 zYxB310?mVWz4#DMt8o^pr>%fiHEkN@9RxcRH5Ll82f*A=AH~J_6@+i$@Xjrh#Bmgt z2k)mHJ4=|A>4$-cetg1e8wv3`pE9euK^1F#pJ&9hm$w@syv*goX~o5Dui9{N+JT-(nZ;j z{*xleC7z}vj}v1sIds!&U+*3$*(mqxNMbc~$u0>!6N8{uXDW{`vJmf6;Pn)^`h?Tj zUf%Fy$GkVb-nLJUAA|2_Qd|Ya5RDw2xX??3uqdvO>skB&*nDTHe8#o~oc55XI^x2Q z-neyuTj~<=_(RTlci-vA<`3*xk%6ItFFQZ>wSq&}Qd$hzaN?$NoZ&E}_`0$A#co$% zXwo}Xzc2`TD<^0+?B_@8#?!wD$NYOvxMaYQg+*?J?&I9Xr%Bw{3x4B`c1IMkC_3)m z$j(ln#`pMJ@T;|b`0jn}Lu+woQ{L92QIi^|8&Z~9YjPi3jh2!$(bSWad1-L9`k94QU^|W-KYc~cW=^8JTHUtOqky_R3hHz zBd^d9tcY_5$|8)9Nga``BhIKKZhY-u=|6mwdd7@>=W%gpvH!{Khk<4kkJRG z5Aw#U{T_rB#+0(BKXd>&xu~AOr+VlvDcT~B1>$uH`F+UdzvwgGwvjNo$Cjz(q+zf@ z?5lc!JUddlebg+pco2SW=8s$S9R^2xqg7kQQ((anGe>G8eYE<1Z1=T6V!g@Z4!z#~ zp!fEf*(iiDQhFszO4A^8XC2uUS-B9T=H!pW{X?LaGQx29*Z|laA{jba+z7h2hY!s~ zDx!~uZn8E++Y-hDoP+Bvd&(<>rNbbVA~BM9QmTOcaFg?1CQiEWU8ec6OW*2gD`CbQfBn{hMr%ysi?Jyf>OJV!xTt;{8h@RG9knxN-sxHOaLpEj=n;{Pz1dq+Rjd~4MB9U zy2x&N2o!wZz+?8{Gm;TLJhi3#4558+4sIWdmsWWZZ`YoS^QJ`|W=?@XjNUlriW-7V zv-AD2l6Dy!L1#C5m$ed4%S4#qnjziJ90 z^@1xwf%mDA1LyT~-!Q5o24zL1X$eEXozBVaYqhuoWFweJwQ5FQg+iI?>4*HiRY*wJEk1F z4)u>hgO&_Ow%xtZ==QY$J>P1$jnubIMr#u~%D+Qx%Jd|mJb0Z0uD6&6yB#vE__5jm z_i>+jHthTJO13y{aim|%h=vgkK$LU)g&Y2ZkSVnJQzvx`NIuBDaxZ!ZDxPO|{`p-4 zLXIX5SZ~?Rj9yCI&5c~WTJiMMT3t=dNw_+KndJvQ?F^Z`MBs zV^jNKlfd!ECz3|MvGzwwlsRQ!O8MhGPU^iVMY#Ue@h!yj5dC#IaL%s!b1R1~b7K!2 ze^`8BE< z?m?(G12PWFsm|2ycO=AEArX_an}SPqyndHhXpt?K1kNv|kH9F~Sw%|mukhWpj}Lmc z6EMbv9M7iyh8(UNP88)K-cK|ldw|{Hhz8 zf|uSjvh0;ytKabJVD%{Z2$|&1xmqV4Km{tFry@Iv*Y)K2ZQJ|znA*h|kwnLa5(`>E z%rJMGJHyJ_b4ujvqZ1b5fqea)&3ba(aBe`~*ZSKyIBow))ii$_8c=CBDDO|4&%*Q0 zc)SkP<0`3(;>1K%;{#Xfc(LIu(SzQf)DR6N+Otb`)3CNd->cL9HP~L^k-7S|41|cp zR*y*Oq280+ z{1R`c`NXCl?$dhneS_r))Vi7e3XZFxX9Ad^@-p#wN}hM#6qJ91d42)Xm=*BQ*^#h8 zQ`UWp)FPPEcAIFfcV$rPjca2-Kn{O9?$r1DsIV3G7aK`gJ>Z^)+MtEVFmzHEYfI}H1g+7u-G;{tAzIiu)4o#* zWs7qcJ!eo&=+|br!q!{q;njuQ?C12^t)Ht}%FHfa3 z*8n#+I=r){ZUv4W-E~<7lITq2r4vP-|IESTwf&jG+-+<7I)*#Pn<|ZFfa)>lJC2MJ zh(wcYM={q3+;f>VKm1}3s0G;!uBz?ez26m)@-lW*GUW~PuAqPB;QPyl5?d9HdvYNv zll2!1d3lkJek$j~x-y7D#qVDS^@hPv;H*6Mdk~BdalF;njDU{EEG=_=+0pGjR$Wau z|2?OdZ9d{GqYy&>v!a^iE)6m#f95BRt~kd2EWwK|l1H~x`I;`u0E7l50IrexOndIJ|GlKfnw;SCRB9Cfm3vX>WO7ZO-ty4L{r z)!MYh){cYR`)-4!&8$7lFHrCL(e5;0nWbSEt7C}vGE?y$yqHQT4|yIvQCa$D$6CChIPqM@cvl9^00tsSIh?OLGBic7u^>_S$n>eZsG|aoiXJk7c@qPLHkC^SKK2 zD(QAXhWudv<{#3i+ZUe;L)X*(QExcs=D{=9qZH|os*+0U-dBQHC$j`^cz^^-#o-*b+zw`v;<2qNG1KBV&AON*Ve8M{kUD~~Z;_~4{e-35+2 zo#z#lt%bXaEo=ii$Drdw&c0PwA@qHQVQ5CpE<(NGbq=^+M|*x!*S$hQVmI%1m(=(H zC#8Ls3}0-<7H|J{+w*Z0az$7U>_{jCA!;vAD?IIk2g131Lc|!+4RU?b=hcYwjpQ6| z-}i0FJ2|mm5l5;hY56cGKDjd0t2~%@>HVXJuYCf(RaSTD#wQ?~?}(RxUMrw6nm!nz zB8J+eNDb?goCxJ1uRjR(s-LYAZ%bMT2fql z-j_!J4FiQ}J?x(G=AU#hK}S=|$44RYeJptW0q$=tTzO@OG6j*;N8wTBV=mA(I<-(Dt!=}buSvrmKYD3lY)dt3rH%gm#0G{aCvUhD1*B7qin z9q~`hBi=v2IrzNSSviM|y>V?nr1#4UqYooMz3rW978ezgTm8sB`}o>@+)K)j%oNL^ zy<&hs7|SpmJ#pOCQ2RG>y*6*wD~8w)a1I`KE>x@dD}}LRTVp%j4@mK2xn3*Z_FvwN z*xVm7czS*U{s@p#7nSdX1N~DKetMu0==xN=!|H4I?&4N|HTMipi1 z$Ni*hEL2fL-ZKV03~if)equ}BX-}FUQ;S#I z&#Mp`=GrA(s!&J0^9?TVCJ#zxw^SC|V(?yEvaoe18f4{kYy11s-37uKHhwCB!P|H7>-7(u_|cz{AXe09yjTjYfXI&70JRk?|{ zE}A^QeGI9(e&HAo(lt?toRQ^5xUOwf_PfB0{Z?8nv3xfU&agLqJ3!S13=91Q44-`j zNqJeZ(&~HAn(8~*$32NTtm_KH?UYM@<5HZCAmTt{-}D=0z_#_h|03~p6^7N%(4OCQogQ;Ooz4nf z1d)V>66}SdAi~h(tFUQt5`LWAs`!Sz9+YUDDm8mh3GC6~qx#*3D3<5WBS&dY*iXsx zOeR0i?_yZnNA#3$5jI?V{&CrlH&QoWiy%~Ll<(cXPXpIe4YodX8F2IC_ls-;DWLwe z@<#FN5~y_f@oP6ciR(6m)&;@)ky@CFI=u=%rbH!Z(&5aGMDopEaMa_(`onb3>a5-8 z%DI?zUFbYIULnqlF)A#sT_r z+1S(C6y4hVzU0+s;(bf<{M?LcZBqZ*a~#BJYa9*t^}rgTXY+%PRo3z*EniMQnSf!6 zQne40@%J9q_12`@kt`5~`5dX7xIRcz%HA&2aU-w|H=bL4gxF{W+@h*#o^8p=J|MqC4#}N#OFkIfU^TQAv3j%NVZx{l$n9^+vDn9gst54h~3*xwg=Vfrc z6*}c*&>9FK*_&HJfG0I#7@^+SX~c=?NuhT4j!yuOvW(z@2c3Y)sk}%vu@Lg9O}e(# ziJ{>;^(CVB6W3Rf=S#w~kDQm!9|ZLcL!Y&}NZ2`n6M^xg8<4X~IkKD;kKtDu($fmp z5#VP%&vq^+FDtgN^U;k%)q6XXruM#uaaWo@z7zh9$a?Mdv0P02*BnvLlX9Qz zXt9*!O9HlQb6W|7#9IQE}n%U{=glZ-=fnz>5HI=S|o=1{bbl6$QmMZ~5abk!3_ zPU$HbcRuKbY0q9tQdtf{g{An)%U!)-QQ=YZ%^kmx0@fmruLi_=!#TKLCuXI7{S-op z>`54{>4+D?q*e5(lqQ6b{l>|MD|tGB#*5*P&E385@|68U`-maXbLS-=rwl8)P;m4M zwGwgM!SgbB9KE6PQ7LHwllyz#CYgimFnIB5DCAa8TCGY^!ziQ&-3=z`Sbhvo%1{A`JM0gdw$PVxl+|p5fG{`T~3RB2rjVvMW>80 zj4{87yS@lg<~8ffe39`m^DL^e&56E6*X~xa&=MGsvEo3IlTWC^}7#QoGHa$v@Wh>;9HJ)YQM$kkbS!-vko_e zQoWA{^^<47)N1W4S35r1UDI0q+e{5$iG!2m^fj>J*7Agg|D!uw%H*{U&c@5uXyCj5 z>R`K_&_4-ca(v8rvVUo7**$CqGtV=7rItq-LmP)>lMv!KvD88vw9eWUB^KfMi z^?iZFA=d}@+kWP8?mD~^>txcJ=ygP{1|6RrJin!5GXoZJdhdiOY{paO^zEb{ zo&#FT?*UC4FQZ^Dg}~LuW1zuOGuKPH7`r)2`y8JbMVW8bGI<^TZKtL=P{*CNd-0nr zR>AUumz_T88=%KRcmA8LQ|Omyz0wlc3m6S8oa09xfdLP>SH_ndu@lw{y2bv~_r`z8 ztWQPTpZf2J`t5#?gPrce5a>I3#ODg(;|Z!5OXa>NctlsjBjSNUB=jggUfPJG=sL#^ zBX(C1qw~kYu_T*q1n)ngeT_?9huD3=A-@aSo%XL)g$%r3iGSvXxeB}=ayY%m z${bhemOM7ktwT1o;cfB*&q2ED>D|u>9)>?tY!lmKig|7_vo8OKy1sUjiKik|n&`${ z0;}JvDh=&e2kj0W>l@X43>0{?zW9lLa6-l9iqXcGfLk0L_&BKr+>vlfEatXhMKxIg z3OlL!cv=1D^~H{~#40APx8VJgp64rY@@n}se}_D5XU5quK7u;hjs@(eXs^k)KlY_Cv4a-g%uD;JaY%iE z#MynIplk)E2HO&BbN#oeLgl;7w_xB3hiPMM3%Tik*S_gv%V_O*ax&MHS=URsYGu)Q{ojHvk?4>AG1jL98h zUQHa)uL{>mo2^%Yy$fezlB*2yUl-LmUJR=SDz2B!|Gp{$sl)Tmi$xP?(v_ih=y&2j z0{*pT1&G>LvqJ_)Iyv3X{=~$sT1(ww{5{ma(^gUZgoPJ<;^fuG4ugu@>dMxdgpWML z2@i1*1AwKzs6Oe5t=1^>_4x30-k*ePU$gvZOCl4GWeUxw>feG;<@%2jo-sc2DC)uk zqAyaU^YB^YJvpdW$mM%w3XxoLaFNp$UF^YVx!z~m*LiX>4|?`k3E{XhKAn)hYq_5~ z9ubh?6!|?1zq`fgMa=nW)TX8E#_MlHlUf<}Z=4?>EBCG5=}Hb*0Y~5AzBjf1TK3&x z=S`&=dW#x-WcWtmX)zrSRo-Rl*rZSBC3H>3{#u}95v1mRZw7r}cQmh*)F7Ip^#O&j z3AoJT^oB@x>iUi3$;o)crfNRp<3vBZG2`CaIuXEAg_|e+`_SU3Wzh!(67-Cl?PZqR z4!T++b@^7$z>|HnCNoOmb?{DWdBd?6)ktfE_FG!tPpx!?u4@*v8TP(I+jyOjP)`($Tdlzb91$v~*v*6+K?$|KG t_t-4h%sH1YCi)s)7~2>!gpwyL;x?Eq$2L`5bEa3uQl2{@UjWE>{s(s&;Aj8< literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat new file mode 100644 index 00000000000..c49703f831a --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d05/inputs_true.dat @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d05/results_true.dat b/tests/regression_tests/surface_source_write/case-d05/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d05/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d05/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d05/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..24b04a862e42c6038d6b0e65184b43715c8145d3 GIT binary patch literal 33344 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�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 + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d06/results_true.dat b/tests/regression_tests/surface_source_write/case-d06/results_true.dat new file mode 100644 index 00000000000..7fb415cb159 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d06/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.263843E-01 3.193920E-02 diff --git a/tests/regression_tests/surface_source_write/case-d06/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d06/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..3eb95bef96a07b77a50c9ef9df25e0a849075c08 GIT binary patch literal 29496 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat new file mode 100644 index 00000000000..e2897125b40 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d07/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 101 102 103 104 105 106 + 300 + 7 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d07/results_true.dat b/tests/regression_tests/surface_source_write/case-d07/results_true.dat new file mode 100644 index 00000000000..63ea1a64d41 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d07/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.756086E-01 4.639209E-02 diff --git a/tests/regression_tests/surface_source_write/case-d07/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d07/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..0332f2dcfb100123fe3a4ee3a47f690be2af1bab GIT binary patch literal 33344 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat b/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat new file mode 100644 index 00000000000..185b8629b90 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d08/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -4 -4 -20 4 4 20 + + + true + + + + 101 102 103 104 105 106 + 300 + 8 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-d08/results_true.dat b/tests/regression_tests/surface_source_write/case-d08/results_true.dat new file mode 100644 index 00000000000..63ea1a64d41 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-d08/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +9.756086E-01 4.639209E-02 diff --git a/tests/regression_tests/surface_source_write/case-d08/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-d08/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..2525592045ca7912138b0559d72d9d4eaeda6118 GIT binary patch literal 33344 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat b/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat new file mode 100644 index 00000000000..ac3c03e37e0 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-e01/inputs_true.dat @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 2 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-e01/results_true.dat b/tests/regression_tests/surface_source_write/case-e01/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-e01/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-e01/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..3047519e8c52b140a4756266192c9d81c7d204fb GIT binary patch literal 33344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-e02/results_true.dat b/tests/regression_tests/surface_source_write/case-e02/results_true.dat new file mode 100644 index 00000000000..8979eb55477 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-e02/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +5.169123E-02 9.144814E-03 diff --git a/tests/regression_tests/surface_source_write/case-e02/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-e02/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..80615285332359f6d38365cce0cffbbfba1ba116 GIT binary patch literal 33344 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat b/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat new file mode 100644 index 00000000000..23bbf0455eb --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-e03/inputs_true.dat @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 5 + 1 + + + -2.0 -2.0 -2.0 2.0 2.0 2.0 + + + true + + + + 4 5 6 7 8 9 + 300 + 3 + + 1 + + diff --git a/tests/regression_tests/surface_source_write/case-e03/results_true.dat b/tests/regression_tests/surface_source_write/case-e03/results_true.dat new file mode 100644 index 00000000000..d793a7e4216 --- /dev/null +++ b/tests/regression_tests/surface_source_write/case-e03/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +4.752377E-02 6.773395E-03 diff --git a/tests/regression_tests/surface_source_write/case-e03/surface_source_true.h5 b/tests/regression_tests/surface_source_write/case-e03/surface_source_true.h5 new file mode 100644 index 0000000000000000000000000000000000000000..becc9254a3f1c326d78c58a23e0be68087b3b5d2 GIT binary patch literal 33344 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