Metadata-Version: 2.4
Name: xoptics-client
Version: 0.3.0
Summary: Python client for the xoptics remote simulation API
Author: xoptics
License: MIT
Project-URL: Homepage, https://xoptics.org
Project-URL: Repository, https://github.com/xoptics-dev/xoptics-cloud
Project-URL: Issues, https://github.com/xoptics-dev/xoptics-cloud/issues
Project-URL: Documentation, https://github.com/xoptics-dev/xoptics-cloud/tree/main/client
Keywords: optics,simulation,diffraction,grating,waveguide,ray-tracing,FMM,RCWA
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27
Requires-Dist: h5py>=3.10
Requires-Dist: numpy>=1.24
Provides-Extra: pandas
Requires-Dist: pandas>=2.0; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"

# xoptics-client

Python client for the xoptics remote simulation API.

## Install

For now: drop [`xoptics_client.py`](xoptics_client.py) next to your notebook. (PyPI package will come later.)

Dependencies:

```bash
pip install httpx h5py numpy
# Optional, for to_dataframe():
pip install pandas
```

## Usage

```python
import os
from xoptics_client import Client

# Pass api_key=..., or set XOPTICS_API_KEY env var
c = Client(api_key=os.environ["XOPTICS_API_KEY"])

# Submit, stream stdout live, wait for completion
result = c.run(
    xml=open("scene.xml").read(),
    cores=64,
    label="my_design_v3",
    on_log=print,         # any callable taking a single str line
)

# Inspect detectors
print(result.detectors())
arr = result.detector("eyebox_g").to_array()    # numpy
df  = result.detector("eyebox_g").to_dataframe()  # pandas (optional)

# Save h5 locally
result.save("./my_result.h5")
```

## Async / fire-and-forget

```python
job = c.submit(xml=..., cores=64, label="long_run")
print(job.id)             # save this somewhere
# ... come back later, possibly from a different process ...
job = c.get_job("abc123def456")
job.wait(on_log=print)
result = job.result()
```

## List your jobs

```python
for j in c.jobs(limit=20):
    print(f"{j.id}  {j.status:7s}  {j.elapsed_sec or 0:.1f}s  {j.label}")
```

## Monitor + cancel

```python
# Your own usage: cores in use, lifetime CPU-hours, running jobs with progress %
c.print_usage()
# demo (Demo user)  [sk_test_qkML...EWEU]
#   cores in use:      32
#   lifetime CPU-hrs:  1.905
#   running jobs:      1
#     457fbec87414  cores= 32   59.5%  elapsed=00:00:30  remaining=00:00:20  'my run'

# Cancel a specific job
c.cancel_job("457fbec87414")
# or:
job.cancel()
```

## Admin: see all users' usage

If your key has `role: admin`:

```python
c.print_fleet_usage()
# server cores: 384 total | 32 in use | 352 free
# ────────────────────────────────────────────────
# admin (Admin)  [sk_live__mcZ...3_LI]  role=admin
#   cores in use:      0
#   lifetime CPU-hrs:  0.456
#   running jobs:      (none)
#
# demo (Demo user)  [sk_test_qkML...EWEU]
#   cores in use:      32
#   lifetime CPU-hrs:  1.905
#   running jobs:      1
#     457fbec87414  cores= 32   59.5%  elapsed=00:00:30  remaining=00:00:20  'my run'
```

Admin can also cancel any user's job through `c._http.delete("/admin/jobs/{id}")`.

## Error handling

```python
from xoptics_client import Client, AuthError, JobFailedError

try:
    result = c.run(xml=..., cores=64)
except AuthError:
    # bad API key, or hitting an admin-only endpoint as a regular user
    ...
except JobFailedError as e:
    # xTracerCL ran but exited non-zero (bad XML, runtime error, etc.)
    print(e)
```

## Configuration

`Client(api_key=..., base_url="https://api.xoptics.org", timeout=30.0)`

- `api_key` — string, or set `XOPTICS_API_KEY` env var
- `base_url` — override only if you're testing against a different deployment
- `timeout` — for individual HTTP requests; streaming (`run`, `wait(on_log=...)`) is not capped

## How it works under the hood

```
client                    server (api.xoptics.org)
──────                    ────────────────────────
c.submit(...)   POST /v1/jobs       ──► server writes input.xml,
                                       launches xTracerCL,
                                       returns job_id immediately

c.run(...)      POST /v1/jobs
                GET  /v1/jobs/{id}/stream  (SSE)  ──► live stdout pushed
                                                       as text/event-stream
                GET  /v1/jobs/{id}/result          ──► h5 file binary
                                                       (cached locally)
```

The `on_log=` callback is invoked once per line as it's emitted by `xTracerCL`, including in-progress messages like `4.5M/21.6M rays processed, 21%`.

## Examples

See [`examples/demo.py`](examples/demo.py).
