Geometry

Reference for the shapes, triangle meshes, extruded polygons and the Structure type that place materials in a Maxwell scene.

This page lists the geometry types you use to place materials in a Scene. You need it whenever you add a Structure, a MaterialRegion, or a MeshOverrideStructure.

Most classes here come from the shared base package witwin.core (PyPI witwin 0.4.x, which witwin-maxwell 0.5.1 requires) and are re-exported by witwin.maxwell: Box, Sphere, Cylinder, Ellipsoid, Cone, Pyramid, Prism, Torus, HollowBox, Mesh and Structure. mw.Box and witwin.core.Box are the same class. PolySlab and ComplexPolySlab are Maxwell's own and live in witwin.maxwell.geometry.

import witwin.maxwell as mw
 
scene.add_structure(
    mw.Structure(
        geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(0.24, 0.24, 0.24)),
        material=mw.Material(eps_r=4.0),
        name="dielectric_box",
    )
)

Common conventions#

These rules hold for every geometry on this page.

  • Units. All lengths are in metres.
  • Storage. Lengths, positions and rotations are stored as float32 torch.Tensor values. You can pass plain numbers or tensors. A tensor with requires_grad=True stays in the autograd graph, so you can optimize shape parameters. See Differentiability.
  • position. A length-3 value. For most shapes it is the centre. Cone and Pyramid put their apex there.
  • rotation. None (no rotation), three Euler angles (roll, pitch, yaw) in radians, or a quaternion (w, x, y, z). A tensor of shape (3,) or (4,) is kept as is.
  • axis. "x", "y" or "z", for shapes that have one. Other values raise ValueError.
  • device. Keyword-only. Places the parameter tensors on that device. Tensor arguments must agree with it, or the constructor raises ValueError.
  • Rasterization. Maxwell's material compiler samples each geometry on the Yee grid through to_mask, which is built on signed_distance.

Warning

with_material(material, **kwargs) is defined on the witwin.core geometry base class, but in witwin 0.4.0 it raises ImportError. Build the structure with mw.Structure(geometry=..., material=...) instead.

Shared geometry methods#

Every geometry class below has these members.

signed_distance#

geometry.signed_distance(xx, yy, zz) -> torch.Tensor

Signed distance in metres from each query point to the surface: negative inside, positive outside. xx, yy, zz are coordinate tensors of the same shape, for example from torch.meshgrid(..., indexing="ij").

to_mask#

geometry.to_mask(xx, yy, zz, offset=0.0, beta=None) -> torch.Tensor

A smooth occupancy in the range 0 to 1: 0.5 * (1 - tanh((d - offset) / beta)) where d is the signed distance. offset grows (negative) or shrinks (positive) the shape, in metres. beta is the transition width in metres; None uses 5% of the smallest coordinate spacing found in the inputs.

to_mesh#

geometry.to_mesh(segments=16) -> tuple[torch.Tensor, torch.Tensor]

A triangle mesh (vertices, faces) in world coordinates: vertices of shape (N, 3) and int64 faces of shape (M, 3). segments sets the tessellation of curved shapes and is ignored by flat-faced ones.

kind and device#

kind is a class-level string such as "box" or "poly_slab". device is a property that returns the device of position.

Box#

class Box(position=(0, 0, 0), size=(1, 1, 1), rotation=None, *, device=None)

A rectangular box centred at position.

positiontuple[float, float, float] | torch.Tensordefault (0, 0, 0)

Centre in metres.

sizefloat | tuple[float, float, float] | torch.Tensordefault (1, 1, 1)

Edge lengths (sx, sy, sz) in metres. A single number gives a cube.

rotationtuple | torch.Tensor | Nonedefault None

Euler angles in radians or a (w, x, y, z) quaternion.

slab = mw.Box(position=(0.0, 0.0, 0.0), size=(0.52, 1.28, 0.12))

Sphere#

class Sphere(position=(0, 0, 0), radius=1.0, rotation=None, *, device=None)

A sphere centred at position. rotation is stored but has no effect on the shape.

positiontuple[float, float, float] | torch.Tensordefault (0, 0, 0)

Centre in metres.

radiusfloat | torch.Tensordefault 1.0

Radius in metres.

Cylinder#

class Cylinder(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)

A circular cylinder centred at position, with its axis along axis before rotation.

radiusfloat | torch.Tensordefault 1.0

Radius in metres.

heightfloat | torch.Tensordefault 1.0

