Identity and versions
The stable IDs of structures, materials, surfaces and antennas, how to allocate them, and the four scene versions that drive cache invalidation.
Solvers compile a scene once and reuse the result as long as nothing relevant changed. Core supports this with two things: stable IDs that name each part of the world, and four version numbers that change when a part changes.
IDs#
| ID type | Names | Set on |
|---|---|---|
StructureId | one structure | Structure.structure_id |
MaterialId | one material object | PhysicalMaterial.material_id, Structure.material_id |
AssignmentId | one material-to-surface binding | Structure.assignment_id |
SurfaceId | one surface | Structure.surface_id |
AntennaId | one endpoint | AntennaState.antenna_id |
PrimitiveId | one primitive (for example a triangle) of a structure | Structure.primitive_ids, Structure.primitive_id(i) |
All of them are plain non-negative int values wrapped in typing.NewType, so StructureId(4) == 4.
Two ways to get an ID#
Automatic. If you omit an ID, Core calls new_*_id(), which draws the next value from a counter that is global to the Python process. The values are unique within the process, but they depend on how many objects you built before. The same scene built in another order, or in another process, gets other IDs.
Reserved. If you pass an ID (Structure(..., structure_id=10)), Core calls reserve_*_id(10). It records your value as is and moves the counter past it, so later automatic IDs never collide with it.
Use reserved IDs whenever the IDs must mean the same thing later: a scene loaded from a file, a scene compared with a stored result, or a cache that outlives the process. Do not mix the two modes for one world: solvers key their caches on these IDs, and mixing can defeat cache reuse.
AntennaState has no automatic mode. Its first argument, antenna_id, is required.
Core never derives an ID from content. Choosing a reproducible scheme is up to you.
Rules a scene enforces#
Scene(...) raises ValueError when:
- two structures share a
StructureId,AssignmentIdorSurfaceId; - two different material objects share a
MaterialId(the same object used twice is fine, and is how structures share a material); - a
PrimitiveIdappears twice in one structure or in two structures; - two endpoints share an
AntennaId.
A structure inherits material_id from its material when the material has one, which PhysicalMaterial always does. Without explicit primitive_ids, Structure.primitive_id(i) returns (structure_id << 32) | i, so primitive IDs are unique across structures as long as each structure has fewer than 2³² primitives.
Versions#
A Scene and a SceneSnapshot each report four versions, in scene.versions (a SceneVersions) or one by one:
| Version | Changes when |
|---|---|
topology_version | The set of structures, their structure, surface or primitive IDs, a mesh's face tensor, or a face_uv tensor changes. |
geometry_version | A geometry tensor, a uv tensor, an endpoint, the snapshot time or any motion changes. |
material_version | A material's ID or version field changes, or one of its tensors changes. |
assignment_version | The structure-to-material binding or a phase screen changes. |
A solver compares versions to decide what to rebuild. For example, when only material_version changed, it can keep everything it built from geometry.
How versions are computed#
Each version combines two things:
- an authored counter that the
with_*methods advance (for examplewith_structure_geometryadds 1 to the geometry counter); - a fingerprint of the tensors in that domain: their identity, device, dtype, shape, stride,
requires_gradand PyTorch's in-place modification counter.
So an in-place update such as position.add_(1.0) or an optimizer step changes the version of the domain that owns the tensor, and only that one.
What versions do not see
- Python scalars are not fingerprinted. Two
PhysicalMaterialobjects with the samematerial_idand different floateps_rgive the samematerial_version. When you replace a material with Python values, raise itsversion(PhysicalMaterial(..., material_id=m, version=1)) or useScene.with_material, which advances the counter for you. - Versions are not content hashes. They include tensor identity, so rebuilding an equal scene from new tensors gives new versions. Compare versions only within one process, and only for equality.
Version values are large integers. Treat them as opaque tokens. SceneVersions(...) and SceneVersions.advanced(...) work with the small authored counters; the values a scene reports are not small.
Reading the versions walks the whole scene, so the cost grows linearly with the number of structures. The 0.4.0 release measured about 17 ms to read all four on a 1024-structure scene.