Skip to content

Known limitations

PhotonHub is in an invitation-gated beta. It is not a production or tape-out signoff tool. Demonstrate mesh and duration convergence and retain independent validation before using results for consequential decisions.

These limitations apply to photonhub 0.1.6. Desktop bundles and cloud engines are versioned separately and can differ. Check their versions. For recovery, see Troubleshooting. For beta help, contact beta@leapfield.ai.

Platforms and connectivity

The packaged headless solver is CPU-only and supports macOS 14+ on Apple Silicon. Desktop installers support macOS 14+ on Apple Silicon and Windows 11 x64 on a supported servicing release. There is no Linux desktop installer. Intel Macs, Windows on ARM and Windows 10 are not certified beta targets.

Desktop registration, sign-in and each launch require Internet access. There is no offline desktop admission mode or self-service password recovery. The headless solver needs no runtime sign-in and local runs stay local.

Engine version skew

The SDK, cloud API service, selected solver image and desktop bundle are separate versioned components. A matching schema does not ensure identical numerical behavior. Engine-version skew can return plausible numbers without an error. Inspect solver_version and git_sha in result provenance and match all numerical settings before comparing runs.

A new GPU solver image is promoted to the cloud after it passes the GPU equivalence tests on GPU hardware, so the cloud engine can trail the SDK. The image built from 0.1.5 passed all 65 of them on 2026-09-29. A cloud engine built before 0.1.6 lacks this release's faster setup for scenes with many structures. It also lacks fixes that change readings: on CPU runs the TF/SF sampling and Bloch magnetic-dipole fixes, and on GPU runs with tensor_full or contour_full the fix for sources built from dipoles, ports and mode launches among them, on structure boundaries. One built before 0.1.4 also lacks earlier solver fixes: among them the absorber ramp above 40 layers, the flux row on an even symmetry plane, the contour_diag range fix, the incident line of plane-wave and mode sources on raised PML settings, the stricter validate, and the defaults for a hand-written file that omits boundaries or subpixel_method (periodic walls and volume smoothing there; files the SDK writes carry both keys). Every GPU run, on any image, stops on field energy alone. Check git_sha in result provenance. Cloud preflight compares quoted grid values when available; it does not certify solver-image equivalence.

