CSGBase

CSGBase is the main class developers should interact with when implementing the generateCSG method for any mesh generator. This framework class acts as a container and driver for all methods necessary for creating a constructive solid geometry (CSG) representation such as generating surfaces, cells, and universes of the mesh generator under consideration.

commentnote

Throughout this documentation, csg_obj will be used in example code blocks to refer to a CSGBase instance.

Declaring that a mesh generator supports the generation of CSG

In order to call generateCSG, the setHasGenerateCSG method must be called on the mesh generator to declare that the method has been implemented.

InputParameters
TestCSGAxialSurfaceMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();
  // input parameter that is an existing mesh generator
  params.addRequiredParam<MeshGeneratorName>("input", "The input MeshGenerator.");
  // additional params for this specific mesh generator
  params.addRequiredParam<Real>("axial_height", "Axial height of the model.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)

How to implement the generateCSG routine

This section will describe the various components developers should implement into the generateCSG method for a given MeshGenerator. This method will return a unique pointer to the CSGBase object that was created or modified by the mesh generator in the generateCSG method.

Initialization

A new CSGBase object can be initialized with:

  auto csg_obj = std::make_unique<CSG::CSGBase>();
(moose/test/src/csg/ExampleCSGInfiniteSquareMeshGenerator.C)

Once initialized, surfaces, cells, and universes can be created and manipulated. The following sections explain in detail how to do this as a part of the generateCSG method.

Surfaces

Surfaces are used to define the spatial extent of the region of a CSGCell. To create a CSGSurface object, the surface constructor must be called directly to create a unique pointer. This pointer then has to be passed to the current CSGBase instance with addSurface which will then return a const reference to that generated surface (const & CSGSurface). The syntax to do this is as follows, where SurfaceType should be replaced with the specific type of surface being created (e.g., CSG::CSGPlane):


// the unique surface pointer is made first, creating the surface object
std::unique_ptr<CSG::CSGSurface> surf_ptr = std::make_unique<SurfaceType>(arguments);
// and then it is explicitly passed to this CSGBase instance, which holds the memory ownership for the object
const auto & surface = csg_obj->addSurface(std::move(surf_ptr));
commentnote:Adding surfaces to the CSGBase instance

Surfaces need to be added to the CSGBase instance with addSurface as described above. If this is not done and these surfaces are referenced in regions used to define cells within the CSGBase instance, an error will occur.

The CSG framework in MOOSE provides various classes for creating basic surfaces (see table below). Information about how to define new types of surfaces can be found in CSGSurface.

Surface TypeClassDescription
PlaneCSGPlanecreate a plane defined by 3 points or from coefficients a, b, c, and d for the equation ax + by + cz = d
SphereCSGSpherecreates a sphere of radius r at an optionally specified center point (default is (0, 0, 0))
CylinderCSGXCylindercreates a cylinder aligned with the x-axis at the specified center location (y, z)
CylinderCSGYCylindercreates a cylinder aligned with the y-axis at the specified center location (x, z)
CylinderCSGZCylindercreates a cylinder aligned with the z-axis at the specified center location (x, y)

Example:

    // create a plane using the coefficients for the equation of a plane
    // z plane equation: 0.0*x + 0.0*y + 1.0*z = (+/-)0.5 * axial_height
    std::unique_ptr<CSG::CSGSurface> surface_ptr =
        std::make_unique<CSG::CSGPlane>(default_surf_name, 0.0, 0.0, 1.0, coeffs[i]);
    auto & csg_plane = csg_obj->addSurface(std::move(surface_ptr));
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)
commentnote:Including Surface Types

