Coordinates and units

SI units in Core, where each primitive sits relative to its position, the axis option, and the two Euler angle orders used by geometry and antennas.

Core uses SI units and a single world frame. Rotations come as quaternions or Euler angles. Read the Euler section carefully: geometry and antennas order their Euler angles differently.

Units#

QuantityUnitExamples
Lengthmetreposition, size, radius, thickness_m, rms_height_m, spacing
AngleradianEuler angles, principal_axis_rad
Timesecondtime_s, reference_time_s
Frequencyhertzevaluate_at_frequency(3.5e9), reference_frequency_hz
Conductivitysiemens per metresigma_e
PowerwattAntennaState.power_w
Velocitymetre per secondLinearTrajectory.velocity, RigidMotion.velocity

eps_r and mu_r are relative (dimensionless). Core stores angular_velocity for solvers but never uses it itself. Names that end in _m, _s, _hz, _rad or _w carry their unit in the name.

Where a primitive sits#

Each geometry has a position (its local origin in world coordinates) and a rotation. The table shows where the shape sits in its local frame, before rotation and translation.

GeometryLocal placement
Box, HollowBoxCentred on the origin; size (or outer_size) is the full edge length per axis.
Sphere, EllipsoidCentred on the origin.
Cylinder, PrismCentred on the origin; height runs from -height/2 to +height/2 along axis.
TorusCentred on the origin; the ring lies in the plane normal to axis.
Cone, PyramidApex at the origin; the base is at +height along axis.
MeshThe authored vertices. With recenter=True (the default) Core first shifts them so their bounding-box centre is at the origin.

axis is "x", "y" or "z" (default "z"). It picks the local direction of the shape's main axis. Use rotation for any other direction.

The world-space vertices of a mesh are (local_vertices * scale) @ R.T + position, where R is the rotation matrix. Only Mesh has a scale.

Rotations#

Quaternions#

A quaternion is scalar-first: (w, x, y, z). The identity is (1, 0, 0, 0). This is the same for geometry, antennas and motion.

Euler angles: two orders#

Geometry and antennas order Euler angles differently

  • Geometry (Box(rotation=...) and every other primitive and Mesh): (roll, pitch, yaw). The third value turns about z.
  • Antennas and motion (AntennaState(orientation=...), ReceiverGrid, RigidMotion.rotation, LinearTrajectory.rotation): (yaw, pitch, roll). The first value turns about z.

Both build the same matrix R = Rz(yaw) @ Ry(pitch) @ Rx(roll) (intrinsic Z-Y-X). Only the order of the three numbers differs.

So a quarter turn about z is written two ways:

import math
 
from witwin.core import AntennaState, Box
 
box = Box(rotation=(0.0, 0.0, math.pi / 2))                                       # (roll, pitch, yaw)
antenna = AntennaState(1, "tx", (0, 0, 0), orientation=(math.pi / 2, 0.0, 0.0))  # (yaw, pitch, roll)

Both map the local x direction to world y. To avoid the difference, pass quaternions.

How each field stores what you pass:

  • Geometry turns a Python tuple of three angles into a quaternion and stores a (4,) tensor. A (3,) tensor is stored as is, so that its gradient flows, and is converted to a quaternion when used.
  • AntennaState.orientation, RigidMotion.rotation and LinearTrajectory.rotation store a (3,) or (4,) tensor as given.

Frames for antennas#

orientation maps the endpoint's local coordinates into world coordinates. polarization and element_positions are authored in the endpoint's local frame; AntennaState.polarization_world() rotates the polarization into the world frame. ReceiverGrid.x_axis and y_axis are world-frame directions and ignore orientation.

Grids and broadcasting#

signed_distance(xx, yy, zz) and to_mask(xx, yy, zz) take three tensors of world coordinates.

  • Primitives evaluate element by element, so xx, yy, zz can have any matching (broadcastable) shape: a 0-d tensor for one point, or the output of torch.meshgrid(..., indexing="ij") for a grid.
  • Mesh.signed_distance treats three 1-D tensors as grid axes and builds the ij meshgrid for you. Any other input is used point by point.

The default beta of to_mask is derived from the grid spacing along dimension 0 of xx, dimension 1 of yy and dimension 2 of zz, which matches an ij meshgrid. See Tensors and gradients.