From 1d8f4c922e9df151ec499d3cde8412cc2e48717b Mon Sep 17 00:00:00 2001 From: agnelson Date: Tue, 27 Jul 2021 18:30:48 -0500 Subject: [PATCH 1/4] Added a set/get_translation attribute to the Cell C-API interface. This is also tested. --- include/openmc/capi.h | 2 ++ openmc/lib/cell.py | 29 +++++++++++++++++++++++++++++ src/cell.cpp | 28 ++++++++++++++++++++++++++++ tests/unit_tests/test_lib.py | 6 ++++++ 4 files changed, 65 insertions(+) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index f08ec5c02f..17a693740c 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -14,12 +14,14 @@ extern "C" { int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n); int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_get_temperature(int32_t index, const int32_t* instance, double* T); + int openmc_cell_get_translation(int32_t index, double xyz[]); int openmc_cell_get_name(int32_t index, const char** name); int openmc_cell_get_num_instances(int32_t index, int32_t* num_instances); int openmc_cell_set_name(int32_t index, const char* name); int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices); int openmc_cell_set_id(int32_t index, int32_t id); 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_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); diff --git a/openmc/lib/cell.py b/openmc/lib/cell.py index 7fdc4542db..3c9132e7ad 100644 --- a/openmc/lib/cell.py +++ b/openmc/lib/cell.py @@ -35,6 +35,9 @@ _dll.openmc_cell_get_temperature.errcheck = _error_handler _dll.openmc_cell_get_name.argtypes = [c_int32, POINTER(c_char_p)] _dll.openmc_cell_get_name.restype = c_int _dll.openmc_cell_get_name.errcheck = _error_handler +_dll.openmc_cell_get_translation.argtypes = [c_int32, POINTER(c_double)] +_dll.openmc_cell_get_translation.restype = c_int +_dll.openmc_cell_get_translation.errcheck = _error_handler _dll.openmc_cell_set_name.argtypes = [c_int32, c_char_p] _dll.openmc_cell_set_name.restype = c_int _dll.openmc_cell_set_name.errcheck = _error_handler @@ -49,6 +52,9 @@ _dll.openmc_cell_set_temperature.argtypes = [ c_int32, c_double, POINTER(c_int32), c_bool] _dll.openmc_cell_set_temperature.restype = c_int _dll.openmc_cell_set_temperature.errcheck = _error_handler +_dll.openmc_cell_set_translation.argtypes = [c_int32, POINTER(c_double)] +_dll.openmc_cell_set_translation.restype = c_int +_dll.openmc_cell_set_translation.errcheck = _error_handler _dll.openmc_get_cell_index.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_get_cell_index.restype = c_int _dll.openmc_get_cell_index.errcheck = _error_handler @@ -213,6 +219,29 @@ class Cell(_FortranObjectWithID): _dll.openmc_cell_set_temperature(self._index, T, instance, set_contained) + def get_translation(self): + """Get the translation vector of a cell + + """ + + translation = np.zeros(3) + _dll.openmc_cell_get_translation( + self._index, translation.ctypes.data_as(POINTER(c_double))) + return translation + + def set_translation(self, translation): + """Set the translation vector of a cell + + Parameters + ---------- + translation : numpy.ndarray + 3-D translation vector + + """ + + _dll.openmc_cell_set_translation( + self._index, translation.ctypes.data_as(POINTER(c_double))) + @property def bounding_box(self): inf = sys.float_info.max diff --git a/src/cell.cpp b/src/cell.cpp index a9e9cc3a5e..6011081d76 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1299,6 +1299,34 @@ openmc_cell_set_id(int32_t index, int32_t id) } } +//! Return the translation vector of a cell +extern "C" int openmc_cell_get_translation(int32_t index, double xyz[]) +{ + if (index >= 0 && index < model::cells.size()) { + auto& cell = openmc::model::cells[index]; + xyz[0] = cell->translation_.x; + xyz[1] = cell->translation_.y; + xyz[2] = cell->translation_.z; + return 0; + } else { + set_errmsg("Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } +} + +//! Set the translation vector of a cell +extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[]) +{ + if (index >= 0 && index < model::cells.size()) { + model::cells[index]->translation_ = Position(xyz); + return 0; + } else { + set_errmsg("Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } +} + +//! Get the number of instances of the requested cell extern "C" int openmc_cell_get_num_instances(int32_t index, int32_t* num_instances) { diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 9a4fc8b9fd..8ec12efdd3 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -141,6 +141,12 @@ def test_properties_temperature(lib_init): openmc.lib.import_properties('properties.h5') assert cell.get_temperature() == pytest.approx(200.0) +def test_cell_translation(lib_init): + cell = openmc.lib.cells[1] + assert cell.get_translation() == pytest.approx([0., 0., 0.]) + cell.set_translation(np.array([1., 0., -1.])) + assert cell.get_translation() == pytest.approx([1., 0., -1.]) + def test_new_cell(lib_init): with pytest.raises(exc.AllocationError): From 7bcf2ca9e8919f5ba51e06d06a9e5e9f3c30ec04 Mon Sep 17 00:00:00 2001 From: agnelson Date: Tue, 27 Jul 2021 19:03:38 -0500 Subject: [PATCH 2/4] Adding a check to ensure we dont translate a cell not filled with a universe and then incorporating this in the unit test. --- src/cell.cpp | 6 ++++++ tests/unit_tests/test_lib.py | 40 ++++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 6011081d76..a586677730 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1318,6 +1318,12 @@ extern "C" int openmc_cell_get_translation(int32_t index, double xyz[]) extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[]) { if (index >= 0 && index < model::cells.size()) { + if (model::cells[index]->fill_ == C_NONE) { + set_errmsg(fmt::format("Cannot apply a translation to cell {}" + " because it is not filled with another universe", + index)); + return OPENMC_E_GEOMETRY; + } model::cells[index]->translation_ = Position(xyz); return 0; } else { diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 8ec12efdd3..0375f1437c 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -99,6 +99,21 @@ def lib_run(lib_simulation_init): openmc.lib.run() +@pytest.fixture(scope='module') +def pincell_model_w_univ(): + """Set up a model to test with and delete files when done""" + openmc.reset_auto_ids() + pincell = openmc.examples.pwr_pin_cell() + clad_univ = openmc.Universe(cells=[openmc.Cell(fill=pincell.materials[1])]) + pincell.geometry.root_universe.cells[2].fill = clad_univ + pincell.settings.verbosity = 1 + + # Write XML files in tmpdir + with cdtemp(): + pincell.export_to_xml() + yield + + def test_cell_mapping(lib_init): cells = openmc.lib.cells assert isinstance(cells, Mapping) @@ -141,12 +156,6 @@ def test_properties_temperature(lib_init): openmc.lib.import_properties('properties.h5') assert cell.get_temperature() == pytest.approx(200.0) -def test_cell_translation(lib_init): - cell = openmc.lib.cells[1] - assert cell.get_translation() == pytest.approx([0., 0., 0.]) - cell.set_translation(np.array([1., 0., -1.])) - assert cell.get_translation() == pytest.approx([1., 0., -1.]) - def test_new_cell(lib_init): with pytest.raises(exc.AllocationError): @@ -699,3 +708,22 @@ def test_trigger_set_n_batches(uo2_trigger_model, mpi_intracomm): # Ensure statepoint was created only at batch 20 when calling set_batches assert not os.path.exists('statepoint.12.h5') assert os.path.exists('statepoint.20.h5') + + +def test_cell_translation(pincell_model_w_univ, mpi_intracomm): + openmc.lib.finalize() + openmc.lib.init(intracomm=mpi_intracomm) + openmc.lib.simulation_init() + # Cell 1 is filled with a material so it has a translation, but we can't + # set it. + cell = openmc.lib.cells[1] + assert cell.get_translation() == pytest.approx([0., 0., 0.]) + with pytest.raises(exc.GeometryError, match='not filled with'): + cell.set_translation(np.array([1., 0., -1.])) + + # Cell 2 was given a universe, so we can assign it a translation vector + cell = openmc.lib.cells[2] + assert cell.get_translation() == pytest.approx([0., 0., 0.]) + # This time we *can* set it + cell.set_translation(np.array([1., 0., -1.])) + assert cell.get_translation() == pytest.approx([1., 0., -1.]) From 6a9d43bcdbb5c7acbe484c3bab0b393412acd938 Mon Sep 17 00:00:00 2001 From: agnelson Date: Wed, 28 Jul 2021 05:37:13 -0500 Subject: [PATCH 3/4] Added c-api methods for rotation matrix set/get --- include/openmc/capi.h | 2 + include/openmc/cell.h | 4 ++ openmc/lib/cell.py | 48 ++++++++++++++- src/cell.cpp | 114 +++++++++++++++++++++++------------ tests/unit_tests/test_lib.py | 15 +++++ 5 files changed, 145 insertions(+), 38 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 17a693740c..262bae6d79 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -15,6 +15,7 @@ extern "C" { int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_get_temperature(int32_t index, const int32_t* instance, double* T); int openmc_cell_get_translation(int32_t index, double xyz[]); + int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n); int openmc_cell_get_name(int32_t index, const char** name); int openmc_cell_get_num_instances(int32_t index, int32_t* num_instances); int openmc_cell_set_name(int32_t index, const char* name); @@ -22,6 +23,7 @@ extern "C" { int openmc_cell_set_id(int32_t index, int32_t id); 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[], const size_t rot_len); 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); diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 634b489de2..8d439b1bbc 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -159,6 +159,10 @@ public: //! temperatures. void set_temperature(double T, int32_t instance = -1, bool set_contained = false); + //! Set the rotation matrix of a cell instance + //! \param[in] rot The rotation matrix of length 3 or 9 + void set_rotation(const vector& rot); + //! Get the name of a cell //! \return Cell name const std::string& name() const { return name_; }; diff --git a/openmc/lib/cell.py b/openmc/lib/cell.py index 3c9132e7ad..dd522d614e 100644 --- a/openmc/lib/cell.py +++ b/openmc/lib/cell.py @@ -1,7 +1,7 @@ import sys from collections.abc import Mapping, Iterable -from ctypes import c_int, c_int32, c_double, c_char_p, POINTER, c_bool +from ctypes import c_int, c_int32, c_double, c_char_p, POINTER, c_bool, c_size_t from weakref import WeakValueDictionary import numpy as np @@ -38,6 +38,10 @@ _dll.openmc_cell_get_name.errcheck = _error_handler _dll.openmc_cell_get_translation.argtypes = [c_int32, POINTER(c_double)] _dll.openmc_cell_get_translation.restype = c_int _dll.openmc_cell_get_translation.errcheck = _error_handler +_dll.openmc_cell_get_rotation.argtypes = [c_int32, POINTER(c_double), + POINTER(c_size_t)] +_dll.openmc_cell_get_rotation.restype = c_int +_dll.openmc_cell_get_rotation.errcheck = _error_handler _dll.openmc_cell_set_name.argtypes = [c_int32, c_char_p] _dll.openmc_cell_set_name.restype = c_int _dll.openmc_cell_set_name.errcheck = _error_handler @@ -55,6 +59,10 @@ _dll.openmc_cell_set_temperature.errcheck = _error_handler _dll.openmc_cell_set_translation.argtypes = [c_int32, POINTER(c_double)] _dll.openmc_cell_set_translation.restype = c_int _dll.openmc_cell_set_translation.errcheck = _error_handler +_dll.openmc_cell_set_rotation.argtypes = [ + c_int32, POINTER(c_double), c_size_t] +_dll.openmc_cell_set_rotation.restype = c_int +_dll.openmc_cell_set_rotation.errcheck = _error_handler _dll.openmc_get_cell_index.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_get_cell_index.restype = c_int _dll.openmc_get_cell_index.errcheck = _error_handler @@ -242,6 +250,44 @@ class Cell(_FortranObjectWithID): _dll.openmc_cell_set_translation( self._index, translation.ctypes.data_as(POINTER(c_double))) + def get_rotation(self): + """Get the rotation matrix of a cell + + """ + + rotation = np.zeros(12) + rot_size = c_size_t() + + _dll.openmc_cell_get_rotation( + self._index, rotation.ctypes.data_as(POINTER(c_double)), rot_size) + rot_size = rot_size.value + + if rot_size == 0: + return None + elif rot_size == 9: + return rotation[:n] + elif rot_size == 12: + return rotation[9:] + else: + raise ValueError( + 'Invalid size of rotation matrix: {}'.format(rot_size)) + + def set_rotation(self, rotation): + """Set the rotation matrix of a cell + + Parameters + ---------- + rotation : numpy.ndarray + The rotation angles (if length is 3) or the 3x3 rotation matrix + + """ + + flat_rotation = rotation.flatten() + + _dll.openmc_cell_set_rotation( + self._index, flat_rotation.ctypes.data_as(POINTER(c_double)), + c_size_t(len(flat_rotation))) + @property def bounding_box(self): inf = sys.float_info.max diff --git a/src/cell.cpp b/src/cell.cpp index a586677730..dccae9bea7 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -248,6 +248,48 @@ BoundingBox Universe::bounding_box() const { // Cell implementation //============================================================================== +void +Cell::set_rotation(const vector& rot) { + if (fill_ == C_NONE) { + fatal_error(fmt::format("Cannot apply a rotation to cell {}" + " because it is not filled with another universe", + id_)); + } + + if (rot.size() != 3 && rot.size() != 9) { + fatal_error(fmt::format("Non-3D rotation vector applied to cell {}", id_)); + } + + // Compute and store the rotation matrix. + rotation_.clear(); + rotation_.reserve(rot.size() == 9 ? 9 : 12); + if (rot.size() == 3) { + double phi = -rot[0] * PI / 180.0; + double theta = -rot[1] * PI / 180.0; + double psi = -rot[2] * PI / 180.0; + rotation_.push_back(std::cos(theta) * std::cos(psi)); + rotation_.push_back(-std::cos(phi) * std::sin(psi) + + std::sin(phi) * std::sin(theta) * std::cos(psi)); + rotation_.push_back(std::sin(phi) * std::sin(psi) + + std::cos(phi) * std::sin(theta) * std::cos(psi)); + rotation_.push_back(std::cos(theta) * std::sin(psi)); + rotation_.push_back(std::cos(phi) * std::cos(psi) + + std::sin(phi) * std::sin(theta) * std::sin(psi)); + rotation_.push_back(-std::sin(phi) * std::cos(psi) + + std::cos(phi) * std::sin(theta) * std::sin(psi)); + rotation_.push_back(-std::sin(theta)); + rotation_.push_back(std::sin(phi) * std::cos(theta)); + rotation_.push_back(std::cos(phi) * std::cos(theta)); + + // When user specifies angles, write them at end of vector + rotation_.push_back(rot[0]); + rotation_.push_back(rot[1]); + rotation_.push_back(rot[2]); + } else { + std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_)); + } +} + double Cell::temperature(int32_t instance) const { @@ -572,44 +614,8 @@ CSGCell::CSGCell(pugi::xml_node cell_node) // Read the rotation transform. if (check_for_node(cell_node, "rotation")) { - if (fill_ == C_NONE) { - fatal_error(fmt::format("Cannot apply a rotation to cell {}" - " because it is not filled with another universe", id_)); - } - auto rot {get_node_array(cell_node, "rotation")}; - if (rot.size() != 3 && rot.size() != 9) { - fatal_error(fmt::format( - "Non-3D rotation vector applied to cell {}", id_)); - } - - // Compute and store the rotation matrix. - rotation_.reserve(rot.size() == 9 ? 9 : 12); - if (rot.size() == 3) { - double phi = -rot[0] * PI / 180.0; - double theta = -rot[1] * PI / 180.0; - double psi = -rot[2] * PI / 180.0; - rotation_.push_back(std::cos(theta) * std::cos(psi)); - rotation_.push_back(-std::cos(phi) * std::sin(psi) - + std::sin(phi) * std::sin(theta) * std::cos(psi)); - rotation_.push_back(std::sin(phi) * std::sin(psi) - + std::cos(phi) * std::sin(theta) * std::cos(psi)); - rotation_.push_back(std::cos(theta) * std::sin(psi)); - rotation_.push_back(std::cos(phi) * std::cos(psi) - + std::sin(phi) * std::sin(theta) * std::sin(psi)); - rotation_.push_back(-std::sin(phi) * std::cos(psi) - + std::cos(phi) * std::sin(theta) * std::sin(psi)); - rotation_.push_back(-std::sin(theta)); - rotation_.push_back(std::sin(phi) * std::cos(theta)); - rotation_.push_back(std::cos(phi) * std::cos(theta)); - - // When user specifies angles, write them at end of vector - rotation_.push_back(rot[0]); - rotation_.push_back(rot[1]); - rotation_.push_back(rot[2]); - } else { - std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_)); - } + set_rotation(rot); } } @@ -1332,6 +1338,40 @@ extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[]) } } +//! Return the rotation matrix of a cell +extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n) +{ + if (index >= 0 && index < model::cells.size()) { + auto& cell = openmc::model::cells[index]; + *n = cell->rotation_.size(); + std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0])); + return 0; + } else { + set_errmsg("Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } +} + +//! Set the flattened rotation matrix of a cell +extern "C" int openmc_cell_set_rotation(int32_t index, const double rot[], + const size_t rot_len) +{ + if (index >= 0 && index < model::cells.size()) { + if (model::cells[index]->fill_ == C_NONE) { + set_errmsg(fmt::format("Cannot apply a rotation to cell {}" + " because it is not filled with another universe", + index)); + return OPENMC_E_GEOMETRY; + } + std::vector vec_rot(rot, rot + rot_len); + model::cells[index]->set_rotation(vec_rot); + return 0; + } else { + set_errmsg("Index in cells array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } +} + //! Get the number of instances of the requested cell extern "C" int openmc_cell_get_num_instances(int32_t index, int32_t* num_instances) diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 0375f1437c..5566874b02 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -727,3 +727,18 @@ def test_cell_translation(pincell_model_w_univ, mpi_intracomm): # This time we *can* set it cell.set_translation(np.array([1., 0., -1.])) assert cell.get_translation() == pytest.approx([1., 0., -1.]) + + +def test_cell_rotation(pincell_model_w_univ): + # Cell 1 is filled with a material so we cannot rotate it, but we can get + # its rotation matrix (which will be the identity matrix) + cell = openmc.lib.cells[1] + assert cell.get_rotation() is None + with pytest.raises(exc.GeometryError, match='not filled with'): + cell.set_rotation(np.array([180., 0., 0.])) + + # Now repeat with Cell 2 and we will be allowed to do it + cell = openmc.lib.cells[2] + assert cell.get_rotation() is None + cell.set_rotation(np.array([180., 0., 0.])) + assert cell.get_rotation() == pytest.approx([180., 0., 0.]) From ad3a14fa3d4ae8f250913a5221ad808d92c33b62 Mon Sep 17 00:00:00 2001 From: agnelson Date: Fri, 30 Jul 2021 11:42:27 -0500 Subject: [PATCH 4/4] Resolving comments from @paulromano. Namely, set the openmc.lib.Cell rotation and translations to be properties that can take iterables vice ndarrays. Also removed const declaration when not needed. Finally, revised code and tests so that we do not return a None for non-initialized cases. This would yield different behavior between translation and rotation and instead of leading to confusion I now simply always return a sensical ndarray of float values.That is no rotation is no longer None, but is now array([0., 0., 0.]). --- include/openmc/capi.h | 2 +- openmc/lib/cell.py | 68 +++++++++++++++--------------------- src/cell.cpp | 6 ++-- tests/unit_tests/test_lib.py | 20 +++++------ 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 262bae6d79..012c7c18a0 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -23,7 +23,7 @@ extern "C" { int openmc_cell_set_id(int32_t index, int32_t id); 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[], const size_t rot_len); + int openmc_cell_set_rotation(int32_t index, const double rot[], size_t rot_len); 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); diff --git a/openmc/lib/cell.py b/openmc/lib/cell.py index dd522d614e..b53a6be7a7 100644 --- a/openmc/lib/cell.py +++ b/openmc/lib/cell.py @@ -103,6 +103,13 @@ class Cell(_FortranObjectWithID): Number of unique cell instances bounding_box : 2-tuple of numpy.ndarray Lower-left and upper-right coordinates of bounding box + translation : Iterable of float + 3-D coordinates of the translation vector + rotation : Iterable of float + The rotation matrix or angles of the universe filling the cell. This + can either be a fully specified 3 x 3 rotation matrix or an Iterable + of length 3 with the angles in degrees about the x, y, and z axes, + respectively. """ __instances = WeakValueDictionary() @@ -227,62 +234,43 @@ class Cell(_FortranObjectWithID): _dll.openmc_cell_set_temperature(self._index, T, instance, set_contained) - def get_translation(self): - """Get the translation vector of a cell - - """ - + @property + def translation(self): translation = np.zeros(3) _dll.openmc_cell_get_translation( self._index, translation.ctypes.data_as(POINTER(c_double))) return translation - def set_translation(self, translation): - """Set the translation vector of a cell - - Parameters - ---------- - translation : numpy.ndarray - 3-D translation vector - - """ - + @translation.setter + def translation(self, translation_vec): + vector = np.asarray(translation_vec, dtype=float) _dll.openmc_cell_set_translation( - self._index, translation.ctypes.data_as(POINTER(c_double))) + self._index, vector.ctypes.data_as(POINTER(c_double))) - def get_rotation(self): - """Get the rotation matrix of a cell - - """ - - rotation = np.zeros(12) + @property + def rotation(self): + rotation_data = np.zeros(12) rot_size = c_size_t() _dll.openmc_cell_get_rotation( - self._index, rotation.ctypes.data_as(POINTER(c_double)), rot_size) + self._index, rotation_data.ctypes.data_as(POINTER(c_double)), + rot_size) rot_size = rot_size.value - if rot_size == 0: - return None - elif rot_size == 9: - return rotation[:n] - elif rot_size == 12: - return rotation[9:] + if rot_size == 9: + return rotation_data[:rot_size].shape(3, 3) + elif rot_size in (0, 12): + # If size is 0, rotation_data[9:] will be zeros. This indicates no + # rotation and is the most straightforward way to always return + # an iterable of floats + return rotation_data[9:] else: raise ValueError( 'Invalid size of rotation matrix: {}'.format(rot_size)) - def set_rotation(self, rotation): - """Set the rotation matrix of a cell - - Parameters - ---------- - rotation : numpy.ndarray - The rotation angles (if length is 3) or the 3x3 rotation matrix - - """ - - flat_rotation = rotation.flatten() + @rotation.setter + def rotation(self, rotation_data): + flat_rotation = np.asarray(rotation_data, dtype=float).flatten() _dll.openmc_cell_set_rotation( self._index, flat_rotation.ctypes.data_as(POINTER(c_double)), diff --git a/src/cell.cpp b/src/cell.cpp index dccae9bea7..97f64e3a1d 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1309,7 +1309,7 @@ openmc_cell_set_id(int32_t index, int32_t id) extern "C" int openmc_cell_get_translation(int32_t index, double xyz[]) { if (index >= 0 && index < model::cells.size()) { - auto& cell = openmc::model::cells[index]; + auto& cell = model::cells[index]; xyz[0] = cell->translation_.x; xyz[1] = cell->translation_.y; xyz[2] = cell->translation_.z; @@ -1342,7 +1342,7 @@ extern "C" int openmc_cell_set_translation(int32_t index, const double xyz[]) extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n) { if (index >= 0 && index < model::cells.size()) { - auto& cell = openmc::model::cells[index]; + auto& cell = model::cells[index]; *n = cell->rotation_.size(); std::memcpy(rot, cell->rotation_.data(), *n * sizeof(cell->rotation_[0])); return 0; @@ -1354,7 +1354,7 @@ extern "C" int openmc_cell_get_rotation(int32_t index, double rot[], size_t* n) //! Set the flattened rotation matrix of a cell extern "C" int openmc_cell_set_rotation(int32_t index, const double rot[], - const size_t rot_len) + size_t rot_len) { if (index >= 0 && index < model::cells.size()) { if (model::cells[index]->fill_ == C_NONE) { diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 5566874b02..41613832f3 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -717,28 +717,28 @@ def test_cell_translation(pincell_model_w_univ, mpi_intracomm): # Cell 1 is filled with a material so it has a translation, but we can't # set it. cell = openmc.lib.cells[1] - assert cell.get_translation() == pytest.approx([0., 0., 0.]) + assert cell.translation == pytest.approx([0., 0., 0.]) with pytest.raises(exc.GeometryError, match='not filled with'): - cell.set_translation(np.array([1., 0., -1.])) + cell.translation = (1., 0., -1.) # Cell 2 was given a universe, so we can assign it a translation vector cell = openmc.lib.cells[2] - assert cell.get_translation() == pytest.approx([0., 0., 0.]) + assert cell.translation == pytest.approx([0., 0., 0.]) # This time we *can* set it - cell.set_translation(np.array([1., 0., -1.])) - assert cell.get_translation() == pytest.approx([1., 0., -1.]) + cell.translation = (1., 0., -1.) + assert cell.translation == pytest.approx([1., 0., -1.]) def test_cell_rotation(pincell_model_w_univ): # Cell 1 is filled with a material so we cannot rotate it, but we can get # its rotation matrix (which will be the identity matrix) cell = openmc.lib.cells[1] - assert cell.get_rotation() is None + assert cell.rotation == pytest.approx([0., 0., 0.]) with pytest.raises(exc.GeometryError, match='not filled with'): - cell.set_rotation(np.array([180., 0., 0.])) + cell.rotation = (180., 0., 0.) # Now repeat with Cell 2 and we will be allowed to do it cell = openmc.lib.cells[2] - assert cell.get_rotation() is None - cell.set_rotation(np.array([180., 0., 0.])) - assert cell.get_rotation() == pytest.approx([180., 0., 0.]) + assert cell.rotation == pytest.approx([0., 0., 0.]) + cell.rotation = (180., 0., 0.) + assert cell.rotation == pytest.approx([180., 0., 0.])