The world model

How a Core Scene is built from structures, materials and antenna endpoints, what a snapshot is, and what Core leaves to the solvers.

Core describes the world; solvers simulate it. A Core Scene is a logical description: shapes, what they are made of, where the antennas are and how things move. It holds no solver state, no GPU buffers and no results. Each solver reads the scene and compiles it into its own runtime resources.

The parts of a scene#

PartClassWhat it holds
SceneSceneA tuple of structures, a tuple of endpoints, a read-only metadata mapping and four version counters.
StructureStructureOne geometry, one material, a name, tags, metadata and stable IDs. Optional UV coordinates and a phase screen.
GeometryBox, Sphere, ..., Mesh, or any GeometrySpecShape and pose. Can produce a triangle mesh and, for the built-in classes, a signed distance.
MaterialPhysicalMaterial, or any MaterialSpecElectromagnetic and surface parameters. Can report its permittivity at a frequency.
EndpointAntennaState, ReceiverGridA transmitter or receiver: position, orientation, polarization, array layout, pattern and power.
Scene
├── structures: (Structure, ...)
│     ├── geometry   -> Box | Sphere | ... | Mesh
│     └── material   -> PhysicalMaterial
└── endpoints:  (AntennaState | ReceiverGrid, ...)

A scene with no structures is valid, and so is a scene with no endpoints. Which parts a solver needs is up to the solver.

Snapshots#

Solvers do not read a Scene directly while they run. They read a SceneSnapshot: the world at one time time_s, with each structure wrapped in a StructureState and each endpoint in an EndpointState.

  • scene.snapshot(time_s) returns a static snapshot. No structure or endpoint moves.
  • DynamicScene(scene, ...).at(time_s) returns a snapshot in which some structures carry a RigidMotion or a DeformationState, and some endpoints carry a RigidMotion.

Core records motion; it does not apply it. A snapshot keeps the authored geometry untouched and attaches the motion next to it. The solver composes the two, in this order:

  1. A deformation replaces the authored local mesh vertices (DeformationState.vertices) or adds offsets to them (DeformationState.offsets).
  2. The authored geometry transform (scale, rotation, position) is applied.
  3. RigidMotion.rotation is composed on the left of the authored rotation, and RigidMotion.translation (world frame) is added last.

Endpoint motion follows the same rule, after the antenna's own orientation. See Animate a scene.

Immutable containers, live tensors#

Scene, Structure, PhysicalMaterial, AntennaState and the other contracts are frozen dataclasses. You cannot assign to their fields. To change a scene, you build a new one, usually with the with_* methods (with_structure_geometry, with_material, with_endpoints, ...). See Update a scene.

The tensors inside are not frozen. Core stores the exact tensor objects you pass, so an optimizer can update them in place and every scene that refers to them sees the new values. The scene's version counters notice such in-place changes. See Tensors and gradients and Identity and versions.

Contracts, not base classes#

Structure accepts any object that satisfies two runtime-checkable protocols:

  • GeometrySpec: has a kind string and a to_mesh(segments=16) method.
  • MaterialSpec: has a name, and the methods capabilities(), evaluate_static() and evaluate_at_frequency(frequency).

So a solver or a Plugin can add its own geometry or material type without subclassing a Core class. Radar's SMPL body and Maxwell's PolySlab are geometry types defined this way, outside Core. See Write your own geometry or material.

What Core does not do#

  • It does not trace rays, solve fields or store results. That is the job of Maxwell, Radar and Channel.
  • It does not compile scenes into GPU resources or cache them. Each solver owns its compiled scene, native resources and caches, keyed on Core's IDs and versions.
  • It reads one file format, Wavefront OBJ (Mesh.from_obj), and has no scene file format of its own.