N-Sided Regular Polygon Unit

The CSGNPolygonUnit is a built-in engineering unit that represents a regular N-sided polygon as a CSGSurfaceEngUnit. It provides a convenient way to define a regular polygonal prism from two parameters, the number of sides and the apothem, instead of manually constructing and combining the individual planes that form each face. General information on how engineering units are created, used, and expanded within a CSGBase instance can be found in Engineering Units.

Geometry and Orientation

A CSGNPolygonUnit represents a regular polygon that is infinite along the z-axis (i.e., a prismatic region). By default, the polygon is centered at the origin with the right-most edge parallel to the y-axis, as shown in Figure 1. This default orientation can be changed by applying transformations to the unit.

Depiction of the assumed default orientation of an N-sided polygon engineering unit.

Figure 1: Depiction of the assumed default orientation of an N-sided polygon engineering unit.

The polygon is defined by infinite planes, one per side. The -th plane (where and the 0th face is the right-most face) is described by the equation

where is the apothem (the center-to-flat distance). In the general plane form , this corresponds to coefficients , , , and . The interior of the polygon is the intersection of the negative half-spaces of these planes.

Construction

A CSGNPolygonUnit is created like any other engineering unit by constructing a unique pointer and adding it to the CSGBase instance with addEngUnit() (see Engineering Units). The constructor requires a unique name, the number of sides (), and the apothem ():

  // define a 4-sided polygon
  std::unique_ptr<CSGNPolygonUnit> poly_ptr =
      std::make_unique<CSGNPolygonUnit>("polygon_unit", 4, 2.0);
  const auto & poly = csg_obj->addEngUnit(std::move(poly_ptr)); // returns CSGEngUnit type
(unit/src/CSGBaseTest.C)
  // make a 4-sided polygon with apothem length 2.0
  std::unique_ptr<CSGNPolygonUnit> poly_ptr = std::make_unique<CSGNPolygonUnit>(name, 4, 2.0);
  // add to base and return as CSGNPolygonUnit type
  const auto & poly = csg_obj->addEngUnit<CSGNPolygonUnit>(std::move(poly_ptr));
(unit/src/CSGBaseTest.C)

Attributes

The getAttributes() method returns a map containing the two defining parameters of the polygon:

AttributeTypeDescription
num_sidesintnumber of sides of the regular polygon
apothemRealdistance from the center to a side (flat)

These attributes are the minimum information needed to fully define the polygon for downstream connected codes and are what get written to the CSG JSON output when the unit is not expanded.

In addition to the attributes above, several convenience getter methods are provided to retrieve other geometric quantities derived from the number of sides and apothem:

  • getNumSides(): returns the number of sides

  • getApothem(): returns the apothem (center-to-flat distance)

  • getSideLength(): returns the edge length, computed as

  • getRadius(): returns the circumradius (center-to-vertex distance), computed as

Use as a Surface

Because CSGNPolygonUnit is a CSGSurfaceEngUnit, it can be used in place of a CSGSurface when defining the region of a CSGCell. The "negative" half-space of the unit corresponds to the interior of the polygon. The example below creates a square (a 4-sided polygon with apothem 2.0, i.e., a side length of 4.0) and uses its interior as the region of a material-filled cell:

  // make a cell that uses the polygon unit in the region definition as if it were a regular surface
  std::unique_ptr<CSGNPolygonUnit> poly_ptr =
      std::make_unique<CSGNPolygonUnit>("polygon_unit", 4, 2.0);
  const auto & poly = csg_obj->addEngUnit(std::move(poly_ptr));
  const auto & cell = csg_obj->createCell("my_cell", "my_mat", -poly); // negative half-space
(unit/src/CSGBaseTest.C)

Half-space Determination

As a CSGSurfaceEngUnit, the polygon implements evaluateSurfaceEquationAtPoint, which is used by getHalfspaceFromPoint to determine whether a point lies inside or outside the polygon. For a point , the method evaluates

for each side and returns the maximum value over all sides. A point is interior to the polygon only if this value is negative for every side, so returning the maximum yields a negative value when the point is inside, a positive value when it is outside, and zero when it lies exactly on a side. This evaluation uses the stored geometric parameters directly and therefore can be performed before the unit is expanded.

Expansion

When a CSGNPolygonUnit is expanded (see Expansion), its expandUnit() implementation creates CSGPlane surfaces, one for each side of the polygon. Each generated plane is named using the scheme [UnitName]_expanded_surf_[k], where k is the side index. For each plane, the half-space containing the origin is determined and intersected with the accumulating region so that final CSGRegion defines the interior of the polygon. When the unit is expanded within a CSGBase instance, the unit is replaced by this new CSGRegion and the generated CSGPlane surfaces are added to the base.