Full length along the axis in metres. The cylinder spans height / 2 on each side of position.

axisstrdefault 'z'

Axis direction: "x", "y" or "z".

rod = mw.Cylinder(position=(0.0, 0.0, 0.0), radius=0.05, height=0.4, axis="x")

Ellipsoid#

class Ellipsoid(position=(0, 0, 0), radii=(1, 1, 1), rotation=None, *, device=None)

An ellipsoid centred at position.

radiifloat | tuple[float, float, float] | torch.Tensordefault (1, 1, 1)

Semi-axes (rx, ry, rz) in metres, before rotation.

Cone#

class Cone(position=(0, 0, 0), radius=1.0, height=1.0, axis="z", rotation=None, *, device=None)

A solid circular cone. The apex is at position, and the cone opens along the positive axis direction to a flat base at distance height.

radiusfloat | torch.Tensordefault 1.0

Base radius in metres.

heightfloat | torch.Tensordefault 1.0

Apex-to-base distance in metres.

axisstrdefault 'z'

Axis direction: "x", "y" or "z".

Pyramid#

class Pyramid(position=(0, 0, 0), base_size=1.0, height=1.0, axis="z", rotation=None, *, device=None)

A pyramid with a square base. The apex is at position, and the base lies at distance height along the positive axis direction.

base_sizefloat | torch.Tensordefault 1.0

Side length of the square base in metres.

heightfloat | torch.Tensordefault 1.0

Apex-to-base distance in metres.

axisstrdefault 'z'

Axis direction: "x", "y" or "z".

Prism#

class Prism(position=(0, 0, 0), radius=1.0, height=1.0, num_sides=6, axis="z", rotation=None, *, device=None)

A right prism with a regular polygon cross-section, centred at position.

radiusfloat | torch.Tensordefault 1.0

Circumradius of the polygon (centre to vertex) in metres.

heightfloat | torch.Tensordefault 1.0

Full length along the axis in metres.

num_sidesintdefault 6

Number of polygon sides. Must be 3 or more, otherwise ValueError.

axisstrdefault 'z'

Axis direction: "x", "y" or "z".

Torus#

class Torus(position=(0, 0, 0), major_radius=1.0, minor_radius=0.25, axis="z", rotation=None, *, device=None)

A torus centred at position, with its symmetry axis along axis.

major_radiusfloat | torch.Tensordefault 1.0

Distance from the centre to the centre of the tube, in metres.

minor_radiusfloat | torch.Tensordefault 0.25

Tube radius in metres.

axisstrdefault 'z'

Symmetry axis: "x", "y" or "z".

ring = mw.Torus(position=(0.0, 0.0, 0.0), major_radius=1.0, minor_radius=0.3)

HollowBox#

class HollowBox(position=(0, 0, 0), outer_size=(1, 1, 1), inner_size=(0.8, 0.8, 0.8), rotation=None, *, device=None)

A box with a box-shaped cavity, both centred at position. The shape is the outer box minus the inner box.

outer_sizefloat | tuple[float, float, float] | torch.Tensordefault (1, 1, 1)

Outer edge lengths in metres.

inner_sizefloat | tuple[float, float, float] | torch.Tensordefault (0.8, 0.8, 0.8)

Cavity edge lengths in metres.

HollowBox.size is a property that returns outer_size as a tuple of floats.

shell = mw.HollowBox(position=(0.0, 0.0, 0.0), outer_size=(0.6, 0.6, 0.6), inner_size=(0.4, 0.4, 0.4))

Mesh#

class Mesh(
    vertices, faces, *,
    position=(0.0, 0.0, 0.0), scale=1.0, rotation=None, recenter=True,
    fill_mode="auto", surface_thickness=None, source_path=None,
    topology_diagnostics=None, device=None,
)

An explicit triangle mesh. World vertices are (local * scale) @ R.T + position, where local is the authored vertices (recentred when recenter=True) and R is the rotation matrix.

verticesarray-like | torch.Tensorrequired

Floating-point vertex positions of shape (N, 3) with N greater than 0, in metres before scale.

facesarray-like | torch.Tensorrequired

Triangle vertex indices of shape (M, 3) with M greater than 0, as int32 or int64. Indices must be in range.

positiontuple[float, float, float] | torch.Tensordefault (0.0, 0.0, 0.0)

Translation applied last, in metres.

scalefloat | tuple[float, float, float] | torch.Tensordefault 1.0

