Skip to content

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 in glibc2.28 and glibc2.17 builds. 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:

python3 --version

On Linux, also check the host's glibc version before installing:

ldd --version

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.

python3 -m venv .venv
source .venv/bin/activate

On macOS, or Linux with glibc 2.27 or newer, install:

python -m pip install photonhub

On Linux below glibc 2.27, use:

python -m pip install --only-binary=:all: photonhub

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:

photonhub install-solver --url "$SOLVER_URL" --sha256 "$SOLVER_SHA256"

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:

photonhub install-solver --archive "$SOLVER_ARCHIVE" --sha256 "$SOLVER_SHA256"

Check what the client will actually run, and why it picked it, and ask that binary to describe its build:

photonhub which-solver --info

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:

shasum -a 256 -c "$SOLVER_ARCHIVE.sha256"

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… or No solver found.: nothing is installed or linked, $PHOTONHUB_SOLVER is not set, and there is no phsolver on PATH. Install the solver as in step 2. If which-solver says the recorded solver is missing or not executable, its files were moved, deleted or changed: install again, or run photonhub uninstall-solver to 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, or unset PHOTONHUB_SOLVER to 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 unset PHOTONHUB_SOLVER if 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, keep phsolver beside its bundled libomp.dylib on macOS or libgomp.so.1 on Linux. On a batch system the solver uses the CPUs the job was given, and OMP_NUM_THREADS sets the count.
  • Linux GLIBC_2.xx not found: compare ldd --version with the archive choice above and reinstall the matching build.
  • Linux Permission denied despite an executable file: the installation directory may be mounted noexec. Set PHOTONHUB_SOLVER_HOME to 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 with xattr -dr com.apple.quarantine "$SOLVER_DIR". install-solver does 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.