Headless CPU quickstart (pip SDK + standalone solver, no GUI)¶
Audience. Invited beta participants who drive simulations from Python scripts, notebooks, SSH sessions, or batch machines and do not want the desktop application. Local CPU runs are free after beta admission. The GUI path is documented separately in
desktop-beta-quickstart.md; building from source requires repository access.Platforms. Packaged headless solvers are available for macOS 14+ on arm64 (
macos-arm64) and Linux x86-64 inglibc2.28andglibc2.17builds. The Linux builds target baseline x86-64 and require no AVX. Windows script users need repository access to build from source.
The headless path has exactly two pieces:
| Piece | What | Where it comes from |
|---|---|---|
SDK (import photonhub as ph) |
open-source pure-Python client, Apache-2.0 | Python package index |
Solver (phsolver) |
proprietary compiled CPU engine, GUI-free | invitation-gated download (archive + .sha256) |
The solver archive is distribution-gated: it is provided to invited beta participants only, alongside its SHA-256 checksum, and is bound by the Community Beta terms included in the archive. It performs no sign-in and sends nothing over the network; local runs stay local.
1. Install the SDK¶
Python 3.11 or newer is required. Check the version before creating .venv.
If Python is missing or the version is older than 3.11, install Python 3.11
or newer, open a new terminal, and check again. If .venv was created with an
older Python, run rm -rf .venv before trying again; creating it over the old
directory keeps the old interpreter. On macOS, the .venv folder is hidden in
Finder because its name starts with a dot.
On macOS or Linux, check:
On Linux, also check the host's glibc version before installing:
If the newer Python has a versioned name (for example python3.12), use that
name in the version check and environment command instead of python3.
On macOS, or Linux with glibc 2.27 or newer, install:
On Linux below glibc 2.27, use:
This selects compatible binary wheels for the SDK's dependencies instead of attempting a source build.
For this published package, pip on an older Python reports
No matching distribution found for photonhub.
2. Install the solver¶
The photonhub command used here requires photonhub 0.1.4 or later. Check
yours with python -m pip show photonhub; with an older SDK, upgrade with
python -m pip install -U photonhub or use the manual route at the end of this section.
On Linux below glibc 2.27, also pass --only-binary=:all: when upgrading.
Choose the Linux archive¶
Read the glibc version from ldd --version. Use the glibc2.28 archive
on glibc 2.28 or newer, and the glibc2.17 archive on anything older.
The latter requires at least glibc 2.17. These are baseline x86-64 builds
with no AVX requirement. Select the URL or archive and checksum for that
build before following the shared installation steps below.
Install the selected archive¶
Set SOLVER_URL to the invitation download URL and SOLVER_SHA256 to the
issued digest. These are invitation-specific values, not literal placeholders.
Then download and verify explicitly:
It downloads, checks the digest against the one you were given, unpacks into
~/.cache/photonhub/solver, and records the result. Nothing to extract by
hand, and no environment variable to set or remember.
Use this alternative instead of the URL command if you already have the
archive. Set SOLVER_ARCHIVE to its filename and
use the same issued SOLVER_SHA256:
Check what the client will actually run, and why it picked it, and ask that binary to describe its build:
The last line is a compact JSON build description.
"workbench_authorization_required":false confirms this is the headless
build, and "openmp":true inside the "cpu" object confirms the multicore
runtime loaded.
The SDK resolves the solver in this order: an explicit solver_path=
argument, $PHOTONHUB_SOLVER, the solver you installed or linked, then
PATH, then an eligible build/phsolver in a source checkout.
The environment variable still wins, so a one-off override needs no
uninstall. photonhub uninstall-solver forgets the recorded one and leaves
the files alone.
Pass --sha256 whenever you have it: the digest, or the whole line of the
.sha256 file. Without it the archive is checked only against its own
provenance.json. That catches a truncated download but cannot tell you the
bytes are the ones we published.
Manual route¶
This works with every SDK release. Verify the archive against
the .sha256 file delivered with it, extract it, and point the SDK at the
solver. Set SOLVER_ARCHIVE to the archive filename and SOLVER_DIR to an
empty directory you choose, such as ./photonhub-solver. Run from the directory
containing the archive and its checksum file. On Linux, use
sha256sum -c "$SOLVER_ARCHIVE.sha256" for the first line:
On Linux, use sha256sum -c "$SOLVER_ARCHIVE.sha256" instead. After the
checksum passes, on either platform:
mkdir -p "$SOLVER_DIR"
tar -xzf "$SOLVER_ARCHIVE" -C "$SOLVER_DIR"
export PHOTONHUB_SOLVER="$SOLVER_DIR/solver/phsolver"
"$PHOTONHUB_SOLVER" info
info prints the same JSON build description as above. Put the export line
in your startup file (or job scripts) to make it permanent. With a release
that has the photonhub command, photonhub link-solver "$SOLVER_DIR"
records the unpacked solver instead, and no variable is needed.
3. Run a simulation¶
This short periodic-domain example checks execution and result loading. It is not a convergence study.
import photonhub as ph
sim = ph.Simulation(
size_um=(2.0, 2.0, 2.0),
grid=ph.UniformMesh(dl_um=0.05),
run=ph.RunSpec(n_steps=200),
boundaries=ph.Boundaries(x="periodic", y="periodic", z="periodic"),
sources=[
ph.PointDipole(
center_um=(1.0, 1.0, 1.0),
polarization="Ez",
source_time=ph.GaussianPulse(freq0_hz=1.934e14, fwidth_hz=4.0e13),
)
],
monitors=[
ph.TimeMonitor(name="probe", center_um=(1.5, 1.0, 1.0), fields=["Ez"]),
ph.SnapshotMonitor(name="final", fields=["Ez", "Hx"]),
],
)
data = ph.run_local(sim, output_dir="out", device="cpu")
print(data["probe"]) # xarray DataArray, dims (t, component)
The same SDK APIs are available with a headless solver, including
sim.to_file("device.sim.json") for portable solver inputs, the materials
library, mode solving and DFT monitors.
Wire JSON omits declarative ports and the user-frame origin. Keep your
construction script and the entire result directory, including client.json,
for those workflows. photonhub 0.1.4 and later write this result-state
record; older SDKs and result directories do not have it.
Cloud GPU (optional)¶
Cloud runs need no local engine. The same SDK submits to the metered cloud GPU service and requires an authenticated beta account with a quoted, pre-committed price per run:
# CLOUD ONLY, BILLABLE: needs PHOTONHUB_URL and PHOTONHUB_API_KEY from your invitation,
# and GPU and SOLVER set as the cloud guide shows.
data = ph.cloud.run_quoted(sim, device=GPU, solver=SOLVER, max_usd=5.00) # quotes first
Troubleshooting¶
Start with photonhub which-solver. It names the binary the SDK will run and
the reason it was chosen. On the manual route with an SDK older than 0.1.4, run
echo "$PHOTONHUB_SOLVER" instead.
phsolver engine binary not found…orNo solver found.: nothing is installed or linked,$PHOTONHUB_SOLVERis not set, and there is nophsolveronPATH. Install the solver as in step 2. Ifwhich-solversays the recorded solver is missing or not executable, its files were moved, deleted or changed: install again, or runphotonhub uninstall-solverto forget it.$PHOTONHUB_SOLVER is not an executable file: …: the variable is set and wins over the installed solver, but it names nothing runnable. Fix the path, orunset PHOTONHUB_SOLVERto use the installed one.authorization denied: sign in through PhotonHub…: the selected binary is the desktop app's bundled solver, which only runs under a signed-in Workbench. Install the headless archive, or unsetPHOTONHUB_SOLVERif it points into the app.- Single-threaded performance:
photonhub which-solver --info(on the manual route,"$PHOTONHUB_SOLVER" info) must report"openmp": true. For a solver you unpacked yourself, keepphsolverbeside its bundledlibomp.dylibon macOS orlibgomp.so.1on Linux. On a batch system the solver uses the CPUs the job was given, andOMP_NUM_THREADSsets the count. - Linux
GLIBC_2.xx not found: compareldd --versionwith the archive choice above and reinstall the matching build. - Linux
Permission denieddespite an executable file: the installation directory may be mountednoexec. SetPHOTONHUB_SOLVER_HOMEto a directory on an executable filesystem and install again. - macOS blocks the binary: this affects a solver you unpacked yourself
(the manual route, or
link-solver), because archives fetched with a browser carry the quarantine attribute. Clear it withxattr -dr com.apple.quarantine "$SOLVER_DIR".install-solverdoes not copy the attribute.
Construction and preflight can raise ValueError; solver execution and local
timeouts raise SolverRunError. Cloud transport and wait deadlines have
different handling. See Troubleshooting
and Known limitations.
For help, contact beta@leapfield.ai.