Example Use Case

The following is an end-to-end example of a mesh generator that produces an N-sided polygon unit, with the option to expand it into its rudimentary components. Within the generateCSG method, the polygon unit is created and added to the CSGBase instance, used as the region of a material cell, and optionally expanded based on the expand_unit input parameter.

  // 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>();

  // create an CSGNPolygonUnit for the surface
  std::unique_ptr<CSG::CSGNPolygonUnit> poly_ptr =
      std::make_unique<CSG::CSGNPolygonUnit>(mg_name + "_poly_surf", _num_sides, _apothem);
  const auto & poly = csg_obj->addEngUnit(std::move(poly_ptr));

  // create the cell with region defined by the polygon
  const auto cell_name = mg_name + "_poly_cell";
  const auto material_name = "poly_material";
  csg_obj->createCell(cell_name, material_name, -poly);

  // expand polygon unit if requested
  if (_expand)
    csg_obj->expandEngUnit(poly);

  return csg_obj;
(test/src/csg/TestPolygonUnitMeshGenerator.C)

When run without expansion, the engineering unit itself is the final output. For example, the following input creates an infinite triangular prism (a 3-sided polygon with an apothem of 4):

[Mesh<<<{"href": "../../syntax/Mesh/index.html"}>>>]
  [tri_prism]
    type = TestPolygonUnitMeshGenerator
    apothem = 4
    num_sides = 3
  []
[]
(test/tests/csg/csg_only_poly_unit.i)

This produces the CSG JSON output below, where the polygon unit is reported directly using its attributes (num_sides and apothem):

{
  "cells": {
    "tri_prism_poly_cell": {
      "fill": "poly_material",
      "filltype": "CSG_MATERIAL",
      "region_infix": [
        "-tri_prism_poly_surf"
      ],
      "region_postfix": [
        "tri_prism_poly_surf",
        "-"
      ]
    }
  },
  "units": {
    "tri_prism_poly_surf": {
      "attributes": {
        "apothem": 4.0,
        "num_sides": 3
      },
      "behavior": "SURFACE",
      "unit_type": "CSG::CSGNPolygonUnit"
    }
  },
  "universes": {
    "ROOT_UNIVERSE": {
      "cells": [
        "tri_prism_poly_cell"
      ],
      "root": true
    }
  }
}
(test/tests/csg/gold/csg_only_poly_unit_out_csg.json)

When expand_unit = true, the polygon unit is removed from the output and replaced with the corresponding plane surfaces and the interior region:

[Mesh<<<{"href": "../../syntax/Mesh/index.html"}>>>]
  [tri_prism]
    type = TestPolygonUnitMeshGenerator
    apothem = 4
    num_sides = 3
    expand_unit = true
  []
[]
(test/tests/csg/csg_only_poly_unit_expand.i)

The resulting output shows the three generated CSGPlane surfaces (named following the [UnitName]_expanded_surf_[k] scheme) and the cell region defined by their intersection:

{
  "cells": {
    "tri_prism_poly_cell": {
      "fill": "poly_material",
      "filltype": "CSG_MATERIAL",
      "region_infix": [
        "-tri_prism_poly_surf_expanded_surf_0",
        "&",
        "-tri_prism_poly_surf_expanded_surf_1",
        "&",
        "-tri_prism_poly_surf_expanded_surf_2"
      ],
      "region_postfix": [
        "tri_prism_poly_surf_expanded_surf_0",
        "-",
        "tri_prism_poly_surf_expanded_surf_1",
        "-",
        "&",
        "tri_prism_poly_surf_expanded_surf_2",
        "-",
        "&"
      ]
    }
  },
  "surfaces": {
    "tri_prism_poly_surf_expanded_surf_0": {
      "coefficients": {
        "a": 1.0,
        "b": 0.0,
        "c": 0.0,
        "d": 4.0
      },
      "type": "CSG::CSGPlane"
    },
    "tri_prism_poly_surf_expanded_surf_1": {
      "coefficients": {
        "a": -0.49999999999999983,
        "b": 0.8660254037844387,
        "c": 0.0,
        "d": 4.0
      },
      "type": "CSG::CSGPlane"
    },
    "tri_prism_poly_surf_expanded_surf_2": {
      "coefficients": {
        "a": -0.5000000000000004,
        "b": -0.8660254037844384,
        "c": 0.0,
        "d": 4.0
      },
      "type": "CSG::CSGPlane"
    }
  },
  "universes": {
    "ROOT_UNIVERSE": {
      "cells": [
        "tri_prism_poly_cell"
      ],
      "root": true
    }
  }
}
(test/tests/csg/gold/csg_only_poly_unit_expand_out_csg.json)