Scale factor, uniform or per axis.

rotationtuple | torch.Tensor | Nonedefault None

Euler angles in radians or a (w, x, y, z) quaternion.

recenterbooldefault True

When True, the authored vertices are shifted so their bounding-box centre is at the origin before scale, rotation and translation.

fill_modestrdefault 'auto'

"solid" fills the enclosed volume. "surface" fills a thin shell around the triangles. "auto" uses solid for a watertight mesh and surface otherwise; it needs topology diagnostics.

surface_thicknessfloat | torch.Tensor | Nonedefault None

For surface fill: points within this distance of the triangles count as inside, in metres. None uses a quarter of the smallest grid spacing. Must be positive when set.

source_pathstr | PathLike | Nonedefault None

Origin file, stored as an absolute path.

topology_diagnosticsbool | Nonedefault None

Whether to compute watertightness statistics. They always run for non-tensor input. For tensor input they run when this is True, or when it is None, fill_mode="auto" and both tensors are on the CPU.

Raises: ValueError for wrong shapes, out-of-range indices, an unknown fill_mode or a non-positive surface_thickness; TypeError for wrong dtypes. At query time, signed_distance raises RuntimeError when fill_mode="auto" and no diagnostics were computed, or when the vertex or face tensors were changed in place after diagnostics ran.

import torch
 
vertices = torch.tensor([[0.0, 0.0, 0.0], [0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]])
faces = torch.tensor([[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]])
tetra = mw.Mesh(vertices, faces, fill_mode="solid")

Mesh.from_obj#

Mesh.from_obj(
    path, *, position=(0.0, 0.0, 0.0), scale=1.0, rotation=None, recenter=True,
    fill_mode="auto", surface_thickness=None, device=None,
) -> Mesh

Loads a Wavefront OBJ file. Polygon faces are triangulated, source_path is set to path, and topology diagnostics always run.

Raises: FileNotFoundError for a missing file; ValueError for malformed records or a file without vertices or triangles.

mesh = mw.Mesh.from_obj("cube.obj", position=(1.0, 2.0, 3.0), scale=2.0)
print(mesh.vertex_count, mesh.face_count, mesh.is_watertight)

Mesh properties#

PropertyReturnsNotes
vertices, facestorch.TensorThe authored tensors, not transformed.
world_verticestorch.TensorVertices after recentring, scale, rotation and translation.
bounds_worldtorch.TensorShape (2, 3): world minimum and maximum corners.
vertex_count, face_countintSizes of the authored arrays.
is_watertightbool or NoneNone when diagnostics did not run.
boundary_edge_count, non_manifold_edge_countint or NoneTopology statistics.
degenerate_face_count, inconsistent_edge_orientation_countint or NoneTopology statistics.
signed_volume, enclosed_volumefloat or NoneVolume of the authored mesh; enclosed_volume is the absolute value.

Mesh methods#

MethodReturnsNotes
has_trainable_geometry()boolTrue when position, rotation, scale, vertices or surface thickness requires grad.
geometry_state_key()tupleCache key that changes when the mesh state changes.
clear_voxel_cache(), clear_sdf_cache()NoneDrop cached rasterization data.
to_mesh(segments=16, *, device=None)tupleWorld vertices and faces, optionally on another device.

PolySlab#

class PolySlab(
    vertices, bounds, axis="z", sidewall_angle=0.0, reference_plane="middle",
    position=(0, 0, 0), rotation=None, *, device=None,
)

A 2D polygon extruded along an axis, with an optional linear sidewall taper. Defined in witwin.maxwell.geometry. The inside test uses the even-odd rule, so non-convex polygons work and vertex winding does not matter.

verticesarray-like | torch.Tensorrequired

Polygon vertices of shape (N, 2) with N of 3 or more, in metres. The two columns are the in-plane coordinates: (x, y) for axis "z", (x, z) for axis "y", (y, z) for axis "x".

boundstuple[float, float] | torch.Tensorrequired

(min, max) extent along the extrusion axis, in metres.

axisstr | intdefault 'z'

Extrusion axis: "x", "y", "z", or 0, 1, 2.

sidewall_anglefloat | torch.Tensordefault 0.0

Taper angle in radians. A positive angle shrinks the cross-section as the axis coordinate increases.

reference_planestrdefault 'middle'

Where along the axis the drawn polygon is exact: "bottom", "middle" or "top".

