diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index fdacfa765b3..25c7208daa7 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -497,6 +497,7 @@ class ParticleData : public GeometryState { CacheDataMG mg_xs_cache_; ParticleType type_ {ParticleType::neutron}; + ParticleType type_last_ {ParticleType::neutron}; double E_; double E_last_; @@ -594,6 +595,8 @@ class ParticleData : public GeometryState { // Particle type (n, p, e, gamma, etc) ParticleType& type() { return type_; } const ParticleType& type() const { return type_; } + ParticleType& type_last() { return type_last_; } + const ParticleType& type_last() const { return type_last_; } // Current particle energy, energy before collision, // and corresponding multigroup group indices. Energy diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index 65098597a5d..6fd9d3bf8d5 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -39,6 +39,7 @@ enum class FilterType { MUSURFACE, PARENT_NUCLIDE, PARTICLE, + PARTICLE_OUT, POLAR, SPHERICAL_HARMONICS, SPATIAL_LEGENDRE, diff --git a/include/openmc/tallies/filter_particle.h b/include/openmc/tallies/filter_particle.h index 863a6d282fe..fff955ee5f2 100644 --- a/include/openmc/tallies/filter_particle.h +++ b/include/openmc/tallies/filter_particle.h @@ -48,5 +48,45 @@ class ParticleFilter : public Filter { vector particles_; }; +//============================================================================== +//! Bins by type of outgoing particle (e.g. neutron, photon). +//============================================================================== + +class ParticleOutFilter : public Filter { +public: + //---------------------------------------------------------------------------- + // Constructors, destructors + + ~ParticleOutFilter() = default; + + //---------------------------------------------------------------------------- + // Methods + + std::string type_str() const override { return "particleout"; } + FilterType type() const override { return FilterType::PARTICLE_OUT; } + + void from_xml(pugi::xml_node node) override; + + void get_all_bins(const Particle& p, TallyEstimator estimator, + FilterMatch& match) const override; + + void to_statepoint(hid_t filter_group) const override; + + std::string text_label(int bin) const override; + + //---------------------------------------------------------------------------- + // Accessors + + const vector& particles() const { return particles_; } + + void set_particles(span particles); + +private: + //---------------------------------------------------------------------------- + // Data members + + vector particles_; +}; + } // namespace openmc #endif // OPENMC_TALLIES_FILTER_PARTICLE_H diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 374daff92a0..ceaeb2a3ec4 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -207,6 +207,7 @@ extern std::unordered_map tally_map; extern vector> tallies; extern vector active_tallies; extern vector active_analog_tallies; +extern vector active_particleout_analog_tallies; extern vector active_tracklength_tallies; extern vector active_timed_tracklength_tallies; extern vector active_collision_tallies; diff --git a/include/openmc/tallies/tally_scoring.h b/include/openmc/tallies/tally_scoring.h index c3ab779e6a1..2efbfd94c42 100644 --- a/include/openmc/tallies/tally_scoring.h +++ b/include/openmc/tallies/tally_scoring.h @@ -72,14 +72,16 @@ void score_collision_tally(Particle& p); //! Analog tallies are triggered at every collision, not every event. // //! \param p The particle being tracked -void score_analog_tally_ce(Particle& p); +//! \param tallies A vector of the indices of the tallies to score to +void score_analog_tally_ce(Particle& p, const vector& tallies); //! Score tallies based on a simple count of events (for multigroup). // //! Analog tallies are triggered at every collision, not every event. // //! \param p The particle being tracked -void score_analog_tally_mg(Particle& p); +//! \param tallies A vector of the indices of the tallies to score to +void score_analog_tally_mg(Particle& p, const vector& tallies); //! Score tallies using a tracklength estimate of the flux. // diff --git a/openmc/filter.py b/openmc/filter.py index e7c54f2f0ee..27ee235907b 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -24,9 +24,9 @@ 'universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', 'energyout', 'mu', 'musurface', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom', 'materialfrom', 'legendre', 'spatiallegendre', - 'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'cellinstance', - 'collision', 'time', 'parentnuclide', 'weight', 'meshborn', 'meshsurface', - 'meshmaterial', + 'sphericalharmonics', 'zernike', 'zernikeradial', 'particle', 'particleout', + 'cellinstance', 'collision', 'time', 'parentnuclide', 'weight', 'meshborn', + 'meshsurface', 'meshmaterial', ) _CURRENT_NAMES = ( @@ -785,6 +785,29 @@ def from_xml_element(cls, elem, **kwargs): filter_id = int(get_text(elem, "id")) bins = get_elem_list(elem, "bins", str) or [] return cls(bins, filter_id=filter_id) + + +class ParticleoutFilter(ParticleFilter): + """Bins tally events based on the outgoing particle type. + + Parameters + ---------- + bins : str, or sequence of str + The particles to tally represented as strings ('neutron', 'photon', + 'electron', 'positron'). + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + bins : sequence of str + The particles to tally + id : int + Unique identifier for the filter + num_bins : Integral + The number of filter bins + + """ class ParentNuclideFilter(ParticleFilter): diff --git a/openmc/lib/filter.py b/openmc/lib/filter.py index 5cf496107b3..10c171e7b6d 100644 --- a/openmc/lib/filter.py +++ b/openmc/lib/filter.py @@ -22,9 +22,9 @@ 'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'LegendreFilter', 'MaterialFilter', 'MaterialFromFilter', 'MeshFilter', 'MeshBornFilter', 'MeshMaterialFilter', 'MeshSurfaceFilter', 'MuFilter', 'MuSurfaceFilter', - 'ParentNuclideFilter', 'ParticleFilter', 'PolarFilter', 'SphericalHarmonicsFilter', - 'SpatialLegendreFilter', 'SurfaceFilter', 'TimeFilter', 'UniverseFilter', - 'WeightFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' + 'ParentNuclideFilter', 'ParticleFilter', 'ParticleoutFilter', 'PolarFilter', + 'SphericalHarmonicsFilter', 'SpatialLegendreFilter', 'SurfaceFilter', 'TimeFilter', + 'UniverseFilter', 'WeightFilter', 'ZernikeFilter', 'ZernikeRadialFilter', 'filters' ] # Tally functions @@ -604,6 +604,10 @@ def bins(self): return [ParticleType(i) for i in particle_i] +class ParticleoutFilter(ParticleFilter): + filter_type = 'particleout' + + class PolarFilter(Filter): filter_type = 'polar' diff --git a/src/particle.cpp b/src/particle.cpp index b9f9c8f86b6..5536feca46a 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -89,6 +89,27 @@ bool Particle::create_secondary( bank.E = settings::run_CE ? E : g(); bank.time = time(); bank_second_E() += bank.E; + + // Score tallies affected by secondary particles + if (!model::active_particleout_analog_tallies.empty()) { + // Create secondary particle for tallying purposes only + Particle tmp; + tmp.from_source(&bank); + tmp.u_last() = this->u(); + tmp.r_last() = this->r(); + tmp.E_last() = this->E(); + tmp.type_last() = this->type(); + + // Load geometry info + if (!exhaustive_find_cell(tmp)) + fatal_error("Cannot find temporary particle in model."); + + if (settings::run_CE) { + score_analog_tally_ce(tmp, model::active_particleout_analog_tallies); + } else { + score_analog_tally_mg(tmp, model::active_particleout_analog_tallies); + } + } return true; } @@ -128,6 +149,7 @@ void Particle::from_source(const SourceSite* src) // Copy attributes from source bank site type() = src->particle; + type_last() = src->particle; wgt() = src->wgt; wgt_last() = src->wgt; r() = src->r; @@ -165,6 +187,7 @@ void Particle::event_calculate_xs() // Store pre-collision particle properties wgt_last() = wgt(); E_last() = E(); + type_last() = type(); u_last() = u(); r_last() = r(); time_last() = time(); @@ -364,9 +387,9 @@ void Particle::event_collide() score_collision_tally(*this); if (!model::active_analog_tallies.empty()) { if (settings::run_CE) { - score_analog_tally_ce(*this); + score_analog_tally_ce(*this, model::active_analog_tallies); } else { - score_analog_tally_mg(*this); + score_analog_tally_mg(*this, model::active_analog_tallies); } } diff --git a/src/particle_restart.cpp b/src/particle_restart.cpp index 6b7778211af..ea4070b951c 100644 --- a/src/particle_restart.cpp +++ b/src/particle_restart.cpp @@ -64,6 +64,7 @@ void read_particle_restart(Particle& p, RunMode& previous_run_mode) p.r_last() = p.r(); p.u_last() = p.u(); p.E_last() = p.E(); + p.type_last() = p.type(); p.g_last() = p.g(); p.time_last() = p.time(); diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 79817981db0..af23718b24b 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -146,6 +146,8 @@ Filter* Filter::create(const std::string& type, int32_t id) return Filter::create(id); } else if (type == "particle") { return Filter::create(id); + } else if (type == "particleout") { + return Filter::create(id); } else if (type == "polar") { return Filter::create(id); } else if (type == "surface") { diff --git a/src/tallies/filter_particle.cpp b/src/tallies/filter_particle.cpp index eef1d1e63c5..80295cded8c 100644 --- a/src/tallies/filter_particle.cpp +++ b/src/tallies/filter_particle.cpp @@ -6,6 +6,10 @@ namespace openmc { +//============================================================================== +// ParticleFilter implementation +//============================================================================== + void ParticleFilter::from_xml(pugi::xml_node node) { auto particles = get_node_array(node, "bins"); @@ -34,8 +38,11 @@ void ParticleFilter::set_particles(span particles) void ParticleFilter::get_all_bins( const Particle& p, TallyEstimator estimator, FilterMatch& match) const { + // Get the pre-collision type of the particle. + auto type = p.type_last(); + for (auto i = 0; i < particles_.size(); i++) { - if (particles_[i] == p.type()) { + if (particles_[i] == type) { match.bins_.push_back(i); match.weights_.push_back(1.0); } @@ -58,6 +65,66 @@ std::string ParticleFilter::text_label(int bin) const return fmt::format("Particle: {}", particle_type_to_str(p)); } +//============================================================================== +// ParticleOutFilter implementation +//============================================================================== + +void ParticleOutFilter::from_xml(pugi::xml_node node) +{ + auto particles = get_node_array(node, "bins"); + + // Convert to vector of ParticleType + vector types; + for (auto& p : particles) { + types.push_back(str_to_particle_type(p)); + } + this->set_particles(types); +} + +void ParticleOutFilter::set_particles(span particles) +{ + // Clear existing particles + particles_.clear(); + particles_.reserve(particles.size()); + + // Set particles and number of bins + for (auto p : particles) { + particles_.push_back(p); + } + n_bins_ = particles_.size(); +} + +void ParticleOutFilter::get_all_bins( + const Particle& p, TallyEstimator estimator, FilterMatch& match) const +{ + for (auto i = 0; i < particles_.size(); i++) { + if (particles_[i] == p.type()) { + match.bins_.push_back(i); + match.weights_.push_back(1.0); + } + } +} + +void ParticleOutFilter::to_statepoint(hid_t filter_group) const +{ + Filter::to_statepoint(filter_group); + vector particles; + for (auto p : particles_) { + particles.push_back(particle_type_to_str(p)); + } + write_dataset(filter_group, "bins", particles); +} + +std::string ParticleOutFilter::text_label(int bin) const +{ + const auto& p = particles_.at(bin); + return fmt::format("ParticleOut: {}", particle_type_to_str(p)); +} + +//============================================================================== +// C-API functions +//============================================================================== + extern "C" int openmc_particle_filter_get_bins(int32_t idx, int bins[]) { if (int err = verify_filter(idx)) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 6eef1da9cfd..19d9f61a422 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -58,6 +58,7 @@ std::unordered_map tally_map; vector> tallies; vector active_tallies; vector active_analog_tallies; +vector active_particleout_analog_tallies; vector active_tracklength_tallies; vector active_timed_tracklength_tallies; vector active_collision_tallies; @@ -160,7 +161,8 @@ Tally::Tally(pugi::xml_node node) // Change the tally estimator if a filter demands it FilterType filt_type = f->type(); if (filt_type == FilterType::ENERGY_OUT || - filt_type == FilterType::LEGENDRE) { + filt_type == FilterType::LEGENDRE || + filt_type == FilterType::PARTICLE_OUT) { estimator_ = TallyEstimator::ANALOG; } else if (filt_type == FilterType::SPHERICAL_HARMONICS) { auto sf = dynamic_cast(f); @@ -326,8 +328,7 @@ Tally::Tally(pugi::xml_node node) if (has_energyout && i_nuc == -1) { fatal_error(fmt::format( "Error on tally {}: Cannot use a " - "'nuclide_density' or 'temperature' derivative on a tally with " - "an " + "'nuclide_density' or 'temperature' derivative on a tally with an " "outgoing energy filter and 'total' nuclide rate. Instead, tally " "each nuclide in the material individually.", id_)); @@ -580,7 +581,6 @@ void Tally::set_scores(const vector& scores) fatal_error("Cannot tally flux with an outgoing energy filter."); break; - case SCORE_TOTAL: case SCORE_ABSORPTION: case SCORE_FISSION: if (energyout_present) @@ -589,6 +589,7 @@ void Tally::set_scores(const vector& scores) "outgoing energy filter"); break; + case SCORE_TOTAL: case SCORE_SCATTER: if (legendre_present) estimator_ = TallyEstimator::ANALOG; @@ -1140,6 +1141,7 @@ void setup_active_tallies() { model::active_tallies.clear(); model::active_analog_tallies.clear(); + model::active_particleout_analog_tallies.clear(); model::active_tracklength_tallies.clear(); model::active_timed_tracklength_tallies.clear(); model::active_collision_tallies.clear(); @@ -1156,12 +1158,15 @@ void setup_active_tallies() bool mesh_present = (tally.get_filter() || tally.get_filter()); auto time_filter = tally.get_filter(); + bool particleout_present = tally.has_filter(FilterType::PARTICLE_OUT); switch (tally.type_) { case TallyType::VOLUME: switch (tally.estimator_) { case TallyEstimator::ANALOG: model::active_analog_tallies.push_back(i); + if (particleout_present) + model::active_particleout_analog_tallies.push_back(i); break; case TallyEstimator::TRACKLENGTH: if (time_filter && mesh_present) { @@ -1204,6 +1209,7 @@ void free_memory_tally() model::active_tallies.clear(); model::active_analog_tallies.clear(); + model::active_particleout_analog_tallies.clear(); model::active_tracklength_tallies.clear(); model::active_timed_tracklength_tallies.clear(); model::active_collision_tallies.clear(); diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 67e851644a9..1a02760dc1c 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -2311,7 +2311,7 @@ void score_general_mg(Particle& p, int i_tally, int start_index, } } -void score_analog_tally_ce(Particle& p) +void score_analog_tally_ce(Particle& p, const vector& tallies) { // Since electrons/positrons are not transported, we assign a flux of zero. // Note that the heating score does NOT use the flux and will be non-zero for @@ -2321,7 +2321,7 @@ void score_analog_tally_ce(Particle& p) ? 1.0 : 0.0; - for (auto i_tally : model::active_analog_tallies) { + for (auto i_tally : tallies) { const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are @@ -2363,9 +2363,9 @@ void score_analog_tally_ce(Particle& p) match.bins_present_ = false; } -void score_analog_tally_mg(Particle& p) +void score_analog_tally_mg(Particle& p, const vector& tallies) { - for (auto i_tally : model::active_analog_tallies) { + for (auto i_tally : tallies) { const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are diff --git a/tests/regression_tests/filter_particleout/__init__.py b/tests/regression_tests/filter_particleout/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/filter_particleout/inputs_true.dat b/tests/regression_tests/filter_particleout/inputs_true.dat new file mode 100644 index 00000000000..42619630960 --- /dev/null +++ b/tests/regression_tests/filter_particleout/inputs_true.dat @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + fixed source + 10000 + 10 + + + 0.0 0.0 0.0 + + + 2000000.0 1.0 + + + true + + + + neutron + + + photon + + + 10000.0 10974.9877 12045.0354 13219.4115 14508.2878 15922.8279 17475.284 19179.1026 21049.0414 23101.297 25353.6449 27825.594 30538.5551 33516.0265 36783.7977 40370.1726 44306.2146 48626.0158 53366.9923 58570.2082 64280.7312 70548.0231 77426.3683 84975.3436 93260.3347 102353.102 112332.403 123284.674 135304.777 148496.826 162975.083 178864.953 196304.065 215443.469 236448.941 259502.421 284803.587 312571.585 343046.929 376493.581 413201.24 453487.851 497702.356 546227.722 599484.25 657933.225 722080.902 792482.898 869749.003 954548.457 1047615.75 1149757.0 1261856.88 1384886.37 1519911.08 1668100.54 1830738.28 2009233.0 2205130.74 2420128.26 2656087.78 2915053.06 3199267.14 3511191.73 3853528.59 4229242.87 4641588.83 5094138.01 5590810.18 6135907.27 6734150.66 7390722.03 8111308.31 8902150.85 9770099.57 10722672.2 11768119.5 12915496.7 14174741.6 15556761.4 17073526.5 18738174.2 20565123.1 22570197.2 24770763.6 27185882.4 29836472.4 32745491.6 35938136.6 39442060.6 43287612.8 47508101.6 52140082.9 57223676.6 62802914.4 68926121.0 75646332.8 83021756.8 91116275.6 100000000.0 + + + 1 2 3 + total + analog + + + diff --git a/tests/regression_tests/filter_particleout/results_true.dat b/tests/regression_tests/filter_particleout/results_true.dat new file mode 100644 index 00000000000..27610573cd3 --- /dev/null +++ b/tests/regression_tests/filter_particleout/results_true.dat @@ -0,0 +1,199 @@ +tally 1: +2.803000E-01 +7.899090E-03 +3.483000E-01 +1.214713E-02 +3.907000E-01 +1.532231E-02 +4.527000E-01 +2.054189E-02 +5.254000E-01 +2.765018E-02 +5.925000E-01 +3.513577E-02 +7.129000E-01 +5.086499E-02 +8.202000E-01 +6.733918E-02 +1.017600E+00 +1.036527E-01 +1.247900E+00 +1.559066E-01 +1.520700E+00 +2.313731E-01 +1.823900E+00 +3.328213E-01 +2.282400E+00 +5.211727E-01 +2.852200E+00 +8.138458E-01 +3.568000E+00 +1.273357E+00 +4.329900E+00 +1.875591E+00 +5.602350E+01 +3.138822E+02 +1.124200E+00 +1.264838E-01 +1.167100E+00 +1.364348E-01 +1.299500E+00 +1.690742E-01 +1.525800E+00 +2.330320E-01 +1.913600E+00 +3.663497E-01 +2.032500E+00 +4.133832E-01 +2.613100E+00 +6.830790E-01 +3.794300E+00 +1.439925E+00 +6.613200E+00 +4.374276E+00 +3.068100E+00 +9.415793E-01 +2.519200E+00 +6.348918E-01 +2.344800E+00 +5.499640E-01 +2.259700E+00 +5.106904E-01 +2.151800E+00 +4.632270E-01 +2.317600E+00 +5.374687E-01 +2.557800E+00 +6.544854E-01 +2.704600E+00 +7.319093E-01 +2.852900E+00 +8.141188E-01 +3.125500E+00 +9.772850E-01 +3.300000E+00 +1.089410E+00 +3.476100E+00 +1.208627E+00 +3.596300E+00 +1.293913E+00 +3.766100E+00 +1.418948E+00 +3.907500E+00 +1.527112E+00 +4.048700E+00 +1.639931E+00 +4.825700E+00 +2.329247E+00 +6.058500E+00 +3.671578E+00 +6.091100E+00 +3.711118E+00 +5.054100E+00 +2.554896E+00 +4.319200E+00 +1.865836E+00 +4.292900E+00 +1.843096E+00 +4.404600E+00 +1.940307E+00 +3.878800E+00 +1.504872E+00 +3.778500E+00 +1.428066E+00 +3.875500E+00 +1.502577E+00 +3.373500E+00 +1.138456E+00 +3.549000E+00 +1.259773E+00 +3.510600E+00 +1.232725E+00 +3.731000E+00 +1.392373E+00 +3.907600E+00 +1.527663E+00 +3.640600E+00 +1.325788E+00 +3.090100E+00 +9.549517E-01 +2.517400E+00 +6.341261E-01 +2.075400E+00 +4.308890E-01 +1.555100E+00 +2.420523E-01 +1.005800E+00 +1.012240E-01 +8.566000E-01 +7.343774E-02 +1.405700E+00 +1.978421E-01 +2.084000E-01 +4.349900E-03 +1.401000E-01 +1.984950E-03 +6.610000E-02 +4.420700E-04 +2.790000E-02 +8.125000E-05 +1.790000E-02 +3.465000E-05 +1.670000E-02 +3.023000E-05 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/filter_particleout/test.py b/tests/regression_tests/filter_particleout/test.py new file mode 100644 index 00000000000..45f4d43e262 --- /dev/null +++ b/tests/regression_tests/filter_particleout/test.py @@ -0,0 +1,92 @@ +import numpy as np +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + +@pytest.fixture +def model(): + openmc.reset_auto_ids() + model = openmc.Model() + + # ============================================================================= + # Materials + # ============================================================================= + + u238 = openmc.Material(name='U-238') + u238.add_nuclide('U238', 1.0) + u238.set_density('g/cm3', 19.1) + model.materials = openmc.Materials([u238]) + + # ============================================================================= + # Geometry + # ============================================================================= + + sphere = openmc.Sphere(r=1e90, boundary_type='vacuum') + cell = openmc.Cell(fill=u238, region=-sphere) + universe = openmc.Universe(cells=[cell]) + model.geometry = openmc.Geometry(universe) + + # ============================================================================= + # Settings + # ============================================================================= + + source = openmc.Source() + source.space = openmc.stats.Point() + source.energy = openmc.stats.Discrete([2.0e6], [1.0]) + model.settings = openmc.Settings() + model.settings.run_mode = 'fixed source' + model.settings.particles = 10_000 + model.settings.batches = 10 + model.settings.source = source + model.settings.photon_transport = True + + # ============================================================================= + # Tallies + # ============================================================================= + + energy_bins = np.array( + [1.00000000e+04, 1.09749877e+04, 1.20450354e+04, 1.32194115e+04, + 1.45082878e+04, 1.59228279e+04, 1.74752840e+04, 1.91791026e+04, + 2.10490414e+04, 2.31012970e+04, 2.53536449e+04, 2.78255940e+04, + 3.05385551e+04, 3.35160265e+04, 3.67837977e+04, 4.03701726e+04, + 4.43062146e+04, 4.86260158e+04, 5.33669923e+04, 5.85702082e+04, + 6.42807312e+04, 7.05480231e+04, 7.74263683e+04, 8.49753436e+04, + 9.32603347e+04, 1.02353102e+05, 1.12332403e+05, 1.23284674e+05, + 1.35304777e+05, 1.48496826e+05, 1.62975083e+05, 1.78864953e+05, + 1.96304065e+05, 2.15443469e+05, 2.36448941e+05, 2.59502421e+05, + 2.84803587e+05, 3.12571585e+05, 3.43046929e+05, 3.76493581e+05, + 4.13201240e+05, 4.53487851e+05, 4.97702356e+05, 5.46227722e+05, + 5.99484250e+05, 6.57933225e+05, 7.22080902e+05, 7.92482898e+05, + 8.69749003e+05, 9.54548457e+05, 1.04761575e+06, 1.14975700e+06, + 1.26185688e+06, 1.38488637e+06, 1.51991108e+06, 1.66810054e+06, + 1.83073828e+06, 2.00923300e+06, 2.20513074e+06, 2.42012826e+06, + 2.65608778e+06, 2.91505306e+06, 3.19926714e+06, 3.51119173e+06, + 3.85352859e+06, 4.22924287e+06, 4.64158883e+06, 5.09413801e+06, + 5.59081018e+06, 6.13590727e+06, 6.73415066e+06, 7.39072203e+06, + 8.11130831e+06, 8.90215085e+06, 9.77009957e+06, 1.07226722e+07, + 1.17681195e+07, 1.29154967e+07, 1.41747416e+07, 1.55567614e+07, + 1.70735265e+07, 1.87381742e+07, 2.05651231e+07, 2.25701972e+07, + 2.47707636e+07, 2.71858824e+07, 2.98364724e+07, 3.27454916e+07, + 3.59381366e+07, 3.94420606e+07, 4.32876128e+07, 4.75081016e+07, + 5.21400829e+07, 5.72236766e+07, 6.28029144e+07, 6.89261210e+07, + 7.56463328e+07, 8.30217568e+07, 9.11162756e+07, 1.00000000e+08]) + + particle_filter = openmc.ParticleFilter(['neutron']) + particleout_filter = openmc.ParticleoutFilter(['photon']) + energyout_filter = openmc.EnergyoutFilter(energy_bins) + cell_filter = openmc.CellFilter([cell]) + material_filter = openmc.MaterialFilter([u238]) + + # Define tally + tally = openmc.Tally(name='photon spectrum') + tally.filters = [particle_filter, particleout_filter, energyout_filter] + tally.scores = ['total'] + tally.estimator = 'analog' + model.tallies = openmc.Tallies([tally]) + return model + + +def test_filter_particleout(model): + harness = PyAPITestHarness("statepoint.10.h5", model=model) + harness.main()