diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 9bfe4aa9e..07c6bc80e 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -70,6 +70,7 @@ extern "C" { int openmc_next_batch(int* status); int openmc_nuclide_name(int index, const char** name); int openmc_plot_geometry(); + int openmc_id_map(void* slice, int32_t *data_out); int openmc_reset(); int openmc_run(); void openmc_set_seed(int64_t new_seed); diff --git a/include/openmc/plot.h b/include/openmc/plot.h index aef7e613b..0bbd3c3ad 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -20,7 +20,7 @@ namespace openmc { //=============================================================================== class Plot; -class PlotC; + namespace model { @@ -74,21 +74,16 @@ enum class PlotColorBy { //=============================================================================== // Plot class //=============================================================================== - - -// plot class for interaction with external code -class CPlot { - -public: +struct Slice { + // Members Position origin_; //!< Plot origin in geometry Position width_; //!< Plot width in geometry PlotBasis basis_; //!< Plot basis (XY/XZ/YZ) - std::array pixels_; //!< Plot size in pixels + int pixels_[3]; //!< Plot size in pixels int level_; //!< Plot universe level }; - -class Plot : public CPlot +class Plot : public Slice { public: diff --git a/openmc/capi/plot.py b/openmc/capi/plot.py index 10a6aa280..fe7dd531c 100644 --- a/openmc/capi/plot.py +++ b/openmc/capi/plot.py @@ -6,19 +6,18 @@ from .error import _error_handler import numpy as np -class _IntThree(): - type_ = c_int*3 - val_ = None - def __init__(self, vals=None): - if vals and iterable(vals) and all(vals == int): - self.val_ = type_(vals[0], vals[1], vals[2]) - else: - raise ValueError("{} is not a valid object to construct IntThree.".format(vals)) - - def __str__(self): - return "({}, {}, {})".format(self.val_[0], self.val_[1], self.val_[2]) - class _Position(Structure): + """Definition of an xyz location in space with underlying c-types + + C-type Attributes + ----------------- + x : c_double + Position's x value (default: 0.0) + y : c_double + Position's y value (default: 0.0) + z : c_double + Position's z value (default: 0.0) + """ _fields_ = [('x', c_double), ('y', c_double), ('z', c_double)] @@ -61,9 +60,43 @@ class _Position(Structure): self.z = z_val def __str__(self): - return "({}, {}, {})".format(self.x, self.y, self.z) + return "Position: ({}, {}, {})".format(self.x, self.y, self.z) + class _Plot(Structure): + """A Plot structure defining a 2-D slice with underlying c-types + + C-Type Attributes + ----------------- + origin : openmc.capi.plot._Position + A position defining the origin of the plot. + width_ : openmc.capi.plot._Position + The width of the plot along the x, y, and z axes, respectively + basis_ : c_int + The axes basis of the plot view. + pixels_ : c_int[3] + The resolution of the plot in the horizontal and vertical dimensions + level_ : c_int + The universe level for the plot view + + Attributes + ---------- + origin : tuple or list of ndarray + Origin (center) of the plot + width : float + The horizontal dimension of the plot in geometry units (cm) + height : float + The vertical dimension of the plot in geometry units (cm) + basis : string + One of {'xy', 'xz', 'yz'} indicating the horizontal and vertical + axes of the plot. + hRes : float + The horizontal resolution of the plot in pixels + vRes : float + The vertical resolution of the plot in pixels + level : int + The universe level for the plot (default: -1 -> all universes shown) + """ _fields_ = [('origin_', _Position), ('width_', _Position), ('basis_', c_int), @@ -127,6 +160,7 @@ class _Plot(Structure): def basis(self, basis): if isinstance(basis, str): valid_bases = ('xy', 'xz', 'yz') + basis = basis.lower() if basis not in valid_bases: raise ValueError("{} is not a valid plot basis.".format(basis)) @@ -159,25 +193,43 @@ class _Plot(Structure): def level(self, level): self.level_ = level - def __str__(self): - out_str = "-------" + def __repr__(self): + out_str = "-----\n" out_str += "Plot:\n" - out_str += "-------" - out_str += "Origin: " + str(self.origin) + "\n" - out_str += "Width: " + str(self.width) + "\n" - out_str += "Height: " + str(self.height) + "\n" - out_str += "Basis: " + str(self.basis) + "\n" - out_str += "HRes: " + str(self.hRes) + "\n" - out_str += "VRes: " + str(self.vRes) + "\n" - out_str += "Level: " + str(self.level) + "\n" + out_str += "-----\n" + out_str += "Origin: {}\n".format(self.origin) + out_str += "Width: {}\n".format(self.width) + out_str += "Height: {}\n".format(self.height) + out_str += "Basis: {}\n".format(self.basis) + out_str += "HRes: {}\n".format(self.hRes) + out_str += "VRes: {}\n".format(self.vRes) + out_str += "Level: {}\n".format(self.level) return out_str + def __str__(self): + return self.__repr__() + _dll.openmc_id_map.argtypes= [POINTER(_Plot),] _dll.openmc_id_map.restype = c_int _dll.openmc_id_map.errcheck = _error_handler + def id_map(plot): - img_data = np.zeros((plot.pixels_[1], plot.pixels_[0], 2), dtype=np.dtype('int32')) - out = _dll.openmc_id_map(POINTER(_Plot)(plot), img_data.ctypes.data_as(POINTER(c_int32))) + """ + Generate a 2-D map of (cell_id, material_id). Used for in-memory image generation. + + Parameters + ---------- + plot : An openmc.capi.plot._Plot object describing the slice of the model to + be generated + + Returns + ------- + id_map : a NumPy array with shape (vertical pixels, horizontal pixels, 2) + of OpenMC property ids with dtype int32 + + """ + img_data = np.zeros((plot.vRes, plot.hRes, 2), dtype=np.dtype('int32')) + _dll.openmc_id_map(POINTER(_Plot)(plot), img_data.ctypes.data_as(POINTER(c_int32))) return img_data diff --git a/src/plot.cpp b/src/plot.cpp index 9bf3656d9..73aa3d4f6 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -886,7 +886,8 @@ void create_voxel(Plot pl) // Write current date and time write_attribute(file_id, "date_and_time", time_stamp().c_str()); hsize_t three = 3; - write_attribute(file_id, "num_voxels", pl.pixels_); + std::array pixels = {pl.pixels_[0], pl.pixels_[1], pl.pixels_[2]}; + write_attribute(file_id, "num_voxels", pixels); write_attribute(file_id, "voxel_width", vox); write_attribute(file_id, "lower_left", ll); @@ -979,40 +980,42 @@ RGBColor random_color() { return {int(prn()*255), int(prn()*255), int(prn()*255)}; } -extern "C" int openmc_id_map(const CPlot& pl, int* data_out) { +extern "C" int openmc_id_map(void* slice, int32_t* data_out) { - size_t width = pl.pixels_[0]; - size_t height = pl.pixels_[1]; + auto* sl = static_cast(slice); - double in_pixel = (pl.width_[0])/static_cast(width); - double out_pixel = (pl.width_[1])/static_cast(height); + size_t width = sl->pixels_[0]; + size_t height = sl->pixels_[1]; + + double in_pixel = (sl->width_[0])/static_cast(width); + double out_pixel = (sl->width_[1])/static_cast(height); IDData data; data.resize({height, width, 2}); int in_i, out_i; double xyz[3]; - switch(pl.basis_) { + switch(sl->basis_) { case PlotBasis::xy : in_i = 0; out_i = 1; - xyz[0] = pl.origin_[0] - pl.width_[0] / 2.; - xyz[1] = pl.origin_[1] + pl.width_[1] / 2.; - xyz[2] = pl.origin_[2]; + xyz[0] = sl->origin_[0] - sl->width_[0] / 2. + in_pixel / 2.; + xyz[1] = sl->origin_[1] + sl->width_[1] / 2. - out_pixel / 2.; + xyz[2] = sl->origin_[2]; break; case PlotBasis::xz : in_i = 0; out_i = 2; - xyz[0] = pl.origin_[0] - pl.width_[0] / 2.; - xyz[1] = pl.origin_[1]; - xyz[2] = pl.origin_[2] + pl.width_[1] / 2.; + xyz[0] = sl->origin_[0] - sl->width_[0] / 2. + in_pixel / 2.; + xyz[1] = sl->origin_[1]; + xyz[2] = sl->origin_[2] + sl->width_[1] / 2. - out_pixel / 2.; break; case PlotBasis::yz : in_i = 1; out_i = 2; - xyz[0] = pl.origin_[0]; - xyz[1] = pl.origin_[1] - pl.width_[0] / 2.; - xyz[2] = pl.origin_[2] + pl.width_[1] / 2.; + xyz[0] = sl->origin_[0]; + xyz[1] = sl->origin_[1] - sl->width_[0] / 2. + in_pixel / 2.; + xyz[2] = sl->origin_[2] + sl->width_[1] / 2. - out_pixel / 2.; break; } @@ -1025,15 +1028,15 @@ extern "C" int openmc_id_map(const CPlot& pl, int* data_out) { std::copy(xyz, xyz+3, p.coord[0].xyz); std::copy(dir, dir+3, p.coord[0].uvw); p.coord[0].universe = model::root_universe; - int level = pl.level_; + int level = sl->level_; int j{}; #pragma omp for for (int y = 0; y < height; y++) { p.coord[0].xyz[out_i] = xyz[out_i] - out_pixel * y; - p.n_coord = 1; for (int x = 0; x < width; x++) { p.coord[0].xyz[in_i] = xyz[in_i] + in_pixel * x; + p.n_coord = 1; // local variables bool found_cell = find_cell(&p, 0); j = p.n_coord - 1; diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 0ebc898b4..9d9ab66c0 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -400,3 +400,24 @@ def test_load_nuclide(capi_init): # load non-existent nuclide with pytest.raises(exc.DataError): openmc.capi.load_nuclide('Pu3') + + +def test_id_map(capi_init): + expected_ids = np.array([[(3,3), (2,2), (3,3)], + [(2,2), (1,1), (2,2)], + [(3,3), (2,2), (3,3)]], dtype = 'int32') + + # create a plot object + p = openmc.capi.plot._Plot() + + p.width = 1.26 + p.height = 1.26 + p.vRes = 3 + p.hRes = 3 + p.origin = (0,0,0) + p.basis = 'xy' + p.level = -1 + + ids = openmc.capi.plot.id_map(p) + print(ids) + assert np.array_equal(expected_ids, ids)