Concepts¶
Three pieces, one contract¶
| Piece | Runs where | Licence |
|---|---|---|
SDK (import photonhub as ph) |
your machine, pure Python | Apache-2.0 |
Solver (phsolver) |
your machine or a remote GPU | proprietary |
Cloud service (via ph.cloud) |
LeapField infrastructure | invitation-gated |
The object that crosses every boundary is
Simulation. It serialises to JSON against a
versioned schema, so the same scene is executable by a local
binary, a remote GPU worker, or the desktop application, and comes back as the
same RunResult structure. Numerical agreement
also depends on the selected engine and settings.
That is why the SDK is useful on its own: building, validating, and pricing a scene needs no engine at all.
Building a scene¶
A Simulation describes domain extents, grid, run controls, structures,
sources, monitors and boundaries. It is a frozen model, so:
- Construction validates. Structural and cross-field checks run at build time, so an out-of-domain monitor or a malformed polygon raises before a solver starts.
- It is comparable and hashable. Equality compares model values. Wire serialization represents the resolved solver inputs.
- Edits produce new objects. Helper methods such as
with_auto_mesh()andwith_mesh_overrides()return a modified copy. The latter accepts positionalMeshOverrideobjects, plus auto-mesh options.
The SDK resolves mesh cell counts and timesteps. It checks supported grid
and dispersion-stability rules, including full-tensor restrictions.
check_runnable() checks sources, boundary slabs and monitor positions.
It refuses a source inside the PML or absorber layers, one error per mode
launch, port or beam, naming the face and the depth; a symmetry plane is a
mirror, not a layer, and periodic axes have none.
The selected engine remains authoritative via phsolver validate.
Available device memory and backend-specific limits still need engine
validation or cloud preflight.
Engine provenance¶
A common schema and result structure do not guarantee identical numbers. Match engine revision, mesh, materials, subpixel settings, boundaries, precision, sources, monitors and run controls. CPU/GPU comparisons use the numerical contract's tolerances, rather than general bit-for-bit equality.
# data from the local quickstart run
print(data.provenance.get("solver_version"))
print(data.provenance.get("git_sha"))
The cloud API and solver images are independently versioned. Grid comparisons
in cloud preflight check the quoted grid, not the engine's numerical behavior.
An older engine can return plausible numbers. The cloud engine can trail the
source. Absorbers thicker than 40 layers are one recorded difference until the
cloud engine is rebuilt. Check git_sha in result provenance. See
Known limitations.
Units¶
Lengths are microns and carry a _um suffix (size_um, dl_um,
center_um). Frequencies are hertz. Times are seconds. The suffix is
part of the field name to make the expected unit explicit. Check it before
entering a value. A valid number in the wrong unit can still pass validation.
Pricing before running¶
Use sim and data from the quickstart's first dipole example for the
following snippets.
Every Simulation can price itself with no solver and no GPU:
The estimator resolves cell count, timestep and step count with the SDK's
rules. Match versions before comparing with an engine or service. The unit is the
Tcell-step: 10¹² cell updates, num_cells * num_steps / 1e12.
The rate is a model input, not a price
The default planning rate is overridable and is not a production
billing commitment. CostEstimate exposes every field (.num_cells,
.num_steps, .usd, .device_memory_bytes, …) so you can substitute your
own rate. See quote.
Results are labelled arrays¶
Monitors come back as xarray objects with named dimensions and physical
coordinates:
# data = ph.run_local(sim), from the quickstart
flux = data["flux"] # dims ('f',), f in Hz
snap = data["snapshot"] # dims ('t','component','z','y','x')
ez = snap.isel(t=-1).sel(component="Ez")
plane = ez.sel(z=1.5, method="nearest") # microns
Frequency-domain monitors report the continuous-wave response to the
sources as declared: flux in watts, fields in V/m and A/m (the engine
normalizes per unit amplitude of the first source and the reader multiplies
that amplitude back in). Ratios such as R and T against an empty reference
run are amplitude-free. The convention used is recorded in
.attrs["normalization"]. Cross-validation workflows turn paired runs into
R, T, C_sca, or n_eff; see the Examples.
Where the analysis lives¶
Everything above the raw fields is Python, in the Apache-2.0 SDK:
- Mode solving: scalar and full-vector solvers with modes consistent with the FDTD fields that will carry them.
- Mode launch and readout:
mode_launchbuilds a matched source/monitor pair;transmissionturns a finished run into a power spectrum. - S-matrix, EME, far field, resonances, spectral completion: see the analysis reference.
- Inverse design: discrete adjoint topology optimisation
(
ph.inverse_design).
Because it is Python, you can read exactly what any of it does.
Subpixel smoothing¶
Material interfaces are averaged with Kottke–Farjadpour–Johnson tensor
smoothing rather than snapped to the nearest cell. On axis-aligned and
waveguide walls the blend is exact and second-order, so wall positions are not
limited to the staircase resolution of the grid. On curved 3-D bodies
(spheres, cylinders) the current diagonal blend is a documented approximation
that can read absolute scattering cross-sections worse than the plain
staircase; set subpixel=False when you need absolute cross-sections of
curved scatterers. Smoothing is a Simulation field (subpixel,
subpixel_method), so it is recorded in the scene and travels with it.