positiontuple[float, float, float] | torch.Tensordefault (0, 0, 0)

Translation of the local frame in metres. vertices and bounds are relative to it.

rotationtuple | torch.Tensor | Nonedefault None

Euler angles in radians or a (w, x, y, z) quaternion.

Raises: ValueError for vertices not shaped (N, 2) with N of 3 or more, bounds without two values, an integer axis outside 0 to 2, or an unknown reference_plane. to_mesh raises ValueError when the polygon is not simple enough to triangulate.

UM = 1e-6
waveguide = mw.PolySlab(
    vertices=[(0.0, 0.0), (2.0 * UM, 0.0), (2.0 * UM, 0.5 * UM), (0.0, 0.5 * UM)],
    bounds=(0.0, 0.22 * UM),
    sidewall_angle=0.1,
)

ComplexPolySlab#

class ComplexPolySlab(
    loops, bounds, axis="z", sidewall_angle=0.0, reference_plane="middle",
    position=(0, 0, 0), rotation=None, *, device=None,
)

A PolySlab whose cross-section is built from several vertex loops. Self-intersecting loops (such as a bowtie) and multiple loops are allowed. The inside test is the even-odd rule over all loops together, so an inner loop carves a hole. Defined in witwin.maxwell.geometry.

loopslist of (N, 2) arraysrequired

One or more polygon loops, each with 3 or more vertices, in metres.

The other parameters match PolySlab. loop_sizes holds the vertex count of each loop.

Raises: ValueError for an empty loops list or a malformed loop. to_mesh raises NotImplementedError, so a ComplexPolySlab cannot be exported as a mesh.

UM = 1e-6
outer = [(0.0, 0.0), (2.0 * UM, 0.0), (2.0 * UM, 1.0 * UM), (0.0, 1.0 * UM)]
hole = [(0.5 * UM, 0.25 * UM), (1.5 * UM, 0.25 * UM), (1.5 * UM, 0.75 * UM), (0.5 * UM, 0.75 * UM)]
holed = mw.ComplexPolySlab(loops=[outer, hole], bounds=(0.0, 0.22 * UM))

Structure#

class Structure(
    geometry, material, name=None, priority=0, enabled=True, tags=(), metadata=None, *,
    structure_id=None, material_id=None, assignment_id=None, surface_id=None,
    primitive_ids=(), phase_screen=None, uv=None, face_uv=None,
)

Binds one geometry to one material. Immutable. Re-exported from witwin.core; add it to a scene with Scene.add_structure.

geometrygeometryrequired

Any object with a kind attribute and a to_mesh() method, such as the classes on this page. Otherwise TypeError.

materialMaterialrequired

A material that implements the witwin.core MaterialSpec contract, such as mw.Material. Otherwise TypeError. See Materials.

namestr | Nonedefault None

Label, converted to str.

priorityintdefault 0

Overlap order. Where structures overlap, Maxwell uses the one with the higher priority; at equal priority, the one added later.

enabledbooldefault True

When False, Maxwell's material compiler skips the structure.

tagsIterable[str]default ()

Free-form tags, stored as a tuple of strings.

metadataMapping | Nonedefault None

Free-form metadata, stored as a read-only mapping.

The keyword-only arguments belong to the shared witwin.core scene contract:

ArgumentMeaning
structure_id, material_id, assignment_id, surface_idStable integer identities. When omitted, new ones are allocated; material_id is taken from the material when it has one.
primitive_idsExplicit per-primitive identities. An empty sequence means none.
phase_screenA witwin.core PhaseScreen, or None.
uv, face_uvTexture coordinates (T, 2) and per-face UV indices (F, 3). Pass both or neither; they require mesh geometry with one face_uv row per face.

Maxwell's solver does not read phase_screen, uv or face_uv.

Raises: TypeError for an unsupported geometry, material, phase screen or UV dtype; ValueError for mismatched UV shapes or devices.

Structure.primitive_id#

Structure.primitive_id(local_index) -> PrimitiveId

Returns the identity of primitive local_index. With primitive_ids set it indexes that tuple; otherwise it combines structure_id and the index. Raises ValueError for a negative index or one that does not fit in 32 bits, and IndexError when the index is outside primitive_ids.

structure = mw.Structure(
    geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(1.0, 1.0, 1.0)),
    material=mw.Material(eps_r=3.0, sigma_e=0.25),
    name="target",
    priority=7,
)