In order to define a surface, the header file for that surface type must be included in the MeshGenerator.C file (i.e., #include "CSGPlane.h" to create planes).

The CSGSurface objects can then be accessed or updated with the following methods from CSGBase:

  • addSurface: add a unique pointer to a CSGSurface object to this CSGBase instance

  • getAllSurfaces: retrieve a list of const references to each CSGSurface object in the CSGBase instance

  • getSurfaceByName: retrieve a const reference to the CSGSurface of the specified name

  • renameSurface: change the name of the CSGSurface

Regions

A region is a space defined by boolean operations applied to surfaces and other regions. Half-space regions are defined as the positive and negative space separated by a surface. These regions can be unionized, intersected, or the complement taken to further define more complex regions. Series of operations can be defined using parentheses ( ) to indicate which operations to perform first. The types of operators available to define a CSGRegion using CSGSurface objects are:

OperatorDescriptionExample Use
+positive half-space+surf
-negative half-space-surf
&intersection-surfA & +surfB
|union-surfA | +surfB
~complement~(-surfA & +surfB)
&=update existing region with an intersectionregion1 &= -surfA
|=update existing region with a unionregion1 |= +surfB

The following is an example of using a combination of all operators to define the space outside a cylinder of a finite height that is topped with a half-sphere. Each of the half-spaces associated with each surface are shown in Figure 1. The cylinder and planes are then combined via intersection to form the region inside a finite cylinder, and the space above the top plane is intersected with the sphere to define a half sphere (Figure 2). These two regions are unionized as shown in Figure 3. The complement of the previous combination then defines the final region ~((-cylinder_surf & -top_plane & +bottom_plane) | (+top_plane & -sphere_surf)), as shown in blue in Figure 4.

Four different surfaces: an infinite cylinder (blue), a top plane (orange), a bottom plane (red), and a sphere (green)

Figure 1: Four different surfaces: an infinite cylinder (blue), a top plane (orange), a bottom plane (red), and a sphere (green)

Two separate regions both defined as *intersections* of *half-spaces*.

Figure 2: Two separate regions both defined as intersections of half-spaces.

One region defined by the *union* of two other regions.

Figure 3: One region defined by the union of two other regions.

A region defined as the *complement* of an existing region.

Figure 4: A region defined as the complement of an existing region.

Cells

A cell is an object defined by a region and a fill. To create any CSGCell, use the method createCell from CSGBase which will return a const reference to the CSGCell object that is created (const CSGCell &). At the time of calling createCell, a unique cell name, the cell region (CSGRegion), and an indicator of the fill must be provided. The CSGRegion is defined by boolean combinations of CSGSurfaces as described below. Four types of cell fills are currently supported: void, material, universe, and lattice. If creating a void cell, no fill has to be passed to the creation method. To create a cell with a material fill, simply provide it with a name of a material as a string. For a cell with a CSGUniverse fill, pass it a reference to the CSGUniverse. And for a CSGLattice fill, pass a reference to the CSGLattice. Some examples of creating the different types of cells are shown below:

    // create a void cell with name cname1 and defined by region reg1
    csg_obj->createCell(cname1, reg1);
(moose/unit/src/CSGBaseTest.C)
    // create a material-filled cell with name cname1, a fill with material matname,
    // and defined by region reg1
    csg_obj->createCell(cname1, "matname", reg1);
(moose/unit/src/CSGBaseTest.C)
    // create a universe-filled cell with name cname1, a fill of universe new_univ,
    // and defined by region reg1
    csg_obj->createCell(cname1, new_univ, reg1);
(moose/unit/src/CSGBaseTest.C)
    // create a lattice-filled cell with name cname1, a fill of lattice,
    // and defined by region reg1
    csg_obj->createCell(cname1, lattice, reg1);
(moose/unit/src/CSGBaseTest.C)
commentnote:Materials as Placeholders

A cell with a material fill is not connected to a MOOSE material definition at this time. The "material" is currently just a string to represent the name of a CSG material or other type of fill that is otherwise undefined.

The CSGCell objects can then be accessed or updated with the following methods from CSGBase:

  • getAllCells: retrieve a list of const references to each CSGCell object in the CSGBase instance

  • getCellByName: retrieve a const reference to the CSGCell object of the specified name

  • renameCell: change the name of the CSGCell object

  • updateCellRegion: change the region of the cell; if used, all CSGSurface objects used to define the new CSGRegion must also be a part of the current CSGBase

Universes

A universe is a collection of cells and is created by calling createUniverse from CSGBase which will return a const reference to the CSGUniverse object (const CSGUniverse &). A CSGUniverse can be initialized as an empty universe, or by passing a vector of shared pointers to CSGCell objects. Any CSGUniverse object can be renamed (including the root universe) with renameUniverse.

The CSGUniverse objects can then be accessed or updated with the following methods from CSGBase:

  • getAllUniverses: retrieve a list of const references to each CSGUniverse object in the CSGBase instance

  • getUniverseByName: retrieve a const reference to the CSGUniverse of the specified name

  • renameUniverse: change the name of the CSGUniverse

Examples:

    auto new_univ = csg_obj->createUniverse("new_univ");
(moose/unit/src/CSGBaseTest.C)
    // create a list of cells to be added to the universe
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    auto & univ = csg_obj->createUniverse("louise", cells);
(moose/unit/src/CSGBaseTest.C)

Root Universe

All universes in a model should be able to be traced back, through the hierarchical tree of cells and universes, to a singular overarching universe known as the root universe. Because universes are a collection of cells and cells can be filled with universe, a tree of universes can be constructed such that the root universe contains the collection of all cells in the model. When a CSGBase object is first initialized, a root CSGUniverse called ROOT_UNIVERSE is created by default. Every CSGCell that is created will be added to the root universe unless otherwise specified (as described below). The root universe exists by default, and which universe is set as the root cannot be changed, except when joining CSGBase objects, as described below. However, the name of the root universe can be updated and cells can be manually added or removed using the same methods described above.

Methods available for managing the root universe:

  • getRootUniverse: returns a const reference to the root universe of the CSGBase instance

  • renameRootUniverse: change the name of the root universe

Adding or Removing Cells

There are multiple ways in which cells can be added to a universe:

  1. At the time of universe creation, a vector of references to CSGCell objects can be passed into createUniverse (as described above). Example:

    // create a list of cells to be added to the universe
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    auto & univ = csg_obj->createUniverse("louise", cells);
(moose/unit/src/CSGBaseTest.C)
  1. When a CSGCell is created with createCell, a pointer to a CSGUniverse can be passed as the final argument to indicate that the cell will be created and added directly to that specified universe. In this case, the cell will not be added to the root universe. A cell that has a universe fill type cannot be added to the same universe that is being used for the fill. For example, the two snippets below come from the same file where a new universe is initialized and passed by reference to the cell when it is created:

  // make a new universe to which the new cells can be added at time of creation
  auto & add_to_univ = csg_obj->createUniverse("add_univ");
(moose/unit/src/CSGBaseTest.C)
    // create a cell and add to different universe, not root
    std::string cname2 = "void_cell2";
    csg_obj->createCell(cname2, reg1, &add_to_univ);
(moose/unit/src/CSGBaseTest.C)
  1. A cell or list of cells can be added to an existing universe with the addCellToUniverse and addCellsToUniverse methods. In this case, if a CSGCell exists in another CSGUniverse (such as the root universe), it will not be removed when being added to another (i.e. if the same behavior as option 2 above is desired, the cell will have to be manually removed from the root universe, as described below). The following is an example where the list of cells is collected first and then added at one time to the existing universe, but this could also be accomplished by using addCellToUniverse in a for-loop after each cell is initially created.

  // add a list of cells to an existing universe
  {
    std::vector<std::reference_wrapper<const CSG::CSGCell>> cells = {c1, c2};
    csg_obj->addCellsToUniverse(univ, cells);
(moose/unit/src/CSGBaseTest.C)

Cells can also be removed from a universe in the same way as method 3 above by using the removeCellFromUniverse and removeCellsFromUniverse methods. An example is shown above where the cells are removed from the root universe after they are added to the new universe. Doing this in multiple steps has the same outcome as that of method 2 for adding cells to a universe at the time of cell creation.

commentnote:Maintaining Connectivity

When adding and removing cells to/from universes, it is important to maintain the connectivity of all universes meaning all universes should be nested under the root universe at the end of the generation process, in order to have a consistent model.

Lattices

A CSGLattice is defined as a patterned arrangement of CSGUniverse objects and an "outer" to fill the space around lattice elements. To create any type of lattice, the lattice constructor must be called directly to create a unique pointer. This pointer then has to be passed to the current CSGBase instance with addLattice (optionally specifying the lattice type) which will then return a const reference to that generated lattice object (const & CSGLattice or const & LatticeType). At the time that addLattice is called to add the lattice to the base instance, any CSGUniverse objects associated with the lattice must also be in the CSGBase instance already. The syntax for creating the pointer and adding it is shown below, where LatticeType should be replaced with the specific type of the lattice (e.g., CSGCartesianLattice or CSGHexagonalLattice).


// the unique lattice pointer is made first, creating the lattice object of the specified type
std::unique_ptr<LatticeType> lat_ptr = std::make_unique<LatticeType>(arguments);
// and then it is explicitly passed to this CSGBase instance, which will keep the memory ownership for the object
const auto & lattice = csg_obj->addLattice<LatticeType>(std::move(lat_ptr));

An example of creating a Cartesian lattice is shown below:

    std::unique_ptr<CSG::CSGCartesianLattice> lat_ptr =
        std::make_unique<CSG::CSGCartesianLattice>(lat_name, _pitch, universe_pattern);
    csg_obj->addLattice<CSG::CSGCartesianLattice>(std::move(lat_ptr));
(moose/test/src/csg/TestCSGLatticeMeshGenerator.C)
commentnote:Specifying the LatticeType

It is _highly_ recommended that the specific LatticeType of the lattice object always be specified when calling addLattice or getLatticeByName. If it is not specified, it will default to setting the type as the abstract type CSGLattice. If this is the case, no methods or data that is specific to the actual lattice type will be callable on the lattice object, unless a reference cast is used.

The CSGBase class provides support for two types of 2D lattice types (Cartesian and regular hexagonal), but any custom lattice type can also be defined by following the information in CSGLattice. It is assumed that both the Cartesian and hexagonal lattice types are in the plane (having a normal). Three types of outer fill are supported: void (default), material (an std::string name), and CSGUniverse. For both lattice types, the lattice can be initialized minimally with a name and pitch (the flat-to-flat size of a lattice element). The pattern of universes can also be set at the time of initialization or updated later using the setLatticeUniverses method. And similarly, the outer fill (either a CSGUniverse object or an std::string representing the name of a material) can be set at the time of initialization or set later with the setLatticeOuter method; if the outer is not set, it is assumed to be VOID. Some examples of initializing a lattice with the various options are shown below.

    // initialize without universe map, outer is void, and pitch=1.0
    auto cart_lattice = CSGCartesianLattice("cartlat", 1.0);
(moose/unit/src/CSGLatticeTest.C)
    // initialize with universe map and set outer fill to be a universe
    auto cart_lattice = CSGCartesianLattice("cartlat", 1.0, univ_map, outer_univ);
(moose/unit/src/CSGLatticeTest.C)
    // initialize without universe map but set outer to a material
    auto cart_lattice = CSGCartesianLattice("cartlat", 1.0, "outer_mat");
(moose/unit/src/CSGLatticeTest.C)
commentnote:2D vs. 3D Lattices

The CSGBase class supports only the creation of 2D lattices. A "3D" lattice can be created by filling multiple 3D cells with 2D lattices and arranging them in layers to form the desired 3D structure.

The CSGLattice objects can be accessed or updated with the following methods from CSGBase:

  • setLatticeUniverses: sets the vector of vectors of CSGUniverse objects as the lattice layout.

  • setUniverseAtLatticeIndex: add a CSGUniverse to the lattice at the specified location index (replaces the existing universe).

  • setLatticeOuter: sets the CSGUniverse (for a CSGUniverse outer) or the std::string name (for a material outer) as the outer fill.

  • resetLatticeOuter: resets the outer fill to void for the lattice.

  • renameLattice: change the name of the CSGLattice object.

  • getAllLattices: retrieve a vector of const references to each CSGLattice object in the CSGBase instance.

  • getLatticeByName: retrieve a const reference to the lattice object of the specified name.

Lattice Indexing

Both the Cartesian and hexagonal lattice types follow a row-column indexing scheme for the location of universes in the lattice.

For Cartesian lattices, there can be any number of rows and columns, but each row must be the same length. The indexing starts at the top left corner of the lattice with element . An example of the indices for a Cartesian lattice is shown in Figure 5.

Example of a $2 \times 3$ Cartesian lattice and the corresponding location indices.

Figure 5: Example of a Cartesian lattice and the corresponding location indices.

For hexagonal lattices, the lattice is assumed to be x-oriented and also uses the row-column indexing scheme, with element being the top row, leftmost element. The length of the rows is expected to be consistent with the size of the lattice (i.e., the number of rings), which is verified when the universes are set. An example of the indices for a 3-ring hexagonal lattice is shown in Figure 6.

Example of a 3-ring hexagonal lattice that has the location indices labeled in the $(i, j)$ form.

Figure 6: Example of a 3-ring hexagonal lattice that has the location indices labeled in the form.

Convenience methods are provided for CSGHexagonalLattice objects to convert between the required indices and a ring-based indexing scheme. In the ring-based scheme, the outermost ring is the 0th ring, and the right-most element in the ring is the 0th position of the ring with indices increasing counter-clockwise around the ring. For the 3-ring lattice above in Figure 6, the corresponding indices would be as shown in Figure 7.

Example of a 3-ring hexagonal lattice that has the location indices labeled in the $(r, p)$ form.

Figure 7: Example of a 3-ring hexagonal lattice that has the location indices labeled in the form.

For any lattice, the index of a universe in the lattice can be retrieved from the index by calling the getRingIndexFromRowIndex method. And similarly, if the index form is known, the corresponding index can be retrieved using the getRowIndexFromRingIndex method. It is important to note that while these convenience methods exist to convert between the two indexing schemes, the CSGUniverse objects in the lattice can only be accessed using the index.

Defining the Universe Layout

As mentioned above, the layout of the CSGUniverse objects for the lattice can be set at the time of initialization or set/updated later. At the time that the universes are set, the dimensionality of the lattice is determined (i.e., the number of rows, columns, or rings for the lattice). If the dimensionality should need to be changed, a new complete universe arrangement can be set to overwrite the previous arrangement using setLatticeUniverses. Anytime the universe layout is set or changed, the dimensionality will be validated to ensure it compatible with the lattice type. To replace a single element of the lattice, the setUniverseAtLatticeIndex method can be used by providing the element location in form. In order to use this method, the full set of universes must have already been defined, either during the lattice initialization or with setLatticeUniverses.

    // initialize a lattice without universes and then add universes with setLatticeUniverses
    std::unique_ptr<CSGCartesianLattice> new_lat_ptr =
        std::make_unique<CSGCartesianLattice>("new_lattice", 1.0);
    const auto & lat = csg_obj->addLattice(std::move(new_lat_ptr));
    std::vector<std::vector<std::reference_wrapper<const CSGUniverse>>> new_univs = {{univ1},
                                                                                     {univ1}};
    csg_obj->setLatticeUniverses(lat, new_univs);
(moose/unit/src/CSGBaseTest.C)
    csg_obj->setUniverseAtLatticeIndex(lat, univ2, std::make_pair<int, int>(1, 0));
(moose/unit/src/CSGBaseTest.C)
commentnote:Building the Lattice Layout Incrementally

The setUniverseAtLatticeIndex method is not meant to be used to change a lattice's dimensions by building the lattice element-by-element because the index supplied would be considered out of range in this context. The dimensionality of the lattice is determined when setLatticeUniverses is called. Therefore, to build a lattice incrementally, the recommendation is to build up a vector of vectors of universes incrementally and then call setLatticeUniverses one time. From there, setUniverseAtLatticeIndex can be called to replace an existing universe in the lattice.

Updating Existing CSGBase Objects

An empty CSGBase object can be initialized on its own in each generateCSG method for each mesh generator. However, in most cases, it is necessary to update an existing CSGBase object from a previous MeshGenerator or join multiple CSGBase together such that only one CSGBase object is ultimately produced at the end of the mesh/CSG generation process. There are two main ways to handle this: passing and joining.

Passing between Mesh Generators

CSGBase objects from other mesh generators can be accessed through methods that parallel those available for accessing other MeshGenerator objects. For all methods listed below, a unique pointer to the CSGBase object(s) created by generateCSG for the specified MeshGenerator names are returned.

  • getCSGBase: get the CSGBase object given a parameter name represented as a std::string that stores the mesh generator name

  • getCSGBases: get the CSGBase objects given a parameter name represented as a std::string that stores a list of mesh generator names

  • getCSGBaseByName: get the CSGBase object given a MeshGeneratorName

  • getCSGBasesByName: get all CSGBase objects given a list of MeshGeneratorNames

These functions should be called from the constructor of the MeshGenerator, so that the MeshGenerator system can properly define the dependency tree of all mesh generators in the input file. The returned CSGBase pointers can be stored in a member variable and updated in the generateCSG() method in order to make any changes to the CSGBase object.

For example, the following member variable stores the pointer to the CSGBase object that is generated by an input mesh generator:

  /// Holds the generated CSGBase object
  std::unique_ptr<CSG::CSGBase> * _build_csg;
(moose/test/include/csg/TestCSGAxialSurfaceMeshGenerator.h)

This variable is initialized in the constructor as:

  _build_csg = &getCSGBase("input");
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)

Finally, in the generateCSG() method, std::move is called on the member variable to transfer ownership to the current mesh generator

  // get the existing CSGBase associated with the input mesh generator
  // this is the CSGBase object that will be updated
  std::unique_ptr<CSG::CSGBase> csg_obj = std::move(*_build_csg);
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)
commentnote:Accessing other MeshGenerator objects by name

