Quickstart
Build a Core scene, turn its geometry into meshes and occupancy grids, evaluate a material, and take a gradient through a shape parameter.
This page builds a small scene and uses each part of Core once. It runs on CPU in a few seconds. You need witwin installed (see Installation).
Build a scene
A scene is a list of structures and a list of antenna endpoints. A structure binds one geometry to one material. Lengths are in metres.
import torch from witwin.core import AntennaState, Box, PhysicalMaterial, Scene, Sphere, Structure concrete = PhysicalMaterial(eps_r=5.24, sigma_e=0.05, name="concrete") metal = PhysicalMaterial.perfect_conductor(name="metal") wall = Structure(Box(position=(4.0, 0.0, 1.5), size=(0.2, 6.0, 3.0)), concrete, name="wall") ball = Structure(Sphere(position=(2.0, 1.0, 1.0), radius=0.3), metal, name="ball") tx = AntennaState(1, "tx", (0.0, 0.0, 1.5), power_w=1.0) rx = AntennaState(2, "rx", (3.0, -1.0, 1.5)) scene = Scene(structures=[wall, ball], endpoints=[tx, rx]) print([s.name for s in scene.structures], [e.role for e in scene.endpoints])['wall', 'ball'] ['tx', 'rx']Read the identities
Every structure gets a stable
StructureId,MaterialId,AssignmentIdandSurfaceId. Solvers key their caches on them. In a fresh Python process the automatic IDs start at 0.for s in scene.structures: print(s.name, int(s.structure_id), int(s.material_id), int(s.assignment_id), int(s.surface_id))wall 0 0 0 0 ball 1 1 1 1Pass explicit IDs (for example
Structure(..., structure_id=10)) when you need the same IDs in every run. See Identity and versions.Turn geometry into a mesh
Every geometry can produce a world-space triangle mesh as two tensors.
vertices, faces = ball.geometry.to_mesh(segments=8) print(vertices.shape, faces.shape, faces.dtype)torch.Size([144, 3]) torch.Size([224, 3]) torch.int64Query distances and occupancy
signed_distanceis negative inside, zero on the surface and positive outside.to_maskturns it into a soft occupancy between 0 and 1.x = torch.linspace(3.5, 4.5, 51) # 2 cm spacing across the wall y = torch.linspace(-3.5, 3.5, 71) # 10 cm spacing z = torch.linspace(-0.5, 3.5, 41) # 10 cm spacing xx, yy, zz = torch.meshgrid(x, y, z, indexing="ij") distance = wall.geometry.signed_distance(xx, yy, zz) occupancy = wall.geometry.to_mask(xx, yy, zz) print(tuple(distance.shape), round(float(distance.min()), 3)) print(round(float(occupancy.sum()) * 0.02 * 0.1 * 0.1, 2), "m^3")(51, 71, 41) -0.1 3.62 m^3The deepest point is 0.1 m inside the 0.2 m thick wall. Summing occupancy times the cell volume gives about the wall volume, 0.2 × 6 × 3 = 3.6 m³. The small excess comes from the 2 cm sampling of the wall thickness.
Evaluate a material
Materials report their complex relative permittivity at a frequency in Hz.
sample = concrete.evaluate_at_frequency(3.5e9) print(sample.eps_r, metal.capabilities().perfect_conductor)(5.24-0.25678719406460493j) TrueTake a gradient
Pass a tensor with
requires_grad=Trueanywhere a continuous value goes. Core keeps the tensor itself, so the autograd graph reaches it.radius = torch.tensor(0.3, requires_grad=True) ball_geometry = Sphere(position=(2.0, 1.0, 1.0), radius=radius) axis = torch.linspace(-0.5, 0.5, 41) xx, yy, zz = torch.meshgrid(axis + 2.0, axis + 1.0, axis + 1.0, indexing="ij") volume = ball_geometry.to_mask(xx, yy, zz, beta=0.01).sum() * 0.025**3 volume.backward() print(round(volume.item(), 4), round(radius.grad.item(), 3))0.1133 1.131The volume is close to 4/3 π r³ = 0.113 m³, and the gradient is close to its derivative 4 π r² = 1.13 m².
Take a snapshot
Solvers read a
SceneSnapshot: the scene at one time. A static scene gives the same snapshot at every time.snapshot = scene.snapshot(time_s=0.0) print(len(snapshot.structures), snapshot.versions == scene.versions)2 True