From 9b7ab0bacb3e2781d64b12756c0e5d416fffdad5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 25 Jul 2019 15:49:14 -0500 Subject: [PATCH 1/3] Expose/improve EnergyFunctionFilter through C-API Add three functions that can be used to modify EnergyFunctionFilters through the C-API: - openmc_energyfunc_filter_set_data: set energy and y data - openmc_energyfunc_filter_get_energy: obtain energies used in interpolation - openmc_energyfunc_filter_get_y: obtain ordinate values These functions are modeled after openmc_energy_filter_[get|set]_bins. The set_data function relies upon the new EnergyFunctionFilter::set_data function, which is analogous to EnergyFilter::set_bins function. Checks are performed to make sure the energy and ordinate vectors are of equal size before resetting and populating energy and y private members. An EnergyFunctionFilter did exist in openmc.capi, and has now been flushed out to provide a better __init__ method, as well as properties for retrieving energies and ordinates for interpolation. --- include/openmc/capi.h | 4 + include/openmc/tallies/filter_energyfunc.h | 7 ++ openmc/capi/filter.py | 45 ++++++++++ src/tallies/filter_energyfunc.cpp | 97 +++++++++++++++++++++- tests/unit_tests/test_capi.py | 31 ++++++- 5 files changed, 180 insertions(+), 4 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index ded41f738..3d2e8f57b 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -21,6 +21,10 @@ extern "C" { int openmc_cell_set_temperature(int32_t index, double T, const int32_t* instance); int openmc_energy_filter_get_bins(int32_t index, const double** energies, size_t* n); int openmc_energy_filter_set_bins(int32_t index, size_t n, const double* energies); + int openmc_energyfunc_filter_get_energy(int32_t index, size_t* n, const double** energy); + int openmc_energyfunc_filter_get_y(int32_t index, size_t* n, const double** y); + int openmc_energyfunc_filter_set_data(int32_t index, size_t n, + const double* energies, const double* y); int openmc_extend_cells(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_filters(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end); diff --git a/include/openmc/tallies/filter_energyfunc.h b/include/openmc/tallies/filter_energyfunc.h index 9b8d839f8..df82e659e 100644 --- a/include/openmc/tallies/filter_energyfunc.h +++ b/include/openmc/tallies/filter_energyfunc.h @@ -40,6 +40,13 @@ public: std::string text_label(int bin) const override; + //---------------------------------------------------------------------------- + // Accessors + + const std::vector& energy() const { return energy_; } + const std::vector& y() const { return y_; } + void set_data(gsl::span energy, gsl::span y); + private: //---------------------------------------------------------------------------- // Data members diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index bda3c3178..f1f0cdc46 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -34,6 +34,18 @@ _dll.openmc_energy_filter_get_bins.errcheck = _error_handler _dll.openmc_energy_filter_set_bins.argtypes = [c_int32, c_size_t, POINTER(c_double)] _dll.openmc_energy_filter_set_bins.restype = c_int _dll.openmc_energy_filter_set_bins.errcheck = _error_handler +_dll.openmc_energyfunc_filter_set_data.restype = c_int +_dll.openmc_energyfunc_filter_set_data.errcheck = _error_handler +_dll.openmc_energyfunc_filter_set_data.argtypes = [ + c_int32, c_size_t, POINTER(c_double), POINTER(c_double)] +_dll.openmc_energyfunc_filter_get_energy.resttpe = c_int +_dll.openmc_energyfunc_filter_get_energy.errcheck = _error_handler +_dll.openmc_energyfunc_filter_get_energy.argtypes = [ + c_int32, POINTER(c_size_t), POINTER(POINTER(c_double))] +_dll.openmc_energyfunc_filter_get_y.resttpe = c_int +_dll.openmc_energyfunc_filter_get_y.errcheck = _error_handler +_dll.openmc_energyfunc_filter_get_y.argtypes = [ + c_int32, POINTER(c_size_t), POINTER(POINTER(c_double))] _dll.openmc_filter_get_id.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_filter_get_id.restype = c_int _dll.openmc_filter_get_id.errcheck = _error_handler @@ -201,6 +213,39 @@ class DistribcellFilter(Filter): class EnergyFunctionFilter(Filter): filter_type = 'energyfunction' + def __new__(cls, energy=None, y=None, uid=None, new=True, index=None): + return super().__new__(cls, uid=uid, new=new, index=index) + + def __init__(self, energy=None, y=None, uid=None, new=True, index=None): + if (energy is None) != (y is None): + raise AttributeError("Need both energy and y or neither") + super().__init__(uid, new, index) + if energy is not None: + self.set_interp_data(energy, y) + + def set_interp_data(self, energy, y): + energy_array = np.asarray(energy) + y_array = np.asarray(y) + energy_p = energy_array.ctypes.data_as(POINTER(c_double)) + y_p = y_array.ctypes.data_as(POINTER(c_double)) + + _dll.openmc_energyfunc_filter_set_data( + self._index, len(energy_array), energy_p, y_p) + + @property + def energy(self): + return self._get_attr(_dll.openmc_energyfunc_filter_get_energy) + + @property + def y(self): + return self._get_attr(_dll.openmc_energyfunc_filter_get_y) + + def _get_attr(self, cfunc): + array_p = POINTER(c_double)() + n = c_size_t() + cfunc(self._index, n, array_p) + return as_array(array_p, (n.value, )) + class LegendreFilter(Filter): filter_type = 'legendre' diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index 9b28e0f76..cf74d9836 100644 --- a/src/tallies/filter_energyfunc.cpp +++ b/src/tallies/filter_energyfunc.cpp @@ -21,12 +21,37 @@ EnergyFunctionFilter::from_xml(pugi::xml_node node) if (!check_for_node(node, "energy")) fatal_error("Energy grid not specified for EnergyFunction filter."); - energy_ = get_node_array(node, "energy"); + auto energy = get_node_array(node, "energy"); if (!check_for_node(node, "y")) fatal_error("y values not specified for EnergyFunction filter."); - y_ = get_node_array(node, "y"); + auto y = get_node_array(node, "y"); + + this->set_data(energy, y); +} + +void +EnergyFunctionFilter::set_data(gsl::span energy, + gsl::span y) +{ + // Check for consistent sizes with new data + if (energy.size() != y.size()) { + fatal_error("Energy grid and y values are not consistent"); + } + energy_.clear(); + energy_.reserve(energy.size()); + y_.clear(); + y_.reserve(y.size()); + + // Copy over energy values, ensuring they are valid + for (gsl::index i = 0; i < energy.size(); ++i) { + if (i > 0 && energy[i] <= energy[i - 1]) { + throw std::runtime_error{"Energy bins must be monotonically increasing."}; + } + energy_.push_back(energy[i]); + y_.push_back(y[i]); + } } void @@ -65,4 +90,72 @@ EnergyFunctionFilter::text_label(int bin) const return out.str(); } +//============================================================================== +// C-API functions +//============================================================================== + +extern"C" int +openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, + const double* y) +{ + // Ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // Get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // Downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // Check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + + filt->set_data({energy, n}, {y, n}); + return 0; +} + +extern"C" int +openmc_energyfunc_filter_get_energy(int32_t index, size_t *n, const double** energy) +{ + // ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + *energy = filt->energy().data(); + *n = filt->energy().size(); + return 0; +} + +extern"C" int +openmc_energyfunc_filter_get_y(int32_t index, size_t *n, const double** y) +{ + // ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + *y = filt->y().data(); + *n = filt->y().size(); + return 0; +} + } // namespace openmc diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 6953186eb..31db130db 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -35,6 +35,14 @@ def pincell_model(): zernike_tally.scores = ['fission'] pincell.tallies.append(zernike_tally) + # Add an energy function tally + energyfunc_tally = openmc.Tally() + energyfunc_filter = openmc.EnergyFunctionFilter( + [0.0, 20e6], [0.0, 20e6]) + energyfunc_tally.scores = ['fission'] + energyfunc_tally.filters = [energyfunc_filter] + pincell.tallies.append(energyfunc_tally) + # Write XML files in tmpdir with cdtemp(): pincell.export_to_xml() @@ -170,12 +178,21 @@ def test_settings(capi_init): def test_tally_mapping(capi_init): tallies = openmc.capi.tallies assert isinstance(tallies, Mapping) - assert len(tallies) == 2 + assert len(tallies) == 3 for tally_id, tally in tallies.items(): assert isinstance(tally, openmc.capi.Tally) assert tally_id == tally.id +def test_energy_function_filter(capi_init): + """Test special __new__ and __init__ for EnergyFunctionFilter""" + efunc = openmc.capi.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) + assert len(efunc.energy) == 2 + assert efunc.energy == [0.0, 1.0] + assert len(efunc.y) == 2 + assert efunc.y == [0.0, 2.0] + + def test_tally(capi_init): t = openmc.capi.tallies[1] assert t.type == 'volume' @@ -211,6 +228,16 @@ def test_tally(capi_init): assert len(t2.filters[1].bins) == 3 assert t2.filters[0].order == 5 + t3 = openmc.capi.tallies[3] + assert len(t3.filters) == 1 + t3_f = t3.filters[0] + assert isinstance(t3_f, openmc.capi.EnergyFunctionFilter) + assert len(t3_f.energy) == 2 + assert len(t3_f.y) == 2 + t3_f.set_interp_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) + assert len(t3_f.energy) == 3 + assert len(t3_f.y) == 3 + def test_new_tally(capi_init): with pytest.raises(exc.AllocationError): @@ -219,7 +246,7 @@ def test_new_tally(capi_init): new_tally.scores = ['flux'] new_tally_with_id = openmc.capi.Tally(10) new_tally_with_id.scores = ['flux'] - assert len(openmc.capi.tallies) == 4 + assert len(openmc.capi.tallies) == 5 def test_tally_activate(capi_simulation_init): From 9d952c3c5807e5426155d4ae5bd79a06ea1bd48a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 10:14:20 -0500 Subject: [PATCH 2/3] Use a.all() for equality in capi EnergyFunctionFilter tests --- tests/unit_tests/test_capi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 31db130db..57978838f 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -188,9 +188,9 @@ def test_energy_function_filter(capi_init): """Test special __new__ and __init__ for EnergyFunctionFilter""" efunc = openmc.capi.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) assert len(efunc.energy) == 2 - assert efunc.energy == [0.0, 1.0] + assert (efunc.energy == [0.0, 1.0]).all() assert len(efunc.y) == 2 - assert efunc.y == [0.0, 2.0] + assert (efunc.y == [0.0, 2.0]).all() def test_tally(capi_init): From 8d920b792a6d0b5f9bb4f97472cc274ff7065c88 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 15:39:55 -0500 Subject: [PATCH 3/3] set_interp_data -> set_data for capi.EnergyFunctionFilter Add doctring to set_data method. Update test_capi.py with this change --- openmc/capi/filter.py | 13 +++++++++++-- src/tallies/filter_energyfunc.cpp | 6 +++--- tests/unit_tests/test_capi.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index f1f0cdc46..92818a2aa 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -221,9 +221,18 @@ class EnergyFunctionFilter(Filter): raise AttributeError("Need both energy and y or neither") super().__init__(uid, new, index) if energy is not None: - self.set_interp_data(energy, y) + self.set_data(energy, y) - def set_interp_data(self, energy, y): + def set_data(self, energy, y): + """Set the interpolation information for the filter + + Parameters + ---------- + energy : numpy.ndarray + Independent variable for the interpolation + y : numpy.ndarray + Dependent variable for the interpolation + """ energy_array = np.asarray(energy) y_array = np.asarray(y) energy_p = energy_array.ctypes.data_as(POINTER(c_double)) diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index cf74d9836..3e3fbd909 100644 --- a/src/tallies/filter_energyfunc.cpp +++ b/src/tallies/filter_energyfunc.cpp @@ -94,7 +94,7 @@ EnergyFunctionFilter::text_label(int bin) const // C-API functions //============================================================================== -extern"C" int +extern "C" int openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, const double* y) { @@ -116,7 +116,7 @@ openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, return 0; } -extern"C" int +extern "C" int openmc_energyfunc_filter_get_energy(int32_t index, size_t *n, const double** energy) { // ensure this is a valid index to allocated filter @@ -137,7 +137,7 @@ openmc_energyfunc_filter_get_energy(int32_t index, size_t *n, const double** ene return 0; } -extern"C" int +extern "C" int openmc_energyfunc_filter_get_y(int32_t index, size_t *n, const double** y) { // ensure this is a valid index to allocated filter diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 57978838f..ed0bfd441 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -234,7 +234,7 @@ def test_tally(capi_init): assert isinstance(t3_f, openmc.capi.EnergyFunctionFilter) assert len(t3_f.energy) == 2 assert len(t3_f.y) == 2 - t3_f.set_interp_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) + t3_f.set_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) assert len(t3_f.energy) == 3 assert len(t3_f.y) == 3