diff --git a/README.md b/README.md index 67e7a97..87df3b7 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,15 @@ Xanadu-style job conversion example: bash examples/hardware_integration/run.sh ``` +Aurora/QCA/GKP fixture conversions: + +```bash +bash examples/hardware_integration/run_public_datasets.sh +``` + +For large real datasets, use converter streaming/chunk flags: +`--stream --shot-start --max-shots [--append-out]`. + Replay converted NDJSON through the C++ adapter: ```bash diff --git a/docs/hardware-integration.md b/docs/hardware-integration.md index 8b5f6de..aed2fa0 100644 --- a/docs/hardware-integration.md +++ b/docs/hardware-integration.md @@ -29,13 +29,26 @@ Key fields: Each line is one `DecodeRequest` JSON object. See `schemas/decoder_io_example.ndjson` for examples. -### Xanadu job conversion helper +### Xanadu dataset conversion helper -Use the built-in converter example to transform Xanadu-style job outputs into -`DecodeRequest` NDJSON: +Use the built-in converter example to transform Xanadu data into +`DecodeRequest` NDJSON. + +Supported source modes: + +- `xanadu_job_json`: legacy job payloads with `output`/`samples`. +- `aurora_switch_dir`: Aurora decoder-demo batch directory with + `switch_settings_qpu_*.npy` (or `.json` in fixture mode). +- `shot_matrix`: generic shot arrays from `.json`, `.npy`, or `.npz` + (covers QCA `samples.npy`). +- `count_table_json`: count-compressed outcomes (`sample` + `count`), useful for + GKP-style exports. + +Legacy job JSON: ```bash python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format xanadu_job_json \ --input /path/to/xanadu_job.json \ --mapping /path/to/your_mapping.json \ --out examples/results/hardware_integration/decoder_requests.ndjson @@ -47,9 +60,85 @@ Quick demo: bash examples/hardware_integration/run.sh ``` +Aurora / QCA / GKP fixture demos: + +```bash +bash examples/hardware_integration/run_public_datasets.sh +``` + +Real Aurora decoder-demo batch: + +```bash +python3 -m pip install numpy + +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format aurora_switch_dir \ + --stream \ + --input /path/to/decoder_demo/signal/batch_0 \ + --mapping examples/hardware_integration/xanadu_aurora_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_aurora.ndjson \ + --aurora-binarize \ + --max-shots 20000 \ + --progress-every 5000 +``` + +Real QCA sample matrix: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --max-shots 50000 \ + --progress-every 10000 +``` + +Chunk large QCA files by repeating conversion with shifted `--shot-start` and `--append-out`: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --shot-start 0 \ + --max-shots 200000 + +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --append-out \ + --shot-start 200000 \ + --max-shots 200000 +``` + +Count-compressed GKP outcomes: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format count_table_json \ + --input /path/to/gkp_outcome_counts.json \ + --mapping examples/hardware_integration/xanadu_gkp_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_gkp.ndjson +``` + The mapping file controls how measured modes are converted to syndrome events. See `examples/hardware_integration/xanadu_syndrome_mapping_example.json`. +Large-data controls: + +- `--stream`: use memory-mapped loading where possible (notably `.npy`). +- `--shot-start`: skip the first `N` expanded shots. +- `--max-shots`: cap this run to `K` shots. +- `--append-out`: append to an existing NDJSON file. +- `--progress-every`: print progress every `M` written requests. + ### Replay NDJSON through LiDMaS+ adapter Use the C++ CLI replay mode to decode each NDJSON `DecodeRequest` line and write diff --git a/docs/releases/v1.2.0-rc.1.md b/docs/releases/v1.2.0-rc.1.md new file mode 100644 index 0000000..47a302e --- /dev/null +++ b/docs/releases/v1.2.0-rc.1.md @@ -0,0 +1,58 @@ +# LiDMaS+ v1.2.0-rc.1 + +Release date: 2026-03-17 + +Tag: `v1.2.0-rc.1` +Package version: `1.2.0rc1` + +This release candidate focuses on Xanadu hardware-data integration and large-dataset conversion reliability. + +## Highlights + +- Added multi-format Xanadu data conversion into `decoder_io` NDJSON: + - legacy job JSON (`output`/`samples`) + - Aurora decoder-demo switch-setting batches + - QCA/Borealis shot matrices (`.json`, `.npy`, `.npz`) + - count-compressed outcome tables (GKP-style exports) +- Added C++ replay workflow integration for converted NDJSON requests. +- Added streaming/chunked conversion controls for heavy datasets: + - `--stream` + - `--shot-start` + - `--max-shots` + - `--append-out` + - `--progress-every` +- Added runnable fixtures/scripts for Aurora/QCA/GKP conversion and replay validation. + +## Included Changes Since v1.1.4 + +- `feat(hardware): add decoder_io NDJSON replay CLI and Xanadu integration example` (`431b0c9`) +- `feat(hardware): add streaming Xanadu dataset integration` (`a544e2b`) + +Comparison: +`https://github.com/DennisWayo/lidmas_cpp/compare/v1.1.4...v1.2.0-rc.1` + +## Validation Performed + +- Python compile check: + - `python3 -m py_compile examples/hardware_integration/convert_xanadu_job_to_decoder_io.py` +- Fixture conversions: + - `bash examples/hardware_integration/run.sh` + - `bash examples/hardware_integration/run_public_datasets.sh` +- Replay checks: + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_aurora.ndjson` + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_qca.ndjson` + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_gkp.ndjson` +- Result: replay completed with `errors=0` on fixture datasets. + +## Notes + +- `.npy/.npz` conversion paths require NumPy (`pip install numpy`). +- This is an RC intended for real-data validation at scale before final `v1.2.0`. +- No breaking changes are expected for existing workflows. + +## Upgrade / Usage + +- Use this RC tag for validation runs: + - `git checkout v1.2.0-rc.1` +- For large QCA/Aurora inputs, prefer streaming/chunking: + - `--stream --shot-start --max-shots [--append-out]` diff --git a/docs/releases/v1.2.0-rc.2.md b/docs/releases/v1.2.0-rc.2.md new file mode 100644 index 0000000..d498d56 --- /dev/null +++ b/docs/releases/v1.2.0-rc.2.md @@ -0,0 +1,58 @@ +# LiDMaS+ v1.2.0-rc.2 + +Release date: 2026-03-17 + +Tag: `v1.2.0-rc.2` +Package version: `1.2.0rc2` + +This release candidate focuses on Xanadu hardware-data integration and large-dataset conversion reliability. + +## Highlights + +- Added multi-format Xanadu data conversion into `decoder_io` NDJSON: + - legacy job JSON (`output`/`samples`) + - Aurora decoder-demo switch-setting batches + - QCA/Borealis shot matrices (`.json`, `.npy`, `.npz`) + - count-compressed outcome tables (GKP-style exports) +- Added C++ replay workflow integration for converted NDJSON requests. +- Added streaming/chunked conversion controls for heavy datasets: + - `--stream` + - `--shot-start` + - `--max-shots` + - `--append-out` + - `--progress-every` +- Added runnable fixtures/scripts for Aurora/QCA/GKP conversion and replay validation. + +## Included Changes Since v1.1.4 + +- `feat(hardware): add decoder_io NDJSON replay CLI and Xanadu integration example` (`431b0c9`) +- `feat(hardware): add streaming Xanadu dataset integration` (`a544e2b`) + +Comparison: +`https://github.com/DennisWayo/lidmas_cpp/compare/v1.1.4...v1.2.0-rc.2` + +## Validation Performed + +- Python compile check: + - `python3 -m py_compile examples/hardware_integration/convert_xanadu_job_to_decoder_io.py` +- Fixture conversions: + - `bash examples/hardware_integration/run.sh` + - `bash examples/hardware_integration/run_public_datasets.sh` +- Replay checks: + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_aurora.ndjson` + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_qca.ndjson` + - `bash examples/hardware_integration/replay.sh examples/results/hardware_integration/decoder_requests_gkp.ndjson` +- Result: replay completed with `errors=0` on fixture datasets. + +## Notes + +- `.npy/.npz` conversion paths require NumPy (`pip install numpy`). +- This is an RC intended for real-data validation at scale before final `v1.2.0`. +- No breaking changes are expected for existing workflows. + +## Upgrade / Usage + +- Use this RC tag for validation runs: + - `git checkout v1.2.0-rc.2` +- For large QCA/Aurora inputs, prefer streaming/chunking: + - `--stream --shot-start --max-shots [--append-out]` diff --git a/examples/README.md b/examples/README.md index 0ca74cd..83b4108 100644 --- a/examples/README.md +++ b/examples/README.md @@ -27,7 +27,7 @@ LIDMAS_SKIP_PY_DEPS=1 ./examples/hybrid_threshold/run.sh - `decoder_comparison/`: same sweep across multiple decoders. - `failure_debug/`: stress run and failure-dump capture workflow. - `plot_only/`: publication-grade plotting from existing CSV files. -- `hardware_integration/`: convert Xanadu-style job output JSON to LiDMaS+ decoder IO NDJSON. +- `hardware_integration/`: convert Xanadu datasets (Aurora/QCA/GKP/job JSON) to LiDMaS+ decoder IO NDJSON. ## Central Results Folder diff --git a/examples/hardware_integration/README.md b/examples/hardware_integration/README.md index 4fa96d6..569628e 100644 --- a/examples/hardware_integration/README.md +++ b/examples/hardware_integration/README.md @@ -1,13 +1,29 @@ # hardware_integration -This example converts Xanadu-style job outputs to LiDMaS+ `decoder_io` NDJSON. +This example converts Xanadu datasets to LiDMaS+ `decoder_io` NDJSON. +The same converter now supports: + +- legacy Xanadu job JSON payloads, +- Aurora decoder-demo switch-setting directories, +- QCA/Borealis-style shot matrices (`samples.npy`), +- count-compressed outcomes (useful for GKP-style exports). ## Files - `convert_xanadu_job_to_decoder_io.py`: converter script. - `xanadu_job_result_example.json`: minimal sample job payload. - `xanadu_syndrome_mapping_example.json`: mode-to-syndrome parity mapping. +- `aurora_batch_example/`: tiny Aurora-style batch with `switch_settings_qpu_*.json`. +- `xanadu_aurora_mapping_example.json`: Aurora mapping. +- `xanadu_qca_samples_example.json`: QCA-like shot-matrix fixture. +- `xanadu_qca_mapping_example.json`: QCA mapping. +- `xanadu_gkp_counts_example.json`: count-compressed outcome fixture. +- `xanadu_gkp_mapping_example.json`: GKP mapping. - `run.sh`: one-command local demo. +- `run_aurora.sh`: Aurora conversion demo. +- `run_qca.sh`: QCA conversion demo. +- `run_gkp.sh`: GKP count-table conversion demo. +- `run_public_datasets.sh`: run Aurora + QCA + GKP demos. - `replay.sh`: decode generated requests via `./build/lidmas --decoder_io_replay`. ## Quick Run @@ -22,6 +38,18 @@ Output: Each NDJSON line is a `DecodeRequest` compatible with `schemas/decoder_io.proto`. +Run all public-dataset fixtures: + +```bash +bash examples/hardware_integration/run_public_datasets.sh +``` + +Outputs: + +- `examples/results/hardware_integration/decoder_requests_aurora.ndjson` +- `examples/results/hardware_integration/decoder_requests_qca.ndjson` +- `examples/results/hardware_integration/decoder_requests_gkp.ndjson` + Replay those requests through the C++ adapter: ```bash @@ -32,9 +60,118 @@ Replay those requests through the C++ adapter: --decoder_io_continue_on_error ``` +Helper script (auto-derives response filename): + +```bash +bash examples/hardware_integration/replay.sh \ + examples/results/hardware_integration/decoder_requests_aurora.ndjson +``` + ## Real Xanadu Job Data -Use the converter with your exported job JSON: +### Aurora decoder_demo batch (switch settings) + +Requires NumPy when reading `.npy` files: + +```bash +python3 -m pip install numpy +``` + +Convert one Aurora batch directory: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format aurora_switch_dir \ + --input /path/to/decoder_demo/signal/batch_0 \ + --mapping examples/hardware_integration/xanadu_aurora_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_aurora.ndjson \ + --aurora-binarize \ + --max-shots 20000 \ + --meta hardware=xanadu \ + --meta dataset=aurora_decoder_demo +``` + +For large Aurora batches, enable streaming and progress logs: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format aurora_switch_dir \ + --stream \ + --input /path/to/decoder_demo/signal/batch_0 \ + --mapping examples/hardware_integration/xanadu_aurora_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_aurora.ndjson \ + --aurora-binarize \ + --max-shots 200000 \ + --progress-every 50000 +``` + +### QCA / Borealis samples.npy + +Convert `samples.npy` (shape typically `n x 1 x M`): + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --max-shots 50000 \ + --meta hardware=xanadu \ + --meta dataset=qca +``` + +Chunked QCA conversion (memory-safe over very large files): + +```bash +# chunk 1 +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --shot-start 0 \ + --max-shots 200000 \ + --progress-every 50000 + +# chunk 2 (append) +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format shot_matrix \ + --stream \ + --input /path/to/fig3a/samples.npy \ + --mapping examples/hardware_integration/xanadu_qca_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_qca.ndjson \ + --append-out \ + --shot-start 200000 \ + --max-shots 200000 \ + --progress-every 50000 +``` + +### GKP outcome counts + +For count-compressed outcomes exported from your analysis notebook: + +```bash +python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ + --source-format count_table_json \ + --input /path/to/gkp_outcome_counts.json \ + --mapping examples/hardware_integration/xanadu_gkp_mapping_example.json \ + --out examples/results/hardware_integration/decoder_requests_gkp.ndjson \ + --max-shots 100000 \ + --meta hardware=xanadu \ + --meta dataset=gkp +``` + +`gkp_outcome_counts.json` entries should look like: + +```json +{"counts":[{"sample":[0,1,0],"count":12},{"sample":[1,0,1],"count":5}]} +``` + +### Legacy Xanadu job JSON + +Use the converter with exported job JSON payloads: ```bash python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ @@ -62,3 +199,11 @@ python3 examples/hardware_integration/convert_xanadu_job_to_decoder_io.py \ This mapping is hardware- and experiment-specific. Illustrative mappings may produce non-physical syndromes; use `--decoder_io_continue_on_error` during early integration. + +## Large-Data Flags + +- `--stream`: uses NumPy memory-mapped loading when available (`.npy`), avoiding full in-memory expansion. +- `--shot-start N`: skip the first `N` expanded shots before writing. +- `--max-shots K`: write at most `K` shots this run. +- `--append-out`: append NDJSON to an existing output file. +- `--progress-every M`: emit progress every `M` written shots to stderr. diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_0.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_0.json new file mode 100644 index 0000000..3e575fb --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_0.json @@ -0,0 +1 @@ +[3,0,3,0,3,3,0,0] diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_1.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_1.json new file mode 100644 index 0000000..07766da --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_1.json @@ -0,0 +1 @@ +[0,3,0,3,0,3,0,3] diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_2.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_2.json new file mode 100644 index 0000000..85cb262 --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_2.json @@ -0,0 +1 @@ +[3,3,0,0,3,0,3,0] diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_3.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_3.json new file mode 100644 index 0000000..3ad8df8 --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_3.json @@ -0,0 +1 @@ +[0,0,3,3,0,3,0,3] diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_4.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_4.json new file mode 100644 index 0000000..b3d67a4 --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_4.json @@ -0,0 +1 @@ +[3,0,0,3,3,0,0,3] diff --git a/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_5.json b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_5.json new file mode 100644 index 0000000..ab782ec --- /dev/null +++ b/examples/hardware_integration/aurora_batch_example/switch_settings_qpu_5.json @@ -0,0 +1 @@ +[0,3,3,0,0,3,3,0] diff --git a/examples/hardware_integration/convert_xanadu_job_to_decoder_io.py b/examples/hardware_integration/convert_xanadu_job_to_decoder_io.py index 22b3c99..13f7df1 100755 --- a/examples/hardware_integration/convert_xanadu_job_to_decoder_io.py +++ b/examples/hardware_integration/convert_xanadu_job_to_decoder_io.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Convert Xanadu-style job JSON into LiDMaS+ decoder_io NDJSON.""" +"""Convert Xanadu datasets into LiDMaS+ decoder_io NDJSON.""" from __future__ import annotations @@ -15,6 +15,13 @@ "z": "Z", "unknown": "UNKNOWN", } +_SOURCE_FORMATS = ( + "auto", + "xanadu_job_json", + "shot_matrix", + "aurora_switch_dir", + "count_table_json", +) def _load_json(path: Path) -> Any: @@ -22,7 +29,35 @@ def _load_json(path: Path) -> Any: return json.load(f) -def _extract_samples(payload: Any) -> list[Any]: +def _require_numpy(): + try: + import numpy as np # type: ignore + except Exception as exc: # noqa: BLE001 + raise ValueError( + "NumPy is required for .npy/.npz inputs. Install it with: pip install numpy" + ) from exc + return np + + +def _load_numpy_array(path: Path, array_key: str, mmap_mode: str | None) -> Any: + np = _require_numpy() + obj = np.load(path, allow_pickle=False, mmap_mode=mmap_mode) + + if hasattr(obj, "files"): + files = list(getattr(obj, "files")) + if not files: + raise ValueError(f"{path}: .npz file has no arrays.") + key = array_key or files[0] + if key not in files: + raise ValueError(f"{path}: key {key!r} not found in .npz file. Keys: {files}.") + arr = obj[key] + obj.close() + return arr + + return obj + + +def _extract_samples_from_job_payload(payload: Any) -> list[Any]: candidates: list[Any] = [] if isinstance(payload, dict): candidates.extend( @@ -44,7 +79,6 @@ def _extract_samples(payload: Any) -> list[Any]: if isinstance(cand, list): if not cand: return [] - # If this is a single shot vector, wrap to one-shot list. if all(not isinstance(x, (list, dict)) for x in cand): return [cand] return cand @@ -54,18 +88,83 @@ def _extract_samples(payload: Any) -> list[Any]: ) +def _extract_samples_from_shot_matrix(payload: Any, array_key: str) -> Any: + if isinstance(payload, dict): + candidates: list[Any] = [] + if array_key: + candidates.append(payload.get(array_key)) + candidates.extend( + [ + payload.get("samples"), + payload.get("output"), + payload.get("shots"), + payload.get("data"), + ] + ) + for cand in candidates: + if isinstance(cand, list): + payload = cand + break + else: + raise ValueError( + "Could not locate shot list in shot-matrix input. " + "Provide --array-key or one of keys: samples, output, shots, data." + ) + + if isinstance(payload, list): + if not payload: + return [] + if all(not isinstance(x, (list, dict)) for x in payload): + return [payload] + return payload + + if hasattr(payload, "shape") and hasattr(payload, "ndim"): + return payload + + raise ValueError( + f"Shot-matrix payload must be list/object/array; got {type(payload).__name__}." + ) + + def _normalize_shot(shot: Any, shot_index: int) -> list[int]: if isinstance(shot, dict): if "output" in shot: shot = shot["output"] elif "samples" in shot: shot = shot["samples"] + elif "sample" in shot: + shot = shot["sample"] + elif "outcome" in shot: + shot = shot["outcome"] else: - raise ValueError(f"Shot {shot_index}: dict shot does not contain 'output' or 'samples'.") + raise ValueError( + f"Shot {shot_index}: dict shot does not contain output/samples/sample/outcome." + ) + + if hasattr(shot, "tolist") and not isinstance(shot, (list, tuple)): + shot = shot.tolist() + + if isinstance(shot, tuple): + shot = list(shot) + + while isinstance(shot, list) and len(shot) == 1 and isinstance(shot[0], (list, tuple)): + shot = list(shot[0]) + if not isinstance(shot, list): raise ValueError(f"Shot {shot_index}: expected list, got {type(shot).__name__}.") + out: list[int] = [] for i, v in enumerate(shot): + while isinstance(v, (list, tuple)) and len(v) == 1: + v = v[0] + if hasattr(v, "tolist") and not isinstance(v, (list, tuple, dict)): + maybe_list = v.tolist() + if isinstance(maybe_list, (list, tuple, dict)): + v = maybe_list + if isinstance(v, (list, tuple, dict)): + raise ValueError( + f"Shot {shot_index}, mode {i}: nested element found; expected scalar integer-like values." + ) try: iv = int(v) except (TypeError, ValueError) as exc: @@ -76,6 +175,75 @@ def _normalize_shot(shot: Any, shot_index: int) -> list[int]: return out +class _IntSequence: + def __init__(self, raw: Any, name: str): + self.name = name + self.raw = raw + self.kind = "" + self.length = 0 + + if isinstance(raw, tuple): + raw = list(raw) + self.raw = raw + + if isinstance(raw, list): + if not raw: + self.kind = "list1d" + self.length = 0 + return + if all(isinstance(x, (list, tuple)) and len(x) == 1 for x in raw): + self.kind = "list_n1" + self.length = len(raw) + return + if len(raw) == 1 and isinstance(raw[0], (list, tuple)): + self.kind = "list_1n" + self.length = len(raw[0]) + return + if any(isinstance(x, (list, tuple, dict)) for x in raw): + raise ValueError( + f"{name}: expected 1D numeric sequence, got nested data. " + "Provide 1D arrays for Aurora switch settings." + ) + self.kind = "list1d" + self.length = len(raw) + return + + if hasattr(raw, "shape") and hasattr(raw, "ndim"): + ndim = int(raw.ndim) + if ndim == 1: + self.kind = "arr1d" + self.length = int(raw.shape[0]) + return + if ndim == 2 and int(raw.shape[1]) == 1: + self.kind = "arr_n1" + self.length = int(raw.shape[0]) + return + if ndim == 2 and int(raw.shape[0]) == 1: + self.kind = "arr_1n" + self.length = int(raw.shape[1]) + return + raise ValueError( + f"{name}: expected 1D sequence or shape (N,1)/(1,N), got shape {tuple(raw.shape)}." + ) + + raise ValueError(f"{name}: expected array/list, got {type(raw).__name__}.") + + def value(self, i: int) -> int: + if self.kind == "list1d": + return int(self.raw[i]) + if self.kind == "list_n1": + return int(self.raw[i][0]) + if self.kind == "list_1n": + return int(self.raw[0][i]) + if self.kind == "arr1d": + return int(self.raw[i]) + if self.kind == "arr_n1": + return int(self.raw[i, 0]) + if self.kind == "arr_1n": + return int(self.raw[0, i]) + raise ValueError(f"Unsupported internal sequence kind {self.kind!r}.") + + def _parse_type(raw: Any, stab_index: int) -> str: if not isinstance(raw, str): raise ValueError(f"Stabilizer {stab_index}: 'type' must be string.") @@ -134,12 +302,63 @@ def _job_identifier(payload: Any) -> str: def _build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( - description="Convert Xanadu-style job output JSON to LiDMaS+ decoder_io NDJSON." + description="Convert Xanadu hardware-style data to LiDMaS+ decoder_io NDJSON." + ) + p.add_argument("--input", required=True, help="Input path (JSON/NPY/NPZ file or Aurora batch directory).") + p.add_argument( + "--source-format", + choices=_SOURCE_FORMATS, + default="auto", + help=( + "Input interpretation. " + "auto: infer from path; " + "xanadu_job_json: job payload with output/samples; " + "shot_matrix: generic shot matrix (.json/.npy/.npz); " + "aurora_switch_dir: Aurora decoder_demo batch dir with switch_settings_qpu_*.{npy,json}; " + "count_table_json: JSON counts table with sample+count entries." + ), ) - p.add_argument("--input", required=True, help="Path to Xanadu job JSON.") p.add_argument("--mapping", required=True, help="Path to syndrome mapping JSON.") p.add_argument("--out", required=True, help="Output NDJSON path.") - p.add_argument("--max-shots", type=int, default=0, help="Optional cap on number of shots (0 = all).") + p.add_argument( + "--append-out", + action="store_true", + help="Append to output NDJSON instead of overwriting.", + ) + p.add_argument( + "--array-key", + default="", + help="Optional array key for dict/npz inputs (e.g., 'samples').", + ) + p.add_argument( + "--stream", + action="store_true", + help="Stream large inputs when possible (recommended for large .npy files).", + ) + p.add_argument( + "--shot-start", + type=int, + default=0, + help="Skip the first N expanded shots before conversion.", + ) + p.add_argument( + "--aurora-qpu-count", + type=int, + default=6, + help="Number of Aurora qpu switch-setting files to read in aurora_switch_dir mode.", + ) + p.add_argument( + "--aurora-binarize", + action="store_true", + help="Map non-zero Aurora switch settings to 1 (keep 0 as 0).", + ) + p.add_argument("--max-shots", type=int, default=0, help="Optional cap on output shots (0 = all).") + p.add_argument( + "--progress-every", + type=int, + default=0, + help="Print progress every N written lines (0 disables).", + ) p.add_argument("--code-id", default="", help="Override code_id from mapping.") p.add_argument("--n-qubits", type=int, default=0, help="Override n_qubits from mapping.") p.add_argument("--round-start", type=int, default=None, help="Override mapping round_start.") @@ -164,20 +383,271 @@ def _build_parser() -> argparse.ArgumentParser: return p +def _resolve_source_format(source_format: str, input_path: Path, payload: Any) -> str: + if source_format != "auto": + return source_format + + if input_path.is_dir(): + return "aurora_switch_dir" + + suffix = input_path.suffix.lower() + if suffix in (".npy", ".npz"): + return "shot_matrix" + + if suffix == ".json": + if isinstance(payload, dict): + if any(k in payload for k in ("counts", "histogram", "entries")): + return "count_table_json" + if "shots" in payload and "output" not in payload and "samples" not in payload: + return "shot_matrix" + return "xanadu_job_json" + if isinstance(payload, list): + if payload and isinstance(payload[0], dict): + keys = set(payload[0].keys()) + if any(k in keys for k in ("count", "n", "freq")) and any( + k in keys for k in ("sample", "outcome", "shot", "modes") + ): + return "count_table_json" + return "shot_matrix" + + return "shot_matrix" + + +def _get_source_payload(input_path: Path, source_format: str, array_key: str, stream: bool) -> Any: + suffix = input_path.suffix.lower() + + if source_format == "xanadu_job_json": + return _load_json(input_path) + + if source_format == "count_table_json": + return _load_json(input_path) + + if source_format == "shot_matrix": + if suffix == ".json": + return _load_json(input_path) + if suffix in (".npy", ".npz"): + mmap_mode = "r" if (stream and suffix == ".npy") else None + return _load_numpy_array(input_path, array_key, mmap_mode=mmap_mode) + raise ValueError( + f"Unsupported shot-matrix input extension {suffix!r}. Use .json, .npy, or .npz." + ) + + return {} + + +def _iter_job_shots(payload: Any, shot_start: int, max_shots: int): + shots = _extract_samples_from_job_payload(payload) + total = len(shots) + start = min(shot_start, total) + end = total if max_shots <= 0 else min(total, start + max_shots) + for idx in range(start, end): + yield idx, shots[idx] + + +def _iter_shot_matrix(payload: Any, array_key: str, shot_start: int, max_shots: int): + data = _extract_samples_from_shot_matrix(payload, array_key) + + if isinstance(data, list): + total = len(data) + start = min(shot_start, total) + end = total if max_shots <= 0 else min(total, start + max_shots) + for idx in range(start, end): + yield idx, data[idx] + return + + if hasattr(data, "ndim") and hasattr(data, "shape"): + ndim = int(data.ndim) + if ndim == 0: + raise ValueError("shot_matrix array must have at least 1 dimension.") + total = 1 if ndim == 1 else int(data.shape[0]) + start = min(shot_start, total) + end = total if max_shots <= 0 else min(total, start + max_shots) + for idx in range(start, end): + if ndim == 1: + yield idx, data + else: + yield idx, data[idx] + return + + raise ValueError(f"Unsupported shot_matrix container type: {type(data).__name__}") + + +def _iter_count_table(payload: Any, shot_start: int, max_shots: int): + table = payload + if isinstance(payload, dict): + table = payload.get("counts", payload.get("histogram", payload.get("entries"))) + if not isinstance(table, list): + raise ValueError( + "Count-table input must be a list or an object with one of keys: counts, histogram, entries." + ) + + target_end = None if max_shots <= 0 else shot_start + max_shots + expanded_cursor = 0 + + for i, entry in enumerate(table): + if not isinstance(entry, dict): + raise ValueError(f"Count-table entry {i}: expected object.") + + shot_raw = entry.get("sample", entry.get("outcome", entry.get("shot", entry.get("modes")))) + if shot_raw is None: + raise ValueError( + f"Count-table entry {i}: missing sample vector. Expected key sample/outcome/shot/modes." + ) + + count_raw = entry.get("count", entry.get("n", entry.get("freq", 1))) + try: + count = int(count_raw) + except (TypeError, ValueError) as exc: + raise ValueError(f"Count-table entry {i}: invalid count {count_raw!r}.") from exc + if count <= 0: + continue + + block_start = expanded_cursor + block_end = expanded_cursor + count + expanded_cursor = block_end + + take_start = max(block_start, shot_start) + take_end = block_end + if target_end is not None: + take_end = min(take_end, target_end) + + if take_start >= take_end: + continue + + shot_norm = _normalize_shot(shot_raw, i) + for global_idx in range(take_start, take_end): + yield global_idx, shot_norm + + if target_end is not None and expanded_cursor >= target_end: + return + + +def _iter_aurora_switch_dir( + batch_dir: Path, + qpu_count: int, + array_key: str, + stream: bool, + binarize: bool, + shot_start: int, + max_shots: int, +): + if not batch_dir.is_dir(): + raise ValueError( + "Aurora switch mode expects --input to be a directory containing " + "switch_settings_qpu_{0..N}.npy or .json files." + ) + if qpu_count <= 0: + raise ValueError("aurora_qpu_count must be > 0.") + + columns: list[_IntSequence] = [] + for qpu in range(qpu_count): + npy_path = batch_dir / f"switch_settings_qpu_{qpu}.npy" + json_path = batch_dir / f"switch_settings_qpu_{qpu}.json" + if npy_path.exists(): + raw = _load_numpy_array(npy_path, array_key, mmap_mode="r" if stream else None) + elif json_path.exists(): + raw = _load_json(json_path) + else: + raise ValueError( + f"Missing Aurora switch file for qpu {qpu}: " + f"expected {npy_path.name} or {json_path.name} in {batch_dir}." + ) + columns.append(_IntSequence(raw, str(npy_path if npy_path.exists() else json_path))) + + n_bins = columns[0].length + for qpu, seq in enumerate(columns): + if seq.length != n_bins: + raise ValueError( + f"Aurora switch length mismatch: qpu0 has {n_bins}, qpu{qpu} has {seq.length}." + ) + + start = min(shot_start, n_bins) + end = n_bins if max_shots <= 0 else min(n_bins, start + max_shots) + + for idx in range(start, end): + shot: list[int] = [] + for col in columns: + v = col.value(idx) + shot.append(0 if (binarize and v == 0) else (1 if binarize else v)) + yield idx, shot + + +def _compile_stabilizers(stabilizers: Any) -> list[tuple[int, str, list[int], int, int, int]]: + if not isinstance(stabilizers, list): + raise ValueError("Mapping JSON must contain list field 'stabilizers'.") + compiled: list[tuple[int, str, list[int], int, int, int]] = [] + for s_i, stab in enumerate(stabilizers): + if not isinstance(stab, dict): + raise ValueError(f"Stabilizer {s_i}: expected object.") + stab_index = int(stab.get("index", s_i)) + stab_type = _parse_type(stab.get("type", "UNKNOWN"), s_i) + modes = _parse_modes(stab.get("modes", []), s_i) + mod = int(stab.get("mod", 2)) + trigger_on = int(stab.get("trigger_on", 1)) + time_offset_ns = int(stab.get("time_offset_ns", 0)) + if mod <= 0: + raise ValueError(f"Stabilizer {s_i}: mod must be > 0.") + if trigger_on < 0 or trigger_on >= mod: + raise ValueError(f"Stabilizer {s_i}: trigger_on must be in [0, mod).") + compiled.append((stab_index, stab_type, modes, mod, trigger_on, time_offset_ns)) + return compiled + + +def _iter_source_shots(args: argparse.Namespace, input_path: Path, source_format: str, payload: Any): + shot_start = max(0, int(args.shot_start)) + max_shots = int(args.max_shots) + + if source_format == "xanadu_job_json": + yield from _iter_job_shots(payload, shot_start=shot_start, max_shots=max_shots) + return + + if source_format == "shot_matrix": + yield from _iter_shot_matrix(payload, array_key=args.array_key, shot_start=shot_start, max_shots=max_shots) + return + + if source_format == "aurora_switch_dir": + yield from _iter_aurora_switch_dir( + input_path, + qpu_count=args.aurora_qpu_count, + array_key=args.array_key, + stream=args.stream, + binarize=args.aurora_binarize, + shot_start=shot_start, + max_shots=max_shots, + ) + return + + if source_format == "count_table_json": + yield from _iter_count_table(payload, shot_start=shot_start, max_shots=max_shots) + return + + raise ValueError(f"Unsupported source format: {source_format}") + + def main() -> int: args = _build_parser().parse_args() + if args.shot_start < 0: + raise ValueError("--shot-start must be >= 0.") + if args.max_shots < 0: + raise ValueError("--max-shots must be >= 0.") + if args.progress_every < 0: + raise ValueError("--progress-every must be >= 0.") + in_path = Path(args.input) map_path = Path(args.mapping) out_path = Path(args.out) - payload = _load_json(in_path) - mapping = _load_json(map_path) + payload_for_detect: Any = None + if not in_path.is_dir() and in_path.suffix.lower() == ".json": + payload_for_detect = _load_json(in_path) + + source_format = _resolve_source_format(args.source_format, in_path, payload_for_detect) + payload = _get_source_payload(in_path, source_format, args.array_key, args.stream) + mapping = _load_json(map_path) if not isinstance(mapping, dict): raise ValueError("Mapping JSON must be an object.") - if "stabilizers" not in mapping or not isinstance(mapping["stabilizers"], list): - raise ValueError("Mapping JSON must contain list field 'stabilizers'.") code_id = args.code_id or str(mapping.get("code_id", "")).strip() if not code_id: @@ -196,85 +666,77 @@ def main() -> int: if time_ns_stride < 0: raise ValueError("time_ns_stride must be >= 0.") + stabilizers_compiled = _compile_stabilizers(mapping.get("stabilizers")) + base_meta: dict[str, str] = {} mapping_meta = mapping.get("metadata", {}) if isinstance(mapping_meta, dict): for k, v in mapping_meta.items(): base_meta[str(k)] = str(v) base_meta.update(_parse_meta_pairs(args.meta)) + if "source_format" not in base_meta: + base_meta["source_format"] = source_format + if args.shot_start > 0 and "shot_start" not in base_meta: + base_meta["shot_start"] = str(args.shot_start) + if args.stream and "stream" not in base_meta: + base_meta["stream"] = "1" jid = _job_identifier(payload) if jid and "job_id" not in base_meta: base_meta["job_id"] = jid - shots_raw = _extract_samples(payload) - if args.max_shots > 0: - shots_raw = shots_raw[: args.max_shots] - loss_list = _parse_loss_list(args.loss_prob_by_qubit) - requests: list[dict[str, Any]] = [] - stabilizers = mapping["stabilizers"] - - for i, shot_raw in enumerate(shots_raw): - shot = _normalize_shot(shot_raw, i) - events: list[dict[str, Any]] = [] - shot_time = time_ns_start + i * time_ns_stride - - for s_i, stab in enumerate(stabilizers): - if not isinstance(stab, dict): - raise ValueError(f"Stabilizer {s_i}: expected object.") - stab_index = int(stab.get("index", s_i)) - stab_type = _parse_type(stab.get("type", "UNKNOWN"), s_i) - modes = _parse_modes(stab.get("modes", []), s_i) - mod = int(stab.get("mod", 2)) - trigger_on = int(stab.get("trigger_on", 1)) - time_offset_ns = int(stab.get("time_offset_ns", 0)) - if mod <= 0: - raise ValueError(f"Stabilizer {s_i}: mod must be > 0.") - if trigger_on < 0 or trigger_on >= mod: - raise ValueError(f"Stabilizer {s_i}: trigger_on must be in [0, mod).") - - parity_sum = 0 - for m in modes: - if m >= len(shot): - raise ValueError( - f"Shot {i}: mode index {m} out of range for shot width {len(shot)}." + out_path.parent.mkdir(parents=True, exist_ok=True) + out_mode = "a" if args.append_out else "w" + + wrote = 0 + with out_path.open(out_mode, encoding="utf-8") as f: + for global_idx, shot_raw in _iter_source_shots(args, in_path, source_format, payload): + shot = _normalize_shot(shot_raw, global_idx) + events: list[dict[str, Any]] = [] + shot_time = time_ns_start + global_idx * time_ns_stride + + for stab_index, stab_type, modes, mod, trigger_on, time_offset_ns in stabilizers_compiled: + parity_sum = 0 + for m in modes: + if m >= len(shot): + raise ValueError( + f"Shot {global_idx}: mode index {m} out of range for shot width {len(shot)}." + ) + parity_sum += shot[m] + bit = parity_sum % mod + if bit == trigger_on: + events.append( + { + "index": stab_index, + "time_ns": shot_time + time_offset_ns, + "type": stab_type, + } ) - parity_sum += shot[m] - bit = parity_sum % mod - if bit == trigger_on: - events.append( - { - "index": stab_index, - "time_ns": shot_time + time_offset_ns, - "type": stab_type, - } - ) - - req: dict[str, Any] = { - "code_id": code_id, - "round_index": round_start + i * round_stride, - "n_qubits": n_qubits, - "events": events, - "noise": { - "sigma": args.sigma, - "gate_error_rate": args.gate_error_rate, - "meas_error_rate": args.meas_error_rate, - "idle_error_rate": args.idle_error_rate, - "loss_prob_by_qubit": loss_list, - }, - "metadata": dict(base_meta), - } - req["metadata"]["shot_index"] = str(i) - requests.append(req) - out_path.parent.mkdir(parents=True, exist_ok=True) - with out_path.open("w", encoding="utf-8") as f: - for req in requests: + req: dict[str, Any] = { + "code_id": code_id, + "round_index": round_start + global_idx * round_stride, + "n_qubits": n_qubits, + "events": events, + "noise": { + "sigma": args.sigma, + "gate_error_rate": args.gate_error_rate, + "meas_error_rate": args.meas_error_rate, + "idle_error_rate": args.idle_error_rate, + "loss_prob_by_qubit": loss_list, + }, + "metadata": dict(base_meta), + } + req["metadata"]["shot_index"] = str(global_idx) f.write(json.dumps(req, separators=(",", ":")) + "\n") + wrote += 1 + + if args.progress_every > 0 and wrote % args.progress_every == 0: + print(f"progress: wrote={wrote} latest_shot_index={global_idx}", file=sys.stderr) - print(f"Wrote {len(requests)} DecodeRequest lines to {out_path}") + print(f"Wrote {wrote} DecodeRequest lines to {out_path}") return 0 diff --git a/examples/hardware_integration/replay.sh b/examples/hardware_integration/replay.sh index 7fb623c..fcb8bd7 100755 --- a/examples/hardware_integration/replay.sh +++ b/examples/hardware_integration/replay.sh @@ -7,8 +7,14 @@ source "${REPO_ROOT}/examples/common.sh" BIN="$(resolve_lidmas_binary "${REPO_ROOT}")" RESULT_DIR="$(results_dir_for "${REPO_ROOT}" "hardware_integration")" -REQ="${RESULT_DIR}/decoder_requests.ndjson" -RESP="${RESULT_DIR}/decoder_responses.ndjson" +REQ="${1:-${RESULT_DIR}/decoder_requests.ndjson}" +if [ "${#}" -ge 2 ]; then + RESP="${2}" +else + BASE_REQ="$(basename "${REQ}")" + BASE_RESP="${BASE_REQ/requests/responses}" + RESP="${RESULT_DIR}/${BASE_RESP}" +fi CFG="${REPO_ROOT}/schemas/surface_decoder_adapter_config.json" if [ ! -f "${REQ}" ]; then diff --git a/examples/hardware_integration/run_aurora.sh b/examples/hardware_integration/run_aurora.sh new file mode 100755 index 0000000..0f4e4a6 --- /dev/null +++ b/examples/hardware_integration/run_aurora.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +source "${REPO_ROOT}/examples/common.sh" + +RESULT_DIR="$(results_dir_for "${REPO_ROOT}" "hardware_integration")" + +python3 "${SCRIPT_DIR}/convert_xanadu_job_to_decoder_io.py" \ + --source-format aurora_switch_dir \ + --input "${SCRIPT_DIR}/aurora_batch_example" \ + --mapping "${SCRIPT_DIR}/xanadu_aurora_mapping_example.json" \ + --out "${RESULT_DIR}/decoder_requests_aurora.ndjson" \ + --aurora-binarize \ + --sigma 0.16 \ + --gate-error-rate 0.0007 \ + --meas-error-rate 0.0009 \ + --idle-error-rate 0.0003 \ + --meta dataset=aurora_decoder_demo + +echo "Wrote ${RESULT_DIR}/decoder_requests_aurora.ndjson" diff --git a/examples/hardware_integration/run_gkp.sh b/examples/hardware_integration/run_gkp.sh new file mode 100755 index 0000000..915e8e7 --- /dev/null +++ b/examples/hardware_integration/run_gkp.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +source "${REPO_ROOT}/examples/common.sh" + +RESULT_DIR="$(results_dir_for "${REPO_ROOT}" "hardware_integration")" + +python3 "${SCRIPT_DIR}/convert_xanadu_job_to_decoder_io.py" \ + --source-format count_table_json \ + --input "${SCRIPT_DIR}/xanadu_gkp_counts_example.json" \ + --mapping "${SCRIPT_DIR}/xanadu_gkp_mapping_example.json" \ + --out "${RESULT_DIR}/decoder_requests_gkp.ndjson" \ + --max-shots 1000 \ + --sigma 0.10 \ + --gate-error-rate 0.0004 \ + --meas-error-rate 0.0006 \ + --idle-error-rate 0.0002 \ + --meta dataset=gkp + +echo "Wrote ${RESULT_DIR}/decoder_requests_gkp.ndjson" diff --git a/examples/hardware_integration/run_public_datasets.sh b/examples/hardware_integration/run_public_datasets.sh new file mode 100755 index 0000000..1547b58 --- /dev/null +++ b/examples/hardware_integration/run_public_datasets.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +bash "${SCRIPT_DIR}/run_aurora.sh" +bash "${SCRIPT_DIR}/run_qca.sh" +bash "${SCRIPT_DIR}/run_gkp.sh" + +echo "All public dataset example conversions completed." diff --git a/examples/hardware_integration/run_qca.sh b/examples/hardware_integration/run_qca.sh new file mode 100755 index 0000000..0d732cf --- /dev/null +++ b/examples/hardware_integration/run_qca.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +source "${REPO_ROOT}/examples/common.sh" + +RESULT_DIR="$(results_dir_for "${REPO_ROOT}" "hardware_integration")" + +python3 "${SCRIPT_DIR}/convert_xanadu_job_to_decoder_io.py" \ + --source-format shot_matrix \ + --input "${SCRIPT_DIR}/xanadu_qca_samples_example.json" \ + --array-key samples \ + --mapping "${SCRIPT_DIR}/xanadu_qca_mapping_example.json" \ + --out "${RESULT_DIR}/decoder_requests_qca.ndjson" \ + --sigma 0.12 \ + --gate-error-rate 0.0005 \ + --meas-error-rate 0.0008 \ + --idle-error-rate 0.0002 \ + --meta dataset=qca + +echo "Wrote ${RESULT_DIR}/decoder_requests_qca.ndjson" diff --git a/examples/hardware_integration/xanadu_aurora_mapping_example.json b/examples/hardware_integration/xanadu_aurora_mapping_example.json new file mode 100644 index 0000000..3f9a32d --- /dev/null +++ b/examples/hardware_integration/xanadu_aurora_mapping_example.json @@ -0,0 +1,43 @@ +{ + "code_id": "gkp_surface_d5", + "n_qubits": 40, + "round_start": 0, + "round_stride": 1, + "time_ns_start": 1000, + "time_ns_stride": 1000, + "metadata": { + "hardware": "xanadu", + "dataset": "aurora_decoder_demo", + "notes": "Example parity mapping over Aurora switch settings (qpu_0..qpu_5)" + }, + "stabilizers": [ + { + "index": 0, + "type": "Z", + "modes": [0, 1], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 1, + "type": "Z", + "modes": [2, 3], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 2, + "type": "Z", + "modes": [4, 5], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 3, + "type": "Z", + "modes": [1, 3, 5], + "mod": 2, + "trigger_on": 1 + } + ] +} diff --git a/examples/hardware_integration/xanadu_gkp_counts_example.json b/examples/hardware_integration/xanadu_gkp_counts_example.json new file mode 100644 index 0000000..3a5fee7 --- /dev/null +++ b/examples/hardware_integration/xanadu_gkp_counts_example.json @@ -0,0 +1,20 @@ +{ + "counts": [ + { + "sample": [0, 0, 0], + "count": 5 + }, + { + "sample": [1, 0, 0], + "count": 3 + }, + { + "sample": [0, 1, 1], + "count": 2 + }, + { + "sample": [2, 1, 0], + "count": 1 + } + ] +} diff --git a/examples/hardware_integration/xanadu_gkp_mapping_example.json b/examples/hardware_integration/xanadu_gkp_mapping_example.json new file mode 100644 index 0000000..ebf9c02 --- /dev/null +++ b/examples/hardware_integration/xanadu_gkp_mapping_example.json @@ -0,0 +1,36 @@ +{ + "code_id": "gkp_surface_d5", + "n_qubits": 40, + "round_start": 0, + "round_stride": 1, + "time_ns_start": 3000, + "time_ns_stride": 1000, + "metadata": { + "hardware": "xanadu", + "dataset": "gkp", + "notes": "Example mapping for count-compressed PNR outcomes" + }, + "stabilizers": [ + { + "index": 0, + "type": "Z", + "modes": [0, 1], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 1, + "type": "Z", + "modes": [1, 2], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 2, + "type": "Z", + "modes": [0, 2], + "mod": 2, + "trigger_on": 1 + } + ] +} diff --git a/examples/hardware_integration/xanadu_qca_mapping_example.json b/examples/hardware_integration/xanadu_qca_mapping_example.json new file mode 100644 index 0000000..e25d7d9 --- /dev/null +++ b/examples/hardware_integration/xanadu_qca_mapping_example.json @@ -0,0 +1,36 @@ +{ + "code_id": "qca_surface_d5", + "n_qubits": 40, + "round_start": 0, + "round_stride": 1, + "time_ns_start": 2000, + "time_ns_stride": 1000, + "metadata": { + "hardware": "xanadu", + "dataset": "qca", + "notes": "Example parity mapping for Borealis-like samples.npy rows" + }, + "stabilizers": [ + { + "index": 0, + "type": "Z", + "modes": [0, 1], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 1, + "type": "Z", + "modes": [2, 3], + "mod": 2, + "trigger_on": 1 + }, + { + "index": 2, + "type": "Z", + "modes": [4, 5], + "mod": 2, + "trigger_on": 1 + } + ] +} diff --git a/examples/hardware_integration/xanadu_qca_samples_example.json b/examples/hardware_integration/xanadu_qca_samples_example.json new file mode 100644 index 0000000..bf7b890 --- /dev/null +++ b/examples/hardware_integration/xanadu_qca_samples_example.json @@ -0,0 +1,8 @@ +{ + "samples": [ + [[0, 1, 0, 2, 0, 1]], + [[1, 0, 1, 0, 2, 0]], + [[0, 0, 0, 0, 0, 0]], + [[2, 1, 1, 1, 0, 2]] + ] +} diff --git a/pyproject.toml b/pyproject.toml index 3ddab58..1fa356b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "lidmas" -version = "1.1.4" +version = "1.2.0rc2" description = "LiDMaS+ (Logical Injection & Decoding Modeling System) quantum error-correction simulator" readme = "README.md" requires-python = ">=3.9"