Simulation and numerical restrictions

  • With subpixel smoothing on, the full-tensor methods tensor_full and contour_full are refused on a graded mesh on every backend. Use the diagonal methods (tensor, contour_diag) or a uniform mesh. They are also refused in a scene with a PEC structure, a dispersive medium or an absorber boundary.
  • Anisotropic media are diagonal in the simulation axes: a crystal axis along x, y or z only, no rotated tensor. They take no dispersion poles (a scalar conductivity applies to all three axes) and cannot be PEC. A scene with one runs with subpixel smoothing off by default, and subpixel=True is refused.
  • Curved-body subpixel smoothing is an approximation. Use subpixel=False and independent convergence checks for absolute scattering cross-sections of spheres or cylinders.
  • GPU and cloud runs stop on field-energy decay alone. A resonant device can stop before its spectrum has settled; lower run.shutoff (for example to 1e-7) and compare two values. Local CPU runs also wait for the recorded spectra (run.dft_shutoff), which is an estimate, not a guarantee. A broadband dipole can leave a static residue and use the whole step cap. Reaching the cap does not establish duration convergence.
  • A near-to-far-field projection of a box leaves a front/back asymmetry that falls as the square of the cell size. For a point dipole in vacuum at 25 cells per wavelength (uniform mesh, no structures, no dispersion, 25 PML layers, a box 1.2 wavelengths wide) it is 0.1 % of the peak with the dipole half a cell from the box centre, up to about 0.80 % with it near a box corner (0.10 to 0.12 µm from each of the three faces there) and up to about 1.06 % with it near a box edge (two faces near, the third coordinate centred). Wherever the build does not warn (no source within 2 cells of a face's innermost recorded sample, measured where the solver runs the source) the bound is 2 % of the peak. In a dielectric, count the cells per medium wavelength and use at least 30, where the bound is also 2 %: a dipole near a box edge reads up to 0.96 %, 1.07 % and 1.11 % at index 1.5, 2.5 and 3.5 (at 25 cells, up to 1.15 % at index 3.5 three cells from a face). On a graded mesh count the cells per wavelength in the background, use at least 30, and stay in the range the accuracy is shown for: every box face at least 0.35 wavelengths from the nearest grid line where the cell size changes (along its normal, on either side; a refined region's edge counts, not only the end of its grading) and every source at least 0.1 wavelengths from one (a point source along each axis, a launch along its normal). End a refinement inside the box with that room to spare, or carry it that far past every face, and keep sources that far from where it ends. In that range, with refinements 2 or 3 times as fine as the background, the front/back asymmetry stays within 2 % at 30 background cells (measured up to 0.90 % and 0.93 % with the refinement inside the box, a dipole near a box edge, and 0.13 % and 0.055 % with it carried past every face), falling at about second order for a source fixed in space, and the radiated power stays within 0.24 %; higher ratios are not covered. Outside that range it can be larger, and no bound is given. A source on or just past the edge of a refined region radiates an asymmetric field by itself, which no projection removes: up to 1.085 % at three times as fine and 0.92 % at twice as fine, at 30 background cells. A refined region that ends just outside a box face reads 1.79 % at 25 background cells, 1.25 % at 30 and 0.92 % at 35 (twice as fine, the worst of 33 placements): the solver's time step follows the finest cell, so the background runs at a lower Courant number and its dispersion error grows, and the projection assumes one homogeneous medium outside the box. FarFieldMonitor records a box this way and warns when a face or a source is outside the range. This needs box faces recorded one cell thick; faces with one sample along their normal still use the earlier readout, whose asymmetry is first order in the cell size (0.28 of the peak for the same dipole), and far_field warns when it does. A symmetry-reduced run projects as the full run does, within that run's own asymmetry. The far-field guide gives the measured accuracy with its settings.
  • Near-to-far-field projection radiates into one homogeneous, lossless medium: vacuum by default, or the index given by far_field(..., n_medium=...). A layered half-space, such as cladding over a substrate, needs a layered Green's function, which PhotonHub does not have.
  • An open far-field surface, a single plane or a FarFieldMonitor box with a face left out (exclude), is a truncated surface (a Kirchhoff approximation): it reads the pattern well only where the field it cuts off is small. For a point dipole it is not. In a medium of index 1.45, at 20 and 30 cells per wavelength, a plane 0.62 µm from the dipole and 2.41 µm in half width misreads the pattern by 36 to 37 % of the peak within 30° of its normal (7.5 to 7.8 % with an edge window of 0.4, which costs accuracy toward the plane's edge), and a box 0.62 µm in half width with a face left out by about 10 % within 30° of its closed side (10.1 to 10.2 % from 20 to 60 cells; the faces kept are read up to a cell from their grid lines, a residual first order in the cell of at most about 0.7 points within 60° at 20 cells). Where the open edge is declared matters: moving it one cell moved that pattern by 8.5 % of the peak. In one medium the power within a cone reads closer (within 2 % for the open box, 7.4 % for the plane). Over a substrate (a dipole 0.48 µm above index 1.45) the box open on the substrate's side reads the upper half space 13.5 to 13.7 % off within 30°, and its cone power is off too, by +3.5 to +3.9 % within 30° and -10.8 % within 80°; a windowed plane's by +17 % within 30°. A wider plane with a window helps (on exact dipole fields, a half width of 14 medium wavelengths (index 1.45), 0.9 from the dipole, and a window of 0.2: 0.27 % within 30°); a larger open box much less. Close the box in one medium wherever the scene allows; see When an open surface holds. A box with a face left out given by hand as a face list is read without its open edge (6 to 9 % of the peak from the declared box within 30°, 7.6 to 11.6 % over the whole sphere, on exact dipole fields at 20 to 30 cells); declare it with exclude=.
  • The exact far-field projection, far_field(..., exact=True), sums the field over every recorded sample for points other than a meshed grid on a plane parallel to one recorded plane. Its cost grows as the number of points times the number of samples. Near the surface it loses accuracy: on a dipole's exact field 3 to 6 % of the peak one sample spacing out at 20 cells per wavelength, and over a box's edges more on finer meshes (up to 11 % at 40 cells and 21 % at 80; the readout's own error reached from two, three and four spacings). It warns within two spacings or a tenth of a wavelength, whichever is farther. An open surface carries its truncation error into the exact projection as into the far field.
  • The inverse-design ModePower objective has an uncalibrated scene/grid-dependent scale; its adjoint gradient is the exact derivative of that objective for the subpixel-off model over the recorded run window. Report transmission through S-parameters or transmission(), rather than the objective as absolute transmission.
  • PowerMonitor results use whole-device power with symmetry= planes. On a domain fitted with domain=, windows with an edge on a symmetry plane or crossing it asymmetrically are refused. A window on the kept side of an even (PMC) plane is also refused if its edge lies between the plane and the first cell's centre. A later mesh change can trigger that refusal when reading the result. data.wire(name) returns the modeled part's power.
  • select_resonances() defaults to require_decay=True, which can drop an undamped mode in a closed lossless cavity. Use require_decay=False for that case.
  • A single pml_num_layers value applies to all axes. Boundary layers need enough space to leave a usable simulation interior.
  • A source inside the PML or absorber layers is refused before the run, because the boundary absorbs the part inside it and the spectra cannot be trusted. Every sample of a mode launch or explicit beam window must clear the layers; a beam's default window stops at them. run_local(..., allow_sources_in_boundary=True) runs it anyway, for boundary diagnostics only; the cloud always refuses it.

These restrictions do not replace construction checks, check_runnable() or validation by the selected engine. See Concepts.

Open issues in 0.1.6

These are known defects in photonhub 0.1.6, each with a workaround where one exists.

  • The dft_shutoff stop rule can run to the step cap. On a local CPU run, the automatic spectrum-convergence stop waits until every included recorded frequency has settled (frequencies the source barely drives are left out). A slowly decaying, high-Q or multi-mode recording can still hold it to its time cap after the energy rule has passed. The cap warning then says how many monitor frequencies were still settling and names the furthest one. Set run=ph.RunSpec(..., dft_shutoff=1e-3) for a looser stop, or dft_shutoff=0 to stop on the energy rule alone.
  • The Workbench does not keep dft_shutoff=0. The Run dialog applies the default stop rule and Save removes the value, even from a file that holds it. A file written with sim.to_file keeps the value when a script reloads it with ph.Simulation.from_file, so run such a scene from the script.
  • The default centre of a hand-built mode source on a symmetry axis. mode_source, mode_launch when it has no recorded solve centre, the hand-built mode readout and the Workbench mode source default to the middle of the simulated domain. On an axis with a symmetry plane that is the middle of the modeled half, not the plane. Pass center_um with the symmetry plane's coordinate on that axis. Ports declared with ph.Port and beams are not affected.
  • Use symmetry=, not a pmc outer boundary, for a mirror plane. The port readout does not treat a pmc wall as a symmetry plane, so a port centred on one reads half of its power.
  • A ph.Port reads wrong when its thickness lies on the plane's first axis. Take the two axes of a port plane in x, y, z order. When thickness_axis is the first of them ("y" on an x-normal port, "x" on a y-normal or z-normal port), the mode is solved as the other polarization, so mode="TE0" gives the guide's TM0. The mode window's width and thickness land on swapped axes, and a fitted domain can refuse a wide port for lack of room. The readout also appears to compare against a reference mode turned by 90 degrees. In one check (a 0.5 x 0.22 um silicon strip in oxide, uniform 0.05 um mesh, contour subpixel averaging, no dispersion, 12 PML layers), the lossless straight guide built this way read a transmission above 5. Asking for the other mode name gave the right mode but still read above 2, and about 1 only once the readout's turn was also removed. The library builders pass their thickness_axis to their ports, so the same applies to them. Workaround: turn the device, not only the argument, so that each port's thickness lies on the second axis of its plane. For a guide along x or y, that is a slab normal along z, the default; built that way, the same strip read about 1. For a guide along z, the same rule puts the thickness along y; that case has not been checked.
  • Several GPU fixes need a current cloud engine. 0.1.4 fixed PEC structures on graded meshes with device="gpu:all", subpixel rows at an even symmetry plane, and dipoles and source planes that touch PEC structures. The 0.1.5 GPU solver image passes their equivalence tests on GPU hardware; a cloud engine deployed from an older image does not have them. Check git_sha in result provenance, and for such scenes check one run against the CPU solver.
  • Some features run on the CPU solver only. Bloch boundaries (and so an oblique plane wave), TFSFBox sources, permittivity_data media (including media built from a temperature or carrier map, see Temperature and carrier maps), pmc outer boundaries and CW sources are refused on a GPU before the run starts. Run them with device="cpu". The dft_shutoff stop rule is also CPU-only; on a GPU it is dropped with a warning and the run stops on the energy rule. The full list is photonhub.capabilities.CPU_ONLY_FEATURES.
  • A plane wave in a high-index medium with auto-shutoff off. One review run diverged for a plane wave in a medium of index 2.8 or more with shutoff=0. Keep auto-shutoff on for such scenes and check data.aborted.
  • Thickened boundary layers are not checked against port planes. After raising pml_num_layers on a declared simulation, check with plot_index that each port's mode monitor still sits in the interior.
  • The Workbench result viewer reads the solver's frame. For a simulation fitted with domain=, the viewer and the MCP server show coordinates offset by origin_um from the ones in your script. Read such results in Python with RunResult, which restores your frame.
  • A result saved by 0.1.6 with poles, a Drude term, an anisotropic permittivity or a permittivity grid has no ports on reload. 0.1.6 could not read such a simulation file back, so run_local did not write its client state. RunResult(path) now loads the simulation and the absolute units, but the coordinates stay in the solver's frame, a plane across a symmetry plane reads as the modeled half, and the declared ports do not come back. To read its ports, use 0.1.6: build the simulation there and pass it as simulation=. Runs on this SDK write the state and reload in full.
  • Workbench auto-mesh on two starters. Auto-mesh on the default starter scene and on the Fresnel slab example can produce a document the solver refuses. Set the mesh by hand for those two.

Preservation and compatibility

Simulation JSON stores resolved solver inputs. It omits declarative setup, ports and the user-frame origin. Keep the construction script and complete result directory. From photonhub 0.1.4, client.json preserves result readout state; older results do not have it. See Schema and versioning.

There is no photonhub.migrations module. Incompatible major schema versions are rejected. Desktop recovery is not a backup, and older versions are not guaranteed to read newer archives. Keep backups before changing versions.

Cloud operations

Cloud execution is metered and capacity-dependent. A quote does not reserve capacity. A wait deadline does not stop a service job or its billing, and neither does Ctrl-C while a blocking cloud run waits. Request cancellation with job.cancel(), or resume the existing job by its id and cancel it. After an ambiguous submission failure, reconcile job history before trying again.

A cloud service older than your SDK can quote a one-cell periodic (quasi-2D) axis on a different grid. The SDK then refuses the quote with CloudError and submits nothing. Run quasi-2D scenes locally until the service is updated.

No service availability, speed, price or accuracy promise follows from these examples. See Cloud GPU runs.