A MeshGenerator object(s) can be passed to another mesh generator as input by providing InputParameters of type MeshGeneratorName. See the ExampleAxialSurfaceMeshGenerator implementation below for an example of this.

Joining Bases

When two or more existing CSGBase objects need to be combined to continue to use and update, the joinOtherBase method should be used. This method is called from another CSGBase and at a minimum takes a different existing CSGBase object as input. There are 3 different behaviors for joining bases that are supported depending on the additional arguments that are passed:

  1. No additional arguments: All cells that are in the root universe of the incoming CSGBase object will be added to the existing root universe of the current base object, and the root universe from the incoming base will no longer exist.

  // Case 1: Create two CSGBase objects to join together into a single root
  // CSGBase 1: only one cell containing a lattice of one universe, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  // create a lattice of one universe
  auto & univ_in_lat = base1->createUniverse("univ_in_lat");
  std::vector<std::vector<std::reference_wrapper<const CSGUniverse>>> univs = {{univ_in_lat}};
  std::unique_ptr<CSGCartesianLattice> lat_ptr =
      std::make_unique<CSGCartesianLattice>("lat1", 1.0, univs);
  const auto & lat = base1->addLattice(std::move(lat_ptr));
  // create cell containing lattice
  auto & c1 = base1->createCell("c1", lat, +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: two universes will remain
  // base1 ROOT_UNIVERSE will gain all cells from base2 ROOT_UNIVERSE
  // base2 ROOT_UNIVERSE will not exist as a separate universe
  // the "extra_univ" from base2 and "univ_in_lat" from base1 will remain separate universes
  base1->joinOtherBase(std::move(base2));
(moose/unit/src/CSGBaseTest.C)
  1. One new root universe name (new_root_name_join): All cells in the root universe of the incoming base will be used to create a new universe of the name specified by the new_root_name_join parameter. These cells will not be added to the existing root universe, which will remain unchanged. This new universe will be added as a new non-root universe in the existing base object. This newly created universe will not be connected to the root universe of the existing CSGBase object by default.

  // Case 2: Create two CSGBase objects to join together but keep incoming root separate
  // CSGBase 1: only one cell containing a lattice of one universe, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  // create a lattice of one universe
  auto & univ_in_lat = base1->createUniverse("univ_in_lat");
  std::vector<std::vector<std::reference_wrapper<const CSG::CSGUniverse>>> univs = {{univ_in_lat}};
  std::unique_ptr<CSGCartesianLattice> lat_ptr =
      std::make_unique<CSGCartesianLattice>("lat1", 1.0, univs);
  const auto & lat = base1->addLattice(std::move(lat_ptr));
  // create cell containing lattice
  auto & c1 = base1->createCell("c1", lat, +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: 4 universes will remain
  // base1 ROOT_UNIVERSE and univ_in_lat will remain untouched
  // all cells from ROOT_UNIVERSE in base2 create new universe called "new_univ"
  // the "extra_univ" from base2 and "univ_in_lat" from base1 will remain separate universes
  std::string new_root_name = "new_univ";
  base1->joinOtherBase(std::move(base2), new_root_name);
(moose/unit/src/CSGBaseTest.C)
  1. Two new root universe names (new_root_name_base and new_root_name_join): The cells in the root universe of the current CSGBase object will be used to create a new non-root universe of the name specified by the new_root_name_base parameter, and the cells in the root universe of the incoming CSGBase object will be used to create a separate non-root universe of the name specified by the new_root_name_join parameter. At the end of this join method, the root universe of the current base object will be empty and neither of the two new non-root universes will be connected to the root universe by default.

  // Case 3: Create two CSGBase objects to join together with each root becoming a new universe
  // CSGBase 1: only one cell containing a lattice of one universe, which lives in the ROOT_UNIVERSE
  std::unique_ptr<CSGBase> base1 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr1 = std::make_unique<CSG::CSGSphere>("s1", 1.0);
  const auto & surf1 = base1->addSurface(std::move(surf_ptr1));
  // create a lattice of one universe
  auto & univ_in_lat = base1->createUniverse("univ_in_lat");
  std::vector<std::vector<std::reference_wrapper<const CSGUniverse>>> univs = {{univ_in_lat}};
  std::unique_ptr<CSGCartesianLattice> lat_ptr =
      std::make_unique<CSGCartesianLattice>("lat1", 1.0, univs);
  const auto & lat = base1->addLattice(std::move(lat_ptr));
  // create cell containing lattice
  auto & c1 = base1->createCell("c1", lat, +surf1);
  // CSGBase 2: two total unverses (ROOT_UNIVERSE and extra_univ) with a cell in each
  std::unique_ptr<CSGBase> base2 = std::make_unique<CSG::CSGBase>();
  std::unique_ptr<CSG::CSGSphere> surf_ptr2 = std::make_unique<CSG::CSGSphere>("s2", 1.0);
  const auto & surf2 = base2->addSurface(std::move(surf_ptr2));
  auto & c2 = base2->createCell("c2", +surf2);
  auto & extra_univ = base2->createUniverse("extra_univ");
  auto & c3 = base2->createCell("c3", -surf2, &extra_univ);

  // Joining: 5 universes will remain
  // all cells from base1 ROOT_UNIVERSE will be moved to a new universe called "new_univ1"
  // all cells from base2 ROOT_UNIVERSE will be moved to a new universe called "new_univ2"
  // base1 ROOT_UNIVERSE will be empty
  // the "extra_univ" from base2 and "univ_in_lat" from base1 will remain separate universes
  std::string new_name1 = "new_univ1";
  std::string new_name2 = "new_univ2";
  base1->joinOtherBase(std::move(base2), new_name1, new_name2);
(moose/unit/src/CSGBaseTest.C)

For all of these join methods, any non-root universes will remain unchanged and simply added to the list of universes for the current CSGBase object. Similarly, all incoming cells and surfaces are added alongside existing cells and surfaces.

commentnote:Object Naming Uniqueness

It is very important when using the joinOtherBase method that all CSGSurfaces, CSGCells, and CSGSurfaces are uniquely named so that errors are not encountered when combining sets of objects. An error will be produced during the join process if an object of the same type and name already exists. See recommendations for naming below.

All Constructive Solid Geometry (CSG) methods related to creating or changing a CSG object must be called through CSGBase. Calls that retrieve information only but do not manipulate an object (such as getName methods) can be called on the object directly. For example, if a cell were to be created, the current name and region could be retrieved directly from the CSGCell object, but if the name or region needed to be changed, that would need to be handled through CSGBase.

This ensures proper accounting of all CSG-related objects in the CSGBase instance. Consult the Doxygen documentation for information on all object-specific methods.

Object Naming Recommendations

For each new CSG element (CSGSurface, CSGCell, CSGUniverse, and CSGLattice) that is created, a unique name identifier (of type std::string) must be provided (name parameter for all creation methods). A recommended best practice is to include the mesh generator name (which can be accessed with this->getName() in any MeshGenerator class) as a part of that object name. This name is used as the unique identifier within the CSGBase instance. Methods for renaming objects are available as described in the above sections to help prevent issues and errors.

Example Implementations

Provided here are example implementations of the generateCSG method for three simple MeshGenerator types. The first mesh generator creates an infinite rectangular prism given an input parameter for side_length. The code snippets provided here correspond to the .C file.

InputParameters
ExampleCSGInfiniteSquareMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();

  params.addRequiredParam<Real>("side_length", "Side length of infinite square.");
  params.addParam<MeshGeneratorName>(
      "fill", "optional input lattice mesh generator to fill generated cell with.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}

ExampleCSGInfiniteSquareMeshGenerator::ExampleCSGInfiniteSquareMeshGenerator(
    const InputParameters & params)
  : MeshGenerator(params),
    _side_length(getParam<Real>("side_length")),
    _input_fill_name(isParamValid("fill") ? getParam<MeshGeneratorName>("fill") : ""),
    _has_fill(isParamValid("fill"))
{
  if (_has_fill)
  {
    _input_fill_mg_ptr = &getMesh("fill");
    _input_fill_csg = &getCSGBase("fill");
  }
}

std::unique_ptr<MeshBase>
ExampleCSGInfiniteSquareMeshGenerator::generate()
{
  auto null_mesh = nullptr;
  return null_mesh;
}

std::unique_ptr<CSG::CSGBase>
ExampleCSGInfiniteSquareMeshGenerator::generateCSG()
{
  // name of the current mesh generator to use for naming generated objects
  auto mg_name = this->name();

  // initialize a CSGBase object
  auto csg_obj = std::make_unique<CSG::CSGBase>();

  // Add surfaces and halfspaces corresponding to 4 planes of infinite square
  std::vector<std::vector<Point>> points_on_planes{{Point(1. * _side_length / 2., 0., 0.),
                                                    Point(1. * _side_length / 2., 1., 0.),
                                                    Point(1. * _side_length / 2., 0., 1.)},
                                                   {Point(-1. * _side_length / 2., 0., 0.),
                                                    Point(-1. * _side_length / 2., 1., 0.),
                                                    Point(-1. * _side_length / 2., 0., 1.)},
                                                   {Point(0., 1. * _side_length / 2., 0.),
                                                    Point(1., 1. * _side_length / 2., 0.),
                                                    Point(0., 1. * _side_length / 2., 1.)},
                                                   {Point(0., -1. * _side_length / 2., 0.),
                                                    Point(1., -1. * _side_length / 2., 0.),
                                                    Point(0., -1. * _side_length / 2., 1.)}};
  std::vector<std::string> surf_names{"plus_x", "minus_x", "plus_y", "minus_y"};

  // initialize cell region to be updated
  CSG::CSGRegion region;

  // set the center of the prism to be used for determining half-spaces
  const auto centroid = Point(0, 0, 0);

  for (unsigned int i = 0; i < points_on_planes.size(); ++i)
  {
    // object name includes the mesh generator name for uniqueness
    const auto surf_name = mg_name + "_surf_" + surf_names[i];
    // create the plane for one face of the prism
    std::unique_ptr<CSG::CSGSurface> plane_ptr = std::make_unique<CSG::CSGPlane>(
        surf_name, points_on_planes[i][0], points_on_planes[i][1], points_on_planes[i][2]);
    auto & csg_plane = csg_obj->addSurface(std::move(plane_ptr));
    // determine where the plane is in relation to the centroid to be able to set the half-space
    const auto region_direction = csg_plane.getHalfspaceFromPoint(centroid);
    // half-space is either positive (+plane_ptr) or negative (-plane_ptr)
    // depending on the direction to the centroid
    auto halfspace =
        ((region_direction == CSG::CSGSurface::Halfspace::POSITIVE) ? +csg_plane : -csg_plane);
    // check if this is the first half-space to be added to the region,
    // if not, update the existing region with the intersection of the regions (&=)
    if (region.getRegionType() == CSG::CSGRegion::RegionType::EMPTY)
      region = halfspace;
    else
      region &= halfspace;
  }

  // create the cell defined by the surfaces and region just created
  const auto cell_name = mg_name + "_square_cell";
  // determine fill: either from input fill mesh generator or default material
  if (_has_fill)
  {
    // join the fill CSGBase into the current CSGBase & use the lattice as the fill
    csg_obj->joinOtherBase(std::move(*_input_fill_csg));
    // assume input MG is a lattice type for sake of this example/test
    const CSG::CSGLattice & lattice = csg_obj->getLatticeByName(_input_fill_name + "_lattice");
    csg_obj->createCell(cell_name, lattice, region);
  }
  else // default material fill
  {
    const auto material_name = "square_material";
    csg_obj->createCell(cell_name, material_name, region);
  }

  return csg_obj;
}
(moose/test/src/csg/ExampleCSGInfiniteSquareMeshGenerator.C)

The next example mesh generator builds on the infinite prism example above by taking a MeshGeneratorName for an existing ExampleCSGInfiniteSquareMeshGenerator as input and adds planes to create a finite rectangular prism.

InputParameters
TestCSGAxialSurfaceMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();
  // input parameter that is an existing mesh generator
  params.addRequiredParam<MeshGeneratorName>("input", "The input MeshGenerator.");
  // additional params for this specific mesh generator
  params.addRequiredParam<Real>("axial_height", "Axial height of the model.");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}

TestCSGAxialSurfaceMeshGenerator::TestCSGAxialSurfaceMeshGenerator(const InputParameters & params)
  : MeshGenerator(params),
    _mesh_ptr(getMesh("input")),
    _axial_height(getParam<Real>("axial_height"))
{
  _build_csg = &getCSGBase("input");
}

std::unique_ptr<MeshBase>
TestCSGAxialSurfaceMeshGenerator::generate()
{
  auto null_mesh = nullptr;
  return null_mesh;
}

std::unique_ptr<CSG::CSGBase>
TestCSGAxialSurfaceMeshGenerator::generateCSG()
{
  // get the existing CSGBase associated with the input mesh generator
  // this is the CSGBase object that will be updated
  std::unique_ptr<CSG::CSGBase> csg_obj = std::move(*_build_csg);

  // get the names of the current mesh generator and the input mesh generator
  // so that unique object naming can be enforced
  auto mg_name = this->name();
  auto inp_name = getParam<MeshGeneratorName>("input");

  // get the expected existing cell
  const auto cell_name = inp_name + "_square_cell";
  const auto & csg_cell = csg_obj->getCellByName(cell_name);

  // get the existing cell region to update
  auto cell_region = csg_cell.getRegion();

  // centroid used to determine direction for half-space
  const auto centroid = Point(0, 0, 0);

  // setting a default surface name purely for testing purposes
  const auto default_surf_name = "default_surf";

  // Add surfaces and halfspaces corresponding to top and bottom axial planes
  std::vector<std::string> surf_names{"plus_z", "minus_z"};
  std::vector<Real> coeffs{0.5 * _axial_height, -0.5 * _axial_height};
  for (unsigned int i = 0; i < coeffs.size(); ++i)
  {
    // create a plane using the coefficients for the equation of a plane
    // z plane equation: 0.0*x + 0.0*y + 1.0*z = (+/-)0.5 * axial_height
    std::unique_ptr<CSG::CSGSurface> surface_ptr =
        std::make_unique<CSG::CSGPlane>(default_surf_name, 0.0, 0.0, 1.0, coeffs[i]);
    auto & csg_plane = csg_obj->addSurface(std::move(surface_ptr));

    // Rename surface so that it has a unique surface name based on the mesh generator
    const auto surf_name = mg_name + "_surf_" + surf_names[i];
    csg_obj->renameSurface(csg_plane, surf_name);

    // determine the half-space to add as an updated intersection
    const auto region_direction = csg_plane.getHalfspaceFromPoint(centroid);
    auto halfspace =
        ((region_direction == CSG::CSGSurface::Halfspace::POSITIVE) ? +csg_plane : -csg_plane);

    // update the existing region with a half-space
    cell_region &= halfspace;
  }

  // set the new region for the existing cell
  csg_obj->updateCellRegion(csg_cell, cell_region);

  // Rename cell as it now defines a box region instead of an infinite square region
  csg_obj->renameCell(csg_cell, mg_name + "_box_cell");

  return csg_obj;
}
(moose/test/src/csg/TestCSGAxialSurfaceMeshGenerator.C)

If the above methods were to be used, the following input would generate the corresponding JavaScript Object Notation (JSON) output below.

Example Input:

[Mesh<<<{"href": "../../syntax/Mesh/index.html"}>>>]
  [inf_square]
    type = ExampleCSGInfiniteSquareMeshGenerator
    side_length = 4
  []
  [cube]
    type = TestCSGAxialSurfaceMeshGenerator
    input = inf_square
    axial_height = 5
  []
[]
(moose/test/tests/csg/csg_only_chained.i)

Example Output:

{
  "cells": {
    "cube_box_cell": {
      "fill": "square_material",
      "filltype": "CSG_MATERIAL",
      "region":
          "(+inf_square_surf_plus_x & -inf_square_surf_minus_x & -inf_square_surf_plus_y & +inf_square_surf_minus_y & -cube_surf_plus_z & +cube_surf_minus_z)"
    }
  },
  "surfaces": {
    "inf_square_surf_minus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_minus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "cube_surf_minus_z": {
      "coefficients": {
        "a": 0.0,
        "b": 0.0,
        "c": 1.0,
        "d": -2.5
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_plus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "inf_square_surf_plus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "cube_surf_plus_z": {
      "coefficients": {
        "a": 0.0,
        "b": 0.0,
        "c": 1.0,
        "d": 2.5
      },
      "type": "CSG::CSGPlane"
    }
  },
  "universes": {
    "ROOT_UNIVERSE": {
      "cells": [
        "cube_box_cell"
      ],
      "root": true
    }
  }
}
(moose/test/tests/csg/gold/csg_only_chained_out_csg.json)

A third example implementation shows the construction of a 2D lattice of universes, using the ExampleCSGInfiniteSquareMeshGenerator as input.

InputParameters
TestCSGLatticeMeshGenerator::validParams()
{
  InputParameters params = MeshGenerator::validParams();
  // input parameter that is an existing mesh generator
  params.addRequiredParam<std::vector<MeshGeneratorName>>(
      "inputs",
      "The MeshGenerators that form the components of the lattice. Order of inputs corresponds to "
      "the associated integer ID for the pattern (i.e., 0 for first input, 1 for second input, "
      "etc.)");
  params.addRequiredParam<std::string>(
      "lattice_type", "The type of lattice to create. Options are 'cartesian' and 'hexagonal'.");
  params.addRequiredParam<Real>("pitch",
                                "The pitch (flat-to-flat distance) of each lattice element.");
  params.addRequiredParam<std::vector<std::vector<unsigned int>>>(
      "pattern",
      "A double-indexed array starting with the upper-left corner where the index"
      "represents the index of the mesh/CSG generator in the 'inputs' vector");
  // Declare that this generator has a generateCSG method
  MeshGenerator::setHasGenerateCSG(params);
  return params;
}

TestCSGLatticeMeshGenerator::TestCSGLatticeMeshGenerator(const InputParameters & params)
  : MeshGenerator(params),
    _lattice_type(getParam<std::string>("lattice_type")),
    _pitch(getParam<Real>("pitch")),
    _input_names(getParam<std::vector<MeshGeneratorName>>("inputs")),
    _mesh_ptrs(getMeshes("inputs")),
    _pattern(getParam<std::vector<std::vector<unsigned int>>>("pattern"))
{
  for (auto inp : _input_names)
    _input_csgs.push_back(&getCSGBaseByName(inp));
}

std::unique_ptr<MeshBase>
TestCSGLatticeMeshGenerator::generate()
{
  auto null_mesh = nullptr;
  return null_mesh;
}

std::unique_ptr<CSG::CSGBase>
TestCSGLatticeMeshGenerator::generateCSG()
{
  // create a new CSGBase object to build the lattice in
  auto csg_obj = std::make_unique<CSG::CSGBase>();
  // get the name of the current mesh generator
  auto mg_name = this->name();

  // join each input CSGBase into the new CSGBase as a unique universe
  std::unordered_map<unsigned int, std::string> univ_id_names;
  for (const auto i : index_range(_input_names))
  {
    std::string join_name = _input_names[i] + "_univ";
    csg_obj->joinOtherBase(std::move(*_input_csgs[i]), join_name);
    univ_id_names[i] = join_name;
  }

  // build the universe pattern for the lattice using the input pattern
  std::vector<std::vector<std::reference_wrapper<const CSG::CSGUniverse>>> universe_pattern;
  for (const auto & row : _pattern)
  {
    std::vector<std::reference_wrapper<const CSG::CSGUniverse>> universe_row;
    for (const auto univ_id : row)
    {
      const auto & univ = csg_obj->getUniverseByName(univ_id_names[univ_id]);
      universe_row.push_back(univ);
    }
    universe_pattern.push_back(universe_row);
  }

  // create the lattice based on the specified type
  std::string lat_name = mg_name + "_lattice";
  if (_lattice_type == "cartesian")
  {
    std::unique_ptr<CSG::CSGCartesianLattice> lat_ptr =
        std::make_unique<CSG::CSGCartesianLattice>(lat_name, _pitch, universe_pattern);
    csg_obj->addLattice<CSG::CSGCartesianLattice>(std::move(lat_ptr));
  }
  else if (_lattice_type == "hexagonal")
  {
    std::unique_ptr<CSG::CSGHexagonalLattice> lat_ptr =
        std::make_unique<CSG::CSGHexagonalLattice>(lat_name, _pitch, universe_pattern);
    csg_obj->addLattice<CSG::CSGHexagonalLattice>(std::move(lat_ptr));
  }
  return csg_obj;
}
(moose/test/src/csg/TestCSGLatticeMeshGenerator.C)

For this example, the following input would generate the corresponding JSON output below.

Example Input:

[Mesh<<<{"href": "../../syntax/Mesh/index.html"}>>>]
  [sq1]
    type = ExampleCSGInfiniteSquareMeshGenerator
    side_length = 4
  []
  [sq2]
    type = ExampleCSGInfiniteSquareMeshGenerator
    side_length = 3
  []
  [cart_lat]
    type = TestCSGLatticeMeshGenerator
    lattice_type = 'cartesian'
    inputs = 'sq1 sq2'
    pattern = '0 0;
               0 1'
    pitch = 5
  []
  [sq3]
      type = ExampleCSGInfiniteSquareMeshGenerator
      side_length = 15
      fill = 'cart_lat'
  []
[]
(moose/test/tests/csg/csg_lattice_cart.i)

Example Output:

{
  "cells": {
    "sq1_square_cell": {
      "fill": "square_material",
      "filltype": "CSG_MATERIAL",
      "region": "(+sq1_surf_plus_x & -sq1_surf_minus_x & -sq1_surf_plus_y & +sq1_surf_minus_y)"
    },
    "sq2_square_cell": {
      "fill": "square_material",
      "filltype": "CSG_MATERIAL",
      "region": "(+sq2_surf_plus_x & -sq2_surf_minus_x & -sq2_surf_plus_y & +sq2_surf_minus_y)"
    },
    "sq3_square_cell": {
      "fill": "cart_lat_lattice",
      "filltype": "LATTICE",
      "region": "(+sq3_surf_plus_x & -sq3_surf_minus_x & -sq3_surf_plus_y & +sq3_surf_minus_y)"
    }
  },
  "lattices": {
    "cart_lat_lattice": {
      "attributes": {
        "ncol": 2,
        "nrow": 2,
        "pitch": 5.0
      },
      "outertype": "VOID",
      "type": "CSG::CSGCartesianLattice",
      "universes": [
        [
          "sq1_univ",
          "sq1_univ"
        ],
        [
          "sq1_univ",
          "sq2_univ"
        ]
      ]
    }
  },
  "surfaces": {
    "sq1_surf_minus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "sq1_surf_minus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "sq1_surf_plus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": -2.0
      },
      "type": "CSG::CSGPlane"
    },
    "sq1_surf_plus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": 2.0
      },
      "type": "CSG::CSGPlane"
    },
    "sq2_surf_minus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 1.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq2_surf_minus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": -1.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq2_surf_plus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": -1.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq2_surf_plus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": 1.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq3_surf_minus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 7.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq3_surf_minus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": -7.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq3_surf_plus_x": {
      "coefficients": {
        "a": -1.0,
        "b": 0.0,
        "c": 0.0,
        "d": -7.5
      },
      "type": "CSG::CSGPlane"
    },
    "sq3_surf_plus_y": {
      "coefficients": {
        "a": 0.0,
        "b": 1.0,
        "c": 0.0,
        "d": 7.5
      },
      "type": "CSG::CSGPlane"
    }
  },
  "universes": {
    "ROOT_UNIVERSE": {
      "cells": [
        "sq3_square_cell"
      ],
      "root": true
    },
    "sq1_univ": {
      "cells": [
        "sq1_square_cell"
      ]
    },
    "sq2_univ": {
      "cells": [
        "sq2_square_cell"
      ]
    }
  }
}
(moose/test/tests/csg/gold/csg_lattice_cart_out_csg.json)
commentnote:Running the Examples

To run either of the above examples, use --allow-test-objects:


./moose_test-opt --allow-test-objects --csg-only -i tests/csg/csg_only_chained.i

./moose_test-opt --allow-test-objects --csg-only -i tests/csg/csg_lattice_cart.i