• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 21991279157

13 Feb 2026 02:53PM UTC coverage: 81.82% (-0.06%) from 81.875%
21991279157

Pull #3805

github

web-flow
Merge 0a7a80411 into bcb939520
Pull Request #3805: Remove xtensor and xtl Dependencies

17242 of 24268 branches covered (71.05%)

Branch coverage included in aggregate %.

977 of 1013 new or added lines in 39 files covered. (96.45%)

404 existing lines in 8 files now uncovered.

57420 of 66983 relevant lines covered (85.72%)

45458907.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.67
/include/openmc/plot.h
1
#ifndef OPENMC_PLOT_H
2
#define OPENMC_PLOT_H
3

4
#include <cmath>
5
#include <sstream>
6
#include <unordered_map>
7
#include <unordered_set>
8

9
#include "openmc/tensor.h"
10
#include "pugixml.hpp"
11

12
#include "hdf5.h"
13
#include "openmc/cell.h"
14
#include "openmc/constants.h"
15
#include "openmc/error.h"
16
#include "openmc/geometry.h"
17
#include "openmc/particle.h"
18
#include "openmc/position.h"
19
#include "openmc/random_lcg.h"
20
#include "openmc/xml_interface.h"
21

22
namespace openmc {
23

24
//===============================================================================
25
// Global variables
26
//===============================================================================
27

28
class PlottableInterface;
29

30
namespace model {
31

32
extern std::unordered_map<int, int> plot_map; //!< map of plot ids to index
33
extern vector<std::unique_ptr<PlottableInterface>>
34
  plots; //!< Plot instance container
35

36
extern uint64_t plotter_seed; // Stream index used by the plotter
37

38
} // namespace model
39

40
//===============================================================================
41
// RGBColor holds color information for plotted objects
42
//===============================================================================
43

44
struct RGBColor {
45
  // Constructors
46
  RGBColor() : red(0), green(0), blue(0) {};
1,971,116✔
47
  RGBColor(const int v[3]) : red(v[0]), green(v[1]), blue(v[2]) {};
48
  RGBColor(int r, int g, int b) : red(r), green(g), blue(b) {};
164,558✔
49

50
  RGBColor(const vector<int>& v)
280✔
51
  {
280✔
52
    if (v.size() != 3) {
280!
53
      throw std::out_of_range("Incorrect vector size for RGBColor.");
×
54
    }
55
    red = v[0];
280✔
56
    green = v[1];
280✔
57
    blue = v[2];
280✔
58
  }
280✔
59

60
  bool operator==(const RGBColor& other)
5,508✔
61
  {
62
    return red == other.red && green == other.green && blue == other.blue;
5,508!
63
  }
64

65
  RGBColor& operator*=(const double x)
674,210✔
66
  {
67
    red *= x;
674,210✔
68
    green *= x;
674,210✔
69
    blue *= x;
674,210✔
70
    return *this;
674,210✔
71
  }
72

73
  // Members
74
  uint8_t red, green, blue;
75
};
76

77
// some default colors
78
const RGBColor WHITE {255, 255, 255};
79
const RGBColor RED {255, 0, 0};
80
const RGBColor BLACK {0, 0, 0};
81

82
/**
83
 * \class PlottableInterface
84
 * \brief Interface for plottable objects.
85
 *
86
 * PlottableInterface classes must have unique IDs. If no ID (or -1) is
87
 * provided, the next available ID is assigned automatically. They guarantee
88
 * the ability to create output in some form. This interface is designed to be
89
 * implemented by classes that produce plot-relevant data which can be
90
 * visualized.
91
 */
92

93
typedef tensor::Tensor<RGBColor> ImageData;
94
class PlottableInterface {
95
public:
96
  PlottableInterface() = default;
10✔
97

98
  void set_default_colors();
99

100
private:
101
  void set_id(pugi::xml_node plot_node);
102
  int id_ {C_NONE}; // unique plot ID
103

104
  void set_bg_color(pugi::xml_node plot_node);
105
  void set_universe(pugi::xml_node plot_node);
106
  void set_color_by(pugi::xml_node plot_node);
107
  void set_user_colors(pugi::xml_node plot_node);
108
  void set_overlap_color(pugi::xml_node plot_node);
109
  void set_mask(pugi::xml_node plot_node);
110

111
protected:
112
  // Plot output filename, derived classes have logic to set it
113
  std::string path_plot_;
114

115
public:
116
  enum class PlotColorBy { cells = 0, mats = 1 };
117

118
  // Generates image data based on plot parameters and returns it
119
  virtual ImageData create_image() const = 0;
120

121
  // Creates the output image named path_plot_
122
  virtual void create_output() const = 0;
123

124
  // Write populated image data to file
125
  void write_image(const ImageData& data) const;
126

127
  // Print useful info to the terminal
128
  virtual void print_info() const = 0;
129

130
  const std::string& path_plot() const { return path_plot_; }
210✔
131
  std::string& path_plot() { return path_plot_; }
490✔
132
  int id() const { return id_; }
1,774✔
133
  void set_id(int id = C_NONE);
134
  int level() const { return level_; }
220✔
135
  PlotColorBy color_by() const { return color_by_; }
136

137
  // Public color-related data
138
  PlottableInterface(pugi::xml_node plot_node);
139
  virtual ~PlottableInterface() = default;
764✔
140
  int level_ {-1};                           // Universe level to plot
141
  bool color_overlaps_ {false};              // Show overlapping cells?
142
  PlotColorBy color_by_ {PlotColorBy::mats}; // Plot coloring (cell/material)
143
  RGBColor not_found_ {WHITE};               // Plot background color
144
  RGBColor overlap_color_ {RED};             // Plot overlap color
145
  vector<RGBColor> colors_;                  // Plot colors
146
};
147

148
struct IdData {
149
  // Constructor
150
  IdData(size_t h_res, size_t v_res);
151

152
  // Methods
153
  void set_value(size_t y, size_t x, const GeometryState& p, int level);
154
  void set_overlap(size_t y, size_t x);
155

156
  // Members
157
  tensor::Tensor<int32_t> data_; //!< 2D array of cell & material ids
158
};
159

160
struct PropertyData {
161
  // Constructor
162
  PropertyData(size_t h_res, size_t v_res);
163

164
  // Methods
165
  void set_value(size_t y, size_t x, const GeometryState& p, int level);
166
  void set_overlap(size_t y, size_t x);
167

168
  // Members
169
  tensor::Tensor<double> data_; //!< 2D array of temperature & density data
170
};
171

172
//===============================================================================
173
// Plot class
174
//===============================================================================
175

176
class SlicePlotBase {
177
public:
178
  template<class T>
179
  T get_map() const;
180

181
  enum class PlotBasis { xy = 1, xz = 2, yz = 3 };
182

183
  // Accessors
184

185
  const std::array<size_t, 3>& pixels() const { return pixels_; }
106,430✔
186
  std::array<size_t, 3>& pixels() { return pixels_; }
1,448✔
187

188
  // Members
189
public:
190
  Position origin_;           //!< Plot origin in geometry
191
  Position width_;            //!< Plot width in geometry
192
  PlotBasis basis_;           //!< Plot basis (XY/XZ/YZ)
193
  array<size_t, 3> pixels_;   //!< Plot size in pixels
194
  bool slice_color_overlaps_; //!< Show overlapping cells?
195
  int slice_level_ {-1};      //!< Plot universe level
196
private:
197
};
198

199
template<class T>
200
T SlicePlotBase::get_map() const
4,683✔
201
{
202

203
  size_t width = pixels_[0];
4,683✔
204
  size_t height = pixels_[1];
4,683✔
205

206
  // get pixel size
207
  double in_pixel = (width_[0]) / static_cast<double>(width);
4,683✔
208
  double out_pixel = (width_[1]) / static_cast<double>(height);
4,683✔
209

210
  // size data array
211
  T data(width, height);
4,683✔
212

213
  // setup basis indices and initial position centered on pixel
214
  int in_i, out_i;
215
  Position xyz = origin_;
4,683✔
216
  switch (basis_) {
4,683!
217
  case PlotBasis::xy:
4,563✔
218
    in_i = 0;
4,563✔
219
    out_i = 1;
4,563✔
220
    break;
4,563✔
221
  case PlotBasis::xz:
50✔
222
    in_i = 0;
50✔
223
    out_i = 2;
50✔
224
    break;
50✔
225
  case PlotBasis::yz:
70✔
226
    in_i = 1;
70✔
227
    out_i = 2;
70✔
228
    break;
70✔
UNCOV
229
  default:
×
UNCOV
230
    UNREACHABLE();
×
231
  }
232

233
  // set initial position
234
  xyz[in_i] = origin_[in_i] - width_[0] / 2. + in_pixel / 2.;
4,683✔
235
  xyz[out_i] = origin_[out_i] + width_[1] / 2. - out_pixel / 2.;
4,683✔
236

237
  // arbitrary direction
238
  Direction dir = {1. / std::sqrt(2.), 1. / std::sqrt(2.), 0.0};
4,683✔
239

240
#pragma omp parallel
2,811✔
241
  {
242
    GeometryState p;
1,872✔
243
    p.r() = xyz;
1,872✔
244
    p.u() = dir;
1,872✔
245
    p.coord(0).universe() = model::root_universe;
1,872✔
246
    int level = slice_level_;
1,872✔
247
    int j {};
1,872✔
248

249
#pragma omp for
250
    for (int y = 0; y < height; y++) {
352,864✔
251
      p.r()[out_i] = xyz[out_i] - out_pixel * y;
350,992✔
252
      for (int x = 0; x < width; x++) {
69,744,512✔
253
        p.r()[in_i] = xyz[in_i] + in_pixel * x;
69,393,520✔
254
        p.n_coord() = 1;
69,393,520✔
255
        // local variables
256
        bool found_cell = exhaustive_find_cell(p);
69,393,520✔
257
        j = p.n_coord() - 1;
69,393,520✔
258
        if (level >= 0) {
69,393,520!
259
          j = level;
260
        }
261
        if (found_cell) {
69,393,520✔
262
          data.set_value(y, x, p, j);
14,038,152✔
263
        }
264
        if (slice_color_overlaps_ && check_cell_overlap(p, false)) {
69,393,520✔
265
          data.set_overlap(y, x);
136,112✔
266
        }
267
      } // inner for
268
    }
269
  }
1,872✔
270

271
  return data;
9,366✔
UNCOV
272
}
×
273

274
// Represents either a voxel or pixel plot
275
class Plot : public PlottableInterface, public SlicePlotBase {
276

277
public:
278
  enum class PlotType { slice = 1, voxel = 2 };
279

280
  Plot(pugi::xml_node plot, PlotType type);
281

282
private:
283
  void set_output_path(pugi::xml_node plot_node);
284
  void set_basis(pugi::xml_node plot_node);
285
  void set_origin(pugi::xml_node plot_node);
286
  void set_width(pugi::xml_node plot_node);
287
  void set_meshlines(pugi::xml_node plot_node);
288

289
public:
290
  // Add mesh lines to ImageData
291
  void draw_mesh_lines(ImageData& data) const;
292
  ImageData create_image() const override;
293
  void create_voxel() const;
294

295
  void create_output() const override;
296
  void print_info() const override;
297

298
  PlotType type_;                 //!< Plot type (Slice/Voxel)
299
  int meshlines_width_;           //!< Width of lines added to the plot
300
  int index_meshlines_mesh_ {-1}; //!< Index of the mesh to draw on the plot
301
  RGBColor meshlines_color_;      //!< Color of meshlines on the plot
302
};
303

304
/**
305
 * \class RaytracePlot
306
 * \brief Base class for plots that generate images through ray tracing.
307
 *
308
 * This class serves as a base for plots that create their visuals by tracing
309
 * rays from a camera through the problem geometry. It inherits from
310
 * PlottableInterface, ensuring that it provides an implementation for
311
 * generating output specific to ray-traced visualization. WireframeRayTracePlot
312
 * and SolidRayTracePlot provide concrete implementations of this class.
313
 */
314
class RayTracePlot : public PlottableInterface {
315
public:
316
  RayTracePlot() = default;
10✔
317
  RayTracePlot(pugi::xml_node plot);
318

319
  // Standard getters. No setting since it's done from XML.
320
  const Position& camera_position() const { return camera_position_; }
102,980✔
321
  Position& camera_position() { return camera_position_; }
40✔
322
  const Position& look_at() const { return look_at_; }
323
  Position& look_at() { return look_at_; }
20✔
324

325
  const double& horizontal_field_of_view() const
326
  {
327
    return horizontal_field_of_view_;
328
  }
329
  double& horizontal_field_of_view() { return horizontal_field_of_view_; }
20✔
330

331
  void print_info() const override;
332

333
  const std::array<int, 2>& pixels() const { return pixels_; }
13,725,752✔
334
  std::array<int, 2>& pixels() { return pixels_; }
280✔
335

336
  const Direction& up() const { return up_; }
337
  Direction& up() { return up_; }
20✔
338

339
  //! brief Updates the cached camera-to-model matrix after changes to
340
  //! camera parameters.
341
  void update_view();
342

343
protected:
344
  Direction camera_x_axis() const
400,000✔
345
  {
346
    return {camera_to_model_[0], camera_to_model_[3], camera_to_model_[6]};
400,000✔
347
  }
348

349
  Direction camera_y_axis() const
400,000✔
350
  {
351
    return {camera_to_model_[1], camera_to_model_[4], camera_to_model_[7]};
400,000✔
352
  }
353

354
  Direction camera_z_axis() const
400,000✔
355
  {
356
    return {camera_to_model_[2], camera_to_model_[5], camera_to_model_[8]};
400,000✔
357
  }
358

359
  void set_output_path(pugi::xml_node plot_node);
360

361
  /*
362
   * Gets the starting position and direction for the pixel corresponding
363
   * to this horizontal and vertical position.
364
   */
365
  std::pair<Position, Direction> get_pixel_ray(int horiz, int vert) const;
366

367
private:
368
  void set_look_at(pugi::xml_node node);
369
  void set_camera_position(pugi::xml_node node);
370
  void set_field_of_view(pugi::xml_node node);
371
  void set_pixels(pugi::xml_node node);
372
  void set_orthographic_width(pugi::xml_node node);
373

374
  double horizontal_field_of_view_ {70.0}; // horiz. f.o.v. in degrees
375
  Position camera_position_;               // where camera is
376
  Position look_at_;                     // point camera is centered looking at
377
  std::array<int, 2> pixels_ {100, 100}; // pixel dimension of resulting image
378
  Direction up_ {0.0, 0.0, 1.0};         // which way is up
379

380
  /* The horizontal thickness, if using an orthographic projection.
381
   * If set to zero, we assume using a perspective projection.
382
   */
383
  double orthographic_width_ {C_NONE};
384

385
  /*
386
   * Cached camera-to-model matrix with column vectors of axes. The x-axis is
387
   * the vector between the camera_position_ and look_at_; the y-axis is the
388
   * cross product of the x-axis with the up_ vector, and the z-axis is the
389
   * cross product of the x and y axes.
390
   */
391
  std::array<double, 9> camera_to_model_;
392
};
393

394
class ProjectionRay;
395

396
/**
397
 * \class WireframeRayTracePlot
398
 * \brief Creates plots that are like colorful x-ray imaging
399
 *
400
 * WireframeRayTracePlot is a specialized form of RayTracePlot designed for
401
 * creating projection plots. This involves tracing rays from a camera through
402
 * the problem geometry and rendering the results based on depth of penetration
403
 * through materials or cells and their colors.
404
 */
405
class WireframeRayTracePlot : public RayTracePlot {
406

407
  friend class ProjectionRay;
408

409
public:
410
  WireframeRayTracePlot(pugi::xml_node plot);
411

412
  ImageData create_image() const override;
413
  void create_output() const override;
414
  void print_info() const override;
415

416
private:
417
  void set_opacities(pugi::xml_node node);
418
  void set_wireframe_thickness(pugi::xml_node node);
419
  void set_wireframe_ids(pugi::xml_node node);
420
  void set_wireframe_color(pugi::xml_node node);
421

422
  /* Checks if a vector of two TrackSegments is equivalent. We define this
423
   * to mean not having matching intersection lengths, but rather having
424
   * a matching sequence of surface/cell/material intersections.
425
   */
426
  struct TrackSegment;
427
  bool trackstack_equivalent(const vector<TrackSegment>& track1,
428
    const vector<TrackSegment>& track2) const;
429

430
  /* Used for drawing wireframe and colors. We record the list of
431
   * surface/cell/material intersections and the corresponding lengths as a ray
432
   * traverses the geometry, then color by iterating in reverse.
433
   */
434
  struct TrackSegment {
435
    int id;        // material or cell ID (which is being colored)
436
    double length; // length of this track intersection
437

438
    /* Recording this allows us to draw edges on the wireframe. For instance
439
     * if two surfaces bound a single cell, it allows drawing that sharp edge
440
     * where the surfaces intersect.
441
     */
442
    int surface_index {-1}; // last surface index intersected in this segment
443
    TrackSegment(int id_a, double length_a, int surface_a)
2,144,680✔
444
      : id(id_a), length(length_a), surface_index(surface_a)
2,144,680✔
445
    {}
2,144,680✔
446
  };
447

448
  // which color IDs should be wireframed. If empty, all cells are wireframed.
449
  vector<int> wireframe_ids_;
450

451
  // Thickness of the wireframe lines. Can set to zero for no wireframe.
452
  int wireframe_thickness_ {1};
453

454
  RGBColor wireframe_color_ {BLACK}; // wireframe color
455
  vector<double> xs_; // macro cross section values for cell volume rendering
456
};
457

458
/**
459
 * \class SolidRayTracePlot
460
 * \brief Plots 3D objects as the eye might see them.
461
 *
462
 * Plots a geometry with single-scattered Phong lighting plus a diffuse lighting
463
 * contribution. The result is a physically reasonable, aesthetic 3D view of a
464
 * geometry.
465
 */
466
class SolidRayTracePlot : public RayTracePlot {
467
  friend class PhongRay;
468

469
public:
470
  SolidRayTracePlot() = default;
10✔
471

472
  SolidRayTracePlot(pugi::xml_node plot);
473

474
  ImageData create_image() const override;
475
  void create_output() const override;
476
  void print_info() const override;
477

478
  const std::unordered_set<int>& opaque_ids() const { return opaque_ids_; }
479
  std::unordered_set<int>& opaque_ids() { return opaque_ids_; }
20✔
480

481
  const Position& light_location() const { return light_location_; }
482
  Position& light_location() { return light_location_; }
20✔
483

484
  const double& diffuse_fraction() const { return diffuse_fraction_; }
485
  double& diffuse_fraction() { return diffuse_fraction_; }
20✔
486

487
private:
488
  void set_opaque_ids(pugi::xml_node node);
489
  void set_light_position(pugi::xml_node node);
490
  void set_diffuse_fraction(pugi::xml_node node);
491

492
  std::unordered_set<int> opaque_ids_;
493

494
  double diffuse_fraction_ {0.1};
495

496
  // By default, the light is at the camera unless otherwise specified.
497
  Position light_location_;
498
};
499

500
// Base class that implements ray tracing logic, not necessarily through
501
// defined regions of the geometry but also outside of it.
502
class Ray : public GeometryState {
503

504
public:
505
  Ray(Position r, Direction u) { init_from_r_u(r, u); }
3,200,960✔
506

507
  // Called at every surface intersection within the model
508
  virtual void on_intersection() = 0;
509

510
  /*
511
   * Traces the ray through the geometry, calling on_intersection
512
   * at every surface boundary.
513
   */
514
  void trace();
515

516
  // Stops the ray and exits tracing when called from on_intersection
517
  void stop() { stop_ = true; }
32,330✔
518

519
  // Sets the dist_ variable
520
  void compute_distance();
521

522
protected:
523
  // Records how far the ray has traveled
524
  double traversal_distance_ {0.0};
525

526
private:
527
  // Max intersections before we assume ray tracing is caught in an infinite
528
  // loop:
529
  static const int MAX_INTERSECTIONS = 1000000;
530

531
  bool hit_something_ {false};
532
  bool stop_ {false};
533

534
  unsigned event_counter_ {0};
535
};
536

537
class ProjectionRay : public Ray {
538
public:
539
  ProjectionRay(Position r, Direction u, const WireframeRayTracePlot& plot,
2,000,000✔
540
    vector<WireframeRayTracePlot::TrackSegment>& line_segments)
541
    : Ray(r, u), plot_(plot), line_segments_(line_segments)
2,000,000✔
542
  {}
2,000,000✔
543

544
  void on_intersection() override;
545

546
private:
547
  /* Store a reference to the plot object which is running this ray, in order
548
   * to access some of the plot settings which influence the behavior where
549
   * intersections are.
550
   */
551
  const WireframeRayTracePlot& plot_;
552

553
  /* The ray runs through the geometry, and records the lengths of ray segments
554
   * and cells they lie in along the way.
555
   */
556
  vector<WireframeRayTracePlot::TrackSegment>& line_segments_;
557
};
558

559
class PhongRay : public Ray {
560
public:
561
  PhongRay(Position r, Direction u, const SolidRayTracePlot& plot)
1,200,960✔
562
    : Ray(r, u), plot_(plot)
1,200,960✔
563
  {
564
    result_color_ = plot_.not_found_;
1,200,960✔
565
  }
1,200,960✔
566

567
  void on_intersection() override;
568

569
  const RGBColor& result_color() { return result_color_; }
1,200,960✔
570

571
private:
572
  const SolidRayTracePlot& plot_;
573

574
  /* After the ray is reflected, it is moving towards the
575
   * camera. It does that in order to see if the exposed surface
576
   * is shadowed by something else.
577
   */
578
  bool reflected_ {false};
579

580
  // Have to record the first hit ID, so that if the region
581
  // does get shadowed, we recall what its color should be
582
  // when tracing from the surface to the light.
583
  int orig_hit_id_ {-1};
584

585
  RGBColor result_color_;
586
};
587

588
//===============================================================================
589
// Non-member functions
590
//===============================================================================
591

592
/* Write a PPM image
593
 * filename - name of output file
594
 * data - image data to write
595
 */
596
void output_ppm(const std::string& filename, const ImageData& data);
597

598
#ifdef USE_LIBPNG
599
/* Write a PNG image
600
 * filename - name of output file
601
 * data - image data to write
602
 */
603
void output_png(const std::string& filename, const ImageData& data);
604
#endif
605

606
//! Initialize a voxel file
607
//! \param[in] id of an open hdf5 file
608
//! \param[in] dimensions of the voxel file (dx, dy, dz)
609
//! \param[out] dataspace pointer to voxel data
610
//! \param[out] dataset pointer to voxesl data
611
//! \param[out] pointer to memory space of voxel data
612
void voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset,
613
  hid_t* memspace);
614

615
//! Write a section of the voxel data to hdf5
616
//! \param[in] voxel slice
617
//! \param[out] dataspace pointer to voxel data
618
//! \param[out] dataset pointer to voxesl data
619
//! \param[out] pointer to data to write
620
void voxel_write_slice(
621
  int x, hid_t dspace, hid_t dset, hid_t memspace, void* buf);
622

623
//! Close voxel file entities
624
//! \param[in] data space to close
625
//! \param[in] dataset to close
626
//! \param[in] memory space to close
627
void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace);
628

629
//===============================================================================
630
// External functions
631
//===============================================================================
632

633
//! Read plot specifications from a plots.xml file
634
void read_plots_xml();
635

636
//! Read plot specifications from an XML Node
637
//! \param[in] XML node containing plot info
638
void read_plots_xml(pugi::xml_node root);
639

640
//! Clear memory
641
void free_memory_plot();
642

643
//! Create a randomly generated RGB color
644
//! \return RGBColor with random value
645
RGBColor random_color();
646

647
} // namespace openmc
648
#endif // OPENMC_PLOT_H
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc