ORANGE

The Oak Ridge Advanced Nested Geometry Engine (ORANGE) [Johnson et al., 2025] is a surface-based geometry that has been adapted to GPU execution to support platform portability in Celeritas. It can be built via its interface to SCALE or constructed automatically from Geant4 geometry representation.

Construction API

ORANGE input can be manually constructed via an API for testing and direct integration with other applications.

Intersect region

The lowest level primitive for construction is the “intersect region,” which is a CSG intersection of half-spaces. The IntersectRegion interface helps construct these objects.

class IntersectRegionInterface

Interface class for building non-reentrant spatial regions.

This is a building block for constructing more complex objects out of smaller spatial regions. A shape object will have a single intersect region, and a solid object region may have multiple adjacent intersect regions.

Convex regions should be as minimal as possible and rely on transformations to change axes, displacement, etc. As a general rule, the exterior bounding box of a intersect region should be centered on the origin, and objects should be aligned along the z axis.

When implementing this class, prefer to build simpler surfaces (planes) before complex ones (cones) in case we implement short-circuiting logic, since expressions are currently sorted.

Note

Additional methods such as volume calculation may be added here later.

Subclassed by celeritas::orangeinp::Box, celeritas::orangeinp::Cone, celeritas::orangeinp::Cylinder, celeritas::orangeinp::Ellipsoid, celeritas::orangeinp::GenPrism, celeritas::orangeinp::InfWedge, celeritas::orangeinp::Involute, celeritas::orangeinp::Parallelepiped, celeritas::orangeinp::Prism, celeritas::orangeinp::Sphere

class Box : public celeritas::orangeinp::IntersectRegionInterface

A rectangular parallelepiped/cuboid centered on the origin.

The box is constructed with half-widths.

class Cone : public celeritas::orangeinp::IntersectRegionInterface

A closed truncated cone along the z axis centered on the origin.

A quadric cone technically defines two opposing cones that touch at a single vanishing point, but this cone is required to be truncated so that the vanishing point is on our outside the cone.

The midpoint along the z axis of the cone is the origin. A cone is not allowed to have equal radii: for that, use a cylinder. However, it may have a single radius of zero, which puts the vanishing point on one end of the cone.

This intersect region, along with the Cylinder, is a base component of the G4Polycone (PCON).

class Cylinder : public celeritas::orangeinp::IntersectRegionInterface

A z-aligned cylinder centered on the origin.

The cylinder is defined with a radius and half-height.

class Ellipsoid : public celeritas::orangeinp::IntersectRegionInterface

An axis-alligned ellipsoid centered at the origin.

The ellipsoid is constructed with the three radial lengths.

class GenPrism : public celeritas::orangeinp::IntersectRegionInterface

A generalized polygon with parallel flat faces along the z axis.

A GenPrism, like VecGeom’s GenTrap, ROOT’s Arb8, and Geant4’s G4GenericTrap, represents a generalized volume with polyhedral faces on two parallel planes perpendicular to the z axis. Unlike those other codes, the number of faces can be arbitrary in number.

The faces have an orientation and ordering so that twisted faces can be constructed by joining corresponding points using straight-line “vertical” edges, directly matching the G4GenericTrap definition, but directly generating a hyperbolic paraboloid for each twisted face.

Trapezoids constructed from the helper functions will have sides that are same ordering as a prism: the rightward face is first (normal is along the +x axis), then the others follow counterclockwise.

class InfWedge : public celeritas::orangeinp::IntersectRegionInterface

An open wedge shape from the z axis.

The wedge is defined by an interior angle that must be less than or equal to 180 degrees (half a turn) and must be more than zero. It can be subtracted, or its negation can be subtracted. The start angle is mapped onto \([0, 1)\) on construction.

class Involute : public celeritas::orangeinp::IntersectRegionInterface

An involute “blade” centered on the origin.

This is the intersection of two parallel involutes with a cylindrical shell. The three radii, which must be in ascending order, are that of the involute, the inner cylinder, and the outer cylinder.

The “chirality” of the involute is viewed from the +z axis looking down: whether it spirals to the right or left.

class Parallelepiped : public celeritas::orangeinp::IntersectRegionInterface

A general parallelepiped centered on the origin.

A parallelepiped is a shape having 3 pairs of parallel faces out of which one is parallel with the x-y plane (z faces). All faces are parallelograms in the general case. The z faces have 2 edges parallel with the x axis. Note that all angle parameters are expressed in terms of fractions of a 360-degree turn.

The shape has the center in the origin and it is defined by:

  • halfedges: a 3-vector (dY, dY, dZ) with half-lengths of the projections of the edges on X, Y, Z. The lower Z face is positioned at -dZ, and the upper one at +dZ.

  • alpha angle between the segment defined by the centers of the X-parallel edges and Y axis. Validity range is (-1/4, 1/4);

  • theta polar angle of the shape’s main axis, e.g. the segment defined by the centers of the Z faces. Validity range is [0, 1/4);

  • phi azimuthal angle of the shape’s main axis (as explained above).   Validity range is [0, 1).

class Prism : public celeritas::orangeinp::IntersectRegionInterface

A regular, z-extruded polygon centered on the origin.

This is the base component of a G4Polyhedra (PGON). The default rotation is to put a y-aligned plane on the bottom of the shape, so looking at an x-y slice given an apothem a, every shape has a surface at \( y = -a \):

  • n=3 is a triangle with a flat bottom, point up

  • n=4 is a square with axis-aligned sides

  • n=6 is a flat-top hexagon

The “orientation” parameter is a scaled counterclockwise rotation on \([0, 1)\), where zero preserves the orientation described above, and unity replicates the original shape but with the “p0” face being where the “p1” originally was. With a value of 0.5:

  • n=3 is a downward-pointing triangle

  • n=4 is a diamond

  • n=6 is a pointy-top hexagon

class Sphere : public celeritas::orangeinp::IntersectRegionInterface

A sphere centered on the origin.

Note

Be aware there’s also a sphere surface at orange/surf/Sphere.hh in a different namespace.

Objects

Each unit is constructed from the user defining ObjectInterface implementations and relationships, and specifying which of them are volumes. The Object interface is implemented by:

Shape

A finite (and usually convex) region of space defined by the intersection of multiple quadric surfaces. The Shape is implemented using a single IntersectRegion, which is an implementation that builds the underlying surfaces and bounding boxes. Shapes should be as simple as possible, aligned along and usually centered on the z axis.

Solid

A shape that’s hollowed out and/or has a slice removed. It is equivalent to a CSG operation on two shapes of the same type and an azimuthal wedge.

PolySolid

A union of transformed solids along the z axis, which can also be hollowed and sliced azimuthally.

Transformed

Applies a transformation (rotation, translation) to another CSG object.

AnyObjects, AllObjects, and NegatedObject

Apply the CSG operations of union, intersection, and negation. The first two are implemented as templates of a JoinObjects class.

Objects are typically constructed and used as shared pointers so that they can be reused in multiple locations.

template<class T>
class Shape : public celeritas::orangeinp::ShapeBase

Shape that holds an intersect region and forwards construction args to it.

Construct as:

BoxShape s{"mybox", Real3{1, 2, 3}};
or
Shape s{"mybox", Box{{1, 2, 3}}};

See IntersectRegion.hh for a list of the regions and their construction arguments.

template<class T>
class Solid : public celeritas::orangeinp::SolidBase

A shape that is hollow, is truncated azimuthally, or both.

Examples:

// A cone with a thickness of 0.1
Solid s{"cone", Cone{{1, 2}, 10.0}, Cone{{0.9, 1.9}, 10.0}};
// A cylinder segment in z={-2.5, 2.5}, r={0.5, 0.75}, theta={-45, 45} deg
Solid s{"cyl", Cylinder{0.75, 5.0}, Cylinder{0.5, 5.0},
        {Turn{0}, Turn{0.25}};
// The east-facing quarter of a cone shape
Solid s{"cone", Cone{{1, 2}, 10.0}, {Turn{-0.125}, Turn{0.25}};

class PolyCone : public celeritas::orangeinp::PolySolidBase

A series of stacked cones or cylinders or combination of both.

class Transformed : public celeritas::orangeinp::ObjectInterface

Build a translated or transformed object.

class NegatedObject : public celeritas::orangeinp::ObjectInterface

Everywhere but the embedded object.

template<OperatorToken Op>
class JoinObjects : public celeritas::orangeinp::ObjectInterface

Join all of the given objects with an intersection or union.

std::shared_ptr<AllObjects const> celeritas::orangeinp::make_subtraction(std::string &&label, SPConstObject const &minuend, SPConstObject const &subtrahend)

Make a new object that is the second object subtracted from the first.

This just takes the intersection the first object and the negated second:

A - B <=> A & ~B

std::shared_ptr<AllObjects const> celeritas::orangeinp::make_rdv(std::string &&label, VecSenseObj &&inp)

Make a combination of possibly negated objects.

The Region Definition Vector (RDV) is an intersection of objects and/or their negations. It is the KENO/SCALE [Hollenbach, DF et al., 1993] way for defining media, boundaries, etc. It must not be empty.

        classDiagram
  Object <|-- Transformed
  Object <|-- Shape
  Object <|-- NegatedObject
  Object <|-- JoinObjects
  ShapeBase <|-- Shape
  class Object {
    +string_view label()*
    +NodeId build(VolumeBuilder&)*
  }
  <<Interface>> Object
  class Transformed {
    -SPConstObject obj
    -VariantTransform transform
  }
  Transformed *-- Object

  class ShapeBase {
    #IntersectRegion const& interior()*
  }
  <<Abstract>> ShapeBase

  class Shape {
    -string label;
    -IntersectRegion region;
  }
  Shape *-- IntersectRegion

  class IntersectRegion {
    +void build(IntersectSurfaceBuilder&)*
  }
  <<Interface>> IntersectRegion
  IntersectRegion <|-- Box
  IntersectRegion <|-- Sphere

  class Box {
    -Real3 halfwidths
  }
  class Sphere {
    -real_type radius
  }

  Shape <|.. BoxShape
  Shape <|.. SphereShape

  BoxShape *-- Box
  SphereShape *-- Sphere
    

CSG unit

The CSG unit is a general scene comprising arbitrary volumes made of arbitrary quadric and planar faces. The name “unit” is derived from the KENO criticality safety code [Hollenbach, DF et al., 1993], where a unit is a reusable composable building block for arrays.

class UnitProto : public celeritas::orangeinp::ProtoInterface

Construct a general CSG universe, aka a “unit”.

A “unit” is a region of space (specified by the “boundary” object) that is divided up into multiple smaller regions:

  • A “material” (aka “media” in SCALE) is a single homogeneous CSG object filled with a particular material ID. This is equivalent to a leaf “physical volume” in a GDML/Geant4 volume hierarchy.

  • A “daughter” (aka “hole” in SCALE) is another unit that is transformed and placed into this universe.

Regarding boundary conditions: “Input” is for how the unit is defined:

========== ========================================================== Input Description ========== ========================================================== Implicit Boundary implicitly truncates interior (KENO) Explicit Interior CSG definition includes boundary (RTK) ========== ==========================================================

Additionally, whether the universe is the top-level global universe (see the ExteriorBoundary type) affects the construction.

========== ========================================================== ExtBound Description ========== ========================================================== Daughter Boundary is already truncated by higher-level unit Global Boundary must explicitly be represented as a volume ========== ==========================================================

These result in different z ordering for the exterior:

===== ===== ================== ======================================== Inp ExB Resulting zorder Description ===== ===== ================== ======================================== I N implicit_exterior Higher-level universe truncates X N implicit_exterior Higher-level universe truncates I Y exterior Global unit that truncates other regions X Y media Global unit with well-connected exterior ===== ===== ================== ========================================

The Object classes above are all factory functions for creating a CSG tree and transformed surfaces corresponding to leaf nodes. Some important aspects of this construction process are:

  • Transforming constructed surfaces based on the stack of transformations

  • Simplifying and normalizing surfaces (e.g., ensuring planes are pointing in a “positive” direction and converting arbitrary planes to axis-aligned planes)

  • De-duplicating “close” surfaces to eliminate boundary crossing errors

  • Naming constructed surfaces based on the constructing surface type

  • Constructing bounding boxes using the original and simplified surfaces, as well as additional specifications from the convex regions

  • Adding surfaces as leaf nodes to the CSG tree, and defining additional nodes based on those

  • Simplifying the CSG tree based on boundary conditions and other factors

Geant4 geometry translation

The Geant4 geometry is a hierarchy of “logical volumes” comprised of solids. Child (“daughter”) volumes are “placed” into a parent (“mother”) volume after applying a transformation (translation, rotation, reflection, or a combination), displacing the material in the parent volume. Besides this displacement, no overlap is allowed.

Solids are parametrized volumes that may be hollowed out, have slices removed, or be defined as a CSG operation on placed volumes. They are sometimes but not always convex. See the Geant4 documentation for descriptions of all the predefined solids.

A logical volume can be referenced multiple times, i.e., placed multiple times in multiple different volumes. The Geant4-ORANGE converter decomposes the graph of logical volume relationships into subgraphs that each become a CSG unit. This decomposition is currently tuned so that:

  • Volumes with no children are directly placed as “material” leaf nodes into a unit

  • Logical volumes placed in a singular location without transforms are also placed as materials with child volumes explicitly subtracted out

  • Union or poly volumes (for now!) must be placed as materials even if they are used multiple times and have daughter volumes.

Runtime interfaces

class OrangeParams : public celeritas::GeoParamsSurfaceInterface, public celeritas::ParamsDataInterface<OrangeParamsData>

Persistent model data for an ORANGE geometry.

This class initializes and manages the data used by ORANGE (surfaces, volumes) and provides a host-based interface for them.

class OrangeTrackView

Navigate through an ORANGE geometry on a single thread.

Since the navigation relies on computationally expensive calls and must ensure a consistent state between physical and logical boundaries, there is an ordering followed by Celeritas’ internal calls to each track’s state. Access (pos, dir, volume/surface/is_outside/is_on_boundary) is valid at any time.

The required ordering is:

  • Initialization, via the assignment operator

  • Locating the boundary crossing along the current direction, find_next_step

  • Locating the closest point on the boundary in any direction, find_safety

  • Movement within a volume, not crossing a boundary: move_internal or move_to_boundary

  • If on a boundary, logically moving to the adjacent volume (“relocation”) with cross_boundary

At any time, set_dir may be called, but then find_next_step must again be called before any subsequent move or cross action above .

The main point is that find_next_step depends on the current straight-line direction, move_to_boundary and move_internal (with a step length) depends on that distance, and cross_boundary depends on being on the boundary with a knowledge of the post-boundary state.

Todo:

move_internal with a position should depend on the safety distance, but that check is not yet implemented.