Implement filter for cosine of angle of surface crossing (#2768)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Zoe Prieto 2024-10-04 02:07:03 -03:00 committed by GitHub
parent 1a520c9f4d
commit c285a2c4ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 254 additions and 6 deletions

View file

@ -416,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

View file

@ -133,6 +133,7 @@ Constructing Tallies
openmc.EnergyFilter
openmc.EnergyoutFilter
openmc.MuFilter
openmc.MuSurfaceFilter
openmc.PolarFilter
openmc.AzimuthalFilter
openmc.DistribcellFilter

View file

@ -36,6 +36,7 @@ enum class FilterType {
MESHBORN,
MESH_SURFACE,
MU,
MUSURFACE,
PARTICLE,
POLAR,
SPHERICAL_HARMONICS,

View file

@ -40,7 +40,7 @@ public:
void set_bins(gsl::span<double> bins);
private:
protected:
//----------------------------------------------------------------------------
// Data members

View file

@ -0,0 +1,34 @@
#ifndef OPENMC_TALLIES_FILTER_MU_SURFACE_H
#define OPENMC_TALLIES_FILTER_MU_SURFACE_H
#include <gsl/gsl-lite.hpp>
#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

View file

@ -22,7 +22,7 @@ from ._xml import get_text
_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'
@ -765,7 +765,7 @@ class ParticleFilter(Filter):
@Filter.bins.setter
def bins(self, bins):
cv.check_type('bins', bins, Sequence, str)
bins = np.atleast_1d(bins)
bins = np.atleast_1d(bins)
for edge in bins:
cv.check_value('filter bin', edge, _PARTICLES)
self._bins = bins
@ -1850,6 +1850,44 @@ class MuFilter(RealFilter):
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.

View file

@ -21,9 +21,9 @@ __all__ = [
'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 @@ _FILTER_TYPE_MAP = {
'meshborn': MeshBornFilter,
'meshsurface': MeshSurfaceFilter,
'mu': MuFilter,
'musurface': MuSurfaceFilter,
'particle': ParticleFilter,
'polar': PolarFilter,
'sphericalharmonics': SphericalHarmonicsFilter,

View file

@ -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<MeshSurfaceFilter>(id);
} else if (type == "mu") {
return Filter::create<MuFilter>(id);
} else if (type == "musurface") {
return Filter::create<MuSurfaceFilter>(id);
} else if (type == "particle") {
return Filter::create<ParticleFilter>(id);
} else if (type == "polar") {

View file

@ -0,0 +1,36 @@
#include "openmc/tallies/filter_musurface.h"
#include <cmath> // 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

View file

@ -0,0 +1,37 @@
<?xml version='1.0' encoding='utf-8'?>
<model>
<materials>
<material depletable="true" id="1">
<density units="g/cm3" value="10.0"/>
<nuclide ao="1.0" name="U235"/>
</material>
<material id="2">
<density units="g/cm3" value="1.0"/>
<nuclide ao="1.0" name="Zr90"/>
</material>
</materials>
<geometry>
<cell id="1" material="1" region="-1" universe="1"/>
<cell id="2" material="2" region="1 -2" universe="1"/>
<surface coeffs="0.0 0.0 1.0" id="1" type="z-cylinder"/>
<surface boundary="vacuum" coeffs="0.0 0.0 3.0" id="2" type="z-cylinder"/>
</geometry>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>1000</particles>
<batches>5</batches>
<inactive>0</inactive>
</settings>
<tallies>
<filter id="1" type="surface">
<bins>1</bins>
</filter>
<filter id="2" type="musurface">
<bins>-1.0 -0.5 0.0 0.5 1.0</bins>
</filter>
<tally id="1">
<filters>1 2</filters>
<scores>current</scores>
</tally>
</tallies>
</model>

View file

@ -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

View file

@ -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()

View file

@ -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