From 64e4840070a25e71d141b5b518ff733887d86d88 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:41:16 +0100 Subject: [PATCH 01/19] Refactor to support custom gates --- Cargo.lock | 75 + devenv.nix | 1 + docs/README.md | 10 + docs/extensibility/README.md | 63 + docs/extensibility/error-model/README.md | 56 + docs/extensibility/error-model/c.md | 332 +++ docs/extensibility/error-model/rust.md | 236 ++ docs/extensibility/fundamentals.md | 250 ++ docs/extensibility/gates/README.md | 62 + docs/extensibility/gates/c.md | 211 ++ docs/extensibility/gates/rust.md | 167 ++ docs/extensibility/runtime/README.md | 50 + docs/extensibility/runtime/c.md | 248 ++ docs/extensibility/runtime/rust.md | 252 ++ docs/extensibility/simulator/README.md | 48 + docs/extensibility/simulator/c.md | 204 ++ docs/extensibility/simulator/rust.md | 191 ++ docs/migration/0.3.md | 559 ++++ docs/migration/README.md | 3 + examples/clifford_t_stack/Cargo.lock | 391 +++ examples/clifford_t_stack/Cargo.toml | 18 + examples/clifford_t_stack/README.md | 241 ++ .../c/include/clifford_t_qis.h | 24 + .../c/src/clifford_t_interface.c | 258 ++ examples/clifford_t_stack/cpp/CMakeLists.txt | 51 + .../cpp/src/qrack_simulator.cpp | 421 +++ examples/clifford_t_stack/hatch_build.py | 169 ++ examples/clifford_t_stack/justfile | 23 + examples/clifford_t_stack/pyproject.toml | 42 + .../__init__.py | 59 + .../selene_example_clifford_t_stack/build.py | 136 + .../selene_example_clifford_t_stack/gates.py | 54 + .../plugins.py | 119 + .../selene_example_clifford_t_stack/py.typed | 1 + .../python/tests/programs/bell.ct.c | 19 + .../python/tests/programs/hth.ct.c | 11 + .../python/tests/test_python_bindings.py | 132 + examples/clifford_t_stack/src/error_model.rs | 173 ++ examples/clifford_t_stack/src/gates.rs | 103 + examples/clifford_t_stack/src/interface.rs | 54 + examples/clifford_t_stack/src/lib.rs | 22 + examples/clifford_t_stack/src/runtime.rs | 230 ++ examples/clifford_t_stack/src/simulator.rs | 182 ++ examples/clifford_t_stack/src/tests.rs | 91 + examples/clifford_t_stack/uv.lock | 1025 ++++++++ justfile | 7 +- pyproject.toml | 2 +- selene-core/Cargo.toml | 13 +- selene-core/c/include/selene/core_types.h | 19 - selene-core/c/include/selene/error_model.h | 54 +- selene-core/c/include/selene/gatewire.h | 196 ++ selene-core/c/include/selene/runtime.h | 65 +- selene-core/c/include/selene/simulator.h | 46 +- selene-core/cbindgen/core_types.toml | 58 + selene-core/cbindgen/error_model.toml | 62 +- selene-core/cbindgen/runtime.toml | 62 +- selene-core/cbindgen/simulator.toml | 62 +- selene-core/examples/error_model/Cargo.lock | 125 + selene-core/examples/error_model/rust/lib.rs | 346 ++- selene-core/examples/runtime/Cargo.lock | 137 + selene-core/examples/runtime/rust/lib.rs | 133 +- selene-core/examples/simulator/Cargo.lock | 125 + selene-core/examples/simulator/rust/lib.rs | 52 +- selene-core/hatch_build.py | 7 + selene-core/pyproject.toml | 1 + selene-core/python/selene_core/__init__.py | 48 + selene-core/python/selene_core/gatewire.py | 436 +++ selene-core/rust/encoder.rs | 2 +- selene-core/rust/error_model.rs | 11 + selene-core/rust/error_model/helper.rs | 45 +- selene-core/rust/error_model/inline.rs | 27 + selene-core/rust/error_model/interface.rs | 15 +- selene-core/rust/error_model/plugin.rs | 142 +- selene-core/rust/error_model/version.rs | 6 +- selene-core/rust/examples/rust_transformer.rs | 58 + selene-core/rust/gatewire.rs | 46 + selene-core/rust/gatewire/builtin.rs | 22 + selene-core/rust/gatewire/cbindgen.toml | 72 + selene-core/rust/gatewire/decl.rs | 46 + selene-core/rust/gatewire/dynamic.rs | 118 + selene-core/rust/gatewire/error.rs | 37 + selene-core/rust/gatewire/ffi.rs | 7 + selene-core/rust/gatewire/ffi/convert.rs | 173 ++ selene-core/rust/gatewire/ffi/functions.rs | 410 +++ selene-core/rust/gatewire/ffi/status.rs | 30 + selene-core/rust/gatewire/ffi/types.rs | 136 + selene-core/rust/gatewire/id.rs | 51 + selene-core/rust/gatewire/instance.rs | 47 + selene-core/rust/gatewire/operand.rs | 177 ++ selene-core/rust/gatewire/tests.rs | 62 + selene-core/rust/gatewire/typed.rs | 80 + selene-core/rust/gatewire/wire.rs | 205 ++ selene-core/rust/lib.rs | 15 +- selene-core/rust/macros.rs | 195 ++ selene-core/rust/operation.rs | 162 +- selene-core/rust/operation/plugin.rs | 103 +- selene-core/rust/plugin.rs | 309 +++ selene-core/rust/runtime.rs | 56 +- selene-core/rust/runtime/helper.rs | 222 +- selene-core/rust/runtime/inline.rs | 115 +- selene-core/rust/runtime/interface.rs | 40 +- selene-core/rust/runtime/plugin.rs | 299 ++- selene-core/rust/runtime/version.rs | 6 +- selene-core/rust/simulator.rs | 71 +- selene-core/rust/simulator/README.md | 21 +- .../conformance_testing/framework.rs | 64 +- selene-core/rust/simulator/helper.rs | 204 +- selene-core/rust/simulator/inline.rs | 237 +- selene-core/rust/simulator/interface.rs | 10 + selene-core/rust/simulator/plugin.rs | 256 +- selene-core/rust/simulator/version.rs | 6 +- selene-ext/compat/README.md | 75 + selene-ext/compat/v02-common/Cargo.lock | 290 ++ selene-ext/compat/v02-common/Cargo.toml | 19 + selene-ext/compat/v02-common/src/lib.rs | 218 ++ selene-ext/compat/v02-error-model/Cargo.lock | 437 +++ selene-ext/compat/v02-error-model/Cargo.toml | 23 + selene-ext/compat/v02-error-model/src/lib.rs | 581 ++++ selene-ext/compat/v02-runtime/Cargo.lock | 436 +++ selene-ext/compat/v02-runtime/Cargo.toml | 22 + selene-ext/compat/v02-runtime/src/lib.rs | 509 ++++ selene-ext/compat/v02-simulator/Cargo.lock | 436 +++ selene-ext/compat/v02-simulator/Cargo.toml | 22 + selene-ext/compat/v02-simulator/src/lib.rs | 247 ++ .../plugin.py | 4 +- .../error-models/depolarizing/rust/lib.rs | 207 +- selene-ext/error-models/ideal/rust/lib.rs | 18 +- .../error-models/simple-leakage/rust/lib.rs | 76 +- .../interfaces/helios_qis/c/src/helios_ops.c | 77 +- .../interfaces/helios_qis/c/src/interface.c | 53 + .../python/selene_helios_qis_plugin/plugin.py | 2 +- .../interfaces/sol_qis/c/src/interface.c | 53 + selene-ext/interfaces/sol_qis/c/src/sol_ops.c | 77 +- .../python/selene_sol_qis_plugin/plugin.py | 2 +- .../selene_simple_runtime_plugin/plugin.py | 24 +- selene-ext/runtimes/simple/rust/lib.rs | 158 +- .../selene_soft_rz_runtime_plugin/plugin.py | 16 +- .../instructions_batching_1.json | 2331 ++++++++++++----- .../instructions_batching_2.json | 2331 ++++++++++++----- .../instructions_batching_8.json | 2331 ++++++++++++----- .../trace_batching_1.json | 370 +-- .../trace_batching_2.json | 370 +-- .../trace_batching_8.json | 370 +-- .../soft_rz/python/tests/test_batching.py | 4 +- selene-ext/runtimes/soft_rz/rust/lib.rs | 154 +- .../simulators/classical-replay/rust/lib.rs | 51 +- .../simulators/classical-replay/rust/tests.rs | 2 +- selene-ext/simulators/coinflip/rust/lib.rs | 51 +- .../simulators/quantum-replay/rust/lib.rs | 75 +- .../quest/python/gate_definitions.py | 20 +- selene-ext/simulators/quest/rust/lib.rs | 57 +- .../stim/python/gate_definitions.py | 12 +- selene-ext/simulators/stim/rust/lib.rs | 73 +- selene-ext/utilities/argreader/Cargo.lock | 115 + selene-sim/c/CMakeLists.txt | 3 + selene-sim/c/include/selene/selene.h | 30 +- selene-sim/cbindgen.toml | 2 +- selene-sim/python/selene_sim/README.md | 4 +- .../selene_sim/event_hooks/instruction_log.py | 345 +-- .../python/selene_sim/interactive/_library.py | 30 + .../selene_sim/interactive/full_stack.py | 101 +- .../python/selene_sim/interactive/runtime.py | 191 +- .../selene_sim/interactive/simulator.py | 122 +- selene-sim/python/selene_sim/process.py | 7 +- .../snapshots/test_guppy/test_metrics/metrics | 18 +- .../test_metrics_on_exit/metrics_on_exit | 18 +- .../test_simple_vs_softrz/simple_events.json | 1028 +++++--- .../test_simple_vs_softrz/soft_events.json | 764 ++++-- .../test_trace/test_ghz_trace/trace.json | 274 +- .../unparsed_metrics | 24 +- selene-sim/python/tests/test_interactive.py | 126 +- selene-sim/python/tests/test_runtimes.py | 8 +- selene-sim/rust/emulator.rs | 41 +- selene-sim/rust/event_hooks.rs | 68 +- .../rust/event_hooks/instruction_log.rs | 28 +- selene-sim/rust/event_hooks/metrics.rs | 476 ++-- selene-sim/rust/ffi_interface.rs | 64 +- selene-sim/rust/selene_instance/quantum.rs | 21 +- uv.lock | 95 +- 179 files changed, 23917 insertions(+), 6309 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/extensibility/README.md create mode 100644 docs/extensibility/error-model/README.md create mode 100644 docs/extensibility/error-model/c.md create mode 100644 docs/extensibility/error-model/rust.md create mode 100644 docs/extensibility/fundamentals.md create mode 100644 docs/extensibility/gates/README.md create mode 100644 docs/extensibility/gates/c.md create mode 100644 docs/extensibility/gates/rust.md create mode 100644 docs/extensibility/runtime/README.md create mode 100644 docs/extensibility/runtime/c.md create mode 100644 docs/extensibility/runtime/rust.md create mode 100644 docs/extensibility/simulator/README.md create mode 100644 docs/extensibility/simulator/c.md create mode 100644 docs/extensibility/simulator/rust.md create mode 100644 docs/migration/0.3.md create mode 100644 docs/migration/README.md create mode 100644 examples/clifford_t_stack/Cargo.lock create mode 100644 examples/clifford_t_stack/Cargo.toml create mode 100644 examples/clifford_t_stack/README.md create mode 100644 examples/clifford_t_stack/c/include/clifford_t_qis.h create mode 100644 examples/clifford_t_stack/c/src/clifford_t_interface.c create mode 100644 examples/clifford_t_stack/cpp/CMakeLists.txt create mode 100644 examples/clifford_t_stack/cpp/src/qrack_simulator.cpp create mode 100644 examples/clifford_t_stack/hatch_build.py create mode 100644 examples/clifford_t_stack/justfile create mode 100644 examples/clifford_t_stack/pyproject.toml create mode 100644 examples/clifford_t_stack/python/selene_example_clifford_t_stack/__init__.py create mode 100644 examples/clifford_t_stack/python/selene_example_clifford_t_stack/build.py create mode 100644 examples/clifford_t_stack/python/selene_example_clifford_t_stack/gates.py create mode 100644 examples/clifford_t_stack/python/selene_example_clifford_t_stack/plugins.py create mode 100644 examples/clifford_t_stack/python/selene_example_clifford_t_stack/py.typed create mode 100644 examples/clifford_t_stack/python/tests/programs/bell.ct.c create mode 100644 examples/clifford_t_stack/python/tests/programs/hth.ct.c create mode 100644 examples/clifford_t_stack/python/tests/test_python_bindings.py create mode 100644 examples/clifford_t_stack/src/error_model.rs create mode 100644 examples/clifford_t_stack/src/gates.rs create mode 100644 examples/clifford_t_stack/src/interface.rs create mode 100644 examples/clifford_t_stack/src/lib.rs create mode 100644 examples/clifford_t_stack/src/runtime.rs create mode 100644 examples/clifford_t_stack/src/simulator.rs create mode 100644 examples/clifford_t_stack/src/tests.rs create mode 100644 examples/clifford_t_stack/uv.lock create mode 100644 selene-core/c/include/selene/gatewire.h create mode 100644 selene-core/python/selene_core/gatewire.py create mode 100644 selene-core/rust/examples/rust_transformer.rs create mode 100644 selene-core/rust/gatewire.rs create mode 100644 selene-core/rust/gatewire/builtin.rs create mode 100644 selene-core/rust/gatewire/cbindgen.toml create mode 100644 selene-core/rust/gatewire/decl.rs create mode 100644 selene-core/rust/gatewire/dynamic.rs create mode 100644 selene-core/rust/gatewire/error.rs create mode 100644 selene-core/rust/gatewire/ffi.rs create mode 100644 selene-core/rust/gatewire/ffi/convert.rs create mode 100644 selene-core/rust/gatewire/ffi/functions.rs create mode 100644 selene-core/rust/gatewire/ffi/status.rs create mode 100644 selene-core/rust/gatewire/ffi/types.rs create mode 100644 selene-core/rust/gatewire/id.rs create mode 100644 selene-core/rust/gatewire/instance.rs create mode 100644 selene-core/rust/gatewire/operand.rs create mode 100644 selene-core/rust/gatewire/tests.rs create mode 100644 selene-core/rust/gatewire/typed.rs create mode 100644 selene-core/rust/gatewire/wire.rs create mode 100644 selene-core/rust/macros.rs create mode 100644 selene-core/rust/plugin.rs create mode 100644 selene-ext/compat/README.md create mode 100644 selene-ext/compat/v02-common/Cargo.lock create mode 100644 selene-ext/compat/v02-common/Cargo.toml create mode 100644 selene-ext/compat/v02-common/src/lib.rs create mode 100644 selene-ext/compat/v02-error-model/Cargo.lock create mode 100644 selene-ext/compat/v02-error-model/Cargo.toml create mode 100644 selene-ext/compat/v02-error-model/src/lib.rs create mode 100644 selene-ext/compat/v02-runtime/Cargo.lock create mode 100644 selene-ext/compat/v02-runtime/Cargo.toml create mode 100644 selene-ext/compat/v02-runtime/src/lib.rs create mode 100644 selene-ext/compat/v02-simulator/Cargo.lock create mode 100644 selene-ext/compat/v02-simulator/Cargo.toml create mode 100644 selene-ext/compat/v02-simulator/src/lib.rs create mode 100644 selene-sim/python/selene_sim/interactive/_library.py diff --git a/Cargo.lock b/Cargo.lock index 6a25b4ff..54fb2947 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + [[package]] name = "autocfg" version = "1.5.0" @@ -85,6 +97,20 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "bytesize" version = "2.3.1" @@ -162,6 +188,30 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "delegate" version = "0.13.5" @@ -188,10 +238,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version", "syn", + "unicode-xid", ] [[package]] @@ -608,9 +660,14 @@ name = "selene-core" version = "0.3.0-alpha.1" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] @@ -799,6 +856,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -907,6 +970,18 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "url" version = "2.5.8" diff --git a/devenv.nix b/devenv.nix index 5655e262..1415465f 100644 --- a/devenv.nix +++ b/devenv.nix @@ -4,6 +4,7 @@ packages = with pkgs; [ cmake just + stdenv.cc zlib libxml2 ncurses diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..90b31d65 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# Selene Documentation + +This directory contains user-facing documentation for extending Selene. + +If you are moving from Selene 0.2 to the 0.3 series, start with the +[0.3 migration guide](migration/0.3.md). + +Start with [Extensibility](extensibility/README.md) if you want to write a +plugin, define gates, or understand how Selene connects a Python-facing +interface to runtime, error model, and simulator backends. diff --git a/docs/extensibility/README.md b/docs/extensibility/README.md new file mode 100644 index 00000000..74d8bc16 --- /dev/null +++ b/docs/extensibility/README.md @@ -0,0 +1,63 @@ +# Extending Selene + +Selene is built around replaceable pieces. A user-facing interface, such as a +Python interactive session or a QIS binding, describes work in terms of gates. +A runtime decides how to schedule that work. An error model can mutate the work +stochastically. A simulator finally executes the operations it receives. + +Plugins exist so those pieces can evolve independently. You can write a new +hardware-aware runtime without changing a simulator. You can test a new noise +model against several simulators. You can add a simulator backend without +teaching every interface about its internal gate names. + +The key idea is that all quantum gates cross extension boundaries through +gatewire. Gatewire gives each gate a stable semantic identity and a typed list +of operands. During setup, each layer negotiates a gateset: + +1. The interface registers the gates it intends to send. +2. The runtime accepts that gateset and returns the gates it may emit. +3. The error model accepts the runtime gateset and returns the gates it may + emit. +4. The simulator accepts the final gateset or rejects the configuration. + +That handshake is what lets Selene support cases such as a runtime accepting +`RZ` from users while lowering it into something else before the simulator sees +it. + +## Learning Path + +Read these in order if you are new to Selene plugins: + +1. [Fundamentals](fundamentals.md) explains the lifecycle, descriptors, + gatesets, metrics, and memory rules shared by all plugin types. +2. [Gates and gatesets](gates/README.md) explains builtin gates, custom gate + definitions, and gatewire transport. +3. [Worked examples](examples/README.md) shows a complete custom-gateset stack + from interface registration to simulator validation. +4. [Runtime plugins](runtime/README.md) explains how a runtime receives user + operations and emits batches. +5. [Error model plugins](error-model/README.md) explains how stochastic errors + are injected. +6. [Simulator plugins](simulator/README.md) explains how a backend executes the + final operation stream. + +Each plugin section has a Rust tutorial and a C tutorial. Rust plugins normally +use Selene's traits and export macros. C plugins implement the descriptor ABI +directly. + +## Choosing an Extension Point + +Write a runtime when you want to control scheduling, qubit allocation, barriers, +operation batching, or the way user-facing operations are lowered before noise +and simulation. + +Write an error model when you want to represent noise by injecting gates, +measurements, resets, or other operation-level effects between the runtime and +simulator. + +Write a simulator when you want a new execution backend. Simulators should do +exactly what they are told. They should reject unsupported gates during gateset +negotiation rather than silently approximating them. + +Define gates when the builtin gates are not enough. Gates are not plugins by +themselves; they are the shared vocabulary that interfaces and plugins negotiate. diff --git a/docs/extensibility/error-model/README.md b/docs/extensibility/error-model/README.md new file mode 100644 index 00000000..ed694dbe --- /dev/null +++ b/docs/extensibility/error-model/README.md @@ -0,0 +1,56 @@ +# Error Model Plugins + +An error model plugin sits between the runtime and simulator. It receives +batches of operations from the runtime, applies a stochastic model, calls the +simulator, and reports measurement results back to Selene. + +Selene's current error-model API is operation-level. Noise is represented by +injecting gates, resets, measurements, delays, or other operation mutations. It +does not currently expose a density-matrix channel API to error models, although +that may be added later. + +Use an error model plugin when you want to model behavior such as: + +- Depolarizing, dephasing, or biased Pauli errors. +- Leakage modeled by extra operations or altered measurement results. +- Measurement assignment errors. +- Time-dependent or qubit-dependent stochastic effects. +- Hardware-inspired noise tied to runtime batch timing. + +## Mental Model + +An error model handles a runtime batch like this: + +```text +runtime batch + -> inspect each operation + -> maybe inject extra operations + -> call simulator + -> collect simulator measurement values + -> return result IDs and values to Selene +``` + +The error model does not own the quantum state. It owns its random process and +any model state, then drives the simulator through the simulator operation +interface. + +## Gateset Role + +The error model receives the gateset the runtime may emit and returns the gateset +the error model may emit to the simulator. + +If the error model only forwards operations unchanged, it can return the input +gateset. If it injects extra gates, the returned gateset must include them. For +example, a model that accepts `RZ` and `PhasedX` from the runtime but injects +`ZZPhase` must return a gateset containing `ZZPhase`. + +Many error models should inspect gate arity rather than gate names. In Rust, +`OwnedGateInstance::qubit_operands()` gives the qubits touched by any gate +instance, builtin or custom. In C, use +`gw_decoded_gate_qubit_operand_count` and `gw_decoded_gate_qubit_operand_at` +after decoding a gate with `gw_gate_deserialize`. + +## Tutorials + +- [Writing an error model in Rust](rust.md) +- [Writing an error model in C](c.md) diff --git a/docs/extensibility/error-model/c.md b/docs/extensibility/error-model/c.md new file mode 100644 index 00000000..9eda7ead --- /dev/null +++ b/docs/extensibility/error-model/c.md @@ -0,0 +1,332 @@ +# Writing an Error Model Plugin in C + +C error models implement `SeleneErrorModelPluginDescriptorV1` from +`selene/error_model.h`. They also use `selene/gatewire.h` for gateset +negotiation and gate decoding. + +The C error-model callback is the most callback-heavy plugin API. It receives: + +- A runtime batch extraction handle. +- A simulator operation handle. +- A result-setting handle. + +Your job is to extract runtime operations, call the simulator with any original +or injected operations, and set measurement results by result ID. + +## 1. Define Instance State + +```c +#include +#include +#include +#include + +typedef struct { + uint64_t n_qubits; + uint64_t rng_state; + uint64_t injected_errors; + double probability; +} MyErrorModel; +``` + +Initialize and free the instance: + +```c +static SeleneErrno my_error_model_init(SeleneErrorModelInstance *out, + uint64_t n_qubits, + uint32_t argc, + const char *const *argv) { + MyErrorModel *model = calloc(1, sizeof(MyErrorModel)); + if (model == NULL) { + return 1; + } + model->n_qubits = n_qubits; + model->probability = argc > 0 ? atof(argv[0]) : 0.0; + *out = model; + return 0; +} + +static SeleneErrno my_error_model_exit(SeleneErrorModelInstance handle) { + free((MyErrorModel *)handle); + return 0; +} +``` + +## 2. Negotiate Gates + +Return every gate your model may send to the simulator. If you inject `ZZPhase`, +include it even when the runtime does not emit it: + +```c +static SeleneErrno my_error_model_negotiate_gateset(SeleneErrorModelInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written) { + (void)handle; + + GwGateSet *incoming = NULL; + if (gw_gateset_deserialize(input, input_len, &incoming) != GW_STATUS_OK) { + return 1; + } + + GwGateSet *out_set = NULL; + gw_gateset_new(&out_set); + gw_gateset_add_builtin_rz(out_set); + gw_gateset_add_builtin_phased_x(out_set); + gw_gateset_add_builtin_zz_phase(out_set); + + /* Validate incoming here if your model only accepts a subset. */ + + size_t required = 0; + gw_gateset_serialized_len(out_set, &required); + if (output == NULL || output_len == 0) { + *written = required; + gw_gateset_free(incoming); + gw_gateset_free(out_set); + return 0; + } + + GwStatus status = gw_gateset_serialize(out_set, output, output_len, written); + gw_gateset_free(incoming); + gw_gateset_free(out_set); + return status == GW_STATUS_OK ? 0 : 1; +} +``` + +## 3. Understand Batch Extraction + +Selene does not expose the runtime batch as a raw array. Instead, the error +model passes a collector to `batch.interface.extract_fn`. Selene replays the +batch into that collector through callbacks such as `gate_fn`, `measure_fn`, and +`reset_fn`. + +A minimal collector looks like this: + +```c +typedef struct { + struct SimulatorHandle simulator; + struct ErrorModelSetResultHandle results; + MyErrorModel *model; +} Collector; + +static void collect_gate(SeleneRuntimeGetOperationInstance instance, + const uint8_t *data, + size_t len) { + Collector *collector = (Collector *)instance; + + collector->simulator.interface.gate_fn( + collector->simulator.instance, + data, + len + ); + + /* You can decode data with gw_gate_deserialize and inject extra gates here. */ +} + +static void collect_measure(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + uint64_t result_id) { + Collector *collector = (Collector *)instance; + SeleneErrno measured = collector->simulator.interface.measure_fn( + collector->simulator.instance, + qubit + ); + + if (measured == 0 || measured == 1) { + collector->results.interface.set_bool_result_fn( + collector->results.instance, + result_id, + measured == 1 + ); + } +} + +static void collect_reset(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit) { + Collector *collector = (Collector *)instance; + collector->simulator.interface.reset_fn(collector->simulator.instance, qubit); +} + +static void collect_measure_leaked(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + uint64_t result_id) { + Collector *collector = (Collector *)instance; + SeleneErrno measured = collector->simulator.interface.measure_fn( + collector->simulator.instance, + qubit + ); + if (measured == 0 || measured == 1) { + collector->results.interface.set_u64_result_fn( + collector->results.instance, + result_id, + (uint64_t)measured + ); + } +} + +static void collect_custom(SeleneRuntimeGetOperationInstance instance, + size_t tag, + const void *data, + size_t len) { + (void)instance; + (void)tag; + (void)data; + (void)len; + /* Return an error from the enclosing handle call in production code. */ +} + +static void collect_batch_time(SeleneRuntimeGetOperationInstance instance, + uint64_t start, + uint64_t duration) { + (void)instance; + (void)start; + (void)duration; + /* Store timing if the model has time-dependent noise. */ +} +``` + +The simulator measurement convention is the same as the simulator C API: +`0` means false, `1` means true, and any other value is an error. + +## 4. Handle a Runtime Batch + +Build a `RuntimeGetOperationHandle` from your collector and ask Selene to +extract the batch: + +```c +static SeleneErrno my_error_model_handle_operations( + SeleneErrorModelInstance handle, + struct RuntimeExtractOperationHandle batch, + struct SimulatorHandle simulator, + struct ErrorModelSetResultHandle results +) { + Collector collector = { + .simulator = simulator, + .results = results, + .model = (MyErrorModel *)handle, + }; + + struct RuntimeGetOperationInterface interface = { + .measure_fn = collect_measure, + .measure_leaked_fn = collect_measure_leaked, + .reset_fn = collect_reset, + .custom_fn = collect_custom, + .set_batch_time_fn = collect_batch_time, + .gate_fn = collect_gate, + }; + + struct RuntimeGetOperationHandle sink = { + .instance = &collector, + .interface = interface, + }; + + batch.interface.extract_fn(batch, sink); + return 0; +} +``` + +Production code should collect errors from callbacks and return nonzero if any +simulator call fails. A small collector struct is usually the cleanest way to +store that error state. + +## 5. Inject Gates + +To inject a gate, serialize a `GwGateInstanceView` and call the simulator's +`gate_fn`: + +```c +static SeleneErrno inject_rz(struct SimulatorHandle simulator, + uint32_t qubit, + double theta) { + GwGateValue values[2] = { + { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_QUBIT, + .data.qubit = qubit, + }, + { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_F64, + .data.f64_value = theta, + }, + }; + + GwGateInstanceView gate = { + .abi_size = sizeof(GwGateInstanceView), + .semantic_id = gw_builtin_rz_semantic_id(), + .values_ptr = values, + .values_len = 2, + }; + + size_t len = 0; + if (gw_gate_serialized_len(&gate, &len) != GW_STATUS_OK) { + return 1; + } + + uint8_t *buffer = malloc(len); + if (buffer == NULL) { + return 1; + } + + size_t written = 0; + GwStatus status = gw_gate_serialize(&gate, buffer, len, &written); + if (status == GW_STATUS_OK) { + status = simulator.interface.gate_fn(simulator.instance, buffer, written) == 0 + ? GW_STATUS_OK + : GW_STATUS_PANIC; + } + + free(buffer); + return status == GW_STATUS_OK ? 0 : 1; +} +``` + +## 6. Reseed and Report Metrics + +```c +static SeleneErrno my_error_model_shot_start(SeleneErrorModelInstance handle, + uint64_t shot_id, + uint64_t seed) { + MyErrorModel *model = (MyErrorModel *)handle; + (void)shot_id; + model->rng_state = seed; + model->injected_errors = 0; + return 0; +} + +static SeleneErrno my_error_model_get_metrics(SeleneErrorModelInstance handle, + uint8_t nth_metric, + char *tag, + uint8_t *datatype, + uint64_t *value) { + MyErrorModel *model = (MyErrorModel *)handle; + if (nth_metric != 0) { + return 1; + } + strcpy(tag, "injected_errors"); + *datatype = 2; /* u64 */ + *value = model->injected_errors; + return 0; +} +``` + +## 7. Export the Descriptor + +```c +const SeleneErrorModelPluginDescriptorV1 selene_error_model_plugin_descriptor_v1 = { + .struct_size = sizeof(SeleneErrorModelPluginDescriptorV1), + .api_version = SELENE_ERROR_MODEL_CURRENT_API_VERSION, + .init_fn = my_error_model_init, + .exit_fn = my_error_model_exit, + .shot_start_fn = my_error_model_shot_start, + .shot_end_fn = my_error_model_shot_end, + .handle_operations_fn = my_error_model_handle_operations, + .get_metrics_fn = my_error_model_get_metrics, + .negotiate_gateset_fn = my_error_model_negotiate_gateset, +}; +``` + +The descriptor should make the plugin's contract obvious: what it accepts, what +it emits, and how it reports failures. diff --git a/docs/extensibility/error-model/rust.md b/docs/extensibility/error-model/rust.md new file mode 100644 index 00000000..ec13cbbb --- /dev/null +++ b/docs/extensibility/error-model/rust.md @@ -0,0 +1,236 @@ +# Writing an Error Model Plugin in Rust + +Rust error models implement `ErrorModelInterface` and +`ErrorModelInterfaceFactory`, then export the plugin with +`selene_core::export_error_model_plugin!`. + +The example below shows the structure of an operation-level stochastic model. It +forwards runtime operations to the simulator and injects an extra gate with some +probability. + +## 1. Create a `cdylib` Crate + +```toml +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +rand = "0.8" +rand_chacha = "0.3" +selene-core = { path = "../../path/to/selene-core/rust" } +``` + +## 2. Define Injected Gates + +The incoming gateset is what the runtime may emit. Many error models should not +care which named gate caused the noise; they only need to know which qubits the +gate touched. In that case, forward incoming gates unchanged and add only the +extra gates the model may inject. + +This example injects Pauli-like rotations, so it needs builtin `RZ` and +`PhasedX` downstream: + +```rust +use selene_core::define_gateset; +use selene_core::gatewire::builtin::{PhasedX, RZ}; + +define_gateset! { + enum InjectedGates { + RZ(RZ), + PhasedX(PhasedX), + } +} +``` + +## 3. Store Model State + +```rust +use anyhow::{Result, bail}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use selene_core::error_model::{BatchResult, ErrorModelInterface}; +use selene_core::gatewire::{Angle, DynamicGateSet, GateDecl, GateSetSpec, GateSpec, Qubit}; +use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::simulator::SimulatorInterface; +use selene_core::utils::MetricValue; + +struct MyErrorModel { + probability: f64, + rng: ChaCha8Rng, + injected_errors: u64, +} +``` + +Reseed the RNG in `shot_start`. That makes each shot reproducible from Selene's +shot seed. + +## 4. Negotiate Gates + +Return a gateset containing all forwarded input gates plus every gate the model +may inject. If a required builtin semantic ID is already present, check that the +declaration is the builtin declaration you expect: + +```rust +impl MyErrorModel { + fn ensure_output_gate(declarations: &mut Vec, required: GateDecl) -> Result<()> { + match declarations + .iter() + .find(|decl| decl.semantic_id == required.semantic_id) + { + Some(existing) if *existing == required => Ok(()), + Some(existing) => bail!( + "required builtin gate {} conflicts with incoming gate {}", + required.name, + existing.name, + ), + None => { + declarations.push(required); + Ok(()) + } + } + } +} + +impl ErrorModelInterface for MyErrorModel { + fn negotiate_gateset(&mut self, input: &DynamicGateSet) -> Result { + let mut declarations: Vec<_> = input.declarations().cloned().collect(); + Self::ensure_output_gate(&mut declarations, RZ::declaration())?; + Self::ensure_output_gate(&mut declarations, PhasedX::declaration())?; + DynamicGateSet::from_declarations(declarations).map_err(Into::into) + } + + /* other methods shown below */ +} +``` + +If your model forwards every gate unchanged and injects nothing, return +`input.clone()`. If your model only supports a particular semantic vocabulary, +reject unsupported declarations here instead. + +## 5. Handle Operations + +The error model receives a `BatchOperation` and a mutable simulator interface. +It can call the simulator directly: + +```rust +fn handle_operations( + &mut self, + operations: BatchOperation, + simulator: &mut dyn SimulatorInterface, +) -> Result { + let mut results = BatchResult::default(); + + for op in operations { + match op { + Operation::Gate { gate } => { + let qubits: Vec = gate.qubit_operands().map(u64::from).collect(); + simulator.handle_operations(BatchOperation::simulator(vec![ + Operation::from_gate_instance(gate)?, + ]))?; + + if qubits.len() == 1 && self.rng.random::() < self.probability { + let injected = selene_core::gatewire::builtin::PhasedX { + q0: Qubit(qubits[0].try_into()?), + theta: Angle(std::f64::consts::PI), + phi: Angle(0.0), + } + .to_instance(); + + simulator.handle_operations(BatchOperation::simulator(vec![ + Operation::Gate { gate: injected }, + ]))?; + self.injected_errors += 1; + } + } + Operation::Measure { qubit_id, result_id } => { + let mut batch = simulator.handle_operations(BatchOperation::simulator(vec![ + Operation::Measure { qubit_id, result_id }, + ]))?; + results.extend(batch); + } + Operation::MeasureLeaked { qubit_id, result_id } => { + let mut batch = simulator.handle_operations(BatchOperation::simulator(vec![ + Operation::MeasureLeaked { qubit_id, result_id }, + ]))?; + results.extend(batch); + } + Operation::Reset { qubit_id } => { + simulator.handle_operations(BatchOperation::simulator(vec![ + Operation::Reset { qubit_id }, + ]))?; + } + Operation::Custom { custom_tag, .. } => { + bail!("error model does not support custom operation {custom_tag}"); + } + } + } + + Ok(results) +} +``` + +`OwnedGateInstance::qubit_operands()` is the useful generic hook here. It lets a +model such as depolarizing noise apply a one-qubit channel to any one-qubit gate +and a two-qubit channel to any two-qubit gate without knowing the gate's display +name or semantic ID. + +## 6. Lifecycle and Metrics + +```rust +fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { + self.rng = ChaCha8Rng::seed_from_u64(seed); + self.injected_errors = 0; + Ok(()) +} + +fn get_metric(&mut self, nth_metric: u8) -> Result> { + match nth_metric { + 0 => Ok(Some(("injected_errors".into(), MetricValue::U64(self.injected_errors)))), + _ => Ok(None), + } +} +``` + +Metrics should describe model behavior. Prefer names such as `injected_errors`, +`measurement_flips`, or `leakage_events` over names tied to one gate unless the +metric is truly gate-specific. + +## 7. Export the Plugin + +```rust +use std::sync::Arc; +use selene_core::error_model::ErrorModelInterfaceFactory; + +struct MyErrorModelFactory; + +impl ErrorModelInterfaceFactory for MyErrorModelFactory { + type Interface = MyErrorModel; + + fn init( + self: Arc, + _n_qubits: u64, + args: &[impl AsRef], + ) -> Result> { + // Rust plugin factories receive argv-style arguments. args[0] is a + // synthetic program name so parsers such as clap can consume the same + // slice directly. Skip it when parsing manually. + let probability = args + .get(1) + .map(|arg| arg.as_ref().parse()) + .transpose()? + .unwrap_or(0.0); + + Ok(Box::new(MyErrorModel { + probability, + rng: ChaCha8Rng::seed_from_u64(0), + injected_errors: 0, + })) + } +} + +selene_core::export_error_model_plugin!(MyErrorModelFactory); +``` + +Build the crate and configure Selene to load the resulting shared library as an +error model. diff --git a/docs/extensibility/fundamentals.md b/docs/extensibility/fundamentals.md new file mode 100644 index 00000000..a07dd188 --- /dev/null +++ b/docs/extensibility/fundamentals.md @@ -0,0 +1,250 @@ +# Plugin Fundamentals + +This page is the shared background for runtime, error model, and simulator +plugins. If you understand the flow here, the individual plugin tutorials become +much smaller: they are all implementations of the same contract at different +points in the pipeline. + +## Why Selene Uses Plugins + +Selene deliberately separates four concerns: + +- The interface describes the user's program. +- The runtime controls scheduling, allocation, batching, and result futures. +- The error model mutates the operation stream to represent stochastic noise. +- The simulator executes the final operation stream. + +Those concerns are different enough that they should be developed and tested +independently. A runtime might care about batching gates into hardware-timed +windows. An error model might care about random seeds and injected operations. A +simulator might care about state-vector, stabilizer, tensor-network, or external +engine details. The plugin API keeps those decisions out of the core loop while +still giving Selene enough information to validate compatibility before a shot +starts. + +## The Runtime Pipeline + +A configured Selene run has this shape: + +```text +user/interface + -> runtime + -> error model + -> simulator +``` + +At configuration time, gates flow through the same path as a negotiation: + +```text +interface gateset + -> runtime accepts and returns runtime output gateset + -> error model accepts and returns error-model output gateset + -> simulator accepts or rejects +``` + +At shot time, operations flow through the pipeline: + +1. Selene calls `shot_start(shot_id, seed)` on each plugin instance. +2. The user-facing interface calls runtime operations such as `qalloc`, `gate`, + `measure`, `reset`, and barriers. +3. The runtime buffers or lowers those requests. +4. Selene repeatedly asks the runtime for the next batch of operations. +5. The error model receives each batch, may inject or mutate operations, calls + the simulator, and records measurement results. +6. Selene returns measurement results to the runtime by result ID. +7. The user-facing interface reads results from the runtime. +8. Selene calls `shot_end()`, gathers metrics, and either starts another shot + or calls `exit()`. + +The runtime is the only plugin that talks directly to the user-facing interface. +The simulator is the only plugin that actually owns the quantum state. The error +model sits between them. + +## Shared Library Model + +Native plugins are loaded from a shared library. The library exposes a descriptor +for one plugin type: + +- Runtime libraries expose `SeleneRuntimePluginDescriptorV1`. +- Error model libraries expose `SeleneErrorModelPluginDescriptorV1`. +- Simulator libraries expose `SeleneSimulatorPluginDescriptorV1`. + +Selene finds that descriptor either as a public symbol named +`selene__plugin_descriptor_v1` or through a getter function named +`selene__get_plugin_descriptor_v1`. + +The descriptor contains: + +- `struct_size`, so Selene can detect a descriptor compiled against a different + struct layout. +- `api_version`, so Selene can reject an incompatible ABI version. +- Function pointers for the plugin lifecycle and operation callbacks. + +Rust plugins usually do not write the descriptor by hand. They implement a +Selene trait and use an export macro. C plugins write the descriptor directly. + +## Descriptor Compatibility + +Descriptor validation is intentionally boring: + +- `struct_size` must match the descriptor type Selene expects. +- The API version reserved byte must be zero. +- The major and minor API versions must match Selene's current API. +- Required callbacks must be present. +- Optional callbacks may be null, in which case Selene uses the documented + default behavior for that feature. + +For C plugins, use the version macro from the header for the plugin type you are +implementing: + +```c +#include + +const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { + .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), + .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + /* callbacks */ +}; +``` + +For Rust plugins, use the export macro. It fills in `struct_size` and the +current API version for you. + +## Instances and Ownership + +The descriptor describes the plugin library. An instance is the state for one +configured Selene component. Selene can create more than one instance from the +same library, so do not store per-run state in global variables unless it is +protected and intentionally shared. + +The C ABI represents instances as opaque pointers. The plugin allocates its +state during `init`, writes the pointer to the out-parameter, receives it back in +every callback, and frees it during `exit`. + +The Rust helpers map that pattern onto boxed trait objects. Your plugin owns a +normal Rust struct; the export macro handles the opaque pointer conversion. + +## Gatewire and Gatesets + +Gatewire is the gate transport layer. It solves two problems: + +- A plugin should not need to know which Python method, QIS instruction, or + frontend produced a gate. +- Selene should validate gate compatibility before operations are emitted. + +A gate declaration contains: + +- A semantic ID, such as `gatewire.builtin.RZ.v1`. +- A display name, such as `RZ`. +- A version. +- An ordered list of typed operands. + +A gate instance contains the same semantic ID and concrete operand values. The +wire format is stable bytes. Rust code normally works with typed gate structs or +`OwnedGateInstance`; C code works with serialized bytes and the `gw_*` functions +from `selene/gatewire.h`. + +The builtin gates are: + +- `RZ(q0, theta)` +- `PhasedX(q0, theta, phi)` +- `ZZPhase(q0, q1, theta)` +- `PhasedXX(q0, q1, theta, phi)` + +The old operation names are not part of the plugin API. If a QIS frontend has an +old or external name for a gate, it should translate that name into a gatewire +gate before calling Selene. + +## Gateset Negotiation + +Every plugin type can implement `negotiate_gateset`. + +The input is the gateset the previous layer may send. The output is the gateset +this plugin may send to the next layer. A plugin can: + +- Return the input unchanged. +- Reject the input with an error. +- Accept the input but return a different output gateset after lowering. + +For example, a runtime may accept `RZ` because users are allowed to write it, but +emit only `PhasedX` and `ZZPhase` after lowering. The simulator should validate +the gateset it actually receives from the error model, not the gateset the user +originally requested. + +The C ABI uses a two-call buffer protocol for gateset negotiation: + +1. Selene calls the callback with `output == NULL` and `output_len == 0`. + The plugin writes the required byte count to `written`. +2. Selene allocates a buffer and calls again. The plugin writes serialized + gateset bytes into `output` and writes the byte count to `written`. + +If the plugin leaves `negotiate_gateset_fn` null, Selene treats that as +"identity negotiation": the plugin accepts and emits the same gateset. Prefer an +explicit implementation for public plugins, because it documents the gates you +actually support. + +## Operations and Batches + +The runtime does not call the simulator directly. It returns batches through a +callback interface. A batch can contain gates, measurements, resets, custom +operations, and timing information. + +The error model receives the batch through an extractor interface, not as a raw +array. This keeps the C ABI stable while allowing Selene's internal batch +representation to change. The error model then calls the simulator operation +interface to apply gates, measurements, resets, or postselection. + +Rust plugins normally see `BatchOperation` and `BatchResult` values. C plugins +see callback handles. + +## Measurement Results + +Runtimes assign result IDs when a measurement is scheduled. A result ID is a +future, not the measurement value. The runtime returns the ID to the interface, +and Selene later calls `set_bool_result` or `set_u64_result` when the simulator +result is available. + +Runtimes must maintain reference counts for result IDs. Once a result's +reference count reaches zero, it is invalid to refer to it again. + +Error models report measurement results back through an +`ErrorModelSetResultHandle`. Simulators return measurement outcomes to the error +model immediately. + +## Randomness and Reproducibility + +`shot_start` receives a seed. Any plugin that uses randomness should derive all +per-shot randomness from that seed. A shot should be repeatable when rerun with +the same configuration and seed, regardless of how many shots came before it. + +This matters most for error models, but it also applies to runtimes or +simulators that use randomized choices internally. + +## Metrics + +Each plugin can expose dynamic metrics. Selene calls `get_metric(0)`, then +`get_metric(1)`, and so on until the plugin reports that there are no more +metrics. + +Metric names should describe the plugin's own behavior rather than hardcoding a +particular gate vocabulary. For example, prefer `injected_errors` or +`unsupported_gate_rejections` over names tied to a single builtin gate unless the +metric is genuinely gate-specific. + +## Error Handling + +Rust plugins return `anyhow::Result`. C plugins return `SeleneErrno`, where +zero means success and nonzero means failure. A failure normally aborts the +current emulation run and propagates to the user. + +For gatewire functions, check `GwStatus`. A nonzero status means the gate or +gateset was invalid, the buffer was too small, or a pointer argument was wrong. + +## Threading and Reentrancy + +Plugin callbacks should not assume they are globally unique. Selene may create +multiple instances, and future execution modes may run independent instances at +the same time. Keep instance state behind the instance pointer or Rust struct. +If you use process-global state, protect it explicitly and document why it is +shared. + diff --git a/docs/extensibility/gates/README.md b/docs/extensibility/gates/README.md new file mode 100644 index 00000000..bde59db1 --- /dev/null +++ b/docs/extensibility/gates/README.md @@ -0,0 +1,62 @@ +# Gates and Gatesets + +Gates are Selene's shared vocabulary. They are not tied to a Python method name, +a Rust enum variant, or a simulator's private instruction set. A gate is defined +by a stable semantic ID and a typed operand list, and a gateset is the set of +gate declarations a layer promises it can send or receive. + +Read the language-specific tutorials when you need to construct or consume +gates: + +- [Writing gates in Rust](rust.md) +- [Writing gates in C](c.md) + +## Builtin Gates + +Selene ships four builtin gate declarations: + +- `RZ(q0, theta)` +- `PhasedX(q0, theta, phi)` +- `ZZPhase(q0, q1, theta)` +- `PhasedXX(q0, q1, theta, phi)` + +The builtin names are the names used by the plugin API. Older names such as +`rz`, `rxy`, `rzz`, and `rpp` belong at compatibility boundaries such as QIS +parsers, not inside plugin implementations. + +## Custom Gates + +Use a custom gate when a runtime, error model, and simulator need to agree on an +operation that is not covered by the builtins. The declaration should be stable: +choose a semantic ID that includes your namespace and a version number, then +only change the meaning by introducing a new version. + +For example: + +```text +com.example.calibration.VirtualZ.v1(q0: qubit, theta: f64) +``` + +Once a gate is declared, instances of that gate can travel through the same +runtime, error model, and simulator path as builtin gates. + +## Gatesets in the Pipeline + +The interface creates the initial gateset. Each plugin then negotiates: + +```text +interface gateset -> runtime output -> error model output -> simulator input +``` + +The output gateset can differ from the input gateset. This is intentional. It +lets a layer accept a user-facing vocabulary while emitting a lower-level one. + +## What to Read Next + +If you are writing a plugin in Rust, start with [Writing gates in Rust](rust.md) +and then the Rust guide for your plugin type. + +If you are writing a plugin in C, start with [Writing gates in C](c.md). The C +plugin tutorials assume you are comfortable with serialized gatewire bytes and +the `gw_*` functions. + diff --git a/docs/extensibility/gates/c.md b/docs/extensibility/gates/c.md new file mode 100644 index 00000000..f094f8cd --- /dev/null +++ b/docs/extensibility/gates/c.md @@ -0,0 +1,211 @@ +# Writing Gates in C + +C plugins use the gatewire C ABI from `selene/gatewire.h`. The ABI is explicit: +you create gatesets through opaque `GwGateSet` handles, serialize gate instances +into byte buffers, and decode incoming gate bytes through `GwDecodedGate`. + +## Create a Gateset + +Use the builtin helpers when possible: + +```c +#include + +GwGateSet *set = NULL; +GwStatus status = gw_gateset_new(&set); +if (status != GW_STATUS_OK) { + return 1; +} + +gw_gateset_add_builtin_rz(set); +gw_gateset_add_builtin_phased_x(set); +gw_gateset_add_builtin_zz_phase(set); +``` + +Free the gateset when you are done: + +```c +gw_gateset_free(set); +``` + +## Serialize a Gateset + +Plugin negotiation callbacks use serialized gateset bytes. The pattern is: + +```c +static int write_gateset(GwGateSet *set, + uint8_t *output, + size_t output_len, + size_t *written) { + size_t required = 0; + GwStatus status = gw_gateset_serialized_len(set, &required); + if (status != GW_STATUS_OK) { + return 1; + } + + if (output == NULL || output_len == 0) { + *written = required; + return 0; + } + + status = gw_gateset_serialize(set, output, output_len, written); + return status == GW_STATUS_OK ? 0 : 1; +} +``` + +The same two-call shape is used by runtime, error model, and simulator gateset +negotiation. + +## Decode an Incoming Gate + +Runtime and simulator `gate_fn` callbacks receive gate bytes. Decode them before +inspecting operands: + +```c +static int handle_gate(const uint8_t *data, size_t len) { + GwDecodedGate *gate = NULL; + GwStatus status = gw_gate_deserialize(data, len, &gate); + if (status != GW_STATUS_OK) { + return 1; + } + + GwSemanticId id; + status = gw_decoded_gate_semantic_id(gate, &id); + if (status != GW_STATUS_OK) { + gw_decoded_gate_free(gate); + return 1; + } + + if (gw_semantic_id_eq(id, gw_builtin_rz_semantic_id())) { + GwGateValue q0; + GwGateValue theta; + gw_decoded_gate_value_at(gate, 0, &q0); + gw_decoded_gate_value_at(gate, 1, &theta); + /* q0.data.qubit, theta.data.f64_value */ + } + + gw_decoded_gate_free(gate); + return 0; +} +``` + +Always check both the semantic ID and the expected operand kinds. The order of +operands is part of the declaration. + +## Inspect Qubit Operands Generically + +Some plugins care about gate arity rather than the gate's identity. A +depolarizing error model, for example, may want to apply a one-qubit channel +after every one-qubit gate and a two-qubit channel after every two-qubit gate. + +Use the qubit operand helpers after decoding the gate: + +```c +size_t qubits = 0; +if (gw_decoded_gate_qubit_operand_count(gate, &qubits) != GW_STATUS_OK) { + gw_decoded_gate_free(gate); + return 1; +} + +if (qubits == 1) { + uint32_t q0 = 0; + if (gw_decoded_gate_qubit_operand_at(gate, 0, &q0) != GW_STATUS_OK) { + gw_decoded_gate_free(gate); + return 1; + } + /* apply a one-qubit policy to q0 */ +} else if (qubits == 2) { + uint32_t q0 = 0; + uint32_t q1 = 0; + gw_decoded_gate_qubit_operand_at(gate, 0, &q0); + gw_decoded_gate_qubit_operand_at(gate, 1, &q1); + /* apply a two-qubit policy to q0 and q1 */ +} +``` + +This keeps the plugin independent of display names and works equally well for +builtin and custom gates. + +## Define a Custom Gate + +Create a semantic ID and a declaration view: + +```c +static GwStatus add_virtual_z(GwGateSet *set) { + static const char id_text[] = "com.example.calibration.VirtualZ.v1"; + static const char name[] = "VirtualZ"; + static const char q0_name[] = "q0"; + static const char theta_name[] = "theta"; + + GwSemanticId id; + GwStatus status = gw_semantic_id_from_text( + id_text, + sizeof(id_text) - 1, + &id + ); + if (status != GW_STATUS_OK) { + return status; + } + + GwOperandDeclView operands[2] = { + { + .abi_size = sizeof(GwOperandDeclView), + .name_ptr = q0_name, + .name_len = sizeof(q0_name) - 1, + .kind = GW_OPERAND_KIND_QUBIT, + }, + { + .abi_size = sizeof(GwOperandDeclView), + .name_ptr = theta_name, + .name_len = sizeof(theta_name) - 1, + .kind = GW_OPERAND_KIND_F64, + }, + }; + + GwGateDeclView decl = { + .abi_size = sizeof(GwGateDeclView), + .semantic_id = id, + .name_ptr = name, + .name_len = sizeof(name) - 1, + .operands_ptr = operands, + .operands_len = 2, + .version = 1, + }; + + return gw_gateset_add_decl(set, &decl); +} +``` + +The strings and operand arrays only need to live for the duration of +`gw_gateset_add_decl`; the gateset stores its own copy. + +## Serialize a Gate Instance + +Use a `GwGateInstanceView` with operand values: + +```c +GwGateValue values[2] = { + { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_QUBIT, + .data.qubit = 0, + }, + { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_F64, + .data.f64_value = 3.141592653589793, + }, +}; + +GwGateInstanceView view = { + .abi_size = sizeof(GwGateInstanceView), + .semantic_id = gw_builtin_rz_semantic_id(), + .values_ptr = values, + .values_len = 2, +}; + +size_t required = 0; +gw_gate_serialized_len(&view, &required); +``` + +After you know the size, allocate a buffer and call `gw_gate_serialize`. diff --git a/docs/extensibility/gates/rust.md b/docs/extensibility/gates/rust.md new file mode 100644 index 00000000..30a7f756 --- /dev/null +++ b/docs/extensibility/gates/rust.md @@ -0,0 +1,167 @@ +# Writing Gates in Rust + +Rust code should use the typed gatewire API whenever possible. You define gate +structs, combine them into a gateset enum, and serialize or decode gate +instances through that gateset. + +## Use Builtin Gates + +The builtin gates live in `selene_core::gatewire::builtin`: + +```rust +use selene_core::define_gateset; +use selene_core::gatewire::builtin::RZ; +use selene_core::gatewire::{Angle, GateSet, Qubit}; + +define_gateset! { + enum RzOnly { + RZ(RZ), + } +} + +let gate = RZ { + q0: Qubit(0), + theta: Angle(std::f64::consts::PI), +}; + +let gateset = GateSet::::new()?; +let bytes = gateset.serialize_gate(&RzOnly::RZ(gate))?; +``` + +For a set containing several gate types, define an enum: + +```rust +use selene_core::define_gateset; +use selene_core::gatewire::builtin::{PhasedX, RZ, ZZPhase}; + +define_gateset! { + pub enum HeliosGates { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), + } +} +``` + +The enum gives you one type that can serialize outgoing gates and decode +incoming gate instances: + +```rust +use selene_core::gatewire::{GateSet, TryDecode}; + +let gateset = GateSet::::new()?; +let decoded = gateset.decode(&wire_bytes)?; +``` + +## Define a Custom Gate + +Use `define_gate!` for custom declarations: + +```rust +use selene_core::define_gate; +use selene_core::gatewire::{Angle, Qubit}; + +define_gate! { + pub struct VirtualZ [ + id = "com.example.calibration.VirtualZ.v1", + display = "VirtualZ", + version = 1, + ] { + q0: Qubit, + theta: Angle, + } +} +``` + +The semantic ID should be globally stable. Include a namespace you control and a +version suffix. If the operand order or meaning changes, define `VirtualZ.v2` +instead of reusing `VirtualZ.v1`. + +## Negotiate Gates in a Plugin + +A Rust runtime or error model receives a `DynamicGateSet` and returns the +`DynamicGateSet` it will emit: + +```rust +use anyhow::Result; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec}; + +fn output_gateset() -> DynamicGateSet { + DynamicGateSet::from_declarations(HeliosGates::declarations()) + .expect("Helios gate declarations are unique") +} + +fn negotiate_gateset(input: &DynamicGateSet) -> Result { + for decl in input.declarations() { + // Reject gates you cannot accept from the previous layer. + if !output_gateset().contains(decl.semantic_id) { + anyhow::bail!("unsupported input gate {}", decl.name); + } + } + + // Return the gates this plugin may emit downstream. + Ok(output_gateset()) +} +``` + +Do not rely on display names for compatibility. Use semantic IDs and typed +decoding. + +## Decode Incoming Gates + +Plugin callbacks receive generic `OwnedGateInstance` values. Decode them with +your gateset enum: + +```rust +use selene_core::gatewire::TryDecode; + +match HeliosGates::try_from_instance(gate)? { + Some(HeliosGates::RZ(rz)) => { + // use rz.q0 and rz.theta + } + Some(HeliosGates::PhasedX(px)) => { + // use px.q0, px.theta, px.phi + } + Some(HeliosGates::ZZPhase(zz)) => { + // use zz.q0, zz.q1, zz.theta + } + None => anyhow::bail!("gate was not part of HeliosGates"), +} +``` + +This keeps the plugin independent of frontend naming and independent of the +serialized wire format. + +## Inspect Qubit Operands Generically + +Some plugins should not decode by gate identity. Error models often care only +whether a gate touched one qubit, two qubits, or something else. Use +`OwnedGateInstance::qubit_operands()` for that: + +```rust +use selene_core::runtime::Operation; + +match operation { + Operation::Gate { gate } => { + let qubits: Vec = gate.qubit_operands().map(u64::from).collect(); + match qubits.as_slice() { + [q0] => { + // apply a one-qubit policy to q0 + } + [q0, q1] => { + // apply a two-qubit policy to q0 and q1 + } + _ => { + // pass through, reject, or use a model-specific policy + } + } + } + _ => {} +} +``` + +There are also convenience methods: + +- `qubit_operand_count()` returns the number of qubit operands. +- `single_qubit_operand()` returns `Some(q)` only when exactly one qubit operand + is present. diff --git a/docs/extensibility/runtime/README.md b/docs/extensibility/runtime/README.md new file mode 100644 index 00000000..f89c1e59 --- /dev/null +++ b/docs/extensibility/runtime/README.md @@ -0,0 +1,50 @@ +# Runtime Plugins + +A runtime plugin is the scheduling layer. It receives user-facing operations, +tracks qubit allocation and measurement futures, and emits batches of operations +for the error model and simulator. + +Use a runtime plugin when you want to control any of these: + +- Qubit allocation and deallocation. +- Gate lowering or rewriting. +- Operation batching. +- Timing annotations and barriers. +- Lazy measurement execution. +- Custom runtime calls exposed by higher-level interfaces. + +The runtime should not simulate quantum state, and it should not inject noise. +It may reorder or lower operations if that is the runtime's purpose, but it must +preserve the semantics it promises to the user-facing interface. + +## Mental Model + +The interface calls methods such as `qalloc`, `gate`, `measure`, and `reset`. +The runtime records those calls. When Selene needs work to execute, it calls +`get_next_operations`, and the runtime emits a batch. + +Measurement is deliberately indirect. The runtime returns a result ID when a +measurement is requested. Later, after the error model and simulator have +processed the batch, Selene calls `set_bool_result` or `set_u64_result` on the +runtime with the value for that result ID. + +## Gateset Role + +The runtime is the first plugin in the gateset handshake. It receives the gates +the interface may call. It returns the gates it may emit in batches. + +This means a runtime can accept a high-level gate and lower it: + +```text +interface -> runtime: RZ, PhasedX, ZZPhase +runtime -> next layer: PhasedX, ZZPhase +``` + +If a runtime cannot accept one of the interface gates, it should reject the +configuration during negotiation. Do not wait until the first `gate` call. + +## Tutorials + +- [Writing a runtime in Rust](rust.md) +- [Writing a runtime in C](c.md) + diff --git a/docs/extensibility/runtime/c.md b/docs/extensibility/runtime/c.md new file mode 100644 index 00000000..6de4cc23 --- /dev/null +++ b/docs/extensibility/runtime/c.md @@ -0,0 +1,248 @@ +# Writing a Runtime Plugin in C + +C runtimes implement the descriptor ABI directly. You include +`selene/runtime.h` for the runtime descriptor and `selene/gatewire.h` for gate +transport. + +## 1. Define Instance State + +The instance pointer is owned by your plugin: + +```c +#include +#include +#include +#include + +typedef struct { + uint64_t n_qubits; + uint8_t *allocated; + uint64_t next_result; + /* Your operation queue and result storage go here. */ +} MyRuntime; +``` + +Allocate it in `init` and free it in `exit`: + +```c +static SeleneErrno my_runtime_init(RuntimeInstance *out, + uint64_t n_qubits, + uint64_t start, + uint32_t argc, + const char *const *argv) { + (void)start; + (void)argc; + (void)argv; + + MyRuntime *runtime = calloc(1, sizeof(MyRuntime)); + if (runtime == NULL) { + return 1; + } + runtime->n_qubits = n_qubits; + runtime->allocated = calloc((size_t)n_qubits, sizeof(uint8_t)); + if (runtime->allocated == NULL) { + free(runtime); + return 1; + } + + *out = runtime; + return 0; +} + +static SeleneErrno my_runtime_exit(RuntimeInstance handle) { + MyRuntime *runtime = (MyRuntime *)handle; + free(runtime->allocated); + free(runtime); + return 0; +} +``` + +## 2. Negotiate Gates + +The runtime receives the interface gateset and returns the gates it may emit. +This example accepts and emits `RZ`, `PhasedX`, and `ZZPhase`: + +```c +static SeleneErrno my_runtime_negotiate_gateset(RuntimeInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written) { + (void)handle; + + GwGateSet *incoming = NULL; + if (gw_gateset_deserialize(input, input_len, &incoming) != GW_STATUS_OK) { + return 1; + } + + GwGateSet *accepted = NULL; + gw_gateset_new(&accepted); + gw_gateset_add_builtin_rz(accepted); + gw_gateset_add_builtin_phased_x(accepted); + gw_gateset_add_builtin_zz_phase(accepted); + + size_t len = 0; + gw_gateset_len(incoming, &len); + for (size_t i = 0; i < len; i++) { + GwGateDeclInfo decl; + uint8_t contains = 0; + gw_gateset_decl_at(incoming, i, &decl); + gw_gateset_contains(accepted, decl.semantic_id, &contains); + if (!contains) { + gw_gateset_free(incoming); + gw_gateset_free(accepted); + return 1; + } + } + + size_t required = 0; + gw_gateset_serialized_len(accepted, &required); + if (output == NULL || output_len == 0) { + *written = required; + gw_gateset_free(incoming); + gw_gateset_free(accepted); + return 0; + } + + GwStatus status = gw_gateset_serialize(accepted, output, output_len, written); + gw_gateset_free(incoming); + gw_gateset_free(accepted); + return status == GW_STATUS_OK ? 0 : 1; +} +``` + +If your runtime lowers gates, validate against the input gates you accept and +serialize a different gateset for the gates you emit. + +## 3. Accept Gates + +`gate_fn` receives a serialized gate instance: + +```c +static SeleneErrno my_runtime_gate(RuntimeInstance handle, + const uint8_t *data, + size_t len) { + MyRuntime *runtime = (MyRuntime *)handle; + GwDecodedGate *gate = NULL; + if (gw_gate_deserialize(data, len, &gate) != GW_STATUS_OK) { + return 1; + } + + GwSemanticId id; + gw_decoded_gate_semantic_id(gate, &id); + if (gw_semantic_id_eq(id, gw_builtin_rz_semantic_id())) { + /* Decode operands and append an RZ operation to your queue. */ + } else if (gw_semantic_id_eq(id, gw_builtin_phased_x_semantic_id())) { + /* Append PhasedX. */ + } else if (gw_semantic_id_eq(id, gw_builtin_zz_phase_semantic_id())) { + /* Append ZZPhase. */ + } else { + gw_decoded_gate_free(gate); + return 1; + } + + (void)runtime; + gw_decoded_gate_free(gate); + return 0; +} +``` + +Store enough information in your own queue to emit the operation later from +`get_next_operations`. + +## 4. Allocate Qubits and Create Result IDs + +Qubit allocation returns `UINT64_MAX` when no qubits are available: + +```c +static SeleneErrno my_runtime_qalloc(RuntimeInstance handle, uint64_t *out) { + MyRuntime *runtime = (MyRuntime *)handle; + for (uint64_t q = 0; q < runtime->n_qubits; q++) { + if (!runtime->allocated[q]) { + runtime->allocated[q] = 1; + *out = q; + return 0; + } + } + *out = UINT64_MAX; + return 0; +} +``` + +Measurements return a future result ID and queue a measurement operation: + +```c +static SeleneErrno my_runtime_measure(RuntimeInstance handle, + uint64_t qubit, + uint64_t *result_id) { + MyRuntime *runtime = (MyRuntime *)handle; + *result_id = runtime->next_result++; + /* Queue a measurement of qubit with this result ID. */ + return 0; +} +``` + +Implement `set_bool_result_fn`, `get_bool_result_fn`, and the reference-count +callbacks so the interface can safely observe results. + +## 5. Emit a Batch + +`get_next_operations_fn` receives a `RuntimeGetOperationHandle`. Use its +callbacks to populate the batch: + +```c +static SeleneErrno my_runtime_get_next_operations(RuntimeInstance handle, + struct RuntimeGetOperationHandle ops) { + MyRuntime *runtime = (MyRuntime *)handle; + + /* For each queued operation: */ + /* ops.interface.gate_fn(ops.instance, gate_bytes, gate_len); */ + /* ops.interface.measure_fn(ops.instance, qubit, result_id); */ + /* ops.interface.reset_fn(ops.instance, qubit); */ + + (void)runtime; + return 0; +} +``` + +An empty callback sequence means the runtime has no work ready. + +## 6. Export the Descriptor + +The descriptor is the public ABI Selene loads: + +```c +const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { + .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), + .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + .init_fn = my_runtime_init, + .exit_fn = my_runtime_exit, + .get_next_operations_fn = my_runtime_get_next_operations, + .shot_start_fn = my_runtime_shot_start, + .shot_end_fn = my_runtime_shot_end, + .get_metrics_fn = my_runtime_get_metrics, + .qalloc_fn = my_runtime_qalloc, + .qfree_fn = my_runtime_qfree, + .local_barrier_fn = my_runtime_local_barrier, + .global_barrier_fn = my_runtime_global_barrier, + .measure_fn = my_runtime_measure, + .measure_leaked_fn = my_runtime_measure_leaked, + .reset_fn = my_runtime_reset, + .force_result_fn = my_runtime_force_result, + .get_bool_result_fn = my_runtime_get_bool_result, + .get_u64_result_fn = my_runtime_get_u64_result, + .set_bool_result_fn = my_runtime_set_bool_result, + .set_u64_result_fn = my_runtime_set_u64_result, + .increment_future_refcount_fn = my_runtime_increment_future_refcount, + .decrement_future_refcount_fn = my_runtime_decrement_future_refcount, + .custom_call_fn = my_runtime_custom_call, + .simulate_delay_fn = my_runtime_simulate_delay, + .gate_fn = my_runtime_gate, + .negotiate_gateset_fn = my_runtime_negotiate_gateset, +}; +``` + +Use clear failures for unsupported features. For example, if your runtime does +not support `custom_call`, return nonzero rather than silently ignoring it. + diff --git a/docs/extensibility/runtime/rust.md b/docs/extensibility/runtime/rust.md new file mode 100644 index 00000000..8024338a --- /dev/null +++ b/docs/extensibility/runtime/rust.md @@ -0,0 +1,252 @@ +# Writing a Runtime Plugin in Rust + +Rust runtimes should implement `RuntimeInterface` and +`RuntimeInterfaceFactory`, then export the plugin with +`selene_core::export_runtime_plugin!`. The macro handles the C descriptor, +opaque instance pointer, ABI version, gateset serialization, and error +translation. + +## 1. Create a `cdylib` Crate + +Your plugin crate should build a shared library: + +```toml +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +selene-core = { path = "../../path/to/selene-core/rust" } +``` + +In a workspace, use the same `selene-core` revision that will load the plugin. + +## 2. Define the Gate Vocabulary + +Use typed gatewire gates. This example accepts the Helios-style builtin set: + +```rust +use selene_core::define_gateset; +use selene_core::gatewire::builtin::{PhasedX, RZ, ZZPhase}; + +define_gateset! { + enum RuntimeGates { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), + } +} +``` + +If the runtime lowers gates, define a second gateset for output. The input +gateset is what the interface may send. The output gateset is what the runtime +may emit downstream. + +## 3. Store Runtime State + +The runtime instance owns the state for one configured run: + +```rust +use selene_core::runtime::{BatchOperation, Operation, RuntimeInterface}; +use selene_core::utils::MetricValue; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec, OwnedGateInstance}; +use anyhow::{Result, bail}; +use std::collections::{HashMap, VecDeque}; + +struct MyRuntime { + n_qubits: u64, + batch_start: selene_core::time::Instant, + allocated: Vec, + queue: VecDeque, + next_result: u64, + bool_results: HashMap, +} +``` + +Keep per-shot or per-run state in the struct. Avoid global mutable state unless +it is intentionally shared and synchronized. + +## 4. Negotiate Gates + +Validate the interface gates and return the runtime output gates: + +```rust +impl MyRuntime { + fn output_gateset() -> DynamicGateSet { + DynamicGateSet::from_declarations(RuntimeGates::declarations()) + .expect("runtime gate declarations are unique") + } +} + +impl RuntimeInterface for MyRuntime { + fn negotiate_gateset(&mut self, input: &DynamicGateSet) -> Result { + let accepted = Self::output_gateset(); + for decl in input.declarations() { + if !accepted.contains(decl.semantic_id) { + bail!("runtime does not accept gate {}", decl.name); + } + } + Ok(accepted) + } + + /* other methods shown below */ +} +``` + +If you omit `negotiate_gateset`, Selene assumes identity negotiation. Public +runtimes should implement it explicitly so unsupported gates fail at +configuration time. + +## 5. Accept Generic Gates + +The runtime receives gates as `OwnedGateInstance`. Decode them through the +gateset you negotiated: + +```rust +fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + let Some(gate) = RuntimeGates::try_from_instance(gate)? else { + bail!("runtime received a gate outside its negotiated gateset"); + }; + + let instance = gate.to_instance(); + self.queue.push_back(Operation::from_gate_instance(instance)?); + Ok(()) +} +``` + +This is the only gate entry point. Do not add separate runtime methods for each +builtin gate. + +## 6. Manage Qubits and Measurements + +The interface asks the runtime to allocate qubits: + +```rust +fn qalloc(&mut self) -> Result { + if let Some((index, slot)) = self + .allocated + .iter_mut() + .enumerate() + .find(|(_, allocated)| !**allocated) + { + *slot = true; + Ok(index as u64) + } else { + Ok(u64::MAX) + } +} + +fn qfree(&mut self, qubit: u64) -> Result<()> { + let Some(slot) = self.allocated.get_mut(qubit as usize) else { + bail!("invalid qubit {qubit}"); + }; + if !*slot { + bail!("qubit {qubit} is not allocated"); + } + *slot = false; + Ok(()) +} +``` + +Measurements return result IDs: + +```rust +fn measure(&mut self, qubit: u64) -> Result { + let result_id = self.next_result; + self.next_result += 1; + self.queue.push_back(Operation::Measure { qubit_id: qubit, result_id }); + Ok(result_id) +} + +fn set_bool_result(&mut self, result_id: u64, value: bool) -> Result<()> { + self.bool_results.insert(result_id, value); + Ok(()) +} + +fn get_bool_result(&mut self, result_id: u64) -> Result> { + Ok(self.bool_results.get(&result_id).copied()) +} +``` + +A production runtime should also implement result reference counting with +`increment_future_refcount` and `decrement_future_refcount`. + +## 7. Emit Batches + +`get_next_operations` returns `None` when the runtime has no work ready. When it +does have work, return a `BatchOperation`: + +```rust +fn get_next_operations(&mut self) -> Result> { + if self.queue.is_empty() { + return Ok(None); + } + + let ops: Vec<_> = self.queue.drain(..).collect(); + let duration = selene_core::time::Duration::from(0); + Ok(Some(BatchOperation::runtime(ops, self.batch_start, duration))) +} +``` + +Use the runtime batch source to attach timing information. If your runtime does +not model time, a zero duration is fine. + +## 8. Complete the Trait + +A minimal runtime also implements lifecycle, reset, barriers, result forcing, +leakage measurement if supported, metrics, and exit. Unsupported optional +features should return a clear error. Methods that are part of the core runtime +contract should be real implementations, not silent no-ops. + +Metrics are dynamic: + +```rust +fn get_metric(&mut self, nth_metric: u8) -> Result> { + match nth_metric { + 0 => Ok(Some(("queued_operations".into(), MetricValue::U64(self.queue.len() as u64)))), + 1 => Ok(Some(("allocated_qubits".into(), MetricValue::U64( + self.allocated.iter().filter(|allocated| **allocated).count() as u64, + )))), + _ => Ok(None), + } +} +``` + +Prefer metrics that describe runtime behavior rather than hardcoding specific +gate names. + +## 9. Export the Plugin + +Implement a factory and export it: + +```rust +use std::sync::Arc; +use selene_core::runtime::{RuntimeInterfaceFactory}; + +struct MyRuntimeFactory; + +impl RuntimeInterfaceFactory for MyRuntimeFactory { + type Interface = MyRuntime; + + fn init( + self: Arc, + n_qubits: u64, + _start: selene_core::time::Instant, + _args: &[impl AsRef], + ) -> Result> { + Ok(Box::new(MyRuntime { + n_qubits, + batch_start: _start, + allocated: vec![false; n_qubits as usize], + queue: VecDeque::new(), + next_result: 0, + bool_results: HashMap::new(), + })) + } +} + +selene_core::export_runtime_plugin!(MyRuntimeFactory); +``` + +Build the crate, then configure Selene to load the resulting shared library as a +runtime plugin. diff --git a/docs/extensibility/simulator/README.md b/docs/extensibility/simulator/README.md new file mode 100644 index 00000000..d99db055 --- /dev/null +++ b/docs/extensibility/simulator/README.md @@ -0,0 +1,48 @@ +# Simulator Plugins + +A simulator plugin owns the quantum state. It receives the final operation +stream after runtime scheduling and error-model mutation, then executes exactly +what it is told. + +Use a simulator plugin when you want to connect Selene to a new backend: + +- A state-vector simulator. +- A stabilizer simulator. +- A tensor-network simulator. +- A hardware-backed or external engine. +- A specialized backend such as a Clifford-only simulator. + +The simulator should not inject errors. It should not silently approximate gates +outside its domain. If it cannot execute a gate, it should reject the gateset +during negotiation. For example, a Clifford-only simulator should reject a +non-Clifford gateset before the first shot starts. + +## Mental Model + +The simulator receives operations from the error model: + +```text +runtime batch -> error model -> simulator operation interface +``` + +For each shot, the simulator prepares state in `shot_start`, applies gates, +measures qubits, resets qubits, and cleans up in `shot_end`. + +Measurements return values immediately to the caller. In the normal pipeline, +the caller is the error model, which maps those values back to runtime result +IDs. + +## Gateset Role + +The simulator is the final gateset authority. It receives the gateset the error +model may emit and must either accept it unchanged or return an error. + +A simulator usually returns the input gateset unchanged after validation. It +should not return a different gateset unless it is also acting as a lowering +layer, which is unusual and should be documented. + +## Tutorials + +- [Writing a simulator in Rust](rust.md) +- [Writing a simulator in C](c.md) + diff --git a/docs/extensibility/simulator/c.md b/docs/extensibility/simulator/c.md new file mode 100644 index 00000000..d4548827 --- /dev/null +++ b/docs/extensibility/simulator/c.md @@ -0,0 +1,204 @@ +# Writing a Simulator Plugin in C + +C simulators implement `SeleneSimulatorPluginDescriptorV1` from +`selene/simulator.h`. Use `selene/gatewire.h` to negotiate and decode gates. + +## 1. Define Instance State + +```c +#include +#include +#include + +typedef struct { + uint64_t n_qubits; + uint64_t measurements; + /* Backend state goes here. */ +} MySimulator; +``` + +Allocate state in `init`: + +```c +static SeleneErrno my_simulator_init(SeleneSimulatorInstance *out, + uint64_t n_qubits, + uint32_t argc, + const char *const *argv) { + (void)argc; + (void)argv; + + MySimulator *sim = calloc(1, sizeof(MySimulator)); + if (sim == NULL) { + return 1; + } + sim->n_qubits = n_qubits; + *out = sim; + return 0; +} + +static SeleneErrno my_simulator_exit(SeleneSimulatorInstance handle) { + free((MySimulator *)handle); + return 0; +} +``` + +## 2. Negotiate Supported Gates + +The simulator receives the final gateset. Validate it and return it unchanged: + +```c +static SeleneErrno my_simulator_negotiate_gateset(SeleneSimulatorInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written) { + (void)handle; + + GwGateSet *incoming = NULL; + if (gw_gateset_deserialize(input, input_len, &incoming) != GW_STATUS_OK) { + return 1; + } + + GwGateSet *supported = NULL; + gw_gateset_new(&supported); + gw_gateset_add_builtin_rz(supported); + gw_gateset_add_builtin_phased_x(supported); + gw_gateset_add_builtin_zz_phase(supported); + gw_gateset_add_builtin_phased_xx(supported); + + size_t len = 0; + gw_gateset_len(incoming, &len); + for (size_t i = 0; i < len; i++) { + GwGateDeclInfo decl; + uint8_t contains = 0; + gw_gateset_decl_at(incoming, i, &decl); + gw_gateset_contains(supported, decl.semantic_id, &contains); + if (!contains) { + gw_gateset_free(incoming); + gw_gateset_free(supported); + return 1; + } + } + + size_t required = 0; + gw_gateset_serialized_len(incoming, &required); + if (output == NULL || output_len == 0) { + *written = required; + gw_gateset_free(incoming); + gw_gateset_free(supported); + return 0; + } + + GwStatus status = gw_gateset_serialize(incoming, output, output_len, written); + gw_gateset_free(incoming); + gw_gateset_free(supported); + return status == GW_STATUS_OK ? 0 : 1; +} +``` + +For a restricted simulator, build a smaller supported set and reject anything +outside it. + +## 3. Decode and Apply Gates + +`gate_fn` receives serialized gate bytes: + +```c +static SeleneErrno my_simulator_gate(SeleneSimulatorInstance handle, + const uint8_t *data, + size_t len) { + MySimulator *sim = (MySimulator *)handle; + GwDecodedGate *gate = NULL; + if (gw_gate_deserialize(data, len, &gate) != GW_STATUS_OK) { + return 1; + } + + GwSemanticId id; + gw_decoded_gate_semantic_id(gate, &id); + + if (gw_semantic_id_eq(id, gw_builtin_rz_semantic_id())) { + GwGateValue q0; + GwGateValue theta; + gw_decoded_gate_value_at(gate, 0, &q0); + gw_decoded_gate_value_at(gate, 1, &theta); + /* backend_apply_rz(sim, q0.data.qubit, theta.data.f64_value); */ + } else if (gw_semantic_id_eq(id, gw_builtin_phased_x_semantic_id())) { + /* Decode q0, theta, phi and apply. */ + } else { + gw_decoded_gate_free(gate); + return 1; + } + + (void)sim; + gw_decoded_gate_free(gate); + return 0; +} +``` + +Validate operand kinds before using them in production code. + +## 4. Measure, Reset, and Postselect + +Simulator measurement returns the value directly as the function return code: + +```c +static SeleneErrno my_simulator_measure(SeleneSimulatorInstance handle, + uint64_t qubit) { + MySimulator *sim = (MySimulator *)handle; + sim->measurements++; + /* Return 0 for false, 1 for true, or another nonzero value for error. */ + return backend_measure(sim, qubit) ? 1 : 0; +} +``` + +Reset should apply a physical reset in the backend. Postselection is optional: +return a nonzero error if unsupported. + +## 5. Metrics and Lifecycle + +`shot_start` should initialize the quantum state for a shot and seed any RNG. +`shot_end` should validate and clean up per-shot state. `get_metrics_fn` is +called with increasing `nth_metric` until it returns nonzero. + +```c +static SeleneErrno my_simulator_get_metrics(SeleneSimulatorInstance handle, + uint8_t nth_metric, + char *tag, + uint8_t *datatype, + uint64_t *value) { + MySimulator *sim = (MySimulator *)handle; + if (nth_metric != 0) { + return 1; + } + strcpy(tag, "measurements"); + *datatype = 2; /* u64 */ + *value = sim->measurements; + return 0; +} +``` + +## 6. Export the Descriptor + +```c +const SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1 = { + .struct_size = sizeof(SeleneSimulatorPluginDescriptorV1), + .api_version = SELENE_SIMULATOR_CURRENT_API_VERSION, + .get_name_fn = my_simulator_get_name, + .init_fn = my_simulator_init, + .exit_fn = my_simulator_exit, + .shot_start_fn = my_simulator_shot_start, + .shot_end_fn = my_simulator_shot_end, + .measure_fn = my_simulator_measure, + .postselect_fn = my_simulator_postselect, + .reset_fn = my_simulator_reset, + .get_metrics_fn = my_simulator_get_metrics, + .dump_state_fn = my_simulator_dump_state, + .gate_fn = my_simulator_gate, + .negotiate_gateset_fn = my_simulator_negotiate_gateset, +}; +``` + +Use `NULL` only for callbacks documented as optional. Required callbacks are +validated when Selene loads the plugin. + diff --git a/docs/extensibility/simulator/rust.md b/docs/extensibility/simulator/rust.md new file mode 100644 index 00000000..cee3c5bb --- /dev/null +++ b/docs/extensibility/simulator/rust.md @@ -0,0 +1,191 @@ +# Writing a Simulator Plugin in Rust + +Rust simulators implement `SimulatorInterface` and +`SimulatorInterfaceFactory`, then export the plugin with +`selene_core::export_simulator_plugin!`. + +The simulator owns quantum state. The examples here use placeholder state +methods such as `apply_rz`; replace them with your backend's real operations. + +## 1. Create a `cdylib` Crate + +```toml +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +selene-core = { path = "../../path/to/selene-core/rust" } +``` + +## 2. Define Supported Gates + +If your simulator supports the standard native gates, define a gateset enum: + +```rust +use selene_core::define_gateset; +use selene_core::gatewire::builtin::{PhasedX, PhasedXX, RZ, ZZPhase}; + +define_gateset! { + enum SimulatorGates { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), + PhasedXX(PhasedXX), + } +} +``` + +A restricted simulator can define a smaller set. A Clifford-only simulator, for +example, should reject non-Clifford declarations in `negotiate_gateset`. + +## 3. Store Simulator State + +```rust +use anyhow::{Result, bail}; +use selene_core::error_model::BatchResult; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec}; +use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::simulator::SimulatorInterface; +use selene_core::utils::MetricValue; + +struct MySimulator { + n_qubits: u64, + measurements: u64, + // backend state goes here +} +``` + +Initialize state in the factory and reset per-shot state in `shot_start`. + +## 4. Negotiate Gates + +The simulator validates the final gateset: + +```rust +impl MySimulator { + fn supported_gateset() -> DynamicGateSet { + DynamicGateSet::from_declarations(SimulatorGates::declarations()) + .expect("simulator gate declarations are unique") + } +} + +impl SimulatorInterface for MySimulator { + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = Self::supported_gateset(); + for decl in gateset.declarations() { + if !supported.contains(decl.semantic_id) { + bail!("simulator does not support gate {}", decl.name); + } + } + Ok(gateset.clone()) + } + + /* other methods shown below */ +} +``` + +Rejecting here gives users a configuration error before any shot is run. + +## 5. Execute Operations + +`handle_operations` receives a batch and returns measurement results: + +```rust +fn handle_operations(&mut self, operations: BatchOperation) -> Result { + let mut results = BatchResult::default(); + + for op in operations { + match op { + Operation::Gate { gate } => { + let Some(gate) = SimulatorGates::try_from_instance(&gate)? else { + bail!("simulator received an unnegotiated gate"); + }; + self.apply_gate(gate)?; + } + Operation::Measure { qubit_id, result_id } => { + let value = self.measure_qubit(qubit_id)?; + results.set_bool_result(result_id, value); + self.measurements += 1; + } + Operation::MeasureLeaked { qubit_id, result_id } => { + let value = self.measure_leaked_qubit(qubit_id)?; + results.set_u64_result(result_id, value); + self.measurements += 1; + } + Operation::Reset { qubit_id } => { + self.reset_qubit(qubit_id)?; + } + Operation::Custom { custom_tag, .. } => { + bail!("simulator does not support custom operation {custom_tag}"); + } + } + } + + Ok(results) +} +``` + +Decode gates by semantic ID, not by display string: + +```rust +impl MySimulator { + fn apply_gate(&mut self, gate: SimulatorGates) -> Result<()> { + match gate { + SimulatorGates::RZ(g) => self.apply_rz(g.q0.0.into(), g.theta.0), + SimulatorGates::PhasedX(g) => self.apply_phased_x(g.q0.0.into(), g.theta.0, g.phi.0), + SimulatorGates::ZZPhase(g) => self.apply_zz_phase(g.q0.0.into(), g.q1.0.into(), g.theta.0), + SimulatorGates::PhasedXX(g) => { + self.apply_phased_xx(g.q0.0.into(), g.q1.0.into(), g.theta.0, g.phi.0) + } + } + } +} +``` + +## 6. Implement Lifecycle and Optional Features + +`shot_start` should reset state for a shot and seed any simulator RNG. `shot_end` +should validate and release per-shot resources. `exit` should make the instance +unusable. + +Postselection and state dumping are optional. If unsupported, return a clear +error. Metrics are dynamic: + +```rust +fn get_metric(&mut self, nth_metric: u8) -> Result> { + match nth_metric { + 0 => Ok(Some(("measurements".into(), MetricValue::U64(self.measurements)))), + _ => Ok(None), + } +} +``` + +## 7. Export the Plugin + +```rust +use std::sync::Arc; +use selene_core::simulator::SimulatorInterfaceFactory; + +struct MySimulatorFactory; + +impl SimulatorInterfaceFactory for MySimulatorFactory { + type Interface = MySimulator; + + fn init( + self: Arc, + n_qubits: u64, + _args: &[impl AsRef], + ) -> Result> { + Ok(Box::new(MySimulator { + n_qubits, + measurements: 0, + })) + } +} + +selene_core::export_simulator_plugin!(MySimulatorFactory); +``` + +After building the shared library, configure Selene to load it as a simulator. + diff --git a/docs/migration/0.3.md b/docs/migration/0.3.md new file mode 100644 index 00000000..6ecfb3a2 --- /dev/null +++ b/docs/migration/0.3.md @@ -0,0 +1,559 @@ +# Migrating To Selene 0.3 + +This guide is for projects moving from the `main`/0.2-series API to the +0.3-series API. + +Selene 0.3 is intentionally a breaking release. The Python build-and-run +workflow is mostly stable, but the extension model has changed substantially: +gates are now explicit data carried through a negotiated gateset, and plugin +ABIs are descriptor-based. + +## Who Needs To Change Code? + +You probably need no or minimal changes if you only: + +- call `selene_sim.build(...)`; +- run with bundled plugins such as `Quest`, `Stim`, `SimpleRuntime`, + `SoftRZRuntime`, or `DepolarizingErrorModel`; +- inspect final shot results. + +You need changes if you: + +- use `InteractiveSimulator` or `InteractiveFullStack` gate methods directly; +- inspect event-hook instruction dictionaries or metric names; +- maintain a simulator, runtime, error model, utility, or QIS interface plugin; +- call Selene's C ABI directly; +- assert snapshots containing `Rxy`, `Rz`, `Rzz`, or old metric keys. + +## Upgrade Checklist + +1. Upgrade `selene-sim` and `selene-core` together to the 0.3 series. +2. Rebuild all native plugins. 0.2 plugin binaries are not ABI-compatible with + 0.3. +3. Replace old gate names: + + | 0.2 name | 0.3 name | + | --- | --- | + | `rz` / `Rz` | `RZ` | + | `rxy` / `Rxy` | `PhasedX` | + | `rzz` / `Rzz` | `ZZPhase` | + | `rpp` / `Rpp` | `PhasedXX` | + +4. Replace direct interactive gate calls with gateset-bound gates and + `sim.gate(...)`. +5. Update snapshots and metric assertions from typed gate opcodes to generic + gate records. +6. For plugin code, implement gateset negotiation and generic gate handling. + +## Python Build And Run + +The normal build/run shape is still the same: + +```python +from selene_sim.build import build +from selene_sim import Quest + +instance = build(program) +shots = instance.run_shots( + simulator=Quest(), + n_qubits=10, + n_shots=100, +) +``` + +Selene still owns the workflow: + +1. the user program is passed to `selene_sim.build(...)`; +2. the build planner identifies the input kind; +3. build steps compile and link a Selene executable; +4. `instance.run(...)` or `instance.run_shots(...)` runs that executable with + runtime, error-model, and simulator plugins. + +Do not migrate examples by parsing LLVM IR directly. If a custom frontend needs +new behavior, register a build kind and build step with Selene's build planner. + +### Selecting Helios Or Sol + +Helios remains the default interface. 0.3 also includes a Sol interface. + +Use either an explicit interface: + +```python +from selene_sim.build import build +from selene_helios_qis_plugin import HeliosInterface +from selene_sol_qis_plugin import SolInterface + +helios_instance = build(program, interface=HeliosInterface()) +sol_instance = build(program, interface=SolInterface()) +``` + +or use the platform configuration where supported: + +```python +helios_instance = build(program, platform="helios") +sol_instance = build(program, platform="sol") +``` + +Helios registers and emits `RZ`, `PhasedX`, and `ZZPhase`. Sol registers and +emits `RZ`, `PhasedX`, and `PhasedXX`. + +## Interactive Python + +The old interactive API exposed direct methods for Selene's historical builtin +gates: + +```python +from math import pi +from selene_sim import Quest +from selene_sim.interactive import InteractiveSimulator + +sim = InteractiveSimulator(simulator=Quest(), n_qubits=2) +sim.rxy(0, pi, 0.0) +sim.rz(0, pi / 2) +sim.rzz(0, 1, pi / 2) +``` + +In 0.3, construct or import a gateset, pass it to the interactive simulator, +then instantiate gates from that gateset: + +```python +from math import pi +from selene_core import Gateset, PhasedX, RZ, ZZPhase +from selene_sim import Quest +from selene_sim.interactive import InteractiveSimulator + +gates = Gateset(RZ, PhasedX, ZZPhase) +sim = InteractiveSimulator( + simulator=Quest(), + n_qubits=2, + gateset=gates, +) + +sim.gate(gates.PhasedX(q0=0, theta=pi, phi=0.0)) +sim.gate(gates.RZ(q0=0, theta=pi / 2)) +sim.gate(gates.ZZPhase(q0=0, q1=1, theta=pi / 2)) +``` + +Positional operands also work: + +```python +sim.gate(gates.PhasedX(0, pi, 0.0)) +``` + +Use the same model with `InteractiveFullStack`: + +```python +from selene_core import Gateset, PhasedX, RZ, ZZPhase +from selene_sim import Quest, SoftRZRuntime +from selene_sim.interactive import InteractiveFullStack + +gates = Gateset(RZ, PhasedX, ZZPhase) +stack = InteractiveFullStack( + simulator=Quest(), + runtime=SoftRZRuntime(), + n_qubits=4, + gateset=gates, +) + +emitted = stack.emitted_gateset +``` + +`emitted_gateset` may differ from the input gateset. For example, +`SoftRZRuntime` accepts `RZ` but may not emit `RZ` downstream. + +## Gatewire In Python + +`selene_core` now exposes gatewire helpers: + +```python +from selene_core import ( + BOOL, + F64, + QUBIT, + GateDefinition, + Gateset, + OperandDefinition, + PhasedX, + RZ, + ZZPhase, + builtin_gateset, +) +``` + +Use builtin definitions for Selene's builtin gates: + +```python +gates = builtin_gateset() +gate = gates.PhasedX(q0=0, theta=1.57079632679, phi=0.0) +``` + +Define custom gates with semantic IDs: + +```python +from selene_core import GateDefinition, Gateset, OperandDefinition, QUBIT, F64 + +H = GateDefinition( + "example.clifford_t.H.v1", + "H", + [OperandDefinition("q0", QUBIT)], +) +T = GateDefinition( + "example.clifford_t.T.v1", + "T", + [OperandDefinition("q0", QUBIT)], +) +CNOT = GateDefinition( + "example.clifford_t.CNOT.v1", + "CNOT", + [OperandDefinition("control", QUBIT), OperandDefinition("target", QUBIT)], +) + +clifford_t = Gateset(H, T, CNOT) +gate = clifford_t.CNOT(control=0, target=1) +``` + +The semantic ID is the compatibility key. The display name is for humans, +metrics, traces, and snapshots. + +## Events, Traces, And Metrics + +Instruction/event payloads now use generic gate records rather than typed gate +opcodes. + +Old event shape: + +```json +{"op": "Rxy", "qubit": 0, "theta": 3.141592653589793, "phi": 0.0} +``` + +New event shape: + +```json +{ + "op": "Gate", + "gate": "PhasedX", + "qubits": [0], + "params": [3.141592653589793, 0.0] +} +``` + +Trace records likewise use the new display names: + +```diff +- "gate_name": "Rxy" ++ "gate_name": "PhasedX" +``` + +Metrics are keyed by gate display name: + +```diff +- rxy_count +- rz_count +- rzz_count ++ gate:PhasedX:count ++ gate:RZ:count ++ gate:ZZPhase:count +``` + +Post-runtime metrics follow the same pattern: + +```diff +- rxy_batch_count +- rxy_individual_count ++ gate:PhasedX:batch_count ++ gate:PhasedX:individual_count +``` + +When reviewing snapshot changes, name and representation churn like this is +expected. Changes in operation ordering, measurement behavior, or stochastic +error placement should be treated as behavior changes and checked carefully. + +## Plugin Authors + +All native plugin authors need to update for 0.3. The old exported function +sets have been replaced by plugin descriptors, and gate operations now travel +through gatewire. + +### The New Gateset Handshake + +Every stack now negotiates gates in order: + +1. the QIS interface registers the gateset it will emit after Selene has loaded + configuration; +2. the runtime accepts or rejects that gateset and returns the gateset it may + emit; +3. the error model accepts or rejects the runtime's output gateset and returns + the gateset it may emit; +4. the simulator accepts or rejects the final gateset. + +Returned gatesets may be different from input gatesets. If an error model +injects `RZ` or `PhasedX`, its negotiated output gateset must include those +gate declarations even if the input did not. + +### Rust Runtime Plugins + +0.2 runtimes implemented per-gate methods: + +```rust +fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()>; +fn rzz_gate(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()>; +fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()>; +``` + +0.3 runtimes implement generic gates: + +```rust +use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; + +fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + Ok(gateset.clone()) +} + +fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + // Decode by semantic ID, or forward/store the generic gate. + Ok(()) +} +``` + +Runtime output batches now emit gates through the generic `Operation::Gate` +variant rather than `RXYGate`, `RZGate`, or `RZZGate`. + +### Rust Error Model Plugins + +0.2 error models owned and loaded the simulator. Their factory `init` received +the simulator plugin path and simulator arguments, and `shot_start` received a +simulator seed. + +In 0.3, Selene owns the simulator and passes a simulator interface to the error +model when operations are handled: + +```rust +use selene_core::gatewire::DynamicGateSet; +use selene_core::simulator::SimulatorInterface; + +fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + Ok(gateset.clone()) +} + +fn handle_operations( + &mut self, + operations: BatchOperation, + simulator: &mut dyn SimulatorInterface, +) -> Result { + simulator.handle_operations(operations) +} +``` + +The factory no longer receives simulator plugin information: + +```rust +fn init( + self: Arc, + n_qubits: u64, + error_model_args: &[impl AsRef], +) -> Result>; +``` + +When a generic error model depends on gate arity, use the gatewire helpers +rather than hardcoded gate names: + +```rust +let qubits: Vec = gate.qubit_operands().map(u64::from).collect(); +match qubits.as_slice() { + [q0] => { /* one-qubit gate */ } + [q0, q1] => { /* two-qubit gate */ } + _ => {} +} +``` + +### Rust Simulator Plugins + +0.2 simulators implemented typed operations: + +```rust +fn rxy(&mut self, qubit: u64, theta: f64, phi: f64) -> Result<()>; +fn rz(&mut self, qubit: u64, theta: f64) -> Result<()>; +fn rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()>; +``` + +0.3 simulators negotiate a gateset and handle batches: + +```rust +use selene_core::gatewire::DynamicGateSet; + +fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + // Reject unsupported gates here. + Ok(gateset.clone()) +} + +fn handle_operations(&mut self, operations: BatchOperation) -> Result { + for operation in operations { + match operation { + Operation::Gate { gate } => { + // Decode by semantic ID and operands. + } + Operation::Measure { qubit_id, result_id } => { + // Measure and return the result in BatchResult. + } + _ => {} + } + } + Ok(BatchResult::default()) +} +``` + +Reject unsupported gates during `negotiate_gateset` wherever possible. For +example, a Clifford-only simulator should reject non-Clifford gates there. + +## C ABI Plugins + +0.3 uses descriptor structs rather than requiring every plugin function to be +exported as a top-level symbol. + +Simulator descriptors now include: + +- `gate_fn(handle, const uint8_t *data, size_t len)`; +- `negotiate_gateset_fn(handle, input, input_len, output, output_len, written)`; +- measurement, postselect, reset, metrics, state dump, shot lifecycle, and + init/exit callbacks. + +Runtime descriptors now include: + +- `gate_fn(handle, const uint8_t *data, size_t len)`; +- `negotiate_gateset_fn(...)`; +- generic output callback `gate_fn` in `SeleneRuntimeGetOperationInterface`. + +Error-model descriptors now include: + +- `handle_operations_fn(handle, RuntimeExtractOperationHandle, SimulatorHandle, + ErrorModelSetResultHandle)`; +- `negotiate_gateset_fn(...)`; +- init without simulator plugin path/arguments. + +Use `selene/gatewire.h` for gatesets and serialized gates. In C, text fields +are `const char *` plus a length. Serialized gates and gatesets are byte +buffers: + +```c +const uint8_t *data; +size_t len; +``` + +Do not manually reproduce gatewire internals in plugins. Use the gatewire C +helpers to: + +- create a gateset; +- add builtin or custom gate declarations; +- serialize and deserialize gatesets; +- deserialize gates; +- inspect semantic IDs and qubit operands; +- validate gates against a gateset. + +## QIS Interface Authors + +QIS interfaces must register the gateset they emit after `selene_load_config`. +After registration, emit generic gates through the Selene/gatewire boundary. + +In 0.2, compiled frontends could call typed Selene entrypoints such as: + +```c +selene_rxy(selene_instance, q0, theta, phi); +selene_rz(selene_instance, q0, theta); +selene_rzz(selene_instance, q0, q1, theta); +``` + +In 0.3, register the interface gateset once after configuration: + +```c +struct selene_void_result_t result = + selene_load_config(&selene_instance, configuration_file); + +/* Build and serialize a GwGateSet with selene/gatewire.h. */ +result = selene_register_gateset( + selene_instance, + gateset_bytes, + gateset_bytes_len, + NULL, + 0, + &accepted_len +); + +uint8_t *accepted = malloc(accepted_len); +result = selene_register_gateset( + selene_instance, + gateset_bytes, + gateset_bytes_len, + accepted, + accepted_len, + &accepted_len +); +``` + +Then serialize each gate instance and emit it through `selene_gate`: + +```c +/* Build and serialize a GwGateInstanceView with selene/gatewire.h. */ +selene_gate(selene_instance, gate_bytes, gate_bytes_len); +``` + +For bundled interfaces: + +- Helios emits `RZ`, `PhasedX`, and `ZZPhase`; +- Sol emits `RZ`, `PhasedX`, and `PhasedXX`. + +For third-party interfaces, define the gates your frontend actually emits. Do +not pretend to be the builtin gateset unless you are emitting those semantic +IDs and operand layouts. + +Utility plugins that only perform classical work or use +`selene_custom_runtime_call` may not need changes. Utilities or frontend shims +that emit quantum gates must use the same gateset registration and `selene_gate` +path as QIS interfaces. + +## Custom Gateset Example + +The repository includes a complete 0.3-style example under +`examples/clifford_t_stack`. It demonstrates: + +- a custom Clifford+T gateset; +- a custom QIS interface and build step; +- Rust runtime and error-model plugins; +- a C++ simulator plugin; +- Python bindings; +- a `pyproject.toml` and `hatch_build.py`; +- a `justfile` for setup and tests. + +Use it as the reference for third-party stacks that need to compile and run +through normal Selene workflows. + +## Common Migration Failures + +### "Plugin did not expose descriptor symbol or accessor" + +The plugin is still exporting the 0.2 ABI. Rebuild it against 0.3 and export +the descriptor expected by the relevant plugin kind. + +### "The chosen simulator does not support gate ..." + +The gateset handshake reached the simulator with a gate it does not support. +Either change the upstream interface/runtime/error model to emit a supported +gate, or teach the simulator to negotiate and implement that gate. + +### Interactive `.rxy(...)` or `.rz(...)` is missing + +Use a gateset and `sim.gate(...)` instead. + +### Snapshot diffs show `Rxy` changing to `PhasedX` + +This is expected nomenclature and representation churn. Update the snapshot if +the operation order and measurement behavior are otherwise unchanged. + +### Metrics such as `rxy_count` are missing + +Use the generic gate metric keys, for example `gate:PhasedX:count`. + +## Release-Branch Note + +At the time this guide was written, `main` represented the 0.2-series release +line and `0.3-series` contained the breaking gatewire/plugin work. This guide +is written for users moving from 0.2.x to the 0.3-series release. diff --git a/docs/migration/README.md b/docs/migration/README.md new file mode 100644 index 00000000..3fa77988 --- /dev/null +++ b/docs/migration/README.md @@ -0,0 +1,3 @@ +# Migration Guides + +- [Migrating To Selene 0.3](0.3.md) diff --git a/examples/clifford_t_stack/Cargo.lock b/examples/clifford_t_stack/Cargo.lock new file mode 100644 index 00000000..cd3eb97e --- /dev/null +++ b/examples/clifford_t_stack/Cargo.lock @@ -0,0 +1,391 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_pcg" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "selene-core" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "delegate", + "derive_more", + "indexmap", + "libloading", + "smallvec", + "static_assertions", + "thiserror", +] + +[[package]] +name = "selene-example-clifford-t-stack" +version = "0.1.0" +dependencies = [ + "anyhow", + "rand", + "rand_pcg", + "selene-core", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/examples/clifford_t_stack/Cargo.toml b/examples/clifford_t_stack/Cargo.toml new file mode 100644 index 00000000..3b944f06 --- /dev/null +++ b/examples/clifford_t_stack/Cargo.toml @@ -0,0 +1,18 @@ +[workspace] + +[package] +name = "selene-example-clifford-t-stack" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +name = "selene_example_clifford_t_stack" +path = "src/lib.rs" +crate-type = ["rlib", "cdylib"] + +[dependencies] +anyhow = "1.0" +rand = "0.9" +rand_pcg = "0.9" +selene-core = { path = "../../selene-core" } diff --git a/examples/clifford_t_stack/README.md b/examples/clifford_t_stack/README.md new file mode 100644 index 00000000..495c9773 --- /dev/null +++ b/examples/clifford_t_stack/README.md @@ -0,0 +1,241 @@ +# Clifford+T Stack Example + +This is a complete, buildable Selene example that uses a custom Clifford+T +gateset instead of Selene's builtin `RZ`, `PhasedX`, `ZZPhase`, or `PhasedXX` +gates. + +The example follows the normal Selene workflow: + +1. A user program is provided to `selene_sim.build`. +2. A custom `QuantumInterface` registers build-planner kinds and steps. +3. The build step invokes Zig to compile the user program and a C QIS shim into + a Selene object file. +4. Selene's existing build steps link that object into a normal executable. +5. `instance.run_shots(...)` runs the executable with the custom runtime, error + model, and simulator plugins. +6. The C QIS shim registers the Clifford+T gateset with Selene after + `selene_load_config`, then emits generic `selene_gate(...)` calls. + +The simulator is a C++ plugin backed by Qrack's `QStabilizerHybrid`. It starts +from Qrack's stabilizer machinery, enables exact near-Clifford/T-injection +handling, and reports an error if Qrack falls back to a full generic engine. +The runtime and error model remain Rust plugins. + +## Step 1: Build and Test the Rust Plugins + +Run from this directory: + +```bash +direnv exec ../.. cargo test --manifest-path "$PWD/Cargo.toml" +``` + +The Rust tests compile the example runtime/error-model library and exercise the +in-process interface/runtime/error-model path with a small Rust test simulator. +They check that: + +- The runtime, error model, and simulator negotiate the Clifford+T gateset. +- The Rust `CliffordTInterface` emits gatewire gate instances. +- The error model forwards gates and injects an `X` after each one-qubit gate + according to its configured probability. +- The final gate stream is still a valid Clifford+T stream. + +## Step 2: Run the Real Selene Build Workflow + +The local `justfile` creates a virtual environment, installs Selene, installs +this example, and runs the Python tests: + +```bash +just setup +just test +``` + +The setup recipe uses: + +```bash +python -m venv venv +venv/bin/pip install ../../ +venv/bin/pip install ../../selene-core +venv/bin/pip install . +venv/bin/pip install pytest +``` + +The `pip install .` step runs the example's Hatch build hook, which builds the +Rust runtime/error-model library and the C++ Qrack simulator plugin. + +If you want to run those pieces manually, the equivalent native build/test flow +is: + +```bash +direnv exec ../.. cargo build --manifest-path "$PWD/Cargo.toml" +direnv exec ../.. cmake -S "$PWD/cpp" -B "$PWD/target/qrack-simulator-release" -DCMAKE_BUILD_TYPE=Release +direnv exec ../.. cmake --build "$PWD/target/qrack-simulator-release" --target selene_example_clifford_t_qrack --parallel +PYTHONPATH="$PWD/python" direnv exec ../.. "$PWD/venv/bin/python" -m pytest -s "$PWD/python/tests" +``` + +The final Python test does the real Selene thing: + +```python +from selene_sim.build import build +from selene_sim.event_hooks import MetricStore +from selene_example_clifford_t_stack import ( + CliffordTErrorModel, + CliffordTInterface, + CliffordTQrackSimulator, + CliffordTRuntime, + example_program_path, +) + +metrics = MetricStore() +instance = build( + example_program_path(), + interface=CliffordTInterface(), +) + +shots = instance.run_shots( + simulator=CliffordTQrackSimulator(), + runtime=CliffordTRuntime(), + error_model=CliffordTErrorModel(), + event_hook=metrics, + n_qubits=2, + n_shots=1, +) + +shot = dict(next(iter(shots))) +assert shot["q0"] == shot["q1"] +assert metrics.shots[0]["error_model"]["injected_x"] == 2 +``` + +## Step 3: Read the User Program + +The user program is ordinary C in `programs/bell.ct.c`: + +```c +#include + +void ct_program(void) { + uint64_t q0 = ct_qalloc(); + uint64_t q1 = ct_qalloc(); + + ct_h(q0); + ct_t(q0); + ct_cnot(q0, q1); + + bool m0 = ct_measure(q0); + bool m1 = ct_measure(q1); + + ct_record_bool("q0", m0); + ct_record_bool("q1", m1); +} +``` + +The program only sees the small QIS header in `c/include/clifford_t_qis.h`. +It does not know about gatewire serialization, runtime batching, error-model +injection, or simulator details. + +## Step 4: Follow the Build Extension + +`python/selene_example_clifford_t_stack/build.py` defines: + +- `CliffordTCSourceKind`, which identifies `.ct.c` user programs. +- `CliffordTCToSeleneObjectStep`, which compiles the user program and the C + shim with `invoke_zig`. +- `CliffordTInterface`, which registers the kind and step with Selene's + `BuildPlanner`. + +The custom build step produces a normal `SeleneObjectFileKind`. From there, the +existing Selene build steps link the executable in the usual way. + +## Step 5: Follow Gateset Registration + +The C shim in `c/src/clifford_t_interface.c` owns the non-interactive gateset +registration boundary: + +```text +main(...) + -> selene_load_config(...) + -> register Clifford+T gateset with selene_register_gateset(...) + -> for each shot: + selene_on_shot_start(...) + ct_program() + selene_on_shot_end(...) + -> selene_exit(...) +``` + +This mirrors the Helios and Sol interfaces: the compiled program registers the +frontend gateset after Selene has been configured, and Selene performs the +runtime -> error model -> simulator handshake before accepting gate operations. + +## Step 6: Inspect the Gates + +The Rust gates are defined in `src/gates.rs` with `define_gate!`: + +```rust +define_gate! { + pub struct T [ + id = "example.clifford_t.T.v1", + display = "T", + version = 1, + ] { q0: Qubit } +} +``` + +The C shim declares the same semantic IDs through `selene/gatewire.h` before it +registers the gateset. The semantic ID is the compatibility key. The display +name is only for humans and metrics. + +## Step 7: Play Interactively + +The same Python package also exposes interactive helpers for quick experiments: + +```python +from selene_sim.interactive import InteractiveSimulator +from selene_example_clifford_t_stack import ( + CliffordTQrackSimulator, + clifford_t_gateset, + h, + t, + cnot, +) + +gates = clifford_t_gateset() +sim = InteractiveSimulator( + simulator=CliffordTQrackSimulator(), + n_qubits=2, + gateset=gates, +) + +sim.gate(h(0)) +sim.gate(t(0)) +sim.gate(cnot(0, 1)) +m0 = sim.measure(0) +m1 = sim.measure(1) +assert m1 == m0 +``` + +This path is useful for exploration, but the compiled-program example above is +the primary Selene workflow. + +## File Map + +- `programs/bell.ct.c` is the user program passed to `selene_sim.build`. +- `c/include/clifford_t_qis.h` is the user-facing C QIS header. +- `c/src/clifford_t_interface.c` is the C shim that registers the gateset and + emits `selene_gate(...)` calls. +- `python/selene_example_clifford_t_stack/build.py` extends Selene's build + planner. +- `python/selene_example_clifford_t_stack/gates.py` exposes Python gateset and + gate constructors for interactive use. +- `python/selene_example_clifford_t_stack/plugins.py` exposes Python plugin + classes for the native Rust and C++ plugin libraries. +- `cpp/src/qrack_simulator.cpp` is the Qrack-backed simulator plugin. +- `src/gates.rs` defines the Clifford+T gatewire gateset. +- `src/interface.rs` is the Rust in-process QIS-style emitter used by tests. +- `src/runtime.rs` is the runtime plugin. +- `src/error_model.rs` is the error model plugin. +- `src/simulator.rs` is a small in-process simulator used by Rust tests. +- `src/tests.rs` exercises the Rust pieces without going through Python. + +The important lesson is that every layer negotiates and decodes by semantic ID. +The build system is also extensible: a custom interface can teach Selene how to +compile a new user-program format without bypassing the normal executable and +`run_shots` flow. diff --git a/examples/clifford_t_stack/c/include/clifford_t_qis.h b/examples/clifford_t_stack/c/include/clifford_t_qis.h new file mode 100644 index 00000000..5e373d9b --- /dev/null +++ b/examples/clifford_t_stack/c/include/clifford_t_qis.h @@ -0,0 +1,24 @@ +#ifndef CLIFFORD_T_QIS_H +#define CLIFFORD_T_QIS_H + +#include +#include + +uint64_t ct_qalloc(void); +void ct_qfree(uint64_t q); +void ct_reset(uint64_t q); + +void ct_h(uint64_t q); +void ct_s(uint64_t q); +void ct_sdg(uint64_t q); +void ct_t(uint64_t q); +void ct_tdg(uint64_t q); +void ct_x(uint64_t q); +void ct_cnot(uint64_t control, uint64_t target); + +bool ct_measure(uint64_t q); +void ct_record_bool(char const* tag, bool value); + +void ct_program(void); + +#endif diff --git a/examples/clifford_t_stack/c/src/clifford_t_interface.c b/examples/clifford_t_stack/c/src/clifford_t_interface.c new file mode 100644 index 00000000..d81bd348 --- /dev/null +++ b/examples/clifford_t_stack/c/src/clifford_t_interface.c @@ -0,0 +1,258 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +static SeleneInstance* selene_instance = NULL; + +static GwSemanticId h_id; +static GwSemanticId s_id; +static GwSemanticId sdg_id; +static GwSemanticId t_id; +static GwSemanticId tdg_id; +static GwSemanticId x_id; +static GwSemanticId cnot_id; +static bool gate_ids_initialized = false; + +static void fail(char const* message) { + fprintf(stderr, "clifford_t_interface: %s\n", message); + abort(); +} + +static void check_gw(GwStatus status) { + if (status != GW_STATUS_OK) { + fprintf(stderr, "clifford_t_interface: gatewire error: %s\n", gw_status_message(status)); + abort(); + } +} + +static void check_void(struct selene_void_result_t result, char const* context) { + if (result.error_code != 0) { + fprintf(stderr, "clifford_t_interface: %s failed with error code %" PRIu32 "\n", context, result.error_code); + abort(); + } +} + +static uint64_t unwrap_u64(struct selene_u64_result_t result, char const* context) { + if (result.error_code != 0) { + fprintf(stderr, "clifford_t_interface: %s failed with error code %" PRIu32 "\n", context, result.error_code); + abort(); + } + return result.value; +} + +static bool unwrap_bool(struct selene_bool_result_t result, char const* context) { + if (result.error_code != 0) { + fprintf(stderr, "clifford_t_interface: %s failed with error code %" PRIu32 "\n", context, result.error_code); + abort(); + } + return result.value; +} + +static struct selene_string_t borrowed_string(char const* text) { + struct selene_string_t value = { + .data = text, + .length = strlen(text), + .owned = false, + }; + return value; +} + +static GwSemanticId semantic_id(char const* text) { + GwSemanticId id; + check_gw(gw_semantic_id_from_text(text, strlen(text), &id)); + return id; +} + +static GwOperandDeclView qubit_operand(char const* name) { + GwOperandDeclView operand = { + .abi_size = sizeof(GwOperandDeclView), + .name_ptr = name, + .name_len = strlen(name), + .kind = GW_OPERAND_KIND_QUBIT, + }; + return operand; +} + +static void add_gate(GwGateSet* set, GwSemanticId id, char const* display, GwOperandDeclView const* operands, size_t operands_len) { + GwGateDeclView decl = { + .abi_size = sizeof(GwGateDeclView), + .semantic_id = id, + .name_ptr = display, + .name_len = strlen(display), + .operands_ptr = operands, + .operands_len = operands_len, + .version = 1, + }; + check_gw(gw_gateset_add_decl(set, &decl)); +} + +static void init_gate_ids(void) { + if (gate_ids_initialized) { + return; + } + h_id = semantic_id("example.clifford_t.H.v1"); + s_id = semantic_id("example.clifford_t.S.v1"); + sdg_id = semantic_id("example.clifford_t.Sdg.v1"); + t_id = semantic_id("example.clifford_t.T.v1"); + tdg_id = semantic_id("example.clifford_t.Tdg.v1"); + x_id = semantic_id("example.clifford_t.X.v1"); + cnot_id = semantic_id("example.clifford_t.CNOT.v1"); + gate_ids_initialized = true; +} + +static void register_clifford_t_gateset(void) { + init_gate_ids(); + + GwGateSet* set = NULL; + check_gw(gw_gateset_new(&set)); + + GwOperandDeclView q0[] = {qubit_operand("q0")}; + GwOperandDeclView cnot[] = {qubit_operand("control"), qubit_operand("target")}; + add_gate(set, h_id, "H", q0, 1); + add_gate(set, s_id, "S", q0, 1); + add_gate(set, sdg_id, "Sdg", q0, 1); + add_gate(set, t_id, "T", q0, 1); + add_gate(set, tdg_id, "Tdg", q0, 1); + add_gate(set, x_id, "X", q0, 1); + add_gate(set, cnot_id, "CNOT", cnot, 2); + + size_t input_len = 0; + check_gw(gw_gateset_serialized_len(set, &input_len)); + uint8_t* input = malloc(input_len); + if (input == NULL) { + gw_gateset_free(set); + fail("failed to allocate gateset buffer"); + } + size_t written = 0; + check_gw(gw_gateset_serialize(set, input, input_len, &written)); + gw_gateset_free(set); + + size_t output_len = 0; + check_void(selene_register_gateset(selene_instance, input, written, NULL, 0, &output_len), "selene_register_gateset(size)"); + uint8_t* output = malloc(output_len); + if (output == NULL && output_len != 0) { + free(input); + fail("failed to allocate accepted gateset buffer"); + } + check_void(selene_register_gateset(selene_instance, input, written, output, output_len, &output_len), "selene_register_gateset(write)"); + free(input); + free(output); +} + +static GwGateValue qubit_value(uint64_t q) { + GwGateValue value = { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_QUBIT, + .data.qubit = (uint32_t)q, + .bytes_ptr = NULL, + .bytes_len = 0, + }; + return value; +} + +static void emit_gate(GwSemanticId id, GwGateValue const* values, size_t values_len) { + GwGateInstanceView gate = { + .abi_size = sizeof(GwGateInstanceView), + .semantic_id = id, + .values_ptr = values, + .values_len = values_len, + }; + size_t buffer_len = 0; + check_gw(gw_gate_serialized_len(&gate, &buffer_len)); + uint8_t* buffer = malloc(buffer_len); + if (buffer == NULL) { + fail("failed to allocate gate buffer"); + } + size_t written = 0; + check_gw(gw_gate_serialize(&gate, buffer, buffer_len, &written)); + check_void(selene_gate(selene_instance, buffer, written), "selene_gate"); + free(buffer); +} + +static void emit_single_qubit_gate(GwSemanticId id, uint64_t q) { + init_gate_ids(); + GwGateValue values[] = {qubit_value(q)}; + emit_gate(id, values, 1); +} + +uint64_t ct_qalloc(void) { + return unwrap_u64(selene_qalloc(selene_instance), "selene_qalloc"); +} + +void ct_qfree(uint64_t q) { + check_void(selene_qfree(selene_instance, q), "selene_qfree"); +} + +void ct_reset(uint64_t q) { + check_void(selene_qubit_reset(selene_instance, q), "selene_qubit_reset"); +} + +void ct_h(uint64_t q) { + emit_single_qubit_gate(h_id, q); +} + +void ct_s(uint64_t q) { + emit_single_qubit_gate(s_id, q); +} + +void ct_sdg(uint64_t q) { + emit_single_qubit_gate(sdg_id, q); +} + +void ct_t(uint64_t q) { + emit_single_qubit_gate(t_id, q); +} + +void ct_tdg(uint64_t q) { + emit_single_qubit_gate(tdg_id, q); +} + +void ct_x(uint64_t q) { + emit_single_qubit_gate(x_id, q); +} + +void ct_cnot(uint64_t control, uint64_t target) { + init_gate_ids(); + GwGateValue values[] = {qubit_value(control), qubit_value(target)}; + emit_gate(cnot_id, values, 2); +} + +bool ct_measure(uint64_t q) { + return unwrap_bool(selene_qubit_measure(selene_instance, q), "selene_qubit_measure"); +} + +void ct_record_bool(char const* tag, bool value) { + char buffer[256]; + int written = snprintf(buffer, sizeof(buffer), "USER:BOOL:%s", tag); + if (written < 0 || (size_t)written >= sizeof(buffer)) { + fail("result tag is too long"); + } + check_void(selene_print_bool(selene_instance, borrowed_string(buffer), value), "selene_print_bool"); +} + +int main(int argc, char** argv) { + if (argc < 3 || strcmp(argv[1], "--configuration") != 0) { + fprintf(stderr, "Usage: %s --configuration \n", argv[0]); + return 1; + } + + check_void(selene_load_config(&selene_instance, argv[2]), "selene_load_config"); + register_clifford_t_gateset(); + + uint64_t n_shots = unwrap_u64(selene_shot_count(selene_instance), "selene_shot_count"); + for (uint64_t shot = 0; shot < n_shots; ++shot) { + check_void(selene_on_shot_start(selene_instance, shot), "selene_on_shot_start"); + ct_program(); + check_void(selene_on_shot_end(selene_instance), "selene_on_shot_end"); + } + check_void(selene_exit(selene_instance), "selene_exit"); + return 0; +} diff --git a/examples/clifford_t_stack/cpp/CMakeLists.txt b/examples/clifford_t_stack/cpp/CMakeLists.txt new file mode 100644 index 00000000..0bc11788 --- /dev/null +++ b/examples/clifford_t_stack/cpp/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.20) + +project(selene_example_clifford_t_qrack LANGUAGES CXX) + +include(ExternalProject) +include(GNUInstallDirs) + +find_package(Threads REQUIRED) + +set(QRACK_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/qrack-install) +set(QRACK_LIBRARY + ${QRACK_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/qrack/${CMAKE_STATIC_LIBRARY_PREFIX}qrack${CMAKE_STATIC_LIBRARY_SUFFIX} +) + +ExternalProject_Add(qrack_external + GIT_REPOSITORY https://github.com/vm6502q/qrack.git + GIT_TAG vm6502q.v10.10.0 + CMAKE_ARGS + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${QRACK_INSTALL_DIR} + -DENABLE_EXAMPLES=OFF + -DENABLE_TESTS=OFF + -DENABLE_OPENCL=OFF + -DENABLE_CUDA=OFF + -DENABLE_QBDT=OFF + BUILD_BYPRODUCTS ${QRACK_LIBRARY} +) + +file(MAKE_DIRECTORY + ${QRACK_INSTALL_DIR}/include/qrack + ${QRACK_INSTALL_DIR}/include/qrack/common +) + +add_library(qrack::qrack STATIC IMPORTED GLOBAL) +set_target_properties(qrack::qrack PROPERTIES + IMPORTED_LOCATION ${QRACK_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${QRACK_INSTALL_DIR}/include/qrack;${QRACK_INSTALL_DIR}/include/qrack/common" +) +add_dependencies(qrack::qrack qrack_external) + +add_library(selene_example_clifford_t_qrack SHARED src/qrack_simulator.cpp) +target_compile_features(selene_example_clifford_t_qrack PRIVATE cxx_std_17) +target_include_directories(selene_example_clifford_t_qrack PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../selene-core/c/include +) +target_link_libraries(selene_example_clifford_t_qrack PRIVATE qrack::qrack Threads::Threads) + +set_target_properties(selene_example_clifford_t_qrack PROPERTIES + CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON +) diff --git a/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp b/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp new file mode 100644 index 00000000..97875029 --- /dev/null +++ b/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp @@ -0,0 +1,421 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/qrack_functions.hpp" +#include "common/qrack_types.hpp" +#include "qstabilizerhybrid.hpp" + +namespace selene_example_qrack { + +struct GateSetDeleter { + void operator()(GwGateSet* set) const { gw_gateset_free(set); } +}; + +struct DecodedGateDeleter { + void operator()(GwDecodedGate* gate) const { gw_decoded_gate_free(gate); } +}; + +using GateSetPtr = std::unique_ptr; +using DecodedGatePtr = std::unique_ptr; + +void check_gw(GwStatus status, const char* context) { + if (status == GW_STATUS_OK) { + return; + } + const char* message = gw_status_message(status); + throw std::runtime_error( + std::string(context) + ": " + (message == nullptr ? "unknown gatewire error" : message)); +} + +bool id_eq(const std::optional& lhs, GwSemanticId rhs) { + return lhs.has_value() && gw_semantic_id_eq(*lhs, rhs) != 0; +} + +bool id_eq(GwSemanticId lhs, GwSemanticId rhs) { return gw_semantic_id_eq(lhs, rhs) != 0; } + +GwSemanticId semantic_id(const char* text) { + GwSemanticId id{}; + check_gw(gw_semantic_id_from_text(text, std::strlen(text), &id), "build semantic id"); + return id; +} + +DecodedGatePtr decode_gate(const uint8_t* data, size_t len) { + GwDecodedGate* raw = nullptr; + check_gw(gw_gate_deserialize(data, len, &raw), "decode gate"); + return DecodedGatePtr(raw); +} + +GateSetPtr decode_gateset(const uint8_t* data, size_t len) { + GwGateSet* raw = nullptr; + check_gw(gw_gateset_deserialize(data, len, &raw), "decode gateset"); + return GateSetPtr(raw); +} + +uint64_t qubit_operand(const GwDecodedGate* gate, size_t index) { + GwGateValue value{}; + value.abi_size = sizeof(GwGateValue); + check_gw(gw_decoded_gate_value_at(gate, index, &value), "read gate operand"); + if (value.kind != GW_OPERAND_KIND_QUBIT) { + throw std::runtime_error("gate has unexpected operands"); + } + return value.data.qubit; +} + +struct GateIds { + std::optional h; + std::optional s; + std::optional sdg; + std::optional t; + std::optional tdg; + std::optional x; + std::optional cnot; +}; + +GateIds clifford_t_ids() { + return GateIds{ + semantic_id("example.clifford_t.H.v1"), + semantic_id("example.clifford_t.S.v1"), + semantic_id("example.clifford_t.Sdg.v1"), + semantic_id("example.clifford_t.T.v1"), + semantic_id("example.clifford_t.Tdg.v1"), + semantic_id("example.clifford_t.X.v1"), + semantic_id("example.clifford_t.CNOT.v1"), + }; +} + +struct QrackSimulator { + explicit QrackSimulator(uint64_t qubit_count) + : n_qubits(qubit_count), + sim(std::make_shared( + std::vector{Qrack::QINTERFACE_CPU}, + static_cast(qubit_count))) { + sim->SetTInjection(true); + sim->SetUseExactNearClifford(true); + sim->SetNcrp(0.0F); + } + + uint64_t n_qubits = 0; + std::shared_ptr sim; + GateIds ids; + std::optional measured_register_sample; + uint64_t gates_seen = 0; + uint64_t measurements = 0; + uint64_t engine_fallbacks = 0; +}; + +void ensure_qubit(const QrackSimulator& simulator, uint64_t qubit) { + if (qubit >= simulator.n_qubits) { + throw std::out_of_range("qubit index is out of bounds"); + } +} + +void ensure_no_engine_fallback(QrackSimulator& simulator) { + if (!simulator.sim->isClifford()) { + ++simulator.engine_fallbacks; + throw std::runtime_error("Qrack left stabilizer-hybrid mode and switched to a full engine"); + } +} + +bool sample_bit(const bitCapInt& sample, uint64_t qubit) { + return bi_compare_0(sample & Qrack::pow2(static_cast(qubit))) != 0; +} + +void invalidate_measured_register_sample(QrackSimulator& simulator) { + simulator.measured_register_sample.reset(); +} + +void expect_arity(size_t operands, size_t expected) { + if (operands != expected) { + throw std::runtime_error("gate declaration has unexpected arity"); + } +} + +void remember_gate(GateIds& negotiated, const GateIds& supported, GwSemanticId id, size_t operands) { + if (id_eq(*supported.h, id)) { + expect_arity(operands, 1); + negotiated.h = id; + } else if (id_eq(*supported.s, id)) { + expect_arity(operands, 1); + negotiated.s = id; + } else if (id_eq(*supported.sdg, id)) { + expect_arity(operands, 1); + negotiated.sdg = id; + } else if (id_eq(*supported.t, id)) { + expect_arity(operands, 1); + negotiated.t = id; + } else if (id_eq(*supported.tdg, id)) { + expect_arity(operands, 1); + negotiated.tdg = id; + } else if (id_eq(*supported.x, id)) { + expect_arity(operands, 1); + negotiated.x = id; + } else if (id_eq(*supported.cnot, id)) { + expect_arity(operands, 2); + negotiated.cnot = id; + } else { + throw std::runtime_error("Qrack Clifford+T simulator received an unsupported gate"); + } +} + +GateIds parse_gateset(const uint8_t* data, size_t len) { + auto set = decode_gateset(data, len); + GateIds ids; + const GateIds supported = clifford_t_ids(); + size_t gate_count = 0; + check_gw(gw_gateset_len(set.get(), &gate_count), "read gateset length"); + for (size_t gate = 0; gate < gate_count; ++gate) { + GwGateDeclInfo decl{}; + decl.abi_size = sizeof(GwGateDeclInfo); + check_gw(gw_gateset_decl_at(set.get(), gate, &decl), "read gate declaration"); + for (size_t operand = 0; operand < decl.operands_len; ++operand) { + GwOperandDeclInfo operand_decl{}; + operand_decl.abi_size = sizeof(GwOperandDeclInfo); + check_gw( + gw_gateset_decl_operand_at(set.get(), gate, operand, &operand_decl), "read operand declaration"); + if (operand_decl.kind != GW_OPERAND_KIND_QUBIT) { + throw std::runtime_error("Clifford+T gates must use only qubit operands"); + } + } + remember_gate(ids, supported, decl.semantic_id, decl.operands_len); + } + return ids; +} + +int fail(const char* context, const std::exception& error) { + std::cerr << "selene_example_clifford_t_qrack: " << context << ": " << error.what() << "\n"; + return -1; +} + +template int wrap_errno(const char* context, Fn fn) { + try { + fn(); + return 0; + } catch (const std::exception& error) { + return fail(context, error); + } +} + +QrackSimulator& instance(SeleneSimulatorInstance handle) { + if (handle == nullptr) { + throw std::runtime_error("simulator instance is null"); + } + return *static_cast(handle); +} + +} // namespace selene_example_qrack + +using namespace selene_example_qrack; + +extern "C" const char* qrack_get_name() { return "Qrack Clifford+T stabilizer-hybrid simulator"; } + +extern "C" SeleneErrno qrack_init( + SeleneSimulatorInstance* handle, uint64_t n_qubits, uint32_t, const char* const*) { + return wrap_errno("init", [&]() { + if (handle == nullptr) { + throw std::runtime_error("output handle is null"); + } + *handle = new QrackSimulator(n_qubits); + }); +} + +extern "C" SeleneErrno qrack_exit(SeleneSimulatorInstance handle) { + return wrap_errno("exit", [&]() { delete &instance(handle); }); +} + +extern "C" SeleneErrno qrack_shot_start(SeleneSimulatorInstance handle, uint64_t, uint64_t seed) { + return wrap_errno("shot_start", [&]() { + auto& simulator = instance(handle); + simulator.sim = std::make_shared( + std::vector{Qrack::QINTERFACE_CPU}, + static_cast(simulator.n_qubits)); + simulator.sim->SetTInjection(true); + simulator.sim->SetUseExactNearClifford(true); + simulator.sim->SetNcrp(0.0F); + simulator.sim->SetRandomSeed(static_cast(seed)); + simulator.measured_register_sample.reset(); + simulator.gates_seen = 0; + simulator.measurements = 0; + simulator.engine_fallbacks = 0; + }); +} + +extern "C" SeleneErrno qrack_shot_end(SeleneSimulatorInstance handle) { + return wrap_errno("shot_end", [&]() { ensure_no_engine_fallback(instance(handle)); }); +} + +extern "C" SeleneErrno qrack_gate(SeleneSimulatorInstance handle, const uint8_t* data, size_t len) { + return wrap_errno("gate", [&]() { + auto& simulator = instance(handle); + invalidate_measured_register_sample(simulator); + auto gate = decode_gate(data, len); + GwSemanticId gate_id{}; + check_gw(gw_decoded_gate_semantic_id(gate.get(), &gate_id), "read gate semantic id"); + + if (id_eq(simulator.ids.h, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->H(static_cast(q)); + } else if (id_eq(simulator.ids.s, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->S(static_cast(q)); + } else if (id_eq(simulator.ids.sdg, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->IS(static_cast(q)); + } else if (id_eq(simulator.ids.t, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->T(static_cast(q)); + } else if (id_eq(simulator.ids.tdg, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->IT(static_cast(q)); + } else if (id_eq(simulator.ids.x, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->X(static_cast(q)); + } else if (id_eq(simulator.ids.cnot, gate_id)) { + const auto control = qubit_operand(gate.get(), 0); + const auto target = qubit_operand(gate.get(), 1); + ensure_qubit(simulator, control); + ensure_qubit(simulator, target); + simulator.sim->CNOT( + static_cast(control), static_cast(target)); + } else { + throw std::runtime_error("gate is not in the negotiated Clifford+T gateset"); + } + + ++simulator.gates_seen; + ensure_no_engine_fallback(simulator); + }); +} + +extern "C" SeleneErrno qrack_measure(SeleneSimulatorInstance handle, uint64_t qubit) { + try { + auto& simulator = instance(handle); + ensure_qubit(simulator, qubit); + bool result = false; + + if (!simulator.measured_register_sample.has_value()) { + const bitCapInt sample = simulator.sim->MAll(); + simulator.measured_register_sample = sample; + } + result = sample_bit(*simulator.measured_register_sample, qubit); + + ++simulator.measurements; + ensure_no_engine_fallback(simulator); + return result ? 1 : 0; + } catch (const std::exception& error) { + return fail("measure", error); + } +} + +extern "C" SeleneErrno qrack_postselect( + SeleneSimulatorInstance handle, uint64_t qubit, bool target_value) { + return wrap_errno("postselect", [&]() { + auto& simulator = instance(handle); + invalidate_measured_register_sample(simulator); + ensure_qubit(simulator, qubit); + (void)simulator.sim->ForceM(static_cast(qubit), target_value, true, true); + ensure_no_engine_fallback(simulator); + }); +} + +extern "C" SeleneErrno qrack_reset(SeleneSimulatorInstance handle, uint64_t qubit) { + return wrap_errno("reset", [&]() { + auto& simulator = instance(handle); + invalidate_measured_register_sample(simulator); + ensure_qubit(simulator, qubit); + simulator.sim->SetBit(static_cast(qubit), false); + ensure_no_engine_fallback(simulator); + }); +} + +void write_metric(const char* tag, uint8_t datatype, uint64_t value, char* tag_out, uint8_t* datatype_out, + uint64_t* value_out) { + std::strcpy(tag_out, tag); + *datatype_out = datatype; + *value_out = value; +} + +extern "C" SeleneErrno qrack_get_metrics( + SeleneSimulatorInstance handle, uint8_t nth_metric, char* tag_out, uint8_t* datatype_out, uint64_t* value_out) { + try { + auto& simulator = instance(handle); + switch (nth_metric) { + case 0: + write_metric("gates_seen", 2, simulator.gates_seen, tag_out, datatype_out, value_out); + return 0; + case 1: + write_metric("measurements", 2, simulator.measurements, tag_out, datatype_out, value_out); + return 0; + case 2: + write_metric("engine_fallbacks", 2, simulator.engine_fallbacks, tag_out, datatype_out, value_out); + return 0; + default: + return 1; + } + } catch (const std::exception& error) { + return fail("get_metrics", error); + } +} + +extern "C" SeleneErrno qrack_dump_state(SeleneSimulatorInstance, const char*, const uint64_t*, uint64_t) { + std::cerr << "selene_example_clifford_t_qrack: dump_state is not implemented\n"; + return -1; +} + +extern "C" SeleneErrno qrack_negotiate_gateset(SeleneSimulatorInstance handle, const uint8_t* input, + size_t input_len, uint8_t* output, size_t output_len, size_t* written) { + return wrap_errno("negotiate_gateset", [&]() { + if (written == nullptr) { + throw std::runtime_error("written pointer is null"); + } + auto& simulator = instance(handle); + GateIds ids = parse_gateset(input, input_len); + *written = input_len; + if (output == nullptr) { + simulator.ids = std::move(ids); + return; + } + if (output_len < input_len) { + throw std::runtime_error("output buffer is too small"); + } + std::memcpy(output, input, input_len); + simulator.ids = std::move(ids); + }); +} + +extern "C" SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1 = { + sizeof(SeleneSimulatorPluginDescriptorV1), + SELENE_SIMULATOR_CURRENT_API_VERSION, + qrack_get_name, + qrack_init, + qrack_exit, + qrack_shot_start, + qrack_shot_end, + qrack_measure, + qrack_postselect, + qrack_reset, + qrack_get_metrics, + qrack_dump_state, + qrack_gate, + qrack_negotiate_gateset, +}; + +extern "C" const SeleneSimulatorPluginDescriptorV1* selene_simulator_get_plugin_descriptor_v1() { + return &selene_simulator_plugin_descriptor_v1; +} diff --git a/examples/clifford_t_stack/hatch_build.py b/examples/clifford_t_stack/hatch_build.py new file mode 100644 index 00000000..468fdb12 --- /dev/null +++ b/examples/clifford_t_stack/hatch_build.py @@ -0,0 +1,169 @@ +import os +import shutil +import subprocess +import sys +from pathlib import Path + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface +from packaging.tags import sys_tags + + +class CliffordTExampleBuildHook(BuildHookInterface): + package_dir = Path("python/selene_example_clifford_t_stack") + qrack_build_dir = Path("target/qrack-simulator-release") + + def get_cargo_release_dir(self) -> Path: + target = os.environ.get("CARGO_BUILD_TARGET") + if target: + candidate = Path(self.root) / "target" / target / "release" + if candidate.exists(): + return candidate + return Path(self.root) / "target" / "release" + + def rust_library_filenames(self) -> list[str]: + lib_name = "selene_example_clifford_t_stack" + match sys.platform: + case "darwin": + return [f"lib{lib_name}.dylib"] + case "linux": + return [f"lib{lib_name}.so"] + case "win32": + return [f"{lib_name}.dll", f"lib{lib_name}.dll.a"] + case _: + raise RuntimeError(f"Unsupported platform: {sys.platform}") + + def qrack_library_filename(self) -> str: + lib_name = "selene_example_clifford_t_qrack" + match sys.platform: + case "darwin": + return f"lib{lib_name}.dylib" + case "linux": + return f"lib{lib_name}.so" + case "win32": + return f"{lib_name}.dll" + case _: + raise RuntimeError(f"Unsupported platform: {sys.platform}") + + def build_cargo_library(self) -> None: + self.app.display_mini_header("Building Clifford+T example library") + try: + subprocess.run( + [ + "cargo", + "build", + "--manifest-path", + str(Path(self.root) / "Cargo.toml"), + "--release", + "--locked", + ], + cwd=self.root, + check=True, + capture_output=True, + ) + except subprocess.CalledProcessError as error: + self.app.display_error(error.stderr.decode()) + raise + + def build_qrack_simulator(self) -> None: + self.app.display_mini_header("Building Qrack Clifford+T simulator") + build_dir = Path(self.root) / self.qrack_build_dir + build_dir.mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + try: + subprocess.run( + [ + "cmake", + "-S", + str(Path(self.root) / "cpp"), + "-B", + str(build_dir), + "-DCMAKE_BUILD_TYPE=Release", + ], + cwd=self.root, + env=env, + check=True, + capture_output=True, + ) + subprocess.run( + [ + "cmake", + "--build", + str(build_dir), + "--config", + "Release", + "--target", + "selene_example_clifford_t_qrack", + "--parallel", + ], + cwd=self.root, + env=env, + check=True, + capture_output=True, + ) + except FileNotFoundError as error: + raise RuntimeError( + "Building the Qrack simulator requires CMake and a C++ compiler " + "from the devenv shell." + ) from error + except subprocess.CalledProcessError as error: + self.app.display_error(error.stdout.decode()) + self.app.display_error(error.stderr.decode()) + raise + + def copy_tree_contents(self, source: Path, destination: Path) -> None: + if destination.exists(): + shutil.rmtree(destination) + shutil.copytree(source, destination) + + def collect_dist(self) -> list[str]: + package_root = Path(self.root) / self.package_dir + dist_dir = package_root / "_dist" + if dist_dir.exists(): + shutil.rmtree(dist_dir) + + lib_dir = dist_dir / "lib" + lib_dir.mkdir(parents=True) + release_dir = self.get_cargo_release_dir() + for filename in self.rust_library_filenames(): + source = release_dir / filename + if not source.exists(): + raise FileNotFoundError( + f"Compiled library {source} not found after cargo build" + ) + self.app.display_info(f"Copying {source} to {lib_dir}") + shutil.copy(source, lib_dir / filename) + + qrack_library = ( + Path(self.root) / self.qrack_build_dir / self.qrack_library_filename() + ) + if not qrack_library.exists(): + raise FileNotFoundError( + f"Compiled Qrack simulator {qrack_library} not found after CMake build" + ) + self.app.display_info(f"Copying {qrack_library} to {lib_dir}") + shutil.copy(qrack_library, lib_dir / qrack_library.name) + + self.copy_tree_contents(Path(self.root) / "c" / "include", dist_dir / "include") + self.copy_tree_contents(Path(self.root) / "c" / "src", dist_dir / "src") + + return [ + str(path.relative_to(self.root).as_posix()) + for path in dist_dir.rglob("*") + if path.is_file() + ] + + def initialize(self, version: str, build_data: dict) -> None: + self.build_cargo_library() + self.build_qrack_simulator() + artifacts = self.collect_dist() + build_data["artifacts"] += artifacts + build_data["pure_python"] = False + + tag = next( + iter( + tag + for tag in sys_tags() + if "manylinux" not in tag.platform and "musllinux" not in tag.platform + ) + ) + build_data["tag"] = f"py3-none-{tag.platform}" diff --git a/examples/clifford_t_stack/justfile b/examples/clifford_t_stack/justfile new file mode 100644 index 00000000..b816d207 --- /dev/null +++ b/examples/clifford_t_stack/justfile @@ -0,0 +1,23 @@ +python := env_var_or_default("PYTHON", "python") +venv := "venv" + +default: + just --list + +venv: + {{python}} -m venv {{venv}} + +setup: venv + {{venv}}/bin/pip install --upgrade pip + {{venv}}/bin/pip install ../../ + {{venv}}/bin/pip install ../../selene-core + {{venv}}/bin/pip install . + {{venv}}/bin/pip install pytest + +test: + {{venv}}/bin/python -m pytest -s python/tests + +all: setup test + +clean: + rm -rf {{venv}} diff --git a/examples/clifford_t_stack/pyproject.toml b/examples/clifford_t_stack/pyproject.toml new file mode 100644 index 00000000..403b77e1 --- /dev/null +++ b/examples/clifford_t_stack/pyproject.toml @@ -0,0 +1,42 @@ +[build-system] +requires = ["hatchling", "packaging"] +build-backend = "hatchling.build" + +[project] +name = "selene-example-clifford-t-stack" +version = "0.1.0" +requires-python = ">=3.10" +description = "A worked Selene custom-gateset example using Clifford+T plugins" +readme = "README.md" +dependencies = [ + "selene-core~=0.3.0a0", + "selene-sim~=0.3.0a0", +] + +[tool.uv.sources] +selene-core = { path = "../../selene-core", editable = true } +selene-sim = { path = "../..", editable = true } + +[dependency-groups] +test = [ + "pytest>=8.3.5", +] + +[tool.hatch.build.hooks.custom] +path = "hatch_build.py" + +[tool.hatch.build.targets.wheel] +packages = ["python/selene_example_clifford_t_stack"] + +[tool.uv] +cache-keys = [ + { file = "Cargo.lock" }, + { file = "Cargo.toml" }, + { file = "hatch_build.py" }, + { file = "c/**/*.c" }, + { file = "c/**/*.h" }, + { file = "cpp/CMakeLists.txt" }, + { file = "cpp/**/*.cpp" }, + { file = "programs/**/*.c" }, + { file = "src/**/*.rs" }, +] diff --git a/examples/clifford_t_stack/python/selene_example_clifford_t_stack/__init__.py b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/__init__.py new file mode 100644 index 00000000..01ab162d --- /dev/null +++ b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/__init__.py @@ -0,0 +1,59 @@ +from .build import ( + CliffordTCSourceKind, + CliffordTCToSeleneObjectStep, + CliffordTInterface, +) +from .gates import ( + CLIFFORD_T, + CNOT, + H, + S, + Sdg, + T, + Tdg, + X, + clifford_t_gateset, + cnot, + h, + s, + sdg, + t, + tdg, + x, +) +from .plugins import ( + CliffordTErrorModel, + CliffordTQrackSimulator, + CliffordTRuntime, + library_file, + qrack_library_file, + rust_library_file, +) + +__all__ = [ + "CLIFFORD_T", + "CNOT", + "H", + "S", + "Sdg", + "T", + "Tdg", + "X", + "CliffordTCSourceKind", + "CliffordTCToSeleneObjectStep", + "CliffordTErrorModel", + "CliffordTInterface", + "CliffordTQrackSimulator", + "CliffordTRuntime", + "clifford_t_gateset", + "cnot", + "h", + "library_file", + "qrack_library_file", + "rust_library_file", + "s", + "sdg", + "t", + "tdg", + "x", +] diff --git a/examples/clifford_t_stack/python/selene_example_clifford_t_stack/build.py b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/build.py new file mode 100644 index 00000000..f47a5370 --- /dev/null +++ b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/build.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +import platform +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from selene_core import BuildPlanner, QuantumInterface +from selene_core.build_utils import Artifact, ArtifactKind, BuildCtx, Step +from selene_core.build_utils.builtins import SeleneObjectFileKind +from selene_core.build_utils.utils import invoke_zig + +from .plugins import _dist_root, _source_root + + +class CliffordTCSourceKind(ArtifactKind[Path]): + priority = 10_000 + + @classmethod + def matches(cls, resource: Any) -> bool: + return ( + isinstance(resource, Path) + and resource.is_file() + and resource.suffixes[-2:] + == [ + ".ct", + ".c", + ] + ) + + +class CliffordTCToSeleneObjectStep(Step): + input_kind = CliffordTCSourceKind + output_kind = SeleneObjectFileKind + + @classmethod + def apply( + cls, build_ctx: BuildCtx, input_artifact: Artifact[Path] + ) -> Artifact[Path]: + source_root = _source_root() + dist_root = _dist_root() + include_root = ( + source_root / "c" / "include" if source_root else dist_root / "include" + ) + shim_source = ( + source_root / "c" / "src" / "clifford_t_interface.c" + if source_root + else dist_root / "src" / "clifford_t_interface.c" + ) + object_suffix = ".obj" if platform.system() == "Windows" else ".o" + user_obj = build_ctx.artifact_dir / f"user_program{object_suffix}" + interface_obj = build_ctx.artifact_dir / f"clifford_t_interface{object_suffix}" + out_path = build_ctx.artifact_dir / f"program.clifford_t{object_suffix}" + cache_dir = build_ctx.artifact_dir / "zig-cache" + cache_dir.mkdir(exist_ok=True) + + include_dirs = [include_root] + if source_root is not None: + repo_root = source_root.parents[1] + include_dirs.extend( + [ + repo_root / "selene-sim" / "c" / "include", + repo_root / "selene-core" / "c" / "include", + ] + ) + try: + import selene_sim + + include_dirs.append( + Path(selene_sim.__file__).resolve().parent / "_dist" / "include" + ) + except ImportError: + pass + try: + import selene_core + + include_dirs.append( + Path(selene_core.__file__).resolve().parent / "_dist" / "include" + ) + except ImportError: + pass + include_flags = [ + flag for include_dir in include_dirs for flag in ("-I", include_dir) + ] + + common_flags = [ + "-std=c11", + "-fPIC", + *include_flags, + ] + + invoke_zig( + "cc", + "-c", + input_artifact.resource, + "-o", + user_obj, + *common_flags, + verbose=build_ctx.verbose, + cache_dir=cache_dir, + ) + invoke_zig( + "cc", + "-c", + shim_source, + "-o", + interface_obj, + *common_flags, + verbose=build_ctx.verbose, + cache_dir=cache_dir, + ) + invoke_zig( + "cc", + "-r", + "-o", + out_path, + user_obj, + interface_obj, + verbose=build_ctx.verbose, + cache_dir=cache_dir, + ) + return cls._make_artifact(out_path) + + +@dataclass +class CliffordTInterface(QuantumInterface): + @property + def library_file(self) -> Path: + source_root = _source_root() + if source_root is not None: + return source_root / "c" / "src" / "clifford_t_interface.c" + return _dist_root() / "src" / "clifford_t_interface.c" + + def register_build_steps(self, planner: BuildPlanner) -> None: + planner.add_kind(CliffordTCSourceKind) + planner.add_step(CliffordTCToSeleneObjectStep) diff --git a/examples/clifford_t_stack/python/selene_example_clifford_t_stack/gates.py b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/gates.py new file mode 100644 index 00000000..3c044219 --- /dev/null +++ b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/gates.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from selene_core import Gate, GateDefinition, Gateset, OperandDefinition, QUBIT + + +def _gate(name: str, operands: list[str]) -> GateDefinition: + return GateDefinition( + f"example.clifford_t.{name}.v1", + name, + [OperandDefinition(operand, QUBIT) for operand in operands], + ) + + +H = _gate("H", ["q0"]) +S = _gate("S", ["q0"]) +Sdg = _gate("Sdg", ["q0"]) +T = _gate("T", ["q0"]) +Tdg = _gate("Tdg", ["q0"]) +X = _gate("X", ["q0"]) +CNOT = _gate("CNOT", ["control", "target"]) + +CLIFFORD_T = Gateset(H, S, Sdg, T, Tdg, X, CNOT) + + +def clifford_t_gateset() -> Gateset: + return CLIFFORD_T + + +def h(q0: int) -> Gate: + return CLIFFORD_T.H(q0=q0) + + +def s(q0: int) -> Gate: + return CLIFFORD_T.S(q0=q0) + + +def sdg(q0: int) -> Gate: + return CLIFFORD_T.Sdg(q0=q0) + + +def t(q0: int) -> Gate: + return CLIFFORD_T.T(q0=q0) + + +def tdg(q0: int) -> Gate: + return CLIFFORD_T.Tdg(q0=q0) + + +def x(q0: int) -> Gate: + return CLIFFORD_T.X(q0=q0) + + +def cnot(control: int, target: int) -> Gate: + return CLIFFORD_T.CNOT(control=control, target=target) diff --git a/examples/clifford_t_stack/python/selene_example_clifford_t_stack/plugins.py b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/plugins.py new file mode 100644 index 00000000..4cc31d18 --- /dev/null +++ b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/plugins.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import os +import platform +from dataclasses import dataclass +from pathlib import Path + +from selene_core import ErrorModel, Runtime, Simulator + + +def _package_root() -> Path: + return Path(__file__).resolve().parent + + +def _project_root() -> Path: + return Path(__file__).resolve().parents[2] + + +def _source_root() -> Path | None: + root = _project_root() + if (root / "Cargo.toml").exists() and (root / "src").exists(): + return root + return None + + +def _dist_root() -> Path: + return _package_root() / "_dist" + + +def _platform_library_name(lib_name: str) -> str: + match platform.system(): + case "Linux": + return f"lib{lib_name}.so" + case "Darwin": + return f"lib{lib_name}.dylib" + case "Windows": + return f"{lib_name}.dll" + case other: + raise RuntimeError(f"Unsupported platform: {other}") + + +def _library_file( + *, + env_var: str, + lib_name: str, + source_subdir: Path | None = None, + legacy_env_var: str | None = None, +) -> Path: + override = os.environ.get(legacy_env_var) if legacy_env_var is not None else None + override = os.environ.get(env_var, override) + if override: + return Path(override) + + filename = _platform_library_name(lib_name) + source_root = _source_root() + if source_root is not None: + if source_subdir is not None: + candidate = source_root / source_subdir / filename + if candidate.exists(): + return candidate + debug_library = source_root / "target" / "debug" / filename + if debug_library.exists(): + return debug_library + release_library = source_root / "target" / "release" / filename + if release_library.exists(): + return release_library + return _dist_root() / "lib" / filename + + +def library_file() -> Path: + return rust_library_file() + + +def rust_library_file() -> Path: + return _library_file( + env_var="SELENE_EXAMPLE_CLIFFORD_T_RUST_LIB", + lib_name="selene_example_clifford_t_stack", + legacy_env_var="SELENE_EXAMPLE_CLIFFORD_T_LIB", + ) + + +def qrack_library_file() -> Path: + return _library_file( + env_var="SELENE_EXAMPLE_CLIFFORD_T_QRACK_LIB", + lib_name="selene_example_clifford_t_qrack", + source_subdir=Path("target/qrack-simulator-release"), + ) + + +@dataclass +class CliffordTRuntime(Runtime): + @property + def library_file(self) -> Path: + return rust_library_file() + + def get_init_args(self) -> list[str]: + return [] + + +@dataclass +class CliffordTErrorModel(ErrorModel): + flip_probability: float = 1.0 + + @property + def library_file(self) -> Path: + return rust_library_file() + + def get_init_args(self) -> list[str]: + return [str(self.flip_probability)] + + +@dataclass +class CliffordTQrackSimulator(Simulator): + @property + def library_file(self) -> Path: + return qrack_library_file() + + def get_init_args(self) -> list[str]: + return [] diff --git a/examples/clifford_t_stack/python/selene_example_clifford_t_stack/py.typed b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/py.typed new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/clifford_t_stack/python/selene_example_clifford_t_stack/py.typed @@ -0,0 +1 @@ + diff --git a/examples/clifford_t_stack/python/tests/programs/bell.ct.c b/examples/clifford_t_stack/python/tests/programs/bell.ct.c new file mode 100644 index 00000000..652aa515 --- /dev/null +++ b/examples/clifford_t_stack/python/tests/programs/bell.ct.c @@ -0,0 +1,19 @@ +#include + +void ct_program(void) { + uint64_t q0 = ct_qalloc(); + uint64_t q1 = ct_qalloc(); + + ct_h(q0); + ct_t(q0); + ct_cnot(q0, q1); + + bool m0 = ct_measure(q0); + bool m1 = ct_measure(q1); + + ct_record_bool("q0", m0); + ct_record_bool("q1", m1); + + ct_qfree(q0); + ct_qfree(q1); +} diff --git a/examples/clifford_t_stack/python/tests/programs/hth.ct.c b/examples/clifford_t_stack/python/tests/programs/hth.ct.c new file mode 100644 index 00000000..55d8aba9 --- /dev/null +++ b/examples/clifford_t_stack/python/tests/programs/hth.ct.c @@ -0,0 +1,11 @@ +#include + +void ct_program(void) { + uint64_t qubit = ct_qalloc(); + ct_h(qubit); + ct_t(qubit); + ct_h(qubit); + bool measurement = ct_measure(qubit); + ct_record_bool("measurement", measurement); + ct_qfree(qubit); +} diff --git a/examples/clifford_t_stack/python/tests/test_python_bindings.py b/examples/clifford_t_stack/python/tests/test_python_bindings.py new file mode 100644 index 00000000..f3906a89 --- /dev/null +++ b/examples/clifford_t_stack/python/tests/test_python_bindings.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from pathlib import Path + +from selene_sim.build import build +from selene_sim.event_hooks import MetricStore +from selene_sim.interactive import InteractiveFullStack, InteractiveSimulator + +from selene_example_clifford_t_stack import ( + CliffordTErrorModel, + CliffordTInterface, + CliffordTQrackSimulator, + CliffordTRuntime, + clifford_t_gateset, + cnot, + h, + t, + x, +) + + +PROGRAMS_PATH = Path(__file__).parent / "programs" + + +def test_interactive_simulator_accepts_clifford_t_gates_from_python(): + gateset = clifford_t_gateset() + sim = InteractiveSimulator( + simulator=CliffordTQrackSimulator(), + n_qubits=2, + gateset=gateset, + ) + + assert sim.emitted_gateset is not None + assert [definition.name for definition in sim.emitted_gateset] == [ + "H", + "S", + "Sdg", + "T", + "Tdg", + "X", + "CNOT", + ] + + sim.gate(h(0)) + sim.gate(t(0)) + sim.gate(x(0)) + sim.gate(cnot(0, 1)) + + m0 = sim.measure(0) + m1 = sim.measure(1) + assert isinstance(m0, bool) + assert m1 == m0 + assert sim.get_metrics() == { + "gates_seen": 4, + "measurements": 2, + "engine_fallbacks": 0, + } + + +def test_interactive_simulator_hth_stats(): + gateset = clifford_t_gateset() + sim = InteractiveSimulator( + simulator=CliffordTQrackSimulator(random_seed=1234), + n_qubits=1, + gateset=gateset, + ) + + outcomes = [0, 0] + + shots = 1000 + for _ in range(shots): + sim.reset(0) + sim.gate(h(0)) + sim.gate(t(0)) + sim.gate(h(0)) + meas = sim.measure(0) + outcomes[meas] += 1 + + assert 0.8 < outcomes[0] / shots < 0.9 + assert 0.1 < outcomes[1] / shots < 0.2 + + +def test_interactive_full_stack_uses_example_runtime_error_model_and_simulator(): + gates = clifford_t_gateset() + stack = InteractiveFullStack( + runtime=CliffordTRuntime(), + error_model=CliffordTErrorModel(), + simulator=CliffordTQrackSimulator(), + n_qubits=2, + gateset=gates, + ) + + q0 = stack.qalloc() + q1 = stack.qalloc() + stack.gate(gates.H(q0=q0.id)) + stack.gate(gates.T(q0=q0.id)) + stack.gate(gates.CNOT(control=q0.id, target=q1.id)) + + m0 = stack.measure(q0) + m1 = stack.measure(q1) + assert isinstance(m0, bool) + assert m1 == m0 + + +def test_selene_build_runs_a_compiled_clifford_t_user_program(): + metric_store = MetricStore() + runner = build( + PROGRAMS_PATH / "bell.ct.c", + interface=CliffordTInterface(), + strict=True, + ) + + shots = runner.run_shots( + simulator=CliffordTQrackSimulator(), + runtime=CliffordTRuntime(), + error_model=CliffordTErrorModel(), + event_hook=metric_store, + n_qubits=2, + n_shots=1, + random_seed=1234, + ) + + shot_dicts = [dict(shot) for shot in shots] + assert len(shot_dicts) == 1 + assert shot_dicts[0]["q1"] == shot_dicts[0]["q0"] + metrics = metric_store.shots[0] + assert metrics["user_program"]["gate:T:count"] == 1 + assert metrics["post_runtime"]["gate:T:individual_count"] == 1 + assert metrics["error_model"]["injected_x"] == 2 + assert metrics["simulator"]["gates_seen"] == 5 + assert metrics["simulator"]["measurements"] == 2 + assert metrics["simulator"]["engine_fallbacks"] == 0 diff --git a/examples/clifford_t_stack/src/error_model.rs b/examples/clifford_t_stack/src/error_model.rs new file mode 100644 index 00000000..9f8e4688 --- /dev/null +++ b/examples/clifford_t_stack/src/error_model.rs @@ -0,0 +1,173 @@ +use anyhow::{Result, anyhow}; +use rand::{Rng, SeedableRng}; +use rand_pcg::Pcg64Mcg; +use selene_core::error_model::{ + BatchResult, ErrorModelInterface, interface::ErrorModelInterfaceFactory, +}; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec, Qubit}; +use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::simulator::SimulatorInterface; +use selene_core::utils::MetricValue; + +use crate::gates::{CliffordT, X, clifford_t_gateset, require_clifford_t}; + +#[derive(Debug)] +pub struct CliffordTErrorModel { + rng: Pcg64Mcg, + flip_probability: f64, + injected_x: u64, +} + +impl CliffordTErrorModel { + pub fn new(flip_probability: f64) -> Result { + if !(0.0..=1.0).contains(&flip_probability) { + return Err(anyhow!("flip probability must be between 0 and 1")); + } + Ok(Self { + rng: Pcg64Mcg::seed_from_u64(0), + flip_probability, + injected_x: 0, + }) + } + + pub fn deterministic() -> Self { + Self::new(1.0).expect("deterministic probability is valid") + } + + fn send(simulator: &mut dyn SimulatorInterface, op: Operation) -> Result<()> { + let results = simulator.handle_operations(BatchOperation::error_model(vec![op]))?; + if results.bool_results.is_empty() && results.u64_results.is_empty() { + Ok(()) + } else { + Err(anyhow!("non-measurement operation produced results")) + } + } + + pub fn injected_x(&self) -> u64 { + self.injected_x + } +} + +impl ErrorModelInterface for CliffordTErrorModel { + fn exit(&mut self) -> Result<()> { + Ok(()) + } + + fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { + self.rng = Pcg64Mcg::seed_from_u64(seed); + self.injected_x = 0; + Ok(()) + } + + fn shot_end(&mut self) -> Result<()> { + Ok(()) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + require_clifford_t(gateset)?; + Ok(clifford_t_gateset()) + } + + fn handle_operations( + &mut self, + operations: BatchOperation, + simulator: &mut dyn SimulatorInterface, + ) -> Result { + let mut results = BatchResult::default(); + for operation in operations { + match operation { + Operation::Gate { gate } => { + let injection_target = gate.single_qubit_operand(); + Self::send(simulator, Operation::from_gate_instance(gate)?)?; + if let Some(q0) = injection_target { + if self.rng.random::() < self.flip_probability { + let injected = CliffordT::X(X { q0: Qubit(q0) }).to_instance(); + Self::send(simulator, Operation::from_gate_instance(injected)?)?; + self.injected_x += 1; + } + } + } + Operation::Measure { + qubit_id, + result_id, + } => { + let mut measurement = + simulator.handle_operations(BatchOperation::error_model(vec![ + Operation::Measure { + qubit_id, + result_id: 0, + }, + ]))?; + let result = measurement + .bool_results + .pop() + .ok_or_else(|| anyhow!("simulator returned no bool result"))? + .value; + results.set_bool_result(result_id, result); + } + Operation::MeasureLeaked { + qubit_id, + result_id, + } => { + let mut measurement = + simulator.handle_operations(BatchOperation::error_model(vec![ + Operation::Measure { + qubit_id, + result_id: 0, + }, + ]))?; + let result = measurement + .bool_results + .pop() + .ok_or_else(|| anyhow!("simulator returned no bool result"))? + .value; + results.set_u64_result(result_id, result.into()); + } + Operation::Reset { qubit_id } => { + Self::send(simulator, Operation::Reset { qubit_id })?; + } + Operation::Custom { .. } => {} + _ => {} + } + } + Ok(results) + } + + fn get_metric(&mut self, nth_metric: u8) -> Result> { + match nth_metric { + 0 => Ok(Some(( + "injected_x".to_string(), + MetricValue::U64(self.injected_x), + ))), + _ => Ok(None), + } + } +} + +#[derive(Default)] +pub struct CliffordTErrorModelFactory; + +impl ErrorModelInterfaceFactory for CliffordTErrorModelFactory { + type Interface = CliffordTErrorModel; + + fn init( + self: std::sync::Arc, + _n_qubits: u64, + error_model_args: &[impl AsRef], + ) -> Result> { + let args = error_model_args + .get(1..) + .ok_or_else(|| anyhow!("missing plugin argv"))?; + let flip_probability = match args { + [] => 1.0, + [probability] => probability + .as_ref() + .parse::() + .map_err(|error| anyhow!("invalid flip probability: {error}"))?, + _ => return Err(anyhow!("expected at most one error model argument")), + }; + Ok(Box::new(CliffordTErrorModel::new(flip_probability)?)) + } +} + +selene_core::export_error_model_plugin!(crate::CliffordTErrorModelFactory); diff --git a/examples/clifford_t_stack/src/gates.rs b/examples/clifford_t_stack/src/gates.rs new file mode 100644 index 00000000..4366d765 --- /dev/null +++ b/examples/clifford_t_stack/src/gates.rs @@ -0,0 +1,103 @@ +use anyhow::{Result, anyhow, bail}; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec, OwnedGateInstance, Qubit}; +use selene_core::{define_gate, define_gateset}; + +define_gate! { + pub struct H [ + id = "example.clifford_t.H.v1", + display = "H", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct S [ + id = "example.clifford_t.S.v1", + display = "S", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct Sdg [ + id = "example.clifford_t.Sdg.v1", + display = "Sdg", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct T [ + id = "example.clifford_t.T.v1", + display = "T", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct Tdg [ + id = "example.clifford_t.Tdg.v1", + display = "Tdg", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct X [ + id = "example.clifford_t.X.v1", + display = "X", + version = 1, + ] { q0: Qubit } +} + +define_gate! { + pub struct CNOT [ + id = "example.clifford_t.CNOT.v1", + display = "CNOT", + version = 1, + ] { + control: Qubit, + target: Qubit, + } +} + +define_gateset! { + pub enum CliffordT { + H(H), + S(S), + Sdg(Sdg), + T(T), + Tdg(Tdg), + X(X), + CNOT(CNOT), + } +} + +pub fn clifford_t_gateset() -> DynamicGateSet { + DynamicGateSet::from_declarations(CliffordT::declarations()) + .expect("Clifford+T declarations are unique") +} + +pub fn gateset_names(gateset: &DynamicGateSet) -> Vec { + gateset + .declarations() + .map(|decl| decl.name.clone()) + .collect() +} + +pub fn require_clifford_t(input: &DynamicGateSet) -> Result<()> { + let supported = clifford_t_gateset(); + if let Some(decl) = input.first_unsupported_by(&supported) { + bail!("unsupported Clifford+T input gate {}", decl.name); + } + Ok(()) +} + +pub fn decode_clifford_t(gate: &OwnedGateInstance) -> Result { + CliffordT::try_from_instance(gate)? + .ok_or_else(|| anyhow!("gate is not in the Clifford+T gateset")) +} + +pub fn gate_qubits(gate: &OwnedGateInstance) -> impl Iterator + '_ { + gate.qubit_operands().map(u64::from) +} diff --git a/examples/clifford_t_stack/src/interface.rs b/examples/clifford_t_stack/src/interface.rs new file mode 100644 index 00000000..d130fde3 --- /dev/null +++ b/examples/clifford_t_stack/src/interface.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use selene_core::gatewire::{GateSetSpec, Qubit}; +use selene_core::runtime::RuntimeInterface; + +use crate::gates::{CNOT, CliffordT, H, S, Sdg, T, Tdg, X}; + +pub struct CliffordTInterface<'a> { + runtime: &'a mut dyn RuntimeInterface, +} + +impl<'a> CliffordTInterface<'a> { + pub fn new(runtime: &'a mut dyn RuntimeInterface) -> Self { + Self { runtime } + } + + fn emit(&mut self, gate: CliffordT) -> Result<()> { + self.runtime.gate(&gate.to_instance()) + } + + pub fn h(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::H(H { q0: Qubit(q0) })) + } + + pub fn s(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::S(S { q0: Qubit(q0) })) + } + + pub fn sdg(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::Sdg(Sdg { q0: Qubit(q0) })) + } + + pub fn t(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::T(T { q0: Qubit(q0) })) + } + + pub fn tdg(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::Tdg(Tdg { q0: Qubit(q0) })) + } + + pub fn x(&mut self, q0: u32) -> Result<()> { + self.emit(CliffordT::X(X { q0: Qubit(q0) })) + } + + pub fn cnot(&mut self, control: u32, target: u32) -> Result<()> { + self.emit(CliffordT::CNOT(CNOT { + control: Qubit(control), + target: Qubit(target), + })) + } + + pub fn measure(&mut self, q0: u64) -> Result { + self.runtime.measure(q0) + } +} diff --git a/examples/clifford_t_stack/src/lib.rs b/examples/clifford_t_stack/src/lib.rs new file mode 100644 index 00000000..644b1175 --- /dev/null +++ b/examples/clifford_t_stack/src/lib.rs @@ -0,0 +1,22 @@ +//! A complete custom-gateset Selene example. +//! +//! This crate is intentionally small but real: it defines a Clifford+T gateset, +//! implements plugins that negotiate that gateset, and exports plugin +//! descriptors for the three native plugin types. The companion Python package +//! demonstrates the full `selene_sim.build(...).run_shots(...)` workflow. + +pub mod error_model; +pub mod gates; +pub mod interface; +pub mod runtime; +pub mod simulator; + +pub use error_model::{CliffordTErrorModel, CliffordTErrorModelFactory}; +pub use gates::{CNOT, CliffordT, H, S, Sdg, T, Tdg, X}; +pub use gates::{clifford_t_gateset, decode_clifford_t, gateset_names, require_clifford_t}; +pub use interface::CliffordTInterface; +pub use runtime::{CliffordTRuntime, CliffordTRuntimeFactory}; +pub use simulator::{CliffordTTraceSimulator, TraceEntry}; + +#[cfg(test)] +mod tests; diff --git a/examples/clifford_t_stack/src/runtime.rs b/examples/clifford_t_stack/src/runtime.rs new file mode 100644 index 00000000..43c5fc8c --- /dev/null +++ b/examples/clifford_t_stack/src/runtime.rs @@ -0,0 +1,230 @@ +use std::collections::VecDeque; + +use anyhow::{Result, bail}; +use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; +use selene_core::runtime::{ + BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory, +}; +use selene_core::utils::MetricValue; + +use crate::gates::{clifford_t_gateset, decode_clifford_t, gate_qubits, require_clifford_t}; + +#[derive(Clone, Debug, PartialEq)] +enum QueuedOperation { + Gate(OwnedGateInstance), + Measure { qubit_id: u64, result_id: u64 }, + Reset { qubit_id: u64 }, +} + +#[derive(Debug)] +pub struct CliffordTRuntime { + n_qubits: u64, + allocated: Vec, + queue: VecDeque, + ready: usize, + next_result: u64, + results: Vec>, + start: selene_core::time::Instant, +} + +impl CliffordTRuntime { + pub fn new(n_qubits: u64, start: selene_core::time::Instant) -> Self { + Self { + n_qubits, + allocated: vec![false; n_qubits as usize], + queue: VecDeque::new(), + ready: 0, + next_result: 0, + results: Vec::new(), + start, + } + } + + fn check_qubit(&self, q: u64) -> Result<()> { + if q >= self.n_qubits { + bail!("qubit {q} is out of bounds"); + } + if !self.allocated[q as usize] { + bail!("qubit {q} is not allocated"); + } + Ok(()) + } + + fn operation_from_queued(queued: QueuedOperation) -> Result { + match queued { + QueuedOperation::Gate(gate) => Operation::from_gate_instance(gate), + QueuedOperation::Measure { + qubit_id, + result_id, + } => Ok(Operation::Measure { + qubit_id, + result_id, + }), + QueuedOperation::Reset { qubit_id } => Ok(Operation::Reset { qubit_id }), + } + } +} + +impl RuntimeInterface for CliffordTRuntime { + fn exit(&mut self) -> Result<()> { + Ok(()) + } + + fn get_next_operations(&mut self) -> Result> { + if self.ready == 0 { + return Ok(None); + } + self.ready -= 1; + let Some(queued) = self.queue.pop_front() else { + return Ok(None); + }; + Ok(Some(BatchOperation::runtime( + vec![Self::operation_from_queued(queued)?], + self.start, + Default::default(), + ))) + } + + fn shot_start(&mut self, _shot_id: u64, _seed: u64) -> Result<()> { + self.queue.clear(); + self.ready = 0; + self.next_result = 0; + self.results.clear(); + self.allocated.fill(false); + Ok(()) + } + + fn shot_end(&mut self) -> Result<()> { + Ok(()) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + require_clifford_t(gateset)?; + Ok(clifford_t_gateset()) + } + + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + decode_clifford_t(gate)?; + for q in gate_qubits(gate) { + self.check_qubit(q)?; + } + self.queue.push_back(QueuedOperation::Gate(gate.clone())); + Ok(()) + } + + fn qalloc(&mut self) -> Result { + for (index, allocated) in self.allocated.iter_mut().enumerate() { + if !*allocated { + *allocated = true; + return Ok(index as u64); + } + } + Ok(u64::MAX) + } + + fn qfree(&mut self, qubit_id: u64) -> Result<()> { + self.check_qubit(qubit_id)?; + self.allocated[qubit_id as usize] = false; + Ok(()) + } + + fn measure(&mut self, qubit_id: u64) -> Result { + self.check_qubit(qubit_id)?; + let result_id = self.next_result; + self.next_result += 1; + self.results.push(None); + self.queue.push_back(QueuedOperation::Measure { + qubit_id, + result_id, + }); + Ok(result_id) + } + + fn measure_leaked(&mut self, qubit_id: u64) -> Result { + self.measure(qubit_id) + } + + fn reset(&mut self, qubit_id: u64) -> Result<()> { + self.check_qubit(qubit_id)?; + self.queue.push_back(QueuedOperation::Reset { qubit_id }); + Ok(()) + } + + fn force_result(&mut self, result_id: u64) -> Result<()> { + if result_id as usize >= self.results.len() { + bail!("unknown result id {result_id}"); + } + self.ready = self.queue.len(); + Ok(()) + } + + fn get_bool_result(&mut self, result_id: u64) -> Result> { + Ok(self + .results + .get(result_id as usize) + .and_then(|value| value.map(|value| value != 0))) + } + + fn get_u64_result(&mut self, result_id: u64) -> Result> { + Ok(self + .results + .get(result_id as usize) + .and_then(|value| *value)) + } + + fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { + let Some(slot) = self.results.get_mut(result_id as usize) else { + bail!("unknown result id {result_id}"); + }; + *slot = Some(result.into()); + Ok(()) + } + + fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { + let Some(slot) = self.results.get_mut(result_id as usize) else { + bail!("unknown result id {result_id}"); + }; + *slot = Some(result); + Ok(()) + } + + fn increment_future_refcount(&mut self, _future: u64) -> Result<()> { + Ok(()) + } + + fn decrement_future_refcount(&mut self, _future: u64) -> Result<()> { + Ok(()) + } + + fn get_metric(&mut self, _nth_metric: u8) -> Result> { + Ok(None) + } + + fn local_barrier(&mut self, _qubits: &[u64], _sleep_ns: u64) -> Result<()> { + self.ready = self.queue.len(); + Ok(()) + } + + fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { + self.ready = self.queue.len(); + Ok(()) + } +} + +#[derive(Default)] +pub struct CliffordTRuntimeFactory; + +impl RuntimeInterfaceFactory for CliffordTRuntimeFactory { + type Interface = CliffordTRuntime; + + fn init( + self: std::sync::Arc, + n_qubits: u64, + start: selene_core::time::Instant, + _args: &[impl AsRef], + ) -> Result> { + Ok(Box::new(CliffordTRuntime::new(n_qubits, start))) + } +} + +selene_core::export_runtime_plugin!(crate::CliffordTRuntimeFactory); diff --git a/examples/clifford_t_stack/src/simulator.rs b/examples/clifford_t_stack/src/simulator.rs new file mode 100644 index 00000000..4bc658fd --- /dev/null +++ b/examples/clifford_t_stack/src/simulator.rs @@ -0,0 +1,182 @@ +use anyhow::{Result, bail}; +use selene_core::error_model::BatchResult; +use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; +use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::simulator::SimulatorInterface; +use selene_core::utils::MetricValue; + +use crate::gates::{CliffordT, decode_clifford_t, require_clifford_t}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraceEntry(pub String); + +#[derive(Debug)] +pub struct CliffordTTraceSimulator { + n_qubits: u64, + classical_state: Vec, + trace: Vec, + gates_seen: u64, + measurements: u64, +} + +impl CliffordTTraceSimulator { + pub fn new(n_qubits: u64) -> Self { + Self { + n_qubits, + classical_state: vec![false; n_qubits as usize], + trace: Vec::new(), + gates_seen: 0, + measurements: 0, + } + } + + pub fn trace(&self) -> &[TraceEntry] { + &self.trace + } + + pub fn gates_seen(&self) -> u64 { + self.gates_seen + } + + pub fn measurements(&self) -> u64 { + self.measurements + } + + fn check_qubit(&self, q: u64) -> Result<()> { + if q >= self.n_qubits { + bail!("qubit {q} is out of bounds"); + } + Ok(()) + } + + fn apply_gate(&mut self, gate: OwnedGateInstance) -> Result<()> { + match decode_clifford_t(&gate)? { + CliffordT::H(gate) => { + self.check_qubit(gate.q0.0.into())?; + self.trace.push(TraceEntry(format!("H q{}", gate.q0.0))); + } + CliffordT::S(gate) => { + self.check_qubit(gate.q0.0.into())?; + self.trace.push(TraceEntry(format!("S q{}", gate.q0.0))); + } + CliffordT::Sdg(gate) => { + self.check_qubit(gate.q0.0.into())?; + self.trace.push(TraceEntry(format!("Sdg q{}", gate.q0.0))); + } + CliffordT::T(gate) => { + self.check_qubit(gate.q0.0.into())?; + self.trace.push(TraceEntry(format!("T q{}", gate.q0.0))); + } + CliffordT::Tdg(gate) => { + self.check_qubit(gate.q0.0.into())?; + self.trace.push(TraceEntry(format!("Tdg q{}", gate.q0.0))); + } + CliffordT::X(gate) => { + let q = u64::from(gate.q0.0); + self.check_qubit(q)?; + self.classical_state[q as usize] = !self.classical_state[q as usize]; + self.trace.push(TraceEntry(format!("X q{}", gate.q0.0))); + } + CliffordT::CNOT(gate) => { + let control = u64::from(gate.control.0); + let target = u64::from(gate.target.0); + self.check_qubit(control)?; + self.check_qubit(target)?; + if self.classical_state[control as usize] { + self.classical_state[target as usize] = !self.classical_state[target as usize]; + } + self.trace.push(TraceEntry(format!( + "CNOT q{} q{}", + gate.control.0, gate.target.0 + ))); + } + } + self.gates_seen += 1; + Ok(()) + } +} + +impl SimulatorInterface for CliffordTTraceSimulator { + fn exit(&mut self) -> Result<()> { + Ok(()) + } + + fn shot_start(&mut self, _shot_id: u64, _seed: u64) -> Result<()> { + self.classical_state.fill(false); + self.trace.clear(); + self.gates_seen = 0; + self.measurements = 0; + Ok(()) + } + + fn shot_end(&mut self) -> Result<()> { + Ok(()) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + require_clifford_t(gateset)?; + Ok(gateset.clone()) + } + + fn handle_operations(&mut self, operations: BatchOperation) -> Result { + let mut results = BatchResult::default(); + for operation in operations { + match operation { + Operation::Gate { gate } => self.apply_gate(gate)?, + Operation::Measure { + qubit_id, + result_id, + } => { + self.check_qubit(qubit_id)?; + self.measurements += 1; + let value = self.classical_state[qubit_id as usize]; + self.trace + .push(TraceEntry(format!("MEASURE q{qubit_id} -> {value}"))); + results.set_bool_result(result_id, value); + } + Operation::MeasureLeaked { + qubit_id, + result_id, + } => { + self.check_qubit(qubit_id)?; + self.measurements += 1; + let value = u64::from(self.classical_state[qubit_id as usize]); + self.trace + .push(TraceEntry(format!("MEASURE_LEAKED q{qubit_id} -> {value}"))); + results.set_u64_result(result_id, value); + } + Operation::Reset { qubit_id } => { + self.check_qubit(qubit_id)?; + self.classical_state[qubit_id as usize] = false; + self.trace.push(TraceEntry(format!("RESET q{qubit_id}"))); + } + Operation::Custom { .. } => {} + _ => {} + } + } + Ok(results) + } + + fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { + self.check_qubit(qubit)?; + if self.classical_state[qubit as usize] == target_value { + Ok(()) + } else { + bail!("postselection failed for qubit {qubit}"); + } + } + + fn get_metric(&mut self, nth_metric: u8) -> Result> { + match nth_metric { + 0 => Ok(Some(( + "gates_seen".to_string(), + MetricValue::U64(self.gates_seen), + ))), + 1 => Ok(Some(( + "measurements".to_string(), + MetricValue::U64(self.measurements), + ))), + _ => Ok(None), + } + } +} diff --git a/examples/clifford_t_stack/src/tests.rs b/examples/clifford_t_stack/src/tests.rs new file mode 100644 index 00000000..daa7f5e3 --- /dev/null +++ b/examples/clifford_t_stack/src/tests.rs @@ -0,0 +1,91 @@ +use selene_core::error_model::ErrorModelInterface; +use selene_core::runtime::RuntimeInterface; +use selene_core::simulator::SimulatorInterface; + +use crate::{ + CliffordTErrorModel, CliffordTInterface, CliffordTRuntime, CliffordTTraceSimulator, + clifford_t_gateset, gateset_names, +}; + +#[test] +fn gateset_negotiation_is_explicit_at_each_layer() { + let interface_gateset = clifford_t_gateset(); + let mut runtime = CliffordTRuntime::new(2, selene_core::time::Instant::from(0)); + let runtime_gateset = runtime.negotiate_gateset(&interface_gateset).unwrap(); + let mut error_model = CliffordTErrorModel::deterministic(); + let error_model_gateset = error_model.negotiate_gateset(&runtime_gateset).unwrap(); + let mut simulator = CliffordTTraceSimulator::new(2); + let simulator_gateset = simulator.negotiate_gateset(&error_model_gateset).unwrap(); + + let names = vec!["H", "S", "Sdg", "T", "Tdg", "X", "CNOT"]; + assert_eq!(gateset_names(&interface_gateset), names); + assert_eq!(gateset_names(&runtime_gateset), names); + assert_eq!(gateset_names(&error_model_gateset), names); + assert_eq!(gateset_names(&simulator_gateset), names); +} + +#[test] +fn interface_runtime_error_model_and_simulator_work_together() { + let mut runtime = CliffordTRuntime::new(2, selene_core::time::Instant::from(0)); + let mut error_model = CliffordTErrorModel::deterministic(); + let mut simulator = CliffordTTraceSimulator::new(2); + + let runtime_gateset = runtime.negotiate_gateset(&clifford_t_gateset()).unwrap(); + let error_model_gateset = error_model.negotiate_gateset(&runtime_gateset).unwrap(); + simulator.negotiate_gateset(&error_model_gateset).unwrap(); + + runtime.shot_start(0, 0).unwrap(); + error_model.shot_start(0, 0).unwrap(); + simulator.shot_start(0, 0).unwrap(); + + assert_eq!(runtime.qalloc().unwrap(), 0); + assert_eq!(runtime.qalloc().unwrap(), 1); + let (m0, m1) = { + let mut interface = CliffordTInterface::new(&mut runtime); + interface.h(0).unwrap(); + interface.t(0).unwrap(); + interface.cnot(0, 1).unwrap(); + let m0 = interface.measure(0).unwrap(); + let m1 = interface.measure(1).unwrap(); + (m0, m1) + }; + + runtime.global_barrier(0).unwrap(); + while let Some(batch) = runtime.get_next_operations().unwrap() { + let results = error_model + .handle_operations(batch, &mut simulator) + .unwrap(); + for result in results.bool_results { + runtime + .set_bool_result(result.result_id, result.value) + .unwrap(); + } + } + + assert_eq!(runtime.get_bool_result(m0).unwrap(), Some(false)); + assert_eq!(runtime.get_bool_result(m1).unwrap(), Some(false)); + + simulator.shot_end().unwrap(); + error_model.shot_end().unwrap(); + runtime.shot_end().unwrap(); + + assert_eq!( + simulator + .trace() + .iter() + .map(|entry| entry.0.as_str()) + .collect::>(), + vec![ + "H q0", + "X q0", + "T q0", + "X q0", + "CNOT q0 q1", + "MEASURE q0 -> false", + "MEASURE q1 -> false", + ] + ); + assert_eq!(error_model.injected_x(), 2); + assert_eq!(simulator.gates_seen(), 5); + assert_eq!(simulator.measurements(), 2); +} diff --git a/examples/clifford_t_stack/uv.lock b/examples/clifford_t_stack/uv.lock new file mode 100644 index 00000000..f040445f --- /dev/null +++ b/examples/clifford_t_stack/uv.lock @@ -0,0 +1,1025 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version >= '3.12' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and platform_machine != 'x86_64') or (python_full_version < '3.11' and sys_platform != 'darwin')", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "blake3" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/6a/4cc5a9dd40fd8a6d283fd3761e5f59c490109571ef8e3c73245417e5a305/blake3-1.0.9.tar.gz", hash = "sha256:5fa374fa5070ca084368776c19b420157eb0f2d3f091343d6bc59189929d62e2", size = 116872, upload-time = "2026-06-22T18:02:25.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/2f/5398493cef29d9f216be1ff74a303e809e4958a633a44545035a98af4f60/blake3-1.0.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:38e61d3b0386af16b3c03a18e0db82b626d63796274637a1fef855fd1c778d82", size = 346497, upload-time = "2026-06-22T17:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/8aeca9a40899258353a8f79ad164fba1184bc1554ca18607cab4671952f3/blake3-1.0.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9e1d0392624c2f9d049d786f0dc547ce818d2f2b356bcf1c4d74b6f9cc026b4", size = 335390, upload-time = "2026-06-22T17:59:59.162Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0a/74c67827a9cae097ccab7015018182da9cfec347c686a25ef33faf2f46a1/blake3-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8114fb2a1f6cba9cba5411d62cbcb283b2205b154d0076f20b77e22592eb2719", size = 378100, upload-time = "2026-06-22T18:00:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8e/cef564771169b6fb429d9c52652dd2da8c9bbadb63d2d66f232f8bf045de/blake3-1.0.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b985eb08db76550ec97444e03b10acd737baa03fd98aaf3b8455a1c644c8f5d6", size = 377559, upload-time = "2026-06-22T18:00:01.822Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2df756e410d18aba6fef6392b35b835c76412709739a2cde552d246afa4b/blake3-1.0.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a517f0460007edec3767595115c520ed1f157ddd0ed23dddbf6b9d8b0082afb6", size = 451544, upload-time = "2026-06-22T18:00:03.293Z" }, + { url = "https://files.pythonhosted.org/packages/88/69/44423d63e7c6d09000ce69784dd9fb45bda93237f1d2f611099f5ffe27c7/blake3-1.0.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dad0a8a716dd201860f8e82011a340e6bdd5ee37a8eb4357b48ac64c4e6de1c2", size = 492654, upload-time = "2026-06-22T18:00:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/a2/02/7ca45b504796a755bcd765e54f0c6762c16a1dac1adec3a03a45ae9c2f12/blake3-1.0.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bca166d0b01c00dcf2a936f790ed947bd9079b0a0a7df1b76746f201aa4f4ac4", size = 387295, upload-time = "2026-06-22T18:00:06.026Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e4/c8fa46a0e24cb877fbf28f839d8ceda39418259f677ec55d680ea433b62b/blake3-1.0.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6e5c7533c915a24d840ae4be787e9a6059be7e77944b005b3d967a0257a17d", size = 387632, upload-time = "2026-06-22T18:00:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b3/6315be017515868126e106f3dfe50223fbbb87bed67109bfbf883228f505/blake3-1.0.9-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:24acb1e6f31021fa08b7eb31433035facfcf0d82e964170d5eb85a30ce913ba9", size = 384740, upload-time = "2026-06-22T18:00:08.747Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e8/fe7e40384c0f7995fe8dca57428241768897533b9e17cbc367c1614ef82f/blake3-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:216977b1d592a60150cd5de64d5853dc6afb0eb522cb387723ae7f78f380d947", size = 553251, upload-time = "2026-06-22T18:00:10.192Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/e9ecb843308db2b5ca29d604589a15f50d13c20df792260053bf9f014de4/blake3-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6f2dd643166dfeb7cf4ad53eb2d801f944d247212d3481950b4d5b4a20551461", size = 595209, upload-time = "2026-06-22T18:00:11.644Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/201d43f8fee831693f34f7b384a65e41ab7720e6cd8d775cb57d9da69993/blake3-1.0.9-cp310-cp310-win32.whl", hash = "sha256:c755044ba7bec3d03dae44b968194112f0eb0e8c4523465f3dd9e1a87e178d89", size = 231157, upload-time = "2026-06-22T18:00:13.035Z" }, + { url = "https://files.pythonhosted.org/packages/f2/12/f23a64ba2ef270457345499f857628757fafd83f52274c1588e1b4a5b4c0/blake3-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:8cd10c6a421a7d3c81136658e52e9ef58bfcc1df04193466664eb24981784f4c", size = 220829, upload-time = "2026-06-22T18:00:14.298Z" }, + { url = "https://files.pythonhosted.org/packages/27/12/aa8d72228b6ff61c675bd6f55ab138a91d71499c8a707cc9fb2052f1d2b5/blake3-1.0.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f169519c7ef25ef2c446b05e2f08e7e59fae312d569f98a3134b38d4caf7abd4", size = 346253, upload-time = "2026-06-22T18:00:15.537Z" }, + { url = "https://files.pythonhosted.org/packages/72/3a/820d2f729dfe152d5ebde16390f808c762dce3f21fb764ab033803ff2b1a/blake3-1.0.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5e1f21b49492d01fa5a02084894c491ab9e7a1867fced107f7126c80d067c94", size = 335497, upload-time = "2026-06-22T18:00:16.942Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d6/d5462ec19a7f3d084fe327e08618fa107799ee708df04b3a2d620bd62816/blake3-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee96daaa850700fd342a811fa10a8780fd2e8464a71b83a1779c7b6becd3dd5", size = 377621, upload-time = "2026-06-22T18:00:18.389Z" }, + { url = "https://files.pythonhosted.org/packages/92/98/dbc433f2a45be1b2344a6035d4212dfb6e6eb45046ad15103ead9c82d491/blake3-1.0.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09deb024cd75cb200e7f647cd038800e6edc8f190c8188e0c69ec1c2b920e125", size = 377495, upload-time = "2026-06-22T18:00:20.067Z" }, + { url = "https://files.pythonhosted.org/packages/e0/3d/c7a699fb60d8ed31f3f28e6aec7658d29e45ec89e7054906b3040ce3ee65/blake3-1.0.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c99afb0459c82dd13e456b6b68d45c4768b539ca998dacd3ed726f1e75e91dc", size = 451158, upload-time = "2026-06-22T18:00:21.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a1/0b1b0dbf2dd772483e372237bb65385602b019e24b67424b1fc9e5447837/blake3-1.0.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28528d1f29e6f3d45faf3482e1197e5e175730eef38bdc74e56ee11b68e0ad0d", size = 491988, upload-time = "2026-06-22T18:00:22.984Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d1/ed319477f6d263a4f6b7e9aa465b06be5235a854923edbc9ea09508b6638/blake3-1.0.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65c0c20014df687694af5ccf0cec3bdb194511da8ebd50c30b0fd55c83fa4fd5", size = 386848, upload-time = "2026-06-22T18:00:24.319Z" }, + { url = "https://files.pythonhosted.org/packages/80/3e/a4cfb269f3e0955598b415a7843c358c4f79e826e3c9118dc9fb1f101ee6/blake3-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:964b642631a3c8fe117b3439c8ae64a9a0981af9444e409656d1f1e464bfa125", size = 387842, upload-time = "2026-06-22T18:00:25.589Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/d4ee3d89eece42f86eb46663aa42702000516b7ffbc53f60b918efe95b57/blake3-1.0.9-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2fd000708662b04be211a22c1095b65fe399d7276e9f3bb2fd1ef8aacc545791", size = 384317, upload-time = "2026-06-22T18:00:26.891Z" }, + { url = "https://files.pythonhosted.org/packages/3a/aa/317106349d10de3b51332ad1e761f4864ebe887854396b75975304dcfbd1/blake3-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:82ecade6ac425fdfc39a4371d6d9232fd6e5c28748fd8d3489016ead17407014", size = 553005, upload-time = "2026-06-22T18:00:28.246Z" }, + { url = "https://files.pythonhosted.org/packages/39/cc/7fbce61a0b24bda1aac99da674bd74ac2b687b61db071c888ffdb30cb47a/blake3-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b4102ba86b86c992a931b4a88c58a632d6097461e14a1e63ebd2ecb98ff0898f", size = 595086, upload-time = "2026-06-22T18:00:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/6ddc7a8b582a0871f23d6db722f4950a8918096d5fa10f9f0f992c2aea39/blake3-1.0.9-cp311-cp311-win32.whl", hash = "sha256:2f4ce45da903f3d0a7e342fa70c7cce9c10cef6b529eadb4d6213be0ab0eaf84", size = 231230, upload-time = "2026-06-22T18:00:31.247Z" }, + { url = "https://files.pythonhosted.org/packages/23/68/ea698e6df48eeb417671544cfbb18c60f863cb689306cc52f19666dd98f8/blake3-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:d819457dccfd82fe34684ec99e36725f747bd5761a0e17f537387fb31d121193", size = 220622, upload-time = "2026-06-22T18:00:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d2/9bdf8345c70993aaef635398f52edfb915d6e8ad2c000c801204e387c456/blake3-1.0.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a70c20542d5e7960983a0ff32999049a2b0e5ef1f22dbbbdfb51cf04828a4156", size = 344587, upload-time = "2026-06-22T18:00:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/be8b1f7f85b12bb45a0fade6ca7bdbf83a507d23d0b6141ba29fe69c8cea/blake3-1.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:72cdecf088a9d25e6ec79948a578995649b0dbee407e7a46c543a9ecc0f6f281", size = 328864, upload-time = "2026-06-22T18:00:35.59Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/66580635d744c826671fd219938caffb16281a26f62c4f856695d4233677/blake3-1.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42fa57bf462285ef16400601b0fd32214c248ba92505bbb94b1221ab9af5a092", size = 373795, upload-time = "2026-06-22T18:00:36.887Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/b5b17d3004bb81a5732c0b176c812703d200ed8c652b3b7713b9633bbe10/blake3-1.0.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b25ccde5a64be070f20e5c7a81da70292db40b164b6c77588cbd6230856badbb", size = 374183, upload-time = "2026-06-22T18:00:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/3c/63/0d209c44b2041bbe130ced12a23c92dd995fbfe5bce7ee77fffea16f5cb0/blake3-1.0.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a800b87433955f37691b5f361ad29c7dd3ee089c9cd109adc5aea8e24bc4c1f", size = 446783, upload-time = "2026-06-22T18:00:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/c5/51/efd1f9b8a9d3e9a0e235f3ced99a738529a1019fe78b3988e29d9c2fbba6/blake3-1.0.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6879739e7904b9c42afbedbcc2e8c36cebe140fb3fc3f5c492993579cf5cd516", size = 487369, upload-time = "2026-06-22T18:00:40.875Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/a8dcaea9e0b26e419a540ca0cd6203c9fbb505e85b02b03c5a59bf9e6a45/blake3-1.0.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6edeb3d49a24c307995899b70dd47aa901d0e9ad51d2f8a79aba4f074f32d8c5", size = 383845, upload-time = "2026-06-22T18:00:42.251Z" }, + { url = "https://files.pythonhosted.org/packages/f6/10/e9907f5b86410d5071982aaf05d149ca4d4fd8acab7e77eebbc9a333c7b4/blake3-1.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd56a7a972c4185070f7042ccc20166927eec3c0f98b8405f375d007b604a0b", size = 383851, upload-time = "2026-06-22T18:00:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/c7863a185550706a9624f6aa7b6d46470aaed0bb46a827c5cda2a7d03151/blake3-1.0.9-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:a288664d08dee154cc496e06e62517fc9e655ecec12b0d7db538d244ac79edf1", size = 380067, upload-time = "2026-06-22T18:00:45.249Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/e7af679c719368b400c9ba9c3460072aac2ba077ddbd4bc806fef28cda03/blake3-1.0.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:91db52a809b68b5bebe7c413ddcd230e1f759398e7fa7a873104595a4fa648b6", size = 549471, upload-time = "2026-06-22T18:00:46.793Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/37c1dd3539b7bd9b6d2eef019802aacdb4a3d48ab484b140603bbf9c5b5a/blake3-1.0.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfaa671b07eb73883162ca940442193868358b0b904cfa266e4b74131ce966da", size = 591396, upload-time = "2026-06-22T18:00:48.122Z" }, + { url = "https://files.pythonhosted.org/packages/ae/55/4f0a23b72795292e74084834130900ea778c0583004519c86698dfffe1a5/blake3-1.0.9-cp312-cp312-win32.whl", hash = "sha256:ae47c3d5729ff89baa6ddf6de47fcfcc915985d39eb1bfcd6db653331f3c6fcc", size = 229271, upload-time = "2026-06-22T18:00:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/7db93e4689f0f145bcb954dc62936e5f5090548a9fa20c6bbebfaeaa648a/blake3-1.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:15566065ff90ab3da46ec0be1417406f00507af902b6fb0fbc6563e77f02fc42", size = 218220, upload-time = "2026-06-22T18:00:50.659Z" }, + { url = "https://files.pythonhosted.org/packages/41/1b/95b473d649f5322e69674622a307ffdb4f0b63adb0a0adcbc5cb8a8833c2/blake3-1.0.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:69ff5aebc7650954443aa701feff2028d7c7ea5b5e18ee265f15e2104e892328", size = 343869, upload-time = "2026-06-22T18:00:51.936Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9d/adec22c719d8451af1dc9e624bf5907008ef1e0afa51aa69fd1e8c91e60e/blake3-1.0.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0cdfeff65488089ef86f7587c76055ff72b28d28d10e427b547f5711477c376d", size = 328482, upload-time = "2026-06-22T18:00:53.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/aa/0a6967ff9a6ae182419a681aed54f7338b34a1f71372e90f787a2afa42e6/blake3-1.0.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:766f1555cbe614f14f399c2fbec0983568d20edb36837ba04040807eb9e1a609", size = 373616, upload-time = "2026-06-22T18:00:54.701Z" }, + { url = "https://files.pythonhosted.org/packages/1c/51/5d4e198bf3ae902c6697ad6ec77d7210736ad8f680980e8b648dcfcd09a0/blake3-1.0.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:128a62136c9a39c7cb9fdaa5fb38471f2418853da7f5a89f31495735d0ba6f2c", size = 374149, upload-time = "2026-06-22T18:00:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/7e/62/d3c7c364925b3f10828e5137376f3947f112c32188e899b42f09c2fde98a/blake3-1.0.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1ea0bf17b184b03444007646d902207d2b4d4f3e91a0cac3836552d83db74b9", size = 446151, upload-time = "2026-06-22T18:00:57.378Z" }, + { url = "https://files.pythonhosted.org/packages/b1/01/55b89389c5036c9d24b1d762d6265e91552e10b76a3c99fece3c4a7a4783/blake3-1.0.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73a48f7e9f0e047f51a445d9b0361ab1907bdc72b6857815a84dacd2e59556f8", size = 487256, upload-time = "2026-06-22T18:00:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7a/a21b52253292ad3e4df63ea4a01ce11d3ee8f4a8a8d80eaf0c7ce92a62bd/blake3-1.0.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b27550ada40f839aca64c66127940e4318bb6ef3e291890ef913017f6f637448", size = 383977, upload-time = "2026-06-22T18:01:00.192Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/fe7188201a29ee9b042616c786a98afd864d537ca96198e64c3fe4ff13a9/blake3-1.0.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c84dbc2a31eda88b55bbf5c5b711037bf0698eba0fd1faf06bdaf313c39048", size = 383615, upload-time = "2026-06-22T18:01:01.557Z" }, + { url = "https://files.pythonhosted.org/packages/22/08/f6a213b950e30fe9ef7d7fc061ec388e66ed62643570226882e6f7136ea3/blake3-1.0.9-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:dab59b324aa65c09e937d6c43de5de85ec9581627f4e79dcc9806d85b54a1c34", size = 380288, upload-time = "2026-06-22T18:01:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/b171e47c1b835483bcf1545ebc289458165f8dc0f5c7f74a9176d7e9af03/blake3-1.0.9-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:eca281fedcbe5c56655bd5a4176e6036eddbbe57df96114a03838fce08b1e0ca", size = 549122, upload-time = "2026-06-22T18:01:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d8/7bf71c2c85a0951e406971f151435e0751716907e3924c6c48a2d6dae0db/blake3-1.0.9-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3cbe7f190164896dc3908e920716ee66bc31d40f1a0fb603ed59ac53290fb9cf", size = 591183, upload-time = "2026-06-22T18:01:06.259Z" }, + { url = "https://files.pythonhosted.org/packages/20/85/34c3ea03cc90b2516628494ab3e0a98aec4ca8b04d037840ccd390e480ca/blake3-1.0.9-cp313-cp313-win32.whl", hash = "sha256:508ccaf8f9377cc47e6026c2897fdc37de61faeb1420dc023b6379cc2474eb65", size = 229053, upload-time = "2026-06-22T18:01:07.638Z" }, + { url = "https://files.pythonhosted.org/packages/db/2e/f09e8ed426f360aa2005206466ceab2f707486eb5d9db7051dbcbae056d1/blake3-1.0.9-cp313-cp313-win_amd64.whl", hash = "sha256:caded2806d2cbeed638c5e2517ed8b2a94165b3452fda35e72896142d22070e0", size = 217589, upload-time = "2026-06-22T18:01:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4b/b2dd7c25378a3b5de30ed908d38e6427bc4c644c0c12e8359361abd3a9ca/blake3-1.0.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ab0c030cf6644c30e786b0e785bde4e4596013ae9ea6ce9877e39d52383e25d7", size = 345406, upload-time = "2026-06-22T18:01:10.311Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/c0dab2963ddf04a4a938363f61716f9b75de6d3a9bc4a89e78f0854d4d31/blake3-1.0.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83b4a2336105af3800f7e17ac4b943f293a3927a2d66a6308d50dba944a6953e", size = 330077, upload-time = "2026-06-22T18:01:11.926Z" }, + { url = "https://files.pythonhosted.org/packages/20/f1/d03950a86d105a6332a8c422cb87658a7d247e214f1ea8f29ed09ff04e00/blake3-1.0.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95fc3545f80901b0dcd0508d16bc40f15ae39556709fa6cf86675f742d4f3c9c", size = 375147, upload-time = "2026-06-22T18:01:13.198Z" }, + { url = "https://files.pythonhosted.org/packages/10/75/711b1842e0a90aaad6a1c9a9022e90aa16206ac1f224516118bc24482532/blake3-1.0.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1bd981dc318c05375c3160a99df493b7cc4c83fffa1a34d14b18a071b47b262b", size = 373711, upload-time = "2026-06-22T18:01:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a0/f512799d1d0c0b4718fa6f0e99ccbe108e98bac7bf82c200803a62b57876/blake3-1.0.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:689a7e4069de681d9c5d9445b8b6473ee880ad04d7960a6789c60bd788980250", size = 446993, upload-time = "2026-06-22T18:01:15.924Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/6636ae8a46fc3352694188f5a5a325567782bc88fd1823b0b67be2c92184/blake3-1.0.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8adb0b0032e53919ee95b3d4f911448d3268316c28cd7df232ff2a1e7c9a4ba4", size = 488478, upload-time = "2026-06-22T18:01:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c5/a2b3c086f7e37c9db6017dc2890a76ad2a729e4a554896e855e511811e6b/blake3-1.0.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32bd4521ec2d477627ad93eb70f9ac4d01e12d1489024159bcaeff79466332f6", size = 384900, upload-time = "2026-06-22T18:01:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b8/1298806dd6c464a6f807df24c9640ad3bf27ee54ff4de82b2b5a823a8aba/blake3-1.0.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65d77eb05331495485048f6804f53885b192b998acb7e6fe1487d941bf08435", size = 384333, upload-time = "2026-06-22T18:01:20.35Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cc/0c29d9404155adfd6db716e9765d36ea6cbed287060759f5d764f0d9d99e/blake3-1.0.9-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ca7dfe8fb197ff8a3f5c915424183ccd52a99e8afb12680f51b2e1f4c9c6c97f", size = 381142, upload-time = "2026-06-22T18:01:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/d6/91/9af20d563f0ced71e08a60fc0ee534146da4e265710ed6792d5d799f4c0f/blake3-1.0.9-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f5c9d57f0dcb92243b6ae575c3065793edc9df9008d0ebd98d8245cdeb7c3f84", size = 550587, upload-time = "2026-06-22T18:01:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fa/06f46fc0aa486b799d776f9a80ed0b3605e2be1570cf48007860948aa5d9/blake3-1.0.9-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:172d44245a19dfec08ab771c1b7a506b97783163cdc65f559fe020007e403c99", size = 591888, upload-time = "2026-06-22T18:01:24.805Z" }, + { url = "https://files.pythonhosted.org/packages/50/68/d6198f4069a7c4a184ed854df45b82cc3e2d4b0be476b2a3ee65ad2344cf/blake3-1.0.9-cp314-cp314-win32.whl", hash = "sha256:249e5964fa9e768924bc7cc3d4efe75a425bb5dd3fb7671c3eda8eeddfa50591", size = 229410, upload-time = "2026-06-22T18:01:26.24Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/f29af72a8312b3827b50e55491f1bf9ae2347591de5c47365c5cbd2525a9/blake3-1.0.9-cp314-cp314-win_amd64.whl", hash = "sha256:0aba416bb2e3ef0c65e74d5eba21062483c714cd78e7e303c9d03c547fc7d015", size = 218526, upload-time = "2026-06-22T18:01:27.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/7e/d932fe437ccf656cfba77abc466fb3d1a0ce3c31df92e760d9e4c34932b4/blake3-1.0.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5b35abe24a66a7b3db423eb4f8668ed7be1a362aa9c0024ab6483ec0b2c16058", size = 345049, upload-time = "2026-06-22T18:01:29.228Z" }, + { url = "https://files.pythonhosted.org/packages/55/1e/d92fb284fcacf86f5d1083e29d0a8c834b60432786928915238d9760f514/blake3-1.0.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bbdff61e049297ef3180867ce1f079cea7e5b372fd76953c3183da5b8124206", size = 329367, upload-time = "2026-06-22T18:01:30.566Z" }, + { url = "https://files.pythonhosted.org/packages/9d/da/e25fa75d5bfea4527fc21024dde86a9376db798e469a084741968299f215/blake3-1.0.9-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a69fcedf06785bb81d4d3d39f95ee65dbaf2cb246e174cfc9ff64d027f7551", size = 374203, upload-time = "2026-06-22T18:01:31.998Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4d/0224916202b773dfdf08dcbe4ed1ad1018d4ddcd4df7a7e2978d28f89b74/blake3-1.0.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5d5bf0f68cd77108a942c95db98e960d9c3d5643b95172f783822ce22667759", size = 373713, upload-time = "2026-06-22T18:01:33.387Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e5/4ba968831b7afaec431c588c826cef76a96d6d6976188ed07d932072e673/blake3-1.0.9-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9767f16199b99aa022b61ff825ac4dbd39864bf637ae712605a2ce1f8b6a55e0", size = 446574, upload-time = "2026-06-22T18:01:34.687Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f5/08a9099c7177f282d2563abe4f7cc626c636642f7979cf58f2ab7ded2096/blake3-1.0.9-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865a8cfb2b3d7c0baf5267f2fa6816a3384e836cd1bd0caf359f406cb1e8fba", size = 487232, upload-time = "2026-06-22T18:01:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/9392bf1ebc81b5b09ce58b94613fa2d37308e825ff2dc7b54d00ee622c77/blake3-1.0.9-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42609e4adc4b2d7423137f2cb35135bca598b925c5af09d2bc0a2c368b25aeb1", size = 384751, upload-time = "2026-06-22T18:01:37.512Z" }, + { url = "https://files.pythonhosted.org/packages/84/fc/b6e9aef02ca14ef62fa47783b9eeeb5b2d3f73fdf698d8bb94c36f5dd69f/blake3-1.0.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f648fa425138452d1e585ac625c7aefddb946d9765906c4c12d564a1523cd8", size = 384546, upload-time = "2026-06-22T18:01:38.868Z" }, + { url = "https://files.pythonhosted.org/packages/ff/cb/452e92dba9402b36a953aa8b9b06253445ccce43dcd0bcf521c5e3c3e15d/blake3-1.0.9-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:9cef6d4d07a7de0c44f5ba17f6383d55276d9efc8d601f75113538fcaa35008b", size = 380596, upload-time = "2026-06-22T18:01:40.412Z" }, + { url = "https://files.pythonhosted.org/packages/b2/01/7a84a7e10c5d14e6ed8a4403bd7f64c1e01f8ebabea0d6fe5f093b894cbd/blake3-1.0.9-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:28404301de485e9546365d01b30f65eaa835520c4211d6ef61242975b6722b60", size = 550032, upload-time = "2026-06-22T18:01:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/58/7d/7aea0222f59cf84044ec52e2bfdaa0e3c355d221292b0ea1b722cf1edd6c/blake3-1.0.9-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8a99f896e7718050ed033a888245098aab3d6a5338f91cc9450c563b53f90ad5", size = 592244, upload-time = "2026-06-22T18:01:43.426Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e5/b44c230108745ff9c70c7bbafe22563772bc0c22322a8d15c10455f6ca02/blake3-1.0.9-cp314-cp314t-win32.whl", hash = "sha256:021309d760b390706fecf13498f9a25aa8f689bbb65a0896029b8fa223aae18b", size = 229481, upload-time = "2026-06-22T18:01:45.307Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/ac03f37dc9aeebf398d42089720648b3bc8438e733d3e522196c5d12ab39/blake3-1.0.9-cp314-cp314t-win_amd64.whl", hash = "sha256:5ea0c60dd9c1e3d05610606579e4bf80f562854c46ed55f9ee8545e18987a480", size = 217979, upload-time = "2026-06-22T18:01:46.629Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "hugr" +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphviz" }, + { name = "pydantic" }, + { name = "pydantic-extra-types" }, + { name = "semver" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/9b/8259806baa41ba6faa0d3ab822288579fd3cb2c9320f92eaf435c418fe6a/hugr-0.17.1.tar.gz", hash = "sha256:e5d779aca4cd41a9020668e1291151568b4a654c62dc58ad04ba2e87b621be52", size = 1051412, upload-time = "2026-06-10T16:51:39.656Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/e1e5794ea1d90eb9a10006e6e3c463b66978a5b637cc1e672022885df896/hugr-0.17.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:cf59376b5280bdfb230fcfaa4d65cffc6cc4b496e55fe03d399ac683cb15fc80", size = 3408541, upload-time = "2026-06-10T16:51:36.715Z" }, + { url = "https://files.pythonhosted.org/packages/4f/24/496c3840bb50a583f80150e90f916b045d397294806f46dba6ade6450cfb/hugr-0.17.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e76bb0ea768dd488ddf2d6a2efe33b033c90457173ecfe0418acbd63a3af76d", size = 3097049, upload-time = "2026-06-10T16:51:33.279Z" }, + { url = "https://files.pythonhosted.org/packages/65/04/c467d906428adf115962712cfa6c83b47932bf3c28ba9ac4fe2ddb82cd35/hugr-0.17.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8fbf4a7d61e2960f8137f8cd26d96d0c68f7cf35c0a68ff6618193f0adcbaed0", size = 3437170, upload-time = "2026-06-10T16:50:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c5/f0fb727e06356cb91dedae18aa28a152abbb00be5552baf676bb58b49957/hugr-0.17.1-cp310-abi3-manylinux_2_28_armv7l.whl", hash = "sha256:c038b4fbecfb2b2f41caee757c8060bac07b08542ece926ef36693fe6d95af6c", size = 3429725, upload-time = "2026-06-10T16:51:01.019Z" }, + { url = "https://files.pythonhosted.org/packages/52/5b/539eb797bd1bc23b813f55f6145f461974b7601aedc879a6fd6c4ac41e8f/hugr-0.17.1-cp310-abi3-manylinux_2_28_i686.whl", hash = "sha256:1959e930cced649c5a69778f5fd918228cd34ed124ddbbb1cf7618e1b4a2461f", size = 3706870, upload-time = "2026-06-10T16:51:10.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/cf/01e17f5bb0cf32f7caee3c2a21ea3f7d4b9da0a6d7ee1267d97a788b2385/hugr-0.17.1-cp310-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:1fd78dfecf57b11077c5575bd310374bfa769e6534f8bf4eb7236c2df1fab279", size = 3809864, upload-time = "2026-06-10T16:51:04.532Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3f/b87cbe60acd1f81e32a4eb2f5d90f653d1673060ae5b51a0f6e8a00d1683/hugr-0.17.1-cp310-abi3-manylinux_2_28_s390x.whl", hash = "sha256:20f5f18c0a71eacbd851b620552b7cfb4e48226fcf251074a545388ede11a4e2", size = 3819252, upload-time = "2026-06-10T16:51:07.693Z" }, + { url = "https://files.pythonhosted.org/packages/70/77/9c84fa949d833e7cbaab694351dc52ce7adab135e4c7575f423d6847ce12/hugr-0.17.1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b97fef66ac422b44eebb465827f8ded307593141a6b9fe874bd436efba94810e", size = 3655678, upload-time = "2026-06-10T16:51:13.802Z" }, + { url = "https://files.pythonhosted.org/packages/d9/61/753f734a36056be3033e9865af51075d39d604eda17e686effec8bee2c65/hugr-0.17.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3e39d6fb0596c436009c31857da34d3f294498f2bcc7c182bb815384118f0a1e", size = 3641324, upload-time = "2026-06-10T16:51:16.929Z" }, + { url = "https://files.pythonhosted.org/packages/e8/24/a93748069fe37ebf9a4bba892a6ecf93dae9a0ac0291cdd69a0711579211/hugr-0.17.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:a617126d8f959fa5b524dd6198c2f79e0d8919a5b700f9e9481e84acd6a9c1fc", size = 3717221, upload-time = "2026-06-10T16:51:20.101Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/47f1cb2eae5fac0a1e8a5342f10ad9bfc74c63626f588ae8ff35989a6db8/hugr-0.17.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:90b9ed0ac02820a43fd8de1be0c669e8e10e5d9b738f4835c0c9eb4d175ad167", size = 3804246, upload-time = "2026-06-10T16:51:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/9d/5f/773c166c026aea5253c490b9dd7d2f08e5cf2f8293e79f0c13ac7917bd85/hugr-0.17.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0cf70914ae9d733fa50ca51481503b16e1bcec40ee99d7076e678ccf9da8bd70", size = 3875736, upload-time = "2026-06-10T16:51:26.384Z" }, + { url = "https://files.pythonhosted.org/packages/12/b4/df392373f2f86682773ea46e203e22df440a2215ec074fa4d56d106473eb/hugr-0.17.1-cp310-abi3-win32.whl", hash = "sha256:658805cc8cbf468ac321696903079a2e143d0cf7e5a3969ed2740d3b76a72ed0", size = 3093333, upload-time = "2026-06-10T16:51:31.749Z" }, + { url = "https://files.pythonhosted.org/packages/1e/03/b68fdec2d02cfa7e088ea0cfb8ee6cacff4ac028f7ee9bd3609b41c072a0/hugr-0.17.1-cp310-abi3-win_amd64.whl", hash = "sha256:fbb2dc987ea5aed171b78da001f906b2659abc3ca802369a22fe73a7cc455459", size = 3296731, upload-time = "2026-06-10T16:51:30.143Z" }, + { url = "https://files.pythonhosted.org/packages/77/4c/41d75f547f7c4dade59cafc842a0adfdc3ac07ec97c7258b759ed71e5a90/hugr-0.17.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bbb208dcdb972f4947e4fda8825e9756615636d3d9cdebda111bac9019b858fb", size = 3404015, upload-time = "2026-06-10T16:51:38.227Z" }, + { url = "https://files.pythonhosted.org/packages/de/bf/48c99cd9299d9aad75da8ddb1f3682df29559feda57a96da896def93b163/hugr-0.17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:35d6e5e4a881248cebfb23ff032c5ee4652b3705f1b118c6d0e2f1c5be3020b5", size = 3095804, upload-time = "2026-06-10T16:51:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/11/00/b992996f6a3d3be36f8f7a8d4836619a9fdac73a0abc44d5be645b242af7/hugr-0.17.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3d439fb3d42d1f5d34494f5606362c59af041bfce36cdfa509122198832a7a75", size = 3439075, upload-time = "2026-06-10T16:50:58.888Z" }, + { url = "https://files.pythonhosted.org/packages/46/f8/65dc9413625c6d3267d91a4ec471c2ac7216cc6542189452c0375df933dc/hugr-0.17.1-cp314-cp314t-manylinux_2_28_armv7l.whl", hash = "sha256:abac467b73ad5bd5b143b17ff15028edd82b604bd0007ab75463c4e62c15b257", size = 3427932, upload-time = "2026-06-10T16:51:03.034Z" }, + { url = "https://files.pythonhosted.org/packages/b4/64/97fa352c28850f4ece0f0bce15fb7c78a842613fc0c8e52d1e571a3d4d0e/hugr-0.17.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:4658f55b73d8f2ddfecec013fb80b5d72539d71b396fe4c6ced23777b4872cb0", size = 3708262, upload-time = "2026-06-10T16:51:12.404Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c0/504e2921780d675c152f9100b5885ed2d3add70ce38c5e9153eb8f76a39e/hugr-0.17.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:b71fd44699db0767efa4e4079f8d0f3b86d6d5b697277bd274ba7bc9cc2860e8", size = 3808264, upload-time = "2026-06-10T16:51:06.114Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/341b2b16a0c8d08afbe59201b7d9110cd79074012efc067e69b3e6c8cf15/hugr-0.17.1-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:d0d43604171d2b7c17259ff522a4580b5779752b9e40c46221c35fc5a35af669", size = 3818378, upload-time = "2026-06-10T16:51:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/c4/62/9d19da58528e9bf6cb69650f2cb259f2e334d296248fddbf37fa1f0aca87/hugr-0.17.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:659333e1fd17dfcaf3c0944c0be9266a26121c7cc8c4855e62ec617d0f6e9dd0", size = 3656941, upload-time = "2026-06-10T16:51:15.506Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bb/a8c6628585c94ddc798c32d28576579407560cdf30883dcc5ec19db5c265/hugr-0.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d962d68cd90de0a6278f3c0d5a8fd518af6233a8310a87524f73786cb1211f64", size = 3643439, upload-time = "2026-06-10T16:51:18.618Z" }, + { url = "https://files.pythonhosted.org/packages/b7/9f/73cbca2aa536f1d6c2b23f051a7d61a28a9e714f76e90bfbbd2032b7ed9f/hugr-0.17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:5638a8072a9e6f09a03a3f721b4119e49c87c6f5f2df6a22c9c1dc8d75b7a33b", size = 3712437, upload-time = "2026-06-10T16:51:21.771Z" }, + { url = "https://files.pythonhosted.org/packages/84/f0/bbbc9ed0f38b24c170ece4fad358258154989306ba4e62eac168d531ec19/hugr-0.17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:016961f9e516de67be6a750d894fc36ff29b77747fb3bf4172e7d6ed977c5bad", size = 3806546, upload-time = "2026-06-10T16:51:24.841Z" }, + { url = "https://files.pythonhosted.org/packages/42/e9/4002b7b28c88b339097a4565c51209d76f48baf4d6408d5e34d066033d7b/hugr-0.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0e7df3a0bc3a3e6cbe81021a60f45107d7f8d03356daaded31e4c01f43202e0", size = 3878618, upload-time = "2026-06-10T16:51:28.767Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "lief" +version = "0.17.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/b9/6b27bff4676de0db4231ca585ed35bc6e13f5430c1bbf0ad0e9d2e9f552f/lief-0.17.6.tar.gz", hash = "sha256:c2164243f152e82c49b0ccd606155b758644f4b1ee221f0dbd4da055469a922f", size = 7171, upload-time = "2026-03-18T06:59:43.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/7b/c2d33e92dd42de61df217bcdba53d3472d90c972fdf713730b9bc0e5d7f6/lief-0.17.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:27cabac8f34885294b63e814952686453b59bce35ffb0c7d12e722adef389cd2", size = 2983532, upload-time = "2026-03-18T06:57:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/be/15/5325dad3d956741f2fd2be4ef031b4d0a1e9840cbce912c377f31dac7cf7/lief-0.17.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:c9cfcd463bbbe7cabb2fed9d22211ba1337775cf07f4d754da24c3c3f5c426d5", size = 3094902, upload-time = "2026-03-18T06:57:16.728Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4b/4a2cbc489aa7a310b388cfdfae857c2eb2e8a9ee1e56a9e68d9e52b8f098/lief-0.17.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:12db9cce6644b11b1cfa5c228574ae52d37121d1a8380266b2b3eb0721aa5b98", size = 3662265, upload-time = "2026-03-18T06:57:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/57/05/cc96b72a9e892b5d0da98b3f31ff176b3ce8bef26ee3e17ce09f64acaf6a/lief-0.17.6-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:f24fa49f0fe3f7d350aa87610fc5765890b18272c2aafaf720a10b2be0675154", size = 3468438, upload-time = "2026-03-18T06:57:19.978Z" }, + { url = "https://files.pythonhosted.org/packages/98/44/9641dd4bf6ed42eb53f5f28b47f7c81e92e020cd38a31f05b756b6a0865a/lief-0.17.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3a0678eafed01245d98187815714bdd602f285b8c9a05a4982ff9ddf82360717", size = 3392174, upload-time = "2026-03-18T06:57:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/97/b2/aca89b4d5bfef2baa44e04edf65bb463237e7d9f19054d8a19e2174c06a7/lief-0.17.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd673591c870bdedfd5e583c423fb67bbd99e00eb1852061f0dec6a918d4634", size = 3584834, upload-time = "2026-03-18T06:57:23.687Z" }, + { url = "https://files.pythonhosted.org/packages/15/1e/89f7facc0d064ed471dbb7bc2d64039eb0689488ac85df6cab66d107b27d/lief-0.17.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a6935a08a7b3285d0cf053d852fd739475fea15572b5559160a88d284987e995", size = 3925069, upload-time = "2026-03-18T06:57:25.664Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cb/c8f9e650623b6aa80c6e3633239a8cd1aa828639461f44b9c8c1d7f5d339/lief-0.17.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dbe04dda79f5d17c5b2d1b381997d93aa039f59c7c52b9fe48d398dda9cce8ea", size = 3695290, upload-time = "2026-03-18T06:57:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/ea/16/e17242c25c5056e54f28a44f831b352e19079af85b929e7e42166a1310d0/lief-0.17.6-cp310-cp310-win32.whl", hash = "sha256:17371938a85fcf64febb9eca77beb6537daa418fd3f86511e5ae402dc8bc2866", size = 3439299, upload-time = "2026-03-18T06:57:30.152Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/003aeed4385242bf1bd189b80c37d84839596a2b022388bd249f687b7b7e/lief-0.17.6-cp310-cp310-win_amd64.whl", hash = "sha256:45fd98016f5743f81f635628c2efc25becda80caa22cfc03bd002f359bcb7f71", size = 3627336, upload-time = "2026-03-18T06:57:31.791Z" }, + { url = "https://files.pythonhosted.org/packages/08/b9/ac73db72900eb87e9bd4f2b3b741a9af74bf632eb1b2823d72f922a76ee8/lief-0.17.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68cc28da07bd50a530590a56142c809a68035f29ace0b107046b0e0784650f50", size = 2989752, upload-time = "2026-03-18T06:57:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/f5/78/fcc1fa1b79610a54c71e4acd4347948f30f07519ba598ce8e8b55a680478/lief-0.17.6-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:ba6fb4f5926f3631e0de13218bedce0cc6b229c7eb84fae095f0985385c8beb1", size = 3095326, upload-time = "2026-03-18T06:57:35.147Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/c27d7411c920497429da9364d0a02f34ef5605143f4531e0a3175966b59a/lief-0.17.6-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6dce8652883b5b7fe06b5416807a8bc3cc4c1ab3e498512d242c38925e8a7d77", size = 3665830, upload-time = "2026-03-18T06:57:37.115Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c8/9fc1cbe95ec6b5c963152c2136ab59cc9baf30b54402a8dda5c8e53a8f34/lief-0.17.6-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:3ae021be2d65ea6f522884356c152ddf25d16674bab00240b04abe83c1cd5cb8", size = 3468663, upload-time = "2026-03-18T06:57:39.048Z" }, + { url = "https://files.pythonhosted.org/packages/96/1b/16128615044653349ec6322a5303f50fa2036b8c7fe4c6532191f6cb21c2/lief-0.17.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:94463d54bc5ecce9e3ae3855a084bacd5b473a23c1a080746bf54a0ed0339255", size = 3392279, upload-time = "2026-03-18T06:57:41.849Z" }, + { url = "https://files.pythonhosted.org/packages/fe/57/e6a834f792399a958bab70273899511ce3866b340b96950b948234f5e1e5/lief-0.17.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f471fa92430de76b84aa9d036d7fa7cd14256a81814fd3a055d156462fb5bb56", size = 3587501, upload-time = "2026-03-18T06:57:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/34/cc/67804187ccc810b5d0f0da9e96f8f6dc08f42c966d5f4ebd6646c236d2d8/lief-0.17.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e03969294b3be2728162fd3ce15f9f6d1571ba05f62abbb6aa9512c656e22c3", size = 3925847, upload-time = "2026-03-18T06:57:45.152Z" }, + { url = "https://files.pythonhosted.org/packages/b9/66/51af5df317631dc9fb974c3fe9061148519b8af492c2bfc6d60ffc6eff83/lief-0.17.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b690c719abf67632701ff69e14a22610eef79100b51abc5e7fbdc70a3d19504", size = 3695570, upload-time = "2026-03-18T06:57:47.075Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4a/0a65be6faf0a4426e3cf71ce49565da0c12a745d4bcb623309467803a26d/lief-0.17.6-cp311-cp311-win32.whl", hash = "sha256:fb8ea20af86b25b852d7fa4ba96cdaab2184b1a1529469786b2474dc2e1be446", size = 3439338, upload-time = "2026-03-18T06:57:48.664Z" }, + { url = "https://files.pythonhosted.org/packages/27/6a/5c79df6a36e249332a661dfb6d0a892af657d561d03f4838d491f3922875/lief-0.17.6-cp311-cp311-win_amd64.whl", hash = "sha256:347495918478606fc47d90a503791c308812f0a3ef5200b2c1e577e0bebd7c7e", size = 3627765, upload-time = "2026-03-18T06:57:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/a1ba3dc448cd560622dee015fdfd76c689eb150e9d60b59ebdf1908d074a/lief-0.17.6-cp311-cp311-win_arm64.whl", hash = "sha256:1b8339f385b64bf9da42ac8f5d5fc4c9f4235c4d9d804e472ffe8f1fddc830cb", size = 3458551, upload-time = "2026-03-18T06:57:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/1f/29/e7a0dabcb853867da70fda2b397012dd3d9ef4994ab7e8bd21f248bea64b/lief-0.17.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5a19642e42578fe0b701bd86b10dd7e86d69c35c67d25ac1433f72410a7c2bb", size = 2997018, upload-time = "2026-03-18T06:57:53.686Z" }, + { url = "https://files.pythonhosted.org/packages/97/1b/ad22e3e18b462ad3e3737cd07f01b88fbaf59c84cec66c665832a48441e2/lief-0.17.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:e1ded9ee9b5184b5753e4823343e3550a623d34f5407cb2f8d7918e17856d860", size = 3106901, upload-time = "2026-03-18T06:57:55.293Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/d80506bad2b23d7010ab7c23e81c6c9b099b2860136fd2ae724a2ab2b820/lief-0.17.6-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:e29552f52749249c9b05041d96d9156de20207d745916d599b4eb49ee7a8e1bf", size = 3666809, upload-time = "2026-03-18T06:57:57.105Z" }, + { url = "https://files.pythonhosted.org/packages/f4/99/db752fef6c3455c7612a46a834f37a955e329af60546f0e82510653144cd/lief-0.17.6-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:e186ac1ea8a5f4729c4b8d2b7f2fe6c55dbf1eddd8bc15fa4d19ed08dfa6cc54", size = 3473955, upload-time = "2026-03-18T06:57:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9f/77ca67789fda7fee355c8b4e6c58c0717fa4c5c3c5a4272777eb993df172/lief-0.17.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1df9b22f3851de7d0e86a8731ad07e47ca562ebe430605d90aecfcd6d20125d0", size = 3402099, upload-time = "2026-03-18T06:58:00.999Z" }, + { url = "https://files.pythonhosted.org/packages/82/43/859b6fbd1914d71e20047308719765956856a6f1f19bbbdac44311cd9eda/lief-0.17.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6484de5a7053c1b7022cb93f41450532f93daaf6b5ce6421c682b87fd2cd2122", size = 3589071, upload-time = "2026-03-18T06:58:02.685Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/7a042746ca0ac66f17b5dfe9ec3258cdf6bf84d0ba13a23315605038d52e/lief-0.17.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04b07e91213ce345febb4698efd310c6745f48190a1d7ce5dd0e7b306839362d", size = 3931684, upload-time = "2026-03-18T06:58:05.038Z" }, + { url = "https://files.pythonhosted.org/packages/21/18/dbe8944ce3a809885ff87afe474c1be3081f1deb264e84a79c5c05b4a6e3/lief-0.17.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5df55be6cd29c654b8a205846d67637955063ad0cfd83875451f339cf623b101", size = 3703617, upload-time = "2026-03-18T06:58:06.814Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/c83222badede13959735f3b253fb52d231327b5386d6fd2cf9e3d4b83933/lief-0.17.6-cp312-cp312-win32.whl", hash = "sha256:0aca84f35ec67854ffdb38a23b1848cb214df3e3f95eb7579bac3107e9f68cc8", size = 3446288, upload-time = "2026-03-18T06:58:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/17/d7/cd49540bb32fde031e612044d1345c381e79e5b0729b47ca6b9694a47071/lief-0.17.6-cp312-cp312-win_amd64.whl", hash = "sha256:5a34d651eb82e24a113f837b1a961d23e155be41d72bf39a37407854c6597a8b", size = 3639449, upload-time = "2026-03-18T06:58:10.821Z" }, + { url = "https://files.pythonhosted.org/packages/2e/96/c557b6757b72cfaea89a432e9d0a5ea669970ef9ae8086a17d1f73274156/lief-0.17.6-cp312-cp312-win_arm64.whl", hash = "sha256:234a422fe7158e755ac0acdd0bfdfd41f75392dad9dac147dd3b9c7a9f1a6811", size = 3461129, upload-time = "2026-03-18T06:58:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/9a/40/285c39e29bf7ecf5045f5aef1344419d23ae4c729671157406988570ce02/lief-0.17.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7384abed26200f8c6cb50ca9cedac70e7452e85fe72e82d4c5e9050c78eff0ae", size = 2990524, upload-time = "2026-03-18T06:58:14.172Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/afc7124787b4ae13d84135341d4721da9ed8f699a940bc7e2851e6d25c62/lief-0.17.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:7b7759b443745d0e5211d87723c0a84c4a74364ef6194cc8f8d315d98d117648", size = 3106720, upload-time = "2026-03-18T06:58:16.04Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/8aca6e7e70d68cdbd6aecf2adbb88bef1194d5657794c522a09cb0ddafd2/lief-0.17.6-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:3e59a64012a602772270aa1a930cff9c39cddca42f0ca5d7f1959f4dd951f38e", size = 3666563, upload-time = "2026-03-18T06:58:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/80/c1/5bdfd614a740f4bd22c20abb4c3b352f082fe75980ea73e6d4fccf356f37/lief-0.17.6-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:f764c77c848cf7478623e754099f50699d5e23b5bc4a34ce68cd20af7e0b5541", size = 3473626, upload-time = "2026-03-18T06:58:20.048Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f5/bf4be32af7b1892a8a1a2d3edb75a6a418548801548f39f3f879a6d3be73/lief-0.17.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:520e5f8a7b1e2487630e27639751d9fb13c94205fed72d358a87994e44a73815", size = 3402364, upload-time = "2026-03-18T06:58:21.871Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c5/84aa0c62636d0e0e9754cbab77ab9bb21b306e0be707475045b3d4dc5947/lief-0.17.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dbfe15d3d21d389857dac8cedc04f03f8ef98c5503e5e147a34480ecbf351826", size = 3589949, upload-time = "2026-03-18T06:58:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/e3/69/d5444ef2ec27adb777f4c08248a934dfdc2c65c1e4f66fbe10faf65fa01f/lief-0.17.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de5716279c82640359fe59137ec0572a1ed9859051c1d901de593d6e0e99d9c8", size = 3931688, upload-time = "2026-03-18T06:58:25.49Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/2b8083800a6bc9f157a40ed30b53e6ca81147af565de46623891a45336b0/lief-0.17.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ef618117ec33665697e3d1fe9c15fac8d6c42e2eeaf4aca9c31ea12fdb056c67", size = 3703589, upload-time = "2026-03-18T06:58:27.27Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ea/51e90c58b40bc316307f2081160ab6100b9e7d177fde3225b441085defd2/lief-0.17.6-cp313-cp313-win32.whl", hash = "sha256:2f669d5b4e63c6e66cac48e07d0f23436bf898ec9d0630016d23250e2eb43d28", size = 3446184, upload-time = "2026-03-18T06:58:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/6b/73/8ddc48c492f3295637a6aa13f07c67387d80c815ee779443938689dc59b4/lief-0.17.6-cp313-cp313-win_amd64.whl", hash = "sha256:51b6c5932d4f36d61fb17fe783d9e1bfba33ec1d72b3d07486c96e6f548781ff", size = 3639310, upload-time = "2026-03-18T06:58:31.162Z" }, + { url = "https://files.pythonhosted.org/packages/bf/bf/b7802b6578ca3a6506aaac6696ac1e8de500419fee3cd288184e82a8c2aa/lief-0.17.6-cp313-cp313-win_arm64.whl", hash = "sha256:6d4eb8adce400af52cc174ac5cbe40ab10b9df5824193975d12e2d4f85b298a3", size = 3461166, upload-time = "2026-03-18T06:58:32.975Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2f/1a0a116cdf4f0e9f1846e34676deed3b1275c432cde8b3aafd4ff9a77175/lief-0.17.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af39643ab79ae644d2063a2ef93de908e61a8f40e37b155683c477c1928e6c64", size = 2990571, upload-time = "2026-03-18T06:58:35.046Z" }, + { url = "https://files.pythonhosted.org/packages/76/bd/1bc1c1e364c06b74b9f16089f05622a29ed995fa8b5aba86e0a9fe5500a0/lief-0.17.6-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:c8129a70bc73e04fd9db4f49f386d4336a3a78ceef07c83ca74f9cf464c03c22", size = 3108044, upload-time = "2026-03-18T06:58:36.871Z" }, + { url = "https://files.pythonhosted.org/packages/80/bd/9fab6a9388fec0eec6fc975a1f49e2ff12dd4d75bfebc05d824a612c732c/lief-0.17.6-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:b5885e8a422066f3691b9707045b85d9728eaba621991def0b4e0044b0b0b063", size = 3671717, upload-time = "2026-03-18T06:58:39.111Z" }, + { url = "https://files.pythonhosted.org/packages/86/fe/470e32a95d0d95dccee560405cc217b9a739fa96a9536e08515ca3a8df44/lief-0.17.6-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:6324add89c366607a6d652553e4cac6309e952ca638c24f38a8b00331f064a50", size = 3473999, upload-time = "2026-03-18T06:58:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0f/1dcc499697f747a9b8f5274659774a1e6529aa180d59a297619842bf458f/lief-0.17.6-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:365bf48528339a0d9a5c993b0a54f5c3bb8fcd11ca85797c79f9ae6179777492", size = 3403400, upload-time = "2026-03-18T06:58:42.647Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3a/7e39b5dc0c393142a72e4272c6c812d01eb9e45411f3a9afb88157389aa4/lief-0.17.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3bd852c4d934d9c8357d6b9491db85e6722bc0076249f8b23a205a8912a85ed5", size = 3589670, upload-time = "2026-03-18T06:58:44.707Z" }, + { url = "https://files.pythonhosted.org/packages/de/7b/b0ffc4a08860f3499d915c4efab20f5abde6722d659c5391332d06957679/lief-0.17.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8561a156ccea562e200e5bde0db8070785e3194fcd0ddf9109c8470970978076", size = 3931468, upload-time = "2026-03-18T06:58:46.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/25/0992398b5ce911e29bf7d6a3e1724259358aebe5da543044d3b9ad9b4ffa/lief-0.17.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a74f792564a5e69915d08530618d79aa1fd8b5e7b72513fac765e1106c63f57a", size = 3705396, upload-time = "2026-03-18T06:58:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/8f/51/f79886a906eee3a5abc58dc30da1e6c22c41c868e0df446fb280ff2344cb/lief-0.17.6-cp314-cp314-win32.whl", hash = "sha256:503fd8df6425a6c0386df9ca6e4f4ce29d07d268f0620ee1d4059eb4d48c2562", size = 3446331, upload-time = "2026-03-18T06:58:50.52Z" }, + { url = "https://files.pythonhosted.org/packages/83/6f/d396dae3808a35699c4be74e356d3e05f58e150fc14b49c39f0bacb62e11/lief-0.17.6-cp314-cp314-win_amd64.whl", hash = "sha256:918ea953830ecf348e5a8d9cf0b1a178035d6d4032bf2a9aa1dc72483e06b3a1", size = 3638021, upload-time = "2026-03-18T06:58:52.275Z" }, + { url = "https://files.pythonhosted.org/packages/e3/4c/02df1befee243e4c14bf5740c391178ba4f7b4602ff08936da170341afe9/lief-0.17.6-cp314-cp314-win_arm64.whl", hash = "sha256:7dcefa6467f0f0d75413a10e7869e488344347f0c67eff5bc49ec216714f0674", size = 3462306, upload-time = "2026-03-18T06:58:54.937Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'darwin'", +] +sdist = { url = "https://files.pythonhosted.org/packages/99/8d/5baf1cef7f9c084fb35a8afbde88074f0d6a727bc63ef764fe0e7543ba40/llvmlite-0.45.1.tar.gz", hash = "sha256:09430bb9d0bb58fc45a45a57c7eae912850bedc095cd0810a57de109c69e1c32", size = 185600, upload-time = "2025-10-01T17:59:52.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/6d/585c84ddd9d2a539a3c3487792b3cf3f988e28ec4fa281bf8b0e055e1166/llvmlite-0.45.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1b1af0c910af0978aa55fa4f60bbb3e9f39b41e97c2a6d94d199897be62ba07a", size = 43043523, upload-time = "2025-10-01T18:02:58.621Z" }, + { url = "https://files.pythonhosted.org/packages/04/ad/9bdc87b2eb34642c1cfe6bcb4f5db64c21f91f26b010f263e7467e7536a3/llvmlite-0.45.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:60f92868d5d3af30b4239b50e1717cb4e4e54f6ac1c361a27903b318d0f07f42", size = 43043526, upload-time = "2025-10-01T18:03:15.051Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7c/82cbd5c656e8991bcc110c69d05913be2229302a92acb96109e166ae31fb/llvmlite-0.45.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:28e763aba92fe9c72296911e040231d486447c01d4f90027c8e893d89d49b20e", size = 43043524, upload-time = "2025-10-01T18:03:30.666Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e2/c185bb7e88514d5025f93c6c4092f6120c6cea8fe938974ec9860fb03bbb/llvmlite-0.45.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d9ea9e6f17569a4253515cc01dade70aba536476e3d750b2e18d81d7e670eb15", size = 43043524, upload-time = "2025-10-01T18:03:43.249Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version >= '3.12' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", + "(python_full_version < '3.11' and platform_machine != 'x86_64') or (python_full_version < '3.11' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/f5/a1bde3aa8c43524b0acaf3f72fb3d80a32dd29dbb42d7dc434f84584cdcc/llvmlite-0.47.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41270b0b1310717f717cf6f2a9c68d3c43bd7905c33f003825aebc361d0d1b17", size = 37232772, upload-time = "2026-03-31T18:28:12.198Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fb/76d88fc05ee1f9c1a6efe39eb493c4a727e5d1690412469017cd23bcb776/llvmlite-0.47.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f9d118bc1dd7623e0e65ca9ac485ec6dd543c3b77bc9928ddc45ebd34e1e30a7", size = 56275179, upload-time = "2026-03-31T18:28:15.725Z" }, + { url = "https://files.pythonhosted.org/packages/4d/08/29da7f36217abd56a0c389ef9a18bea47960826e691ced1a36c92c6ce93c/llvmlite-0.47.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea5cfb04a6ab5b18e46be72b41b015975ba5980c4ddb41f1975b83e19031063", size = 55128632, upload-time = "2026-03-31T18:28:19.946Z" }, + { url = "https://files.pythonhosted.org/packages/df/f8/5e12e9ed447d65f04acf6fcf2d79cded2355640b5131a46cee4c99a5949d/llvmlite-0.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:166b896a2262a2039d5fc52df5ee1659bd1ccd081183df7a2fba1b74702dd5ea", size = 38138402, upload-time = "2026-03-31T18:28:23.327Z" }, + { url = "https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07", size = 37232770, upload-time = "2026-03-31T18:28:26.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb", size = 56275177, upload-time = "2026-03-31T18:28:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/7e/51/48a53fedf01cb1f3f43ef200be17ebf83c8d9a04018d3783c1a226c342c2/llvmlite-0.47.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12a69d4bb05f402f30477e21eeabe81911e7c251cecb192bed82cd83c9db10d8", size = 55128631, upload-time = "2026-03-31T18:28:36.046Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327", size = 38138400, upload-time = "2026-03-31T18:28:40.076Z" }, + { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, + { url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" }, + { url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" }, + { url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" }, + { url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" }, + { url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" }, + { url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" }, + { url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version < '3.11' and platform_machine != 'x86_64') or (python_full_version < '3.11' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version >= '3.12' and sys_platform != 'darwin')", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version < '3.11' and platform_machine != 'x86_64') or (python_full_version < '3.11' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version == '3.11.*' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version >= '3.12' and sys_platform != 'darwin')", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, +] + +[[package]] +name = "pydot" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/35/b17cb89ff865484c6a20ef46bf9d95a5f07328292578de0b295f4a6beec2/pydot-4.0.1.tar.gz", hash = "sha256:c2148f681c4a33e08bf0e26a9e5f8e4099a82e0e2a068098f32ce86577364ad5", size = 162594, upload-time = "2025-06-17T20:09:56.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl", hash = "sha256:869c0efadd2708c0be1f916eb669f3d664ca684bc57ffb7ecc08e70d5e93fee6", size = 37087, upload-time = "2025-06-17T20:09:55.25Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "selene-core" +version = "0.3.0a1" +source = { editable = "../../selene-core" } +dependencies = [ + { name = "blake3" }, + { name = "hugr" }, + { name = "lief" }, + { name = "llvmlite", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, + { name = "llvmlite", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pydantic" }, + { name = "pydot" }, + { name = "pyyaml" }, + { name = "typing-extensions" }, + { name = "ziglang" }, +] + +[package.metadata] +requires-dist = [ + { name = "blake3", specifier = ">=1.0.0" }, + { name = "hugr", specifier = ">=0.13.0" }, + { name = "lief", specifier = ">=0.16.5" }, + { name = "llvmlite", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = "~=0.47" }, + { name = "llvmlite", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==0.45.1" }, + { name = "networkx", specifier = ">=2.6,<4" }, + { name = "pydantic", specifier = ">=2.12.5" }, + { name = "pydot", specifier = ">=4.0.0" }, + { name = "pyyaml", specifier = "~=6.0" }, + { name = "typing-extensions", specifier = ">=4" }, + { name = "ziglang", specifier = "~=0.13" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "qir-qis", specifier = "~=0.1.6" }] + +[[package]] +name = "selene-example-clifford-t-stack" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "selene-core" }, + { name = "selene-sim" }, +] + +[package.dev-dependencies] +test = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "selene-core", editable = "../../selene-core" }, + { name = "selene-sim", editable = "../../" }, +] + +[package.metadata.requires-dev] +test = [{ name = "pytest", specifier = ">=8.3.5" }] + +[[package]] +name = "selene-sim" +version = "0.3.0a1" +source = { editable = "../../" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pyyaml" }, + { name = "selene-core" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "numpy", specifier = ">=2.2.6" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "selene-core", editable = "../../selene-core" }, + { name = "tqdm", specifier = ">=4.67.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "hugr", specifier = "~=0.16.0" }, + { name = "mypy", specifier = ">=1.14.1" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-snapshot", specifier = "~=0.9.0" }, + { name = "pytket", specifier = "~=2.16" }, + { name = "qir-qis", specifier = "~=0.1.6" }, + { name = "ruff", specifier = ">=0.8.0" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20250402" }, +] +test = [ + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-snapshot", specifier = "~=0.9.0" }, + { name = "pytket", specifier = "~=2.16" }, + { name = "qir-qis", specifier = "~=0.1.6" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "ziglang" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/59/012f0c2800f7428b87bb16c5c78db7ef806efed274491998155955c02558/ziglang-0.16.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:b61e5413c49508d9d62e5dcea543e3af491594154d74a00ff52f84ed508260cc", size = 97252633, upload-time = "2026-04-15T03:55:02.488Z" }, + { url = "https://files.pythonhosted.org/packages/75/60/f924aa24b95a1ad347e845acc7ab6b5d062ae5b0b540d494654cd40d4e0b/ziglang-0.16.0-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:18e14f6b25678d7b7c65708c82501ee6090fe39ed5c783477d56267af1fa5629", size = 101228024, upload-time = "2026-04-15T03:55:12.268Z" }, + { url = "https://files.pythonhosted.org/packages/cd/aa/7966d158d768fb0e40bf5fef6e7ffe230dfee502382782ec39be1331ee4a/ziglang-0.16.0-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:8b83661247430aa335b3cbc29569084900412dde5633b02c80a6d2ff734de3d2", size = 101810276, upload-time = "2026-04-15T03:55:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ed/7b79023aa27ceb5d461ecf761181e7c33c57bbc1a6256a39535d1c7083d2/ziglang-0.16.0-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:9fcda73f62b851dd72a54b710ad40a209896db14cfb13649e62191243556342b", size = 97941847, upload-time = "2026-04-15T03:55:23.246Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ed/d6663a5e52c504944d578b9e0bfcb7857f292803bcd09ebe0d10fe2b293d/ziglang-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e27d409812b11e0fb89ed0200cf2e55b6464d43f9461553104e4a4f9a94a1fd5", size = 95008112, upload-time = "2026-04-15T03:55:32.025Z" }, + { url = "https://files.pythonhosted.org/packages/07/e4/beae76926e3070978d06fa156b243642d5f75ffd784f7cd64e783520e456/ziglang-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:7249779f0f916cfd2d1a9d54eb7600f3901384dc8dcbe1eea226bd32a8237fb5", size = 95860236, upload-time = "2026-04-15T03:55:37.069Z" }, + { url = "https://files.pythonhosted.org/packages/b6/fc/5cb1555281d2a998355ee7081f05f45d2f6a2789ca0fcb02015cfcb4b900/ziglang-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:0fd671c599f6961638cc302cf1f9461486696385311d4c0276171dcf7d2b67f5", size = 104100995, upload-time = "2026-04-15T03:55:42.594Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/e0138d89ec47da986ac08009ae8802af052c1e335163d983c074d9eaa1c3/ziglang-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x.whl", hash = "sha256:df0e6599e41d087c912b0d3d7cedef6c9cb1f0032465c8fc820e9986772d11e4", size = 103517876, upload-time = "2026-04-15T03:55:48.695Z" }, + { url = "https://files.pythonhosted.org/packages/fe/29/d281591fa0be47aefcb23ac379cdbff5b34a1fdaafa139a5f8703ca9559c/ziglang-0.16.0-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:d4bd8197344fffe276e1d6446e4ef7bc38637d821b4685fe96a936503d4b0619", size = 98388042, upload-time = "2026-04-15T03:55:54.114Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f0/10a5203071af21bd649a0f97e29f70b710d6ad97b1ac1995a0bb30f51a71/ziglang-0.16.0-py3-none-win32.whl", hash = "sha256:f978c12b5337cf418034964de00023b7ec5ec484383dc97c6a6585f4a9105840", size = 100655765, upload-time = "2026-04-15T03:55:59.7Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3c/baff40b3fc8ab4e83530246a52e8f3a5186c3606562712dfb58483b04f79/ziglang-0.16.0-py3-none-win_amd64.whl", hash = "sha256:089a16a4eb5a2f45151993342f8fabad24ff1a0723dc146642895c29208a3939", size = 98676957, upload-time = "2026-04-15T03:56:05.934Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0b/bff28a4acc437cb7da705bffafd81fafbbd37a23144363cecc72becaef93/ziglang-0.16.0-py3-none-win_arm64.whl", hash = "sha256:e44a5271f7b72f7980017bb615823ed52e765f96b6482d93a2fd338cd53101bf", size = 94347646, upload-time = "2026-04-15T03:56:11.85Z" }, +] diff --git a/justfile b/justfile index 2ec5c34a..58d62fac 100644 --- a/justfile +++ b/justfile @@ -51,6 +51,11 @@ generate-selene-core-headers: --crate selene-core \ --output selene-core/c/include/selene/runtime.h + cbindgen \ + --config selene-core/rust/gatewire/cbindgen.toml \ + --crate selene-core \ + --output selene-core/c/include/selene/gatewire.h + generate-headers: just generate-selene-core-headers just generate-selene-sim-headers @@ -83,4 +88,4 @@ build-ci: mkdir -p /tmp/ci-cache export CACHE_CARGO=true uv build --package selene-core --out-dir wheelhouse - cibuildwheel . + uvx cibuildwheel . diff --git a/pyproject.toml b/pyproject.toml index b4703860..40379f36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ ignore_missing_imports = true exclude = ["test_.*.py$", "^target/.*$", "^selene-core/.*$", "^scratch/.*$"] [tool.pytest.ini_options] -norecursedirs = ['*.egg', '.*', 'build', 'target', 'dist', 'venv'] +norecursedirs = ['*.egg', '.*', 'build', 'target', 'dist', 'venv', 'examples'] [tool.deptry] diff --git a/selene-core/Cargo.toml b/selene-core/Cargo.toml index f5909422..f153ee47 100644 --- a/selene-core/Cargo.toml +++ b/selene-core/Cargo.toml @@ -2,6 +2,12 @@ name = "selene-core" version = "0.3.0-alpha.1" edition = "2024" +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +license.workspace = true +description = "Core Rust APIs for Selene plugins, gatewire gatesets, and simulation infrastructure." +readme = "README.md" [lib] name = "selene_core" @@ -13,7 +19,12 @@ anyhow = "1.0" thiserror = "2.0" libloading = "0.8" delegate = "0.13" -derive_more = { version = "2.1", features = ["from", "into", "add", "add_assign"] } +derive_more = { version = "2.1", features = ["full"] } +bitflags = "2" +blake3 = "1" +indexmap = "2" +smallvec = "1" +static_assertions = "1" [lints.clippy] undocumented_unsafe_blocks = "allow" # TODO: add safety docs diff --git a/selene-core/c/include/selene/core_types.h b/selene-core/c/include/selene/core_types.h index ee862caf..fc2e399f 100644 --- a/selene-core/c/include/selene/core_types.h +++ b/selene-core/c/include/selene/core_types.h @@ -7,23 +7,4 @@ typedef struct Operation Operation; -typedef struct ErrorModelAPIVersion { - /** - * Reserved for future use, must be 0. - */ - uint8_t reserved; - /** - * Major version of the API. - */ - uint8_t major; - /** - * Minor version of the API. - */ - uint8_t minor; - /** - * Patch version of the API. - */ - uint8_t patch; -} ErrorModelAPIVersion; - typedef int32_t SeleneErrno; diff --git a/selene-core/c/include/selene/error_model.h b/selene-core/c/include/selene/error_model.h index 3a547523..248ebfbb 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -4,7 +4,7 @@ #include #include #include "selene/core_types.h" - +#define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL typedef struct SeleneErrorModelAPIVersion { /** @@ -29,7 +29,7 @@ typedef void *SeleneErrorModelInstance; /** * An instance is provided to `selene_runtime_get_next_operations`, which must - * pass that back to any function it calls in it's provided + * pass that back to any function it calls in its provided * [ErrorModelSetResultInterface]. */ typedef void *SeleneErrorModelSetResultInstance; @@ -77,22 +77,9 @@ typedef struct RuntimeGetOperationInterface { void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t); - void (*rzz_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t, - double); - void (*rxy_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - double, - double); - void (*rz_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - double); - void (*rpp_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t, - double, - double); + void (*gate_fn)(SeleneRuntimeGetOperationInstance, + const uint8_t*, + size_t); } RuntimeGetOperationInterface; typedef struct RuntimeGetOperationHandle { @@ -118,22 +105,6 @@ typedef struct SimulatorOperationInterface { uint64_t shot_id, uint64_t seed); SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance instance); - SeleneErrno (*rxy_fn)(SeleneSimulatorInstance instance, - uint64_t qubit, - double theta, - double phi); - SeleneErrno (*rz_fn)(SeleneSimulatorInstance instance, - uint64_t qubit, - double theta); - SeleneErrno (*rzz_fn)(SeleneSimulatorInstance instance, - uint64_t qubit1, - uint64_t qubit2, - double theta); - SeleneErrno (*rpp_fn)(SeleneSimulatorInstance instance, - uint64_t qubit1, - uint64_t qubit2, - double theta, - double phi); SeleneErrno (*measure_fn)(SeleneSimulatorInstance instance, uint64_t qubit); SeleneErrno (*postselect_fn)(SeleneSimulatorInstance instance, @@ -150,6 +121,15 @@ typedef struct SimulatorOperationInterface { const char *file, const uint64_t *qubits, uint64_t n_qubits); + SeleneErrno (*gate_fn)(SeleneSimulatorInstance instance, + const uint8_t *data, + size_t len); + SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance instance, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); } SimulatorOperationInterface; typedef struct SimulatorHandle { @@ -183,4 +163,10 @@ typedef struct SeleneErrorModelPluginDescriptorV1 { char *out_tag_str, uint8_t *out_datatype, uint64_t *out_data); + SeleneErrno (*negotiate_gateset_fn)(SeleneErrorModelInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); } SeleneErrorModelPluginDescriptorV1; diff --git a/selene-core/c/include/selene/gatewire.h b/selene-core/c/include/selene/gatewire.h new file mode 100644 index 00000000..ad0d7a75 --- /dev/null +++ b/selene-core/c/include/selene/gatewire.h @@ -0,0 +1,196 @@ +#ifndef SELENE_GATEWIRE_H +#define SELENE_GATEWIRE_H + +#include +#include +#include +#include +#include +#include +#include + +#define GW_OPERAND_KIND_QUBIT 1 + +#define GW_OPERAND_KIND_F64 2 + +#define GW_OPERAND_KIND_U64 3 + +#define GW_OPERAND_KIND_I64 4 + +#define GW_OPERAND_KIND_U8 5 + +#define GW_OPERAND_KIND_BOOL 6 + +typedef int32_t GwStatus; + +typedef struct { + uint8_t bytes[16]; +} GwSemanticId; + +typedef struct { + uint8_t _private[0]; +} GwGateSet; + +typedef struct { + size_t abi_size; + const char *name_ptr; + size_t name_len; + uint32_t kind; +} GwOperandDeclView; + +typedef struct { + size_t abi_size; + GwSemanticId semantic_id; + const char *name_ptr; + size_t name_len; + const GwOperandDeclView *operands_ptr; + size_t operands_len; + uint32_t version; +} GwGateDeclView; + +typedef struct { + size_t abi_size; + GwSemanticId semantic_id; + const char *name_ptr; + size_t name_len; + size_t operands_len; + uint32_t version; +} GwGateDeclInfo; + +typedef struct { + size_t abi_size; + const char *name_ptr; + size_t name_len; + uint32_t kind; +} GwOperandDeclInfo; + +typedef union { + uint32_t qubit; + double f64_value; + uint64_t u64_value; + int64_t i64_value; + uint8_t u8_value; + uint8_t bool_value; +} GwGateValueData; + +typedef struct { + size_t abi_size; + uint32_t kind; + GwGateValueData data; + const uint8_t *bytes_ptr; + size_t bytes_len; +} GwGateValue; + +typedef struct { + size_t abi_size; + GwSemanticId semantic_id; + const GwGateValue *values_ptr; + size_t values_len; +} GwGateInstanceView; + +typedef struct { + uint8_t _private[0]; +} GwDecodedGate; + +#define GW_STATUS_OK 0 + +#define GW_STATUS_NULL_POINTER 1 + +#define GW_STATUS_INVALID_ARGUMENT 2 + +#define GW_STATUS_DUPLICATE_GATE 3 + +#define GW_STATUS_NOT_FOUND 4 + +#define GW_STATUS_BUFFER_TOO_SMALL 5 + +#define GW_STATUS_DECODE_ERROR 6 + +#define GW_STATUS_VALIDATION_ERROR 7 + +#define GW_STATUS_PANIC 255 + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +const char *gw_status_message(GwStatus status); + +GwStatus gw_semantic_id_from_text(const char *ptr, size_t len, GwSemanticId *out); + +uint8_t gw_semantic_id_eq(GwSemanticId a, GwSemanticId b); + +GwSemanticId gw_builtin_rz_semantic_id(void); + +GwSemanticId gw_builtin_phased_x_semantic_id(void); + +GwSemanticId gw_builtin_zz_phase_semantic_id(void); + +GwSemanticId gw_builtin_phased_xx_semantic_id(void); + +GwStatus gw_gateset_new(GwGateSet **out); + +GwStatus gw_builtin_gateset_new(GwGateSet **out); + +void gw_gateset_free(GwGateSet *set); + +GwStatus gw_gateset_add_decl(GwGateSet *set, const GwGateDeclView *decl); + +GwStatus gw_gateset_add_builtin_rz(GwGateSet *set); + +GwStatus gw_gateset_add_builtin_phased_x(GwGateSet *set); + +GwStatus gw_gateset_add_builtin_zz_phase(GwGateSet *set); + +GwStatus gw_gateset_add_builtin_phased_xx(GwGateSet *set); + +GwStatus gw_gateset_len(const GwGateSet *set, size_t *out); + +GwStatus gw_gateset_contains(const GwGateSet *set, GwSemanticId id, uint8_t *out); + +GwStatus gw_gateset_decl_at(const GwGateSet *set, size_t index, GwGateDeclInfo *out); + +GwStatus gw_gateset_decl_operand_at(const GwGateSet *set, + size_t decl_index, + size_t operand_index, + GwOperandDeclInfo *out); + +GwStatus gw_gateset_serialized_len(const GwGateSet *set, size_t *out); + +GwStatus gw_gateset_serialize(const GwGateSet *set, + uint8_t *buffer, + size_t buffer_len, + size_t *written); + +GwStatus gw_gateset_deserialize(const uint8_t *data, size_t len, GwGateSet **out); + +GwStatus gw_gate_serialized_len(const GwGateInstanceView *view, size_t *out); + +GwStatus gw_gate_serialize(const GwGateInstanceView *view, + uint8_t *buffer, + size_t buffer_len, + size_t *written); + +GwStatus gw_gate_deserialize(const uint8_t *data, size_t len, GwDecodedGate **out); + +void gw_decoded_gate_free(GwDecodedGate *gate); + +GwStatus gw_decoded_gate_semantic_id(const GwDecodedGate *gate, GwSemanticId *out); + +GwStatus gw_decoded_gate_value_count(const GwDecodedGate *gate, size_t *out); + +GwStatus gw_decoded_gate_value_at(const GwDecodedGate *gate, size_t index, GwGateValue *out); + +GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); + +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, size_t qubit_index, uint32_t *out); + +GwStatus gw_gateset_validate_decoded(const GwGateSet *set, + const GwDecodedGate *gate, + uint8_t *out_matches); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* SELENE_GATEWIRE_H */ diff --git a/selene-core/c/include/selene/runtime.h b/selene-core/c/include/selene/runtime.h index ee78ddfc..3e68791c 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -4,26 +4,7 @@ #include #include #include "selene/core_types.h" - - -typedef struct ErrorModelAPIVersion { - /** - * Reserved for future use, must be 0. - */ - uint8_t reserved; - /** - * Major version of the API. - */ - uint8_t major; - /** - * Minor version of the API. - */ - uint8_t minor; - /** - * Patch version of the API. - */ - uint8_t patch; -} ErrorModelAPIVersion; +#define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL typedef struct SeleneRuntimeAPIVersion { /** @@ -67,22 +48,9 @@ typedef struct SeleneRuntimeGetOperationInterface { void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t); - void (*rzz_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t, - double); - void (*rxy_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - double, - double); - void (*rz_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - double); - void (*rpp_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t, - double, - double); + void (*gate_fn)(SeleneRuntimeGetOperationInstance, + const uint8_t*, + size_t); } SeleneRuntimeGetOperationInterface; typedef void *SeleneRuntimeExtractOperationInstance; @@ -136,22 +104,6 @@ typedef struct SeleneRuntimePluginDescriptorV1 { uint64_t sleep_ns); SeleneErrno (*global_barrier_fn)(RuntimeInstance handle, uint64_t sleep_ns); - SeleneErrno (*rxy_gate_fn)(RuntimeInstance handle, - uint64_t qubit, - double theta, - double phi); - SeleneErrno (*rzz_gate_fn)(RuntimeInstance handle, - uint64_t qubit0, - uint64_t qubit1, - double theta); - SeleneErrno (*rz_gate_fn)(RuntimeInstance handle, - uint64_t qubit, - double theta); - SeleneErrno (*rpp_gate_fn)(RuntimeInstance handle, - uint64_t qubit0, - uint64_t qubit1, - double theta, - double phi); SeleneErrno (*measure_fn)(RuntimeInstance handle, uint64_t qubit, uint64_t *result_id); @@ -185,4 +137,13 @@ typedef struct SeleneRuntimePluginDescriptorV1 { uint64_t *result); SeleneErrno (*simulate_delay_fn)(RuntimeInstance handle, uint64_t delay_ns); + SeleneErrno (*gate_fn)(RuntimeInstance handle, + const uint8_t *data, + size_t len); + SeleneErrno (*negotiate_gateset_fn)(RuntimeInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); } SeleneRuntimePluginDescriptorV1; diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index 78a70ff2..8af24992 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -4,26 +4,7 @@ #include #include #include "selene/core_types.h" - - -typedef struct ErrorModelAPIVersion { - /** - * Reserved for future use, must be 0. - */ - uint8_t reserved; - /** - * Major version of the API. - */ - uint8_t major; - /** - * Minor version of the API. - */ - uint8_t minor; - /** - * Patch version of the API. - */ - uint8_t patch; -} ErrorModelAPIVersion; +#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL typedef struct SeleneSimulatorAPIVersion { /** @@ -61,22 +42,6 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { uint64_t shot_id, uint64_t seed); SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance handle); - SeleneErrno (*rxy_fn)(SeleneSimulatorInstance handle, - uint64_t qubit, - double theta, - double phi); - SeleneErrno (*rz_fn)(SeleneSimulatorInstance handle, - uint64_t qubit0, - double theta); - SeleneErrno (*rzz_fn)(SeleneSimulatorInstance handle, - uint64_t qubit0, - uint64_t qubit1, - double theta); - SeleneErrno (*rpp_fn)(SeleneSimulatorInstance handle, - uint64_t qubit0, - uint64_t qubit1, - double theta, - double phi); SeleneErrno (*measure_fn)(SeleneSimulatorInstance handle, uint64_t qubit); SeleneErrno (*postselect_fn)(SeleneSimulatorInstance handle, @@ -93,4 +58,13 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { const char *file, const uint64_t *qubits, uint64_t n_qubits); + SeleneErrno (*gate_fn)(SeleneSimulatorInstance handle, + const uint8_t *data, + size_t len); + SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance handle, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); } SeleneSimulatorPluginDescriptorV1; diff --git a/selene-core/cbindgen/core_types.toml b/selene-core/cbindgen/core_types.toml index f0214426..8b259086 100644 --- a/selene-core/cbindgen/core_types.toml +++ b/selene-core/cbindgen/core_types.toml @@ -21,6 +21,64 @@ usize_is_size_t = true [export] include = ["Errno", "Operation"] +exclude = [ + "GwStatus", + "GW_STATUS_OK", + "GW_STATUS_NULL_POINTER", + "GW_STATUS_INVALID_ARGUMENT", + "GW_STATUS_DUPLICATE_GATE", + "GW_STATUS_NOT_FOUND", + "GW_STATUS_BUFFER_TOO_SMALL", + "GW_STATUS_DECODE_ERROR", + "GW_STATUS_VALIDATION_ERROR", + "GW_STATUS_PANIC", + "GW_OPERAND_KIND_QUBIT", + "GW_OPERAND_KIND_F64", + "GW_OPERAND_KIND_U64", + "GW_OPERAND_KIND_I64", + "GW_OPERAND_KIND_U8", + "GW_OPERAND_KIND_BOOL", + "GwSemanticId", + "GwGateSet", + "GwOperandDeclView", + "GwGateDeclView", + "GwGateDeclInfo", + "GwOperandDeclInfo", + "GwGateValueData", + "GwGateValue", + "GwGateInstanceView", + "GwDecodedGate", + "gw_status_message", + "gw_semantic_id_from_text", + "gw_semantic_id_eq", + "gw_builtin_rz_semantic_id", + "gw_builtin_phased_x_semantic_id", + "gw_builtin_zz_phase_semantic_id", + "gw_builtin_phased_xx_semantic_id", + "gw_gateset_new", + "gw_builtin_gateset_new", + "gw_gateset_free", + "gw_gateset_add_decl", + "gw_gateset_add_builtin_rz", + "gw_gateset_add_builtin_phased_x", + "gw_gateset_add_builtin_zz_phase", + "gw_gateset_add_builtin_phased_xx", + "gw_gateset_len", + "gw_gateset_contains", + "gw_gateset_decl_at", + "gw_gateset_decl_operand_at", + "gw_gateset_serialized_len", + "gw_gateset_serialize", + "gw_gateset_deserialize", + "gw_gate_serialized_len", + "gw_gate_serialize", + "gw_gate_deserialize", + "gw_decoded_gate_free", + "gw_decoded_gate_semantic_id", + "gw_decoded_gate_value_count", + "gw_decoded_gate_value_at", + "gw_gateset_validate_decoded", +] item_types = ["functions", "structs", "opaque", "enums", "typedefs"] renaming_overrides_prefixing = false diff --git a/selene-core/cbindgen/error_model.toml b/selene-core/cbindgen/error_model.toml index ee83bd20..3c3d417d 100644 --- a/selene-core/cbindgen/error_model.toml +++ b/selene-core/cbindgen/error_model.toml @@ -4,7 +4,7 @@ include_version = false includes = ["selene/core_types.h"] no_includes = false cpp_compat = true -after_includes = "" +after_includes = "#define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL" braces = "SameLine" line_length = 100 tab_width = 2 @@ -30,6 +30,65 @@ include = [ "SimulatorOperationInterface", "Errno", ] +exclude = [ + "CURRENT_API_VERSION", + "GwStatus", + "GW_STATUS_OK", + "GW_STATUS_NULL_POINTER", + "GW_STATUS_INVALID_ARGUMENT", + "GW_STATUS_DUPLICATE_GATE", + "GW_STATUS_NOT_FOUND", + "GW_STATUS_BUFFER_TOO_SMALL", + "GW_STATUS_DECODE_ERROR", + "GW_STATUS_VALIDATION_ERROR", + "GW_STATUS_PANIC", + "GW_OPERAND_KIND_QUBIT", + "GW_OPERAND_KIND_F64", + "GW_OPERAND_KIND_U64", + "GW_OPERAND_KIND_I64", + "GW_OPERAND_KIND_U8", + "GW_OPERAND_KIND_BOOL", + "GwSemanticId", + "GwGateSet", + "GwOperandDeclView", + "GwGateDeclView", + "GwGateDeclInfo", + "GwOperandDeclInfo", + "GwGateValueData", + "GwGateValue", + "GwGateInstanceView", + "GwDecodedGate", + "gw_status_message", + "gw_semantic_id_from_text", + "gw_semantic_id_eq", + "gw_builtin_rz_semantic_id", + "gw_builtin_phased_x_semantic_id", + "gw_builtin_zz_phase_semantic_id", + "gw_builtin_phased_xx_semantic_id", + "gw_gateset_new", + "gw_builtin_gateset_new", + "gw_gateset_free", + "gw_gateset_add_decl", + "gw_gateset_add_builtin_rz", + "gw_gateset_add_builtin_phased_x", + "gw_gateset_add_builtin_zz_phase", + "gw_gateset_add_builtin_phased_xx", + "gw_gateset_len", + "gw_gateset_contains", + "gw_gateset_decl_at", + "gw_gateset_decl_operand_at", + "gw_gateset_serialized_len", + "gw_gateset_serialize", + "gw_gateset_deserialize", + "gw_gate_serialized_len", + "gw_gate_serialize", + "gw_gate_deserialize", + "gw_decoded_gate_free", + "gw_decoded_gate_semantic_id", + "gw_decoded_gate_value_count", + "gw_decoded_gate_value_at", + "gw_gateset_validate_decoded", +] item_types = ["functions", "structs", "opaque", "enums", "typedefs"] renaming_overrides_prefixing = false @@ -44,7 +103,6 @@ renaming_overrides_prefixing = false "RuntimeExtractOperationInstance" = "SeleneRuntimeExtractOperationInstance" "SimulatorInstance" = "SeleneSimulatorInstance" "Errno" = "SeleneErrno" -"CURRENT_API_VERSION" = "SELENE_ERROR_MODEL_CURRENT_API_VERSION" [export.mangle] rename_types = "SnakeCase" diff --git a/selene-core/cbindgen/runtime.toml b/selene-core/cbindgen/runtime.toml index 5df133dc..6da1b95b 100644 --- a/selene-core/cbindgen/runtime.toml +++ b/selene-core/cbindgen/runtime.toml @@ -4,7 +4,7 @@ include_version = false includes = ["selene/core_types.h"] no_includes = false cpp_compat = true -after_includes = "" +after_includes = "#define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL" braces = "SameLine" line_length = 100 tab_width = 2 @@ -26,6 +26,65 @@ include = [ "RuntimePluginDescriptorV1", "Errno", ] +exclude = [ + "CURRENT_API_VERSION", + "GwStatus", + "GW_STATUS_OK", + "GW_STATUS_NULL_POINTER", + "GW_STATUS_INVALID_ARGUMENT", + "GW_STATUS_DUPLICATE_GATE", + "GW_STATUS_NOT_FOUND", + "GW_STATUS_BUFFER_TOO_SMALL", + "GW_STATUS_DECODE_ERROR", + "GW_STATUS_VALIDATION_ERROR", + "GW_STATUS_PANIC", + "GW_OPERAND_KIND_QUBIT", + "GW_OPERAND_KIND_F64", + "GW_OPERAND_KIND_U64", + "GW_OPERAND_KIND_I64", + "GW_OPERAND_KIND_U8", + "GW_OPERAND_KIND_BOOL", + "GwSemanticId", + "GwGateSet", + "GwOperandDeclView", + "GwGateDeclView", + "GwGateDeclInfo", + "GwOperandDeclInfo", + "GwGateValueData", + "GwGateValue", + "GwGateInstanceView", + "GwDecodedGate", + "gw_status_message", + "gw_semantic_id_from_text", + "gw_semantic_id_eq", + "gw_builtin_rz_semantic_id", + "gw_builtin_phased_x_semantic_id", + "gw_builtin_zz_phase_semantic_id", + "gw_builtin_phased_xx_semantic_id", + "gw_gateset_new", + "gw_builtin_gateset_new", + "gw_gateset_free", + "gw_gateset_add_decl", + "gw_gateset_add_builtin_rz", + "gw_gateset_add_builtin_phased_x", + "gw_gateset_add_builtin_zz_phase", + "gw_gateset_add_builtin_phased_xx", + "gw_gateset_len", + "gw_gateset_contains", + "gw_gateset_decl_at", + "gw_gateset_decl_operand_at", + "gw_gateset_serialized_len", + "gw_gateset_serialize", + "gw_gateset_deserialize", + "gw_gate_serialized_len", + "gw_gate_serialize", + "gw_gate_deserialize", + "gw_decoded_gate_free", + "gw_decoded_gate_semantic_id", + "gw_decoded_gate_value_count", + "gw_decoded_gate_value_at", + "gw_gateset_validate_decoded", +] item_types = ["functions", "structs", "opaque", "enums", "typedefs"] renaming_overrides_prefixing = false @@ -37,7 +96,6 @@ renaming_overrides_prefixing = false "RuntimeExtractOperationInstance" = "SeleneRuntimeExtractOperationInstance" "RuntimePluginDescriptorV1" = "SeleneRuntimePluginDescriptorV1" "Errno" = "SeleneErrno" -"CURRENT_API_VERSION" = "SELENE_RUNTIME_CURRENT_API_VERSION" [export.mangle] rename_types = "SnakeCase" diff --git a/selene-core/cbindgen/simulator.toml b/selene-core/cbindgen/simulator.toml index 890b6dd8..d381d231 100644 --- a/selene-core/cbindgen/simulator.toml +++ b/selene-core/cbindgen/simulator.toml @@ -4,7 +4,7 @@ include_version = false includes = ["selene/core_types.h"] no_includes = false cpp_compat = true -after_includes = "" +after_includes = "#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL" braces = "SameLine" line_length = 100 tab_width = 2 @@ -23,6 +23,65 @@ include = [ "SimulatorPluginDescriptorV1", "Errno", ] +exclude = [ + "CURRENT_API_VERSION", + "GwStatus", + "GW_STATUS_OK", + "GW_STATUS_NULL_POINTER", + "GW_STATUS_INVALID_ARGUMENT", + "GW_STATUS_DUPLICATE_GATE", + "GW_STATUS_NOT_FOUND", + "GW_STATUS_BUFFER_TOO_SMALL", + "GW_STATUS_DECODE_ERROR", + "GW_STATUS_VALIDATION_ERROR", + "GW_STATUS_PANIC", + "GW_OPERAND_KIND_QUBIT", + "GW_OPERAND_KIND_F64", + "GW_OPERAND_KIND_U64", + "GW_OPERAND_KIND_I64", + "GW_OPERAND_KIND_U8", + "GW_OPERAND_KIND_BOOL", + "GwSemanticId", + "GwGateSet", + "GwOperandDeclView", + "GwGateDeclView", + "GwGateDeclInfo", + "GwOperandDeclInfo", + "GwGateValueData", + "GwGateValue", + "GwGateInstanceView", + "GwDecodedGate", + "gw_status_message", + "gw_semantic_id_from_text", + "gw_semantic_id_eq", + "gw_builtin_rz_semantic_id", + "gw_builtin_phased_x_semantic_id", + "gw_builtin_zz_phase_semantic_id", + "gw_builtin_phased_xx_semantic_id", + "gw_gateset_new", + "gw_builtin_gateset_new", + "gw_gateset_free", + "gw_gateset_add_decl", + "gw_gateset_add_builtin_rz", + "gw_gateset_add_builtin_phased_x", + "gw_gateset_add_builtin_zz_phase", + "gw_gateset_add_builtin_phased_xx", + "gw_gateset_len", + "gw_gateset_contains", + "gw_gateset_decl_at", + "gw_gateset_decl_operand_at", + "gw_gateset_serialized_len", + "gw_gateset_serialize", + "gw_gateset_deserialize", + "gw_gate_serialized_len", + "gw_gate_serialize", + "gw_gate_deserialize", + "gw_decoded_gate_free", + "gw_decoded_gate_semantic_id", + "gw_decoded_gate_value_count", + "gw_decoded_gate_value_at", + "gw_gateset_validate_decoded", +] item_types = ["functions", "structs", "opaque", "enums", "typedefs"] renaming_overrides_prefixing = false @@ -31,7 +90,6 @@ renaming_overrides_prefixing = false "SimulatorInstance" = "SeleneSimulatorInstance" "SimulatorPluginDescriptorV1" = "SeleneSimulatorPluginDescriptorV1" "Errno" = "SeleneErrno" -"CURRENT_API_VERSION" = "SELENE_SIMULATOR_CURRENT_API_VERSION" [export.mangle] rename_types = "SnakeCase" diff --git a/selene-core/examples/error_model/Cargo.lock b/selene-core/examples/error_model/Cargo.lock index 981d1151..116962f2 100644 --- a/selene-core/examples/error_model/Cargo.lock +++ b/selene-core/examples/error_model/Cargo.lock @@ -58,12 +58,48 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -116,6 +152,30 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "delegate" version = "0.13.3" @@ -142,12 +202,26 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version", "syn", + "unicode-xid", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "getrandom" version = "0.3.3" @@ -160,12 +234,28 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -279,9 +369,14 @@ name = "selene-core" version = "0.3.0-alpha.1" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] @@ -302,6 +397,24 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -345,6 +458,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/selene-core/examples/error_model/rust/lib.rs b/selene-core/examples/error_model/rust/lib.rs index 3a4e56b8..dc695424 100644 --- a/selene-core/examples/error_model/rust/lib.rs +++ b/selene-core/examples/error_model/rust/lib.rs @@ -1,19 +1,17 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; +use clap::Parser; +use rand::{Rng, SeedableRng}; +use rand_pcg::Pcg64Mcg; use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; -use selene_core::runtime::{BatchOperation, Operation}; -use selene_core::simulator::{Simulator, SimulatorInterface}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; -use std::ffi::OsStr; -use rand::{Rng, SeedableRng}; -use rand_pcg::Pcg64Mcg; -use clap::Parser; - #[derive(Parser, Debug)] struct Params { - #[arg(long, default_value= "0.01")] + #[arg(long, default_value = "0.01")] flip_probability: f64, #[arg(long, default_value = "0.01")] angle_mutation: f64, @@ -28,48 +26,45 @@ struct Stats { total_angle_error: f64, } - pub struct ExampleErrorModel { - simulator: Simulator, rng: Pcg64Mcg, - // Store the user-customisable properties for this instance error_params: Params, - // We can gather statistics that will be reported back to the user - // if they are using the MetricStore. stats: Stats, leakage_map: Vec, } impl ExampleErrorModel { fn mutate_angle(&mut self, angle: f64) -> f64 { - // Mutate the angle by a small random amount - let offset = self.rng.random_range(-self.error_params.angle_mutation..self.error_params.angle_mutation); + let offset = self + .rng + .random_range(-self.error_params.angle_mutation..self.error_params.angle_mutation); self.stats.total_angle_error += offset.abs(); angle + offset } + fn should_flip(&mut self) -> bool { - // Randomly decide whether to flip a qubit in the computational basis self.rng.random_bool(self.error_params.flip_probability) } + fn should_leak(&mut self) -> bool { - // Randomly decide whether to leak a qubit self.rng.random_bool(self.error_params.leak_probability) } - fn flip_qubit(&mut self, qubit_id: u64) -> Result<()> { - // Flip the qubit in the computational basis - self.apply_simulator_void(Operation::RXYGate { - qubit_id, - theta: std::f64::consts::PI, - phi: 0.0, - })?; + + fn flip_qubit(&mut self, simulator: &mut dyn SimulatorInterface, qubit_id: u64) -> Result<()> { + self.apply_simulator_void( + simulator, + Operation::phased_x(qubit_id, std::f64::consts::PI, 0.0)?, + )?; self.stats.flips_induced += 1; Ok(()) } - fn apply_simulator_void(&mut self, operation: Operation) -> Result<()> { - let results = self - .simulator - .handle_operations(BatchOperation::error_model(vec![operation]))?; + fn apply_simulator_void( + &mut self, + simulator: &mut dyn SimulatorInterface, + operation: Operation, + ) -> Result<()> { + let results = simulator.handle_operations(BatchOperation::error_model(vec![operation]))?; if results.bool_results.is_empty() && results.u64_results.is_empty() { Ok(()) } else { @@ -79,13 +74,17 @@ impl ExampleErrorModel { } } - fn measure_simulator(&mut self, qubit_id: u64) -> Result { - let results = self - .simulator - .handle_operations(BatchOperation::error_model(vec![Operation::Measure { + fn measure_simulator( + &mut self, + simulator: &mut dyn SimulatorInterface, + qubit_id: u64, + ) -> Result { + let results = simulator.handle_operations(BatchOperation::error_model(vec![ + Operation::Measure { qubit_id, result_id: 0, - }]))?; + }, + ]))?; if results.u64_results.is_empty() && results.bool_results.len() == 1 { Ok(results.bool_results[0].value) } else { @@ -94,128 +93,118 @@ impl ExampleErrorModel { )) } } + + fn handle_gate( + &mut self, + operation: Operation, + simulator: &mut dyn SimulatorInterface, + ) -> Result<()> { + match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => { + let theta = self.mutate_angle(theta); + let phi = self.mutate_angle(phi); + self.apply_simulator_void(simulator, Operation::phased_x(qubit_id, theta, phi)?)?; + if self.should_flip() { + self.flip_qubit(simulator, qubit_id)?; + } + if self.should_leak() { + self.stats.leaks_induced += 1; + self.leakage_map[qubit_id as usize] = true; + } + } + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => { + let theta = self.mutate_angle(theta); + let mut leaked_1 = self.leakage_map[qubit_id_1 as usize]; + let mut leaked_2 = self.leakage_map[qubit_id_2 as usize]; + match (leaked_1, leaked_2) { + (false, true) => { + self.leakage_map[qubit_id_1 as usize] = true; + leaked_1 = true; + } + (true, false) => { + self.leakage_map[qubit_id_2 as usize] = true; + leaked_2 = true; + } + (false, false) => { + if self.should_leak() { + self.stats.leaks_induced += 2; + self.leakage_map[qubit_id_1 as usize] = true; + self.leakage_map[qubit_id_2 as usize] = true; + leaked_1 = true; + leaked_2 = true; + } + } + _ => {} + } + if !leaked_1 && !leaked_2 { + self.apply_simulator_void( + simulator, + Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?, + )?; + if self.should_flip() { + self.flip_qubit(simulator, qubit_id_1)?; + self.flip_qubit(simulator, qubit_id_2)?; + } + } + } + Some(BuiltinGate::RZ { qubit_id, theta }) => { + let theta = self.mutate_angle(theta); + self.apply_simulator_void(simulator, Operation::rz(qubit_id, theta)?)?; + if self.should_flip() { + self.flip_qubit(simulator, qubit_id)?; + } + if self.should_leak() { + self.stats.leaks_induced += 1; + self.leakage_map[qubit_id as usize] = true; + } + } + Some(BuiltinGate::PhasedXX { .. }) | None => { + self.apply_simulator_void(simulator, operation)?; + } + } + Ok(()) + } } impl ErrorModelInterface for ExampleErrorModel { fn exit(&mut self) -> Result<()> { Ok(()) } - fn shot_start(&mut self, shot_id: u64, error_model_seed: u64, simulator_seed: u64) -> Result<()> { - self.simulator.shot_start(shot_id, simulator_seed)?; - self.rng = Pcg64Mcg::seed_from_u64(error_model_seed); + + fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { + self.rng = Pcg64Mcg::seed_from_u64(seed); Ok(()) } + fn shot_end(&mut self) -> Result<()> { - self.simulator.shot_end()?; Ok(()) } - fn handle_operations(&mut self, operations: BatchOperation) -> Result { + fn handle_operations( + &mut self, + operations: BatchOperation, + simulator: &mut dyn SimulatorInterface, + ) -> Result { let mut results = BatchResult::default(); - for op in operations { - match op { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => { - // An RXY gate has been requested. - // - // randomly mutate theta and phi - let theta = self.mutate_angle(theta); - let phi = self.mutate_angle(phi); - // Apply the RXY gate - self.apply_simulator_void(Operation::RXYGate { - qubit_id, - theta, - phi, - })?; - // randomly flip the qubit in the computational basis - if self.should_flip() { - self.flip_qubit(qubit_id)?; - } - if self.should_leak() { - self.stats.leaks_induced += 1; - self.leakage_map[qubit_id as usize] = true; - } - } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => { - // An RZZ gate has been requested. - // - // randomly mutate theta - let theta = self.mutate_angle(theta); - // apply the RZZ gate - let mut leaked_1 = self.leakage_map[qubit_id_1 as usize]; - let mut leaked_2 = self.leakage_map[qubit_id_2 as usize]; - match (leaked_1, leaked_2) { - // For this example, we model leakage like a contagion. - // If one has leaked, leak the other upon interaction. - (false, true) => { - self.leakage_map[qubit_id_1 as usize] = true; - leaked_1 = true; - } - (true, false) => { - self.leakage_map[qubit_id_2 as usize] = true; - leaked_2 = true; - } - (false, false) => { - if self.should_leak() { - // leak both - self.stats.leaks_induced += 2; - self.leakage_map[qubit_id_1 as usize] = true; - self.leakage_map[qubit_id_2 as usize] = true; - leaked_1 = true; - leaked_2 = true; - } - } - _ => {} - } - if !leaked_1 && !leaked_2 { - self.apply_simulator_void(Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - })?; - // randomly flip both qubits in the computational basis - if self.should_flip() { - self.flip_qubit(qubit_id_1)?; - self.flip_qubit(qubit_id_2)?; - } - } - } - Operation::RZGate { qubit_id, theta } => { - // An RZ gate has been requested. - // - // randomly mutate theta - let theta = self.mutate_angle(theta); - // apply the RZ gate - self.apply_simulator_void(Operation::RZGate { qubit_id, theta })?; - // randomly flip the qubit in the computational basis - if self.should_flip() { - self.flip_qubit(qubit_id)?; - } - if self.should_leak() { - self.stats.leaks_induced += 1; - self.leakage_map[qubit_id as usize] = true; - } - } + for operation in operations { + match operation { + Operation::Gate { .. } => self.handle_gate(operation, simulator)?, Operation::Measure { qubit_id, result_id, } => { - // If leaked, measure 1 with 90% chance. let measurement = if self.leakage_map[qubit_id as usize] { self.rng.random_bool(0.9) } else { - // A measurement has been requested. - // We need to perform the measurement and store the result. - let true_measurement = self.measure_simulator(qubit_id)?; - // But wait! Let's randomly flip the measurement result with a small - // probability. + let true_measurement = self.measure_simulator(simulator, qubit_id)?; if self.should_flip() { self.stats.flips_induced += 1; !true_measurement @@ -229,19 +218,13 @@ impl ErrorModelInterface for ExampleErrorModel { qubit_id, result_id, } => { - // If leaked, return 2, otherwise measure 0 or 1 according to the - // normal measurement procedure. let measurement = if self.leakage_map[qubit_id as usize] { - 2 // Indicate leakage + 2 } else { - // A measurement has been requested. - // We need to perform the measurement and store the result. - let true_measurement = self.measure_simulator(qubit_id)?; - // But wait! Let's randomly flip the measurement result with a small - // probability. + let true_measurement = self.measure_simulator(simulator, qubit_id)?; if self.should_flip() { self.stats.flips_induced += 1; - !true_measurement as u64 + (!true_measurement) as u64 } else { true_measurement as u64 } @@ -249,17 +232,14 @@ impl ErrorModelInterface for ExampleErrorModel { results.set_u64_result(result_id, measurement); } Operation::Reset { qubit_id } => { - // A reset has been requested. - self.leakage_map[qubit_id as usize] = false; // Reset leakage state - self.apply_simulator_void(Operation::Reset { qubit_id })?; - // So ideally it is in |0> now. Let's flip it with a small probability. + self.leakage_map[qubit_id as usize] = false; + self.apply_simulator_void(simulator, Operation::Reset { qubit_id })?; if self.should_flip() { - self.flip_qubit(qubit_id)?; + self.flip_qubit(simulator, qubit_id)?; } } - Operation::Custom { .. } => { - // Passively ignore custom operations - } + Operation::Custom { .. } => {} + _ => {} } } Ok(results) @@ -267,29 +247,23 @@ impl ErrorModelInterface for ExampleErrorModel { fn get_metric(&mut self, nth_metric: u8) -> Result> { match nth_metric { - 0 => { - // Return the number of flips induced by the error model - Ok(Some(("flips_induced".to_string(), MetricValue::U64(self.stats.flips_induced)))) - } - 1 => { - // Return the total angle error induced by the error model - Ok(Some(("total_angle_error".to_string(), MetricValue::F64(self.stats.total_angle_error)))) - } - 2 => { - // Return the number of leaks induced by the error model - Ok(Some(("leaks_induced".to_string(), MetricValue::U64(self.stats.leaks_induced)))) - } - 3 => { - // No other metrics are defined. Note this is NOT an error. We are - // simply reporting to Selene that we are at the end of the metric list. - Ok(None) - } - _ => { - // Selene should not be requesting another metric. This shouldn't happen, - // and you shouldn't need to handle this, but it's an example of how to provide - // errors through this function. - Err(anyhow!("Selene requested an out of bounds metric: {}", nth_metric)) - } + 0 => Ok(Some(( + "flips_induced".to_string(), + MetricValue::U64(self.stats.flips_induced), + ))), + 1 => Ok(Some(( + "total_angle_error".to_string(), + MetricValue::F64(self.stats.total_angle_error), + ))), + 2 => Ok(Some(( + "leaks_induced".to_string(), + MetricValue::U64(self.stats.leaks_induced), + ))), + 3 => Ok(None), + _ => Err(anyhow!( + "Selene requested an out of bounds metric: {}", + nth_metric + )), } } } @@ -304,26 +278,18 @@ impl ErrorModelInterfaceFactory for ExampleErrorModelFactory { self: std::sync::Arc, n_qubits: u64, error_model_args: &[impl AsRef], - simulator_path: &impl AsRef, - simulator_args: &[impl AsRef], ) -> Result> { match Params::try_parse_from(error_model_args.iter().map(|s| s.as_ref())) { Err(e) => Err(anyhow!( "Error parsing arguments to the example error model plugin: {}", e )), - Ok(params) => { - let simulator = - Simulator::load_from_file(simulator_path, n_qubits, simulator_args)?; - let leakage_map = vec![false; n_qubits as usize]; // Initialize a leakage map if needed - Ok(Box::new(ExampleErrorModel { - rng: Pcg64Mcg::seed_from_u64(0), - simulator, - error_params: params, - stats: Stats::default(), - leakage_map, - })) - } + Ok(params) => Ok(Box::new(ExampleErrorModel { + rng: Pcg64Mcg::seed_from_u64(0), + error_params: params, + stats: Stats::default(), + leakage_map: vec![false; n_qubits as usize], + })), } } } diff --git a/selene-core/examples/runtime/Cargo.lock b/selene-core/examples/runtime/Cargo.lock index 40c88fc0..cc0f4645 100644 --- a/selene-core/examples/runtime/Cargo.lock +++ b/selene-core/examples/runtime/Cargo.lock @@ -8,12 +8,78 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "delegate" version = "0.13.3" @@ -40,12 +106,48 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version", "syn", + "unicode-xid", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + [[package]] name = "libloading" version = "0.8.8" @@ -88,9 +190,14 @@ name = "selene-core" version = "0.3.0-alpha.1" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] @@ -108,6 +215,24 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.102" @@ -145,6 +270,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "windows-targets" version = "0.53.0" diff --git a/selene-core/examples/runtime/rust/lib.rs b/selene-core/examples/runtime/rust/lib.rs index 8e127dd5..7c77ce1d 100644 --- a/selene-core/examples/runtime/rust/lib.rs +++ b/selene-core/examples/runtime/rust/lib.rs @@ -3,7 +3,11 @@ use std::collections::VecDeque; use anyhow::{bail, Result}; use selene_core::{ export_runtime_plugin, - runtime::{interface::RuntimeInterfaceFactory, BatchOperation, Operation, RuntimeInterface}, + gatewire::OwnedGateInstance, + runtime::{ + interface::RuntimeInterfaceFactory, BatchOperation, BuiltinGate, Operation, + RuntimeInterface, + }, utils::MetricValue, }; @@ -95,42 +99,12 @@ impl RuntimeInterface for ExampleRuntime { .enumerate() { for op in operation.iter_ops() { - match op { - Operation::RXYGate { qubit_id, .. } => { - if qubits.contains(qubit_id) { - last_op_using_qubits = i; - } - } - Operation::RZGate { qubit_id, .. } => { - if qubits.contains(qubit_id) { - last_op_using_qubits = i; - } - } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - .. - } => { - if qubits.contains(qubit_id_1) || qubits.contains(qubit_id_2) { - last_op_using_qubits = i; - } - } - Operation::Measure { qubit_id, .. } => { - if qubits.contains(qubit_id) { - last_op_using_qubits = i; - } - } - Operation::MeasureLeaked { qubit_id, .. } => { - if qubits.contains(qubit_id) { - last_op_using_qubits = i; - } - } - Operation::Reset { qubit_id, .. } => { - if qubits.contains(qubit_id) { - last_op_using_qubits = i; - } - } - Operation::Custom { .. } => {} + if op + .get_qubit_ids() + .iter() + .any(|qubit_id| qubits.contains(qubit_id)) + { + last_op_using_qubits = i; } } } @@ -155,47 +129,50 @@ impl RuntimeInterface for ExampleRuntime { Ok(()) } } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rxy gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.push(Operation::RXYGate { - qubit_id, - theta, - phi: phi - phase, // The Z phase is enacted here. - }); - Ok(()) - } - fn rzz_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result<()> { - if qubit_id_1 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit1 {qubit_id_1}"); - } - if qubit_id_2 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit2 {qubit_id_2}"); - } - self.push(Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - }); - Ok(()) - } - fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rz gate to out-of-bounds qubit {qubit_id}"); + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying PhasedX gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.push(Operation::phased_x(qubit_id, theta, phi - phase)?); + } + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => { + if qubit_id_1 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit1 {qubit_id_1}"); + } + if qubit_id_2 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit2 {qubit_id_2}"); + } + self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); + } + Some(BuiltinGate::RZ { qubit_id, theta }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.qubits[qubit_id as usize] = QubitStatus::Active { + phase: phase + theta, + }; + } + Some(BuiltinGate::PhasedXX { .. }) => { + bail!("ExampleRuntime does not support PhasedXX gates"); + } + None => bail!("ExampleRuntime does not support custom gates"), } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - // We don't apply an RZ gate. Instead, we accumulate a phase, and mutate - // RXY gates' phi parameters to account for the phase shift. RZZ and measurement - // are unaffected. - self.qubits[qubit_id as usize] = QubitStatus::Active { - phase: phase + theta, - }; Ok(()) } // Lifetime ops @@ -204,7 +181,7 @@ impl RuntimeInterface for ExampleRuntime { bail!("measuring out-of-bounds qubit {qubit_id}") } let result_id = self.future_results.len() as u64; - self.future_result.push(FutureResult { + self.future_results.push(FutureResult { is_set: false, value: 0, }); diff --git a/selene-core/examples/simulator/Cargo.lock b/selene-core/examples/simulator/Cargo.lock index 2d171242..6103e220 100644 --- a/selene-core/examples/simulator/Cargo.lock +++ b/selene-core/examples/simulator/Cargo.lock @@ -67,6 +67,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + [[package]] name = "autocfg" version = "1.4.0" @@ -79,6 +91,30 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -131,6 +167,30 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "delegate" version = "0.13.3" @@ -157,12 +217,26 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version", "syn", + "unicode-xid", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "getrandom" version = "0.3.3" @@ -175,12 +249,28 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -303,9 +393,14 @@ name = "selene-core" version = "0.3.0-alpha.1" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] @@ -327,6 +422,24 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -370,6 +483,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/selene-core/examples/simulator/rust/lib.rs b/selene-core/examples/simulator/rust/lib.rs index dadf8151..910437a1 100644 --- a/selene-core/examples/simulator/rust/lib.rs +++ b/selene-core/examples/simulator/rust/lib.rs @@ -4,7 +4,7 @@ use rand::{Rng, SeedableRng}; use rand_pcg::Pcg64Mcg; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -30,23 +30,23 @@ impl ExampleSimulator { self.true_flips as f64 / self.total_flips as f64 } - fn rxy(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_x(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RXY(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } } - fn rzz(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RZZ(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -63,12 +63,12 @@ impl ExampleSimulator { } } - fn rpp(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_xx(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RPP(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -123,23 +123,26 @@ impl SimulatorInterface for ExampleSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, @@ -150,6 +153,7 @@ impl SimulatorInterface for ExampleSimulator { } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, Operation::Custom { .. } => {} + _ => {} } } Ok(results) diff --git a/selene-core/hatch_build.py b/selene-core/hatch_build.py index 4f4daff0..d7ddbf6f 100644 --- a/selene-core/hatch_build.py +++ b/selene-core/hatch_build.py @@ -1,6 +1,7 @@ import json from hatchling.builders.hooks.plugin.interface import BuildHookInterface from pathlib import Path +import shutil class SeleneCoreBuildHook(BuildHookInterface): @@ -27,6 +28,12 @@ def initialize(self, version: str, build_data: dict) -> None: json.dumps(trace_module.Trace.model_json_schema(), indent=2) ) + include_source = Path("c/include") + include_target = Path("python/selene_core/_dist/include") + if include_target.exists(): + shutil.rmtree(include_target) + shutil.copytree(include_source, include_target) + artifacts = [] dist_dir = Path("python/selene_core/_dist") for artifact in dist_dir.rglob("*"): diff --git a/selene-core/pyproject.toml b/selene-core/pyproject.toml index 329da42b..d54acc86 100644 --- a/selene-core/pyproject.toml +++ b/selene-core/pyproject.toml @@ -5,6 +5,7 @@ requires-python = ">=3.10" description = "The core interop library for Selene python interfaces" readme = "python/selene_core/README.md" dependencies = [ + "blake3>=1.0.0", "hugr>=0.13.0", # required for inspecting object files to find defined and declared symbols "lief>=0.16.5", diff --git a/selene-core/python/selene_core/__init__.py b/selene-core/python/selene_core/__init__.py index 5d420d22..60cab242 100644 --- a/selene-core/python/selene_core/__init__.py +++ b/selene-core/python/selene_core/__init__.py @@ -12,6 +12,31 @@ DEFAULT_BUILD_PLANNER, ) from .headers import get_include_directory +from .gatewire import ( + BOOL, + F64, + I64, + QUBIT, + U8, + U64, + BoundGate, + Gate, + GateDefinition, + GateValue, + Gateset, + OperandDefinition, + OperandKind, + PhasedX, + PhasedXX, + RZ, + ZZPhase, + builtin_gateset, + phased_x, + phased_xx, + rz, + semantic_id_from_text, + zz_phase, +) __all__ = [ "SeleneComponent", @@ -26,6 +51,29 @@ "BuildCtx", "DEFAULT_BUILD_PLANNER", "get_include_directory", + "BOOL", + "F64", + "I64", + "QUBIT", + "U8", + "U64", + "BoundGate", + "Gate", + "GateDefinition", + "GateValue", + "Gateset", + "OperandDefinition", + "OperandKind", + "PhasedX", + "PhasedXX", + "RZ", + "ZZPhase", + "builtin_gateset", + "phased_x", + "phased_xx", + "rz", + "semantic_id_from_text", + "zz_phase", ] # This is updated by our release-please workflow, triggered by this diff --git a/selene-core/python/selene_core/gatewire.py b/selene-core/python/selene_core/gatewire.py new file mode 100644 index 00000000..225190b8 --- /dev/null +++ b/selene-core/python/selene_core/gatewire.py @@ -0,0 +1,436 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import IntEnum +import struct +from typing import Iterable + +import blake3 + + +class OperandKind(IntEnum): + QUBIT = 1 + F64 = 2 + U64 = 3 + I64 = 4 + U8 = 5 + BOOL = 6 + + +@dataclass(frozen=True) +class OperandDefinition: + name: str + kind: OperandKind + + +@dataclass(frozen=True) +class GateDefinition: + semantic_id: bytes + name: str + operands: tuple[OperandDefinition, ...] + version: int = 1 + + def __init__( + self, + semantic_id: str | bytes, + name: str, + operands: Iterable[OperandDefinition], + version: int = 1, + ): + object.__setattr__( + self, + "semantic_id", + semantic_id_from_text(semantic_id) + if isinstance(semantic_id, str) + else bytes(semantic_id), + ) + if len(self.semantic_id) != 16: + raise ValueError("semantic_id must be 16 bytes") + object.__setattr__(self, "name", name) + object.__setattr__(self, "operands", tuple(operands)) + object.__setattr__(self, "version", int(version)) + + +@dataclass(frozen=True) +class GateValue: + kind: OperandKind + value: int | float | bool + + @staticmethod + def qubit(value: int) -> GateValue: + return GateValue(OperandKind.QUBIT, int(value)) + + @staticmethod + def f64(value: float) -> GateValue: + return GateValue(OperandKind.F64, float(value)) + + @staticmethod + def u64(value: int) -> GateValue: + return GateValue(OperandKind.U64, int(value)) + + @staticmethod + def i64(value: int) -> GateValue: + return GateValue(OperandKind.I64, int(value)) + + @staticmethod + def u8(value: int) -> GateValue: + return GateValue(OperandKind.U8, int(value)) + + @staticmethod + def bool(value: bool) -> GateValue: + return GateValue(OperandKind.BOOL, bool(value)) + + +@dataclass(frozen=True) +class Gate: + semantic_id: bytes + operands: tuple[GateValue, ...] + + def __init__(self, semantic_id: str | bytes, operands: Iterable[GateValue]): + object.__setattr__( + self, + "semantic_id", + semantic_id_from_text(semantic_id) + if isinstance(semantic_id, str) + else bytes(semantic_id), + ) + if len(self.semantic_id) != 16: + raise ValueError("semantic_id must be 16 bytes") + object.__setattr__(self, "operands", tuple(operands)) + + def serialize(self) -> bytes: + out = bytearray() + out.extend(b"GWG1") + out.extend(struct.pack(" Gate: + cursor = _Cursor(bytes(data)) + if cursor.read(4) != b"GWG1": + raise ValueError("bad gate magic") + version, _reserved = struct.unpack(" tuple[GateDefinition, ...]: + return self._definitions + + def bind(self, gate: bytes | bytearray | memoryview | GateDefinition) -> BoundGate: + return BoundGate(self.definition(gate)) + + def __getattr__(self, name: str) -> BoundGate: + for definition in self._definitions: + if definition.name == name: + return BoundGate(definition) + raise AttributeError(f"{type(self).__name__!s} has no gate {name!r}") + + def serialize(self) -> bytes: + out = bytearray() + out.extend(b"GWS1") + out.extend(struct.pack(" Gateset: + cursor = _Cursor(bytes(data)) + if cursor.read(4) != b"GWS1": + raise ValueError("bad gateset magic") + version, _reserved, count = struct.unpack(" GateDefinition: + if isinstance(gate, GateDefinition): + semantic_id = gate.semantic_id + else: + semantic_id = bytes(gate) + + for definition in self._definitions: + if definition.semantic_id == semantic_id: + return definition + raise KeyError("unknown gate semantic_id") + + def gate( + self, + gate: bytes | bytearray | memoryview | GateDefinition, + *values: int | float | bool | GateValue, + **named_values: int | float | bool | GateValue, + ) -> Gate: + definition = self.definition(gate) + if values and named_values: + raise TypeError( + "provide either positional or named gate operands, not both" + ) + if named_values: + missing = [ + operand.name + for operand in definition.operands + if operand.name not in named_values + ] + if missing: + raise TypeError(f"missing gate operands: {', '.join(missing)}") + extra = [ + operand_name + for operand_name in named_values + if operand_name not in {operand.name for operand in definition.operands} + ] + if extra: + raise TypeError(f"unknown gate operands: {', '.join(extra)}") + values = tuple( + named_values[operand.name] for operand in definition.operands + ) + if len(values) != len(definition.operands): + raise TypeError( + f"{definition.name} expects {len(definition.operands)} operands, " + f"got {len(values)}" + ) + return _instantiate_gate(definition, values) + + def __iter__(self): + return iter(self._definitions) + + def __len__(self) -> int: + return len(self._definitions) + + +@dataclass(frozen=True) +class BoundGate: + definition: GateDefinition + + def __call__( + self, + *values: int | float | bool | GateValue, + **named_values: int | float | bool | GateValue, + ) -> Gate: + if values and named_values: + raise TypeError( + "provide either positional or named gate operands, not both" + ) + if named_values: + missing = [ + operand.name + for operand in self.definition.operands + if operand.name not in named_values + ] + if missing: + raise TypeError(f"missing gate operands: {', '.join(missing)}") + expected_names = {operand.name for operand in self.definition.operands} + extra = [ + operand_name + for operand_name in named_values + if operand_name not in expected_names + ] + if extra: + raise TypeError(f"unknown gate operands: {', '.join(extra)}") + values = tuple( + named_values[operand.name] for operand in self.definition.operands + ) + if len(values) != len(self.definition.operands): + raise TypeError( + f"{self.definition.name} expects {len(self.definition.operands)} " + f"operands, got {len(values)}" + ) + return _instantiate_gate(self.definition, values) + + +def semantic_id_from_text(text: str | bytes) -> bytes: + if isinstance(text, str): + text = text.encode("utf-8") + return blake3.blake3(text).digest(length=16) + + +def _write_string(out: bytearray, value: str) -> None: + data = value.encode("utf-8") + out.extend(struct.pack(" Gate: + return Gate( + definition.semantic_id, + [ + _coerce_gate_value(operand.kind, value) + for operand, value in zip(definition.operands, values, strict=True) + ], + ) + + +class _Cursor: + def __init__(self, data: bytes): + self._data = data + self._offset = 0 + + def read(self, length: int) -> bytes: + end = self._offset + length + if end > len(self._data): + raise ValueError("truncated gatewire data") + value = self._data[self._offset : end] + self._offset = end + return value + + def read_string(self) -> str: + (length,) = struct.unpack(" None: + if self._offset != len(self._data): + raise ValueError("trailing gatewire data") + + +QUBIT = OperandKind.QUBIT +F64 = OperandKind.F64 +U64 = OperandKind.U64 +I64 = OperandKind.I64 +U8 = OperandKind.U8 +BOOL = OperandKind.BOOL + + +def _builtin(name: str, operands: Iterable[tuple[str, OperandKind]]) -> GateDefinition: + return GateDefinition( + f"gatewire.builtin.{name}.v1", + name, + [OperandDefinition(operand_name, kind) for operand_name, kind in operands], + ) + + +RZ = _builtin("RZ", [("q0", QUBIT), ("theta", F64)]) +PhasedX = _builtin("PhasedX", [("q0", QUBIT), ("theta", F64), ("phi", F64)]) +ZZPhase = _builtin("ZZPhase", [("q0", QUBIT), ("q1", QUBIT), ("theta", F64)]) +PhasedXX = _builtin( + "PhasedXX", [("q0", QUBIT), ("q1", QUBIT), ("theta", F64), ("phi", F64)] +) + + +def builtin_gateset() -> Gateset: + return Gateset(RZ, PhasedX, ZZPhase, PhasedXX) + + +def rz(q0: int, theta: float) -> Gate: + return Gate(RZ.semantic_id, [GateValue.qubit(q0), GateValue.f64(theta)]) + + +def phased_x(q0: int, theta: float, phi: float) -> Gate: + return Gate( + PhasedX.semantic_id, + [GateValue.qubit(q0), GateValue.f64(theta), GateValue.f64(phi)], + ) + + +def zz_phase(q0: int, q1: int, theta: float) -> Gate: + return Gate( + ZZPhase.semantic_id, + [GateValue.qubit(q0), GateValue.qubit(q1), GateValue.f64(theta)], + ) + + +def phased_xx(q0: int, q1: int, theta: float, phi: float) -> Gate: + return Gate( + PhasedXX.semantic_id, + [ + GateValue.qubit(q0), + GateValue.qubit(q1), + GateValue.f64(theta), + GateValue.f64(phi), + ], + ) diff --git a/selene-core/rust/encoder.rs b/selene-core/rust/encoder.rs index 5b635abf..8c70ecc5 100644 --- a/selene-core/rust/encoder.rs +++ b/selene-core/rust/encoder.rs @@ -6,7 +6,7 @@ pub enum OutputStreamError { #[error("IO Error: {0}")] IoError(std::io::Error), #[error("Empty arrays are not allowed")] - EmptyArrayError, // A zero-length array of non-string primatives is handled as a single + EmptyArrayError, // A zero-length array of non-string primitives is handled as a single // element. #[error("Array size {0} exceeds maximum size")] OversizeArrayError(usize), diff --git a/selene-core/rust/error_model.rs b/selene-core/rust/error_model.rs index 4d5fe0f1..2bd74592 100644 --- a/selene-core/rust/error_model.rs +++ b/selene-core/rust/error_model.rs @@ -3,12 +3,14 @@ use anyhow::{Result, anyhow}; use std::ffi::OsStr; use std::sync; +use crate::gatewire::DynamicGateSet; pub mod helper; pub mod inline; pub mod interface; pub mod plugin; pub mod version; use crate::operation::BatchOperation; +use crate::plugin as plugin_utils; pub use inline::{ErrorModelFFIAdapter, ErrorModelHandle, ErrorModelOperationInterface}; pub use interface::{ErrorModelInterface, ErrorModelInterfaceFactory}; pub use version::ErrorModelAPIVersion; @@ -144,6 +146,15 @@ impl ErrorModelInterface for ErrorModel { ) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + plugin_utils::negotiate_gateset( + "ErrorModel", + self.handle.instance, + Some(self.handle.interface.negotiate_gateset_fn), + gateset, + ) + } + fn handle_operations( &mut self, operations: BatchOperation, diff --git a/selene-core/rust/error_model/helper.rs b/selene-core/rust/error_model/helper.rs index 03b76fd7..91610d96 100644 --- a/selene-core/rust/error_model/helper.rs +++ b/selene-core/rust/error_model/helper.rs @@ -10,6 +10,7 @@ use super::{ use crate::operation::plugin::{ BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInterface, }; +use crate::plugin::write_negotiated_gateset; use crate::simulator::Simulator; use crate::simulator::inline::SimulatorHandle; use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; @@ -96,6 +97,24 @@ impl Helper { ) } + pub unsafe fn negotiate_gateset( + instance: ErrorModelInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + result_to_errno( + "Failed to negotiate gateset", + Self::with_error_model_instance(instance, |error_model| unsafe { + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + error_model.negotiate_gateset(gateset) + }) + }), + ) + } + pub unsafe fn handle_operations( instance: ErrorModelInstance, batch: RuntimeExtractOperationHandle, @@ -172,7 +191,7 @@ macro_rules! export_error_model_plugin { ErrorModelSetResultHandle, ErrorModelSetResultInstance, ErrorModelSetResultInterface, }, - version::CURRENT_API_VERSION, + version::current_api_version, }, operation::plugin::{ BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInstance, @@ -265,6 +284,19 @@ macro_rules! export_error_model_plugin { Helper::shot_end(instance) } + /// Negotiate the gates accepted from the runtime and return the gates this error + /// model may emit to the simulator, encoded as a gatewire gateset. + unsafe extern "C" fn selene_error_model_negotiate_gateset( + instance: ErrorModelInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + Helper::negotiate_gateset(instance, input, input_len, output, output_len, written) + } + /// This function is called to handle a batch of operations extracted from the /// runtime. It is responsible for processing the operations and returning the /// results of any measurements provided in the operation list. @@ -346,13 +378,14 @@ macro_rules! export_error_model_plugin { selene_core::export_plugin_descriptor_v1!( selene_error_model_plugin_descriptor_v1, ErrorModelPluginDescriptorV1, - CURRENT_API_VERSION.as_u64(), + current_api_version().as_u64(), { - init_fn: selene_error_model_init, + init_fn: Some(selene_error_model_init), exit_fn: Some(selene_error_model_exit), - shot_start_fn: selene_error_model_shot_start, - shot_end_fn: selene_error_model_shot_end, - handle_operations_fn: selene_error_model_handle_operations, + shot_start_fn: Some(selene_error_model_shot_start), + shot_end_fn: Some(selene_error_model_shot_end), + negotiate_gateset_fn: Some(selene_error_model_negotiate_gateset), + handle_operations_fn: Some(selene_error_model_handle_operations), get_metrics_fn: Some(selene_error_model_get_metrics), } ); diff --git a/selene-core/rust/error_model/inline.rs b/selene-core/rust/error_model/inline.rs index 98719ace..12da95e7 100644 --- a/selene-core/rust/error_model/inline.rs +++ b/selene-core/rust/error_model/inline.rs @@ -6,6 +6,7 @@ use crate::{ operation::plugin::{ BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInterface, }, + plugin::write_negotiated_gateset, simulator::{Simulator, inline::SimulatorHandle}, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -36,6 +37,7 @@ impl ErrorModelFFIAdapter { exit_fn: Self::exit, shot_start_fn: Self::shot_start, shot_end_fn: Self::shot_end, + negotiate_gateset_fn: Self::negotiate_gateset, handle_operations_fn: Self::handle_operations, get_metrics_fn: Self::get_metrics, _marker: PhantomData, @@ -79,6 +81,23 @@ impl ErrorModelFFIAdapter { }) } + unsafe extern "C" fn negotiate_gateset( + instance: ErrorModelInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + result_to_errno("ErrorModelFFIAdapter: negotiate_gateset failed", unsafe { + Self::with_error_model(instance, |error_model| { + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + error_model.negotiate_gateset(gateset) + }) + }) + }) + } + unsafe extern "C" fn handle_operations( instance: ErrorModelInstance, batch: RuntimeExtractOperationHandle, @@ -151,5 +170,13 @@ pub struct ErrorModelOperationInterface<'a> { ) -> Errno, pub get_metrics_fn: unsafe extern "C" fn(ErrorModelInstance, u8, *mut ffi::c_char, *mut u8, *mut u64) -> Errno, + pub negotiate_gateset_fn: unsafe extern "C" fn( + ErrorModelInstance, + *const u8, + usize, + *mut u8, + usize, + *mut usize, + ) -> Errno, _marker: PhantomData<&'a ()>, } diff --git a/selene-core/rust/error_model/interface.rs b/selene-core/rust/error_model/interface.rs index b1525dae..27a458bd 100644 --- a/selene-core/rust/error_model/interface.rs +++ b/selene-core/rust/error_model/interface.rs @@ -2,13 +2,14 @@ use anyhow::Result; use std::sync::Arc; use crate::error_model::BatchResult; +use crate::gatewire::DynamicGateSet; use crate::operation::BatchOperation; use crate::simulator::SimulatorInterface; use crate::utils::MetricValue; /// Instances of error model plugins implement this interface. /// -/// Many instances of a plugin may exist simultaneously. Instance are +/// Many instances of a plugin may exist simultaneously. Instances are /// generically constructed by impls of [ErrorModelInterfaceFactory]. /// /// All functions can return an error, which will usually result in aborting the @@ -18,13 +19,12 @@ use crate::utils::MetricValue; /// [crate::export_error_model_plugin!] pub trait ErrorModelInterface { /// Signals that the instance of the error model plugin should cleanup. Plugins - /// should `Err`` from any functions called on an instance after `exit`. They can + /// should return `Err` from any functions called on an instance after `exit`. They can /// also return an error from `exit` itself if an expected condition is not met. fn exit(&mut self) -> Result<()>; /// Called to signal that the error model should proceed to the next shot. It should /// reset all state that has an impact on the next shot, such as caches. A new random - /// seed is provided, and the error model should reseed its RNG based on this. It should - /// also invoke shot_start on its underlying simulator with the provided new_simulator_seed. + /// seed is provided, and the error model should reseed its RNG based on this. /// /// It is important that reseeding is performed. This way, a single shot can be /// deterministically repeated by reseeding the error model with the same seed, @@ -32,6 +32,13 @@ pub trait ErrorModelInterface { fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()>; /// Called to signal that the current shot has ended fn shot_end(&mut self) -> Result<()>; + + /// Negotiate the gates accepted from the runtime and return the gates this + /// error model may emit to the simulator. + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + Ok(gateset.clone()) + } + /// Provide the error model with a batch of quantum operations from the runtime. /// The error model should perform any required measurements and return them in the /// BatchResult upon success. diff --git a/selene-core/rust/error_model/plugin.rs b/selene-core/rust/error_model/plugin.rs index e4375b83..ad8cc4c4 100644 --- a/selene-core/rust/error_model/plugin.rs +++ b/selene-core/rust/error_model/plugin.rs @@ -2,10 +2,14 @@ use super::{ BatchResult, BoolResult, ErrorModelAPIVersion, ErrorModelInterface, ErrorModelInterfaceFactory, U64Result, }; +use crate::gatewire::DynamicGateSet; use crate::operation::BatchOperation; +use crate::plugin::{ + NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + require_callback, validate_descriptor_v1, +}; use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; use anyhow::{Result, anyhow}; -use libloading; use std::ffi::OsStr; use std::{ffi, sync::Arc}; @@ -17,25 +21,31 @@ pub type Errno = i32; pub struct ErrorModelPluginDescriptorV1 { pub struct_size: u64, pub api_version: u64, - pub init_fn: unsafe extern "C" fn( - handle: *mut ErrorModelInstance, - n_qubits: u64, - error_model_argc: u32, - error_model_argv: *const *const ffi::c_char, - ) -> Errno, + pub init_fn: Option< + unsafe extern "C" fn( + handle: *mut ErrorModelInstance, + n_qubits: u64, + error_model_argc: u32, + error_model_argv: *const *const ffi::c_char, + ) -> Errno, + >, pub exit_fn: Option Errno>, - pub shot_start_fn: unsafe extern "C" fn( - handle: ErrorModelInstance, - shot_id: u64, - error_model_seed: u64, - ) -> Errno, - pub shot_end_fn: unsafe extern "C" fn(handle: ErrorModelInstance) -> Errno, - pub handle_operations_fn: unsafe extern "C" fn( - handle: ErrorModelInstance, - batch: crate::operation::plugin::RuntimeExtractOperationHandle, - simulator: crate::simulator::inline::SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, - ) -> Errno, + pub shot_start_fn: Option< + unsafe extern "C" fn( + handle: ErrorModelInstance, + shot_id: u64, + error_model_seed: u64, + ) -> Errno, + >, + pub shot_end_fn: Option Errno>, + pub handle_operations_fn: Option< + unsafe extern "C" fn( + handle: ErrorModelInstance, + batch: crate::operation::plugin::RuntimeExtractOperationHandle, + simulator: crate::simulator::inline::SimulatorHandle<'static>, + result: ErrorModelSetResultHandle, + ) -> Errno, + >, pub get_metrics_fn: Option< unsafe extern "C" fn( handle: ErrorModelInstance, @@ -45,6 +55,28 @@ pub struct ErrorModelPluginDescriptorV1 { out_data: *mut u64, ) -> Errno, >, + pub negotiate_gateset_fn: Option< + unsafe extern "C" fn( + handle: ErrorModelInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno, + >, +} + +impl PluginDescriptorV1 for ErrorModelPluginDescriptorV1 { + const KIND: &'static str = "Error model"; + + fn struct_size(&self) -> u64 { + self.struct_size + } + + fn api_version(&self) -> u64 { + self.api_version + } } /// Provides an error model backend that controls a plugin, in the form of a shared object. @@ -56,7 +88,7 @@ pub struct ErrorModelPluginDescriptorV1 { /// This interface allows implementations of behaviour to be written and distributed independently /// of selene. Users should be cautious about the plugins they use, as it is possible that mistakes /// or malicious code could be present in the plugin, and as with all external libraries, due -/// dilligence must be done to verify the source and the trustworthiness of the provider. +/// diligence must be done to verify the source and the trustworthiness of the provider. pub struct ErrorModelPluginInterface { _lib: libloading::Library, init_fn: unsafe extern "C" fn( @@ -72,6 +104,7 @@ pub struct ErrorModelPluginInterface { error_model_seed: u64, ) -> Errno, shot_end_fn: unsafe extern "C" fn(handle: ErrorModelInstance) -> Errno, + negotiate_gateset_fn: Option>, handle_operations_fn: unsafe extern "C" fn( handle: ErrorModelInstance, batch: crate::operation::plugin::RuntimeExtractOperationHandle, @@ -91,48 +124,34 @@ pub struct ErrorModelPluginInterface { impl ErrorModelPluginInterface { pub fn new_from_file(plugin_file: impl AsRef) -> Result> { - let lib = unsafe { libloading::Library::new(plugin_file.as_ref()) }.map_err(|e| { - anyhow!( - "Failed to load error model plugin: {}. Error: {}", - plugin_file.as_ref().to_string_lossy(), - e - ) - })?; + let lib = load_library("error model", &plugin_file)?; let descriptor = unsafe { - lib.get::(b"selene_error_model_plugin_descriptor_v1") - .ok() - .map(|d| *d) - .or_else(|| { - lib.get:: *const ErrorModelPluginDescriptorV1>( - b"selene_error_model_get_plugin_descriptor_v1", - ) - .ok() - .and_then(|f| { - let ptr = f(); - if ptr.is_null() { None } else { Some(*ptr) } - }) - }) - } - .ok_or_else(|| { - anyhow!( - "Error model plugin '{}' does not expose either selene_error_model_plugin_descriptor_v1 or selene_error_model_get_plugin_descriptor_v1", - plugin_file.as_ref().to_string_lossy() + load_descriptor_v1::( + &lib, + &plugin_file, + b"selene_error_model_plugin_descriptor_v1", + b"selene_error_model_get_plugin_descriptor_v1", ) + }?; + validate_descriptor_v1(&descriptor, |api_version| { + ErrorModelAPIVersion::from(api_version).validate() })?; - let version: ErrorModelAPIVersion = descriptor.api_version.into(); - version.validate()?; - if descriptor.struct_size < core::mem::size_of::() as u64 { - return Err(anyhow!( - "Error model plugin descriptor is too small for v1 ABI" - )); - } Ok(Arc::new(Self { _lib: lib, - init_fn: descriptor.init_fn, + init_fn: require_callback("Error model", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, - shot_start_fn: descriptor.shot_start_fn, - shot_end_fn: descriptor.shot_end_fn, - handle_operations_fn: descriptor.handle_operations_fn, + shot_start_fn: require_callback( + "Error model", + "shot_start_fn", + descriptor.shot_start_fn, + )?, + shot_end_fn: require_callback("Error model", "shot_end_fn", descriptor.shot_end_fn)?, + negotiate_gateset_fn: descriptor.negotiate_gateset_fn, + handle_operations_fn: require_callback( + "Error model", + "handle_operations_fn", + descriptor.handle_operations_fn, + )?, get_metrics_fn: descriptor.get_metrics_fn, })) } @@ -188,6 +207,15 @@ impl ErrorModelInterface for ErrorModelPlugin { || anyhow!("ErrorModelPlugin: shot_end failed"), ) } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_gateset( + "ErrorModelPlugin", + self.instance, + self.interface.negotiate_gateset_fn, + gateset, + ) + } fn handle_operations( &mut self, operations: BatchOperation, @@ -273,7 +301,7 @@ impl BatchResultBuilder { } /// An instance is provided to `selene_runtime_get_next_operations`, which must -/// pass that back to any function it calls in it's provided +/// pass that back to any function it calls in its provided /// [ErrorModelSetResultInterface]. pub type ErrorModelSetResultInstance = *mut ffi::c_void; diff --git a/selene-core/rust/error_model/version.rs b/selene-core/rust/error_model/version.rs index 89ce2dc8..512c8e1e 100644 --- a/selene-core/rust/error_model/version.rs +++ b/selene-core/rust/error_model/version.rs @@ -32,13 +32,17 @@ impl From for u64 { } } -pub const CURRENT_API_VERSION: ErrorModelAPIVersion = ErrorModelAPIVersion { +pub(crate) const CURRENT_API_VERSION: ErrorModelAPIVersion = ErrorModelAPIVersion { reserved: 0, major: 0, minor: 2, patch: 0, }; +pub const fn current_api_version() -> ErrorModelAPIVersion { + CURRENT_API_VERSION +} + // Changelog: // 0.1.0: Initial version. // 0.2.0: Replaced set_measurement_result with set_bool_result and set_u64_result in diff --git a/selene-core/rust/examples/rust_transformer.rs b/selene-core/rust/examples/rust_transformer.rs new file mode 100644 index 00000000..d4355205 --- /dev/null +++ b/selene-core/rust/examples/rust_transformer.rs @@ -0,0 +1,58 @@ +use gatewire::builtin::{PhasedXX, PhasedX, RZ, ZZPhase}; +use gatewire::{Angle, GateError, GateSet, Qubit, TryDecode}; + +#[derive(Clone, Debug, PartialEq, gatewire::GateSet)] +enum InputGate { + RZ(RZ), + PhasedX(PhasedX), + PhasedXX(PhasedXX), +} + +#[derive(Clone, Debug, PartialEq, gatewire::GateSet)] +enum OutputGate { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), +} + +fn transform(gate: InputGate) -> OutputGate { + match gate { + InputGate::RZ(g) => OutputGate::RZ(g), + InputGate::PhasedX(g) => OutputGate::PhasedX(g), + InputGate::PhasedXX(g) => { + // Just an example, this isn't real + OutputGate::ZZPhase(ZZPhase { q0: g.q0, q1: g.q1, theta: g.theta + g.phi}) + } + } +} + +fn main() -> Result<(), GateError> { + let input_set = GateSet::::new()?; + let output_set = GateSet::::new()?; + + let incoming = InputGate::PhasedXX(PhasedXX { + q0: Qubit(0), + q1: Qubit(1), + theta: Angle(0.25), + phi: Angle(1.25), + }); + + let incoming_wire = input_set.serialize_gate(&incoming)?; + + let decoded = match input_set.decode(&incoming_wire)? { + TryDecode::Decoded(gate) => gate, + TryDecode::Unknown(_) => return Err(GateError::UnknownGate), + }; + + let outgoing = transform(decoded); + let outgoing_wire = output_set.serialize_gate(&outgoing)?; + let roundtrip = output_set + .try_deserialize(&outgoing_wire)? + .ok_or(GateError::UnknownGate)?; + + println!("input: {incoming:?}"); + println!("output: {roundtrip:?}"); + println!("wire bytes: {} -> {}", incoming_wire.len(), outgoing_wire.len()); + + Ok(()) +} diff --git a/selene-core/rust/gatewire.rs b/selene-core/rust/gatewire.rs new file mode 100644 index 00000000..ee02f8e6 --- /dev/null +++ b/selene-core/rust/gatewire.rs @@ -0,0 +1,46 @@ +//! Standalone gate definition and gateset transport library. +//! +//! The public Rust API is typed and enum-oriented. The C ABI is deliberately +//! explicit: raw views, opaque handles, status codes, and stable wire bytes. + +extern crate self as gatewire; + +pub mod builtin; +pub mod ffi; + +mod decl; +mod dynamic; +mod error; +mod id; +mod instance; +mod operand; +mod typed; +mod wire; + +pub use decl::{GateDecl, OperandSpec, SmallOperandSpecs}; +pub use dynamic::{DynamicGateSet, LocalGateId}; +pub use error::GateError; +pub use ffi::{ + GwDecodedGate, GwGateDeclInfo, GwGateDeclView, GwGateInstanceView, GwGateSet, GwGateValue, + GwGateValueData, GwOperandDeclInfo, GwOperandDeclView, GwSemanticId, GwStatus, +}; +pub use id::GateSemanticId; +pub use instance::OwnedGateInstance; +pub use operand::{ + Angle, GW_OPERAND_KIND_BOOL, GW_OPERAND_KIND_F64, GW_OPERAND_KIND_I64, GW_OPERAND_KIND_QUBIT, + GW_OPERAND_KIND_U8, GW_OPERAND_KIND_U64, GateOperand, GateValue, OperandKind, Qubit, + SmallGateValues, +}; +pub use typed::{GateSet, GateSetSpec, GateSpec, TryDecode}; + +pub mod prelude { + pub use crate::gatewire::builtin; + pub use crate::gatewire::{ + Angle, DynamicGateSet, GateDecl, GateError, GateOperand, GateSemanticId, GateSet, + GateSetSpec, GateSpec, GateValue, OperandKind, OperandSpec, OwnedGateInstance, Qubit, + TryDecode, + }; +} + +#[cfg(test)] +mod tests; diff --git a/selene-core/rust/gatewire/builtin.rs b/selene-core/rust/gatewire/builtin.rs new file mode 100644 index 00000000..66d3e3e9 --- /dev/null +++ b/selene-core/rust/gatewire/builtin.rs @@ -0,0 +1,22 @@ +use crate::gatewire::*; + +// Note: if a gate is updated, provide the version with +// pub struct GateName [ version = N ] { ... } + +crate::define_builtin_gates! { + // We use the same names as TKET for the gates for consistency + pub struct RZ { q0: Qubit, theta: Angle } + pub struct PhasedX { q0: Qubit, theta: Angle, phi: Angle } + pub struct ZZPhase { q0: Qubit, q1: Qubit, theta: Angle } + pub struct PhasedXX { q0: Qubit, q1: Qubit, theta: Angle, phi: Angle} +} + +pub fn all() -> DynamicGateSet { + DynamicGateSet::from_declarations(vec![ + RZ::declaration(), + PhasedX::declaration(), + ZZPhase::declaration(), + PhasedXX::declaration(), + ]) + .expect("builtin gate declarations are unique") +} diff --git a/selene-core/rust/gatewire/cbindgen.toml b/selene-core/rust/gatewire/cbindgen.toml new file mode 100644 index 00000000..4456fb68 --- /dev/null +++ b/selene-core/rust/gatewire/cbindgen.toml @@ -0,0 +1,72 @@ +language = "C" +include_guard = "SELENE_GATEWIRE_H" +cpp_compat = true +usize_is_size_t = true +sys_includes = ["stddef.h", "stdint.h"] +style = "type" + +[export] +include = [ + "GwStatus", + "GW_STATUS_OK", + "GW_STATUS_NULL_POINTER", + "GW_STATUS_INVALID_ARGUMENT", + "GW_STATUS_DUPLICATE_GATE", + "GW_STATUS_NOT_FOUND", + "GW_STATUS_BUFFER_TOO_SMALL", + "GW_STATUS_DECODE_ERROR", + "GW_STATUS_VALIDATION_ERROR", + "GW_STATUS_PANIC", + "GW_OPERAND_KIND_QUBIT", + "GW_OPERAND_KIND_F64", + "GW_OPERAND_KIND_U64", + "GW_OPERAND_KIND_I64", + "GW_OPERAND_KIND_U8", + "GW_OPERAND_KIND_BOOL", + "GwSemanticId", + "GwGateSet", + "GwOperandDeclView", + "GwGateDeclView", + "GwGateDeclInfo", + "GwOperandDeclInfo", + "GwGateValueData", + "GwGateValue", + "GwGateInstanceView", + "GwDecodedGate", + "gw_status_message", + "gw_semantic_id_from_text", + "gw_semantic_id_eq", + "gw_builtin_rz_semantic_id", + "gw_builtin_phased_x_semantic_id", + "gw_builtin_zz_phase_semantic_id", + "gw_builtin_phased_xx_semantic_id", + "gw_gateset_new", + "gw_builtin_gateset_new", + "gw_gateset_free", + "gw_gateset_add_decl", + "gw_gateset_add_builtin_rz", + "gw_gateset_add_builtin_phased_x", + "gw_gateset_add_builtin_zz_phase", + "gw_gateset_add_builtin_phased_xx", + "gw_gateset_len", + "gw_gateset_contains", + "gw_gateset_decl_at", + "gw_gateset_decl_operand_at", + "gw_gateset_serialized_len", + "gw_gateset_serialize", + "gw_gateset_deserialize", + "gw_gate_serialized_len", + "gw_gate_serialize", + "gw_gate_deserialize", + "gw_decoded_gate_free", + "gw_decoded_gate_semantic_id", + "gw_decoded_gate_value_count", + "gw_decoded_gate_value_at", + "gw_gateset_validate_decoded", +] +exclude = [ + "ErrorModelAPIVersion", + "RuntimeAPIVersion", + "SimulatorAPIVersion", +] +item_types = ["constants", "functions", "structs", "unions", "opaque", "typedefs"] diff --git a/selene-core/rust/gatewire/decl.rs b/selene-core/rust/gatewire/decl.rs new file mode 100644 index 00000000..d725c54f --- /dev/null +++ b/selene-core/rust/gatewire/decl.rs @@ -0,0 +1,46 @@ +use crate::gatewire::{GateSemanticId, OperandKind}; +use smallvec::SmallVec; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OperandSpec { + pub name: String, + pub kind: OperandKind, +} + +pub type SmallOperandSpecs = SmallVec<[OperandSpec; 4]>; + +impl OperandSpec { + pub fn new(name: impl Into, kind: OperandKind) -> Self { + Self { + name: name.into(), + kind, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GateDecl { + pub semantic_id: GateSemanticId, + pub name: String, + pub operands: SmallOperandSpecs, + pub version: u32, +} + +impl GateDecl { + pub fn new( + semantic_id: GateSemanticId, + name: impl Into, + operands: I, + version: u32, + ) -> Self + where + I: IntoIterator, + { + Self { + semantic_id, + name: name.into(), + operands: operands.into_iter().collect(), + version, + } + } +} diff --git a/selene-core/rust/gatewire/dynamic.rs b/selene-core/rust/gatewire/dynamic.rs new file mode 100644 index 00000000..7ef121ba --- /dev/null +++ b/selene-core/rust/gatewire/dynamic.rs @@ -0,0 +1,118 @@ +use crate::gatewire::{GateDecl, GateError, GateSemanticId, OwnedGateInstance, wire}; +use indexmap::IndexMap; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct LocalGateId(pub u32); + +#[derive(Clone, Debug)] +pub struct DynamicGateSet { + declarations: IndexMap, +} + +impl DynamicGateSet { + pub fn new() -> Self { + Self { + declarations: IndexMap::new(), + } + } + + pub fn from_declarations(declarations: I) -> Result + where + I: IntoIterator, + { + let mut set = Self::new(); + for decl in declarations { + set.add(decl)?; + } + Ok(set) + } + + pub fn add(&mut self, decl: GateDecl) -> Result { + if self.declarations.contains_key(&decl.semantic_id) { + return Err(GateError::DuplicateGate(decl.name)); + } + let local_id = LocalGateId(self.declarations.len() as u32); + self.declarations.insert(decl.semantic_id, decl); + Ok(local_id) + } + + pub fn len(&self) -> usize { + self.declarations.len() + } + pub fn is_empty(&self) -> bool { + self.declarations.is_empty() + } + pub fn declarations(&self) -> impl ExactSizeIterator + '_ { + self.declarations.values() + } + pub fn declaration(&self, id: GateSemanticId) -> Option<&GateDecl> { + self.declarations.get(&id) + } + pub fn declaration_at(&self, index: usize) -> Option<&GateDecl> { + self.declarations.get_index(index).map(|(_, decl)| decl) + } + pub fn local_id(&self, id: GateSemanticId) -> Option { + self.declarations + .get_index_of(&id) + .map(|index| LocalGateId(index as u32)) + } + pub fn contains(&self, id: GateSemanticId) -> bool { + self.declarations.contains_key(&id) + } + + pub fn is_subset_of(&self, other: &Self) -> bool { + self.declarations + .keys() + .all(|semantic_id| other.contains(*semantic_id)) + } + + pub fn is_superset_of(&self, other: &Self) -> bool { + other.is_subset_of(self) + } + + pub fn first_unsupported_by(&self, supported: &Self) -> Option<&GateDecl> { + self.declarations() + .find(|decl| !supported.contains(decl.semantic_id)) + } + + /// Returns Ok(false) when the gate is well-formed but not in this gateset. + /// Returns Err when the gate claims to be in this gateset but has invalid operands. + pub fn validate_instance(&self, gate: &OwnedGateInstance) -> Result { + let Some(decl) = self.declaration(gate.semantic_id) else { + return Ok(false); + }; + if decl.operands.len() != gate.operands.len() { + return Err(GateError::WrongArity { + gate: decl.name.clone(), + expected: decl.operands.len(), + actual: gate.operands.len(), + }); + } + for (index, (expected, actual)) in + decl.operands.iter().zip(gate.operands.iter()).enumerate() + { + if expected.kind != actual.kind() { + return Err(GateError::WrongOperandKind { + gate: decl.name.clone(), + index, + expected: expected.kind, + actual: actual.kind(), + }); + } + } + Ok(true) + } + + pub fn serialize(&self) -> Vec { + wire::serialize_gateset(self) + } + pub fn deserialize(data: &[u8]) -> Result { + wire::deserialize_gateset(data) + } +} + +impl Default for DynamicGateSet { + fn default() -> Self { + Self::new() + } +} diff --git a/selene-core/rust/gatewire/error.rs b/selene-core/rust/gatewire/error.rs new file mode 100644 index 00000000..6fdb5447 --- /dev/null +++ b/selene-core/rust/gatewire/error.rs @@ -0,0 +1,37 @@ +use crate::gatewire::OperandKind; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum GateError { + #[error("duplicate gate declaration `{0}`")] + DuplicateGate(String), + #[error("unknown gate")] + UnknownGate, + #[error("invalid operand kind `{0}`")] + InvalidKind(u32), + #[error("wrong arity for gate `{gate}`: expected {expected}, got {actual}")] + WrongArity { + gate: String, + expected: usize, + actual: usize, + }, + #[error( + "wrong operand kind for gate `{gate}` operand {index}: expected {expected:?}, got {actual:?}" + )] + WrongOperandKind { + gate: String, + index: usize, + expected: OperandKind, + actual: OperandKind, + }, + #[error("decode error: {0}")] + Decode(&'static str), + #[error("invalid UTF-8 string")] + Utf8(#[from] std::string::FromUtf8Error), + #[error("invalid UTF-8 slice")] + Utf8Slice(#[from] std::str::Utf8Error), + #[error("buffer too small")] + BufferTooSmall, + #[error("null pointer")] + NullPointer, +} diff --git a/selene-core/rust/gatewire/ffi.rs b/selene-core/rust/gatewire/ffi.rs new file mode 100644 index 00000000..2ce95658 --- /dev/null +++ b/selene-core/rust/gatewire/ffi.rs @@ -0,0 +1,7 @@ +mod convert; +mod functions; +mod status; +pub mod types; + +pub use functions::*; +pub use types::*; diff --git a/selene-core/rust/gatewire/ffi/convert.rs b/selene-core/rust/gatewire/ffi/convert.rs new file mode 100644 index 00000000..d56bfa8f --- /dev/null +++ b/selene-core/rust/gatewire/ffi/convert.rs @@ -0,0 +1,173 @@ +use crate::gatewire::ffi::types::*; +use crate::gatewire::{ + DynamicGateSet, GateDecl, GateError, GateSemanticId, GateValue, OperandKind, OperandSpec, + OwnedGateInstance, +}; +use std::ffi::c_char; +use std::{mem, ptr, slice, str}; + +pub(crate) unsafe fn as_set<'a>(set: *const GwGateSet) -> Result<&'a DynamicGateSet, GateError> { + if set.is_null() { + return Err(GateError::NullPointer); + } + unsafe { Ok(&*(set as *const DynamicGateSet)) } +} + +pub(crate) unsafe fn as_set_mut<'a>( + set: *mut GwGateSet, +) -> Result<&'a mut DynamicGateSet, GateError> { + if set.is_null() { + return Err(GateError::NullPointer); + } + unsafe { Ok(&mut *(set as *mut DynamicGateSet)) } +} + +pub(crate) unsafe fn as_decoded<'a>( + gate: *const GwDecodedGate, +) -> Result<&'a OwnedGateInstance, GateError> { + if gate.is_null() { + return Err(GateError::NullPointer); + } + unsafe { Ok(&*(gate as *const OwnedGateInstance)) } +} + +pub(crate) unsafe fn read_bytes<'a>(ptr: *const u8, len: usize) -> Result<&'a [u8], GateError> { + if len == 0 { + return Ok(&[]); + } + if ptr.is_null() { + return Err(GateError::NullPointer); + } + unsafe { Ok(slice::from_raw_parts(ptr, len)) } +} + +pub(crate) unsafe fn read_text<'a>(ptr: *const c_char, len: usize) -> Result<&'a str, GateError> { + unsafe { Ok(str::from_utf8(read_bytes(ptr.cast::(), len)?)?) } +} + +unsafe fn read_str(ptr: *const c_char, len: usize) -> Result { + unsafe { Ok(read_text(ptr, len)?.to_owned()) } +} + +fn check_abi_size(actual: usize, expected: usize) -> Result<(), GateError> { + if actual != 0 && actual < expected { + Err(GateError::Decode("ffi struct abi_size is too small")) + } else { + Ok(()) + } +} + +pub(crate) unsafe fn gate_decl_from_view(view: &GwGateDeclView) -> Result { + check_abi_size(view.abi_size, mem::size_of::())?; + unsafe { + let name = read_str(view.name_ptr, view.name_len)?; + let operands_raw = if view.operands_len == 0 { + &[] + } else { + if view.operands_ptr.is_null() { + return Err(GateError::NullPointer); + } + slice::from_raw_parts(view.operands_ptr, view.operands_len) + }; + let mut operands = Vec::with_capacity(operands_raw.len()); + for raw in operands_raw { + check_abi_size(raw.abi_size, mem::size_of::())?; + operands.push(OperandSpec::new( + read_str(raw.name_ptr, raw.name_len)?, + OperandKind::from_u32(raw.kind)?, + )); + } + Ok(GateDecl::new( + GateSemanticId::from(view.semantic_id), + name, + operands, + view.version, + )) + } +} + +pub(crate) unsafe fn gate_instance_from_view( + view: &GwGateInstanceView, +) -> Result { + check_abi_size(view.abi_size, mem::size_of::())?; + unsafe { + let values_raw = if view.values_len == 0 { + &[] + } else { + if view.values_ptr.is_null() { + return Err(GateError::NullPointer); + } + slice::from_raw_parts(view.values_ptr, view.values_len) + }; + let mut values = Vec::with_capacity(values_raw.len()); + for raw in values_raw { + values.push(gate_value_from_view(raw)?); + } + Ok(OwnedGateInstance::new( + GateSemanticId::from(view.semantic_id), + values, + )) + } +} + +unsafe fn gate_value_from_view(view: &GwGateValue) -> Result { + check_abi_size(view.abi_size, mem::size_of::())?; + unsafe { + match OperandKind::from_u32(view.kind)? { + OperandKind::Qubit => Ok(GateValue::Qubit(view.data.qubit)), + OperandKind::F64 => Ok(GateValue::F64(view.data.f64_value)), + OperandKind::U64 => Ok(GateValue::U64(view.data.u64_value)), + OperandKind::I64 => Ok(GateValue::I64(view.data.i64_value)), + OperandKind::U8 => Ok(GateValue::U8(view.data.u8_value)), + OperandKind::Bool => match view.data.bool_value { + 0 => Ok(GateValue::Bool(false)), + 1 => Ok(GateValue::Bool(true)), + _ => Err(GateError::Decode("invalid bool value")), + }, + } + } +} + +pub(crate) fn value_to_view(value: &GateValue) -> GwGateValue { + let data = match value { + GateValue::Qubit(v) => GwGateValueData { qubit: *v }, + GateValue::F64(v) => GwGateValueData { f64_value: *v }, + GateValue::U64(v) => GwGateValueData { u64_value: *v }, + GateValue::I64(v) => GwGateValueData { i64_value: *v }, + GateValue::U8(v) => GwGateValueData { u8_value: *v }, + GateValue::Bool(v) => GwGateValueData { + bool_value: u8::from(*v), + }, + }; + GwGateValue { + kind: value.kind().as_u32(), + data, + ..Default::default() + } +} + +pub(crate) fn write_to_out( + bytes: &[u8], + buffer: *mut u8, + buffer_len: usize, + written: *mut usize, +) -> Result<(), GateError> { + if !written.is_null() { + unsafe { + *written = bytes.len(); + } + } + if buffer_len < bytes.len() { + return Err(GateError::BufferTooSmall); + } + if bytes.is_empty() { + return Ok(()); + } + if buffer.is_null() { + return Err(GateError::NullPointer); + } + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, bytes.len()); + } + Ok(()) +} diff --git a/selene-core/rust/gatewire/ffi/functions.rs b/selene-core/rust/gatewire/ffi/functions.rs new file mode 100644 index 00000000..15d88cb6 --- /dev/null +++ b/selene-core/rust/gatewire/ffi/functions.rs @@ -0,0 +1,410 @@ +use super::convert::*; +use super::status::ffi_result; +use super::types::*; +use crate::gatewire::{ + DynamicGateSet, GateError, GateSemanticId, GateSpec, OwnedGateInstance, builtin, +}; +use std::ffi::c_char; +use std::mem; + +#[unsafe(no_mangle)] +pub extern "C" fn gw_status_message(status: GwStatus) -> *const c_char { + let message: &'static [u8] = match status { + GW_STATUS_OK => b"ok\0", + GW_STATUS_NULL_POINTER => b"null pointer\0", + GW_STATUS_INVALID_ARGUMENT => b"invalid argument\0", + GW_STATUS_DUPLICATE_GATE => b"duplicate gate\0", + GW_STATUS_NOT_FOUND => b"not found\0", + GW_STATUS_BUFFER_TOO_SMALL => b"buffer too small\0", + GW_STATUS_DECODE_ERROR => b"decode error\0", + GW_STATUS_VALIDATION_ERROR => b"validation error\0", + GW_STATUS_PANIC => b"panic across FFI boundary\0", + _ => b"unknown status\0", + }; + message.as_ptr() as *const c_char +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_semantic_id_from_text( + ptr: *const c_char, + len: usize, + out: *mut GwSemanticId, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let text = read_text(ptr, len)?; + *out = GateSemanticId::from_text(text).into(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_semantic_id_eq(a: GwSemanticId, b: GwSemanticId) -> u8 { + u8::from(a.bytes == b.bytes) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_builtin_rz_semantic_id() -> GwSemanticId { + builtin::RZ::semantic_id().into() +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_builtin_phased_x_semantic_id() -> GwSemanticId { + builtin::PhasedX::semantic_id().into() +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_builtin_zz_phase_semantic_id() -> GwSemanticId { + builtin::ZZPhase::semantic_id().into() +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_builtin_phased_xx_semantic_id() -> GwSemanticId { + builtin::PhasedXX::semantic_id().into() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_new(out: *mut *mut GwGateSet) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = Box::into_raw(Box::new(DynamicGateSet::new())) as *mut GwGateSet; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_builtin_gateset_new(out: *mut *mut GwGateSet) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = Box::into_raw(Box::new(builtin::all())) as *mut GwGateSet; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_free(set: *mut GwGateSet) { + if !set.is_null() { + unsafe { + drop(Box::from_raw(set as *mut DynamicGateSet)); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_add_decl( + set: *mut GwGateSet, + decl: *const GwGateDeclView, +) -> GwStatus { + ffi_result(|| unsafe { + if decl.is_null() { + return Err(GateError::NullPointer); + } + let set = as_set_mut(set)?; + let decl = gate_decl_from_view(&*decl)?; + set.add(decl)?; + Ok(()) + }) +} + +fn add_builtin_gate(set: *mut GwGateSet) -> GwStatus { + ffi_result(|| unsafe { + let set = as_set_mut(set)?; + set.add(G::declaration())?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_gateset_add_builtin_rz(set: *mut GwGateSet) -> GwStatus { + add_builtin_gate::(set) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_gateset_add_builtin_phased_x(set: *mut GwGateSet) -> GwStatus { + add_builtin_gate::(set) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_gateset_add_builtin_zz_phase(set: *mut GwGateSet) -> GwStatus { + add_builtin_gate::(set) +} + +#[unsafe(no_mangle)] +pub extern "C" fn gw_gateset_add_builtin_phased_xx(set: *mut GwGateSet) -> GwStatus { + add_builtin_gate::(set) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_len(set: *const GwGateSet, out: *mut usize) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_set(set)?.len(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_contains( + set: *const GwGateSet, + id: GwSemanticId, + out: *mut u8, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = u8::from(as_set(set)?.contains(GateSemanticId::from(id))); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_decl_at( + set: *const GwGateSet, + index: usize, + out: *mut GwGateDeclInfo, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let decl = as_set(set)? + .declaration_at(index) + .ok_or(GateError::UnknownGate)?; + *out = GwGateDeclInfo { + abi_size: mem::size_of::(), + semantic_id: decl.semantic_id.into(), + name_ptr: decl.name.as_ptr().cast::(), + name_len: decl.name.len(), + operands_len: decl.operands.len(), + version: decl.version, + }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_decl_operand_at( + set: *const GwGateSet, + decl_index: usize, + operand_index: usize, + out: *mut GwOperandDeclInfo, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let decl = as_set(set)? + .declaration_at(decl_index) + .ok_or(GateError::UnknownGate)?; + let operand = decl + .operands + .get(operand_index) + .ok_or(GateError::UnknownGate)?; + *out = GwOperandDeclInfo { + abi_size: mem::size_of::(), + name_ptr: operand.name.as_ptr().cast::(), + name_len: operand.name.len(), + kind: operand.kind.as_u32(), + }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_serialized_len( + set: *const GwGateSet, + out: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_set(set)?.serialize().len(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_serialize( + set: *const GwGateSet, + buffer: *mut u8, + buffer_len: usize, + written: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + let bytes = as_set(set)?.serialize(); + write_to_out(&bytes, buffer, buffer_len, written) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_deserialize( + data: *const u8, + len: usize, + out: *mut *mut GwGateSet, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let set = DynamicGateSet::deserialize(read_bytes(data, len)?)?; + *out = Box::into_raw(Box::new(set)) as *mut GwGateSet; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gate_serialized_len( + view: *const GwGateInstanceView, + out: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + if view.is_null() || out.is_null() { + return Err(GateError::NullPointer); + } + *out = gate_instance_from_view(&*view)?.serialize().len(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gate_serialize( + view: *const GwGateInstanceView, + buffer: *mut u8, + buffer_len: usize, + written: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + if view.is_null() { + return Err(GateError::NullPointer); + } + let bytes = gate_instance_from_view(&*view)?.serialize(); + write_to_out(&bytes, buffer, buffer_len, written) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gate_deserialize( + data: *const u8, + len: usize, + out: *mut *mut GwDecodedGate, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let instance = OwnedGateInstance::deserialize(read_bytes(data, len)?)?; + *out = Box::into_raw(Box::new(instance)) as *mut GwDecodedGate; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_free(gate: *mut GwDecodedGate) { + if !gate.is_null() { + unsafe { + drop(Box::from_raw(gate as *mut OwnedGateInstance)); + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_semantic_id( + gate: *const GwDecodedGate, + out: *mut GwSemanticId, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_decoded(gate)?.semantic_id.into(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_value_count( + gate: *const GwDecodedGate, + out: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_decoded(gate)?.operands.len(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_value_at( + gate: *const GwDecodedGate, + index: usize, + out: *mut GwGateValue, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + let gate = as_decoded(gate)?; + let value = gate.operands.get(index).ok_or(GateError::UnknownGate)?; + *out = value_to_view(value); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_qubit_operand_count( + gate: *const GwDecodedGate, + out: *mut usize, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_decoded(gate)?.qubit_operand_count(); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_decoded_gate_qubit_operand_at( + gate: *const GwDecodedGate, + qubit_index: usize, + out: *mut u32, +) -> GwStatus { + ffi_result(|| unsafe { + if out.is_null() { + return Err(GateError::NullPointer); + } + *out = as_decoded(gate)? + .qubit_operands() + .nth(qubit_index) + .ok_or(GateError::UnknownGate)?; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gw_gateset_validate_decoded( + set: *const GwGateSet, + gate: *const GwDecodedGate, + out_matches: *mut u8, +) -> GwStatus { + ffi_result(|| unsafe { + if out_matches.is_null() { + return Err(GateError::NullPointer); + } + *out_matches = u8::from(as_set(set)?.validate_instance(as_decoded(gate)?)?); + Ok(()) + }) +} diff --git a/selene-core/rust/gatewire/ffi/status.rs b/selene-core/rust/gatewire/ffi/status.rs new file mode 100644 index 00000000..0dede12d --- /dev/null +++ b/selene-core/rust/gatewire/ffi/status.rs @@ -0,0 +1,30 @@ +use crate::gatewire::{GateError, ffi::types::*}; + +use std::panic::{AssertUnwindSafe, catch_unwind}; + +pub(crate) fn ffi_result(f: F) -> GwStatus +where + F: FnOnce() -> Result<(), GateError>, +{ + match catch_unwind(AssertUnwindSafe(f)) { + Ok(Ok(())) => GW_STATUS_OK, + Ok(Err(error)) => status_from_error(error), + Err(_) => GW_STATUS_PANIC, + } +} + +pub(crate) fn status_from_error(error: GateError) -> GwStatus { + match error { + GateError::DuplicateGate(_) => GW_STATUS_DUPLICATE_GATE, + GateError::UnknownGate => GW_STATUS_NOT_FOUND, + GateError::InvalidKind(_) => GW_STATUS_INVALID_ARGUMENT, + GateError::WrongArity { .. } | GateError::WrongOperandKind { .. } => { + GW_STATUS_VALIDATION_ERROR + } + GateError::Decode(_) | GateError::Utf8(_) | GateError::Utf8Slice(_) => { + GW_STATUS_DECODE_ERROR + } + GateError::BufferTooSmall => GW_STATUS_BUFFER_TOO_SMALL, + GateError::NullPointer => GW_STATUS_NULL_POINTER, + } +} diff --git a/selene-core/rust/gatewire/ffi/types.rs b/selene-core/rust/gatewire/ffi/types.rs new file mode 100644 index 00000000..2bc71fbb --- /dev/null +++ b/selene-core/rust/gatewire/ffi/types.rs @@ -0,0 +1,136 @@ +use crate::gatewire::GateSemanticId; +use std::ffi::c_char; +use std::{mem, ptr}; + +pub type GwStatus = i32; + +pub const GW_STATUS_OK: GwStatus = 0; +pub const GW_STATUS_NULL_POINTER: GwStatus = 1; +pub const GW_STATUS_INVALID_ARGUMENT: GwStatus = 2; +pub const GW_STATUS_DUPLICATE_GATE: GwStatus = 3; +pub const GW_STATUS_NOT_FOUND: GwStatus = 4; +pub const GW_STATUS_BUFFER_TOO_SMALL: GwStatus = 5; +pub const GW_STATUS_DECODE_ERROR: GwStatus = 6; +pub const GW_STATUS_VALIDATION_ERROR: GwStatus = 7; +pub const GW_STATUS_PANIC: GwStatus = 255; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct GwSemanticId { + pub bytes: [u8; 16], +} + +impl From for GateSemanticId { + fn from(value: GwSemanticId) -> Self { + Self { bytes: value.bytes } + } +} + +impl From for GwSemanticId { + fn from(value: GateSemanticId) -> Self { + Self { bytes: value.bytes } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct GwOperandDeclView { + pub abi_size: usize, + pub name_ptr: *const c_char, + pub name_len: usize, + pub kind: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct GwGateDeclView { + pub abi_size: usize, + pub semantic_id: GwSemanticId, + pub name_ptr: *const c_char, + pub name_len: usize, + pub operands_ptr: *const GwOperandDeclView, + pub operands_len: usize, + pub version: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct GwGateDeclInfo { + pub abi_size: usize, + pub semantic_id: GwSemanticId, + pub name_ptr: *const c_char, + pub name_len: usize, + pub operands_len: usize, + pub version: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct GwOperandDeclInfo { + pub abi_size: usize, + pub name_ptr: *const c_char, + pub name_len: usize, + pub kind: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub union GwGateValueData { + pub qubit: u32, + pub f64_value: f64, + pub u64_value: u64, + pub i64_value: i64, + pub u8_value: u8, + pub bool_value: u8, +} + +impl Default for GwGateValueData { + fn default() -> Self { + Self { u64_value: 0 } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct GwGateValue { + pub abi_size: usize, + pub kind: u32, + pub data: GwGateValueData, + pub bytes_ptr: *const u8, + pub bytes_len: usize, +} + +impl Default for GwGateValue { + fn default() -> Self { + Self { + abi_size: mem::size_of::(), + kind: 0, + data: GwGateValueData::default(), + bytes_ptr: ptr::null(), + bytes_len: 0, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct GwGateInstanceView { + pub abi_size: usize, + pub semantic_id: GwSemanticId, + pub values_ptr: *const GwGateValue, + pub values_len: usize, +} + +#[repr(C)] +pub struct GwGateSet { + _private: [u8; 0], +} + +#[repr(C)] +pub struct GwDecodedGate { + _private: [u8; 0], +} + +static_assertions::const_assert_eq!(std::mem::size_of::(), 16); +static_assertions::assert_impl_all!(GwSemanticId: Copy, Clone); +static_assertions::assert_impl_all!(GwGateValue: Copy, Clone); diff --git a/selene-core/rust/gatewire/id.rs b/selene-core/rust/gatewire/id.rs new file mode 100644 index 00000000..5dabeb91 --- /dev/null +++ b/selene-core/rust/gatewire/id.rs @@ -0,0 +1,51 @@ +use std::fmt; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct GateSemanticId { + pub bytes: [u8; 16], +} + +impl GateSemanticId { + pub const fn from_bytes(bytes: [u8; 16]) -> Self { + Self { bytes } + } + + /// Deterministic 128-bit semantic ID derived from a stable semantic string. + /// + /// This uses BLAKE3 and truncates the digest to 16 bytes. Builtins use + /// stable text identifiers such as `gatewire.builtin.PhasedX.v1`; user gates + /// should use an owned prefix such as `org.example.gate.MAGIC.v1`. + pub fn from_text(text: &str) -> Self { + let digest = blake3::hash(text.as_bytes()); + let mut bytes = [0u8; 16]; + bytes.copy_from_slice(&digest.as_bytes()[..16]); + Self { bytes } + } + + pub const fn to_bytes(self) -> [u8; 16] { + self.bytes + } + + pub const fn as_bytes(&self) -> &[u8; 16] { + &self.bytes + } +} + +impl From<[u8; 16]> for GateSemanticId { + fn from(bytes: [u8; 16]) -> Self { + Self::from_bytes(bytes) + } +} + +impl fmt::Display for GateSemanticId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in self.bytes { + write!(f, "{byte:02x}")?; + } + Ok(()) + } +} + +static_assertions::assert_eq_size!(GateSemanticId, [u8; 16]); +static_assertions::assert_impl_all!(GateSemanticId: Copy, Send, Sync); diff --git a/selene-core/rust/gatewire/instance.rs b/selene-core/rust/gatewire/instance.rs new file mode 100644 index 00000000..2b452a59 --- /dev/null +++ b/selene-core/rust/gatewire/instance.rs @@ -0,0 +1,47 @@ +use crate::gatewire::{GateError, GateSemanticId, GateValue, SmallGateValues, wire}; + +#[derive(Clone, Debug, PartialEq)] +pub struct OwnedGateInstance { + pub semantic_id: GateSemanticId, + pub operands: SmallGateValues, +} + +impl OwnedGateInstance { + pub fn new(semantic_id: GateSemanticId, operands: I) -> Self + where + I: IntoIterator, + { + Self { + semantic_id, + operands: operands.into_iter().collect(), + } + } + + pub fn qubit_operands(&self) -> impl Iterator + '_ { + self.operands.iter().filter_map(|value| match value { + GateValue::Qubit(qubit) => Some(*qubit), + _ => None, + }) + } + + pub fn qubit_operand_count(&self) -> usize { + self.qubit_operands().count() + } + + pub fn single_qubit_operand(&self) -> Option { + let mut qubits = self.qubit_operands(); + let qubit = qubits.next()?; + if qubits.next().is_none() { + Some(qubit) + } else { + None + } + } + + pub fn serialize(&self) -> Vec { + wire::serialize_gate_instance(self) + } + pub fn deserialize(data: &[u8]) -> Result { + wire::deserialize_gate_instance(data) + } +} diff --git a/selene-core/rust/gatewire/operand.rs b/selene-core/rust/gatewire/operand.rs new file mode 100644 index 00000000..6e3ac52a --- /dev/null +++ b/selene-core/rust/gatewire/operand.rs @@ -0,0 +1,177 @@ +use crate::gatewire::GateError; +use smallvec::SmallVec; + +pub const GW_OPERAND_KIND_QUBIT: u32 = 1; +pub const GW_OPERAND_KIND_F64: u32 = 2; +pub const GW_OPERAND_KIND_U64: u32 = 3; +pub const GW_OPERAND_KIND_I64: u32 = 4; +pub const GW_OPERAND_KIND_U8: u32 = 5; +pub const GW_OPERAND_KIND_BOOL: u32 = 6; + +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum OperandKind { + Qubit = GW_OPERAND_KIND_QUBIT, + F64 = GW_OPERAND_KIND_F64, + U64 = GW_OPERAND_KIND_U64, + I64 = GW_OPERAND_KIND_I64, + U8 = GW_OPERAND_KIND_U8, + Bool = GW_OPERAND_KIND_BOOL, +} + +impl OperandKind { + pub fn from_u32(value: u32) -> Result { + match value { + GW_OPERAND_KIND_QUBIT => Ok(Self::Qubit), + GW_OPERAND_KIND_F64 => Ok(Self::F64), + GW_OPERAND_KIND_U64 => Ok(Self::U64), + GW_OPERAND_KIND_I64 => Ok(Self::I64), + GW_OPERAND_KIND_U8 => Ok(Self::U8), + GW_OPERAND_KIND_BOOL => Ok(Self::Bool), + other => Err(GateError::InvalidKind(other)), + } + } + + pub const fn as_u32(self) -> u32 { + self as u32 + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GateValue { + Qubit(u32), + F64(f64), + U64(u64), + I64(i64), + U8(u8), + Bool(bool), +} + +pub type SmallGateValues = SmallVec<[GateValue; 4]>; + +impl GateValue { + pub fn kind(&self) -> OperandKind { + match self { + Self::Qubit(_) => OperandKind::Qubit, + Self::F64(_) => OperandKind::F64, + Self::U64(_) => OperandKind::U64, + Self::I64(_) => OperandKind::I64, + Self::U8(_) => OperandKind::U8, + Self::Bool(_) => OperandKind::Bool, + } + } +} + +pub trait GateOperand: Clone { + const KIND: OperandKind; + fn into_value(self) -> GateValue; + fn from_value(value: &GateValue) -> Result; +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Qubit(pub u32); + +#[repr(transparent)] +#[derive( + Clone, + Copy, + Debug, + PartialEq, + derive_more::Add, + derive_more::Sub, + derive_more::Mul, + derive_more::Div, +)] +pub struct Angle(pub f64); + +impl GateOperand for Qubit { + const KIND: OperandKind = OperandKind::Qubit; + fn into_value(self) -> GateValue { + GateValue::Qubit(self.0) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::Qubit(v) => Ok(Self(*v)), + _ => Err(GateError::Decode("expected qubit operand")), + } + } +} + +impl GateOperand for Angle { + const KIND: OperandKind = OperandKind::F64; + fn into_value(self) -> GateValue { + GateValue::F64(self.0) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::F64(v) => Ok(Self(*v)), + _ => Err(GateError::Decode("expected angle operand")), + } + } +} + +impl GateOperand for f64 { + const KIND: OperandKind = OperandKind::F64; + fn into_value(self) -> GateValue { + GateValue::F64(self) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::F64(v) => Ok(*v), + _ => Err(GateError::Decode("expected f64 operand")), + } + } +} + +impl GateOperand for u64 { + const KIND: OperandKind = OperandKind::U64; + fn into_value(self) -> GateValue { + GateValue::U64(self) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::U64(v) => Ok(*v), + _ => Err(GateError::Decode("expected u64 operand")), + } + } +} + +impl GateOperand for i64 { + const KIND: OperandKind = OperandKind::I64; + fn into_value(self) -> GateValue { + GateValue::I64(self) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::I64(v) => Ok(*v), + _ => Err(GateError::Decode("expected i64 operand")), + } + } +} + +impl GateOperand for u8 { + const KIND: OperandKind = OperandKind::U8; + fn into_value(self) -> GateValue { + GateValue::U8(self) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::U8(v) => Ok(*v), + _ => Err(GateError::Decode("expected u8 operand")), + } + } +} + +impl GateOperand for bool { + const KIND: OperandKind = OperandKind::Bool; + fn into_value(self) -> GateValue { + GateValue::Bool(self) + } + fn from_value(value: &GateValue) -> Result { + match value { + GateValue::Bool(v) => Ok(*v), + _ => Err(GateError::Decode("expected bool operand")), + } + } +} diff --git a/selene-core/rust/gatewire/tests.rs b/selene-core/rust/gatewire/tests.rs new file mode 100644 index 00000000..18b94175 --- /dev/null +++ b/selene-core/rust/gatewire/tests.rs @@ -0,0 +1,62 @@ +use super::builtin::{PhasedX, PhasedXX, RZ, ZZPhase}; +use crate::gatewire::{Angle, DynamicGateSet, GateSet, Qubit}; + +crate::define_gateset! { + enum ExampleGateSet { + RZ(RZ), + PhasedX(PhasedX), + PhasedXX(PhasedXX), + } +} + +#[test] +fn typed_roundtrip() { + let set = GateSet::::new().unwrap(); + let gate = ExampleGateSet::PhasedXX(PhasedXX { + q0: Qubit(0), + q1: Qubit(1), + theta: Angle(0.25), + phi: Angle(1.25), + }); + let bytes = set.serialize_gate(&gate).unwrap(); + let decoded = set.try_deserialize(&bytes).unwrap().unwrap(); + assert_eq!(decoded, gate); +} + +#[test] +fn dynamic_gateset_roundtrip() { + let set = GateSet::::new().unwrap(); + let bytes = set.serialize_gateset(); + let dynamic = DynamicGateSet::deserialize(&bytes).unwrap(); + assert!(dynamic.contains(RZ::semantic_id())); + assert_eq!(dynamic.len(), 3); +} + +#[test] +fn dynamic_gateset_subset_and_superset_use_semantic_ids() { + let all = DynamicGateSet::from_declarations([ + RZ::declaration(), + PhasedX::declaration(), + ZZPhase::declaration(), + ]) + .unwrap(); + let subset = + DynamicGateSet::from_declarations([RZ::declaration(), PhasedX::declaration()]).unwrap(); + let unsupported = DynamicGateSet::from_declarations([PhasedXX::declaration()]).unwrap(); + + assert!(subset.is_subset_of(&all)); + assert!(all.is_superset_of(&subset)); + assert!(!all.is_subset_of(&subset)); + assert_eq!( + subset + .first_unsupported_by(&all) + .map(|decl| decl.name.as_str()), + None + ); + assert_eq!( + unsupported + .first_unsupported_by(&all) + .map(|decl| decl.name.as_str()), + Some("PhasedXX") + ); +} diff --git a/selene-core/rust/gatewire/typed.rs b/selene-core/rust/gatewire/typed.rs new file mode 100644 index 00000000..95d50069 --- /dev/null +++ b/selene-core/rust/gatewire/typed.rs @@ -0,0 +1,80 @@ +use crate::gatewire::{DynamicGateSet, GateDecl, GateError, GateSemanticId, OwnedGateInstance}; +use std::marker::PhantomData; + +pub trait GateSpec: Clone + Sized { + fn id_text() -> &'static str; + fn name() -> &'static str; + fn version() -> u32; + + fn semantic_id() -> GateSemanticId { + GateSemanticId::from_text(Self::id_text()) + } + fn declaration() -> GateDecl; + fn to_instance(&self) -> OwnedGateInstance; + fn try_from_instance(instance: &OwnedGateInstance) -> Result, GateError>; +} + +pub trait GateSetSpec: Clone + Sized { + fn declarations() -> Vec; + fn to_instance(&self) -> OwnedGateInstance; + fn try_from_instance(instance: &OwnedGateInstance) -> Result, GateError>; +} + +#[derive(Clone, Debug, PartialEq)] +pub enum TryDecode { + Decoded(G), + Unknown(OwnedGateInstance), +} + +#[derive(Clone, Debug)] +pub struct GateSet { + dynamic: DynamicGateSet, + _marker: PhantomData, +} + +impl GateSet { + pub fn new() -> Result { + Ok(Self { + dynamic: DynamicGateSet::from_declarations(G::declarations())?, + _marker: PhantomData, + }) + } + + pub fn dynamic(&self) -> &DynamicGateSet { + &self.dynamic + } + + pub fn serialize_gate(&self, gate: &G) -> Result, GateError> { + let instance = gate.to_instance(); + if !self.dynamic.validate_instance(&instance)? { + return Err(GateError::UnknownGate); + } + Ok(instance.serialize()) + } + + pub fn decode(&self, data: &[u8]) -> Result, GateError> { + let instance = OwnedGateInstance::deserialize(data)?; + if !self.dynamic.validate_instance(&instance)? { + return Ok(TryDecode::Unknown(instance)); + } + match G::try_from_instance(&instance)? { + Some(gate) => Ok(TryDecode::Decoded(gate)), + None => Ok(TryDecode::Unknown(instance)), + } + } + + /// Deserializes a wire gate and attempts to return the concrete enum for this gateset. + /// + /// Ok(None) means the bytes were a valid gate instance, but not one in this gateset. + /// Err means malformed bytes or a schema mismatch for a gate that claims to be in this gateset. + pub fn try_deserialize(&self, data: &[u8]) -> Result, GateError> { + match self.decode(data)? { + TryDecode::Decoded(gate) => Ok(Some(gate)), + TryDecode::Unknown(_) => Ok(None), + } + } + + pub fn serialize_gateset(&self) -> Vec { + self.dynamic.serialize() + } +} diff --git a/selene-core/rust/gatewire/wire.rs b/selene-core/rust/gatewire/wire.rs new file mode 100644 index 00000000..57dfba5d --- /dev/null +++ b/selene-core/rust/gatewire/wire.rs @@ -0,0 +1,205 @@ +use crate::gatewire::{ + DynamicGateSet, GateDecl, GateError, GateSemanticId, GateValue, OperandKind, OperandSpec, + OwnedGateInstance, +}; + +const GATE_MAGIC: [u8; 4] = *b"GWG1"; +const GATESET_MAGIC: [u8; 4] = *b"GWS1"; +const WIRE_VERSION: u16 = 1; + +pub fn serialize_gateset(set: &DynamicGateSet) -> Vec { + let mut out = Vec::new(); + out.extend_from_slice(&GATESET_MAGIC); + write_u16(&mut out, WIRE_VERSION); + write_u16(&mut out, 0); + write_u32(&mut out, set.len() as u32); + + for decl in set.declarations() { + out.extend_from_slice(&decl.semantic_id.bytes); + write_string(&mut out, &decl.name); + write_u32(&mut out, decl.version); + write_u32(&mut out, decl.operands.len() as u32); + for operand in &decl.operands { + write_u32(&mut out, operand.kind.as_u32()); + write_string(&mut out, &operand.name); + } + } + + out +} + +pub fn deserialize_gateset(data: &[u8]) -> Result { + let mut cur = Cursor::new(data); + let magic = cur.read_exact(4)?; + if magic != GATESET_MAGIC.as_slice() { + return Err(GateError::Decode("bad gateset magic")); + } + let version = cur.read_u16()?; + if version != WIRE_VERSION { + return Err(GateError::Decode("unsupported gateset wire version")); + } + let _reserved = cur.read_u16()?; + let count = cur.read_u32()? as usize; + let mut declarations = Vec::with_capacity(count); + + for _ in 0..count { + let id_bytes = cur.read_array_16()?; + let name = cur.read_string()?; + let version = cur.read_u32()?; + let operand_count = cur.read_u32()? as usize; + let mut operands = Vec::with_capacity(operand_count); + for _ in 0..operand_count { + let kind = OperandKind::from_u32(cur.read_u32()?)?; + let name = cur.read_string()?; + operands.push(OperandSpec::new(name, kind)); + } + declarations.push(GateDecl::new( + GateSemanticId::from_bytes(id_bytes), + name, + operands, + version, + )); + } + + cur.finish()?; + DynamicGateSet::from_declarations(declarations) +} + +pub fn serialize_gate_instance(instance: &OwnedGateInstance) -> Vec { + let mut out = Vec::new(); + out.extend_from_slice(&GATE_MAGIC); + write_u16(&mut out, WIRE_VERSION); + write_u16(&mut out, 0); + out.extend_from_slice(&instance.semantic_id.bytes); + write_u32(&mut out, instance.operands.len() as u32); + + for value in &instance.operands { + write_u32(&mut out, value.kind().as_u32()); + match value { + GateValue::Qubit(v) => write_u32(&mut out, *v), + GateValue::F64(v) => write_u64(&mut out, v.to_bits()), + GateValue::U64(v) => write_u64(&mut out, *v), + GateValue::I64(v) => write_u64(&mut out, *v as u64), + GateValue::U8(v) => out.push(*v), + GateValue::Bool(v) => out.push(if *v { 1 } else { 0 }), + } + } + + out +} + +pub fn deserialize_gate_instance(data: &[u8]) -> Result { + let mut cur = Cursor::new(data); + let magic = cur.read_exact(4)?; + if magic != GATE_MAGIC.as_slice() { + return Err(GateError::Decode("bad gate magic")); + } + let version = cur.read_u16()?; + if version != WIRE_VERSION { + return Err(GateError::Decode("unsupported gate wire version")); + } + let _reserved = cur.read_u16()?; + let semantic_id = GateSemanticId::from_bytes(cur.read_array_16()?); + let operand_count = cur.read_u32()? as usize; + let mut operands = Vec::with_capacity(operand_count); + + for _ in 0..operand_count { + let kind = OperandKind::from_u32(cur.read_u32()?)?; + let value = match kind { + OperandKind::Qubit => GateValue::Qubit(cur.read_u32()?), + OperandKind::F64 => GateValue::F64(f64::from_bits(cur.read_u64()?)), + OperandKind::U64 => GateValue::U64(cur.read_u64()?), + OperandKind::I64 => GateValue::I64(cur.read_u64()? as i64), + OperandKind::U8 => GateValue::U8(cur.read_u8()?), + OperandKind::Bool => match cur.read_u8()? { + 0 => GateValue::Bool(false), + 1 => GateValue::Bool(true), + _ => return Err(GateError::Decode("invalid bool operand")), + }, + }; + operands.push(value); + } + + cur.finish()?; + Ok(OwnedGateInstance::new(semantic_id, operands)) +} + +fn write_u16(out: &mut Vec, value: u16) { + out.extend_from_slice(&value.to_le_bytes()); +} +fn write_u32(out: &mut Vec, value: u32) { + out.extend_from_slice(&value.to_le_bytes()); +} +fn write_u64(out: &mut Vec, value: u64) { + out.extend_from_slice(&value.to_le_bytes()); +} + +fn write_string(out: &mut Vec, value: &str) { + write_u32(out, value.len() as u32); + out.extend_from_slice(value.as_bytes()); +} + +struct Cursor<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> Cursor<'a> { + fn new(data: &'a [u8]) -> Self { + Self { data, offset: 0 } + } + + fn read_exact(&mut self, len: usize) -> Result<&'a [u8], GateError> { + let end = self + .offset + .checked_add(len) + .ok_or(GateError::Decode("integer overflow while decoding"))?; + if end > self.data.len() { + return Err(GateError::Decode("truncated input")); + } + let bytes = &self.data[self.offset..end]; + self.offset = end; + Ok(bytes) + } + + fn read_u8(&mut self) -> Result { + Ok(self.read_exact(1)?[0]) + } + + fn read_u16(&mut self) -> Result { + let bytes = self.read_exact(2)?; + Ok(u16::from_le_bytes([bytes[0], bytes[1]])) + } + + fn read_u32(&mut self) -> Result { + let bytes = self.read_exact(4)?; + Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])) + } + + fn read_u64(&mut self) -> Result { + let bytes = self.read_exact(8)?; + Ok(u64::from_le_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ])) + } + + fn read_array_16(&mut self) -> Result<[u8; 16], GateError> { + let bytes = self.read_exact(16)?; + let mut out = [0u8; 16]; + out.copy_from_slice(bytes); + Ok(out) + } + + fn read_string(&mut self) -> Result { + let len = self.read_u32()? as usize; + Ok(String::from_utf8(self.read_exact(len)?.to_vec())?) + } + + fn finish(self) -> Result<(), GateError> { + if self.offset == self.data.len() { + Ok(()) + } else { + Err(GateError::Decode("trailing bytes")) + } + } +} diff --git a/selene-core/rust/lib.rs b/selene-core/rust/lib.rs index c82cb261..abe643f9 100644 --- a/selene-core/rust/lib.rs +++ b/selene-core/rust/lib.rs @@ -1,19 +1,10 @@ pub mod encoder; pub mod error_model; +pub mod gatewire; +pub mod macros; pub mod operation; +pub(crate) mod plugin; pub mod runtime; pub mod simulator; pub mod time; pub mod utils; - -#[macro_export] -macro_rules! export_plugin_descriptor_v1 { - ($symbol:ident, $descriptor_ty:ident, $api_version:expr, { $($field:ident : $value:expr),* $(,)? }) => { - #[unsafe(no_mangle)] - pub static mut $symbol: $descriptor_ty = $descriptor_ty { - struct_size: core::mem::size_of::<$descriptor_ty>() as u64, - api_version: $api_version, - $($field: $value,)* - }; - }; -} diff --git a/selene-core/rust/macros.rs b/selene-core/rust/macros.rs new file mode 100644 index 00000000..8cfc116a --- /dev/null +++ b/selene-core/rust/macros.rs @@ -0,0 +1,195 @@ +#[macro_export] +macro_rules! export_plugin_descriptor_v1 { + ($symbol:ident, $descriptor_ty:ident, $api_version:expr, { $($field:ident : $value:expr),* $(,)? }) => { + #[unsafe(no_mangle)] + pub static mut $symbol: $descriptor_ty = $descriptor_ty { + struct_size: core::mem::size_of::<$descriptor_ty>() as u64, + api_version: $api_version, + $($field: $value,)* + }; + }; +} + +#[macro_export] +macro_rules! define_gate { + ( + $vis:vis struct $name:ident [ + id = $id:expr, + display = $display:expr, + version = $version:expr, + ] { + $( $field:ident : $field_ty:ty ),* $(,)? + } + ) => { + #[derive(Clone, Debug, PartialEq)] + $vis struct $name { $(pub $field: $field_ty),* } + + impl $name { + pub fn semantic_id() -> $crate::gatewire::GateSemanticId { + ::semantic_id() + } + + pub fn declaration() -> $crate::gatewire::GateDecl { + ::declaration() + } + } + + impl $crate::gatewire::GateSpec for $name { + fn id_text() -> &'static str { $id } + fn name() -> &'static str { $display } + fn version() -> u32 { $version } + + fn declaration() -> $crate::gatewire::GateDecl { + $crate::gatewire::GateDecl::new( + Self::semantic_id(), + Self::name(), + vec![$($crate::gatewire::OperandSpec::new(stringify!($field), <$field_ty as $crate::gatewire::GateOperand>::KIND),)*], + Self::version(), + ) + } + + fn to_instance(&self) -> $crate::gatewire::OwnedGateInstance { + $crate::gatewire::OwnedGateInstance::new( + Self::semantic_id(), + vec![$(<$field_ty as $crate::gatewire::GateOperand>::into_value(self.$field.clone()),)*], + ) + } + + fn try_from_instance(instance: &$crate::gatewire::OwnedGateInstance) -> Result, $crate::gatewire::GateError> { + if instance.semantic_id != Self::semantic_id() { return Ok(None); } + let expected = 0usize $(+ { let _ = stringify!($field); 1usize })*; + if instance.operands.len() != expected { + return Err($crate::gatewire::GateError::WrongArity { gate: Self::name().to_string(), expected, actual: instance.operands.len() }); + } + let mut index = 0usize; + $( + let $field = { + let value = &instance.operands[index]; + index += 1; + <$field_ty as $crate::gatewire::GateOperand>::from_value(value)? + }; + )* + let _ = index; + Ok(Some(Self { $($field),* })) + } + } + }; +} + +#[macro_export] +macro_rules! define_builtin_gate { + ( + $vis:vis struct $name:ident { + $( $field:ident : $field_ty:ty ),* $(,)? + } + ) => { + $crate::define_gate! { + $vis struct $name [ + id = concat!( + "gatewire.builtin.", + stringify!($name), + ".v1" + ), + display = stringify!($name), + version = 1, + ] { + $( $field : $field_ty ),* + } + } + }; + + ( + $vis:vis struct $name:ident [ + version = $version:literal $(,)? + ] { + $( $field:ident : $field_ty:ty ),* $(,)? + } + ) => { + $crate::define_gate! { + $vis struct $name [ + id = concat!( + "gatewire.builtin.", + stringify!($name), + ".v", + stringify!($version) + ), + display = stringify!($name), + version = $version, + ] { + $( $field : $field_ty ),* + } + } + }; +} + +#[macro_export] +macro_rules! define_builtin_gates { + ( + $( + $vis:vis struct $name:ident { + $( $field:ident : $field_ty:ty ),* $(,)? + } + )* + ) => { + $( + $crate::define_builtin_gate! { + $vis struct $name [ + version = 1, + ] { + $( $field : $field_ty ),* + } + } + )* + }; + + ( + $( + $vis:vis struct $name:ident [ + version = $version:literal $(,)? + ] { + $( $field:ident : $field_ty:ty ),* $(,)? + } + )* + ) => { + $( + $crate::define_builtin_gate! { + $vis struct $name [ + version = $version, + ] { + $( $field : $field_ty ),* + } + } + )* + }; +} + +#[macro_export] +macro_rules! define_gateset { + ( + $vis:vis enum $name:ident { + $( $variant:ident ( $gate_ty:ty ) ),* $(,)? + } + ) => { + #[derive(Clone, Debug, PartialEq)] + $vis enum $name { $( $variant($gate_ty) ),* } + + impl $crate::gatewire::GateSetSpec for $name { + fn declarations() -> Vec<$crate::gatewire::GateDecl> { + vec![$(<$gate_ty as $crate::gatewire::GateSpec>::declaration(),)*] + } + + fn to_instance(&self) -> $crate::gatewire::OwnedGateInstance { + match self { $( Self::$variant(inner) => <$gate_ty as $crate::gatewire::GateSpec>::to_instance(inner), )* } + } + + fn try_from_instance(instance: &$crate::gatewire::OwnedGateInstance) -> Result, $crate::gatewire::GateError> { + $( + if let Some(gate) = <$gate_ty as $crate::gatewire::GateSpec>::try_from_instance(instance)? { + return Ok(Some(Self::$variant(gate))); + } + )* + Ok(None) + } + } + }; +} diff --git a/selene-core/rust/operation.rs b/selene-core/rust/operation.rs index 13d1c5fd..32ff28f2 100644 --- a/selene-core/rust/operation.rs +++ b/selene-core/rust/operation.rs @@ -3,6 +3,9 @@ pub mod plugin; use std::collections::HashSet; use std::iter; +use crate::gatewire::{Angle, GateSpec, OwnedGateInstance, Qubit, builtin}; +use anyhow::{Result, bail}; + pub use plugin::{ BatchBuilder, BatchExtractor, RuntimeExtractOperationInstance, RuntimeExtractOperationInterface, RuntimeGetOperationInstance, RuntimeGetOperationInterface, @@ -53,39 +56,22 @@ impl Default for BatchSource { } #[derive(Debug, Clone, PartialEq)] -#[non_exhaustive] -#[repr(C)] -pub enum Operation { - Measure { - qubit_id: u64, - result_id: u64, - }, - Reset { - qubit_id: u64, - }, - RXYGate { +pub enum BuiltinGate { + RZ { qubit_id: u64, theta: f64, - phi: f64, }, - RZGate { + PhasedX { qubit_id: u64, theta: f64, + phi: f64, }, - RZZGate { + ZZPhase { qubit_id_1: u64, qubit_id_2: u64, theta: f64, }, - Custom { - custom_tag: usize, - data: Box<[u8]>, - }, - MeasureLeaked { - qubit_id: u64, - result_id: u64, - }, - RPPGate { + PhasedXX { qubit_id_1: u64, qubit_id_2: u64, theta: f64, @@ -93,33 +79,129 @@ pub enum Operation { }, } +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +#[repr(C)] +pub enum Operation { + Measure { qubit_id: u64, result_id: u64 }, + Reset { qubit_id: u64 }, + Custom { custom_tag: usize, data: Box<[u8]> }, + MeasureLeaked { qubit_id: u64, result_id: u64 }, + Gate { gate: OwnedGateInstance }, +} + impl Operation { + pub fn from_gate_instance(gate: OwnedGateInstance) -> Result { + Ok(Self::Gate { gate }) + } + + fn qubit(qubit_id: u64) -> Result { + Ok(Qubit(qubit_id.try_into()?)) + } + + pub fn rz(qubit_id: u64, theta: f64) -> Result { + Ok(Self::Gate { + gate: builtin::RZ { + q0: Self::qubit(qubit_id)?, + theta: Angle(theta), + } + .to_instance(), + }) + } + + pub fn phased_x(qubit_id: u64, theta: f64, phi: f64) -> Result { + Ok(Self::Gate { + gate: builtin::PhasedX { + q0: Self::qubit(qubit_id)?, + theta: Angle(theta), + phi: Angle(phi), + } + .to_instance(), + }) + } + + pub fn zz_phase(qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result { + Ok(Self::Gate { + gate: builtin::ZZPhase { + q0: Self::qubit(qubit_id_1)?, + q1: Self::qubit(qubit_id_2)?, + theta: Angle(theta), + } + .to_instance(), + }) + } + + pub fn phased_xx(qubit_id_1: u64, qubit_id_2: u64, theta: f64, phi: f64) -> Result { + Ok(Self::Gate { + gate: builtin::PhasedXX { + q0: Self::qubit(qubit_id_1)?, + q1: Self::qubit(qubit_id_2)?, + theta: Angle(theta), + phi: Angle(phi), + } + .to_instance(), + }) + } + + pub fn to_gate_instance(&self) -> Result { + match self { + Self::Gate { gate } => Ok(gate.clone()), + _ => bail!("operation is not a gate"), + } + } + + pub fn as_builtin_gate(&self) -> Result> { + let Self::Gate { gate } = self else { + return Ok(None); + }; + if let Some(gate) = builtin::RZ::try_from_instance(gate)? { + return Ok(Some(BuiltinGate::RZ { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + })); + } + if let Some(gate) = builtin::PhasedX::try_from_instance(gate)? { + return Ok(Some(BuiltinGate::PhasedX { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + })); + } + if let Some(gate) = builtin::ZZPhase::try_from_instance(gate)? { + return Ok(Some(BuiltinGate::ZZPhase { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + })); + } + if let Some(gate) = builtin::PhasedXX::try_from_instance(gate)? { + return Ok(Some(BuiltinGate::PhasedXX { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + })); + } + Ok(None) + } + pub fn get_qubit_ids(&self) -> HashSet { match self { Operation::Measure { qubit_id, .. } | Operation::Reset { qubit_id } - | Operation::RXYGate { qubit_id, .. } - | Operation::RZGate { qubit_id, .. } | Operation::MeasureLeaked { qubit_id, .. } => { let mut set = HashSet::new(); set.insert(*qubit_id); set } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - .. - } - | Operation::RPPGate { - qubit_id_1, - qubit_id_2, - .. - } => { - let mut set = HashSet::new(); - set.insert(*qubit_id_1); - set.insert(*qubit_id_2); - set - } + Operation::Gate { gate } => gate + .operands + .iter() + .filter_map(|operand| match operand { + crate::gatewire::GateValue::Qubit(q) => Some((*q).into()), + _ => None, + }) + .collect(), Operation::Custom { .. } => HashSet::new(), } } diff --git a/selene-core/rust/operation/plugin.rs b/selene-core/rust/operation/plugin.rs index 1b0454f0..00cafa7c 100644 --- a/selene-core/rust/operation/plugin.rs +++ b/selene-core/rust/operation/plugin.rs @@ -38,60 +38,6 @@ impl BatchBuilder { go(this) } - unsafe extern "C" fn rzz( - interface: RuntimeGetOperationInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - ) { - Self::push( - interface, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - }, - ) - } - - unsafe extern "C" fn rz(interface: RuntimeGetOperationInstance, qubit_id: u64, theta: f64) { - Self::push(interface, Operation::RZGate { qubit_id, theta }) - } - - unsafe extern "C" fn rxy( - interface: RuntimeGetOperationInstance, - qubit_id: u64, - theta: f64, - phi: f64, - ) { - Self::push( - interface, - Operation::RXYGate { - qubit_id, - theta, - phi, - }, - ) - } - - unsafe extern "C" fn rpp( - interface: RuntimeGetOperationInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - phi: f64, - ) { - Self::push( - interface, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - }, - ) - } - unsafe extern "C" fn measure( interface: RuntimeGetOperationInstance, qubit_id: u64, @@ -136,6 +82,17 @@ impl BatchBuilder { Self::push(interface, Operation::Custom { custom_tag, data }) } + unsafe extern "C" fn gate(interface: RuntimeGetOperationInstance, data: *const u8, len: usize) { + let data = unsafe { slice::from_raw_parts(data, len) }; + let Ok(gate) = crate::gatewire::OwnedGateInstance::deserialize(data) else { + return; + }; + Self::push( + interface, + Operation::from_gate_instance(gate).expect("gate operation"), + ) + } + unsafe extern "C" fn set_batch_time( interface: RuntimeGetOperationInstance, start: u64, @@ -155,10 +112,7 @@ impl BatchBuilder { reset_fn: Self::reset, custom_fn: Self::custom, set_batch_time_fn: Self::set_batch_time, - rzz_fn: Self::rzz, - rxy_fn: Self::rxy, - rz_fn: Self::rz, - rpp_fn: Self::rpp, + gate_fn: Self::gate, }, } } @@ -183,10 +137,7 @@ pub struct RuntimeGetOperationInterface { pub custom_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, usize, *const ffi::c_void, usize), pub set_batch_time_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, u64), - pub rzz_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, u64, f64), - pub rxy_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, f64, f64), - pub rz_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, f64), - pub rpp_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, u64, f64, f64), + pub gate_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, *const u8, usize), } #[derive(Default)] @@ -209,10 +160,7 @@ impl BatchExtractor { reset_fn, custom_fn, set_batch_time_fn, - rzz_fn, - rxy_fn, - rz_fn, - rpp_fn, + gate_fn, .. } = output.interface; if let Some(timing) = batch.runtime_source() { @@ -235,25 +183,10 @@ impl BatchExtractor { result_id, } => unsafe { measure_leaked_fn(output.instance, *qubit_id, *result_id) }, Operation::Reset { qubit_id } => unsafe { reset_fn(output.instance, *qubit_id) }, - Operation::RXYGate { - qubit_id, - theta, - phi, - } => unsafe { rxy_fn(output.instance, *qubit_id, *theta, *phi) }, - Operation::RZGate { qubit_id, theta } => unsafe { - rz_fn(output.instance, *qubit_id, *theta) - }, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => unsafe { rzz_fn(output.instance, *qubit_id_1, *qubit_id_2, *theta) }, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => unsafe { rpp_fn(output.instance, *qubit_id_1, *qubit_id_2, *theta, *phi) }, + Operation::Gate { gate } => { + let data = gate.serialize(); + unsafe { gate_fn(output.instance, data.as_ptr(), data.len()) } + } Operation::Custom { custom_tag, data } => { let (ptr, len) = (data.as_ptr() as *const ffi::c_void, data.len()); unsafe { custom_fn(output.instance, *custom_tag, ptr, len) } diff --git a/selene-core/rust/plugin.rs b/selene-core/rust/plugin.rs new file mode 100644 index 00000000..602e738a --- /dev/null +++ b/selene-core/rust/plugin.rs @@ -0,0 +1,309 @@ +use anyhow::{Result, anyhow, bail}; +use libloading::Library; +use std::ffi::OsStr; + +use crate::gatewire::DynamicGateSet; +use crate::utils::check_errno; + +pub type Errno = i32; + +pub(crate) trait PluginDescriptorV1: Copy { + const KIND: &'static str; + + fn struct_size(&self) -> u64; + fn api_version(&self) -> u64; +} + +pub(crate) fn load_library(kind: &str, plugin_file: &impl AsRef) -> Result { + unsafe { Library::new(plugin_file.as_ref()) }.map_err(|error| { + anyhow!( + "Failed to load {} plugin: {}. Error: {}", + kind, + plugin_file.as_ref().to_string_lossy(), + error + ) + }) +} + +pub(crate) unsafe fn load_descriptor_v1( + lib: &Library, + plugin_file: &impl AsRef, + descriptor_symbol: &'static [u8], + getter_symbol: &'static [u8], +) -> Result { + let descriptor = unsafe { + lib.get::(descriptor_symbol) + .ok() + .map(|descriptor| *descriptor) + .or_else(|| { + lib.get:: *const T>(getter_symbol) + .ok() + .and_then(|get_descriptor| { + let ptr = get_descriptor(); + if ptr.is_null() { None } else { Some(*ptr) } + }) + }) + }; + + descriptor.ok_or_else(|| { + anyhow!( + "{} plugin '{}' does not expose either {} or {}", + T::KIND, + plugin_file.as_ref().to_string_lossy(), + String::from_utf8_lossy(descriptor_symbol), + String::from_utf8_lossy(getter_symbol), + ) + }) +} + +pub(crate) fn validate_descriptor_v1( + descriptor: &T, + validate_api_version: impl FnOnce(u64) -> Result<()>, +) -> Result<()> { + validate_api_version(descriptor.api_version())?; + if descriptor.struct_size() < core::mem::size_of::() as u64 { + return Err(anyhow!( + "{} plugin descriptor is too small for v1 ABI", + T::KIND + )); + } + Ok(()) +} + +pub(crate) fn require_callback( + kind: &str, + callback_name: &str, + callback: Option, +) -> Result { + callback.ok_or_else(|| { + anyhow!("{kind} plugin descriptor is missing required callback {callback_name}") + }) +} + +pub(crate) type NegotiateGatesetFn = unsafe extern "C" fn( + handle: I, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, +) -> Errno; + +pub(crate) fn negotiate_gateset( + kind: &str, + instance: I, + negotiate_gateset_fn: Option>, + gateset: &DynamicGateSet, +) -> Result { + let Some(negotiate_gateset_fn) = negotiate_gateset_fn else { + return Ok(gateset.clone()); + }; + + let input = gateset.serialize(); + let mut written = 0usize; + check_errno( + unsafe { + negotiate_gateset_fn( + instance, + input.as_ptr(), + input.len(), + std::ptr::null_mut(), + 0, + &mut written, + ) + }, + || anyhow!("{kind}: negotiate_gateset failed"), + )?; + + let mut output = vec![0u8; written]; + check_errno( + unsafe { + negotiate_gateset_fn( + instance, + input.as_ptr(), + input.len(), + output.as_mut_ptr(), + output.len(), + &mut written, + ) + }, + || anyhow!("{kind}: negotiate_gateset failed"), + )?; + + output.truncate(written); + Ok(DynamicGateSet::deserialize(&output)?) +} + +pub(crate) unsafe fn write_negotiated_gateset( + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + negotiate: impl FnOnce(&DynamicGateSet) -> Result, +) -> Result<()> { + if written.is_null() { + bail!("written pointer is null"); + } + let input = if input_len == 0 { + &[] + } else { + if input.is_null() { + bail!("input pointer is null"); + } + unsafe { std::slice::from_raw_parts(input, input_len) } + }; + let incoming = DynamicGateSet::deserialize(input)?; + let outgoing = negotiate(&incoming)?; + let bytes = outgoing.serialize(); + unsafe { + *written = bytes.len(); + } + if output.is_null() { + return Ok(()); + } + if output_len < bytes.len() { + bail!("output buffer is too small"); + } + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), output, bytes.len()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::gatewire::builtin; + + #[derive(Clone, Copy)] + struct TestDescriptor { + struct_size: u64, + api_version: u64, + } + + impl PluginDescriptorV1 for TestDescriptor { + const KIND: &'static str = "Test"; + + fn struct_size(&self) -> u64 { + self.struct_size + } + + fn api_version(&self) -> u64 { + self.api_version + } + } + + #[test] + fn descriptor_validation_checks_version_and_size() { + let descriptor = TestDescriptor { + struct_size: core::mem::size_of::() as u64, + api_version: 7, + }; + validate_descriptor_v1(&descriptor, |version| { + assert_eq!(version, 7); + Ok(()) + }) + .unwrap(); + + let descriptor = TestDescriptor { + struct_size: 0, + api_version: 7, + }; + let error = validate_descriptor_v1(&descriptor, |_| Ok(())).unwrap_err(); + assert!( + error + .to_string() + .contains("Test plugin descriptor is too small") + ); + + let descriptor = TestDescriptor { + struct_size: core::mem::size_of::() as u64, + api_version: 7, + }; + let error = + validate_descriptor_v1(&descriptor, |_| Err(anyhow!("bad version"))).unwrap_err(); + assert_eq!(error.to_string(), "bad version"); + } + + #[test] + fn required_callback_validation_names_missing_callback() { + let error = require_callback::("Test", "init_fn", None).unwrap_err(); + assert_eq!( + error.to_string(), + "Test plugin descriptor is missing required callback init_fn" + ); + + let callback = require_callback("Test", "init_fn", Some(|| {})).unwrap(); + callback(); + } + + unsafe extern "C" fn echo_gateset( + _handle: usize, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { + *written = input_len; + if output.is_null() { + return 0; + } + if output_len < input_len { + return -1; + } + std::ptr::copy_nonoverlapping(input, output, input_len); + } + 0 + } + + #[test] + fn negotiate_gateset_uses_two_call_output_protocol() { + let gateset = builtin::all(); + let negotiated = + negotiate_gateset("TestPlugin", 0usize, Some(echo_gateset), &gateset).unwrap(); + assert_eq!(negotiated.serialize(), gateset.serialize()); + } + + #[test] + fn negotiate_gateset_defaults_to_input_when_callback_is_absent() { + let gateset = builtin::all(); + let negotiated = negotiate_gateset::("TestPlugin", 0usize, None, &gateset).unwrap(); + assert_eq!(negotiated.serialize(), gateset.serialize()); + } + + #[test] + fn write_negotiated_gateset_validates_null_pointers() { + let gateset = builtin::all(); + let data = gateset.serialize(); + let mut written = 0usize; + + let error = unsafe { + write_negotiated_gateset( + data.as_ptr(), + data.len(), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + |incoming| Ok(incoming.clone()), + ) + } + .unwrap_err(); + assert_eq!(error.to_string(), "written pointer is null"); + + let error = unsafe { + write_negotiated_gateset( + std::ptr::null(), + data.len(), + std::ptr::null_mut(), + 0, + &mut written, + |incoming| Ok(incoming.clone()), + ) + } + .unwrap_err(); + assert_eq!(error.to_string(), "input pointer is null"); + } +} diff --git a/selene-core/rust/runtime.rs b/selene-core/rust/runtime.rs index 710bdb03..b5cccdc1 100644 --- a/selene-core/rust/runtime.rs +++ b/selene-core/rust/runtime.rs @@ -8,7 +8,7 @@ use std::ffi::OsStr; use std::sync; pub use crate::operation::{ - BatchOperation, BatchSource, ErrorModelBatchSource, Operation, RuntimeBatchSource, + BatchOperation, BatchSource, BuiltinGate, ErrorModelBatchSource, Operation, RuntimeBatchSource, SimulatorBatchSource, }; pub use inline::{RuntimeFFIAdapter, RuntimeHandle, RuntimeOperationInterface}; @@ -18,7 +18,9 @@ pub use version::RuntimeAPIVersion; use crate::utils::{MetricValue, check_errno, read_raw_metric}; use anyhow::{Result, anyhow}; +use crate::gatewire::{DynamicGateSet, OwnedGateInstance}; use crate::operation::plugin::BatchBuilder; +use crate::plugin as plugin_utils; enum RuntimeBacking { Adapter { _adapter: Box }, @@ -119,6 +121,15 @@ impl RuntimeInterface for Runtime { ) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + plugin_utils::negotiate_gateset( + "Runtime", + self.handle.instance, + Some(self.handle.interface.negotiate_gateset_fn), + gateset, + ) + } + fn custom_call(&mut self, custom_tag: u64, data: &[u8]) -> Result { let mut result = 0; check_errno( @@ -171,48 +182,13 @@ impl RuntimeInterface for Runtime { ) } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - check_errno( - unsafe { - (self.handle.interface.rxy_gate_fn)(self.handle.instance, qubit_id, theta, phi) - }, - || anyhow!("Runtime: rxy_gate failed"), - ) - } - - fn rzz_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result<()> { + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + let data = gate.serialize(); check_errno( unsafe { - (self.handle.interface.rzz_gate_fn)( - self.handle.instance, - qubit_id_1, - qubit_id_2, - theta, - ) - }, - || anyhow!("Runtime: rzz_gate failed"), - ) - } - - fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - check_errno( - unsafe { (self.handle.interface.rz_gate_fn)(self.handle.instance, qubit_id, theta) }, - || anyhow!("Runtime: rz_gate failed"), - ) - } - - fn rpp_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64, phi: f64) -> Result<()> { - check_errno( - unsafe { - (self.handle.interface.rpp_gate_fn)( - self.handle.instance, - qubit_id_1, - qubit_id_2, - theta, - phi, - ) + (self.handle.interface.gate_fn)(self.handle.instance, data.as_ptr(), data.len()) }, - || anyhow!("Runtime: rpp_gate failed"), + || anyhow!("Runtime: gate failed"), ) } diff --git a/selene-core/rust/runtime/helper.rs b/selene-core/rust/runtime/helper.rs index 65a14de5..0bbef9c0 100644 --- a/selene-core/rust/runtime/helper.rs +++ b/selene-core/rust/runtime/helper.rs @@ -4,7 +4,9 @@ //! See `selene-simple-runtime-plugin` for a fully worked example. use std::{ffi, mem, sync::Arc}; +use crate::gatewire::OwnedGateInstance; use crate::operation::plugin::{RuntimeGetOperationHandle, RuntimeGetOperationInterface}; +use crate::plugin::write_negotiated_gateset; use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; use super::{ @@ -100,10 +102,7 @@ impl Helper { reset_fn, custom_fn, set_batch_time_fn, - rzz_fn, - rxy_fn, - rz_fn, - rpp_fn, + gate_fn, .. } = ops.interface; if let Some(timing) = batch.runtime_source() { @@ -128,25 +127,10 @@ impl Helper { Operation::Reset { qubit_id } => unsafe { reset_fn(ops.instance, qubit_id) }, - Operation::RZGate { qubit_id, theta } => unsafe { - rz_fn(ops.instance, qubit_id, theta) - }, - Operation::RXYGate { - qubit_id, - theta, - phi, - } => unsafe { rxy_fn(ops.instance, qubit_id, theta, phi) }, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => unsafe { rzz_fn(ops.instance, qubit_id_1, qubit_id_2, theta) }, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => unsafe { rpp_fn(ops.instance, qubit_id_1, qubit_id_2, theta, phi) }, + Operation::Gate { gate } => { + let data = gate.serialize(); + unsafe { gate_fn(ops.instance, data.as_ptr(), data.len()) } + } Operation::Custom { custom_tag, data } => { let (ptr, len) = (data.as_ptr() as *const ffi::c_void, data.len()); unsafe { custom_fn(ops.instance, custom_tag, ptr, len) } @@ -172,6 +156,24 @@ impl Helper { ) } + pub unsafe fn negotiate_gateset( + instance: RuntimeInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + result_to_errno( + "Failed in negotiate_gateset", + Self::with_runtime_instance(instance, |runtime| unsafe { + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + runtime.negotiate_gateset(gateset) + }) + }), + ) + } + pub unsafe fn custom_call( instance: RuntimeInstance, tag: u64, @@ -234,50 +236,14 @@ impl Helper { ) } - pub unsafe fn rxy_gate( - instance: RuntimeInstance, - qubit_id: u64, - theta: f64, - phi: f64, - ) -> Errno { - result_to_errno( - "Failed in rxy_gate", - Self::with_runtime_instance(instance, |runtime| runtime.rxy_gate(qubit_id, theta, phi)), - ) - } - - pub unsafe fn rzz_gate( - instance: RuntimeInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - ) -> Errno { - result_to_errno( - "Failed in rzz_gate", - Self::with_runtime_instance(instance, |runtime| { - runtime.rzz_gate(qubit_id_1, qubit_id_2, theta) - }), - ) - } - - pub unsafe fn rz_gate(instance: RuntimeInstance, qubit_id: u64, theta: f64) -> Errno { - result_to_errno( - "Failed in rz_gate", - Self::with_runtime_instance(instance, |runtime| runtime.rz_gate(qubit_id, theta)), - ) - } - - pub unsafe fn rpp_gate( - instance: RuntimeInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - phi: f64, - ) -> Errno { + pub unsafe fn gate(instance: RuntimeInstance, data: *const u8, data_len: usize) -> Errno { result_to_errno( - "Failed in rpp_gate", + "Failed in gate", Self::with_runtime_instance(instance, |runtime| { - runtime.rpp_gate(qubit_id_1, qubit_id_2, theta, phi) + let gate = OwnedGateInstance::deserialize(unsafe { + std::slice::from_raw_parts(data, data_len) + })?; + runtime.gate(&gate) }), ) } @@ -445,7 +411,7 @@ macro_rules! export_runtime_plugin { use selene_core::runtime::{ interface::RuntimeInterfaceFactory, plugin::{Errno, RuntimeInstance, RuntimePluginDescriptorV1}, - version::CURRENT_API_VERSION, + version::current_api_version, }; use selene_core::operation::plugin::{ RuntimeGetOperationHandle, RuntimeGetOperationInstance, RuntimeGetOperationInterface, @@ -526,6 +492,19 @@ macro_rules! export_runtime_plugin { Helper::shot_end(instance) } + /// Negotiate the gates accepted from an interface and return the gates this runtime + /// may emit downstream, encoded as a gatewire gateset. + unsafe extern "C" fn selene_runtime_negotiate_gateset( + instance: RuntimeInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> i32 { + Helper::negotiate_gateset(instance, input, input_len, output, output_len, written) + } + /// This function is called to provide a runtime with custom operations from /// a user program if supported. The `tag` should be a unique identifier for /// the runtime to interpret to identify the operation that is being requested, @@ -641,70 +620,13 @@ macro_rules! export_runtime_plugin { Helper::global_barrier(instance, sleep_ns) } - /// Instruct the runtime to apply an RXY gate to the qubit with the given ID. - /// It might not be supported by all runtimes, and an error will be returned - /// if it is used on a runtime that does not support it, or if the runtime - /// is unable to apply it for any reason. - /// - /// Note that it is up to the runtime whether or not this gate is applied immediately: - /// The runtime might act lazily and apply the gate at a later time when an observable - /// outcome is requested. - unsafe extern "C" fn selene_runtime_rxy_gate( + /// Instruct the runtime to apply a gate encoded with gatewire. + unsafe extern "C" fn selene_runtime_gate( instance: RuntimeInstance, - qubit_id: u64, - theta: f64, - phi: f64, - ) -> i32 { - Helper::rxy_gate(instance, qubit_id, theta, phi) - } - - /// Instruct the runtime to apply an RZZ gate to the qubits with the given IDs. - /// It might not be supported by all runtimes, and an error will be returned - /// if it is used on a runtime that does not support it, or if the runtime - /// is unable to apply it for any reason. - /// - /// Note that it is up to the runtime whether or not this gate is applied - /// immediately: The runtime might act lazily and apply the gate at a later time. - unsafe extern "C" fn selene_runtime_rzz_gate( - instance: RuntimeInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - ) -> i32 { - Helper::rzz_gate(instance, qubit_id_1, qubit_id_2, theta) - } - - /// Instruct the runtime to apply an RZ gate to the qubit with the given ID. - /// It might not be supported by all runtimes, and an error will be returned - /// if it is used on a runtime that does not support it, or if the runtime - /// is unable to apply it for any reason. - /// - /// Note that it is up to the runtime whether or not this gate is applied - /// immediately: The runtime might act lazily and apply the gate at a later time. - /// It might not apply it at all, as RZ may be elided in code. - unsafe extern "C" fn selene_runtime_rz_gate( - instance: RuntimeInstance, - qubit_id: u64, - theta: f64, - ) -> i32 { - Helper::rz_gate(instance, qubit_id, theta) - } - - /// Instruct the runtime to apply an RPP gate to the qubits with the given IDs. - /// It might not be supported by all runtimes, and an error will be returned - /// if it is used on a runtime that does not support it, or if the runtime - /// is unable to apply it for any reason. - /// - /// Note that it is up to the runtime whether or not this gate is applied - /// immediately: The runtime might act lazily and apply the gate at a later time. - unsafe extern "C" fn selene_runtime_rpp_gate( - instance: RuntimeInstance, - qubit_id_1: u64, - qubit_id_2: u64, - theta: f64, - phi: f64, + data: *const u8, + data_len: usize, ) -> i32 { - Helper::rpp_gate(instance, qubit_id_1, qubit_id_2, theta, phi) + Helper::gate(instance, data, data_len) } /// Instruct the runtime that a measurement is to be requested and to write @@ -815,32 +737,30 @@ macro_rules! export_runtime_plugin { selene_core::export_plugin_descriptor_v1!( selene_runtime_plugin_descriptor_v1, RuntimePluginDescriptorV1, - CURRENT_API_VERSION.as_u64(), + current_api_version().as_u64(), { - init_fn: selene_runtime_init, + init_fn: Some(selene_runtime_init), exit_fn: Some(selene_runtime_exit), - get_next_operations_fn: selene_runtime_get_next_operations, - shot_start_fn: selene_runtime_shot_start, - shot_end_fn: selene_runtime_shot_end, + get_next_operations_fn: Some(selene_runtime_get_next_operations), + shot_start_fn: Some(selene_runtime_shot_start), + shot_end_fn: Some(selene_runtime_shot_end), get_metrics_fn: Some(selene_runtime_get_metrics), - qalloc_fn: selene_runtime_qalloc, - qfree_fn: selene_runtime_qfree, - local_barrier_fn: selene_runtime_local_barrier, - global_barrier_fn: selene_runtime_global_barrier, - rxy_gate_fn: selene_runtime_rxy_gate, - rzz_gate_fn: selene_runtime_rzz_gate, - rz_gate_fn: selene_runtime_rz_gate, - rpp_gate_fn: selene_runtime_rpp_gate, - measure_fn: selene_runtime_measure, - measure_leaked_fn: selene_runtime_measure_leaked, - reset_fn: selene_runtime_reset, - force_result_fn: selene_runtime_force_result, - get_bool_result_fn: selene_runtime_get_bool_result, - get_u64_result_fn: selene_runtime_get_u64_result, - set_bool_result_fn: selene_runtime_set_bool_result, - set_u64_result_fn: selene_runtime_set_u64_result, - increment_future_refcount_fn: selene_runtime_increment_future_refcount, - decrement_future_refcount_fn: selene_runtime_decrement_future_refcount, + qalloc_fn: Some(selene_runtime_qalloc), + qfree_fn: Some(selene_runtime_qfree), + local_barrier_fn: Some(selene_runtime_local_barrier), + global_barrier_fn: Some(selene_runtime_global_barrier), + gate_fn: Some(selene_runtime_gate), + negotiate_gateset_fn: Some(selene_runtime_negotiate_gateset), + measure_fn: Some(selene_runtime_measure), + measure_leaked_fn: Some(selene_runtime_measure_leaked), + reset_fn: Some(selene_runtime_reset), + force_result_fn: Some(selene_runtime_force_result), + get_bool_result_fn: Some(selene_runtime_get_bool_result), + get_u64_result_fn: Some(selene_runtime_get_u64_result), + set_bool_result_fn: Some(selene_runtime_set_bool_result), + set_u64_result_fn: Some(selene_runtime_set_u64_result), + increment_future_refcount_fn: Some(selene_runtime_increment_future_refcount), + decrement_future_refcount_fn: Some(selene_runtime_decrement_future_refcount), custom_call_fn: Some(selene_runtime_custom_call), simulate_delay_fn: Some(selene_runtime_simulate_delay), } diff --git a/selene-core/rust/runtime/inline.rs b/selene-core/rust/runtime/inline.rs index 768af8ab..648c67a8 100644 --- a/selene-core/rust/runtime/inline.rs +++ b/selene-core/rust/runtime/inline.rs @@ -3,7 +3,9 @@ use super::{ plugin::{Errno, RuntimeInstance}, }; use crate::{ + gatewire::OwnedGateInstance, operation::plugin::{RuntimeGetOperationHandle, RuntimeGetOperationInterface}, + plugin::write_negotiated_gateset, runtime::Operation, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -40,10 +42,8 @@ impl RuntimeFFIAdapter { qfree_fn: Self::qfree, local_barrier_fn: Self::local_barrier, global_barrier_fn: Self::global_barrier, - rxy_gate_fn: Self::rxy_gate, - rzz_gate_fn: Self::rzz_gate, - rz_gate_fn: Self::rz_gate, - rpp_gate_fn: Self::rpp_gate, + gate_fn: Self::gate, + negotiate_gateset_fn: Self::negotiate_gateset, measure_fn: Self::measure, measure_leaked_fn: Self::measure_leaked, reset_fn: Self::reset, @@ -91,10 +91,7 @@ impl RuntimeFFIAdapter { reset_fn, custom_fn, set_batch_time_fn, - rzz_fn, - rxy_fn, - rz_fn, - rpp_fn, + gate_fn, .. } = ops.interface; if let Some(timing) = batch.runtime_source() { @@ -115,27 +112,10 @@ impl RuntimeFFIAdapter { result_id, } => measure_leaked_fn(ops.instance, *qubit_id, *result_id), Operation::Reset { qubit_id } => reset_fn(ops.instance, *qubit_id), - Operation::RXYGate { - qubit_id, - theta, - phi, - } => { - rxy_fn(ops.instance, *qubit_id, *theta, *phi); - } - Operation::RZGate { qubit_id, theta } => { - rz_fn(ops.instance, *qubit_id, *theta) + Operation::Gate { gate } => { + let data = gate.serialize(); + gate_fn(ops.instance, data.as_ptr(), data.len()) } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => rzz_fn(ops.instance, *qubit_id_1, *qubit_id_2, *theta), - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => rpp_fn(ops.instance, *qubit_id_1, *qubit_id_2, *theta, *phi), Operation::Custom { custom_tag, data } => custom_fn( ops.instance, *custom_tag, @@ -162,6 +142,23 @@ impl RuntimeFFIAdapter { }) } + unsafe extern "C" fn negotiate_gateset( + instance: RuntimeInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + result_to_errno("RuntimeFFIAdapter: negotiate_gateset failed", unsafe { + Self::with_runtime(instance, |runtime| { + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + runtime.negotiate_gateset(gateset) + }) + }) + }) + } + unsafe extern "C" fn get_metric( instance: RuntimeInstance, nth_metric: u8, @@ -213,46 +210,19 @@ impl RuntimeFFIAdapter { }) } - unsafe extern "C" fn rxy_gate( - instance: RuntimeInstance, - qubit: u64, - theta: f64, - phi: f64, - ) -> Errno { - result_to_errno("RuntimeFFIAdapter: rxy_gate failed", unsafe { - Self::with_runtime(instance, |runtime| runtime.rxy_gate(qubit, theta, phi)) - }) - } - - unsafe extern "C" fn rzz_gate( + unsafe extern "C" fn gate( instance: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - ) -> Errno { - result_to_errno("RuntimeFFIAdapter: rzz_gate failed", unsafe { - Self::with_runtime(instance, |runtime| runtime.rzz_gate(qubit0, qubit1, theta)) - }) - } - - unsafe extern "C" fn rz_gate(instance: RuntimeInstance, qubit: u64, theta: f64) -> Errno { - result_to_errno("RuntimeFFIAdapter: rz_gate failed", unsafe { - Self::with_runtime(instance, |runtime| runtime.rz_gate(qubit, theta)) - }) - } - - unsafe extern "C" fn rpp_gate( - instance: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, + data: *const u8, + data_len: usize, ) -> Errno { - result_to_errno("RuntimeFFIAdapter: rpp_gate failed", unsafe { - Self::with_runtime(instance, |runtime| { - runtime.rpp_gate(qubit0, qubit1, theta, phi) - }) - }) + result_to_errno( + "RuntimeFFIAdapter: gate failed", + (|| unsafe { + let gate = + OwnedGateInstance::deserialize(std::slice::from_raw_parts(data, data_len))?; + Self::with_runtime(instance, |runtime| runtime.gate(&gate)) + })(), + ) } unsafe extern "C" fn measure( @@ -409,10 +379,6 @@ pub struct RuntimeOperationInterface<'a> { pub qfree_fn: unsafe extern "C" fn(RuntimeInstance, u64) -> Errno, pub local_barrier_fn: unsafe extern "C" fn(RuntimeInstance, *const u64, u64, u64) -> Errno, pub global_barrier_fn: unsafe extern "C" fn(RuntimeInstance, u64) -> Errno, - pub rxy_gate_fn: unsafe extern "C" fn(RuntimeInstance, u64, f64, f64) -> Errno, - pub rzz_gate_fn: unsafe extern "C" fn(RuntimeInstance, u64, u64, f64) -> Errno, - pub rz_gate_fn: unsafe extern "C" fn(RuntimeInstance, u64, f64) -> Errno, - pub rpp_gate_fn: unsafe extern "C" fn(RuntimeInstance, u64, u64, f64, f64) -> Errno, pub measure_fn: unsafe extern "C" fn(RuntimeInstance, u64, *mut u64) -> Errno, pub measure_leaked_fn: unsafe extern "C" fn(RuntimeInstance, u64, *mut u64) -> Errno, pub reset_fn: unsafe extern "C" fn(RuntimeInstance, u64) -> Errno, @@ -426,5 +392,14 @@ pub struct RuntimeOperationInterface<'a> { pub custom_call_fn: unsafe extern "C" fn(RuntimeInstance, u64, *const ffi::c_void, usize, *mut u64) -> Errno, pub simulate_delay_fn: unsafe extern "C" fn(RuntimeInstance, u64) -> Errno, + pub gate_fn: unsafe extern "C" fn(RuntimeInstance, *const u8, usize) -> Errno, + pub negotiate_gateset_fn: unsafe extern "C" fn( + RuntimeInstance, + *const u8, + usize, + *mut u8, + usize, + *mut usize, + ) -> Errno, _marker: PhantomData<&'a ()>, } diff --git a/selene-core/rust/runtime/interface.rs b/selene-core/rust/runtime/interface.rs index b3362078..1b1fe1d7 100644 --- a/selene-core/rust/runtime/interface.rs +++ b/selene-core/rust/runtime/interface.rs @@ -3,11 +3,12 @@ use std::sync::Arc; use crate::utils::MetricValue; +use crate::gatewire::{DynamicGateSet, OwnedGateInstance}; use crate::operation::BatchOperation; /// Instances of runtime plugins implement this interface. /// -/// Many instances of a plugin may exist simultaneously. Instance are +/// Many instances of a plugin may exist simultaneously. Instances are /// generically constructed by impls of [RuntimeInterfaceFactory]. /// /// All functions can return an error, which will usually result in aborting the @@ -17,7 +18,7 @@ use crate::operation::BatchOperation; /// [crate::export_runtime_plugin!] pub trait RuntimeInterface { /// Signals that the instance of the runtime plugin should cleanup. Plugins - /// should `Err`` from any functions called on an instance after `exit`. + /// should return `Err` from any functions called on an instance after `exit`. fn exit(&mut self) -> Result<()>; /// Called to retrieve the next batch of operations from the runtime. /// @@ -32,12 +33,18 @@ pub trait RuntimeInterface { /// ideal place to perform validation (if applicable) and cleanup. fn shot_end(&mut self) -> Result<()>; + /// Negotiate the gates accepted from the user-facing interface and return + /// the gates this runtime may emit downstream. + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + Ok(gateset.clone()) + } + // If a runtime has special behaviour that a user may wish to invoke // from within a utility, this function serves as a generic interface // to that. For example, if the runtime supports some behaviour invokable // via frobnicate_qubits(qubit_a, qubit_b), and this doesn't make sense to // expose as a behaviour within standard guppy/hugr/etc, then a developer - // may create extensions to guppy/hugr/etc that define frobinate_qubits + // may create extensions to guppy/hugr/etc that define frobnicate_qubits // for a user to invoke as a symbol. A utility can then be created that // defines frobnicate_qubits as a symbol, invoking selene's exposed // runtime_call_direct function with a unique tag and some data, which @@ -69,30 +76,9 @@ pub trait RuntimeInterface { /// Free a qubit. An error should be returned if the qubit is not allocated. fn qfree(&mut self, qubit_id: u64) -> Result<()>; - /// Schedule an RXY gate to allocated qubit `qubit_id` with the given angles. - fn rxy_gate(&mut self, _qubit_id: u64, _theta: f64, _phi: f64) -> Result<()> { - bail!("RuntimeInterface: The chosen runtime does not support the RXY gate"); - } - - /// Schedule an RZZ gate between allocated qubits `qubit_id_1` and `qubit_id_2` with the given angle. - fn rzz_gate(&mut self, _qubit_id_1: u64, _qubit_id_2: u64, _theta: f64) -> Result<()> { - bail!("RuntimeInterface: The chosen runtime does not support the RZZ gate"); - } - - /// Schedule an RZ gate to allocated qubit `qubit_id` with the given angle. - fn rz_gate(&mut self, _qubit_id: u64, _theta: f64) -> Result<()> { - bail!("RuntimeInterface: The chosen runtime does not support the RZ gate"); - } - - /// Schedule an RPP gate between allocated qubits `qubit_id_1` and `qubit_id_2` with the given angles. - fn rpp_gate( - &mut self, - _qubit_id_1: u64, - _qubit_id_2: u64, - _theta: f64, - _phi: f64, - ) -> Result<()> { - bail!("RuntimeInterface: The chosen runtime does not support the RPP gate"); + /// Schedule a gate using the generic gatewire representation. + fn gate(&mut self, _gate: &OwnedGateInstance) -> Result<()> { + bail!("RuntimeInterface: The chosen runtime does not support this gate") } /// Schedule a measurement of allocated qubit `qubit_id`. The plugin should return a diff --git a/selene-core/rust/runtime/plugin.rs b/selene-core/rust/runtime/plugin.rs index 7520e8af..2a3155e1 100644 --- a/selene-core/rust/runtime/plugin.rs +++ b/selene-core/rust/runtime/plugin.rs @@ -1,14 +1,18 @@ +use crate::gatewire::{DynamicGateSet, OwnedGateInstance}; pub use crate::operation::Operation; pub use crate::operation::plugin::{ BatchBuilder, BatchExtractor, RuntimeExtractOperationHandle, RuntimeExtractOperationInstance, RuntimeExtractOperationInterface, RuntimeGetOperationHandle, RuntimeGetOperationInstance, RuntimeGetOperationInterface, }; +use crate::plugin::{ + NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + require_callback, validate_descriptor_v1, +}; use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; use super::{BatchOperation, RuntimeAPIVersion, RuntimeInterface, RuntimeInterfaceFactory}; use anyhow::{Result, anyhow}; -use libloading; use std::ffi::OsStr; use std::{ffi, sync::Arc}; @@ -21,19 +25,22 @@ pub type Errno = i32; pub struct RuntimePluginDescriptorV1 { pub struct_size: u64, pub api_version: u64, - pub init_fn: unsafe extern "C" fn( - handle: *mut RuntimeInstance, - n_qubits: u64, - start: u64, - argc: u32, - argv: *const *const ffi::c_char, - ) -> Errno, + pub init_fn: Option< + unsafe extern "C" fn( + handle: *mut RuntimeInstance, + n_qubits: u64, + start: u64, + argc: u32, + argv: *const *const ffi::c_char, + ) -> Errno, + >, pub exit_fn: Option Errno>, - pub get_next_operations_fn: + pub get_next_operations_fn: Option< unsafe extern "C" fn(handle: RuntimeInstance, ops: RuntimeGetOperationHandle) -> Errno, + >, pub shot_start_fn: - unsafe extern "C" fn(handle: RuntimeInstance, shot_id: u64, seed: u64) -> Errno, - pub shot_end_fn: unsafe extern "C" fn(handle: RuntimeInstance) -> Errno, + Option Errno>, + pub shot_end_fn: Option Errno>, pub get_metrics_fn: Option< unsafe extern "C" fn( handle: RuntimeInstance, @@ -43,49 +50,41 @@ pub struct RuntimePluginDescriptorV1 { value_out: *mut u64, ) -> i32, >, - pub qalloc_fn: unsafe extern "C" fn(handle: RuntimeInstance, qaddress_out: *mut u64) -> Errno, - pub qfree_fn: unsafe extern "C" fn(handle: RuntimeInstance, qaddress: u64) -> Errno, - pub local_barrier_fn: unsafe extern "C" fn( - handle: RuntimeInstance, - qubits: *const u64, - qubits_len: u64, - sleep_ns: u64, - ) -> Errno, - pub global_barrier_fn: unsafe extern "C" fn(handle: RuntimeInstance, sleep_ns: u64) -> Errno, - pub rxy_gate_fn: - unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, theta: f64, phi: f64) -> Errno, - pub rzz_gate_fn: unsafe extern "C" fn( - handle: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - ) -> Errno, - pub rz_gate_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, theta: f64) -> Errno, - pub rpp_gate_fn: unsafe extern "C" fn( - handle: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, - ) -> Errno, - pub measure_fn: + pub qalloc_fn: + Option Errno>, + pub qfree_fn: Option Errno>, + pub local_barrier_fn: Option< + unsafe extern "C" fn( + handle: RuntimeInstance, + qubits: *const u64, + qubits_len: u64, + sleep_ns: u64, + ) -> Errno, + >, + pub global_barrier_fn: + Option Errno>, + pub measure_fn: Option< unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, result_id: *mut u64) -> Errno, - pub measure_leaked_fn: + >, + pub measure_leaked_fn: Option< unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, result_id: *mut u64) -> Errno, - pub reset_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64) -> Errno, - pub force_result_fn: unsafe extern "C" fn(handle: RuntimeInstance, result_id: u64) -> Errno, + >, + pub reset_fn: Option Errno>, + pub force_result_fn: + Option Errno>, pub get_bool_result_fn: - unsafe extern "C" fn(handle: RuntimeInstance, id: u64, result: *mut i8) -> Errno, + Option Errno>, pub get_u64_result_fn: - unsafe extern "C" fn(handle: RuntimeInstance, id: u64, result: *mut u64) -> Errno, - pub set_bool_result_fn: + Option Errno>, + pub set_bool_result_fn: Option< unsafe extern "C" fn(handle: RuntimeInstance, result_id: u64, result: bool) -> Errno, + >, pub set_u64_result_fn: - unsafe extern "C" fn(handle: RuntimeInstance, result_id: u64, result: u64) -> Errno, + Option Errno>, pub increment_future_refcount_fn: - unsafe extern "C" fn(handle: RuntimeInstance, result_id: u64) -> Errno, + Option Errno>, pub decrement_future_refcount_fn: - unsafe extern "C" fn(handle: RuntimeInstance, result_id: u64) -> Errno, + Option Errno>, pub custom_call_fn: Option< unsafe extern "C" fn( handle: RuntimeInstance, @@ -97,6 +96,30 @@ pub struct RuntimePluginDescriptorV1 { >, pub simulate_delay_fn: Option Errno>, + pub gate_fn: + Option Errno>, + pub negotiate_gateset_fn: Option< + unsafe extern "C" fn( + handle: RuntimeInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno, + >, +} + +impl PluginDescriptorV1 for RuntimePluginDescriptorV1 { + const KIND: &'static str = "Runtime"; + + fn struct_size(&self) -> u64 { + self.struct_size + } + + fn api_version(&self) -> u64 { + self.api_version + } } /// Provides a runtime engine backend that controls a plugin, in the form of a shared object. @@ -106,7 +129,7 @@ pub struct RuntimePluginDescriptorV1 { /// /// Users should be cautious about the plugins they use, as it is possible that mistakes /// or malicious code could be present in the plugin, and as with all external libraries, due -/// dilligence must be done to verify the source and the trustworthiness of the provider. +/// diligence must be done to verify the source and the trustworthiness of the provider. pub struct RuntimePluginInterface { _lib: libloading::Library, init_fn: unsafe extern "C" fn( @@ -139,22 +162,8 @@ pub struct RuntimePluginInterface { sleep_ns: u64, ) -> Errno, global_barrier_fn: unsafe extern "C" fn(handle: RuntimeInstance, sleep_ns: u64) -> Errno, - rxy_gate_fn: - unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, theta: f64, phi: f64) -> Errno, - rzz_gate_fn: unsafe extern "C" fn( - handle: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - ) -> Errno, - rz_gate_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, theta: f64) -> Errno, - rpp_gate_fn: unsafe extern "C" fn( - handle: RuntimeInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, - ) -> Errno, + gate_fn: unsafe extern "C" fn(handle: RuntimeInstance, data: *const u8, len: usize) -> Errno, + negotiate_gateset_fn: Option>, measure_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, result_id: *mut u64) -> Errno, measure_leaked_fn: @@ -189,65 +198,86 @@ pub struct RuntimePluginInterface { impl RuntimePluginInterface { /// Loads a runtime plugin from a file. pub fn new_from_file(plugin_file: impl AsRef) -> Result> { - let lib = unsafe { libloading::Library::new(plugin_file.as_ref()) }.map_err(|e| { - anyhow!( - "Failed to load runtime plugin: {}. Error: {}", - plugin_file.as_ref().to_string_lossy(), - e - ) - })?; + let lib = load_library("runtime", &plugin_file)?; let descriptor = unsafe { - lib.get::(b"selene_runtime_plugin_descriptor_v1") - .ok() - .map(|d| *d) - .or_else(|| { - lib.get:: *const RuntimePluginDescriptorV1>( - b"selene_runtime_get_plugin_descriptor_v1", - ) - .ok() - .and_then(|f| { - let ptr = f(); - if ptr.is_null() { None } else { Some(*ptr) } - }) - }) - }; - let descriptor = descriptor.ok_or_else(|| { - anyhow!( - "Runtime plugin '{}' does not expose either selene_runtime_plugin_descriptor_v1 or selene_runtime_get_plugin_descriptor_v1", - plugin_file.as_ref().to_string_lossy() + load_descriptor_v1::( + &lib, + &plugin_file, + b"selene_runtime_plugin_descriptor_v1", + b"selene_runtime_get_plugin_descriptor_v1", ) + }?; + validate_descriptor_v1(&descriptor, |api_version| { + RuntimeAPIVersion::from(api_version).validate() })?; - let version: RuntimeAPIVersion = descriptor.api_version.into(); - version.validate()?; - if descriptor.struct_size < core::mem::size_of::() as u64 { - return Err(anyhow!("Runtime plugin descriptor is too small for v1 ABI")); - } Ok(Arc::new(Self { _lib: lib, - init_fn: descriptor.init_fn, + init_fn: require_callback("Runtime", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, - get_next_operations_fn: descriptor.get_next_operations_fn, - shot_start_fn: descriptor.shot_start_fn, - shot_end_fn: descriptor.shot_end_fn, + get_next_operations_fn: require_callback( + "Runtime", + "get_next_operations_fn", + descriptor.get_next_operations_fn, + )?, + shot_start_fn: require_callback("Runtime", "shot_start_fn", descriptor.shot_start_fn)?, + shot_end_fn: require_callback("Runtime", "shot_end_fn", descriptor.shot_end_fn)?, get_metrics_fn: descriptor.get_metrics_fn, - qalloc_fn: descriptor.qalloc_fn, - qfree_fn: descriptor.qfree_fn, - local_barrier_fn: descriptor.local_barrier_fn, - global_barrier_fn: descriptor.global_barrier_fn, - rxy_gate_fn: descriptor.rxy_gate_fn, - rzz_gate_fn: descriptor.rzz_gate_fn, - rz_gate_fn: descriptor.rz_gate_fn, - rpp_gate_fn: descriptor.rpp_gate_fn, - measure_fn: descriptor.measure_fn, - measure_leaked_fn: descriptor.measure_leaked_fn, - reset_fn: descriptor.reset_fn, - force_result_fn: descriptor.force_result_fn, - get_bool_result_fn: descriptor.get_bool_result_fn, - get_u64_result_fn: descriptor.get_u64_result_fn, - set_bool_result_fn: descriptor.set_bool_result_fn, - set_u64_result_fn: descriptor.set_u64_result_fn, - increment_future_refcount_fn: descriptor.increment_future_refcount_fn, - decrement_future_refcount_fn: descriptor.decrement_future_refcount_fn, + qalloc_fn: require_callback("Runtime", "qalloc_fn", descriptor.qalloc_fn)?, + qfree_fn: require_callback("Runtime", "qfree_fn", descriptor.qfree_fn)?, + local_barrier_fn: require_callback( + "Runtime", + "local_barrier_fn", + descriptor.local_barrier_fn, + )?, + global_barrier_fn: require_callback( + "Runtime", + "global_barrier_fn", + descriptor.global_barrier_fn, + )?, + gate_fn: require_callback("Runtime", "gate_fn", descriptor.gate_fn)?, + negotiate_gateset_fn: descriptor.negotiate_gateset_fn, + measure_fn: require_callback("Runtime", "measure_fn", descriptor.measure_fn)?, + measure_leaked_fn: require_callback( + "Runtime", + "measure_leaked_fn", + descriptor.measure_leaked_fn, + )?, + reset_fn: require_callback("Runtime", "reset_fn", descriptor.reset_fn)?, + force_result_fn: require_callback( + "Runtime", + "force_result_fn", + descriptor.force_result_fn, + )?, + get_bool_result_fn: require_callback( + "Runtime", + "get_bool_result_fn", + descriptor.get_bool_result_fn, + )?, + get_u64_result_fn: require_callback( + "Runtime", + "get_u64_result_fn", + descriptor.get_u64_result_fn, + )?, + set_bool_result_fn: require_callback( + "Runtime", + "set_bool_result_fn", + descriptor.set_bool_result_fn, + )?, + set_u64_result_fn: require_callback( + "Runtime", + "set_u64_result_fn", + descriptor.set_u64_result_fn, + )?, + increment_future_refcount_fn: require_callback( + "Runtime", + "increment_future_refcount_fn", + descriptor.increment_future_refcount_fn, + )?, + decrement_future_refcount_fn: require_callback( + "Runtime", + "decrement_future_refcount_fn", + descriptor.decrement_future_refcount_fn, + )?, custom_call_fn: descriptor.custom_call_fn, simulate_delay_fn: descriptor.simulate_delay_fn, })) @@ -316,6 +346,15 @@ impl RuntimeInterface for RuntimePlugin { ) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_gateset( + "RuntimePlugin", + self.instance, + self.interface.negotiate_gateset_fn, + gateset, + ) + } + fn get_metric(&mut self, nth_metric: u8) -> Result> { let Some(get_metrics_fn) = self.interface.get_metrics_fn else { return Ok(None); @@ -365,33 +404,11 @@ impl RuntimeInterface for RuntimePlugin { ) } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + let data = gate.serialize(); check_errno( - unsafe { (self.interface.rxy_gate_fn)(self.instance, qubit_id, theta, phi) }, - || anyhow!("RuntimePlugin: rxy_gate failed"), - ) - } - - fn rzz_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result<()> { - check_errno( - unsafe { (self.interface.rzz_gate_fn)(self.instance, qubit_id_1, qubit_id_2, theta) }, - || anyhow!("RuntimePlugin: rzz_gate failed"), - ) - } - - fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - check_errno( - unsafe { (self.interface.rz_gate_fn)(self.instance, qubit_id, theta) }, - || anyhow!("RuntimePlugin: rz_gate failed"), - ) - } - - fn rpp_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64, phi: f64) -> Result<()> { - check_errno( - unsafe { - (self.interface.rpp_gate_fn)(self.instance, qubit_id_1, qubit_id_2, theta, phi) - }, - || anyhow!("RuntimePlugin: rpp_gate failed"), + unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, + || anyhow!("RuntimePlugin: gate failed"), ) } diff --git a/selene-core/rust/runtime/version.rs b/selene-core/rust/runtime/version.rs index c86187e4..478ab48c 100644 --- a/selene-core/rust/runtime/version.rs +++ b/selene-core/rust/runtime/version.rs @@ -31,13 +31,17 @@ impl From for u64 { } } -pub const CURRENT_API_VERSION: RuntimeAPIVersion = RuntimeAPIVersion { +pub(crate) const CURRENT_API_VERSION: RuntimeAPIVersion = RuntimeAPIVersion { reserved: 0, major: 0, minor: 3, patch: 0, }; +pub const fn current_api_version() -> RuntimeAPIVersion { + CURRENT_API_VERSION +} + // CHANGELOG: // 0.0.1: Initial version // 0.0.2: Introduced MeasureLeaked, changed get_result to get_bool_result and get_u64_result diff --git a/selene-core/rust/simulator.rs b/selene-core/rust/simulator.rs index 5cf9dacc..0bc32b12 100644 --- a/selene-core/rust/simulator.rs +++ b/selene-core/rust/simulator.rs @@ -16,6 +16,8 @@ use crate::utils::{MetricValue, check_errno, read_raw_metric}; use anyhow::{Result, anyhow}; use crate::error_model::BatchResult; +use crate::gatewire::DynamicGateSet; +use crate::plugin as plugin_utils; use crate::runtime::{BatchOperation, Operation}; enum SimulatorBacking { @@ -112,69 +114,30 @@ impl SimulatorInterface for Simulator { ) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + plugin_utils::negotiate_gateset( + "Simulator", + self.handle.instance, + Some(self.handle.interface.negotiate_gateset_fn), + gateset, + ) + } + fn handle_operations(&mut self, operations: BatchOperation) -> Result { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => { - check_errno( - unsafe { - (self.handle.interface.rxy_fn)( - self.handle.instance, - qubit_id, - theta, - phi, - ) - }, - || anyhow!("Simulator: rxy failed"), - )?; - } - Operation::RZGate { qubit_id, theta } => { - check_errno( - unsafe { - (self.handle.interface.rz_fn)(self.handle.instance, qubit_id, theta) - }, - || anyhow!("Simulator: rz failed"), - )?; - } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => { - check_errno( - unsafe { - (self.handle.interface.rzz_fn)( - self.handle.instance, - qubit_id_1, - qubit_id_2, - theta, - ) - }, - || anyhow!("Simulator: rzz failed"), - )?; - } - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => { + Operation::Gate { gate } => { + let data = gate.serialize(); check_errno( unsafe { - (self.handle.interface.rpp_fn)( + (self.handle.interface.gate_fn)( self.handle.instance, - qubit_id_1, - qubit_id_2, - theta, - phi, + data.as_ptr(), + data.len(), ) }, - || anyhow!("Simulator: rpp failed"), + || anyhow!("Simulator: gate failed"), )?; } Operation::Measure { diff --git a/selene-core/rust/simulator/README.md b/selene-core/rust/simulator/README.md index b72fcb6e..51860142 100644 --- a/selene-core/rust/simulator/README.md +++ b/selene-core/rust/simulator/README.md @@ -23,8 +23,7 @@ public: MySimulator(std::uint64_t n_qubits); ~MySimulator(); void next_shot(std::uint64_t seed); - void rzz(std::uint64_t q0, std::uint64_t q1, double theta); - void rxy(std::uint64_t q); + void gate(const GateInstance& gate); bool measure(std::uint64_t q); void reset(std::uint64_t q); } @@ -39,8 +38,7 @@ struct MySimulator { impl MySimulator { fn new(n_qubits: u64) -> Self; fn next_shot(seed: u64); - fn rzz(&mut self, q0: u64, q1: u64, theta: f64); - fn rxy(&mut self, q: u64); + fn gate(&mut self, gate: &OwnedGateInstance); fn measure(&mut self, q: u64) -> bool; fn reset(&mut self, q: u64); } @@ -57,8 +55,7 @@ struct MySimulator { void MySimulator_create(struct MySimulator** sim, uint64_t n_qubits); void MySimulator_destroy(struct MySimulator* sim); void MySimulator_next_shot(struct MySimulator* sim, uint64_t seed); -void MySimulator_rzz(struct MySimulator* sim, uint64_t q0, uint64_t q1, double theta); -void MySimulator_rxy(struct MySimulator* sim, uint64_t q); +void MySimulator_gate(struct MySimulator* sim, const GateInstance* gate); bool MySimulator_measure(struct MySimulator* sim, uint64_t q); void MySimulator_reset(struct MySimulator* sim, uint64_t q); ``` @@ -69,8 +66,7 @@ and one that does not expose the internal state might look like: void MySimulator_create(void** sim, uint64_t n_qubits, uint64_t random_seed); void MySimulator_destroy(void* sim); void MySimulator_next_shot(void* sim, uint64_t seed); -void MySimulator_rzz(void* sim, uint64_t q0, uint64_t q1, double theta); -void MySimulator_rxy(void* sim, uint64_t q); +void MySimulator_gate(void* sim, const GateInstance* gate); bool MySimulator_measure(void* sim, uint64_t q); void MySimulator_reset(void* sim, uint64_t q); ``` @@ -88,8 +84,8 @@ MySimulator_create(&instance_1, 10); MySimulator_create(&instance_2, 10); MySimulator_next_shot(instance_1, 1234); MySimulator_next_shot(instance_2, 1235); -MySimulator_rzz(instance_1, 0, 1, 0.123); -MySimulator_rzz(instance_2, 0, 1, 0.456); +MySimulator_gate(instance_1, &gate_1); +MySimulator_gate(instance_2, &gate_2); MySimulator_destroy(instance_1); MySimulator_destroy(instance_2); ``` @@ -121,7 +117,4 @@ and keeping lifetime management simple. One can define both of these with help from [helper.rs](./helper.rs), which allows them to use a normal Rust struct and implement the SimulatorHelper trait. A `export_simulator_plugin!(YourStructName)` macro invocation will provide -`pub extern "C"` functions automatically. For testing purposes, the -`crate_to_inline_simulator!()` macro utilises those same exposed functions to -create an InlineSimulator, which is then usable in the selene-core simulator test framework. - +`pub extern "C"` functions automatically. diff --git a/selene-core/rust/simulator/conformance_testing/framework.rs b/selene-core/rust/simulator/conformance_testing/framework.rs index 88201015..6326f737 100644 --- a/selene-core/rust/simulator/conformance_testing/framework.rs +++ b/selene-core/rust/simulator/conformance_testing/framework.rs @@ -95,10 +95,10 @@ const HALF_PI: f64 = std::f64::consts::FRAC_PI_2; pub const QUARTER_PI: f64 = std::f64::consts::FRAC_PI_4; enum Operation { - Rz(u64, f64), - Rxy(u64, f64, f64), - Rzz(u64, u64, f64), - Rpp(u64, u64, f64, f64), + RZ(u64, f64), + PhasedX(u64, f64, f64), + ZZPhase(u64, u64, f64), + PhasedXX(u64, u64, f64, f64), Reset(u64), Measure(u64), } @@ -128,30 +128,20 @@ impl EngineState { } pub fn run_operation(&mut self, operation: &Operation) { match operation { - Operation::Rz(q, theta) => { + Operation::RZ(q, theta) => { self.qubit_phases[*q as usize] += theta; } - Operation::Rxy(q, theta, phi) => { - self.apply_void(SimulatorOperation::RXYGate { - qubit_id: *q, - theta: *theta, - phi: *phi - self.qubit_phases[*q as usize], - }); + Operation::PhasedX(q, theta, phi) => { + self.apply_void( + SimulatorOperation::phased_x(*q, *theta, *phi - self.qubit_phases[*q as usize]) + .unwrap(), + ); } - Operation::Rpp(q0, q1, theta, phi) => { - self.apply_void(SimulatorOperation::RPPGate { - qubit_id_1: *q0, - qubit_id_2: *q1, - theta: *theta, - phi: *phi, - }); + Operation::PhasedXX(q0, q1, theta, phi) => { + self.apply_void(SimulatorOperation::phased_xx(*q0, *q1, *theta, *phi).unwrap()); } - Operation::Rzz(q0, q1, theta) => { - self.apply_void(SimulatorOperation::RZZGate { - qubit_id_1: *q0, - qubit_id_2: *q1, - theta: *theta, - }); + Operation::ZZPhase(q0, q1, theta) => { + self.apply_void(SimulatorOperation::zz_phase(*q0, *q1, *theta).unwrap()); } Operation::Reset(q) => { self.apply_void(SimulatorOperation::Reset { qubit_id: *q }); @@ -298,19 +288,19 @@ impl TestFramework { } pub fn rz(&mut self, qubit: u64, theta: f64) -> &mut Self { - self.add_operation(Operation::Rz(qubit, theta)); + self.add_operation(Operation::RZ(qubit, theta)); self } - pub fn rxy(&mut self, qubit: u64, theta: f64, phi: f64) -> &mut Self { - self.add_operation(Operation::Rxy(qubit, theta, phi)); + pub fn phased_x(&mut self, qubit: u64, theta: f64, phi: f64) -> &mut Self { + self.add_operation(Operation::PhasedX(qubit, theta, phi)); self } - pub fn rzz(&mut self, qubit1: u64, qubit2: u64, theta: f64) -> &mut Self { - self.add_operation(Operation::Rzz(qubit1, qubit2, theta)); + pub fn zz_phase(&mut self, qubit1: u64, qubit2: u64, theta: f64) -> &mut Self { + self.add_operation(Operation::ZZPhase(qubit1, qubit2, theta)); self } - pub fn rpp(&mut self, qubit1: u64, qubit2: u64, theta: f64, phi: f64) -> &mut Self { - self.add_operation(Operation::Rpp(qubit1, qubit2, theta, phi)); + pub fn phased_xx(&mut self, qubit1: u64, qubit2: u64, theta: f64, phi: f64) -> &mut Self { + self.add_operation(Operation::PhasedXX(qubit1, qubit2, theta, phi)); self } pub fn reset(&mut self, qubit: u64) -> &mut Self { @@ -323,11 +313,11 @@ impl TestFramework { } pub fn rx(&mut self, qubit: u64, theta: f64) -> &mut Self { - self.rxy(qubit, theta, 0.0); + self.phased_x(qubit, theta, 0.0); self } pub fn ry(&mut self, qubit: u64, theta: f64) -> &mut Self { - self.rxy(qubit, theta, HALF_PI); + self.phased_x(qubit, theta, HALF_PI); self } pub fn x(&mut self, qubit: u64) -> &mut Self { @@ -344,16 +334,16 @@ impl TestFramework { } pub fn h(&mut self, qubit: u64) -> &mut Self { - self.rxy(qubit, HALF_PI, -HALF_PI); + self.phased_x(qubit, HALF_PI, -HALF_PI); self.z(qubit); self } pub fn cnot(&mut self, control: u64, target: u64) -> &mut Self { - self.rxy(target, -HALF_PI, HALF_PI); - self.rzz(control, target, HALF_PI); + self.phased_x(target, -HALF_PI, HALF_PI); + self.zz_phase(control, target, HALF_PI); self.rz(control, -HALF_PI); - self.rxy(target, HALF_PI, PI); + self.phased_x(target, HALF_PI, PI); self.rz(target, -HALF_PI); self } diff --git a/selene-core/rust/simulator/helper.rs b/selene-core/rust/simulator/helper.rs index cada9165..20eadb74 100644 --- a/selene-core/rust/simulator/helper.rs +++ b/selene-core/rust/simulator/helper.rs @@ -1,5 +1,5 @@ //! Defines the [crate::export_simulator_plugin] and helpers for implementing -//! error model plugins as rust crates. +//! simulator plugins as rust crates. //! //! See `selene-coinflip-plugin` for an example of how to implement a plugin. use std::{ffi, mem, sync::Arc}; @@ -9,6 +9,8 @@ use super::{ interface::SimulatorInterfaceFactory, plugin::{Errno, SimulatorInstance}, }; +use crate::gatewire::OwnedGateInstance; +use crate::plugin::write_negotiated_gateset; use crate::runtime::{BatchOperation, Operation}; use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; @@ -94,6 +96,23 @@ impl Helper { Self::with_simulator_instance(instance, |simulator| simulator.shot_end()), ) } + pub unsafe fn negotiate_gateset( + instance: SimulatorInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno { + result_to_errno( + "Failed to negotiate gateset", + Self::with_simulator_instance(instance, |simulator| unsafe { + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + simulator.negotiate_gateset(gateset) + }) + }), + ) + } pub unsafe fn get_metric( instance: SimulatorInstance, nth_metric: u8, @@ -128,80 +147,20 @@ impl Helper { }), ) } - pub unsafe fn rxy(instance: SimulatorInstance, qubit: u64, theta: f64, phi: f64) -> Errno { + pub unsafe fn gate(instance: SimulatorInstance, data: *const u8, data_len: usize) -> Errno { result_to_errno( - "Failed to apply RXY gate", + "Failed to apply gate", Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RXYGate { - qubit_id: qubit, - theta, - phi, - }))?; + let gate = OwnedGateInstance::deserialize(unsafe { + std::slice::from_raw_parts(data, data_len) + })?; + let results = simulator.handle_operations(Self::singleton_batch( + Operation::from_gate_instance(gate)?, + ))?; if results.bool_results.is_empty() && results.u64_results.is_empty() { Ok(()) } else { - anyhow::bail!("RXY unexpectedly produced results") - } - }), - ) - } - pub unsafe fn rz(instance: SimulatorInstance, qubit: u64, theta: f64) -> Errno { - result_to_errno( - "Failed to apply RZ gate", - Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZGate { - qubit_id: qubit, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZ unexpectedly produced results") - } - }), - ) - } - pub unsafe fn rzz(instance: SimulatorInstance, qubit1: u64, qubit2: u64, theta: f64) -> Errno { - result_to_errno( - "Failed to apply RZZ gate", - Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZZGate { - qubit_id_1: qubit1, - qubit_id_2: qubit2, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZZ unexpectedly produced results") - } - }), - ) - } - pub unsafe fn rpp( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, - ) -> Errno { - result_to_errno( - "Failed to apply RPP gate", - Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RPPGate { - qubit_id_1: qubit1, - qubit_id_2: qubit2, - theta, - phi, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RPP unexpectedly produced results") + anyhow::bail!("Gate unexpectedly produced results") } }), ) @@ -254,28 +213,6 @@ impl Helper { } } -#[macro_export] -#[allow(clippy::crate_in_macro_def)] -macro_rules! crate_to_inline_simulator { - () => { - SimulatorInlineInterface { - init_fn: crate::selene_simulator_init, - exit_fn: Some(crate::selene_simulator_exit), - shot_start_fn: crate::selene_simulator_shot_start, - shot_end_fn: crate::selene_simulator_shot_end, - rxy_fn: crate::selene_simulator_operation_rxy, - rzz_fn: crate::selene_simulator_operation_rzz, - rx_fn: crate::selene_simulator_operation_rz, - rpp_fn: crate::selene_simulator_operation_rpp, - measure_fn: crate::selene_simulator_operation_measure, - reset_fn: crate::selene_simulator_operation_reset, - postselect_fn: Some(crate::selene_simulator_operation_postselect), - get_metrics_fn: Some(crate::selene_simulator_get_metrics), - dump_state_fn: crate::selene_simulator_dump_state, - } - }; -} - #[macro_export] /// A macro to export a simulator plugin from a crate /// @@ -297,7 +234,7 @@ macro_rules! export_simulator_plugin { plugin::{ Errno, SimulatorInstance, SimulatorPluginDescriptorV1, }, - version::CURRENT_API_VERSION, + version::current_api_version, }; use std::cell::LazyCell; @@ -366,7 +303,7 @@ macro_rules! export_simulator_plugin { /// This function is called at the end of a shot, and it is responsible for /// finalising the simulator plugin for that shot. For example, it may /// clean up any state, such as accumulators or buffers, or set a state vector - /// to zero. We recommenA call to + /// to zero. A call to /// this function will usually be followed either by a call to /// `selene_simulator_shot_start` to prepare for the following shot, or by /// a call to `selene_simulator_exit` to shut down the instance. @@ -376,58 +313,25 @@ macro_rules! export_simulator_plugin { Helper::shot_end(instance) } - /// Apply an RXY gate to the qubit at the requested index, with the provided - /// angles. This gate is also commonly known as the PhasedX gate or the R1XY gate, - /// performing: - /// $R_z(\phi)R_x(\theta)R_z(-\phi)$ - /// (in matrix-multiplication order). - unsafe extern "C" fn selene_simulator_operation_rxy( + /// Negotiate the gates this simulator may receive, encoded as a gatewire gateset. + unsafe extern "C" fn selene_simulator_negotiate_gateset( instance: SimulatorInstance, - qubit: u64, - theta: f64, - phi: f64, - ) -> i32 { - Helper::rxy(instance, qubit, theta, phi) - } - - /// Apply an RZZ gate to the qubits at the requested indices, with the provided - /// angles. This gate is also commonly known as the ZZPhase gate or the R2ZZ gate, - /// performing - /// $diag(\chi^*, \chi, \chi, \chi^*)$ - /// where - /// $\chi = \exp(i \pi \theta / 2)$ - unsafe extern "C" fn selene_simulator_operation_rzz( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - ) -> i32 { - Helper::rzz(instance, qubit1, qubit2, theta) - } - - /// Apply an RZ gate to the qubit at the requested index, with the - /// provided angle. This should perform: - /// $diag(\chi^*, \chi)$ - /// where - /// $chi = \exp(i \pi \theta / 2)$ - unsafe extern "C" fn selene_simulator_operation_rz( - instance: SimulatorInstance, - qubit: u64, - theta: f64, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> i32 { - Helper::rz(instance, qubit, theta) + Helper::negotiate_gateset(instance, input, input_len, output, output_len, written) } - /// Apply an RPP gate to the qubits at the requested indices, with the - /// provided angles. - unsafe extern "C" fn selene_simulator_operation_rpp( + /// Apply a gate encoded with gatewire. + unsafe extern "C" fn selene_simulator_operation_gate( instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, + data: *const u8, + data_len: usize, ) -> i32 { - Helper::rpp(instance, qubit1, qubit2, theta, phi) + Helper::gate(instance, data, data_len) } /// Measure the qubit at the requested index. This is a destructive @@ -499,22 +403,20 @@ macro_rules! export_simulator_plugin { selene_core::export_plugin_descriptor_v1!( selene_simulator_plugin_descriptor_v1, SimulatorPluginDescriptorV1, - CURRENT_API_VERSION.as_u64(), + current_api_version().as_u64(), { get_name_fn: None, - init_fn: selene_simulator_init, + init_fn: Some(selene_simulator_init), exit_fn: Some(selene_simulator_exit), - shot_start_fn: selene_simulator_shot_start, - shot_end_fn: selene_simulator_shot_end, - rxy_fn: Some(selene_simulator_operation_rxy), - rz_fn: Some(selene_simulator_operation_rz), - rzz_fn: Some(selene_simulator_operation_rzz), - rpp_fn: Some(selene_simulator_operation_rpp), - measure_fn: selene_simulator_operation_measure, + shot_start_fn: Some(selene_simulator_shot_start), + shot_end_fn: Some(selene_simulator_shot_end), + negotiate_gateset_fn: Some(selene_simulator_negotiate_gateset), + gate_fn: Some(selene_simulator_operation_gate), + measure_fn: Some(selene_simulator_operation_measure), postselect_fn: Some(selene_simulator_operation_postselect), - reset_fn: selene_simulator_operation_reset, + reset_fn: Some(selene_simulator_operation_reset), get_metrics_fn: Some(selene_simulator_get_metrics), - dump_state_fn: selene_simulator_dump_state, + dump_state_fn: Some(selene_simulator_dump_state), } ); diff --git a/selene-core/rust/simulator/inline.rs b/selene-core/rust/simulator/inline.rs index 765d5a9d..67e44f4d 100644 --- a/selene-core/rust/simulator/inline.rs +++ b/selene-core/rust/simulator/inline.rs @@ -1,5 +1,7 @@ use super::{SimulatorInterface, plugin::SimulatorInstance}; use crate::{ + gatewire::OwnedGateInstance, + plugin::write_negotiated_gateset, runtime::{BatchOperation, Operation}, simulator::plugin::Errno, utils::{result_of_errno_to_errno, result_to_errno}, @@ -28,10 +30,8 @@ pub fn borrowed_simulator_interface( exit_fn: BorrowedSimulatorBridge::exit, shot_start_fn: BorrowedSimulatorBridge::shot_start, shot_end_fn: BorrowedSimulatorBridge::shot_end, - rxy_fn: BorrowedSimulatorBridge::rxy, - rz_fn: BorrowedSimulatorBridge::rz, - rzz_fn: BorrowedSimulatorBridge::rzz, - rpp_fn: BorrowedSimulatorBridge::rpp, + negotiate_gateset_fn: BorrowedSimulatorBridge::negotiate_gateset, + gate_fn: BorrowedSimulatorBridge::gate, measure_fn: BorrowedSimulatorBridge::measure, postselect_fn: BorrowedSimulatorBridge::postselect, reset_fn: BorrowedSimulatorBridge::reset, @@ -71,81 +71,46 @@ impl BorrowedSimulatorBridge { Self::with_simulator(instance, |simulator| simulator.shot_end()) }) } - unsafe extern "C" fn rxy( + unsafe extern "C" fn negotiate_gateset( instance: SimulatorInstance, - qubit: u64, - theta: f64, - phi: f64, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> Errno { - result_to_errno("BorrowedSimulatorBridge: rxy failed", unsafe { - Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RXYGate { - qubit_id: qubit, - theta, - phi, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RXY unexpectedly produced results") - } - }) - }) - } - unsafe extern "C" fn rz(instance: SimulatorInstance, qubit: u64, theta: f64) -> Errno { - result_to_errno("BorrowedSimulatorBridge: rz failed", unsafe { - Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZGate { - qubit_id: qubit, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZ unexpectedly produced results") - } - }) - }) - } - unsafe extern "C" fn rzz(instance: SimulatorInstance, q1: u64, q2: u64, theta: f64) -> Errno { - result_to_errno("BorrowedSimulatorBridge: rzz failed", unsafe { - Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZZGate { - qubit_id_1: q1, - qubit_id_2: q2, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZZ unexpectedly produced results") - } - }) - }) + result_to_errno( + "BorrowedSimulatorBridge: negotiate_gateset failed", + unsafe { + Self::with_simulator(instance, |simulator| { + write_negotiated_gateset( + input, + input_len, + output, + output_len, + written, + |gateset| simulator.negotiate_gateset(gateset), + ) + }) + }, + ) } - unsafe extern "C" fn rpp( + unsafe extern "C" fn gate( instance: SimulatorInstance, - q1: u64, - q2: u64, - theta: f64, - phi: f64, + data: *const u8, + data_len: usize, ) -> Errno { - result_to_errno("BorrowedSimulatorBridge: rpp failed", unsafe { + result_to_errno("BorrowedSimulatorBridge: gate failed", unsafe { Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RPPGate { - qubit_id_1: q1, - qubit_id_2: q2, - theta, - phi, - }))?; + let gate = + OwnedGateInstance::deserialize(std::slice::from_raw_parts(data, data_len))?; + let results = simulator.handle_operations(Self::singleton_batch( + Operation::from_gate_instance(gate)?, + ))?; if results.bool_results.is_empty() && results.u64_results.is_empty() { Ok(()) } else { - anyhow::bail!("RPP unexpectedly produced results") + anyhow::bail!("Gate unexpectedly produced results") } }) }) @@ -242,10 +207,8 @@ impl SimulatorFFIAdapter { exit_fn: Self::exit, shot_start_fn: Self::shot_start, shot_end_fn: Self::shot_end, - rxy_fn: Self::rxy, - rz_fn: Self::rz, - rzz_fn: Self::rzz, - rpp_fn: Self::rpp, + negotiate_gateset_fn: Self::negotiate_gateset, + gate_fn: Self::gate, measure_fn: Self::measure, postselect_fn: Self::postselect, reset_fn: Self::reset, @@ -286,89 +249,39 @@ impl SimulatorFFIAdapter { }) } - unsafe extern "C" fn rxy( + unsafe extern "C" fn negotiate_gateset( instance: SimulatorInstance, - qubit: u64, - theta: f64, - phi: f64, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> Errno { - result_to_errno("SimulatorFFIAdapter: rxy failed", unsafe { + result_to_errno("SimulatorFFIAdapter: negotiate_gateset failed", unsafe { Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RXYGate { - qubit_id: qubit, - theta, - phi, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RXY unexpectedly produced results") - } - }) - }) - } - - unsafe extern "C" fn rz(instance: SimulatorInstance, qubit: u64, theta: f64) -> Errno { - result_to_errno("SimulatorFFIAdapter: rz failed", unsafe { - Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZGate { - qubit_id: qubit, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZ unexpectedly produced results") - } - }) - }) - } - - unsafe extern "C" fn rzz( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - ) -> Errno { - result_to_errno("SimulatorFFIAdapter: rzz failed", unsafe { - Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RZZGate { - qubit_id_1: qubit1, - qubit_id_2: qubit2, - theta, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("RZZ unexpectedly produced results") - } + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + simulator.negotiate_gateset(gateset) + }) }) }) } - unsafe extern "C" fn rpp( + unsafe extern "C" fn gate( instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, + data: *const u8, + data_len: usize, ) -> Errno { - result_to_errno("SimulatorFFIAdapter: rpp failed", unsafe { + result_to_errno("SimulatorFFIAdapter: gate failed", unsafe { Self::with_simulator(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::RPPGate { - qubit_id_1: qubit1, - qubit_id_2: qubit2, - theta, - phi, - }))?; + let gate = + OwnedGateInstance::deserialize(std::slice::from_raw_parts(data, data_len))?; + let results = simulator.handle_operations(Self::singleton_batch( + Operation::from_gate_instance(gate)?, + ))?; if results.bool_results.is_empty() && results.u64_results.is_empty() { Ok(()) } else { - anyhow::bail!("RPP unexpectedly produced results") + anyhow::bail!("Gate unexpectedly produced results") } }) }) @@ -472,26 +385,6 @@ pub struct SimulatorOperationInterface<'a> { pub shot_start_fn: unsafe extern "C" fn(instance: SimulatorInstance, shot_id: u64, seed: u64) -> Errno, pub shot_end_fn: unsafe extern "C" fn(instance: SimulatorInstance) -> Errno, - pub rxy_fn: unsafe extern "C" fn( - instance: SimulatorInstance, - qubit: u64, - theta: f64, - phi: f64, - ) -> Errno, - pub rz_fn: unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64, theta: f64) -> Errno, - pub rzz_fn: unsafe extern "C" fn( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - ) -> Errno, - pub rpp_fn: unsafe extern "C" fn( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, - ) -> Errno, pub measure_fn: unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64) -> Errno, pub postselect_fn: unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, @@ -509,6 +402,16 @@ pub struct SimulatorOperationInterface<'a> { qubits: *const u64, n_qubits: u64, ) -> Errno, + pub gate_fn: + unsafe extern "C" fn(instance: SimulatorInstance, data: *const u8, len: usize) -> Errno, + pub negotiate_gateset_fn: unsafe extern "C" fn( + instance: SimulatorInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno, _marker: PhantomData<&'a ()>, } @@ -518,15 +421,13 @@ impl SimulatorOperationInterface<'_> { exit_fn: self.exit_fn, shot_start_fn: self.shot_start_fn, shot_end_fn: self.shot_end_fn, - rxy_fn: self.rxy_fn, - rz_fn: self.rz_fn, - rzz_fn: self.rzz_fn, - rpp_fn: self.rpp_fn, measure_fn: self.measure_fn, postselect_fn: self.postselect_fn, reset_fn: self.reset_fn, get_metric_fn: self.get_metric_fn, dump_state_fn: self.dump_state_fn, + gate_fn: self.gate_fn, + negotiate_gateset_fn: self.negotiate_gateset_fn, _marker: PhantomData, } } diff --git a/selene-core/rust/simulator/interface.rs b/selene-core/rust/simulator/interface.rs index 7c1a9ab0..992c233d 100644 --- a/selene-core/rust/simulator/interface.rs +++ b/selene-core/rust/simulator/interface.rs @@ -2,6 +2,7 @@ use anyhow::{Result, bail}; use std::sync::Arc; use crate::error_model::BatchResult; +use crate::gatewire::{DynamicGateSet, builtin}; use crate::runtime::BatchOperation; use crate::utils::MetricValue; @@ -18,6 +19,15 @@ pub trait SimulatorInterface { // shot. fn shot_end(&mut self) -> Result<()>; + // Validate the gates this simulator may receive. + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = builtin::all(); + if let Some(decl) = gateset.first_unsupported_by(&supported) { + bail!("The chosen simulator does not support gate {}", decl.name); + } + Ok(gateset.clone()) + } + // Perform a batch of runtime operations on the simulator and return the // results of any measurements in the batch. fn handle_operations(&mut self, operations: BatchOperation) -> Result; diff --git a/selene-core/rust/simulator/plugin.rs b/selene-core/rust/simulator/plugin.rs index fe04e0e4..595bf06b 100644 --- a/selene-core/rust/simulator/plugin.rs +++ b/selene-core/rust/simulator/plugin.rs @@ -1,9 +1,13 @@ use super::{SimulatorAPIVersion, SimulatorInterface, SimulatorInterfaceFactory}; use crate::error_model::BatchResult; +use crate::gatewire::DynamicGateSet; +use crate::plugin::{ + NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + require_callback, validate_descriptor_v1, +}; use crate::runtime::{BatchOperation, Operation}; use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; use anyhow::{Result, anyhow, bail}; -use libloading; use std::ffi::OsStr; use std::ffi::c_char; use std::sync::Arc; @@ -18,58 +22,65 @@ pub struct SimulatorPluginDescriptorV1 { pub struct_size: u64, pub api_version: u64, pub get_name_fn: Option *const c_char>, - pub init_fn: unsafe extern "C" fn( - handle: *mut SimulatorInstance, - n_qubits: u64, - argc: u32, - argv: *const *const c_char, - ) -> Errno, + pub init_fn: Option< + unsafe extern "C" fn( + handle: *mut SimulatorInstance, + n_qubits: u64, + argc: u32, + argv: *const *const c_char, + ) -> Errno, + >, pub exit_fn: Option Errno>, pub shot_start_fn: - unsafe extern "C" fn(handle: SimulatorInstance, shot_id: u64, seed: u64) -> Errno, - pub shot_end_fn: unsafe extern "C" fn(handle: SimulatorInstance) -> Errno, - pub rxy_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, theta: f64, phi: f64) -> Errno, + Option Errno>, + pub shot_end_fn: Option Errno>, + pub measure_fn: Option Errno>, + pub postselect_fn: Option< + unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, >, - pub rz_fn: - Option Errno>, - pub rzz_fn: Option< + pub reset_fn: Option Errno>, + pub get_metrics_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, + nth_metric: u8, + tag_out: *mut c_char, + datatype_out: *mut u8, + value_out: *mut u64, ) -> Errno, >, - pub rpp_fn: Option< + pub dump_state_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, + file: *const c_char, + qubits: *const u64, + n_qubits: u64, ) -> Errno, >, - pub measure_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, - pub postselect_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, + pub gate_fn: Option< + unsafe extern "C" fn(handle: SimulatorInstance, data: *const u8, len: usize) -> Errno, >, - pub reset_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, - pub get_metrics_fn: Option< + pub negotiate_gateset_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, - nth_metric: u8, - tag_out: *mut c_char, - datatype_out: *mut u8, - value_out: *mut u64, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> Errno, >, - pub dump_state_fn: unsafe extern "C" fn( - handle: SimulatorInstance, - file: *const c_char, - qubits: *const u64, - n_qubits: u64, - ) -> Errno, +} + +impl PluginDescriptorV1 for SimulatorPluginDescriptorV1 { + const KIND: &'static str = "Simulator"; + + fn struct_size(&self) -> u64 { + self.struct_size + } + + fn api_version(&self) -> u64 { + self.api_version + } } /// Provides a simulation engine backend that controls a plugin, in the form of a shared object. @@ -79,10 +90,10 @@ pub struct SimulatorPluginDescriptorV1 { /// plugin's implementation of the simulator interface, as well as metadata about the plugin and /// the ABI it implements. /// -/// Plugins are used allow implementations of simulation backends to be written and +/// Plugins allow implementations of simulation backends to be written and /// distributed independently of selene. Users should be cautious about the plugins they use, /// as it is possible that mistakes or malicious code could be present in the plugin, and, as -/// with all external libraries, due dilligence must be done to verify the source and the +/// with all external libraries, due diligence must be done to verify the source and the /// trustworthiness of the provider. pub struct SimulatorPluginInterface { _lib: libloading::Library, @@ -97,28 +108,8 @@ pub struct SimulatorPluginInterface { shot_start_fn: unsafe extern "C" fn(handle: SimulatorInstance, shot_id: u64, seed: u64) -> Errno, shot_end_fn: unsafe extern "C" fn(handle: SimulatorInstance) -> Errno, - rxy_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, theta: f64, phi: f64) -> Errno, - >, - rz_fn: - Option Errno>, - rzz_fn: Option< - unsafe extern "C" fn( - handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - ) -> Errno, - >, - rpp_fn: Option< - unsafe extern "C" fn( - handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, - ) -> Errno, - >, + negotiate_gateset_fn: Option>, + gate_fn: unsafe extern "C" fn(handle: SimulatorInstance, data: *const u8, len: usize) -> Errno, measure_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, postselect_fn: Option< unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, @@ -143,36 +134,18 @@ pub struct SimulatorPluginInterface { impl SimulatorPluginInterface { pub fn new_from_file(plugin_file: impl AsRef) -> Result> { - let lib = unsafe { libloading::Library::new(plugin_file.as_ref()) }.map_err(|e| { - anyhow!( - "Failed to load simulator plugin: {}. Error: {}", - plugin_file.as_ref().to_string_lossy(), - e - ) - })?; + let lib = load_library("simulator", &plugin_file)?; let descriptor = unsafe { - lib.get::(b"selene_simulator_plugin_descriptor_v1") - .ok() - .map(|d| *d) - .or_else(|| { - lib.get:: *const SimulatorPluginDescriptorV1>( - b"selene_simulator_get_plugin_descriptor_v1", - ) - .ok() - .and_then(|f| { - let ptr = f(); - if ptr.is_null() { None } else { Some(*ptr) } - }) - }) - }; - let descriptor = descriptor.ok_or_else(|| { - anyhow!( - "Simulator plugin '{}' does not expose either selene_simulator_plugin_descriptor_v1 or selene_simulator_get_plugin_descriptor_v1", - plugin_file.as_ref().to_string_lossy() + load_descriptor_v1::( + &lib, + &plugin_file, + b"selene_simulator_plugin_descriptor_v1", + b"selene_simulator_get_plugin_descriptor_v1", ) + }?; + validate_descriptor_v1(&descriptor, |api_version| { + SimulatorAPIVersion::from(api_version).validate() })?; - let version: SimulatorAPIVersion = descriptor.api_version.into(); - version.validate()?; let name = unsafe { descriptor .get_name_fn @@ -186,25 +159,28 @@ impl SimulatorPluginInterface { }, ) }; - if descriptor.struct_size < core::mem::size_of::() as u64 { - bail!("Simulator plugin descriptor is too small for v1 ABI"); - } Ok(Arc::new(Self { _lib: lib, name, - init_fn: descriptor.init_fn, + init_fn: require_callback("Simulator", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, - shot_start_fn: descriptor.shot_start_fn, - shot_end_fn: descriptor.shot_end_fn, - rxy_fn: descriptor.rxy_fn, - rz_fn: descriptor.rz_fn, - rzz_fn: descriptor.rzz_fn, - rpp_fn: descriptor.rpp_fn, - measure_fn: descriptor.measure_fn, + shot_start_fn: require_callback( + "Simulator", + "shot_start_fn", + descriptor.shot_start_fn, + )?, + shot_end_fn: require_callback("Simulator", "shot_end_fn", descriptor.shot_end_fn)?, + negotiate_gateset_fn: descriptor.negotiate_gateset_fn, + gate_fn: require_callback("Simulator", "gate_fn", descriptor.gate_fn)?, + measure_fn: require_callback("Simulator", "measure_fn", descriptor.measure_fn)?, postselect_fn: descriptor.postselect_fn, - reset_fn: descriptor.reset_fn, + reset_fn: require_callback("Simulator", "reset_fn", descriptor.reset_fn)?, get_metrics_fn: descriptor.get_metrics_fn, - dump_state_fn: descriptor.dump_state_fn, + dump_state_fn: require_callback( + "Simulator", + "dump_state_fn", + descriptor.dump_state_fn, + )?, })) } } @@ -215,53 +191,11 @@ pub struct SimulatorPlugin { } impl SimulatorPlugin { - fn rxy(&mut self, qubit: u64, theta: f64, phi: f64) -> Result<()> { - let Some(rxy_fn) = self.interface.rxy_fn else { - bail!( - "SimulatorPlugin({}): The chosen simulator does not support the RXY gate", - &self.interface.name - ); - }; - check_errno(unsafe { rxy_fn(self.instance, qubit, theta, phi) }, || { - anyhow!("SimulatorPlugin({}): rxy failed", &self.interface.name) - }) - } - - fn rz(&mut self, qubit: u64, theta: f64) -> Result<()> { - let Some(rz_fn) = self.interface.rz_fn else { - bail!( - "SimulatorPlugin({}): The chosen simulator does not support the RZ gate", - &self.interface.name - ); - }; - check_errno(unsafe { rz_fn(self.instance, qubit, theta) }, || { - anyhow!("SimulatorPlugin({}): rz failed", &self.interface.name) - }) - } - - fn rzz(&mut self, qubit1: u64, qubit2: u64, theta: f64) -> Result<()> { - let Some(rzz_fn) = self.interface.rzz_fn else { - bail!( - "SimulatorPlugin({}): The chosen simulator does not support the RZZ gate", - &self.interface.name - ); - }; + fn gate(&mut self, gate: &crate::gatewire::OwnedGateInstance) -> Result<()> { + let data = gate.serialize(); check_errno( - unsafe { rzz_fn(self.instance, qubit1, qubit2, theta) }, - || anyhow!("SimulatorPlugin({}): rzz failed", &self.interface.name), - ) - } - - fn rpp(&mut self, qubit1: u64, qubit2: u64, theta: f64, phi: f64) -> Result<()> { - let Some(rpp_fn) = self.interface.rpp_fn else { - bail!( - "SimulatorPlugin({}): The chosen simulator does not support the RPP gate", - &self.interface.name - ); - }; - check_errno( - unsafe { rpp_fn(self.instance, qubit1, qubit2, theta, phi) }, - || anyhow!("SimulatorPlugin({}): rpp failed", &self.interface.name), + unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, + || anyhow!("SimulatorPlugin({}): gate failed", &self.interface.name), ) } @@ -333,27 +267,21 @@ impl SimulatorInterface for SimulatorPlugin { || anyhow!("SimulatorPlugin({}): shot_end failed", &self.interface.name), ) } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let plugin = format!("SimulatorPlugin({})", &self.interface.name); + negotiate_gateset( + &plugin, + self.instance, + self.interface.negotiate_gateset_fn, + gateset, + ) + } fn handle_operations(&mut self, operations: BatchOperation) -> Result { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { gate } => self.gate(&gate)?, Operation::Measure { qubit_id, result_id, diff --git a/selene-core/rust/simulator/version.rs b/selene-core/rust/simulator/version.rs index 1d0100a4..d931951a 100644 --- a/selene-core/rust/simulator/version.rs +++ b/selene-core/rust/simulator/version.rs @@ -31,13 +31,17 @@ impl From for u64 { } } -pub const CURRENT_API_VERSION: SimulatorAPIVersion = SimulatorAPIVersion { +pub(crate) const CURRENT_API_VERSION: SimulatorAPIVersion = SimulatorAPIVersion { reserved: 0, major: 0, minor: 1, patch: 1, }; +pub const fn current_api_version() -> SimulatorAPIVersion { + CURRENT_API_VERSION +} + impl SimulatorAPIVersion { pub const fn as_u64(self) -> u64 { ((self.reserved as u64) << 24) diff --git a/selene-ext/compat/README.md b/selene-ext/compat/README.md new file mode 100644 index 00000000..f8cc77cf --- /dev/null +++ b/selene-ext/compat/README.md @@ -0,0 +1,75 @@ +# Selene 0.2 Compatibility Adapters + +These crates provide explicit 0.3 plugins that load 0.2-series plugin shared +objects and translate between the old typed gate ABI and the current generic +gatewire ABI. + +They are intentionally kept in `selene-ext` rather than `selene-core`. The 0.3 +core should only know the 0.3 plugin model; these adapters are transitional +plugins that can be removed once downstream plugins have migrated. + +## Adapter Crates + +- `v02-simulator` builds `libselene_v02_compat_simulator.so`. +- `v02-runtime` builds `libselene_v02_compat_runtime.so`. +- `v02-error-model` builds `libselene_v02_compat_error_model.so`. +- `v02-common` contains the shared ABI definitions and gate translation helpers. + +Each adapter accepts: + +```text +--old-plugin=/path/to/libold_plugin.so +--old-arg=... +``` + +Use one `--old-arg` per argument that should be forwarded to the 0.2 plugin. +For example, an old simulator that used `--shots=10 --mode=test` would be +wrapped with: + +```text +--old-plugin=/path/to/libold_simulator.so +--old-arg=--shots=10 +--old-arg=--mode=test +``` + +## Gateset Contract + +Selene 0.2 only understood the historical `rz/rxy/rzz` gateset. The adapters +therefore negotiate the equivalent 0.3 builtin gateset: + +- `RZ` +- `PhasedX` +- `ZZPhase` + +If a QIS interface or upstream plugin negotiates any other gate, such as +`PhasedXX` or a custom gate, the adapter rejects the handshake. + +## Error Models + +Selene 0.2 error models owned their simulator. Selene 0.3 error models receive +a simulator handle during `handle_operations` instead. + +The `v02-error-model` adapter bridges this by exporting a tiny 0.2 simulator ABI +from the same shared object. During legacy error-model initialization, the +adapter gives the old error model that bridge as its simulator plugin. During +`handle_operations`, bridge calls are forwarded to the real 0.3 simulator handle. + +The bridge intentionally no-ops shot start/end because the real 0.3 simulator is +already started and ended by Selene itself. + +If automatic discovery of the bridge library path is unavailable on a platform, +pass it explicitly: + +```text +--bridge-simulator=/path/to/libselene_v02_compat_error_model.so +``` + +## Build + +Build the adapters with the devenv toolchain: + +```bash +PATH="$PWD/.devenv/profile/bin:$PATH" cargo build --manifest-path selene-ext/compat/v02-simulator/Cargo.toml +PATH="$PWD/.devenv/profile/bin:$PATH" cargo build --manifest-path selene-ext/compat/v02-runtime/Cargo.toml +PATH="$PWD/.devenv/profile/bin:$PATH" cargo build --manifest-path selene-ext/compat/v02-error-model/Cargo.toml +``` diff --git a/selene-ext/compat/v02-common/Cargo.lock b/selene-ext/compat/v02-common/Cargo.lock new file mode 100644 index 00000000..e85f4e1f --- /dev/null +++ b/selene-ext/compat/v02-common/Cargo.lock @@ -0,0 +1,290 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "selene-core" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "delegate", + "derive_more", + "indexmap", + "libloading", + "smallvec", + "static_assertions", + "thiserror", +] + +[[package]] +name = "selene-v02-compat-common" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "libloading", + "selene-core", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/selene-ext/compat/v02-common/Cargo.toml b/selene-ext/compat/v02-common/Cargo.toml new file mode 100644 index 00000000..0ddd5852 --- /dev/null +++ b/selene-ext/compat/v02-common/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "selene-v02-compat-common" +version = "0.3.0-alpha.1" +edition = "2024" + +[workspace] + +[lib] +name = "selene_v02_compat_common" +path = "src/lib.rs" + +[dependencies] +anyhow = "1.0" +libloading = "0.8" +selene-core = { path = "../../../selene-core" } + +[lints.clippy] +undocumented_unsafe_blocks = "allow" +missing_safety_doc = "allow" diff --git a/selene-ext/compat/v02-common/src/lib.rs b/selene-ext/compat/v02-common/src/lib.rs new file mode 100644 index 00000000..4817f685 --- /dev/null +++ b/selene-ext/compat/v02-common/src/lib.rs @@ -0,0 +1,218 @@ +use std::ffi::{OsStr, c_void}; + +use anyhow::{Result, anyhow, bail}; +use libloading::Library; +use selene_core::{ + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, + runtime::{BuiltinGate, Operation}, +}; + +pub type Errno = i32; +pub type Instance = *mut c_void; + +pub const V02_SIMULATOR_API_VERSION: u64 = 0x0000_0100; +pub const V02_RUNTIME_API_VERSION: u64 = 0x0000_0201; +pub const V02_ERROR_MODEL_API_VERSION: u64 = 0x0000_0200; + +pub fn legacy_gateset() -> DynamicGateSet { + DynamicGateSet::from_declarations([ + builtin::RZ::declaration(), + builtin::PhasedX::declaration(), + builtin::ZZPhase::declaration(), + ]) + .expect("legacy builtin declarations are unique") +} + +pub fn negotiate_legacy_gateset(kind: &str, gateset: &DynamicGateSet) -> Result { + let legacy = legacy_gateset(); + if let Some(decl) = gateset.first_unsupported_by(&legacy) { + bail!( + "{kind} 0.2 compatibility adapter only supports gate {}", + decl.name + ); + } + Ok(legacy) +} + +#[derive(Clone, Copy, Debug)] +pub enum LegacyGate { + Rz { + qubit: u64, + theta: f64, + }, + Rxy { + qubit: u64, + theta: f64, + phi: f64, + }, + Rzz { + qubit0: u64, + qubit1: u64, + theta: f64, + }, +} + +impl LegacyGate { + pub fn from_gate_instance(gate: &OwnedGateInstance) -> Result { + let op = Operation::from_gate_instance(gate.clone())?; + match op.as_builtin_gate()? { + Some(BuiltinGate::RZ { qubit_id, theta }) => Ok(Self::Rz { + qubit: qubit_id, + theta, + }), + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => Ok(Self::Rxy { + qubit: qubit_id, + theta, + phi, + }), + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => Ok(Self::Rzz { + qubit0: qubit_id_1, + qubit1: qubit_id_2, + theta, + }), + Some(BuiltinGate::PhasedXX { .. }) | None => { + bail!("0.2 compatibility adapter cannot translate this gate") + } + } + } +} + +pub fn load_library(kind: &str, path: &OsStr) -> Result { + unsafe { Library::new(path) }.map_err(|error| { + anyhow!( + "failed to load {kind} 0.2 plugin '{}': {error}", + path.to_string_lossy() + ) + }) +} + +pub unsafe fn required_symbol(lib: &Library, symbol: &'static [u8]) -> Result { + Ok(*unsafe { lib.get::(symbol) }.map_err(|error| { + anyhow!( + "0.2 compatibility adapter could not load symbol {}: {error}", + String::from_utf8_lossy(symbol) + ) + })?) +} + +pub unsafe fn optional_symbol(lib: &Library, symbol: &'static [u8]) -> Option { + unsafe { lib.get::(symbol) }.ok().map(|symbol| *symbol) +} + +pub unsafe fn validate_api_version( + lib: &Library, + symbol: &'static [u8], + expected_major_minor: u64, + kind: &str, +) -> Result<()> { + let get_version: unsafe extern "C" fn() -> u64 = unsafe { required_symbol(lib, symbol) }?; + let version = unsafe { get_version() }; + if (version & 0x00ff_ff00) != (expected_major_minor & 0x00ff_ff00) { + bail!( + "{kind} 0.2 compatibility adapter expected API version major/minor {:#08x}, got {:#08x}", + expected_major_minor, + version + ); + } + Ok(()) +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LegacyRuntimeGetOperationInterface { + pub rzz_fn: unsafe extern "C" fn(Instance, u64, u64, f64), + pub rxy_fn: unsafe extern "C" fn(Instance, u64, f64, f64), + pub rz_fn: unsafe extern "C" fn(Instance, u64, f64), + pub measure_fn: unsafe extern "C" fn(Instance, u64, u64), + pub measure_leaked_fn: unsafe extern "C" fn(Instance, u64, u64), + pub reset_fn: unsafe extern "C" fn(Instance, u64), + pub custom_fn: unsafe extern "C" fn(Instance, usize, *const c_void, usize), + pub set_batch_time_fn: unsafe extern "C" fn(Instance, u64, u64), +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LegacyRuntimeExtractOperationInterface { + pub extract_fn: unsafe extern "C" fn(Instance, Instance, LegacyRuntimeGetOperationInterface), +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct LegacyErrorModelSetResultInterface { + pub set_bool_result_fn: unsafe extern "C" fn(Instance, u64, bool), + pub set_u64_result_fn: unsafe extern "C" fn(Instance, u64, u64), +} + +#[cfg(test)] +mod tests { + use super::*; + use selene_core::gatewire::{Angle, GateSpec, Qubit}; + + #[test] + fn legacy_gateset_accepts_only_0_2_gate_family() { + let legacy = legacy_gateset(); + assert!(negotiate_legacy_gateset("test", &legacy).is_ok()); + + let with_phased_xx = DynamicGateSet::from_declarations([ + builtin::RZ::declaration(), + builtin::PhasedXX::declaration(), + ]) + .unwrap(); + let error = negotiate_legacy_gateset("test", &with_phased_xx).unwrap_err(); + assert!(error.to_string().contains("only supports gate PhasedXX")); + } + + #[test] + fn translates_current_builtin_gates_to_legacy_calls() { + let rz = builtin::RZ { + q0: Qubit(1), + theta: Angle(0.5), + } + .to_instance(); + assert!(matches!( + LegacyGate::from_gate_instance(&rz).unwrap(), + LegacyGate::Rz { + qubit: 1, + theta: 0.5 + } + )); + + let phased_x = builtin::PhasedX { + q0: Qubit(2), + theta: Angle(0.25), + phi: Angle(0.75), + } + .to_instance(); + assert!(matches!( + LegacyGate::from_gate_instance(&phased_x).unwrap(), + LegacyGate::Rxy { + qubit: 2, + theta: 0.25, + phi: 0.75 + } + )); + + let zz = builtin::ZZPhase { + q0: Qubit(3), + q1: Qubit(4), + theta: Angle(0.125), + } + .to_instance(); + assert!(matches!( + LegacyGate::from_gate_instance(&zz).unwrap(), + LegacyGate::Rzz { + qubit0: 3, + qubit1: 4, + theta: 0.125 + } + )); + } +} diff --git a/selene-ext/compat/v02-error-model/Cargo.lock b/selene-ext/compat/v02-error-model/Cargo.lock new file mode 100644 index 00000000..2fd2e25b --- /dev/null +++ b/selene-ext/compat/v02-error-model/Cargo.lock @@ -0,0 +1,437 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "selene-core" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "delegate", + "derive_more", + "indexmap", + "libloading", + "smallvec", + "static_assertions", + "thiserror", +] + +[[package]] +name = "selene-v02-compat-common" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "libloading", + "selene-core", +] + +[[package]] +name = "selene-v02-compat-error-model" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "clap", + "libc", + "libloading", + "selene-core", + "selene-v02-compat-common", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/selene-ext/compat/v02-error-model/Cargo.toml b/selene-ext/compat/v02-error-model/Cargo.toml new file mode 100644 index 00000000..a3df6fd0 --- /dev/null +++ b/selene-ext/compat/v02-error-model/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "selene-v02-compat-error-model" +version = "0.3.0-alpha.1" +edition = "2024" + +[workspace] + +[lib] +name = "selene_v02_compat_error_model" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } +libc = "0.2" +libloading = "0.8" +selene-core = { path = "../../../selene-core" } +selene-v02-compat-common = { path = "../v02-common" } + +[lints.clippy] +undocumented_unsafe_blocks = "allow" +missing_safety_doc = "allow" diff --git a/selene-ext/compat/v02-error-model/src/lib.rs b/selene-ext/compat/v02-error-model/src/lib.rs new file mode 100644 index 00000000..c5238f87 --- /dev/null +++ b/selene-ext/compat/v02-error-model/src/lib.rs @@ -0,0 +1,581 @@ +use std::{ + cell::RefCell, + ffi::{CStr, CString, c_char, c_void}, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{Result, anyhow, bail}; +use clap::Parser; +use selene_core::{ + error_model::{BatchResult, ErrorModelInterface, interface::ErrorModelInterfaceFactory}, + export_error_model_plugin, + gatewire::DynamicGateSet, + runtime::{BatchOperation, Operation}, + simulator::SimulatorInterface, + utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}, +}; +use selene_v02_compat_common::{ + Errno, Instance, LegacyErrorModelSetResultInterface, LegacyGate, + LegacyRuntimeExtractOperationInterface, LegacyRuntimeGetOperationInterface, + V02_ERROR_MODEL_API_VERSION, V02_SIMULATOR_API_VERSION, load_library, negotiate_legacy_gateset, + optional_symbol, required_symbol, validate_api_version, +}; + +type InitFn = unsafe extern "C" fn( + *mut Instance, + u64, + u32, + *const *const c_char, + *const c_char, + u32, + *const *const c_char, +) -> Errno; +type ExitFn = unsafe extern "C" fn(Instance) -> Errno; +type ShotStartFn = unsafe extern "C" fn(Instance, u64, u64, u64) -> Errno; +type ShotEndFn = unsafe extern "C" fn(Instance) -> Errno; +type HandleOperationsFn = unsafe extern "C" fn( + Instance, + Instance, + *const LegacyRuntimeExtractOperationInterface, + Instance, + *const LegacyErrorModelSetResultInterface, +) -> Errno; +type MetricFn = unsafe extern "C" fn(Instance, u8, *mut c_char, *mut u8, *mut u64) -> Errno; + +type SimulatorPtr = *mut (dyn SimulatorInterface + 'static); + +thread_local! { + static CURRENT_SIMULATOR: RefCell> = const { RefCell::new(None) }; +} + +#[derive(Parser, Debug)] +struct Params { + #[arg(long)] + old_plugin: PathBuf, + + #[arg(long = "old-arg")] + old_args: Vec, + + #[arg(long)] + bridge_simulator: Option, +} + +struct LegacyErrorModelLibrary { + _lib: libloading::Library, + init: InitFn, + exit: Option, + shot_start: ShotStartFn, + shot_end: ShotEndFn, + handle_operations: HandleOperationsFn, + metric: Option, +} + +impl LegacyErrorModelLibrary { + fn load(path: &Path) -> Result> { + let lib = load_library("error model", path.as_os_str())?; + unsafe { + validate_api_version( + &lib, + b"selene_error_model_get_api_version", + V02_ERROR_MODEL_API_VERSION, + "error model", + )?; + Ok(Arc::new(Self { + init: required_symbol(&lib, b"selene_error_model_init")?, + exit: optional_symbol(&lib, b"selene_error_model_exit"), + shot_start: required_symbol(&lib, b"selene_error_model_shot_start")?, + shot_end: required_symbol(&lib, b"selene_error_model_shot_end")?, + handle_operations: required_symbol(&lib, b"selene_error_model_handle_operations")?, + metric: optional_symbol(&lib, b"selene_error_model_get_metrics"), + _lib: lib, + })) + } + } + + fn init_instance( + self: Arc, + n_qubits: u64, + error_model_args: &[String], + bridge_simulator: &Path, + ) -> Result { + let mut instance = std::ptr::null_mut(); + let bridge_simulator = CString::new(bridge_simulator.as_os_str().as_encoded_bytes())?; + with_strings_to_cargs(error_model_args, |argc, argv| { + check_errno( + unsafe { + (self.init)( + &mut instance, + n_qubits, + argc, + argv, + bridge_simulator.as_ptr(), + 0, + std::ptr::null(), + ) + }, + || anyhow!("v02 error-model adapter: legacy init failed"), + ) + })?; + Ok(LegacyErrorModel { + library: self, + instance, + }) + } +} + +struct LegacyBatchExtractor(BatchOperation); + +impl LegacyBatchExtractor { + unsafe extern "C" fn extract( + batch_instance: Instance, + output_instance: Instance, + output_interface: LegacyRuntimeGetOperationInterface, + ) { + let batch = unsafe { &*(batch_instance as *const BatchOperation) }; + if let Some(timing) = batch.runtime_source() { + unsafe { + (output_interface.set_batch_time_fn)( + output_instance, + timing.start().into(), + timing.duration().into(), + ); + } + } + for operation in batch.iter_ops() { + match operation { + Operation::Gate { gate } => match LegacyGate::from_gate_instance(gate) { + Ok(LegacyGate::Rz { qubit, theta }) => unsafe { + (output_interface.rz_fn)(output_instance, qubit, theta) + }, + Ok(LegacyGate::Rxy { qubit, theta, phi }) => unsafe { + (output_interface.rxy_fn)(output_instance, qubit, theta, phi) + }, + Ok(LegacyGate::Rzz { + qubit0, + qubit1, + theta, + }) => unsafe { + (output_interface.rzz_fn)(output_instance, qubit0, qubit1, theta) + }, + Err(error) => eprintln!( + "v02 error-model adapter: failed to translate gate for legacy batch: {error}" + ), + }, + Operation::Measure { + qubit_id, + result_id, + } => unsafe { + (output_interface.measure_fn)(output_instance, *qubit_id, *result_id) + }, + Operation::MeasureLeaked { + qubit_id, + result_id, + } => unsafe { + (output_interface.measure_leaked_fn)(output_instance, *qubit_id, *result_id) + }, + Operation::Reset { qubit_id } => unsafe { + (output_interface.reset_fn)(output_instance, *qubit_id) + }, + Operation::Custom { custom_tag, data } => unsafe { + (output_interface.custom_fn)( + output_instance, + *custom_tag, + data.as_ptr() as *const c_void, + data.len(), + ) + }, + _ => { + eprintln!("v02 error-model adapter: unsupported operation kind in legacy batch") + } + } + } + } + + fn interface(&mut self) -> (Instance, LegacyRuntimeExtractOperationInterface) { + ( + &mut self.0 as *mut BatchOperation as Instance, + LegacyRuntimeExtractOperationInterface { + extract_fn: Self::extract, + }, + ) + } +} + +#[derive(Default)] +struct LegacyResultBuilder(BatchResult); + +impl LegacyResultBuilder { + unsafe extern "C" fn set_bool_result(instance: Instance, result_id: u64, value: bool) { + let result = unsafe { &mut *(instance as *mut BatchResult) }; + result.set_bool_result(result_id, value); + } + + unsafe extern "C" fn set_u64_result(instance: Instance, result_id: u64, value: u64) { + let result = unsafe { &mut *(instance as *mut BatchResult) }; + result.set_u64_result(result_id, value); + } + + fn interface(&mut self) -> (Instance, LegacyErrorModelSetResultInterface) { + ( + &mut self.0 as *mut BatchResult as Instance, + LegacyErrorModelSetResultInterface { + set_bool_result_fn: Self::set_bool_result, + set_u64_result_fn: Self::set_u64_result, + }, + ) + } + + fn finish(self) -> BatchResult { + self.0 + } +} + +struct SimulatorScope; + +impl SimulatorScope { + fn enter(simulator: &mut dyn SimulatorInterface) -> Self { + let ptr: *mut dyn SimulatorInterface = simulator; + let ptr: SimulatorPtr = unsafe { std::mem::transmute(ptr) }; + CURRENT_SIMULATOR.with(|slot| { + *slot.borrow_mut() = Some(ptr); + }); + Self + } +} + +impl Drop for SimulatorScope { + fn drop(&mut self) { + CURRENT_SIMULATOR.with(|slot| { + *slot.borrow_mut() = None; + }); + } +} + +struct LegacyErrorModel { + library: Arc, + instance: Instance, +} + +impl ErrorModelInterface for LegacyErrorModel { + fn exit(&mut self) -> Result<()> { + if let Some(exit) = self.library.exit { + check_errno(unsafe { exit(self.instance) }, || { + anyhow!("v02 error-model adapter: legacy exit failed") + })?; + } + Ok(()) + } + + fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { + check_errno( + unsafe { (self.library.shot_start)(self.instance, shot_id, seed, 0) }, + || anyhow!("v02 error-model adapter: legacy shot_start failed"), + ) + } + + fn shot_end(&mut self) -> Result<()> { + check_errno(unsafe { (self.library.shot_end)(self.instance) }, || { + anyhow!("v02 error-model adapter: legacy shot_end failed") + }) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_legacy_gateset("Error model", gateset) + } + + fn handle_operations( + &mut self, + operations: BatchOperation, + simulator: &mut dyn SimulatorInterface, + ) -> Result { + let _scope = SimulatorScope::enter(simulator); + let mut extractor = LegacyBatchExtractor(operations); + let (batch_instance, batch_interface) = extractor.interface(); + let mut result_builder = LegacyResultBuilder::default(); + let (result_instance, result_interface) = result_builder.interface(); + check_errno( + unsafe { + (self.library.handle_operations)( + self.instance, + batch_instance, + &batch_interface, + result_instance, + &result_interface, + ) + }, + || anyhow!("v02 error-model adapter: legacy handle_operations failed"), + )?; + Ok(result_builder.finish()) + } + + fn get_metric(&mut self, nth_metric: u8) -> Result> { + let Some(metric) = self.library.metric else { + return Ok(None); + }; + read_raw_metric(|tag, datatype, value| unsafe { + metric(self.instance, nth_metric, tag, datatype, value) + }) + } +} + +#[derive(Default)] +struct LegacyErrorModelFactory; + +impl ErrorModelInterfaceFactory for LegacyErrorModelFactory { + type Interface = LegacyErrorModel; + + fn init( + self: Arc, + n_qubits: u64, + args: &[impl AsRef], + ) -> Result> { + let params = Params::try_parse_from(args.iter().map(|arg| arg.as_ref()))?; + let bridge_simulator = match params.bridge_simulator { + Some(path) => path, + None => this_library_path()?, + }; + let library = LegacyErrorModelLibrary::load(¶ms.old_plugin)?; + Ok(Box::new(library.init_instance( + n_qubits, + ¶ms.old_args, + &bridge_simulator, + )?)) + } +} + +fn with_current_simulator( + go: impl FnOnce(&mut dyn SimulatorInterface) -> Result, +) -> Result { + CURRENT_SIMULATOR.with(|slot| { + let Some(ptr) = *slot.borrow() else { + bail!("v02 error-model adapter simulator bridge used outside handle_operations"); + }; + let simulator = unsafe { &mut *ptr }; + go(simulator) + }) +} + +fn forward_gate(gate: LegacyGate) -> Result<()> { + let operation = match gate { + LegacyGate::Rz { qubit, theta } => Operation::rz(qubit, theta)?, + LegacyGate::Rxy { qubit, theta, phi } => Operation::phased_x(qubit, theta, phi)?, + LegacyGate::Rzz { + qubit0, + qubit1, + theta, + } => Operation::zz_phase(qubit0, qubit1, theta)?, + }; + with_current_simulator(|simulator| { + let results = simulator.handle_operations(BatchOperation::error_model(vec![operation]))?; + if results.bool_results.is_empty() && results.u64_results.is_empty() { + Ok(()) + } else { + bail!("v02 simulator bridge: gate unexpectedly produced a result") + } + }) +} + +fn forward_reset(qubit: u64) -> Result<()> { + with_current_simulator(|simulator| { + let results = + simulator.handle_operations(BatchOperation::error_model(vec![Operation::Reset { + qubit_id: qubit, + }]))?; + if results.bool_results.is_empty() && results.u64_results.is_empty() { + Ok(()) + } else { + bail!("v02 simulator bridge: reset unexpectedly produced a result") + } + }) +} + +fn forward_measure(qubit: u64) -> Result { + with_current_simulator(|simulator| { + let results = + simulator.handle_operations(BatchOperation::error_model(vec![Operation::Measure { + qubit_id: qubit, + result_id: 0, + }]))?; + if results.bool_results.len() == 1 && results.u64_results.is_empty() { + Ok(results.bool_results[0].value) + } else { + bail!("v02 simulator bridge: measure expected exactly one bool result") + } + }) +} + +fn this_library_path() -> Result { + let mut info = std::mem::MaybeUninit::::zeroed(); + let rc = unsafe { libc::dladdr(selene_simulator_init as *const c_void, info.as_mut_ptr()) }; + if rc == 0 { + bail!("v02 error-model adapter could not locate its simulator bridge library path"); + } + let info = unsafe { info.assume_init() }; + if info.dli_fname.is_null() { + bail!("v02 error-model adapter simulator bridge path is null"); + } + let path = unsafe { CStr::from_ptr(info.dli_fname) }; + Ok(PathBuf::from(path.to_str()?)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_get_api_version() -> u64 { + V02_SIMULATOR_API_VERSION +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_init( + instance: *mut Instance, + _n_qubits: u64, + _argc: u32, + _argv: *const *const c_char, +) -> Errno { + if instance.is_null() { + return -1; + } + unsafe { + *instance = Box::into_raw(Box::new(())) as Instance; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_exit(instance: Instance) -> Errno { + if !instance.is_null() { + let _ = unsafe { Box::from_raw(instance as *mut ()) }; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_shot_start( + _instance: Instance, + _shot_id: u64, + _seed: u64, +) -> Errno { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_shot_end(_instance: Instance, _seed: u64) -> Errno { + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_rxy( + _instance: Instance, + qubit: u64, + theta: f64, + phi: f64, +) -> Errno { + match forward_gate(LegacyGate::Rxy { qubit, theta, phi }) { + Ok(()) => 0, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_rz( + _instance: Instance, + qubit: u64, + theta: f64, +) -> Errno { + match forward_gate(LegacyGate::Rz { qubit, theta }) { + Ok(()) => 0, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_rzz( + _instance: Instance, + qubit0: u64, + qubit1: u64, + theta: f64, +) -> Errno { + match forward_gate(LegacyGate::Rzz { + qubit0, + qubit1, + theta, + }) { + Ok(()) => 0, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_measure( + _instance: Instance, + qubit: u64, +) -> Errno { + match forward_measure(qubit) { + Ok(false) => 0, + Ok(true) => 1, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_postselect( + _instance: Instance, + qubit: u64, + target_value: bool, +) -> Errno { + match with_current_simulator(|simulator| simulator.postselect(qubit, target_value)) { + Ok(()) => 0, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_operation_reset( + _instance: Instance, + qubit: u64, +) -> Errno { + match forward_reset(qubit) { + Ok(()) => 0, + Err(error) => { + eprintln!("{error:#}"); + -1 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_get_metrics( + _instance: Instance, + _nth_metric: u8, + _tag_out: *mut c_char, + _datatype_out: *mut u8, + _value_out: *mut u64, +) -> Errno { + 1 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_simulator_dump_state( + _instance: Instance, + _file: *const c_char, + _qubits: *const u64, + _n_qubits: u64, +) -> Errno { + -1 +} + +export_error_model_plugin!(crate::LegacyErrorModelFactory); diff --git a/selene-ext/compat/v02-runtime/Cargo.lock b/selene-ext/compat/v02-runtime/Cargo.lock new file mode 100644 index 00000000..b77fa9d9 --- /dev/null +++ b/selene-ext/compat/v02-runtime/Cargo.lock @@ -0,0 +1,436 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "selene-core" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "delegate", + "derive_more", + "indexmap", + "libloading", + "smallvec", + "static_assertions", + "thiserror", +] + +[[package]] +name = "selene-v02-compat-common" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "libloading", + "selene-core", +] + +[[package]] +name = "selene-v02-compat-runtime" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "clap", + "libloading", + "selene-core", + "selene-v02-compat-common", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/selene-ext/compat/v02-runtime/Cargo.toml b/selene-ext/compat/v02-runtime/Cargo.toml new file mode 100644 index 00000000..c3e7378a --- /dev/null +++ b/selene-ext/compat/v02-runtime/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "selene-v02-compat-runtime" +version = "0.3.0-alpha.1" +edition = "2024" + +[workspace] + +[lib] +name = "selene_v02_compat_runtime" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } +libloading = "0.8" +selene-core = { path = "../../../selene-core" } +selene-v02-compat-common = { path = "../v02-common" } + +[lints.clippy] +undocumented_unsafe_blocks = "allow" +missing_safety_doc = "allow" diff --git a/selene-ext/compat/v02-runtime/src/lib.rs b/selene-ext/compat/v02-runtime/src/lib.rs new file mode 100644 index 00000000..f3b1c839 --- /dev/null +++ b/selene-ext/compat/v02-runtime/src/lib.rs @@ -0,0 +1,509 @@ +use std::{ + ffi::{c_char, c_void}, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{Result, anyhow}; +use clap::Parser; +use selene_core::{ + export_runtime_plugin, + gatewire::{DynamicGateSet, OwnedGateInstance}, + runtime::{ + BatchOperation, Operation, RuntimeBatchSource, RuntimeInterface, + interface::RuntimeInterfaceFactory, + }, + utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}, +}; +use selene_v02_compat_common::{ + Errno, Instance, LegacyGate, LegacyRuntimeGetOperationInterface, V02_RUNTIME_API_VERSION, + load_library, negotiate_legacy_gateset, optional_symbol, required_symbol, validate_api_version, +}; + +type InitFn = unsafe extern "C" fn(*mut Instance, u64, u64, u32, *const *const c_char) -> Errno; +type ExitFn = unsafe extern "C" fn(Instance) -> Errno; +type ShotStartFn = unsafe extern "C" fn(Instance, u64, u64) -> Errno; +type ShotEndFn = unsafe extern "C" fn(Instance, u64, u64) -> Errno; +type GetNextOperationsFn = + unsafe extern "C" fn(Instance, Instance, *const LegacyRuntimeGetOperationInterface) -> Errno; +type MetricFn = unsafe extern "C" fn(Instance, u8, *mut c_char, *mut u8, *mut u64) -> Errno; +type QallocFn = unsafe extern "C" fn(Instance, *mut u64) -> Errno; +type QfreeFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type LocalBarrierFn = unsafe extern "C" fn(Instance, *const u64, u64, u64) -> Errno; +type GlobalBarrierFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type RxyGateFn = unsafe extern "C" fn(Instance, u64, f64, f64) -> Errno; +type RzGateFn = unsafe extern "C" fn(Instance, u64, f64) -> Errno; +type RzzGateFn = unsafe extern "C" fn(Instance, u64, u64, f64) -> Errno; +type MeasureFn = unsafe extern "C" fn(Instance, u64, *mut u64) -> Errno; +type ResetFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type ForceResultFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type GetBoolResultFn = unsafe extern "C" fn(Instance, u64, *mut i8) -> Errno; +type GetU64ResultFn = unsafe extern "C" fn(Instance, u64, *mut u64) -> Errno; +type SetBoolResultFn = unsafe extern "C" fn(Instance, u64, bool) -> Errno; +type SetU64ResultFn = unsafe extern "C" fn(Instance, u64, u64) -> Errno; +type RefcountFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type CustomCallFn = unsafe extern "C" fn(Instance, u64, *const u8, usize, *mut u64) -> Errno; +type SimulateDelayFn = unsafe extern "C" fn(Instance, u64) -> Errno; + +#[derive(Parser, Debug)] +struct Params { + #[arg(long)] + old_plugin: PathBuf, + + #[arg(long = "old-arg")] + old_args: Vec, +} + +struct LegacyRuntimeLibrary { + _lib: libloading::Library, + init: InitFn, + exit: Option, + shot_start: ShotStartFn, + shot_end: ShotEndFn, + get_next_operations: GetNextOperationsFn, + metric: Option, + qalloc: QallocFn, + qfree: QfreeFn, + local_barrier: LocalBarrierFn, + global_barrier: GlobalBarrierFn, + rxy_gate: RxyGateFn, + rz_gate: RzGateFn, + rzz_gate: RzzGateFn, + measure: MeasureFn, + measure_leaked: MeasureFn, + reset: ResetFn, + force_result: ForceResultFn, + get_bool_result: GetBoolResultFn, + get_u64_result: GetU64ResultFn, + set_bool_result: SetBoolResultFn, + set_u64_result: SetU64ResultFn, + increment_future_refcount: RefcountFn, + decrement_future_refcount: RefcountFn, + custom_call: Option, + simulate_delay: Option, +} + +impl LegacyRuntimeLibrary { + fn load(path: &Path) -> Result> { + let lib = load_library("runtime", path.as_os_str())?; + unsafe { + validate_api_version( + &lib, + b"selene_runtime_get_api_version", + V02_RUNTIME_API_VERSION, + "runtime", + )?; + Ok(Arc::new(Self { + init: required_symbol(&lib, b"selene_runtime_init")?, + exit: optional_symbol(&lib, b"selene_runtime_exit"), + shot_start: required_symbol(&lib, b"selene_runtime_shot_start")?, + shot_end: required_symbol(&lib, b"selene_runtime_shot_end")?, + get_next_operations: required_symbol(&lib, b"selene_runtime_get_next_operations")?, + metric: optional_symbol(&lib, b"selene_runtime_get_metrics"), + qalloc: required_symbol(&lib, b"selene_runtime_qalloc")?, + qfree: required_symbol(&lib, b"selene_runtime_qfree")?, + local_barrier: required_symbol(&lib, b"selene_runtime_local_barrier")?, + global_barrier: required_symbol(&lib, b"selene_runtime_global_barrier")?, + rxy_gate: required_symbol(&lib, b"selene_runtime_rxy_gate")?, + rz_gate: required_symbol(&lib, b"selene_runtime_rz_gate")?, + rzz_gate: required_symbol(&lib, b"selene_runtime_rzz_gate")?, + measure: required_symbol(&lib, b"selene_runtime_measure")?, + measure_leaked: required_symbol(&lib, b"selene_runtime_measure_leaked")?, + reset: required_symbol(&lib, b"selene_runtime_reset")?, + force_result: required_symbol(&lib, b"selene_runtime_force_result")?, + get_bool_result: required_symbol(&lib, b"selene_runtime_get_bool_result")?, + get_u64_result: required_symbol(&lib, b"selene_runtime_get_u64_result")?, + set_bool_result: required_symbol(&lib, b"selene_runtime_set_bool_result")?, + set_u64_result: required_symbol(&lib, b"selene_runtime_set_u64_result")?, + increment_future_refcount: required_symbol( + &lib, + b"selene_runtime_increment_future_refcount", + )?, + decrement_future_refcount: required_symbol( + &lib, + b"selene_runtime_decrement_future_refcount", + )?, + custom_call: optional_symbol(&lib, b"selene_runtime_custom_call"), + simulate_delay: optional_symbol(&lib, b"selene_runtime_simulate_delay"), + _lib: lib, + })) + } + } + + fn init_instance( + self: Arc, + n_qubits: u64, + start: selene_core::time::Instant, + args: &[String], + ) -> Result { + let mut instance = std::ptr::null_mut(); + with_strings_to_cargs(args, |argc, argv| { + check_errno( + unsafe { (self.init)(&mut instance, n_qubits, start.into(), argc, argv) }, + || anyhow!("v02 runtime adapter: legacy init failed"), + ) + })?; + Ok(LegacyRuntime { + library: self, + instance, + }) + } +} + +struct LegacyBatchBuilder { + ops: Vec, + source: RuntimeBatchSource, +} + +impl Default for LegacyBatchBuilder { + fn default() -> Self { + Self { + ops: Vec::new(), + source: RuntimeBatchSource::new(0.into(), 0.into()), + } + } +} + +impl LegacyBatchBuilder { + fn with_instance(instance: Instance, go: impl FnOnce(&mut Self)) { + let builder = unsafe { &mut *(instance as *mut Self) }; + go(builder) + } + + unsafe extern "C" fn rzz(instance: Instance, qubit0: u64, qubit1: u64, theta: f64) { + Self::with_instance(instance, |builder| { + if let Ok(op) = Operation::zz_phase(qubit0, qubit1, theta) { + builder.ops.push(op); + } + }) + } + + unsafe extern "C" fn rxy(instance: Instance, qubit: u64, theta: f64, phi: f64) { + Self::with_instance(instance, |builder| { + if let Ok(op) = Operation::phased_x(qubit, theta, phi) { + builder.ops.push(op); + } + }) + } + + unsafe extern "C" fn rz(instance: Instance, qubit: u64, theta: f64) { + Self::with_instance(instance, |builder| { + if let Ok(op) = Operation::rz(qubit, theta) { + builder.ops.push(op); + } + }) + } + + unsafe extern "C" fn measure(instance: Instance, qubit_id: u64, result_id: u64) { + Self::with_instance(instance, |builder| { + builder.ops.push(Operation::Measure { + qubit_id, + result_id, + }); + }) + } + + unsafe extern "C" fn measure_leaked(instance: Instance, qubit_id: u64, result_id: u64) { + Self::with_instance(instance, |builder| { + builder.ops.push(Operation::MeasureLeaked { + qubit_id, + result_id, + }); + }) + } + + unsafe extern "C" fn reset(instance: Instance, qubit_id: u64) { + Self::with_instance(instance, |builder| { + builder.ops.push(Operation::Reset { qubit_id }); + }) + } + + unsafe extern "C" fn custom( + instance: Instance, + custom_tag: usize, + data: *const c_void, + len: usize, + ) { + let data = unsafe { std::slice::from_raw_parts(data as *const u8, len) } + .to_vec() + .into_boxed_slice(); + Self::with_instance(instance, |builder| { + builder.ops.push(Operation::Custom { custom_tag, data }); + }) + } + + unsafe extern "C" fn set_batch_time(instance: Instance, start: u64, duration: u64) { + Self::with_instance(instance, |builder| { + builder.source = RuntimeBatchSource::new(start.into(), duration.into()); + }) + } + + fn interface() -> LegacyRuntimeGetOperationInterface { + LegacyRuntimeGetOperationInterface { + rzz_fn: Self::rzz, + rxy_fn: Self::rxy, + rz_fn: Self::rz, + measure_fn: Self::measure, + measure_leaked_fn: Self::measure_leaked, + reset_fn: Self::reset, + custom_fn: Self::custom, + set_batch_time_fn: Self::set_batch_time, + } + } + + fn finish(self) -> BatchOperation { + BatchOperation::runtime_with_source(self.ops, self.source) + } +} + +struct LegacyRuntime { + library: Arc, + instance: Instance, +} + +impl RuntimeInterface for LegacyRuntime { + fn exit(&mut self) -> Result<()> { + if let Some(exit) = self.library.exit { + check_errno(unsafe { exit(self.instance) }, || { + anyhow!("v02 runtime adapter: legacy exit failed") + })?; + } + Ok(()) + } + + fn get_next_operations(&mut self) -> Result> { + let mut builder = LegacyBatchBuilder::default(); + let interface = LegacyBatchBuilder::interface(); + check_errno( + unsafe { + (self.library.get_next_operations)( + self.instance, + &mut builder as *mut _ as Instance, + &interface, + ) + }, + || anyhow!("v02 runtime adapter: legacy get_next_operations failed"), + )?; + if builder.ops.is_empty() { + Ok(None) + } else { + Ok(Some(builder.finish())) + } + } + + fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { + check_errno( + unsafe { (self.library.shot_start)(self.instance, shot_id, seed) }, + || anyhow!("v02 runtime adapter: legacy shot_start failed"), + ) + } + + fn shot_end(&mut self) -> Result<()> { + check_errno( + unsafe { (self.library.shot_end)(self.instance, 0, 0) }, + || anyhow!("v02 runtime adapter: legacy shot_end failed"), + ) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_legacy_gateset("Runtime", gateset) + } + + fn custom_call(&mut self, tag: u64, data: &[u8]) -> Result { + let Some(custom_call) = self.library.custom_call else { + return Err(anyhow!( + "v02 runtime adapter: legacy runtime has no custom_call" + )); + }; + let mut result = 0; + check_errno( + unsafe { custom_call(self.instance, tag, data.as_ptr(), data.len(), &mut result) }, + || anyhow!("v02 runtime adapter: legacy custom_call failed"), + )?; + Ok(result) + } + + fn simulate_delay(&mut self, delay_ns: u64) -> Result<()> { + let Some(simulate_delay) = self.library.simulate_delay else { + return Err(anyhow!( + "v02 runtime adapter: legacy runtime has no simulate_delay" + )); + }; + check_errno(unsafe { simulate_delay(self.instance, delay_ns) }, || { + anyhow!("v02 runtime adapter: legacy simulate_delay failed") + }) + } + + fn get_metric(&mut self, nth_metric: u8) -> Result> { + let Some(metric) = self.library.metric else { + return Ok(None); + }; + read_raw_metric(|tag, datatype, value| unsafe { + metric(self.instance, nth_metric, tag, datatype, value) + }) + } + + fn qalloc(&mut self) -> Result { + let mut result = 0; + check_errno( + unsafe { (self.library.qalloc)(self.instance, &mut result) }, + || anyhow!("v02 runtime adapter: legacy qalloc failed"), + )?; + Ok(result) + } + + fn qfree(&mut self, qubit_id: u64) -> Result<()> { + check_errno( + unsafe { (self.library.qfree)(self.instance, qubit_id) }, + || anyhow!("v02 runtime adapter: legacy qfree failed"), + ) + } + + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + match LegacyGate::from_gate_instance(gate)? { + LegacyGate::Rz { qubit, theta } => check_errno( + unsafe { (self.library.rz_gate)(self.instance, qubit, theta) }, + || anyhow!("v02 runtime adapter: legacy rz_gate failed"), + ), + LegacyGate::Rxy { qubit, theta, phi } => check_errno( + unsafe { (self.library.rxy_gate)(self.instance, qubit, theta, phi) }, + || anyhow!("v02 runtime adapter: legacy rxy_gate failed"), + ), + LegacyGate::Rzz { + qubit0, + qubit1, + theta, + } => check_errno( + unsafe { (self.library.rzz_gate)(self.instance, qubit0, qubit1, theta) }, + || anyhow!("v02 runtime adapter: legacy rzz_gate failed"), + ), + } + } + + fn measure(&mut self, qubit_id: u64) -> Result { + let mut result = 0; + check_errno( + unsafe { (self.library.measure)(self.instance, qubit_id, &mut result) }, + || anyhow!("v02 runtime adapter: legacy measure failed"), + )?; + Ok(result) + } + + fn measure_leaked(&mut self, qubit_id: u64) -> Result { + let mut result = 0; + check_errno( + unsafe { (self.library.measure_leaked)(self.instance, qubit_id, &mut result) }, + || anyhow!("v02 runtime adapter: legacy measure_leaked failed"), + )?; + Ok(result) + } + + fn reset(&mut self, qubit_id: u64) -> Result<()> { + check_errno( + unsafe { (self.library.reset)(self.instance, qubit_id) }, + || anyhow!("v02 runtime adapter: legacy reset failed"), + ) + } + + fn force_result(&mut self, result_id: u64) -> Result<()> { + check_errno( + unsafe { (self.library.force_result)(self.instance, result_id) }, + || anyhow!("v02 runtime adapter: legacy force_result failed"), + ) + } + + fn get_bool_result(&mut self, result_id: u64) -> Result> { + let mut result = -1; + check_errno( + unsafe { (self.library.get_bool_result)(self.instance, result_id, &mut result) }, + || anyhow!("v02 runtime adapter: legacy get_bool_result failed"), + )?; + Ok(match result { + 0 => Some(false), + 1 => Some(true), + _ => None, + }) + } + + fn get_u64_result(&mut self, result_id: u64) -> Result> { + let mut result = u64::MAX; + check_errno( + unsafe { (self.library.get_u64_result)(self.instance, result_id, &mut result) }, + || anyhow!("v02 runtime adapter: legacy get_u64_result failed"), + )?; + Ok((result != u64::MAX).then_some(result)) + } + + fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { + check_errno( + unsafe { (self.library.set_bool_result)(self.instance, result_id, result) }, + || anyhow!("v02 runtime adapter: legacy set_bool_result failed"), + ) + } + + fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { + check_errno( + unsafe { (self.library.set_u64_result)(self.instance, result_id, result) }, + || anyhow!("v02 runtime adapter: legacy set_u64_result failed"), + ) + } + + fn increment_future_refcount(&mut self, future: u64) -> Result<()> { + check_errno( + unsafe { (self.library.increment_future_refcount)(self.instance, future) }, + || anyhow!("v02 runtime adapter: legacy increment_future_refcount failed"), + ) + } + + fn decrement_future_refcount(&mut self, future: u64) -> Result<()> { + check_errno( + unsafe { (self.library.decrement_future_refcount)(self.instance, future) }, + || anyhow!("v02 runtime adapter: legacy decrement_future_refcount failed"), + ) + } + + fn local_barrier(&mut self, qubits: &[u64], sleep_ns: u64) -> Result<()> { + check_errno( + unsafe { + (self.library.local_barrier)( + self.instance, + qubits.as_ptr(), + qubits.len() as u64, + sleep_ns, + ) + }, + || anyhow!("v02 runtime adapter: legacy local_barrier failed"), + ) + } + + fn global_barrier(&mut self, sleep_ns: u64) -> Result<()> { + check_errno( + unsafe { (self.library.global_barrier)(self.instance, sleep_ns) }, + || anyhow!("v02 runtime adapter: legacy global_barrier failed"), + ) + } +} + +#[derive(Default)] +struct LegacyRuntimeFactory; + +impl RuntimeInterfaceFactory for LegacyRuntimeFactory { + type Interface = LegacyRuntime; + + fn init( + self: Arc, + n_qubits: u64, + start: selene_core::time::Instant, + args: &[impl AsRef], + ) -> Result> { + let params = Params::try_parse_from(args.iter().map(|arg| arg.as_ref()))?; + let library = LegacyRuntimeLibrary::load(¶ms.old_plugin)?; + Ok(Box::new(library.init_instance( + n_qubits, + start, + ¶ms.old_args, + )?)) + } +} + +export_runtime_plugin!(crate::LegacyRuntimeFactory); diff --git a/selene-ext/compat/v02-simulator/Cargo.lock b/selene-ext/compat/v02-simulator/Cargo.lock new file mode 100644 index 00000000..442b910e --- /dev/null +++ b/selene-ext/compat/v02-simulator/Cargo.lock @@ -0,0 +1,436 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "selene-core" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "bitflags", + "blake3", + "delegate", + "derive_more", + "indexmap", + "libloading", + "smallvec", + "static_assertions", + "thiserror", +] + +[[package]] +name = "selene-v02-compat-common" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "libloading", + "selene-core", +] + +[[package]] +name = "selene-v02-compat-simulator" +version = "0.3.0-alpha.1" +dependencies = [ + "anyhow", + "clap", + "libloading", + "selene-core", + "selene-v02-compat-common", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/selene-ext/compat/v02-simulator/Cargo.toml b/selene-ext/compat/v02-simulator/Cargo.toml new file mode 100644 index 00000000..5f5587e2 --- /dev/null +++ b/selene-ext/compat/v02-simulator/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "selene-v02-compat-simulator" +version = "0.3.0-alpha.1" +edition = "2024" + +[workspace] + +[lib] +name = "selene_v02_compat_simulator" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } +libloading = "0.8" +selene-core = { path = "../../../selene-core" } +selene-v02-compat-common = { path = "../v02-common" } + +[lints.clippy] +undocumented_unsafe_blocks = "allow" +missing_safety_doc = "allow" diff --git a/selene-ext/compat/v02-simulator/src/lib.rs b/selene-ext/compat/v02-simulator/src/lib.rs new file mode 100644 index 00000000..646454ed --- /dev/null +++ b/selene-ext/compat/v02-simulator/src/lib.rs @@ -0,0 +1,247 @@ +use std::{ + ffi::c_char, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{Result, anyhow, bail}; +use clap::Parser; +use selene_core::{ + error_model::BatchResult, + export_simulator_plugin, + gatewire::DynamicGateSet, + runtime::{BatchOperation, Operation}, + simulator::{SimulatorInterface, interface::SimulatorInterfaceFactory}, + utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}, +}; +use selene_v02_compat_common::{ + Errno, Instance, LegacyGate, V02_SIMULATOR_API_VERSION, load_library, negotiate_legacy_gateset, + optional_symbol, required_symbol, validate_api_version, +}; + +type InitFn = unsafe extern "C" fn(*mut Instance, u64, u32, *const *const c_char) -> Errno; +type ExitFn = unsafe extern "C" fn(Instance) -> Errno; +type ShotStartFn = unsafe extern "C" fn(Instance, u64, u64) -> Errno; +type ShotEndFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type RxyFn = unsafe extern "C" fn(Instance, u64, f64, f64) -> Errno; +type RzFn = unsafe extern "C" fn(Instance, u64, f64) -> Errno; +type RzzFn = unsafe extern "C" fn(Instance, u64, u64, f64) -> Errno; +type MeasureFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type PostselectFn = unsafe extern "C" fn(Instance, u64, bool) -> Errno; +type ResetFn = unsafe extern "C" fn(Instance, u64) -> Errno; +type MetricFn = unsafe extern "C" fn(Instance, u8, *mut c_char, *mut u8, *mut u64) -> Errno; +type DumpStateFn = unsafe extern "C" fn(Instance, *const c_char, *const u64, u64) -> Errno; + +#[derive(Parser, Debug)] +struct Params { + #[arg(long)] + old_plugin: PathBuf, + + #[arg(long = "old-arg")] + old_args: Vec, +} + +struct LegacySimulatorLibrary { + _lib: libloading::Library, + init: InitFn, + exit: Option, + shot_start: ShotStartFn, + shot_end: ShotEndFn, + rxy: RxyFn, + rz: RzFn, + rzz: RzzFn, + measure: MeasureFn, + postselect: Option, + reset: ResetFn, + metric: Option, + dump_state: Option, +} + +impl LegacySimulatorLibrary { + fn load(path: &Path) -> Result> { + let lib = load_library("simulator", path.as_os_str())?; + unsafe { + validate_api_version( + &lib, + b"selene_simulator_get_api_version", + V02_SIMULATOR_API_VERSION, + "simulator", + )?; + Ok(Arc::new(Self { + init: required_symbol(&lib, b"selene_simulator_init")?, + exit: optional_symbol(&lib, b"selene_simulator_exit"), + shot_start: required_symbol(&lib, b"selene_simulator_shot_start")?, + shot_end: required_symbol(&lib, b"selene_simulator_shot_end")?, + rxy: required_symbol(&lib, b"selene_simulator_operation_rxy")?, + rz: required_symbol(&lib, b"selene_simulator_operation_rz")?, + rzz: required_symbol(&lib, b"selene_simulator_operation_rzz")?, + measure: required_symbol(&lib, b"selene_simulator_operation_measure")?, + postselect: optional_symbol(&lib, b"selene_simulator_operation_postselect"), + reset: required_symbol(&lib, b"selene_simulator_operation_reset")?, + metric: optional_symbol(&lib, b"selene_simulator_get_metrics"), + dump_state: optional_symbol(&lib, b"selene_simulator_dump_state"), + _lib: lib, + })) + } + } + + fn init_instance(self: Arc, n_qubits: u64, args: &[String]) -> Result { + let mut instance = std::ptr::null_mut(); + with_strings_to_cargs(args, |argc, argv| { + check_errno( + unsafe { (self.init)(&mut instance, n_qubits, argc, argv) }, + || anyhow!("v02 simulator adapter: legacy init failed"), + ) + })?; + Ok(LegacySimulator { + library: self, + instance, + }) + } +} + +struct LegacySimulator { + library: Arc, + instance: Instance, +} + +impl LegacySimulator { + fn apply_gate(&mut self, gate: LegacyGate) -> Result<()> { + match gate { + LegacyGate::Rz { qubit, theta } => check_errno( + unsafe { (self.library.rz)(self.instance, qubit, theta) }, + || anyhow!("v02 simulator adapter: legacy rz failed"), + ), + LegacyGate::Rxy { qubit, theta, phi } => check_errno( + unsafe { (self.library.rxy)(self.instance, qubit, theta, phi) }, + || anyhow!("v02 simulator adapter: legacy rxy failed"), + ), + LegacyGate::Rzz { + qubit0, + qubit1, + theta, + } => check_errno( + unsafe { (self.library.rzz)(self.instance, qubit0, qubit1, theta) }, + || anyhow!("v02 simulator adapter: legacy rzz failed"), + ), + } + } + + fn measure(&mut self, qubit: u64) -> Result { + match unsafe { (self.library.measure)(self.instance, qubit) } { + 0 => Ok(false), + 1 => Ok(true), + errno => bail!("v02 simulator adapter: legacy measure failed with code {errno}"), + } + } +} + +impl SimulatorInterface for LegacySimulator { + fn exit(&mut self) -> Result<()> { + if let Some(exit) = self.library.exit { + check_errno(unsafe { exit(self.instance) }, || { + anyhow!("v02 simulator adapter: legacy exit failed") + })?; + } + Ok(()) + } + + fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { + check_errno( + unsafe { (self.library.shot_start)(self.instance, shot_id, seed) }, + || anyhow!("v02 simulator adapter: legacy shot_start failed"), + ) + } + + fn shot_end(&mut self) -> Result<()> { + check_errno(unsafe { (self.library.shot_end)(self.instance, 0) }, || { + anyhow!("v02 simulator adapter: legacy shot_end failed") + }) + } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_legacy_gateset("Simulator", gateset) + } + + fn handle_operations(&mut self, operations: BatchOperation) -> Result { + let mut results = BatchResult::default(); + for operation in operations { + match operation { + Operation::Gate { gate } => { + self.apply_gate(LegacyGate::from_gate_instance(&gate)?)? + } + Operation::Measure { + qubit_id, + result_id, + } => results.set_bool_result(result_id, self.measure(qubit_id)?), + Operation::MeasureLeaked { + qubit_id, + result_id, + } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), + Operation::Reset { qubit_id } => check_errno( + unsafe { (self.library.reset)(self.instance, qubit_id) }, + || anyhow!("v02 simulator adapter: legacy reset failed"), + )?, + Operation::Custom { .. } => {} + _ => bail!("v02 simulator adapter: unsupported operation kind"), + } + } + Ok(results) + } + + fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { + let Some(postselect) = self.library.postselect else { + bail!("v02 simulator adapter: legacy simulator does not support postselection"); + }; + check_errno( + unsafe { postselect(self.instance, qubit, target_value) }, + || anyhow!("v02 simulator adapter: legacy postselect failed"), + ) + } + + fn get_metric(&mut self, nth_metric: u8) -> Result> { + let Some(metric) = self.library.metric else { + return Ok(None); + }; + read_raw_metric(|tag, datatype, value| unsafe { + metric(self.instance, nth_metric, tag, datatype, value) + }) + } + + fn dump_state(&mut self, file: &Path, qubits: &[u64]) -> Result<()> { + let Some(dump_state) = self.library.dump_state else { + bail!("v02 simulator adapter: legacy simulator does not support state dumps"); + }; + let file = std::ffi::CString::new(file.as_os_str().as_encoded_bytes())?; + check_errno( + unsafe { + dump_state( + self.instance, + file.as_ptr(), + qubits.as_ptr(), + qubits.len() as u64, + ) + }, + || anyhow!("v02 simulator adapter: legacy dump_state failed"), + ) + } +} + +#[derive(Default)] +struct LegacySimulatorFactory; + +impl SimulatorInterfaceFactory for LegacySimulatorFactory { + type Interface = LegacySimulator; + + fn init( + self: Arc, + n_qubits: u64, + args: &[impl AsRef], + ) -> Result> { + let params = Params::try_parse_from(args.iter().map(|arg| arg.as_ref()))?; + let library = LegacySimulatorLibrary::load(¶ms.old_plugin)?; + Ok(Box::new(library.init_instance(n_qubits, ¶ms.old_args)?)) + } +} + +export_simulator_plugin!(crate::LegacySimulatorFactory); diff --git a/selene-ext/error-models/depolarizing/python/selene_depolarizing_error_model_plugin/plugin.py b/selene-ext/error-models/depolarizing/python/selene_depolarizing_error_model_plugin/plugin.py index 17a2b9f9..e0ea65b5 100644 --- a/selene-ext/error-models/depolarizing/python/selene_depolarizing_error_model_plugin/plugin.py +++ b/selene-ext/error-models/depolarizing/python/selene_depolarizing_error_model_plugin/plugin.py @@ -31,10 +31,10 @@ class DepolarizingPlugin(ErrorModel): def __post_init__(self): assert 0 <= self.p_1q <= 1, ( - f"error_probability for p_1q ({self.p_q1}) must be between 0 and 1 (both inclusive)" + f"error_probability for p_1q ({self.p_1q}) must be between 0 and 1 (both inclusive)" ) assert 0 <= self.p_2q <= 1, ( - f"error_probability for p_2q ({self.p_q2}) must be between 0 and 1 (both inclusive)" + f"error_probability for p_2q ({self.p_2q}) must be between 0 and 1 (both inclusive)" ) assert 0 <= self.p_meas <= 1, ( f"error_probability for p_meas ({self.p_meas}) must be between 0 and 1 (both inclusive)" diff --git a/selene-ext/error-models/depolarizing/rust/lib.rs b/selene-ext/error-models/depolarizing/rust/lib.rs index d04ac3c9..59002b16 100644 --- a/selene-ext/error-models/depolarizing/rust/lib.rs +++ b/selene-ext/error-models/depolarizing/rust/lib.rs @@ -5,6 +5,7 @@ use rand_pcg::Pcg64Mcg; use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; +use selene_core::gatewire::{DynamicGateSet, GateDecl, builtin}; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -89,20 +90,12 @@ impl DepolarizingErrorModel { fn error_operation(&self, qubit: u64, error: ErrorType) -> Option { match error { ErrorType::I => None, - ErrorType::X => Some(Operation::RXYGate { - qubit_id: qubit, - theta: std::f64::consts::PI, - phi: 0.0, - }), - ErrorType::Y => Some(Operation::RXYGate { - qubit_id: qubit, - theta: std::f64::consts::PI, - phi: std::f64::consts::PI / 2.0, - }), - ErrorType::Z => Some(Operation::RZGate { - qubit_id: qubit, - theta: std::f64::consts::PI, - }), + ErrorType::X => Some(Operation::phased_x(qubit, std::f64::consts::PI, 0.0).ok()?), + ErrorType::Y => Some( + Operation::phased_x(qubit, std::f64::consts::PI, std::f64::consts::PI / 2.0) + .ok()?, + ), + ErrorType::Z => Some(Operation::rz(qubit, std::f64::consts::PI).ok()?), } } fn maybe_apply_1q_error(&mut self, q0: u64) -> Result> { @@ -216,6 +209,24 @@ impl DepolarizingErrorModel { } Ok(None) } + + fn ensure_output_gate(declarations: &mut Vec, required: GateDecl) -> Result<()> { + match declarations + .iter_mut() + .find(|decl| decl.semantic_id == required.semantic_id) + { + Some(existing) if *existing == required => Ok(()), + Some(existing) => bail!( + "DepolarizingErrorModel requires builtin gate {}, but the incoming gateset uses the same semantic ID for incompatible gate {}", + required.name, + existing.name + ), + None => { + declarations.push(required); + Ok(()) + } + } + } } impl ErrorModelInterface for DepolarizingErrorModel { @@ -228,6 +239,13 @@ impl ErrorModelInterface for DepolarizingErrorModel { Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let mut declarations: Vec<_> = gateset.declarations().cloned().collect(); + Self::ensure_output_gate(&mut declarations, builtin::RZ::declaration())?; + Self::ensure_output_gate(&mut declarations, builtin::PhasedX::declaration())?; + DynamicGateSet::from_declarations(declarations).map_err(Into::into) + } + fn exit(&mut self) -> Result<()> { Ok(()) } @@ -241,51 +259,20 @@ impl ErrorModelInterface for DepolarizingErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => { - if let Some(error) = self.maybe_apply_1q_error(qubit_id)? { - pending.push(error); - } - pending.push(Operation::RXYGate { - qubit_id, - theta, - phi, - }); - } - Operation::RZGate { qubit_id, theta } => { - if let Some(error) = self.maybe_apply_1q_error(qubit_id)? { - pending.push(error); + Operation::Gate { gate } => { + let qubits: Vec = gate.qubit_operands().map(u64::from).collect(); + match qubits.as_slice() { + [q0] => { + if let Some(error) = self.maybe_apply_1q_error(*q0)? { + pending.push(error); + } + } + [q0, q1] => { + pending.extend(self.maybe_apply_2q_error(*q0, *q1)?); + } + _ => {} } - pending.push(Operation::RZGate { qubit_id, theta }); - } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => { - pending.extend(self.maybe_apply_2q_error(qubit_id_1, qubit_id_2)?); - pending.push(Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - }); - } - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => { - pending.extend(self.maybe_apply_2q_error(qubit_id_1, qubit_id_2)?); - pending.push(Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - }); + pending.push(Operation::from_gate_instance(gate)?); } Operation::Measure { qubit_id, @@ -474,4 +461,108 @@ impl ErrorModelInterfaceFactory for DepolarizingErrorModelFactory { } } +#[cfg(test)] +mod tests { + use super::*; + use selene_core::gatewire::{ + GateDecl, GateSemanticId, GateValue, OperandKind, OperandSpec, OwnedGateInstance, + }; + + struct RecordingSimulator { + operations: Vec, + } + + impl SimulatorInterface for RecordingSimulator { + fn exit(&mut self) -> Result<()> { + Ok(()) + } + + fn shot_start(&mut self, _shot_id: u64, _seed: u64) -> Result<()> { + Ok(()) + } + + fn shot_end(&mut self) -> Result<()> { + Ok(()) + } + + fn handle_operations(&mut self, operations: BatchOperation) -> Result { + self.operations.extend(operations); + Ok(BatchResult::default()) + } + + fn get_metric(&mut self, _nth_metric: u8) -> Result> { + Ok(None) + } + } + + fn custom_1q_decl() -> GateDecl { + GateDecl::new( + GateSemanticId::from_text("test.custom.OneQ.v1"), + "OneQ", + [OperandSpec::new("q0", OperandKind::Qubit)], + 1, + ) + } + + fn model_with_probabilities(p_1q: f64, p_2q: f64) -> DepolarizingErrorModel { + DepolarizingErrorModel { + n_qubits: 4, + rng: Pcg64Mcg::seed_from_u64(0), + error_params: Params { + p_1q, + p_2q, + p_meas: 0.0, + p_init: 0.0, + }, + stats: Stats::default(), + } + } + + #[test] + fn negotiation_accepts_custom_gates_and_adds_injection_gates() { + let custom = custom_1q_decl(); + let custom_id = custom.semantic_id; + let gateset = DynamicGateSet::from_declarations([custom]).unwrap(); + let mut model = model_with_probabilities(0.0, 0.0); + + let negotiated = model.negotiate_gateset(&gateset).unwrap(); + + assert!(negotiated.contains(custom_id)); + assert!(negotiated.contains(builtin::RZ::semantic_id())); + assert!(negotiated.contains(builtin::PhasedX::semantic_id())); + } + + #[test] + fn one_qubit_custom_gate_is_noised_by_arity_without_gate_names() { + let custom = custom_1q_decl(); + let gate = OwnedGateInstance::new(custom.semantic_id, [GateValue::Qubit(2)]); + let mut model = model_with_probabilities(1.0, 0.0); + let mut simulator = RecordingSimulator { + operations: Vec::new(), + }; + + model + .handle_operations( + BatchOperation::runtime( + vec![Operation::from_gate_instance(gate.clone()).unwrap()], + selene_core::time::Instant::from(0), + selene_core::time::Duration::from(1), + ), + &mut simulator, + ) + .unwrap(); + + assert_eq!(simulator.operations.len(), 2); + assert!(matches!( + simulator.operations[0], + Operation::Gate { ref gate } if gate.single_qubit_operand() == Some(2) + )); + assert_eq!( + simulator.operations[1], + Operation::from_gate_instance(gate).unwrap() + ); + assert_eq!(model.stats.gate_count_1q, 1); + } +} + export_error_model_plugin!(crate::DepolarizingErrorModelFactory); diff --git a/selene-ext/error-models/ideal/rust/lib.rs b/selene-ext/error-models/ideal/rust/lib.rs index a80d6202..17350fac 100644 --- a/selene-ext/error-models/ideal/rust/lib.rs +++ b/selene-ext/error-models/ideal/rust/lib.rs @@ -2,6 +2,7 @@ use anyhow::{Result, bail}; use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; +use selene_core::gatewire::{DynamicGateSet, builtin}; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -22,6 +23,14 @@ impl ErrorModelInterface for IdealErrorModel { Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = builtin::all(); + if let Some(decl) = gateset.first_unsupported_by(&supported) { + bail!("IdealErrorModel does not support gate {}", decl.name); + } + Ok(gateset.clone()) + } + fn handle_operations( &mut self, operations: BatchOperation, @@ -31,12 +40,9 @@ impl ErrorModelInterface for IdealErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::RXYGate { .. } - | Operation::RZZGate { .. } - | Operation::RZGate { .. } - | Operation::Measure { .. } - | Operation::RPPGate { .. } - | Operation::Reset { .. } => pending.push(op), + Operation::Gate { .. } | Operation::Measure { .. } | Operation::Reset { .. } => { + pending.push(op) + } Operation::MeasureLeaked { qubit_id, result_id, diff --git a/selene-ext/error-models/simple-leakage/rust/lib.rs b/selene-ext/error-models/simple-leakage/rust/lib.rs index 89301aa4..5bd7e0c0 100644 --- a/selene-ext/error-models/simple-leakage/rust/lib.rs +++ b/selene-ext/error-models/simple-leakage/rust/lib.rs @@ -5,7 +5,8 @@ use rand_pcg::Pcg64Mcg; use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::gatewire::{DynamicGateSet, builtin}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -100,6 +101,17 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = builtin::all(); + if let Some(decl) = gateset.first_unsupported_by(&supported) { + bail!( + "SimpleLeakageErrorModel does not support gate {}", + decl.name + ); + } + Ok(gateset.clone()) + } + fn exit(&mut self) -> Result<()> { Ok(()) } @@ -113,52 +125,42 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => { - self.maybe_leak(qubit_id)?; - pending.push(Operation::RXYGate { + Operation::Gate { .. } => match op.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { qubit_id, theta, phi, - }); - } - Operation::RZGate { qubit_id, theta } => { - self.maybe_leak(qubit_id)?; - pending.push(Operation::RZGate { qubit_id, theta }); - } - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => { - self.maybe_leak(qubit_id_1)?; - self.maybe_leak(qubit_id_2)?; - self.spread_leakage(qubit_id_1, qubit_id_2)?; - pending.push(Operation::RZZGate { + }) => { + self.maybe_leak(qubit_id)?; + pending.push(Operation::phased_x(qubit_id, theta, phi)?); + } + Some(BuiltinGate::RZ { qubit_id, theta }) => { + self.maybe_leak(qubit_id)?; + pending.push(Operation::rz(qubit_id, theta)?); + } + Some(BuiltinGate::ZZPhase { qubit_id_1, qubit_id_2, theta, - }); - } - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => { - self.maybe_leak(qubit_id_1)?; - self.maybe_leak(qubit_id_2)?; - self.spread_leakage(qubit_id_1, qubit_id_2)?; - pending.push(Operation::RPPGate { + }) => { + self.maybe_leak(qubit_id_1)?; + self.maybe_leak(qubit_id_2)?; + self.spread_leakage(qubit_id_1, qubit_id_2)?; + pending.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); + } + Some(BuiltinGate::PhasedXX { qubit_id_1, qubit_id_2, theta, phi, - }); - } + }) => { + self.maybe_leak(qubit_id_1)?; + self.maybe_leak(qubit_id_2)?; + self.spread_leakage(qubit_id_1, qubit_id_2)?; + pending.push(Operation::phased_xx(qubit_id_1, qubit_id_2, theta, phi)?); + } + None => bail!("SimpleLeakageErrorModel: Unsupported gate {:?}", op), + }, Operation::Measure { qubit_id, result_id, diff --git a/selene-ext/interfaces/helios_qis/c/src/helios_ops.c b/selene-ext/interfaces/helios_qis/c/src/helios_ops.c index 5312ccb9..a83028bd 100644 --- a/selene-ext/interfaces/helios_qis/c/src/helios_ops.c +++ b/selene-ext/interfaces/helios_qis/c/src/helios_ops.c @@ -4,8 +4,73 @@ #include // selene_instance #include // unwrap +#include + #include "logging.h" // DIAGNOSTIC +static GwSemanticId rz_id; +static GwSemanticId phased_x_id; +static GwSemanticId zz_phase_id; +static bool gate_ids_initialized = false; + +static void check_gw(GwStatus status) { + if (status != GW_STATUS_OK) { + ERROR("gatewire error: %s\n", gw_status_message(status)); + abort(); + } +} + +static void init_gate_ids(void) { + if (gate_ids_initialized) { + return; + } + rz_id = gw_builtin_rz_semantic_id(); + phased_x_id = gw_builtin_phased_x_semantic_id(); + zz_phase_id = gw_builtin_zz_phase_semantic_id(); + gate_ids_initialized = true; +} + +static GwGateValue qubit_value(uint64_t q) { + GwGateValue value = { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_QUBIT, + .data.qubit = (uint32_t)q, + .bytes_ptr = NULL, + .bytes_len = 0, + }; + return value; +} + +static GwGateValue angle_value(double angle) { + GwGateValue value = { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_F64, + .data.f64_value = angle, + .bytes_ptr = NULL, + .bytes_len = 0, + }; + return value; +} + +static void emit_gate(GwSemanticId semantic_id, GwGateValue const* values, size_t values_len) { + GwGateInstanceView gate = { + .abi_size = sizeof(GwGateInstanceView), + .semantic_id = semantic_id, + .values_ptr = values, + .values_len = values_len, + }; + size_t len = 0; + check_gw(gw_gate_serialized_len(&gate, &len)); + uint8_t* buffer = malloc(len); + if (buffer == NULL) { + ERROR("failed to allocate gate buffer\n"); + abort(); + } + size_t written = 0; + check_gw(gw_gate_serialize(&gate, buffer, len, &written)); + unwrap(selene_gate(selene_instance, buffer, written)); + free(buffer); +} uint64_t ___qalloc() { DIAGNOSTIC("___qalloc()\n"); @@ -20,17 +85,23 @@ void ___qfree(uint64_t q) { } void ___rxy(uint64_t q, double theta, double phi) { DIAGNOSTIC("___rxy(%" PRIu64 ", %f, %f)\n", q, theta, phi); - unwrap(selene_rxy(selene_instance, q, theta, phi)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q), angle_value(theta), angle_value(phi)}; + emit_gate(phased_x_id, values, 3); DIAGNOSTIC(" [done]\n"); } void ___rzz(uint64_t q1, uint64_t q2, double theta) { DIAGNOSTIC("___rzz(%" PRIu64 ", %" PRIu64 ", %f)\n", q1, q2, theta); - unwrap(selene_rzz(selene_instance, q1, q2, theta)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q1), qubit_value(q2), angle_value(theta)}; + emit_gate(zz_phase_id, values, 3); DIAGNOSTIC(" [done]\n"); } void ___rz(uint64_t q, double theta) { DIAGNOSTIC("___rz(%" PRIu64 ", %f)\n", q, theta); - unwrap(selene_rz(selene_instance, q, theta)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q), angle_value(theta)}; + emit_gate(rz_id, values, 2); DIAGNOSTIC(" [done]\n"); } void ___reset(uint64_t q) { diff --git a/selene-ext/interfaces/helios_qis/c/src/interface.c b/selene-ext/interfaces/helios_qis/c/src/interface.c index 634cd531..fc329297 100644 --- a/selene-ext/interfaces/helios_qis/c/src/interface.c +++ b/selene-ext/interfaces/helios_qis/c/src/interface.c @@ -25,6 +25,55 @@ #include "logging.h" +static void check_gw(GwStatus status) { + if (status != GW_STATUS_OK) { + ERROR("gatewire error: %s\n", gw_status_message(status)); + abort(); + } +} + +static int register_helios_gateset(void) { + GwGateSet* set = NULL; + check_gw(gw_gateset_new(&set)); + check_gw(gw_gateset_add_builtin_rz(set)); + check_gw(gw_gateset_add_builtin_phased_x(set)); + check_gw(gw_gateset_add_builtin_zz_phase(set)); + + size_t len = 0; + check_gw(gw_gateset_serialized_len(set, &len)); + uint8_t* buffer = malloc(len); + if (buffer == NULL) { + ERROR("failed to allocate gateset buffer\n"); + gw_gateset_free(set); + return 1; + } + size_t written = 0; + check_gw(gw_gateset_serialize(set, buffer, len, &written)); + gw_gateset_free(set); + + size_t accepted_len = 0; + struct selene_void_result_t result = selene_register_gateset(selene_instance, buffer, written, NULL, 0, &accepted_len); + if (result.error_code != 0) { + ERROR("Error registering Helios gateset: error code %" PRIu32 "\n", result.error_code); + free(buffer); + return result.error_code; + } + uint8_t* accepted = malloc(accepted_len); + if (accepted == NULL && accepted_len != 0) { + ERROR("failed to allocate accepted gateset buffer\n"); + free(buffer); + return 1; + } + result = selene_register_gateset(selene_instance, buffer, written, accepted, accepted_len, &accepted_len); + free(buffer); + free(accepted); + if (result.error_code != 0) { + ERROR("Error registering Helios gateset: error code %" PRIu32 "\n", result.error_code); + return result.error_code; + } + return 0; +} + int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { @@ -42,6 +91,10 @@ int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { ERROR("Error initializing selene: error code %" PRIu32 "\n", void_result.error_code); return void_result.error_code; } + int register_result = register_helios_gateset(); + if (register_result != 0) { + return register_result; + } struct selene_u64_result_t n_shots = selene_shot_count(selene_instance); if (n_shots.error_code != 0) { ERROR("Error fetching shot count from selene: error code %" PRIu32 "\n", void_result.error_code); diff --git a/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py b/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py index fbced4e2..7ff73b64 100644 --- a/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py +++ b/selene-ext/interfaces/helios_qis/python/selene_helios_qis_plugin/plugin.py @@ -4,7 +4,7 @@ The first is a dynamic library (interface) comprising the shims from QIS functionality (specific to Helios) to the Selene library functions, e.g.: ``` - ___rxy(qubit, theta, phi) -> selene_rxy(selene_instance, qubit, theta, phi) + ___rxy(qubit, theta, phi) -> selene_gate(selene_instance, PhasedX(...)) ``` it also contains a function to invoke a callback through a shot loop. It is linked to a base QIS interface that exposes common functions, e.g. diff --git a/selene-ext/interfaces/sol_qis/c/src/interface.c b/selene-ext/interfaces/sol_qis/c/src/interface.c index 3280d169..1dbd132d 100644 --- a/selene-ext/interfaces/sol_qis/c/src/interface.c +++ b/selene-ext/interfaces/sol_qis/c/src/interface.c @@ -25,6 +25,55 @@ #include "logging.h" +static void check_gw(GwStatus status) { + if (status != GW_STATUS_OK) { + ERROR("gatewire error: %s\n", gw_status_message(status)); + abort(); + } +} + +static int register_sol_gateset(void) { + GwGateSet* set = NULL; + check_gw(gw_gateset_new(&set)); + check_gw(gw_gateset_add_builtin_rz(set)); + check_gw(gw_gateset_add_builtin_phased_x(set)); + check_gw(gw_gateset_add_builtin_phased_xx(set)); + + size_t len = 0; + check_gw(gw_gateset_serialized_len(set, &len)); + uint8_t* buffer = malloc(len); + if (buffer == NULL) { + ERROR("failed to allocate gateset buffer\n"); + gw_gateset_free(set); + return 1; + } + size_t written = 0; + check_gw(gw_gateset_serialize(set, buffer, len, &written)); + gw_gateset_free(set); + + size_t accepted_len = 0; + struct selene_void_result_t result = selene_register_gateset(selene_instance, buffer, written, NULL, 0, &accepted_len); + if (result.error_code != 0) { + ERROR("Error registering Sol gateset: error code %" PRIu32 "\n", result.error_code); + free(buffer); + return result.error_code; + } + uint8_t* accepted = malloc(accepted_len); + if (accepted == NULL && accepted_len != 0) { + ERROR("failed to allocate accepted gateset buffer\n"); + free(buffer); + return 1; + } + result = selene_register_gateset(selene_instance, buffer, written, accepted, accepted_len, &accepted_len); + free(buffer); + free(accepted); + if (result.error_code != 0) { + ERROR("Error registering Sol gateset: error code %" PRIu32 "\n", result.error_code); + return result.error_code; + } + return 0; +} + int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { @@ -42,6 +91,10 @@ int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { ERROR("Error initializing selene: error code %" PRIu32 "\n", void_result.error_code); return void_result.error_code; } + int register_result = register_sol_gateset(); + if (register_result != 0) { + return register_result; + } struct selene_u64_result_t n_shots = selene_shot_count(selene_instance); if (n_shots.error_code != 0) { ERROR("Error fetching shot count from selene: error code %" PRIu32 "\n", void_result.error_code); diff --git a/selene-ext/interfaces/sol_qis/c/src/sol_ops.c b/selene-ext/interfaces/sol_qis/c/src/sol_ops.c index c6cbe622..2df43c57 100644 --- a/selene-ext/interfaces/sol_qis/c/src/sol_ops.c +++ b/selene-ext/interfaces/sol_qis/c/src/sol_ops.c @@ -4,8 +4,73 @@ #include // selene_instance #include // unwrap +#include + #include "logging.h" // DIAGNOSTIC +static GwSemanticId rz_id; +static GwSemanticId phased_x_id; +static GwSemanticId phased_xx_id; +static bool gate_ids_initialized = false; + +static void check_gw(GwStatus status) { + if (status != GW_STATUS_OK) { + ERROR("gatewire error: %s\n", gw_status_message(status)); + abort(); + } +} + +static void init_gate_ids(void) { + if (gate_ids_initialized) { + return; + } + rz_id = gw_builtin_rz_semantic_id(); + phased_x_id = gw_builtin_phased_x_semantic_id(); + phased_xx_id = gw_builtin_phased_xx_semantic_id(); + gate_ids_initialized = true; +} + +static GwGateValue qubit_value(uint64_t q) { + GwGateValue value = { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_QUBIT, + .data.qubit = (uint32_t)q, + .bytes_ptr = NULL, + .bytes_len = 0, + }; + return value; +} + +static GwGateValue angle_value(double angle) { + GwGateValue value = { + .abi_size = sizeof(GwGateValue), + .kind = GW_OPERAND_KIND_F64, + .data.f64_value = angle, + .bytes_ptr = NULL, + .bytes_len = 0, + }; + return value; +} + +static void emit_gate(GwSemanticId semantic_id, GwGateValue const* values, size_t values_len) { + GwGateInstanceView gate = { + .abi_size = sizeof(GwGateInstanceView), + .semantic_id = semantic_id, + .values_ptr = values, + .values_len = values_len, + }; + size_t len = 0; + check_gw(gw_gate_serialized_len(&gate, &len)); + uint8_t* buffer = malloc(len); + if (buffer == NULL) { + ERROR("failed to allocate gate buffer\n"); + abort(); + } + size_t written = 0; + check_gw(gw_gate_serialize(&gate, buffer, len, &written)); + unwrap(selene_gate(selene_instance, buffer, written)); + free(buffer); +} uint64_t ___qalloc() { DIAGNOSTIC("___qalloc()\n"); @@ -20,17 +85,23 @@ void ___qfree(uint64_t q) { } void ___rp(uint64_t q, double theta, double phi) { DIAGNOSTIC("___rp(%" PRIu64 ", %f, %f)\n", q, theta, phi); - unwrap(selene_rxy(selene_instance, q, theta, phi)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q), angle_value(theta), angle_value(phi)}; + emit_gate(phased_x_id, values, 3); DIAGNOSTIC(" [done]\n"); } void ___rz(uint64_t q, double theta) { DIAGNOSTIC("___rz(%" PRIu64 ", %f)\n", q, theta); - unwrap(selene_rz(selene_instance, q, theta)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q), angle_value(theta)}; + emit_gate(rz_id, values, 2); DIAGNOSTIC(" [done]\n"); } void ___rpp(uint64_t q1, uint64_t q2, double theta, double phi) { DIAGNOSTIC("___rpp(%" PRIu64 ", %" PRIu64 ", %f, %f)\n", q1, q2, theta, phi); - unwrap(selene_rpp(selene_instance, q1, q2, theta, phi)); + init_gate_ids(); + GwGateValue values[] = {qubit_value(q1), qubit_value(q2), angle_value(theta), angle_value(phi)}; + emit_gate(phased_xx_id, values, 4); DIAGNOSTIC(" [done]\n"); } void ___reset(uint64_t q) { diff --git a/selene-ext/interfaces/sol_qis/python/selene_sol_qis_plugin/plugin.py b/selene-ext/interfaces/sol_qis/python/selene_sol_qis_plugin/plugin.py index 77cf5ec4..ff9bec17 100644 --- a/selene-ext/interfaces/sol_qis/python/selene_sol_qis_plugin/plugin.py +++ b/selene-ext/interfaces/sol_qis/python/selene_sol_qis_plugin/plugin.py @@ -4,7 +4,7 @@ The first is a dynamic library (interface) comprising the shims from QIS functionality (specific to Sol) to the Selene library functions, e.g.: ``` - ___rp(qubit, theta, phi) -> selene_rxy(selene_instance, qubit, theta, phi) + ___rp(qubit, theta, phi) -> selene_gate(selene_instance, PhasedX(...)) ``` it also contains a function to invoke a callback through a shot loop. It is linked to a base QIS interface that exposes common functions, e.g. diff --git a/selene-ext/runtimes/simple/python/selene_simple_runtime_plugin/plugin.py b/selene-ext/runtimes/simple/python/selene_simple_runtime_plugin/plugin.py index 835dd77c..df125149 100644 --- a/selene-ext/runtimes/simple/python/selene_simple_runtime_plugin/plugin.py +++ b/selene-ext/runtimes/simple/python/selene_simple_runtime_plugin/plugin.py @@ -16,31 +16,37 @@ class SimpleRuntimePlugin(Runtime): retrieve the result. """ - duration_ns_rxy: int = 0 - duration_ns_rzz: int = 0 + duration_ns_phased_x: int = 0 + duration_ns_zz_phase: int = 0 duration_ns_rz: int = 0 - duration_ns_rpp: int = 0 + duration_ns_phased_xx: int = 0 duration_ns_measure: int = 0 duration_ns_reset: int = 0 duration_ns_measure_leaked: int = 0 def __post_init__(self): - assert self.duration_ns_rxy >= 0, "duration_ns_rxy must be non-negative" - assert self.duration_ns_rzz >= 0, "duration_ns_rzz must be non-negative" + assert self.duration_ns_phased_x >= 0, ( + "duration_ns_phased_x must be non-negative" + ) + assert self.duration_ns_zz_phase >= 0, ( + "duration_ns_zz_phase must be non-negative" + ) assert self.duration_ns_measure >= 0, "duration_ns_measure must be non-negative" assert self.duration_ns_reset >= 0, "duration_ns_reset must be non-negative" assert self.duration_ns_rz >= 0, "duration_ns_rz must be non-negative" - assert self.duration_ns_rpp >= 0, "duration_ns_rpp must be non-negative" + assert self.duration_ns_phased_xx >= 0, ( + "duration_ns_phased_xx must be non-negative" + ) assert self.duration_ns_measure_leaked >= 0, ( "duration_ns_measure_leaked must be non-negative" ) def get_init_args(self): return [ - f"--duration-ns-rxy={self.duration_ns_rxy}", - f"--duration-ns-rzz={self.duration_ns_rzz}", + f"--duration-ns-phased-x={self.duration_ns_phased_x}", + f"--duration-ns-zz-phase={self.duration_ns_zz_phase}", f"--duration-ns-rz={self.duration_ns_rz}", - f"--duration-ns-rpp={self.duration_ns_rpp}", + f"--duration-ns-phased-xx={self.duration_ns_phased_xx}", f"--duration-ns-measure={self.duration_ns_measure}", f"--duration-ns-reset={self.duration_ns_reset}", f"--duration-ns-measure-leaked={self.duration_ns_measure_leaked}", diff --git a/selene-ext/runtimes/simple/rust/lib.rs b/selene-ext/runtimes/simple/rust/lib.rs index d0b83681..4d5db5b7 100644 --- a/selene-ext/runtimes/simple/rust/lib.rs +++ b/selene-ext/runtimes/simple/rust/lib.rs @@ -4,20 +4,24 @@ use anyhow::{Result, bail}; use clap::Parser; use selene_core::{ export_runtime_plugin, - runtime::{BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory}, + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, + runtime::{ + BatchOperation, BuiltinGate, Operation, RuntimeInterface, + interface::RuntimeInterfaceFactory, + }, utils::MetricValue, }; #[derive(Parser, Debug)] struct Params { #[arg(long)] - duration_ns_rxy: u64, + duration_ns_phased_x: u64, #[arg(long)] - duration_ns_rzz: u64, + duration_ns_zz_phase: u64, #[arg(long)] duration_ns_rz: u64, #[arg(long)] - duration_ns_rpp: u64, + duration_ns_phased_xx: u64, #[arg(long)] duration_ns_measure: u64, #[arg(long)] @@ -61,15 +65,17 @@ impl SimpleRuntime { } pub fn push(&mut self, op: Operation) { - let duration_ns = match op { - Operation::RXYGate { .. } => self.params.duration_ns_rxy, - Operation::RZZGate { .. } => self.params.duration_ns_rzz, - Operation::RZGate { .. } => self.params.duration_ns_rz, - Operation::RPPGate { .. } => self.params.duration_ns_rpp, - Operation::Measure { .. } => self.params.duration_ns_measure, - Operation::Reset { .. } => self.params.duration_ns_reset, - Operation::MeasureLeaked { .. } => self.params.duration_ns_measure_leaked, - _ => 0, + let duration_ns = match op.as_builtin_gate() { + Ok(Some(BuiltinGate::PhasedX { .. })) => self.params.duration_ns_phased_x, + Ok(Some(BuiltinGate::ZZPhase { .. })) => self.params.duration_ns_zz_phase, + Ok(Some(BuiltinGate::RZ { .. })) => self.params.duration_ns_rz, + Ok(Some(BuiltinGate::PhasedXX { .. })) => self.params.duration_ns_phased_xx, + _ => match op { + Operation::Measure { .. } => self.params.duration_ns_measure, + Operation::Reset { .. } => self.params.duration_ns_reset, + Operation::MeasureLeaked { .. } => self.params.duration_ns_measure_leaked, + _ => 0, + }, }; self.operation_queue.push_back(BatchOperation::runtime( vec![op], @@ -101,6 +107,13 @@ impl RuntimeInterface for SimpleRuntime { self.future_results.clear(); Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = builtin::all(); + if let Some(decl) = gateset.first_unsupported_by(&supported) { + bail!("SimpleRuntime does not support gate {}", decl.name); + } + Ok(gateset.clone()) + } fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { // This runtime isn't lazy, so a barrier is not relevant // to its operation. @@ -129,64 +142,69 @@ impl RuntimeInterface for SimpleRuntime { Ok(()) } } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rxy gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.push(Operation::RXYGate { - qubit_id, - theta, - phi, - }); - Ok(()) - } - fn rzz_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result<()> { - if qubit_id_1 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit1 {qubit_id_1}"); - } - if qubit_id_2 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit2 {qubit_id_2}"); - } - self.push(Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - }); - Ok(()) - } - fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rz gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.push(Operation::RZGate { qubit_id, theta }); - Ok(()) - } - fn rpp_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64, phi: f64) -> Result<()> { - if qubit_id_1 >= self.qubits.len() as u64 { - bail!("applying rpp gate to out-of-bounds qubit1 {qubit_id_1}"); - } - if qubit_id_2 >= self.qubits.len() as u64 { - bail!("applying rpp gate to out-of-bounds qubit2 {qubit_id_2}"); + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying PhasedX gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.push(Operation::phased_x(qubit_id, theta, phi)?); + Ok(()) + } + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => { + if qubit_id_1 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit1 {qubit_id_1}"); + } + if qubit_id_2 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit2 {qubit_id_2}"); + } + self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); + Ok(()) + } + Some(BuiltinGate::RZ { qubit_id, theta }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.push(Operation::rz(qubit_id, theta)?); + Ok(()) + } + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => { + if qubit_id_1 >= self.qubits.len() as u64 { + bail!("applying PhasedXX gate to out-of-bounds qubit1 {qubit_id_1}"); + } + if qubit_id_2 >= self.qubits.len() as u64 { + bail!("applying PhasedXX gate to out-of-bounds qubit2 {qubit_id_2}"); + } + let QubitStatus::Active = self.qubits[qubit_id_1 as usize] else { + bail!("Qubit {qubit_id_1} is not active"); + }; + let QubitStatus::Active = self.qubits[qubit_id_2 as usize] else { + bail!("Qubit {qubit_id_2} is not active"); + }; + self.push(Operation::phased_xx(qubit_id_1, qubit_id_2, theta, phi)?); + Ok(()) + } + None => bail!("SimpleRuntime does not support this gate"), } - let QubitStatus::Active = self.qubits[qubit_id_1 as usize] else { - bail!("Qubit {qubit_id_1} is not active"); - }; - let QubitStatus::Active = self.qubits[qubit_id_2 as usize] else { - bail!("Qubit {qubit_id_2} is not active"); - }; - self.push(Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - }); - Ok(()) } // Lifetime ops fn measure(&mut self, qubit_id: u64) -> Result { diff --git a/selene-ext/runtimes/soft_rz/python/selene_soft_rz_runtime_plugin/plugin.py b/selene-ext/runtimes/soft_rz/python/selene_soft_rz_runtime_plugin/plugin.py index 0a1a52e2..a1e77a1d 100644 --- a/selene-ext/runtimes/soft_rz/python/selene_soft_rz_runtime_plugin/plugin.py +++ b/selene-ext/runtimes/soft_rz/python/selene_soft_rz_runtime_plugin/plugin.py @@ -16,16 +16,20 @@ class SoftRZRuntimePlugin(Runtime): retrieve the result. """ - duration_ns_rxy: int = 0 - duration_ns_rzz: int = 0 + duration_ns_phased_x: int = 0 + duration_ns_zz_phase: int = 0 duration_ns_measure: int = 0 duration_ns_reset: int = 0 duration_ns_measure_leaked: int = 0 max_batch_size: int = 1 def __post_init__(self): - assert self.duration_ns_rxy >= 0, "duration_ns_rxy must be non-negative" - assert self.duration_ns_rzz >= 0, "duration_ns_rzz must be non-negative" + assert self.duration_ns_phased_x >= 0, ( + "duration_ns_phased_x must be non-negative" + ) + assert self.duration_ns_zz_phase >= 0, ( + "duration_ns_zz_phase must be non-negative" + ) assert self.duration_ns_measure >= 0, "duration_ns_measure must be non-negative" assert self.duration_ns_reset >= 0, "duration_ns_reset must be non-negative" assert self.duration_ns_measure_leaked >= 0, ( @@ -35,8 +39,8 @@ def __post_init__(self): def get_init_args(self): return [ - f"--duration-ns-rxy={self.duration_ns_rxy}", - f"--duration-ns-rzz={self.duration_ns_rzz}", + f"--duration-ns-phased-x={self.duration_ns_phased_x}", + f"--duration-ns-zz-phase={self.duration_ns_zz_phase}", f"--duration-ns-measure={self.duration_ns_measure}", f"--duration-ns-reset={self.duration_ns_reset}", f"--duration-ns-measure-leaked={self.duration_ns_measure_leaked}", diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_1.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_1.json index c5071de1..38ecd959 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_1.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_1.json @@ -114,630 +114,1000 @@ { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.02454369260617026 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { @@ -1110,28 +1480,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1146,28 +1531,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1182,28 +1582,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1218,28 +1633,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1254,28 +1684,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1290,28 +1735,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1326,28 +1786,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1362,28 +1837,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1398,28 +1888,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ], "duration_ns": 0 } }, @@ -1434,28 +1939,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1470,28 +1990,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1506,28 +2041,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1542,28 +2092,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1578,28 +2143,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1614,28 +2194,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1650,28 +2245,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ], "duration_ns": 0 } }, @@ -1686,28 +2296,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1722,28 +2347,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1758,28 +2398,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1794,28 +2449,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1830,28 +2500,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1866,28 +2551,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ], "duration_ns": 0 } }, @@ -1902,28 +2602,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1938,28 +2653,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1974,28 +2704,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -2010,28 +2755,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -2046,28 +2806,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ], "duration_ns": 0 } }, @@ -2082,28 +2857,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2118,28 +2908,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2154,28 +2959,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2190,28 +3010,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ], "duration_ns": 0 } }, @@ -2226,28 +3061,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2262,28 +3112,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2298,28 +3163,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ], "duration_ns": 0 } }, @@ -2334,28 +3214,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ], "duration_ns": 0 } }, @@ -2370,28 +3265,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ], "duration_ns": 0 } }, @@ -2406,28 +3316,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ], "duration_ns": 0 } }, diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_2.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_2.json index 815e71d6..7ff35bc3 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_2.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_2.json @@ -114,630 +114,1000 @@ { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.02454369260617026 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { @@ -1078,28 +1448,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1114,28 +1499,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1150,28 +1550,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1186,28 +1601,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1222,28 +1652,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1258,28 +1703,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1294,28 +1754,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1330,28 +1805,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1366,28 +1856,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ], "duration_ns": 0 } }, @@ -1402,28 +1907,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1438,28 +1958,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1474,28 +2009,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1510,28 +2060,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1546,28 +2111,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1582,28 +2162,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1618,28 +2213,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ], "duration_ns": 0 } }, @@ -1654,28 +2264,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1690,28 +2315,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1726,28 +2366,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1762,28 +2417,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1798,28 +2468,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1834,28 +2519,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ], "duration_ns": 0 } }, @@ -1870,28 +2570,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1906,28 +2621,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1942,28 +2672,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1978,28 +2723,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -2014,28 +2774,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ], "duration_ns": 0 } }, @@ -2050,28 +2825,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2086,28 +2876,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2122,28 +2927,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2158,28 +2978,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ], "duration_ns": 0 } }, @@ -2194,28 +3029,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2230,28 +3080,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2266,28 +3131,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ], "duration_ns": 0 } }, @@ -2302,28 +3182,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ], "duration_ns": 0 } }, @@ -2338,28 +3233,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ], "duration_ns": 0 } }, @@ -2374,28 +3284,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ], "duration_ns": 0 } }, diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_8.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_8.json index 4731f0e7..8c886b14 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_8.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/instructions_batching_8.json @@ -114,630 +114,1000 @@ { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 0.7853981633974483 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 0.7853981633974483 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 0.39269908169872414 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 0.39269908169872414 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 3, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 3 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 0.19634954084936207 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 0.19634954084936207 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 4, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 4 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 0.09817477042468103 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 0.09817477042468103 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 5, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 5 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 0.04908738521234052 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 0.04908738521234052 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 6, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 6 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 0.02454369260617026 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 0.02454369260617026 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 7, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 7 + ], + "params": [ + 3.141592653589793 + ] } }, { @@ -1054,28 +1424,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1090,28 +1475,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 7, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 7 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1126,28 +1526,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 6, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 6 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1162,28 +1577,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 5, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 5 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1198,28 +1628,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 4, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 4 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1234,28 +1679,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 3, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 3 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1270,28 +1730,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 2, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 2 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1306,28 +1781,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, @@ -1342,28 +1832,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -3.141592653589793 + ], "duration_ns": 0 } }, @@ -1378,28 +1883,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 7, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 7 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1414,28 +1934,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 6, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 6 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1450,28 +1985,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 5, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 5 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1486,28 +2036,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 4, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 4 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1522,28 +2087,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 3, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 3 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1558,28 +2138,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": -0.7853981633974483, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + -0.7853981633974483 + ], "duration_ns": 0 } }, @@ -1594,28 +2189,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": -3.9269908169872414, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + -3.9269908169872414 + ], "duration_ns": 0 } }, @@ -1630,28 +2240,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 7, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 7 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1666,28 +2291,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 6, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 6 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1702,28 +2342,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 5, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 5 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1738,28 +2393,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 4, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 4 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1774,28 +2444,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 2, - "qubit1": 3, - "theta": -0.39269908169872414, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 2, + 3 + ], + "params": [ + -0.39269908169872414 + ], "duration_ns": 0 } }, @@ -1810,28 +2495,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 3, - "theta": 1.5707963267948966, - "phi": -4.319689898685965, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 3 + ], + "params": [ + 1.5707963267948966, + -4.319689898685965 + ], "duration_ns": 0 } }, @@ -1846,28 +2546,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 7, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 7 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1882,28 +2597,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 6, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 6 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1918,28 +2648,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 5, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 5 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1954,28 +2699,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 3, - "qubit1": 4, - "theta": -0.19634954084936207, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 3, + 4 + ], + "params": [ + -0.19634954084936207 + ], "duration_ns": 0 } }, @@ -1990,28 +2750,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 4, - "theta": 1.5707963267948966, - "phi": -4.516039439535327, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 4 + ], + "params": [ + 1.5707963267948966, + -4.516039439535327 + ], "duration_ns": 0 } }, @@ -2026,28 +2801,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 7, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 7 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2062,28 +2852,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 6, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 6 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2098,28 +2903,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 4, - "qubit1": 5, - "theta": -0.09817477042468103, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 4, + 5 + ], + "params": [ + -0.09817477042468103 + ], "duration_ns": 0 } }, @@ -2134,28 +2954,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 5, - "theta": 1.5707963267948966, - "phi": -4.614214209960009, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 5 + ], + "params": [ + 1.5707963267948966, + -4.614214209960009 + ], "duration_ns": 0 } }, @@ -2170,28 +3005,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 7, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 7 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2206,28 +3056,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 5, - "qubit1": 6, - "theta": -0.04908738521234052, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 5, + 6 + ], + "params": [ + -0.04908738521234052 + ], "duration_ns": 0 } }, @@ -2242,28 +3107,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 6, - "theta": 1.5707963267948966, - "phi": -4.663301595172349, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 6 + ], + "params": [ + 1.5707963267948966, + -4.663301595172349 + ], "duration_ns": 0 } }, @@ -2278,28 +3158,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 6, - "qubit1": 7, - "theta": -0.02454369260617026, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 6, + 7 + ], + "params": [ + -0.02454369260617026 + ], "duration_ns": 0 } }, @@ -2314,28 +3209,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -4.687845287778519, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -4.687845287778519 + ], "duration_ns": 0 } }, @@ -2350,28 +3260,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 7, - "theta": 1.5707963267948966, - "phi": -7.829437941368312, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 7 + ], + "params": [ + 1.5707963267948966, + -7.829437941368312 + ], "duration_ns": 0 } }, diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_1.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_1.json index 7933f26d..50e59173 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_1.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_1.json @@ -210,7 +210,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -228,7 +228,7 @@ "qubits": [ 0 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -246,7 +246,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -263,7 +263,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -281,7 +281,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -298,7 +298,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -316,7 +316,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -333,7 +333,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -351,7 +351,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -368,7 +368,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -386,7 +386,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -403,7 +403,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -421,7 +421,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -438,7 +438,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -456,7 +456,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -473,7 +473,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -490,7 +490,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -508,7 +508,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -526,7 +526,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -543,7 +543,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -561,7 +561,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -578,7 +578,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -596,7 +596,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -613,7 +613,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -631,7 +631,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -648,7 +648,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -666,7 +666,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -683,7 +683,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -701,7 +701,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -718,7 +718,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -735,7 +735,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -753,7 +753,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -771,7 +771,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -788,7 +788,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -806,7 +806,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -823,7 +823,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -841,7 +841,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -858,7 +858,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -876,7 +876,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -893,7 +893,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -911,7 +911,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -928,7 +928,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -945,7 +945,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -963,7 +963,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -981,7 +981,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -998,7 +998,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1016,7 +1016,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1033,7 +1033,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1051,7 +1051,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1068,7 +1068,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1086,7 +1086,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1103,7 +1103,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1120,7 +1120,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1138,7 +1138,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1156,7 +1156,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1173,7 +1173,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1191,7 +1191,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1208,7 +1208,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1226,7 +1226,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1243,7 +1243,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1260,7 +1260,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1278,7 +1278,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1296,7 +1296,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1313,7 +1313,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1331,7 +1331,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1348,7 +1348,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1365,7 +1365,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1383,7 +1383,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1401,7 +1401,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -1418,7 +1418,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.02454369260617026 ], @@ -1435,7 +1435,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1453,7 +1453,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1470,7 +1470,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1488,7 +1488,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1972,7 +1972,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1990,7 +1990,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2009,7 +2009,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2029,7 +2029,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2047,7 +2047,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2066,7 +2066,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2085,7 +2085,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2103,7 +2103,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2122,7 +2122,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2141,7 +2141,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2159,7 +2159,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2178,7 +2178,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2197,7 +2197,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2215,7 +2215,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2234,7 +2234,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2253,7 +2253,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2271,7 +2271,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2290,7 +2290,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2309,7 +2309,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2327,7 +2327,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2346,7 +2346,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2365,7 +2365,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2383,7 +2383,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2402,7 +2402,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2420,7 +2420,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2438,7 +2438,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2457,7 +2457,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2477,7 +2477,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2495,7 +2495,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2514,7 +2514,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2533,7 +2533,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2551,7 +2551,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2570,7 +2570,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2589,7 +2589,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2607,7 +2607,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2626,7 +2626,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2645,7 +2645,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2663,7 +2663,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2682,7 +2682,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2701,7 +2701,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2719,7 +2719,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2738,7 +2738,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2757,7 +2757,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2775,7 +2775,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2794,7 +2794,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2812,7 +2812,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2830,7 +2830,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2849,7 +2849,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2869,7 +2869,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2887,7 +2887,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2906,7 +2906,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2925,7 +2925,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2943,7 +2943,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2962,7 +2962,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2981,7 +2981,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2999,7 +2999,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3018,7 +3018,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3037,7 +3037,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3055,7 +3055,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3074,7 +3074,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3093,7 +3093,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3111,7 +3111,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3130,7 +3130,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3148,7 +3148,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3166,7 +3166,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3185,7 +3185,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3205,7 +3205,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3223,7 +3223,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3242,7 +3242,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3261,7 +3261,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3279,7 +3279,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3298,7 +3298,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3317,7 +3317,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3335,7 +3335,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3354,7 +3354,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3373,7 +3373,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3391,7 +3391,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3410,7 +3410,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3428,7 +3428,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3446,7 +3446,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3465,7 +3465,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3485,7 +3485,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3503,7 +3503,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3522,7 +3522,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3541,7 +3541,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3559,7 +3559,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3578,7 +3578,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3597,7 +3597,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3615,7 +3615,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3634,7 +3634,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3652,7 +3652,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3670,7 +3670,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3689,7 +3689,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3709,7 +3709,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3727,7 +3727,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3746,7 +3746,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3765,7 +3765,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3783,7 +3783,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3802,7 +3802,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3820,7 +3820,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3838,7 +3838,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3857,7 +3857,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3877,7 +3877,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3895,7 +3895,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3914,7 +3914,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3932,7 +3932,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3950,7 +3950,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3969,7 +3969,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3988,7 +3988,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4006,7 +4006,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4025,7 +4025,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_2.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_2.json index 16d8fb46..0a4ed73e 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_2.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_2.json @@ -210,7 +210,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -228,7 +228,7 @@ "qubits": [ 0 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -246,7 +246,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -263,7 +263,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -281,7 +281,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -298,7 +298,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -316,7 +316,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -333,7 +333,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -351,7 +351,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -368,7 +368,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -386,7 +386,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -403,7 +403,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -421,7 +421,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -438,7 +438,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -456,7 +456,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -473,7 +473,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -490,7 +490,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -508,7 +508,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -526,7 +526,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -543,7 +543,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -561,7 +561,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -578,7 +578,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -596,7 +596,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -613,7 +613,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -631,7 +631,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -648,7 +648,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -666,7 +666,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -683,7 +683,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -701,7 +701,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -718,7 +718,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -735,7 +735,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -753,7 +753,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -771,7 +771,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -788,7 +788,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -806,7 +806,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -823,7 +823,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -841,7 +841,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -858,7 +858,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -876,7 +876,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -893,7 +893,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -911,7 +911,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -928,7 +928,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -945,7 +945,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -963,7 +963,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -981,7 +981,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -998,7 +998,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1016,7 +1016,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1033,7 +1033,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1051,7 +1051,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1068,7 +1068,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1086,7 +1086,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1103,7 +1103,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1120,7 +1120,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1138,7 +1138,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1156,7 +1156,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1173,7 +1173,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1191,7 +1191,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1208,7 +1208,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1226,7 +1226,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1243,7 +1243,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1260,7 +1260,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1278,7 +1278,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1296,7 +1296,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1313,7 +1313,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1331,7 +1331,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1348,7 +1348,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1365,7 +1365,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1383,7 +1383,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1401,7 +1401,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -1418,7 +1418,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.02454369260617026 ], @@ -1435,7 +1435,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1453,7 +1453,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1470,7 +1470,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1488,7 +1488,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1972,7 +1972,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1990,7 +1990,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2009,7 +2009,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2029,7 +2029,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2047,7 +2047,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2066,7 +2066,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2085,7 +2085,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2103,7 +2103,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2122,7 +2122,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2141,7 +2141,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2159,7 +2159,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2178,7 +2178,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2197,7 +2197,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2215,7 +2215,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2234,7 +2234,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2253,7 +2253,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2271,7 +2271,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2290,7 +2290,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2309,7 +2309,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2327,7 +2327,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2346,7 +2346,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2365,7 +2365,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2383,7 +2383,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2402,7 +2402,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2420,7 +2420,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2438,7 +2438,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2457,7 +2457,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2477,7 +2477,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2495,7 +2495,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2514,7 +2514,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2533,7 +2533,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2551,7 +2551,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2570,7 +2570,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2589,7 +2589,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2607,7 +2607,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2626,7 +2626,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2645,7 +2645,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2663,7 +2663,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2682,7 +2682,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2701,7 +2701,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2719,7 +2719,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2738,7 +2738,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2757,7 +2757,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2775,7 +2775,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2794,7 +2794,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2812,7 +2812,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2830,7 +2830,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2849,7 +2849,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2869,7 +2869,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2887,7 +2887,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2906,7 +2906,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2925,7 +2925,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2943,7 +2943,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2962,7 +2962,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2981,7 +2981,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2999,7 +2999,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3018,7 +3018,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3037,7 +3037,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3055,7 +3055,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3074,7 +3074,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3093,7 +3093,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3111,7 +3111,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3130,7 +3130,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3148,7 +3148,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3166,7 +3166,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3185,7 +3185,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3205,7 +3205,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3223,7 +3223,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3242,7 +3242,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3261,7 +3261,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3279,7 +3279,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3298,7 +3298,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3317,7 +3317,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3335,7 +3335,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3354,7 +3354,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3373,7 +3373,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3391,7 +3391,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3410,7 +3410,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3428,7 +3428,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3446,7 +3446,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3465,7 +3465,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3485,7 +3485,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3503,7 +3503,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3522,7 +3522,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3541,7 +3541,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3559,7 +3559,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3578,7 +3578,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3597,7 +3597,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3615,7 +3615,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3634,7 +3634,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3652,7 +3652,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3670,7 +3670,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3689,7 +3689,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3709,7 +3709,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3727,7 +3727,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3746,7 +3746,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3765,7 +3765,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3783,7 +3783,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3802,7 +3802,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3820,7 +3820,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3838,7 +3838,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3857,7 +3857,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3877,7 +3877,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3895,7 +3895,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3914,7 +3914,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3932,7 +3932,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3950,7 +3950,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3969,7 +3969,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3988,7 +3988,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4006,7 +4006,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4025,7 +4025,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 diff --git a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_8.json b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_8.json index d39179d6..ef5307f8 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_8.json +++ b/selene-ext/runtimes/soft_rz/python/tests/snapshots/test_batching/test_batching_behaviour/trace_batching_8.json @@ -210,7 +210,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -228,7 +228,7 @@ "qubits": [ 0 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -246,7 +246,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -263,7 +263,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -281,7 +281,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -298,7 +298,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -316,7 +316,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -333,7 +333,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -351,7 +351,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -368,7 +368,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -386,7 +386,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -403,7 +403,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -421,7 +421,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -438,7 +438,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -456,7 +456,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -473,7 +473,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 1.5707963267948966 ], @@ -490,7 +490,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -508,7 +508,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -526,7 +526,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -543,7 +543,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -561,7 +561,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -578,7 +578,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -596,7 +596,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -613,7 +613,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -631,7 +631,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -648,7 +648,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -666,7 +666,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -683,7 +683,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -701,7 +701,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -718,7 +718,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.7853981633974483 ], @@ -735,7 +735,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -753,7 +753,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -771,7 +771,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -788,7 +788,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -806,7 +806,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -823,7 +823,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -841,7 +841,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -858,7 +858,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -876,7 +876,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -893,7 +893,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -911,7 +911,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -928,7 +928,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.39269908169872414 ], @@ -945,7 +945,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -963,7 +963,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -981,7 +981,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -998,7 +998,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1016,7 +1016,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1033,7 +1033,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1051,7 +1051,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1068,7 +1068,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1086,7 +1086,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -1103,7 +1103,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.19634954084936207 ], @@ -1120,7 +1120,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1138,7 +1138,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1156,7 +1156,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1173,7 +1173,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1191,7 +1191,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1208,7 +1208,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1226,7 +1226,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -1243,7 +1243,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.09817477042468103 ], @@ -1260,7 +1260,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1278,7 +1278,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1296,7 +1296,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1313,7 +1313,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1331,7 +1331,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -1348,7 +1348,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.04908738521234052 ], @@ -1365,7 +1365,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1383,7 +1383,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1401,7 +1401,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -1418,7 +1418,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 0.02454369260617026 ], @@ -1435,7 +1435,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1453,7 +1453,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1470,7 +1470,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1488,7 +1488,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -1972,7 +1972,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1990,7 +1990,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2009,7 +2009,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -2029,7 +2029,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2047,7 +2047,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2066,7 +2066,7 @@ 0, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2085,7 +2085,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2103,7 +2103,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2122,7 +2122,7 @@ 0, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2141,7 +2141,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2159,7 +2159,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2178,7 +2178,7 @@ 0, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2197,7 +2197,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2215,7 +2215,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2234,7 +2234,7 @@ 0, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2253,7 +2253,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2271,7 +2271,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2290,7 +2290,7 @@ 0, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2309,7 +2309,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2327,7 +2327,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2346,7 +2346,7 @@ 0, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2365,7 +2365,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2383,7 +2383,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2402,7 +2402,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -1.5707963267948966 ], @@ -2420,7 +2420,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2438,7 +2438,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2457,7 +2457,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.141592653589793 @@ -2477,7 +2477,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2495,7 +2495,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2514,7 +2514,7 @@ 1, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2533,7 +2533,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2551,7 +2551,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2570,7 +2570,7 @@ 1, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2589,7 +2589,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2607,7 +2607,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2626,7 +2626,7 @@ 1, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2645,7 +2645,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2663,7 +2663,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2682,7 +2682,7 @@ 1, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2701,7 +2701,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2719,7 +2719,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2738,7 +2738,7 @@ 1, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2757,7 +2757,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2775,7 +2775,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2794,7 +2794,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.7853981633974483 ], @@ -2812,7 +2812,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2830,7 +2830,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2849,7 +2849,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -3.9269908169872414 @@ -2869,7 +2869,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2887,7 +2887,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2906,7 +2906,7 @@ 2, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2925,7 +2925,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2943,7 +2943,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2962,7 +2962,7 @@ 2, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2981,7 +2981,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -2999,7 +2999,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3018,7 +3018,7 @@ 2, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3037,7 +3037,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3055,7 +3055,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3074,7 +3074,7 @@ 2, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3093,7 +3093,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3111,7 +3111,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3130,7 +3130,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.39269908169872414 ], @@ -3148,7 +3148,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3166,7 +3166,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3185,7 +3185,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.319689898685965 @@ -3205,7 +3205,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3223,7 +3223,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3242,7 +3242,7 @@ 3, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3261,7 +3261,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3279,7 +3279,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3298,7 +3298,7 @@ 3, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3317,7 +3317,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3335,7 +3335,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3354,7 +3354,7 @@ 3, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3373,7 +3373,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3391,7 +3391,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3410,7 +3410,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.19634954084936207 ], @@ -3428,7 +3428,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3446,7 +3446,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3465,7 +3465,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.516039439535327 @@ -3485,7 +3485,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3503,7 +3503,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3522,7 +3522,7 @@ 4, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3541,7 +3541,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3559,7 +3559,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3578,7 +3578,7 @@ 4, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3597,7 +3597,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3615,7 +3615,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3634,7 +3634,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.09817477042468103 ], @@ -3652,7 +3652,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3670,7 +3670,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3689,7 +3689,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.614214209960009 @@ -3709,7 +3709,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3727,7 +3727,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3746,7 +3746,7 @@ 5, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3765,7 +3765,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3783,7 +3783,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3802,7 +3802,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.04908738521234052 ], @@ -3820,7 +3820,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3838,7 +3838,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3857,7 +3857,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.663301595172349 @@ -3877,7 +3877,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3895,7 +3895,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3914,7 +3914,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ -0.02454369260617026 ], @@ -3932,7 +3932,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3950,7 +3950,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3969,7 +3969,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -4.687845287778519 @@ -3988,7 +3988,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4006,7 +4006,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 @@ -4025,7 +4025,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -7.829437941368312 diff --git a/selene-ext/runtimes/soft_rz/python/tests/test_batching.py b/selene-ext/runtimes/soft_rz/python/tests/test_batching.py index 34e80675..cd8d9e3d 100644 --- a/selene-ext/runtimes/soft_rz/python/tests/test_batching.py +++ b/selene-ext/runtimes/soft_rz/python/tests/test_batching.py @@ -44,8 +44,8 @@ def main() -> None: # provide some dummy timing information for the runtimes defaults = { - "duration_ns_rxy": 1_200_000, # 1.2ms - "duration_ns_rzz": 2_500_000, # 2.5ms + "duration_ns_phased_x": 1_200_000, # 1.2ms + "duration_ns_zz_phase": 2_500_000, # 2.5ms "duration_ns_measure": 3_000_000, # 3ms "duration_ns_reset": 500_000, # 0.5ms "duration_ns_measure_leaked": 4_000_000, # 4ms diff --git a/selene-ext/runtimes/soft_rz/rust/lib.rs b/selene-ext/runtimes/soft_rz/rust/lib.rs index 0862a2ec..862f868c 100644 --- a/selene-ext/runtimes/soft_rz/rust/lib.rs +++ b/selene-ext/runtimes/soft_rz/rust/lib.rs @@ -4,16 +4,20 @@ use anyhow::{Result, bail}; use clap::Parser; use selene_core::{ export_runtime_plugin, - runtime::{BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory}, + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, + runtime::{ + BatchOperation, BuiltinGate, Operation, RuntimeInterface, + interface::RuntimeInterfaceFactory, + }, utils::MetricValue, }; #[derive(Parser, Debug)] struct Params { #[arg(long)] - duration_ns_rxy: u64, + duration_ns_phased_x: u64, #[arg(long)] - duration_ns_rzz: u64, + duration_ns_zz_phase: u64, #[arg(long)] duration_ns_measure: u64, #[arg(long)] @@ -85,13 +89,15 @@ impl SoftRZRuntime { self.operation_queue[append_idx].add_operation(op); } else { // We didn't find a batch to append to, so we need to create a new batch for this operation. - let duration = match op { - Operation::RXYGate { .. } => self.params.duration_ns_rxy, - Operation::RZZGate { .. } => self.params.duration_ns_rzz, - Operation::Measure { .. } => self.params.duration_ns_measure, - Operation::Reset { .. } => self.params.duration_ns_reset, - Operation::MeasureLeaked { .. } => self.params.duration_ns_measure_leaked, - _ => 0, // Unhandled ops have no duration, since we don't know their semantics. + let duration = match op.as_builtin_gate() { + Ok(Some(BuiltinGate::PhasedX { .. })) => self.params.duration_ns_phased_x, + Ok(Some(BuiltinGate::ZZPhase { .. })) => self.params.duration_ns_zz_phase, + _ => match op { + Operation::Measure { .. } => self.params.duration_ns_measure, + Operation::Reset { .. } => self.params.duration_ns_reset, + Operation::MeasureLeaked { .. } => self.params.duration_ns_measure_leaked, + _ => 0, // Unhandled ops have no duration, since we don't know their semantics. + }, }; self.operation_queue.push_back(BatchOperation::runtime( vec![op], @@ -125,9 +131,18 @@ impl SoftRZRuntime { } // next, check the type of the operations in this batch. If they aren't the same type as op, we can't append, but we can continue searching for an earlier batch that op might fit into. let same_type = batch.iter_ops().all(|batch_op| match (batch_op, op) { - (Operation::RXYGate { .. }, Operation::RXYGate { .. }) => true, - (Operation::RZGate { .. }, Operation::RZGate { .. }) => true, - (Operation::RZZGate { .. }, Operation::RZZGate { .. }) => true, + (Operation::Gate { .. }, Operation::Gate { .. }) => { + matches!( + (batch_op.as_builtin_gate(), op.as_builtin_gate()), + ( + Ok(Some(BuiltinGate::PhasedX { .. })), + Ok(Some(BuiltinGate::PhasedX { .. })) + ) | ( + Ok(Some(BuiltinGate::ZZPhase { .. })), + Ok(Some(BuiltinGate::ZZPhase { .. })) + ) + ) + } (Operation::Measure { .. }, Operation::Measure { .. }) => true, (Operation::MeasureLeaked { .. }, Operation::MeasureLeaked { .. }) => true, (Operation::Reset { .. }, Operation::Reset { .. }) => true, @@ -179,6 +194,21 @@ impl RuntimeInterface for SoftRZRuntime { self.future_results.clear(); Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let accepted = DynamicGateSet::from_declarations([ + builtin::RZ::declaration(), + builtin::PhasedX::declaration(), + builtin::ZZPhase::declaration(), + ])?; + if let Some(decl) = gateset.first_unsupported_by(&accepted) { + bail!("SoftRZRuntime does not support gate {}", decl.name); + } + DynamicGateSet::from_declarations([ + builtin::PhasedX::declaration(), + builtin::ZZPhase::declaration(), + ]) + .map_err(Into::into) + } fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { self.flush_size = self.operation_queue.len(); Ok(()) @@ -219,59 +249,53 @@ impl RuntimeInterface for SoftRZRuntime { Ok(()) } } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rxy gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.push(Operation::RXYGate { - qubit_id, - theta, - phi: phi - phase, // The Z phase is enacted here. - }); - Ok(()) - } - fn rzz_gate(&mut self, qubit_id_1: u64, qubit_id_2: u64, theta: f64) -> Result<()> { - if qubit_id_1 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit1 {qubit_id_1}"); - } - if qubit_id_2 >= self.qubits.len() as u64 { - bail!("applying rzz gate to out-of-bounds qubit2 {qubit_id_2}"); - } - self.push(Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - }); - Ok(()) - } - fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying rz gate to out-of-bounds qubit {qubit_id}"); + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying PhasedX gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.push(Operation::phased_x(qubit_id, theta, phi - phase)?); + Ok(()) + } + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => { + if qubit_id_1 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit1 {qubit_id_1}"); + } + if qubit_id_2 >= self.qubits.len() as u64 { + bail!("applying ZZPhase gate to out-of-bounds qubit2 {qubit_id_2}"); + } + self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); + Ok(()) + } + Some(BuiltinGate::RZ { qubit_id, theta }) => { + if qubit_id >= self.qubits.len() as u64 { + bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); + } + let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { + bail!("Qubit {qubit_id} is not active"); + }; + self.qubits[qubit_id as usize] = QubitStatus::Active { + phase: phase + theta, + }; + Ok(()) + } + Some(BuiltinGate::PhasedXX { .. }) => { + bail!("The PhasedXX gate is not compatible with the SoftRZRuntime") + } + None => bail!("SoftRZRuntime does not support this gate"), } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - // We don't apply an RZ gate. Instead, we accumulate a phase, and mutate - // RXY gates' phi parameters to account for the phase shift. RZZ and measurement - // are unaffected. - self.qubits[qubit_id as usize] = QubitStatus::Active { - phase: phase + theta, - }; - Ok(()) - } - fn rpp_gate( - &mut self, - _qubit_id_1: u64, - _qubit_id_2: u64, - _theta: f64, - _phi: f64, - ) -> Result<()> { - bail!( - "The RPP gate is not compatible with the SoftRZRuntime, as it relies on the properties of rz's interaction with (rxy, rzz)." - ); } // Lifetime ops fn measure(&mut self, qubit_id: u64) -> Result { diff --git a/selene-ext/simulators/classical-replay/rust/lib.rs b/selene-ext/simulators/classical-replay/rust/lib.rs index c6c564f1..d215570c 100644 --- a/selene-ext/simulators/classical-replay/rust/lib.rs +++ b/selene-ext/simulators/classical-replay/rust/lib.rs @@ -2,7 +2,7 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -35,23 +35,23 @@ pub struct ClassicalReplaySimulator { } impl ClassicalReplaySimulator { - fn rxy(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_x(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RXY(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } } - fn rzz(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RZZ(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -68,12 +68,12 @@ impl ClassicalReplaySimulator { } } - fn rpp(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_xx(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RPP(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -144,23 +144,26 @@ impl SimulatorInterface for ClassicalReplaySimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, diff --git a/selene-ext/simulators/classical-replay/rust/tests.rs b/selene-ext/simulators/classical-replay/rust/tests.rs index 737fb392..fed07763 100644 --- a/selene-ext/simulators/classical-replay/rust/tests.rs +++ b/selene-ext/simulators/classical-replay/rust/tests.rs @@ -17,7 +17,7 @@ fn replay_test() { TestFramework::new(5) .h(0) - .rxy(0, std::f64::consts::FRAC_PI_8, std::f64::consts::E) + .phased_x(0, std::f64::consts::FRAC_PI_8, std::f64::consts::E) .test(100, vec![0, 1], |populations| { // if we were to measure the first two // qubits, we should always get false and true, diff --git a/selene-ext/simulators/coinflip/rust/lib.rs b/selene-ext/simulators/coinflip/rust/lib.rs index 84a46e78..0ee49da7 100644 --- a/selene-ext/simulators/coinflip/rust/lib.rs +++ b/selene-ext/simulators/coinflip/rust/lib.rs @@ -4,7 +4,7 @@ use rand::{Rng, SeedableRng}; use rand_pcg::Pcg64Mcg; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -33,23 +33,23 @@ impl CoinflipSimulator { self.true_flips as f64 / self.total_flips as f64 } - fn rxy(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_x(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RXY(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } } - fn rzz(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RZZ(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -66,12 +66,12 @@ impl CoinflipSimulator { } } - fn rpp(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { + fn phased_xx(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { Ok(()) } else { Err(anyhow!( - "RPP(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -126,23 +126,26 @@ impl SimulatorInterface for CoinflipSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, diff --git a/selene-ext/simulators/quantum-replay/rust/lib.rs b/selene-ext/simulators/quantum-replay/rust/lib.rs index f5a3093f..ea06a4c6 100644 --- a/selene-ext/simulators/quantum-replay/rust/lib.rs +++ b/selene-ext/simulators/quantum-replay/rust/lib.rs @@ -2,7 +2,7 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::simulator::{Simulator, SimulatorInterface}; use selene_core::utils::MetricValue; @@ -82,31 +82,23 @@ impl QuantumReplaySimulator { } } - fn rxy(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { + fn phased_x(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { if q0 < self.n_qubits { - self.apply_wrapped_void(Operation::RXYGate { - qubit_id: q0, - theta, - phi, - }) + self.apply_wrapped_void(Operation::phased_x(q0, theta, phi)?) } else { Err(anyhow!( - "RXY(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } } - fn rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { - self.apply_wrapped_void(Operation::RZZGate { - qubit_id_1: q0, - qubit_id_2: q1, - theta, - }) + self.apply_wrapped_void(Operation::zz_phase(q0, q1, theta)?) } else { Err(anyhow!( - "RZZ(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -114,10 +106,7 @@ impl QuantumReplaySimulator { fn rz(&mut self, q0: u64, theta: f64) -> Result<()> { if q0 < self.n_qubits { - self.apply_wrapped_void(Operation::RZGate { - qubit_id: q0, - theta, - }) + self.apply_wrapped_void(Operation::rz(q0, theta)?) } else { Err(anyhow!( "RZ(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", @@ -126,17 +115,12 @@ impl QuantumReplaySimulator { } } - fn rpp(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { + fn phased_xx(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { if q0 < self.n_qubits && q1 < self.n_qubits { - self.apply_wrapped_void(Operation::RPPGate { - qubit_id_1: q0, - qubit_id_2: q1, - theta, - phi, - }) + self.apply_wrapped_void(Operation::phased_xx(q0, q1, theta, phi)?) } else { Err(anyhow!( - "RPP(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } @@ -212,23 +196,26 @@ impl SimulatorInterface for QuantumReplaySimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, diff --git a/selene-ext/simulators/quest/python/gate_definitions.py b/selene-ext/simulators/quest/python/gate_definitions.py index b6525bed..e10c4bbf 100644 --- a/selene-ext/simulators/quest/python/gate_definitions.py +++ b/selene-ext/simulators/quest/python/gate_definitions.py @@ -41,7 +41,7 @@ def ryy(angle): ) -def rzz(angle): +def ZZPhase(angle): return Matrix( [ [exp(-I * angle / 2), 0, 0, 0], @@ -58,11 +58,11 @@ def twin_rz(angle): return sp.trigsimp(rz_q1 * rz_q2) -def rxy(theta, phi): +def PhasedX(theta, phi): return sp.trigsimp(rz(phi) * rx(theta) * rz(-phi)) -def rpp(theta, phi): +def PhasedXX(theta, phi): return sp.trigsimp(twin_rz(phi) * rxx(theta) * twin_rz(-phi)) @@ -99,13 +99,13 @@ def print_summary(name, gate, notes: str | None = None): theta, phi, alpha, beta, gamma = sp.symbols("theta phi alpha beta gamma", real=True) rz_gate = rz(theta) - rxy_gate = rxy(theta, phi) - rzz_gate = sp.simplify( - rzz(theta) * exp(I * theta / 2) + phased_x_gate = PhasedX(theta, phi) + zz_phase_gate = sp.simplify( + ZZPhase(theta) * exp(I * theta / 2) ) # Global phase adjustment for consistency with prior versions - rpp_gate = rpp(theta, phi) + phased_xx_gate = PhasedXX(theta, phi) print_summary(f"rz({theta})", rz_gate) - print_summary(f"rxy({theta}, {phi})", rxy_gate) - print_summary(f"rzz({theta})", rzz_gate) - print_summary(f"rpp({theta}, {phi})", rpp_gate) + print_summary(f"PhasedX({theta}, {phi})", phased_x_gate) + print_summary(f"ZZPhase({theta})", zz_phase_gate) + print_summary(f"PhasedXX({theta}, {phi})", phased_xx_gate) diff --git a/selene-ext/simulators/quest/rust/lib.rs b/selene-ext/simulators/quest/rust/lib.rs index 0cd20ae3..83fd5cd5 100644 --- a/selene-ext/simulators/quest/rust/lib.rs +++ b/selene-ext/simulators/quest/rust/lib.rs @@ -12,7 +12,7 @@ use anyhow::{Result, anyhow, bail}; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -100,23 +100,26 @@ impl SimulatorInterface for QuestSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, @@ -157,14 +160,14 @@ impl QuestSimulator { } } - fn rxy(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { + fn phased_x(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { if q0 >= self.n_qubits { Err(anyhow!( - "RXY(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } else { - // As provided in the accompanying gate_definitions.py, here is the matrix for RXY: + // As provided in the accompanying gate_definitions.py, here is the matrix for PhasedX: // Real part: // @@ -202,14 +205,14 @@ impl QuestSimulator { } } - fn rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { if q0 >= self.n_qubits || q1 >= self.n_qubits { Err(anyhow!( - "RZZ(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } else { - // As provided in the accompanying gate_definitions.py, here is the matrix for RZZ, + // As provided in the accompanying gate_definitions.py, here is the matrix for ZZPhase, // after scaling out a global phase of exp(i*theta/2) for consistency with prior versions: // // Real part: @@ -249,14 +252,14 @@ impl QuestSimulator { } } - fn rpp(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { + fn phased_xx(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { if q0 >= self.n_qubits { Err(anyhow!( - "RPP(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )) } else { - // As provided in the accompanying gate_definitions.py, here is the matrix for RPP: + // As provided in the accompanying gate_definitions.py, here is the matrix for PhasedXX: // // Real part: // diff --git a/selene-ext/simulators/stim/python/gate_definitions.py b/selene-ext/simulators/stim/python/gate_definitions.py index 306346ca..b6689596 100644 --- a/selene-ext/simulators/stim/python/gate_definitions.py +++ b/selene-ext/simulators/stim/python/gate_definitions.py @@ -25,19 +25,19 @@ def rz_gate(theta) -> Circuit: return circ -def rxy_gate(theta, phi) -> Circuit: +def phased_x_gate(theta, phi) -> Circuit: circ = Circuit(1) circ.PhasedX(theta, phi, 0) return circ -def rzz_gate(theta) -> Circuit: +def zz_phase_gate(theta) -> Circuit: circ = Circuit(2) circ.ZZPhase(theta, 0, 1) return circ -def rpp_gate(theta, phi) -> Circuit: +def phased_xx_gate(theta, phi) -> Circuit: circ = Circuit(2) circ.Rz(phi, 0) circ.Rz(phi, 1) @@ -76,6 +76,6 @@ def analyse_span(name, gate, start_angle, end_angle, num_points, dim=1) -> None: if __name__ == "__main__": analyse_span("rz", rz_gate, 0, 2, 16) - analyse_span("rxy", rxy_gate, 0, 2, 16, dim=2) - analyse_span("rzz", rzz_gate, 0, 2, 16, dim=1) - analyse_span("rpp", rpp_gate, 0, 2, 16, dim=2) + analyse_span("PhasedX", phased_x_gate, 0, 2, 16, dim=2) + analyse_span("ZZPhase", zz_phase_gate, 0, 2, 16, dim=1) + analyse_span("PhasedXX", phased_xx_gate, 0, 2, 16, dim=2) diff --git a/selene-ext/simulators/stim/rust/lib.rs b/selene-ext/simulators/stim/rust/lib.rs index 51617120..5e1c8abc 100644 --- a/selene-ext/simulators/stim/rust/lib.rs +++ b/selene-ext/simulators/stim/rust/lib.rs @@ -9,7 +9,7 @@ use clap::Parser; use num_enum::{IntoPrimitive, TryFromPrimitive}; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, Operation}; +use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -95,23 +95,26 @@ impl SimulatorInterface for StimSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::RXYGate { - qubit_id, - theta, - phi, - } => self.rxy(qubit_id, theta, phi)?, - Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => self.rzz(qubit_id_1, qubit_id_2, theta)?, - Operation::RZGate { qubit_id, theta } => self.rz(qubit_id, theta)?, - Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => self.rpp(qubit_id_1, qubit_id_2, theta, phi)?, + Operation::Gate { .. } => match operation.as_builtin_gate()? { + Some(BuiltinGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(BuiltinGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, + Some(BuiltinGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + }, Operation::Measure { qubit_id, result_id, @@ -141,8 +144,8 @@ impl SimulatorInterface for StimSimulator { } impl StimSimulator { - fn rxy(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { - // We can represent the rxy gate as a sequence of clifford + fn phased_x(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { + // We can represent the PhasedX gate as a sequence of clifford // operations for certain combinations of theta and phi. These // are written in application order (e.g. H X means apply H then X), // not multiplication order. @@ -176,7 +179,7 @@ impl StimSimulator { // if q0 >= self.n_qubits { return Err(anyhow!( - "RXY(q0={q0}, theta={theta}, phi={phi}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedX(q0={q0}, theta={theta}, phi={phi}) is out of bounds. q0 must be less than the number of qubits ({}).", self.n_qubits )); } @@ -184,7 +187,7 @@ impl StimSimulator { let q0_u32: u32 = q0.try_into().unwrap(); let Some(approx_theta) = self.get_approximate_quadrant(theta) else { return Err(anyhow!( - "RXY(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." + "PhasedX(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." )); }; if approx_theta == Quadrant::Zero { @@ -195,7 +198,7 @@ impl StimSimulator { let Some(approx_phi) = self.get_approximate_octant(phi) else { return Err(anyhow!( - "RXY(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is nonzero, phi must be an (approximate) multiple of pi/4 for Clifford operations." + "PhasedX(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is nonzero, phi must be an (approximate) multiple of pi/4 for Clifford operations." )); }; @@ -227,7 +230,7 @@ impl StimSimulator { Ok(()) } (Quadrant::FracPi2, _) => Err(anyhow!( - "RXY(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is pi/2, phi must be an (approximate) multiple of pi/2." + "PhasedX(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is pi/2, phi must be an (approximate) multiple of pi/2." )), //////////////////////////////////// @@ -280,7 +283,7 @@ impl StimSimulator { Ok(()) } (Quadrant::Frac3Pi2, _) => Err(anyhow!( - "RXY(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is 3pi/2, phi must be an (approximate) multiple of pi/2." + "PhasedX(q0={q0}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is 3pi/2, phi must be an (approximate) multiple of pi/2." )), } } @@ -323,7 +326,7 @@ impl StimSimulator { Ok(()) } - fn rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { + fn zz_phase(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { // Stim provides SQRT_ZZ and SQRT_ZZ_DAG gates which are // invoked for theta values pi/2 and 3pi/2 respectively. // For theta=pi, we can simply apply a Z gate to both qubits. @@ -331,7 +334,7 @@ impl StimSimulator { if q0 >= self.n_qubits || q1 >= self.n_qubits { return Err(anyhow!( - "RZZ(q0={q0}, q1={q1}, theta={theta}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "ZZPhase(q0={q0}, q1={q1}, theta={theta}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )); } @@ -340,7 +343,7 @@ impl StimSimulator { let q1_u32: u32 = q1.try_into().unwrap(); let Some(approx) = self.get_approximate_quadrant(theta) else { return Err(anyhow!( - "RZZ(q0={q0}, q1={q1}, theta={theta}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." + "ZZPhase(q0={q0}, q1={q1}, theta={theta}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." )); }; @@ -356,8 +359,8 @@ impl StimSimulator { Ok(()) } - fn rpp(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { - // We can represent the rpp gate as a sequence of clifford + fn phased_xx(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { + // We can represent the PhasedXX gate as a sequence of clifford // operations for certain combinations of theta and phi: // // +==========================================================+ @@ -389,7 +392,7 @@ impl StimSimulator { if q0 >= self.n_qubits || q1 >= self.n_qubits { return Err(anyhow!( - "RPP(q0={q0}, q1={q1}, theta={theta}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}, theta={theta}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )); } @@ -399,12 +402,12 @@ impl StimSimulator { let Some(approx_theta) = self.get_approximate_quadrant(theta) else { return Err(anyhow!( - "RPP(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." + "PhasedXX(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. Theta must be an (approximate) multiple of pi/2 for Clifford operations." )); }; let Some(approx_phi) = self.get_approximate_octant(phi) else { return Err(anyhow!( - "RPP(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is nonzero, phi must be an (approximate) multiple of pi/4 for Clifford operations." + "PhasedXX(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is nonzero, phi must be an (approximate) multiple of pi/4 for Clifford operations." )); }; @@ -433,7 +436,7 @@ impl StimSimulator { } (Quadrant::FracPi2, _) => { return Err(anyhow!( - "RPP(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is pi/2, phi must be an (approximate) multiple of pi/2." + "PhasedXX(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is pi/2, phi must be an (approximate) multiple of pi/2." )); } // pi theta, quadrant phi section @@ -483,7 +486,7 @@ impl StimSimulator { } (Quadrant::Frac3Pi2, _) => { return Err(anyhow!( - "RPP(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is 3pi/2, phi must be an (approximate) multiple of pi/2." + "PhasedXX(q0={q0}, q1={q1}, theta={theta}, phi={phi}) is not representable in stabiliser form. When theta is 3pi/2, phi must be an (approximate) multiple of pi/2." )); } } diff --git a/selene-ext/utilities/argreader/Cargo.lock b/selene-ext/utilities/argreader/Cargo.lock index a024be52..4e3b83e7 100644 --- a/selene-ext/utilities/argreader/Cargo.lock +++ b/selene-ext/utilities/argreader/Cargo.lock @@ -8,12 +8,78 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "delegate" version = "0.13.5" @@ -40,10 +106,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", "rustc_version", "syn", + "unicode-xid", ] [[package]] @@ -52,6 +120,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "hashbrown" version = "0.17.0" @@ -74,6 +148,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + [[package]] name = "libloading" version = "0.8.9" @@ -138,9 +218,14 @@ name = "selene-core" version = "0.3.0-alpha.1" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] @@ -204,6 +289,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.117" @@ -241,6 +344,18 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "version_check" version = "0.9.5" diff --git a/selene-sim/c/CMakeLists.txt b/selene-sim/c/CMakeLists.txt index 171d78b2..a8449b20 100644 --- a/selene-sim/c/CMakeLists.txt +++ b/selene-sim/c/CMakeLists.txt @@ -27,6 +27,9 @@ target_link_libraries(Selene INTERFACE install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../../selene-core/c/include/selene/gatewire.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/selene +) # Install the interface target (this now works!) install( diff --git a/selene-sim/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index c6b8b3d4..6cb0dc93 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -6,6 +6,7 @@ #include #include #include +#include "selene/gatewire.h" typedef struct SeleneInstance SeleneInstance; @@ -88,6 +89,10 @@ struct selene_bool_result_t selene_future_read_bool(struct SeleneInstance *insta */ struct selene_u64_result_t selene_future_read_u64(struct SeleneInstance *instance, uint64_t r); +struct selene_void_result_t selene_gate(struct SeleneInstance *instance, + const uint8_t *data, + size_t data_len); + struct selene_u64_result_t selene_get_current_shot(struct SeleneInstance *instance); struct selene_u64_result_t selene_get_tc(struct SeleneInstance *instance); @@ -253,25 +258,12 @@ struct selene_void_result_t selene_refcount_decrement(struct SeleneInstance *ins */ struct selene_void_result_t selene_refcount_increment(struct SeleneInstance *instance, uint64_t r); -struct selene_void_result_t selene_rpp(struct SeleneInstance *instance, - uint64_t qubit_id, - uint64_t qubit_id2, - double theta, - double phi); - -struct selene_void_result_t selene_rxy(struct SeleneInstance *instance, - uint64_t qubit_id, - double theta, - double phi); - -struct selene_void_result_t selene_rz(struct SeleneInstance *instance, - uint64_t qubit_id, - double theta); - -struct selene_void_result_t selene_rzz(struct SeleneInstance *instance, - uint64_t qubit_id, - uint64_t qubit_id2, - double theta); +struct selene_void_result_t selene_register_gateset(struct SeleneInstance *instance, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); struct selene_void_result_t selene_set_tc(struct SeleneInstance *instance, uint64_t tc); diff --git a/selene-sim/cbindgen.toml b/selene-sim/cbindgen.toml index 8481a86e..50edf3f5 100644 --- a/selene-sim/cbindgen.toml +++ b/selene-sim/cbindgen.toml @@ -22,7 +22,7 @@ include_version = false namespaces = [] using_namespaces = [] sys_includes = [] -includes = [] +includes = ["selene/gatewire.h"] no_includes = false # cpp_compat = true after_includes = "" diff --git a/selene-sim/python/selene_sim/README.md b/selene-sim/python/selene_sim/README.md index dcc8c1dd..51eb112c 100644 --- a/selene-sim/python/selene_sim/README.md +++ b/selene-sim/python/selene_sim/README.md @@ -24,7 +24,7 @@ Error models that are currently provided include: And we offer two example quantum runtimes, including: - Simple, which executes the program as-is, without any modifications -- SoftRZ, which elides Z rotations through RXY gates, providing the same observable behaviour with fewer quantum operations +- SoftRZ, which elides Z rotations through PhasedX gates, providing the same observable behaviour with fewer quantum operations ## Usage example @@ -111,7 +111,7 @@ shots = QsysResult(runner.run_shots( print(shots) ``` -- And/or a runtime, such as the SoftRZRuntime, which elides physical RZ gates through subsequent RXY gates: +- And/or a runtime, such as the SoftRZRuntime, which elides physical RZ gates through subsequent PhasedX gates: ```python from selene_sim import SoftRZRuntime diff --git a/selene-sim/python/selene_sim/event_hooks/instruction_log.py b/selene-sim/python/selene_sim/event_hooks/instruction_log.py index cc3fea7d..ef9a0f45 100644 --- a/selene-sim/python/selene_sim/event_hooks/instruction_log.py +++ b/selene-sim/python/selene_sim/event_hooks/instruction_log.py @@ -16,6 +16,7 @@ from typing import Any import math +from selene_core import Gate, PhasedX, PhasedXX, RZ, ZZPhase from selene_core.trace import ( Trace, GateEvent, @@ -165,80 +166,95 @@ def from_iterator(it: Iterator): @dataclass -class Rxy(Operation): - qubit: int - theta: float - phi: float - - def append_to_circuit(self, circuit: "pytket.Circuit"): - assert PYTKET_AVAILABLE, "pytket is not available" - circuit.PhasedX( - angle0=self.theta / math.pi, angle1=self.phi / math.pi, qubit=self.qubit - ) - - def to_dict(self) -> dict: - return {"op": "Rxy", "qubit": self.qubit, "theta": self.theta, "phi": self.phi} - - @staticmethod - def from_iterator(it: Iterator): - qubit = next(it) - theta = next(it) - phi = next(it) - assert isinstance(qubit, int), ( - f"qubit must be an integer, got {qubit} of type {type(qubit)}" - ) - assert isinstance(theta, float), ( - f"theta must be a float, got {theta} of type {type(theta)}" - ) - assert isinstance(phi, float), ( - f"phi must be a float, got {phi} of type {type(phi)}" - ) - return Rxy(qubit=qubit, theta=theta, phi=phi) - - -@dataclass -class Rzz(Operation): - qubit0: int - qubit1: int - theta: float +class GateInstruction(Operation): + gate: Gate def append_to_circuit(self, circuit: "pytket.Circuit"): assert PYTKET_AVAILABLE, "pytket is not available" - circuit.ZZPhase( - angle=self.theta / math.pi, qubit0=self.qubit0, qubit1=self.qubit1 - ) + if self.gate.semantic_id == RZ.semantic_id: + q0, theta = self._values() + q0 = int(q0) + theta = float(theta) + circuit.Rz(angle=theta / math.pi, qubit=q0) + elif self.gate.semantic_id == PhasedX.semantic_id: + q0, theta, phi = self._values() + q0 = int(q0) + theta = float(theta) + phi = float(phi) + circuit.PhasedX(angle0=theta / math.pi, angle1=phi / math.pi, qubit=q0) + elif self.gate.semantic_id == ZZPhase.semantic_id: + q0, q1, theta = self._values() + q0 = int(q0) + q1 = int(q1) + theta = float(theta) + circuit.ZZPhase(angle=theta / math.pi, qubit0=q0, qubit1=q1) + elif self.gate.semantic_id == PhasedXX.semantic_id: + q0, q1, theta, phi = self._values() + q0 = int(q0) + q1 = int(q1) + theta = float(theta) + phi = float(phi) + circuit.PhasedXX( + angle0=theta / math.pi, + angle1=phi / math.pi, + qubit0=q0, + qubit1=q1, + ) def to_dict(self) -> dict: return { - "op": "Rzz", - "qubit0": self.qubit0, - "qubit1": self.qubit1, - "theta": self.theta, + "op": "Gate", + "gate": self.gate_name(), + "qubits": self.qubits(), + "params": self.params(), } - @staticmethod - def from_iterator(it: Iterator): - qubit0 = next(it) - qubit1 = next(it) - theta = next(it) - return Rzz(qubit0=qubit0, qubit1=qubit1, theta=theta) - - -@dataclass -class Rz(Operation): - qubit: int - theta: float - - def append_to_circuit(self, circuit: "pytket.Circuit"): - assert PYTKET_AVAILABLE, "pytket is not available" - circuit.Rz(angle=self.theta / math.pi, qubit=self.qubit) - - def to_dict(self) -> dict: - return {"op": "Rz", "qubit": self.qubit, "theta": self.theta} + def to_trace_event(self) -> GateEvent: + return GateEvent( + gate_name=self.gate_name(), + qubits=self.qubits(), + params=self.params(), + ) @staticmethod def from_iterator(it: Iterator): - return Rz(qubit=next(it), theta=next(it)) + return GateInstruction(Gate.deserialize(bytes(next(it)))) + + def gate_name(self) -> str: + if self.gate.semantic_id == RZ.semantic_id: + return "RZ" + if self.gate.semantic_id == PhasedX.semantic_id: + return "PhasedX" + if self.gate.semantic_id == ZZPhase.semantic_id: + return "ZZPhase" + if self.gate.semantic_id == PhasedXX.semantic_id: + return "PhasedXX" + return self.gate.semantic_id.hex() + + def _values(self) -> list[int | float | bool]: + return [operand.value for operand in self.gate.operands] + + def qubits(self) -> list[int]: + if self.gate.semantic_id in {RZ.semantic_id, PhasedX.semantic_id}: + return [int(self.gate.operands[0].value)] + if self.gate.semantic_id in {ZZPhase.semantic_id, PhasedXX.semantic_id}: + return [int(self.gate.operands[0].value), int(self.gate.operands[1].value)] + return [ + int(operand.value) + for operand in self.gate.operands + if operand.kind.name == "QUBIT" + ] + + def params(self) -> list[int | float | bool]: + if self.gate.semantic_id in {RZ.semantic_id, PhasedX.semantic_id}: + return [operand.value for operand in self.gate.operands[1:]] + if self.gate.semantic_id in {ZZPhase.semantic_id, PhasedXX.semantic_id}: + return [operand.value for operand in self.gate.operands[2:]] + return [ + operand.value + for operand in self.gate.operands + if operand.kind.name != "QUBIT" + ] @dataclass @@ -326,40 +342,6 @@ def from_iterator(it: Iterator): return ClassicalDelay(duration_ns=duration_ns) -@dataclass -class Rpp(Operation): - qubit0: int - qubit1: int - theta: float - phi: float - - def append_to_circuit(self, circuit: "pytket.Circuit"): - assert PYTKET_AVAILABLE, "pytket is not available" - circuit.PhasedXX( - angle0=self.theta / math.pi, - angle1=self.phi / math.pi, - qubit0=self.qubit0, - qubit1=self.qubit1, - ) - - def to_dict(self) -> dict: - return { - "op": "Rpp", - "qubit0": self.qubit0, - "qubit1": self.qubit1, - "theta": self.theta, - "phi": self.phi, - } - - @staticmethod - def from_iterator(it: Iterator): - qubit0 = next(it) - qubit1 = next(it) - theta = next(it) - phi = next(it) - return Rpp(qubit0=qubit0, qubit1=qubit1, theta=theta, phi=phi) - - @dataclass class Postselect(Operation): qubit: int @@ -421,8 +403,8 @@ def from_iterator(it: Iterator): For example: - a QAlloc operation would have a data array of length 1, containing the qubit index to allocate. - - An Rzz would have a data array of length 3, containing - the two qubit indices and the rotation angle. + - a Gate operation would have a single serialized gatewire + payload containing the gate semantic ID and operands. The parsing of the data array is delegated to the operation class itself, and it is responsible for advancing the iterator @@ -446,12 +428,6 @@ class itself, and it is responsible for advancing the iterator operation = MeasureRequest.from_iterator(it) case 5: operation = FutureRead.from_iterator(it) - case 6: - operation = Rxy.from_iterator(it) - case 7: - operation = Rz.from_iterator(it) - case 8: - operation = Rzz.from_iterator(it) case 9: operation = CustomOperation.from_iterator(it) case 10: @@ -462,9 +438,9 @@ class itself, and it is responsible for advancing the iterator operation = MeasureLeakedRequest.from_iterator(it) case 13: operation = ClassicalDelay.from_iterator(it) - case 14: - operation = Rpp.from_iterator(it) case 15: + operation = GateInstruction.from_iterator(it) + case 16: operation = Postselect.from_iterator(it) if operation is None: raise ValueError(f"Unknown instruction operation index {operation_idx}") @@ -596,43 +572,9 @@ def get_trace(self) -> Trace: index=user_program_event_index, ) user_program_event_index += 1 - case Rxy(qubit=qubit, theta=theta, phi=phi): - trace.add_user_program_event( - GateEvent( - gate_name="Rxy", - qubits=[qubit], - params=[theta, phi], - ), - index=user_program_event_index, - ) - user_program_event_index += 1 - case Rzz(qubit0=qubit0, qubit1=qubit1, theta=theta): + case GateInstruction() as gate: trace.add_user_program_event( - GateEvent( - gate_name="Rzz", - qubits=[qubit0, qubit1], - params=[theta], - ), - index=user_program_event_index, - ) - user_program_event_index += 1 - case Rz(qubit=qubit, theta=theta): - trace.add_user_program_event( - GateEvent( - gate_name="Rz", - qubits=[qubit], - params=[theta], - ), - index=user_program_event_index, - ) - user_program_event_index += 1 - case Rpp(qubit0=qubit0, qubit1=qubit1, theta=theta, phi=phi): - trace.add_user_program_event( - GateEvent( - gate_name="Rpp", - qubits=[qubit0, qubit1], - params=[theta, phi], - ), + gate.to_trace_event(), index=user_program_event_index, ) user_program_event_index += 1 @@ -682,43 +624,9 @@ def get_trace(self) -> Trace: trace.add_runtime_event( MeasurementEvent(qubit=qubit), start_time_ns, end_time_ns ) - case Rxy(qubit=qubit, theta=theta, phi=phi): + case GateInstruction() as gate: trace.add_runtime_event( - GateEvent( - gate_name="Rxy", - qubits=[qubit], - params=[theta, phi], - ), - start_time_ns, - end_time_ns, - ) - case Rz(qubit=qubit, theta=theta): - trace.add_runtime_event( - GateEvent( - gate_name="Rz", - qubits=[qubit], - params=[theta], - ), - start_time_ns, - end_time_ns, - ) - case Rzz(qubit0=qubit0, qubit1=qubit1, theta=theta): - trace.add_runtime_event( - GateEvent( - gate_name="Rzz", - qubits=[qubit0, qubit1], - params=[theta], - ), - start_time_ns, - end_time_ns, - ) - case Rpp(qubit0=qubit0, qubit1=qubit1, theta=theta, phi=phi): - trace.add_runtime_event( - GateEvent( - gate_name="Rpp", - qubits=[qubit0, qubit1], - params=[theta, phi], - ), + gate.to_trace_event(), start_time_ns, end_time_ns, ) @@ -755,43 +663,9 @@ def get_trace(self) -> Trace: index=error_model_event_index, ) error_model_event_index += 1 - case Rxy(qubit=qubit, theta=theta, phi=phi): + case GateInstruction() as gate: trace.add_error_model_event( - GateEvent( - gate_name="Rxy", - qubits=[qubit], - params=[theta, phi], - ), - index=error_model_event_index, - ) - error_model_event_index += 1 - case Rz(qubit=qubit, theta=theta): - trace.add_error_model_event( - GateEvent( - gate_name="Rz", - qubits=[qubit], - params=[theta], - ), - index=error_model_event_index, - ) - error_model_event_index += 1 - case Rzz(qubit0=qubit0, qubit1=qubit1, theta=theta): - trace.add_error_model_event( - GateEvent( - gate_name="Rzz", - qubits=[qubit0, qubit1], - params=[theta], - ), - index=error_model_event_index, - ) - error_model_event_index += 1 - case Rpp(qubit0=qubit0, qubit1=qubit1, theta=theta, phi=phi): - trace.add_error_model_event( - GateEvent( - gate_name="Rpp", - qubits=[qubit0, qubit1], - params=[theta, phi], - ), + gate.to_trace_event(), index=error_model_event_index, ) error_model_event_index += 1 @@ -844,46 +718,9 @@ def get_trace(self) -> Trace: duration_ns=duration_ns, ) simulator_event_index += 1 - case Rxy(qubit=qubit, theta=theta, phi=phi): + case GateInstruction() as gate: trace.add_simulator_event( - GateEvent( - gate_name="Rxy", - qubits=[qubit], - params=[theta, phi], - ), - index=simulator_event_index, - duration_ns=duration_ns, - ) - simulator_event_index += 1 - case Rz(qubit=qubit, theta=theta): - trace.add_simulator_event( - GateEvent( - gate_name="Rz", - qubits=[qubit], - params=[theta], - ), - index=simulator_event_index, - duration_ns=duration_ns, - ) - simulator_event_index += 1 - case Rzz(qubit0=qubit0, qubit1=qubit1, theta=theta): - trace.add_simulator_event( - GateEvent( - gate_name="Rzz", - qubits=[qubit0, qubit1], - params=[theta], - ), - index=simulator_event_index, - duration_ns=duration_ns, - ) - simulator_event_index += 1 - case Rpp(qubit0=qubit0, qubit1=qubit1, theta=theta, phi=phi): - trace.add_simulator_event( - GateEvent( - gate_name="Rpp", - qubits=[qubit0, qubit1], - params=[theta, phi], - ), + gate.to_trace_event(), index=simulator_event_index, duration_ns=duration_ns, ) diff --git a/selene-sim/python/selene_sim/interactive/_library.py b/selene-sim/python/selene_sim/interactive/_library.py new file mode 100644 index 00000000..6d1b1073 --- /dev/null +++ b/selene-sim/python/selene_sim/interactive/_library.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import ctypes +import platform +import sys +from functools import cache +from pathlib import Path + +from selene_sim import dist_dir as selene_dist + + +def selene_library_path() -> Path: + lib_path = selene_dist / "lib" + match platform.system(): + case "Darwin": + lib_path /= "libselene.dylib" + case "Linux": + lib_path /= "libselene.so" + case "Windows": + lib_path /= "selene.dll" + case _: + raise RuntimeError(f"Unsupported OS {sys.platform}") + if not lib_path.is_file(): + raise FileNotFoundError(f"Selene library not found at {lib_path}") + return lib_path + + +@cache +def load_selene_global() -> ctypes.CDLL: + return ctypes.CDLL(str(selene_library_path()), mode=ctypes.RTLD_GLOBAL) diff --git a/selene-sim/python/selene_sim/interactive/full_stack.py b/selene-sim/python/selene_sim/interactive/full_stack.py index e54c2ed6..44cb8b60 100644 --- a/selene-sim/python/selene_sim/interactive/full_stack.py +++ b/selene-sim/python/selene_sim/interactive/full_stack.py @@ -2,14 +2,19 @@ import ctypes import os -import platform -import sys import tempfile from pathlib import Path from typing import ClassVar import yaml -from selene_core import ErrorModel, Runtime, SeleneComponent, Simulator +from selene_core import ( + ErrorModel, + Gate, + Gateset, + Runtime, + SeleneComponent, + Simulator, +) from selene_sim import dist_dir as selene_dist from selene_sim.backends import IdealErrorModel, SimpleRuntime @@ -18,6 +23,8 @@ from selene_sim.result_handling import DataStream, ResultStream from selene_sim.result_handling.result_stream import StreamEntry +from ._library import selene_library_path + PathLike = str | os.PathLike | bytes | bytearray @@ -161,18 +168,7 @@ def _uint8_buffer( class SeleneSimLib(ctypes.CDLL): def __init__(self) -> None: - lib_path = selene_dist / "lib" - match platform.system(): - case "Darwin": - lib_path /= "libselene.dylib" - case "Linux": - lib_path /= "libselene.so" - case "Windows": - lib_path /= "selene.dll" - case _: - raise RuntimeError(f"Unsupported OS {sys.platform}") - assert lib_path.is_file(), f"Selene library not found at {lib_path}" - super().__init__(str(lib_path)) + super().__init__(str(selene_library_path()), mode=ctypes.RTLD_GLOBAL) self._configure_signatures() def _configure_signatures(self): @@ -235,22 +231,6 @@ def _configure_signatures(self): self.selene_refcount_decrement.restype = selene_void_result_t self.selene_refcount_increment.argtypes = [SeleneInstancePtr, ctypes.c_uint64] self.selene_refcount_increment.restype = selene_void_result_t - self.selene_rxy.argtypes = [ - SeleneInstancePtr, - ctypes.c_uint64, - ctypes.c_double, - ctypes.c_double, - ] - self.selene_rxy.restype = selene_void_result_t - self.selene_rz.argtypes = [SeleneInstancePtr, ctypes.c_uint64, ctypes.c_double] - self.selene_rz.restype = selene_void_result_t - self.selene_rzz.argtypes = [ - SeleneInstancePtr, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, - ] - self.selene_rzz.restype = selene_void_result_t self.selene_set_tc.argtypes = [SeleneInstancePtr, ctypes.c_uint64] self.selene_set_tc.restype = selene_void_result_t self.selene_shot_count.argtypes = [SeleneInstancePtr] @@ -270,6 +250,17 @@ def _configure_signatures(self): ctypes.c_uint64, ] self.selene_fetch_output.restype = selene_u64_result_t + self.selene_gate.argtypes = [SeleneInstancePtr, BytePtr, ctypes.c_size_t] + self.selene_gate.restype = selene_void_result_t + self.selene_register_gateset.argtypes = [ + SeleneInstancePtr, + BytePtr, + ctypes.c_size_t, + BytePtr, + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_size_t), + ] + self.selene_register_gateset.restype = selene_void_result_t class InternalOutputStream(DataStream): @@ -321,6 +312,7 @@ def __init__( error_model: ErrorModel | None = None, event_hook: EventHook | None = None, random_seed: int | None = None, + gateset: Gateset | None = None, ): self._lib = self.load_library() self._instance = SeleneInstancePtr() @@ -340,6 +332,8 @@ def __init__( self.simulator = simulator self.runtime = runtime or SimpleRuntime() self.error_model = error_model or IdealErrorModel() + self.gateset = gateset + self.emitted_gateset: Gateset | None = None try: config_data = self._build_configuration( @@ -365,12 +359,13 @@ def __init__( self._config_path.write_text(yaml.safe_dump(config_data)) instance_ptr = SeleneInstancePtr() - print(type(instance_ptr)) config_bytes = _encode_text(self._config_path) self._lib.selene_load_config( ctypes.byref(instance_ptr), ctypes.c_char_p(config_bytes) ).unwrap() self._instance = instance_ptr + if self.gateset is not None: + self.emitted_gateset = self.register_gateset(self.gateset) except Exception: self._teardown_environment() raise @@ -544,14 +539,40 @@ def refcount_decrement(self, reference: int) -> None: def refcount_increment(self, reference: int) -> None: self._call_void("selene_refcount_increment", reference) - def rxy(self, qubit: Qubit, theta: float, phi: float) -> None: - self._call_void("selene_rxy", qubit.id, theta, phi) - - def rz(self, qubit: Qubit, theta: float) -> None: - self._call_void("selene_rz", qubit.id, theta) - - def rzz(self, qubit_a: Qubit, qubit_b: Qubit, theta: float) -> None: - self._call_void("selene_rzz", qubit_a.id, qubit_b.id, theta) + def register_gateset(self, gateset: Gateset) -> Gateset: + payload = gateset.serialize() + input_buffer = (ctypes.c_uint8 * len(payload))(*payload) + input_ptr = ctypes.cast(input_buffer, BytePtr) + written = ctypes.c_size_t() + self._lib.selene_register_gateset( + self._instance, + input_ptr, + len(payload), + None, + 0, + ctypes.byref(written), + ).unwrap() + output_buffer = (ctypes.c_uint8 * written.value)() + output_ptr = ctypes.cast(output_buffer, BytePtr) + self._lib.selene_register_gateset( + self._instance, + input_ptr, + len(payload), + output_ptr, + written.value, + ctypes.byref(written), + ).unwrap() + return Gateset.deserialize(bytes(output_buffer[: written.value])) + + def gate(self, gate: Gate) -> None: + payload = gate.serialize() + buffer = (ctypes.c_uint8 * len(payload))(*payload) + self._lib.selene_gate( + self._instance, ctypes.cast(buffer, BytePtr), len(payload) + ).unwrap() + if self._auto_poll_metadata: + self._lib.selene_write_metadata(self._instance) + self._poll_results() def get_state(self, qubits: list[Qubit]): if not hasattr(self.simulator, "extract_states"): diff --git a/selene-sim/python/selene_sim/interactive/runtime.py b/selene-sim/python/selene_sim/interactive/runtime.py index cd401a2b..4010ed70 100644 --- a/selene-sim/python/selene_sim/interactive/runtime.py +++ b/selene-sim/python/selene_sim/interactive/runtime.py @@ -3,9 +3,11 @@ from dataclasses import dataclass import ctypes -from selene_core import Runtime +from selene_core import Gate, Gateset, Runtime import random +from ._library import load_selene_global + class SeleneRuntimeInstance(ctypes.Structure): pass @@ -13,30 +15,11 @@ class SeleneRuntimeInstance(ctypes.Structure): SeleneRuntimeGetOperationInstance = ctypes.c_void_p -RZZ_CB = ctypes.CFUNCTYPE( - None, - SeleneRuntimeGetOperationInstance, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, -) -RXY_CB = ctypes.CFUNCTYPE( +GATE_CB = ctypes.CFUNCTYPE( None, SeleneRuntimeGetOperationInstance, - ctypes.c_uint64, - ctypes.c_double, - ctypes.c_double, -) -RZ_CB = ctypes.CFUNCTYPE( - None, SeleneRuntimeGetOperationInstance, ctypes.c_uint64, ctypes.c_double -) -RPP_CB = ctypes.CFUNCTYPE( - None, - SeleneRuntimeGetOperationInstance, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, - ctypes.c_double, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, ) MEASURE_CB = ctypes.CFUNCTYPE( @@ -65,10 +48,7 @@ class SeleneRuntimeGetOperationInterface(ctypes.Structure): ("reset_fn", RESET_CB), ("custom_fn", CUSTOM_CB), ("set_batch_time_fn", SET_BATCH_TIME_CB), - ("rzz_fn", RZZ_CB), - ("rxy_fn", RXY_CB), - ("rz_fn", RZ_CB), - ("rpp_fn", RPP_CB), + ("gate_fn", GATE_CB), ] @@ -91,31 +71,8 @@ class ResetOperation: @dataclass -class RXYGateOperation: - qubit_id: int - theta: float - phi: float - - -@dataclass -class RZGateOperation: - qubit_id: int - theta: float - - -@dataclass -class RZZGateOperation: - qubit_id_a: int - qubit_id_b: int - theta: float - - -@dataclass -class RPPGateOperation: - qubit_id_a: int - qubit_id_b: int - theta: float - phi: float +class GateOperation: + gate: Gate @dataclass @@ -133,10 +90,7 @@ class MeasureLeakedOperation: RuntimeOperation = ( MeasureOperation | ResetOperation - | RXYGateOperation - | RZGateOperation - | RZZGateOperation - | RPPGateOperation + | GateOperation | CustomOperation | MeasureLeakedOperation ) @@ -154,16 +108,8 @@ def set_time(self, start_time_nanos: int, duration_nanos: int): self.duration_nanos = duration_nanos self.invoked = True - def rxy(self, qubit_id: int, theta: float, phi: float): - self.operations.append(RXYGateOperation(qubit_id, theta, phi)) - self.invoked = True - - def rz(self, qubit_id: int, theta: float): - self.operations.append(RZGateOperation(qubit_id, theta)) - self.invoked = True - - def rzz(self, qubit_id_a: int, qubit_id_b: int, theta: float): - self.operations.append(RZZGateOperation(qubit_id_a, qubit_id_b, theta)) + def gate(self, gate: Gate): + self.operations.append(GateOperation(gate)) self.invoked = True def measure(self, qubit_id: int, result_id: int): @@ -190,25 +136,14 @@ def from_ptr(ptr: SeleneRuntimeGetOperationInstance) -> OperationBatch: return ctypes.cast(ptr, ctypes.POINTER(ctypes.py_object)).contents.value -def callback_rxy( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, theta: float, phi: float -): - OperationBatch.from_ptr(instance).rxy(qubit_id, theta, phi) - - -def callback_rz( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, theta: float -): - OperationBatch.from_ptr(instance).rz(qubit_id, theta) - - -def callback_rzz( +def callback_gate( instance: SeleneRuntimeGetOperationInstance, - qubit_id_a: int, - qubit_id_b: int, - theta: float, + data_ptr: ctypes.c_void_p, + data_len: int, ): - OperationBatch.from_ptr(instance).rzz(qubit_id_a, qubit_id_b, theta) + OperationBatch.from_ptr(instance).gate( + Gate.deserialize(ctypes.string_at(data_ptr, data_len)) + ) def callback_measure( @@ -244,9 +179,7 @@ def callback_set_batch_time( OPERATION_BATCH_CALLBACKS = SeleneRuntimeGetOperationInterface( - rzz_fn=RZZ_CB(callback_rzz), - rxy_fn=RXY_CB(callback_rxy), - rz_fn=RZ_CB(callback_rz), + gate_fn=GATE_CB(callback_gate), measure_fn=MEASURE_CB(callback_measure), measure_leaked_fn=MEASURE_LEAKED_CB(callback_measure_leaked), reset_fn=RESET_CB(callback_reset), @@ -280,19 +213,6 @@ def callback_set_batch_time( Errno, SeleneRuntimeInstancePtr, ctypes.POINTER(ctypes.c_uint64) ) RuntimeQfreeFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64) -RuntimeRxyFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.c_double, ctypes.c_double -) -RuntimeRzFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.c_double -) -RuntimeRzzFn = ctypes.CFUNCTYPE( - Errno, - SeleneRuntimeInstancePtr, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, -) RuntimeMeasureFn = ctypes.CFUNCTYPE( Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64) ) @@ -325,6 +245,18 @@ def callback_set_batch_time( ctypes.c_size_t, ctypes.POINTER(ctypes.c_uint64), ) +RuntimeGateFn = ctypes.CFUNCTYPE( + Errno, SeleneRuntimeInstancePtr, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t +) +RuntimeNegotiateGatesetFn = ctypes.CFUNCTYPE( + Errno, + SeleneRuntimeInstancePtr, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_size_t), +) class RuntimePluginDescriptorV1(ctypes.Structure): @@ -341,10 +273,6 @@ class RuntimePluginDescriptorV1(ctypes.Structure): ("qfree_fn", RuntimeQfreeFn), ("local_barrier_fn", ctypes.c_void_p), ("global_barrier_fn", ctypes.c_void_p), - ("rxy_gate_fn", RuntimeRxyFn), - ("rzz_gate_fn", RuntimeRzzFn), - ("rz_gate_fn", RuntimeRzFn), - ("rpp_gate_fn", ctypes.c_void_p), ("measure_fn", RuntimeMeasureFn), ("measure_leaked_fn", RuntimeMeasureLeakedFn), ("reset_fn", RuntimeResetFn), @@ -357,6 +285,8 @@ class RuntimePluginDescriptorV1(ctypes.Structure): ("decrement_future_refcount_fn", RuntimeDecFutureFn), ("custom_call_fn", RuntimeCustomCallFn), ("simulate_delay_fn", ctypes.c_void_p), + ("gate_fn", RuntimeGateFn), + ("negotiate_gateset_fn", RuntimeNegotiateGatesetFn), ] @@ -365,6 +295,7 @@ class RuntimePluginDescriptorV1(ctypes.Structure): class SeleneSimRuntimeLib(ctypes.CDLL): def __init__(self, runtime: Runtime) -> None: + load_selene_global() super().__init__(str(runtime.library_file)) self._configure_signatures() @@ -394,9 +325,6 @@ def _configure_signatures(self): self.selene_runtime_get_next_operations = descriptor.get_next_operations_fn self.selene_runtime_qalloc = descriptor.qalloc_fn self.selene_runtime_qfree = descriptor.qfree_fn - self.selene_runtime_rxy_gate = descriptor.rxy_gate_fn - self.selene_runtime_rz_gate = descriptor.rz_gate_fn - self.selene_runtime_rzz_gate = descriptor.rzz_gate_fn self.selene_runtime_measure = descriptor.measure_fn self.selene_runtime_measure_leaked = descriptor.measure_leaked_fn self.selene_runtime_reset = descriptor.reset_fn @@ -411,9 +339,15 @@ def _configure_signatures(self): self.selene_runtime_decrement_future_refcount = ( descriptor.decrement_future_refcount_fn ) + self.selene_runtime_gate = descriptor.gate_fn + self.selene_runtime_negotiate_gateset = descriptor.negotiate_gateset_fn if self.selene_runtime_custom_call is None: raise RuntimeError("Runtime plugin does not expose custom_call_fn") + if self.selene_runtime_gate is None: + raise RuntimeError("Runtime plugin does not expose gate_fn") + if self.selene_runtime_negotiate_gateset is None: + raise RuntimeError("Runtime plugin does not expose negotiate_gateset_fn") class InteractiveRuntime: @@ -423,12 +357,15 @@ def __init__( n_qubits: int, runtime: Runtime, start_time_nanos: int = 0, + gateset: Gateset | None = None, ): self._lib = SeleneSimRuntimeLib(runtime) self._instance = SeleneRuntimeInstancePtr() if runtime.random_seed is None: runtime.random_seed = random.randint(0, 2**64 - 1) self.runtime = runtime + self.gateset = gateset + self.emitted_gateset: Gateset | None = None self.n_qubits = n_qubits self.shot_id = 0 arguments = runtime.get_init_args() @@ -439,11 +376,33 @@ def __init__( ctypes.byref(self._instance), n_qubits, start_time_nanos, argc, argv ): raise RuntimeError("Failed to initialize Selene runtime") + if self.gateset is not None: + self.emitted_gateset = self.register_gateset(self.gateset) if 0 != self._lib.selene_runtime_shot_start( self._instance, self.shot_id, self.runtime.random_seed ): raise RuntimeError("Failed to start first shot on Selene runtime") + def register_gateset(self, gateset: Gateset) -> Gateset: + data = gateset.serialize() + input_buffer = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) + written = ctypes.c_size_t() + if 0 != self._lib.selene_runtime_negotiate_gateset( + self._instance, input_buffer, len(data), None, 0, ctypes.byref(written) + ): + raise RuntimeError("Failed to negotiate gateset with Selene runtime") + output_buffer = (ctypes.c_uint8 * written.value)() + if 0 != self._lib.selene_runtime_negotiate_gateset( + self._instance, + input_buffer, + len(data), + output_buffer, + written.value, + ctypes.byref(written), + ): + raise RuntimeError("Failed to negotiate gateset with Selene runtime") + return Gateset.deserialize(bytes(output_buffer[: written.value])) + def next_shot(self): if 0 != self._lib.selene_runtime_shot_end(self._instance): raise RuntimeError("Failed to end current shot on Selene runtime") @@ -487,19 +446,13 @@ def qfree(self, qubit_id: int): if 0 != self._lib.selene_runtime_qfree(self._instance, qubit_id): raise RuntimeError("Failed to free qubit on Selene runtime") - def rxy(self, qubit: int, theta: float, phi: float): - if 0 != self._lib.selene_runtime_rxy_gate(self._instance, qubit, theta, phi): - raise RuntimeError("Failed to apply RXY operation on Selene runtime") - - def rz(self, qubit: int, theta: float): - if 0 != self._lib.selene_runtime_rz_gate(self._instance, qubit, theta): - raise RuntimeError("Failed to apply RZ operation on Selene runtime") - - def rzz(self, qubit_a: int, qubit_b: int, theta: float): - if 0 != self._lib.selene_runtime_rzz_gate( - self._instance, qubit_a, qubit_b, theta - ): - raise RuntimeError("Failed to apply RZZ operation on Selene runtime") + def gate(self, gate: Gate): + data = gate.serialize() + input_buffer = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) + if 0 != self._lib.selene_runtime_gate(self._instance, input_buffer, len(data)): + raise RuntimeError( + "Failed to apply generic gate operation on Selene runtime" + ) def measure(self, qubit: int) -> int: future_ref = ctypes.c_uint64() diff --git a/selene-sim/python/selene_sim/interactive/simulator.py b/selene-sim/python/selene_sim/interactive/simulator.py index 7e4a971b..3f05eecb 100644 --- a/selene-sim/python/selene_sim/interactive/simulator.py +++ b/selene-sim/python/selene_sim/interactive/simulator.py @@ -3,9 +3,11 @@ import ctypes from pathlib import Path -from selene_core import Simulator +from selene_core import Gate, Gateset, Simulator import random +from ._library import load_selene_global + class SeleneSimulatorInstance(ctypes.Structure): pass @@ -32,26 +34,6 @@ class SeleneSimulatorInstance(ctypes.Structure): ctypes.c_uint64, ) SimShotEndFn = ctypes.CFUNCTYPE(Errno, SeleneSimulatorInstancePtr) -SimRxyFn = ctypes.CFUNCTYPE( - Errno, - SeleneSimulatorInstancePtr, - ctypes.c_uint64, - ctypes.c_double, - ctypes.c_double, -) -SimRzFn = ctypes.CFUNCTYPE( - Errno, - SeleneSimulatorInstancePtr, - ctypes.c_uint64, - ctypes.c_double, -) -SimRzzFn = ctypes.CFUNCTYPE( - Errno, - SeleneSimulatorInstancePtr, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, -) SimMeasureFn = ctypes.CFUNCTYPE(Errno, SeleneSimulatorInstancePtr, ctypes.c_uint64) SimPostselectFn = ctypes.CFUNCTYPE( Errno, SeleneSimulatorInstancePtr, ctypes.c_uint64, ctypes.c_bool @@ -72,6 +54,21 @@ class SeleneSimulatorInstance(ctypes.Structure): ctypes.POINTER(ctypes.c_uint64), ctypes.c_uint64, ) +SimGateFn = ctypes.CFUNCTYPE( + Errno, + SeleneSimulatorInstancePtr, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, +) +SimNegotiateGatesetFn = ctypes.CFUNCTYPE( + Errno, + SeleneSimulatorInstancePtr, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_size_t), +) class SimulatorPluginDescriptorV1(ctypes.Structure): @@ -83,15 +80,13 @@ class SimulatorPluginDescriptorV1(ctypes.Structure): ("exit_fn", SimExitFn), ("shot_start_fn", SimShotStartFn), ("shot_end_fn", SimShotEndFn), - ("rxy_fn", SimRxyFn), - ("rz_fn", SimRzFn), - ("rzz_fn", SimRzzFn), - ("rpp_fn", ctypes.c_void_p), ("measure_fn", SimMeasureFn), ("postselect_fn", SimPostselectFn), ("reset_fn", SimResetFn), ("get_metrics_fn", SimGetMetricsFn), ("dump_state_fn", SimDumpStateFn), + ("gate_fn", SimGateFn), + ("negotiate_gateset_fn", SimNegotiateGatesetFn), ] @@ -100,6 +95,7 @@ class SimulatorPluginDescriptorV1(ctypes.Structure): class SeleneSimSimulatorLib(ctypes.CDLL): def __init__(self, simulator: Simulator) -> None: + load_selene_global() super().__init__(str(simulator.library_file)) self._configure_signatures() @@ -125,25 +121,22 @@ def _configure_signatures(self): self.selene_simulator_exit = descriptor.exit_fn self.selene_simulator_shot_start = descriptor.shot_start_fn self.selene_simulator_shot_end = descriptor.shot_end_fn - self.selene_simulator_operation_rxy = descriptor.rxy_fn - self.selene_simulator_operation_rz = descriptor.rz_fn - self.selene_simulator_operation_rzz = descriptor.rzz_fn self.selene_simulator_operation_measure = descriptor.measure_fn self.selene_simulator_operation_postselect = descriptor.postselect_fn self.selene_simulator_operation_reset = descriptor.reset_fn self.selene_simulator_get_metrics = descriptor.get_metrics_fn self.selene_simulator_dump_state = descriptor.dump_state_fn + self.selene_simulator_operation_gate = descriptor.gate_fn + self.selene_simulator_negotiate_gateset = descriptor.negotiate_gateset_fn - if self.selene_simulator_operation_rxy is None: - raise RuntimeError("Simulator plugin does not expose rxy_fn") - if self.selene_simulator_operation_rz is None: - raise RuntimeError("Simulator plugin does not expose rz_fn") - if self.selene_simulator_operation_rzz is None: - raise RuntimeError("Simulator plugin does not expose rzz_fn") if self.selene_simulator_operation_postselect is None: raise RuntimeError("Simulator plugin does not expose postselect_fn") if self.selene_simulator_get_metrics is None: raise RuntimeError("Simulator plugin does not expose get_metrics_fn") + if self.selene_simulator_operation_gate is None: + raise RuntimeError("Simulator plugin does not expose gate_fn") + if self.selene_simulator_negotiate_gateset is None: + raise RuntimeError("Simulator plugin does not expose negotiate_gateset_fn") class InteractiveSimulator: @@ -152,12 +145,15 @@ def __init__( *, n_qubits: int, simulator: Simulator, + gateset: Gateset | None = None, ): self._lib = SeleneSimSimulatorLib(simulator) self._instance = SeleneSimulatorInstancePtr() if simulator.random_seed is None: simulator.random_seed = random.randint(0, 2**64 - 1) self.simulator = simulator + self.gateset = gateset + self.emitted_gateset: Gateset | None = None self.n_qubits = n_qubits self.shot_id = 0 arguments = simulator.get_init_args() @@ -168,6 +164,8 @@ def __init__( ctypes.byref(self._instance), n_qubits, argc, argv ): raise RuntimeError("Failed to initialize Selene simulator") + if self.gateset is not None: + self.emitted_gateset = self.register_gateset(self.gateset) if 0 != self._lib.selene_simulator_shot_start( self._instance, self.shot_id, self.simulator.random_seed ): @@ -181,6 +179,43 @@ def _apply_void_operation(self, func, operation_name: str, *args): f"Failed to apply {operation_name} operation on Selene simulator" ) + def register_gateset(self, gateset: Gateset) -> Gateset: + payload = gateset.serialize() + input_buffer = (ctypes.c_uint8 * len(payload))(*payload) + input_ptr = ctypes.cast(input_buffer, ctypes.POINTER(ctypes.c_uint8)) + written = ctypes.c_size_t() + if 0 != self._lib.selene_simulator_negotiate_gateset( + self._instance, + input_ptr, + len(payload), + None, + 0, + ctypes.byref(written), + ): + raise RuntimeError("Failed to negotiate gateset with Selene simulator") + output_buffer = (ctypes.c_uint8 * written.value)() + output_ptr = ctypes.cast(output_buffer, ctypes.POINTER(ctypes.c_uint8)) + if 0 != self._lib.selene_simulator_negotiate_gateset( + self._instance, + input_ptr, + len(payload), + output_ptr, + written.value, + ctypes.byref(written), + ): + raise RuntimeError("Failed to negotiate gateset with Selene simulator") + return Gateset.deserialize(bytes(output_buffer[: written.value])) + + def gate(self, gate: Gate): + payload = gate.serialize() + buffer = (ctypes.c_uint8 * len(payload))(*payload) + self._apply_void_operation( + self._lib.selene_simulator_operation_gate, + "GATE", + ctypes.cast(buffer, ctypes.POINTER(ctypes.c_uint8)), + len(payload), + ) + def _apply_measure_operation(self, qubit: int) -> bool: result = self._lib.selene_simulator_operation_measure(self._instance, qubit) if result not in (0, 1): @@ -196,25 +231,6 @@ def next_shot(self): ): raise RuntimeError("Failed to start next shot on Selene simulator") - def rxy(self, qubit: int, theta: float, phi: float): - self._apply_void_operation( - self._lib.selene_simulator_operation_rxy, "RXY", qubit, theta, phi - ) - - def rz(self, qubit: int, theta: float): - self._apply_void_operation( - self._lib.selene_simulator_operation_rz, "RZ", qubit, theta - ) - - def rzz(self, qubit_a: int, qubit_b: int, theta: float): - self._apply_void_operation( - self._lib.selene_simulator_operation_rzz, - "RZZ", - qubit_a, - qubit_b, - theta, - ) - def measure(self, qubit: int) -> bool: return self._apply_measure_operation(qubit) diff --git a/selene-sim/python/selene_sim/process.py b/selene-sim/python/selene_sim/process.py index f7337f09..5f5abf4b 100644 --- a/selene-sim/python/selene_sim/process.py +++ b/selene-sim/python/selene_sim/process.py @@ -2,6 +2,7 @@ import platform from pathlib import Path from subprocess import Popen, TimeoutExpired + import yaml @@ -39,6 +40,7 @@ def __init__( ) self.stdout = run_directory / "stdout.txt" self.stderr = run_directory / "stderr.txt" + self.process = None def get_environment(self) -> dict: """ @@ -61,7 +63,10 @@ def get_environment(self) -> dict: return env def __del__(self): - self.terminate() + try: + self.terminate() + except Exception: + pass def terminate(self, expected_natural_exit: bool = False): """ diff --git a/selene-sim/python/tests/snapshots/test_guppy/test_metrics/metrics b/selene-sim/python/tests/snapshots/test_guppy/test_metrics/metrics index 61e0b843..fc311d8f 100644 --- a/selene-sim/python/tests/snapshots/test_guppy/test_metrics/metrics +++ b/selene-sim/python/tests/snapshots/test_guppy/test_metrics/metrics @@ -3,23 +3,26 @@ emulator: post_runtime: custom_op_batch_count: 0 custom_op_individual_count: 0 + gate:PhasedX:batch_count: 8 + gate:PhasedX:individual_count: 8 + gate:RZ:batch_count: 4 + gate:RZ:individual_count: 4 + gate:ZZPhase:batch_count: 5 + gate:ZZPhase:individual_count: 5 measure_batch_count: 3 measure_individual_count: 3 measure_leaked_batch_count: 0 measure_leaked_individual_count: 0 reset_batch_count: 3 reset_individual_count: 3 - rxy_batch_count: 8 - rxy_individual_count: 8 - rz_batch_count: 4 - rz_individual_count: 4 - rzz_batch_count: 5 - rzz_individual_count: 5 total_duration_ns: 0 simulator: cumulative_postselect_probability: 1.0 user_program: currently_allocated: 0 + gate:PhasedX:count: 8 + gate:RZ:count: 4 + gate:ZZPhase:count: 5 global_barrier_count: 0 local_barrier_count: 0 max_allocated: 3 @@ -29,6 +32,3 @@ user_program: qalloc_count: 3 qfree_count: 3 reset_count: 3 - rxy_count: 8 - rz_count: 4 - rzz_count: 5 diff --git a/selene-sim/python/tests/snapshots/test_guppy/test_metrics_on_exit/metrics_on_exit b/selene-sim/python/tests/snapshots/test_guppy/test_metrics_on_exit/metrics_on_exit index 8562eebb..006b7713 100644 --- a/selene-sim/python/tests/snapshots/test_guppy/test_metrics_on_exit/metrics_on_exit +++ b/selene-sim/python/tests/snapshots/test_guppy/test_metrics_on_exit/metrics_on_exit @@ -3,23 +3,26 @@ emulator: post_runtime: custom_op_batch_count: 0 custom_op_individual_count: 0 + gate:PhasedX:batch_count: 8 + gate:PhasedX:individual_count: 8 + gate:RZ:batch_count: 4 + gate:RZ:individual_count: 4 + gate:ZZPhase:batch_count: 5 + gate:ZZPhase:individual_count: 5 measure_batch_count: 1 measure_individual_count: 1 measure_leaked_batch_count: 0 measure_leaked_individual_count: 0 reset_batch_count: 3 reset_individual_count: 3 - rxy_batch_count: 8 - rxy_individual_count: 8 - rz_batch_count: 4 - rz_individual_count: 4 - rzz_batch_count: 5 - rzz_individual_count: 5 total_duration_ns: 0 simulator: cumulative_postselect_probability: 1.0 user_program: currently_allocated: 2 + gate:PhasedX:count: 8 + gate:RZ:count: 4 + gate:ZZPhase:count: 5 global_barrier_count: 0 local_barrier_count: 0 max_allocated: 3 @@ -29,6 +32,3 @@ user_program: qalloc_count: 3 qfree_count: 1 reset_count: 3 - rxy_count: 8 - rz_count: 4 - rzz_count: 5 diff --git a/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/simple_events.json b/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/simple_events.json index cc2cced8..0d6350b8 100644 --- a/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/simple_events.json +++ b/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/simple_events.json @@ -134,10 +134,15 @@ { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { @@ -151,38 +156,58 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { @@ -196,38 +221,58 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { @@ -241,38 +286,58 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { @@ -286,37 +351,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -330,35 +415,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { @@ -372,37 +477,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -416,35 +541,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { @@ -458,37 +603,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { @@ -502,35 +667,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { @@ -544,37 +729,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { @@ -588,35 +793,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { @@ -630,38 +855,58 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { @@ -675,37 +920,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -719,35 +984,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { @@ -761,37 +1046,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -805,35 +1110,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { @@ -847,38 +1172,58 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { @@ -892,37 +1237,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -936,35 +1301,55 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { @@ -978,37 +1363,57 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ], "duration_ns": 0 } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -1022,25 +1427,40 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rz", - "qubit": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rz", - "qubit": 2, - "theta": -1.5707963267948966, + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966 + ], "duration_ns": 0 } }, diff --git a/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/soft_events.json b/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/soft_events.json index a2b11dce..27616020 100644 --- a/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/soft_events.json +++ b/selene-sim/python/tests/snapshots/test_runtimes/test_simple_vs_softrz/soft_events.json @@ -44,182 +44,287 @@ { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": -1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": 3.141592653589793 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 0, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 0 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 1, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.USER", "operation": { - "op": "Rz", - "qubit": 2, - "theta": -1.5707963267948966 + "op": "Gate", + "gate": "RZ", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966 + ] } }, { @@ -372,28 +477,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 3.141592653589793, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 3.141592653589793, + 0.0 + ], "duration_ns": 0 } }, @@ -408,28 +528,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 3.141592653589793, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 3.141592653589793, + 0.0 + ], "duration_ns": 0 } }, @@ -444,28 +579,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -480,28 +630,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -516,28 +681,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ], "duration_ns": 0 } }, @@ -552,28 +732,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 0, - "theta": 1.5707963267948966, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 0 + ], + "params": [ + 1.5707963267948966, + 0.0 + ], "duration_ns": 0 } }, @@ -588,28 +783,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 0.0 + ], "duration_ns": 0 } }, @@ -624,28 +834,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 0.0 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 0.0 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 0.0 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": -1.5707963267948966, - "phi": 0.0, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + -1.5707963267948966, + 0.0 + ], "duration_ns": 0 } }, @@ -660,28 +885,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 0, - "qubit1": 1, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 0, + 1 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -696,28 +936,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 1, - "theta": 1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 1 + ], + "params": [ + 1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -732,28 +987,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": -1.5707963267948966, - "phi": 1.5707963267948966, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + -1.5707963267948966, + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -768,28 +1038,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966 + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rzz", - "qubit0": 1, - "qubit1": 2, - "theta": 1.5707963267948966, + "op": "Gate", + "gate": "ZZPhase", + "qubits": [ + 1, + 2 + ], + "params": [ + 1.5707963267948966 + ], "duration_ns": 0 } }, @@ -804,28 +1089,43 @@ { "source": "Source.OPTIMISER", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.ERROR_MODEL", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793 + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ] } }, { "source": "Source.SIMULATOR", "operation": { - "op": "Rxy", - "qubit": 2, - "theta": 1.5707963267948966, - "phi": 3.141592653589793, + "op": "Gate", + "gate": "PhasedX", + "qubits": [ + 2 + ], + "params": [ + 1.5707963267948966, + 3.141592653589793 + ], "duration_ns": 0 } }, diff --git a/selene-sim/python/tests/snapshots/test_trace/test_ghz_trace/trace.json b/selene-sim/python/tests/snapshots/test_trace/test_ghz_trace/trace.json index 12de1484..28f7dfb3 100644 --- a/selene-sim/python/tests/snapshots/test_trace/test_ghz_trace/trace.json +++ b/selene-sim/python/tests/snapshots/test_trace/test_ghz_trace/trace.json @@ -260,7 +260,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -278,7 +278,7 @@ "qubits": [ 0 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ 3.141592653589793 ], @@ -295,7 +295,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -314,7 +314,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -331,7 +331,7 @@ "qubits": [ 0 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -348,7 +348,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -366,7 +366,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -383,7 +383,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -402,7 +402,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -419,7 +419,7 @@ "qubits": [ 1 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -436,7 +436,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -454,7 +454,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -471,7 +471,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -490,7 +490,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -507,7 +507,7 @@ "qubits": [ 2 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -524,7 +524,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -542,7 +542,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -559,7 +559,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -578,7 +578,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -595,7 +595,7 @@ "qubits": [ 3 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -612,7 +612,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -630,7 +630,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -647,7 +647,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -666,7 +666,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -683,7 +683,7 @@ "qubits": [ 4 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -700,7 +700,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -718,7 +718,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -735,7 +735,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -754,7 +754,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -771,7 +771,7 @@ "qubits": [ 5 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -788,7 +788,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -806,7 +806,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -823,7 +823,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -842,7 +842,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -859,7 +859,7 @@ "qubits": [ 6 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -876,7 +876,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -894,7 +894,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -911,7 +911,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -930,7 +930,7 @@ 7, 8 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -947,7 +947,7 @@ "qubits": [ 7 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -964,7 +964,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -982,7 +982,7 @@ "qubits": [ 8 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -999,7 +999,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -1018,7 +1018,7 @@ 8, 9 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -1035,7 +1035,7 @@ "qubits": [ 8 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -1052,7 +1052,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -1070,7 +1070,7 @@ "qubits": [ 9 ], - "gate_name": "Rz", + "gate_name": "RZ", "params": [ -1.5707963267948966 ], @@ -1379,7 +1379,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1398,7 +1398,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1544,7 +1544,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1563,7 +1563,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1645,7 +1645,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1664,7 +1664,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 3.141592653589793, 0.0 @@ -1779,7 +1779,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1797,7 +1797,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1816,7 +1816,7 @@ "qubits": [ 0 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, -1.5707963267948966 @@ -1835,7 +1835,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -1853,7 +1853,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -1872,7 +1872,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -1892,7 +1892,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -1910,7 +1910,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -1929,7 +1929,7 @@ 0, 1 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -1947,7 +1947,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -1965,7 +1965,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -1984,7 +1984,7 @@ "qubits": [ 1 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2003,7 +2003,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2021,7 +2021,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2040,7 +2040,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2060,7 +2060,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2078,7 +2078,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2097,7 +2097,7 @@ 1, 2 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2115,7 +2115,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2133,7 +2133,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2152,7 +2152,7 @@ "qubits": [ 2 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2171,7 +2171,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2189,7 +2189,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2208,7 +2208,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2228,7 +2228,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2246,7 +2246,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2265,7 +2265,7 @@ 2, 3 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2283,7 +2283,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2301,7 +2301,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2320,7 +2320,7 @@ "qubits": [ 3 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2339,7 +2339,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2357,7 +2357,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2376,7 +2376,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2396,7 +2396,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2414,7 +2414,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2433,7 +2433,7 @@ 3, 4 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2451,7 +2451,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2469,7 +2469,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2488,7 +2488,7 @@ "qubits": [ 4 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2507,7 +2507,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2525,7 +2525,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2544,7 +2544,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2564,7 +2564,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2582,7 +2582,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2601,7 +2601,7 @@ 4, 5 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2619,7 +2619,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2637,7 +2637,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2656,7 +2656,7 @@ "qubits": [ 5 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2675,7 +2675,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2693,7 +2693,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2712,7 +2712,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2732,7 +2732,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2750,7 +2750,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2769,7 +2769,7 @@ 5, 6 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2787,7 +2787,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2805,7 +2805,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2824,7 +2824,7 @@ "qubits": [ 6 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2843,7 +2843,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2861,7 +2861,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2880,7 +2880,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -2900,7 +2900,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2918,7 +2918,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2937,7 +2937,7 @@ 6, 7 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -2955,7 +2955,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2973,7 +2973,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -2992,7 +2992,7 @@ "qubits": [ 7 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3011,7 +3011,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3029,7 +3029,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3048,7 +3048,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3068,7 +3068,7 @@ 7, 8 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3086,7 +3086,7 @@ 7, 8 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3105,7 +3105,7 @@ 7, 8 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3123,7 +3123,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3141,7 +3141,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3160,7 +3160,7 @@ "qubits": [ 8 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3179,7 +3179,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3197,7 +3197,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3216,7 +3216,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ -1.5707963267948966, 1.5707963267948966 @@ -3236,7 +3236,7 @@ 8, 9 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3254,7 +3254,7 @@ 8, 9 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3273,7 +3273,7 @@ 8, 9 ], - "gate_name": "Rzz", + "gate_name": "ZZPhase", "params": [ 1.5707963267948966 ], @@ -3291,7 +3291,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3309,7 +3309,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 @@ -3328,7 +3328,7 @@ "qubits": [ 9 ], - "gate_name": "Rxy", + "gate_name": "PhasedX", "params": [ 1.5707963267948966, 3.141592653589793 diff --git a/selene-sim/python/tests/snapshots/test_unparsed/test_flip_some_with_metrics_unparsed/unparsed_metrics b/selene-sim/python/tests/snapshots/test_unparsed/test_flip_some_with_metrics_unparsed/unparsed_metrics index 92dcf192..cb2035d2 100644 --- a/selene-sim/python/tests/snapshots/test_unparsed/test_flip_some_with_metrics_unparsed/unparsed_metrics +++ b/selene-sim/python/tests/snapshots/test_unparsed/test_flip_some_with_metrics_unparsed/unparsed_metrics @@ -35,14 +35,8 @@ - METRICS:INT:user_program:measure_read_count - 4 - !!python/tuple - - METRICS:INT:user_program:rxy_count + - METRICS:INT:user_program:gate:PhasedX:count - 3 -- !!python/tuple - - METRICS:INT:user_program:rzz_count - - 0 -- !!python/tuple - - METRICS:INT:user_program:rz_count - - 0 - !!python/tuple - METRICS:INT:user_program:global_barrier_count - 0 @@ -80,23 +74,11 @@ - METRICS:INT:post_runtime:reset_individual_count - 4 - !!python/tuple - - METRICS:INT:post_runtime:rxy_batch_count + - METRICS:INT:post_runtime:gate:PhasedX:batch_count - 3 - !!python/tuple - - METRICS:INT:post_runtime:rxy_individual_count + - METRICS:INT:post_runtime:gate:PhasedX:individual_count - 3 -- !!python/tuple - - METRICS:INT:post_runtime:rz_batch_count - - 0 -- !!python/tuple - - METRICS:INT:post_runtime:rz_individual_count - - 0 -- !!python/tuple - - METRICS:INT:post_runtime:rzz_batch_count - - 0 -- !!python/tuple - - METRICS:INT:post_runtime:rzz_individual_count - - 0 - !!python/tuple - METRICS:INT:post_runtime:total_duration_ns - 0 diff --git a/selene-sim/python/tests/test_interactive.py b/selene-sim/python/tests/test_interactive.py index b02545b3..31807990 100644 --- a/selene-sim/python/tests/test_interactive.py +++ b/selene-sim/python/tests/test_interactive.py @@ -6,6 +6,7 @@ import tempfile import numpy as np +from selene_core import Gateset, PhasedX, RZ, ZZPhase from selene_sim import DepolarizingErrorModel, Quest, SoftRZRuntime, SimpleRuntime from selene_sim.interactive import ( @@ -15,6 +16,9 @@ ) +NATIVE_GATES = Gateset(RZ, PhasedX, ZZPhase) + + def test_interactive_full_stack_event_hooks(): from selene_sim.event_hooks import CircuitExtractor, MetricStore, MultiEventHook @@ -22,7 +26,10 @@ def test_interactive_full_stack_event_hooks(): circuit_extractor = CircuitExtractor() metric_store = MetricStore() hook = MultiEventHook([circuit_extractor, metric_store]) - s = InteractiveFullStack(simulator=Quest(), n_qubits=10, event_hook=hook) + gates = NATIVE_GATES + s = InteractiveFullStack( + simulator=Quest(), n_qubits=10, event_hook=hook, gateset=NATIVE_GATES + ) metrics = metric_store.shots[0] extractor = circuit_extractor.shots[0] # Allocate 3 qubits @@ -63,27 +70,27 @@ def test_interactive_full_stack_event_hooks(): # We can apply some gates and check that the metrics are properly updated # We'll build a GHZ state. As we only have access to the native gateset and # not (yet) a direct hadamard or cnot, I'll write it out verbatim. - assert metrics["user_program"]["rxy_count"] == 0 - assert metrics["user_program"]["rzz_count"] == 0 + assert metrics["user_program"].get("gate:PhasedX:count", 0) == 0 + assert metrics["user_program"].get("gate:ZZPhase:count", 0) == 0 # h(q0) - s.rxy(qs[0], pi / 2, -pi / 2) - s.rz(qs[0], pi) + s.gate(gates.PhasedX(qs[0].id, pi / 2, -pi / 2)) + s.gate(gates.RZ(qs[0].id, pi)) # cx(q0, q1) - s.rxy(qs[1], -pi / 2, pi / 2) - s.rzz(qs[0], qs[1], pi / 2) - s.rz(qs[0], -pi / 2) - s.rxy(qs[1], pi / 2, pi) - s.rz(qs[1], -pi / 2) + s.gate(gates.PhasedX(qs[1].id, -pi / 2, pi / 2)) + s.gate(gates.ZZPhase(qs[0].id, qs[1].id, pi / 2)) + s.gate(gates.RZ(qs[0].id, -pi / 2)) + s.gate(gates.PhasedX(qs[1].id, pi / 2, pi)) + s.gate(gates.RZ(qs[1].id, -pi / 2)) # cx(q1, q2) - s.rxy(qs[2], -pi / 2, pi / 2) - s.rzz(qs[1], qs[2], pi / 2) - s.rz(qs[1], -pi / 2) - s.rxy(qs[2], pi / 2, pi) - s.rz(qs[2], -pi / 2) + s.gate(gates.PhasedX(qs[2].id, -pi / 2, pi / 2)) + s.gate(gates.ZZPhase(qs[1].id, qs[2].id, pi / 2)) + s.gate(gates.RZ(qs[1].id, -pi / 2)) + s.gate(gates.PhasedX(qs[2].id, pi / 2, pi)) + s.gate(gates.RZ(qs[2].id, -pi / 2)) # Check that the metrics reflect the gates we've applied - assert metrics["user_program"]["rxy_count"] == 5 - assert metrics["user_program"]["rzz_count"] == 2 - assert metrics["user_program"]["rz_count"] == 5 + assert metrics["user_program"]["gate:PhasedX:count"] == 5 + assert metrics["user_program"]["gate:ZZPhase:count"] == 2 + assert metrics["user_program"]["gate:RZ:count"] == 5 # We can get the state of the qubits. state = s.get_state(qs).get_single_state() @@ -112,9 +119,9 @@ def test_interactive_full_stack_event_hooks(): # As measurement necessarily forces the runtime to have emitted instructions, we can now check the post-runtime metrics # As we're using the SimpleRuntime, all gates are applied (no optimisation, no soft RZ gate, etc) assert metrics["post_runtime"]["measure_individual_count"] == 3 - assert metrics["post_runtime"]["rxy_individual_count"] == 5 - assert metrics["post_runtime"]["rz_individual_count"] == 5 - assert metrics["post_runtime"]["rzz_individual_count"] == 2 + assert metrics["post_runtime"]["gate:PhasedX:individual_count"] == 5 + assert metrics["post_runtime"]["gate:RZ:individual_count"] == 5 + assert metrics["post_runtime"]["gate:ZZPhase:individual_count"] == 2 # We can also check the circuit extractor output = extractor.get_optimiser_output() @@ -173,7 +180,8 @@ def test_interactive_full_stack_error_model_io(): error_model_output = instructions.get_error_model_output() assert error_model_output[0] == {"op": "Reset", "qubit": 0} - assert error_model_output[1]["op"] in {"Rxy", "Rz"} + assert error_model_output[1]["op"] == "Gate" + assert error_model_output[1]["gate"] in {"PhasedX", "RZ"} assert error_model_output[2] == {"op": "MeasureRequest", "qubit": 0} simulator_output = instructions.get_simulator_output() @@ -187,16 +195,33 @@ def test_interactive_full_stack_error_model_io(): ) +def test_interactive_full_stack_gateset_negotiation(): + s = InteractiveFullStack( + simulator=Quest(random_seed=1234), + runtime=SoftRZRuntime(), + n_qubits=1, + gateset=NATIVE_GATES, + ) + assert s.emitted_gateset is not None + assert {definition.name for definition in s.emitted_gateset} == { + "PhasedX", + "ZZPhase", + } + + def test_interactive_simulator(): - sim = InteractiveSimulator(simulator=Quest(random_seed=1234), n_qubits=10) + gates = NATIVE_GATES + sim = InteractiveSimulator( + simulator=Quest(random_seed=1234), n_qubits=10, gateset=NATIVE_GATES + ) # Some simple checks on measurement and postselection assert sim.measure(0) == 0 - sim.rxy(0, pi, 0) + sim.gate(gates.PhasedX(0, pi, 0)) assert sim.measure(0) == 1 assert sim.get_metrics()["cumulative_postselect_probability"] == 1.0 sim.reset(0) - sim.rxy(0, pi / 2, 0) - sim.rxy(1, pi / 2, 0) + sim.gate(gates.PhasedX(0, pi / 2, 0)) + sim.gate(gates.PhasedX(1, pi / 2, 0)) sim.postselect(0, True) sim.postselect(1, False) assert sim.measure(0) @@ -206,20 +231,20 @@ def test_interactive_simulator(): # Now make a GHZ state on qubits 1, 2, 3, dump the state and check amplitudes, postselect one of the qubits, then assert measurements are consistent. # h(1) - sim.rxy(1, pi / 2, -pi / 2) - sim.rz(1, pi) + sim.gate(gates.PhasedX(1, pi / 2, -pi / 2)) + sim.gate(gates.RZ(1, pi)) # cx(1, 2) - sim.rxy(2, -pi / 2, pi / 2) - sim.rzz(1, 2, pi / 2) - sim.rz(1, -pi / 2) - sim.rxy(2, pi / 2, pi) - sim.rz(2, -pi / 2) + sim.gate(gates.PhasedX(2, -pi / 2, pi / 2)) + sim.gate(gates.ZZPhase(1, 2, pi / 2)) + sim.gate(gates.RZ(1, -pi / 2)) + sim.gate(gates.PhasedX(2, pi / 2, pi)) + sim.gate(gates.RZ(2, -pi / 2)) # cx(2, 3) - sim.rxy(3, -pi / 2, pi / 2) - sim.rzz(2, 3, pi / 2) - sim.rz(2, -pi / 2) - sim.rxy(3, pi / 2, pi) - sim.rz(3, -pi / 2) + sim.gate(gates.PhasedX(3, -pi / 2, pi / 2)) + sim.gate(gates.ZZPhase(2, 3, pi / 2)) + sim.gate(gates.RZ(2, -pi / 2)) + sim.gate(gates.PhasedX(3, pi / 2, pi)) + sim.gate(gates.RZ(3, -pi / 2)) # get a temp file; close the handle before re-opening so Windows can read it with tempfile.NamedTemporaryFile(delete=False) as f: @@ -240,16 +265,32 @@ def test_interactive_simulator(): assert sim.measure(3) +def test_interactive_simulator_gateset_gate_api(): + gates = NATIVE_GATES + sim = InteractiveSimulator( + simulator=Quest(random_seed=1234), n_qubits=2, gateset=NATIVE_GATES + ) + assert sim.emitted_gateset is not None + assert {definition.name for definition in sim.emitted_gateset} == { + "RZ", + "PhasedX", + "ZZPhase", + } + sim.gate(gates.PhasedX(q0=0, theta=pi, phi=0)) + assert sim.measure(0) + + def test_interactive_runtime_simple(): # The simple runtime flushes on each quantum operation. - r = InteractiveRuntime(runtime=SimpleRuntime(), n_qubits=10) + gates = NATIVE_GATES + r = InteractiveRuntime(runtime=SimpleRuntime(), n_qubits=10, gateset=NATIVE_GATES) qubits = [r.qalloc() for _ in range(10)] # An alloc doesn't correspond to a physical operation assert len(r.get_operations()) == 0 future_refs = [] for qubit in qubits: r.reset(qubit) - r.rxy(qubit, pi, 0) + r.gate(gates.PhasedX(qubit, pi, 0)) future_refs.append(r.measure(qubit)) # Each of reset, rxy and measure should have caused a flush of operations assert len(r.get_operations()) == 3 @@ -266,7 +307,8 @@ def test_interactive_runtime_simple(): def test_interactive_runtime_softrz(): - r = InteractiveRuntime(runtime=SoftRZRuntime(), n_qubits=10) + gates = NATIVE_GATES + r = InteractiveRuntime(runtime=SoftRZRuntime(), n_qubits=10, gateset=NATIVE_GATES) qubits = [r.qalloc() for _ in range(10)] # the soft RZ runtime waits until futures are forced before flushing # operations. @@ -275,7 +317,7 @@ def test_interactive_runtime_softrz(): future_refs = [] for qubit in qubits: r.reset(qubit) - r.rxy(qubit, pi, 0) + r.gate(gates.PhasedX(qubit, pi, 0)) future_refs.append(r.measure(qubit)) assert len(r.get_operations()) == 0 diff --git a/selene-sim/python/tests/test_runtimes.py b/selene-sim/python/tests/test_runtimes.py index 7333150b..77cd3fb0 100644 --- a/selene-sim/python/tests/test_runtimes.py +++ b/selene-sim/python/tests/test_runtimes.py @@ -85,10 +85,10 @@ def main() -> None: f"User program metrics differ: {soft_metrics['user_program']} vs {simple_metrics['user_program']}" ) - assert simple_metrics["post_runtime"]["rz_batch_count"] > 0 - assert simple_metrics["post_runtime"]["rz_individual_count"] > 0 - assert soft_metrics["post_runtime"]["rz_batch_count"] == 0 - assert soft_metrics["post_runtime"]["rz_individual_count"] == 0 + assert simple_metrics["post_runtime"]["gate:RZ:batch_count"] > 0 + assert simple_metrics["post_runtime"]["gate:RZ:individual_count"] > 0 + assert soft_metrics["post_runtime"].get("gate:RZ:batch_count", 0) == 0 + assert soft_metrics["post_runtime"].get("gate:RZ:individual_count", 0) == 0 def sum_up(metrics: dict[str, int], category: str): return sum( diff --git a/selene-sim/rust/emulator.rs b/selene-sim/rust/emulator.rs index 06a5447a..ce6bc3ab 100644 --- a/selene-sim/rust/emulator.rs +++ b/selene-sim/rust/emulator.rs @@ -2,6 +2,7 @@ use crate::event_hooks::{Operation, SharedEventHook}; use crate::selene_instance::configuration::Configuration; use anyhow::{Result, anyhow}; use selene_core::error_model::{BatchResult, ErrorModel, ErrorModelInterface}; +use selene_core::gatewire::DynamicGateSet; use selene_core::runtime::{ BatchOperation, Operation as RuntimeOperation, Runtime, RuntimeInterface, }; @@ -49,6 +50,10 @@ impl SimulatorInterface for HookedSimulator { self.inner.shot_end() } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + self.inner.negotiate_gateset(gateset) + } + fn handle_operations(&mut self, operations: BatchOperation) -> Result { let mut results = BatchResult::default(); for operation in operations { @@ -207,27 +212,14 @@ impl Emulator { .on_user_call(&Operation::GlobalBarrier(sleep_time)); self.process_runtime() } - pub fn user_issued_rxy(&mut self, q0: u64, theta: f64, phi: f64) -> Result<()> { - self.runtime.rxy_gate(q0, theta, phi)?; - self.event_hooks - .on_user_call(&Operation::RXY(q0, theta, phi)); - self.process_runtime() - } - pub fn user_issued_rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()> { - self.runtime.rzz_gate(q0, q1, theta)?; - self.event_hooks - .on_user_call(&Operation::RZZ(q0, q1, theta)); - self.process_runtime() - } - pub fn user_issued_rpp(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { - self.runtime.rpp_gate(q0, q1, theta, phi)?; + pub fn user_issued_gate( + &mut self, + gate: &selene_core::gatewire::OwnedGateInstance, + ) -> Result<()> { + self.runtime.gate(gate)?; + let operation = RuntimeOperation::from_gate_instance(gate.clone())?; self.event_hooks - .on_user_call(&Operation::RPP(q0, q1, theta, phi)); - self.process_runtime() - } - pub fn user_issued_rz(&mut self, q0: u64, theta: f64) -> Result<()> { - self.runtime.rz_gate(q0, theta)?; - self.event_hooks.on_user_call(&Operation::RZ(q0, theta)); + .on_user_call(&Operation::from_runtime_operation(&operation)); self.process_runtime() } pub fn user_issued_reset(&mut self, q0: u64) -> Result<()> { @@ -314,6 +306,15 @@ impl Emulator { .on_user_call(&Operation::ClassicalDelay(delay_ns)); self.process_runtime() } + + pub fn register_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let runtime_gateset = self.runtime.negotiate_gateset(gateset)?; + let error_model_gateset = self.error_model.negotiate_gateset(&runtime_gateset)?; + self.simulator.negotiate_gateset(&error_model_gateset)?; + self.event_hooks + .on_gatesets_registered(gateset, &runtime_gateset); + Ok(runtime_gateset) + } } impl Emulator { diff --git a/selene-sim/rust/event_hooks.rs b/selene-sim/rust/event_hooks.rs index 98dca771..d218b761 100644 --- a/selene-sim/rust/event_hooks.rs +++ b/selene-sim/rust/event_hooks.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use selene_core::encoder::{OutputStream, OutputStreamError}; use selene_core::error_model::BatchResult; +use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; use selene_core::runtime::BatchOperation; pub mod instruction_log; @@ -13,10 +14,6 @@ pub mod metrics; pub enum Operation { QFree(u64), QAlloc(u64), - RXY(u64, f64, f64), - RZZ(u64, u64, f64), - RZ(u64, f64), - RPP(u64, u64, f64, f64), Reset(u64), MeasureRequest(u64), MeasureLeakedRequest(u64), @@ -25,33 +22,19 @@ pub enum Operation { GlobalBarrier(u64), LocalBarrier(Vec, u64), Custom(u64, Vec), + Gate(Vec), ClassicalDelay(u64), Postselect(u64, bool), } impl Operation { + fn from_gate(gate: &OwnedGateInstance) -> Self { + Operation::Gate(gate.serialize()) + } + pub fn from_runtime_operation(operation: &selene_core::runtime::Operation) -> Self { match operation { selene_core::runtime::Operation::Reset { qubit_id } => Operation::Reset(*qubit_id), - selene_core::runtime::Operation::RXYGate { - qubit_id, - theta, - phi, - } => Operation::RXY(*qubit_id, *theta, *phi), - selene_core::runtime::Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => Operation::RZZ(*qubit_id_1, *qubit_id_2, *theta), - selene_core::runtime::Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => Operation::RPP(*qubit_id_1, *qubit_id_2, *theta, *phi), - selene_core::runtime::Operation::RZGate { qubit_id, theta } => { - Operation::RZ(*qubit_id, *theta) - } selene_core::runtime::Operation::Measure { qubit_id, .. } => { Operation::FutureRead(*qubit_id) } @@ -61,6 +44,7 @@ impl Operation { selene_core::runtime::Operation::Custom { custom_tag, data } => { Operation::Custom(*custom_tag as u64, data.to_vec()) } + selene_core::runtime::Operation::Gate { gate } => Self::from_gate(gate), _ => todo!( "Unsupported runtime operation in instruction log: {:?}", operation @@ -71,25 +55,6 @@ impl Operation { pub fn from_simulator_operation(operation: &selene_core::runtime::Operation) -> Self { match operation { selene_core::runtime::Operation::Reset { qubit_id } => Operation::Reset(*qubit_id), - selene_core::runtime::Operation::RXYGate { - qubit_id, - theta, - phi, - } => Operation::RXY(*qubit_id, *theta, *phi), - selene_core::runtime::Operation::RZZGate { - qubit_id_1, - qubit_id_2, - theta, - } => Operation::RZZ(*qubit_id_1, *qubit_id_2, *theta), - selene_core::runtime::Operation::RPPGate { - qubit_id_1, - qubit_id_2, - theta, - phi, - } => Operation::RPP(*qubit_id_1, *qubit_id_2, *theta, *phi), - selene_core::runtime::Operation::RZGate { qubit_id, theta } => { - Operation::RZ(*qubit_id, *theta) - } selene_core::runtime::Operation::Measure { qubit_id, .. } => { Operation::MeasureRequest(*qubit_id) } @@ -99,6 +64,7 @@ impl Operation { selene_core::runtime::Operation::Custom { custom_tag, data } => { Operation::Custom(*custom_tag as u64, data.to_vec()) } + selene_core::runtime::Operation::Gate { gate } => Self::from_gate(gate), _ => todo!( "Unsupported simulator operation in instruction log: {:?}", operation @@ -109,6 +75,7 @@ impl Operation { pub trait EventHook { fn on_user_call(&mut self, _: &Operation) {} + fn on_gatesets_registered(&mut self, _: &DynamicGateSet, _: &DynamicGateSet) {} fn on_runtime_batch(&mut self, _: &BatchOperation) {} fn on_error_model_output(&mut self, _: &Operation) {} fn on_simulator_call(&mut self, _: &Operation, _: u64) {} @@ -140,6 +107,15 @@ impl EventHook for MultiEventHook { hook.on_user_call(operation); } } + fn on_gatesets_registered( + &mut self, + user_gateset: &DynamicGateSet, + runtime_gateset: &DynamicGateSet, + ) { + for hook in self.hooks.iter_mut() { + hook.on_gatesets_registered(user_gateset, runtime_gateset); + } + } fn on_runtime_batch(&mut self, operation: &BatchOperation) { for hook in self.hooks.iter_mut() { hook.on_runtime_batch(operation); @@ -201,6 +177,14 @@ impl SharedEventHook { self.with_hooks(|hooks| hooks.on_user_call(operation)); } + pub fn on_gatesets_registered( + &self, + user_gateset: &DynamicGateSet, + runtime_gateset: &DynamicGateSet, + ) { + self.with_hooks(|hooks| hooks.on_gatesets_registered(user_gateset, runtime_gateset)); + } + pub fn on_runtime_batch(&self, batch: &BatchOperation) { self.with_hooks(|hooks| hooks.on_runtime_batch(batch)); } diff --git a/selene-sim/rust/event_hooks/instruction_log.rs b/selene-sim/rust/event_hooks/instruction_log.rs index a5bc7aa3..f6746263 100644 --- a/selene-sim/rust/event_hooks/instruction_log.rs +++ b/selene-sim/rust/event_hooks/instruction_log.rs @@ -49,28 +49,15 @@ impl Instruction { encoder.write(5u64)?; encoder.write(*qubit1)?; } - Operation::RXY(qubit1, angle1, angle2) => { - encoder.write(6u64)?; - encoder.write(*qubit1)?; - encoder.write(*angle1)?; - encoder.write(*angle2)?; - } - Operation::RZ(qubit1, angle) => { - encoder.write(7u64)?; - encoder.write(*qubit1)?; - encoder.write(*angle)?; - } - Operation::RZZ(qubit1, qubit2, angle) => { - encoder.write(8u64)?; - encoder.write(*qubit1)?; - encoder.write(*qubit2)?; - encoder.write(*angle)?; - } Operation::Custom(tag, data) => { encoder.write(9u64)?; encoder.write(*tag)?; encoder.write(&**data)?; } + Operation::Gate(data) => { + encoder.write(15u64)?; + encoder.write(&**data)?; + } Operation::LocalBarrier(qubits, sleep_time) => { encoder.write(10u64)?; encoder.write(qubits.len() as u64)?; @@ -91,13 +78,6 @@ impl Instruction { encoder.write(13u64)?; encoder.write(*duration)?; } - Operation::RPP(qubit1, qubit2, theta, phi) => { - encoder.write(14u64)?; - encoder.write(*qubit1)?; - encoder.write(*qubit2)?; - encoder.write(*theta)?; - encoder.write(*phi)?; - } Operation::Postselect(qubit1, target_value) => { encoder.write(16u64)?; encoder.write(*qubit1)?; diff --git a/selene-sim/rust/event_hooks/metrics.rs b/selene-sim/rust/event_hooks/metrics.rs index 7e6cc9c3..610c477f 100644 --- a/selene-sim/rust/event_hooks/metrics.rs +++ b/selene-sim/rust/event_hooks/metrics.rs @@ -1,6 +1,102 @@ use crate::event_hooks::{EventHook, Operation}; use selene_core::encoder::{OutputStream, OutputStreamError}; +use selene_core::gatewire::{DynamicGateSet, GateSemanticId, OwnedGateInstance, builtin}; use selene_core::runtime::{self, BatchOperation}; +use std::collections::{BTreeMap, HashMap}; + +#[derive(Clone, Debug)] +struct GateMetricLabels { + labels: HashMap, +} + +impl GateMetricLabels { + fn from_gateset(gateset: &DynamicGateSet) -> Self { + let mut label_counts: HashMap = HashMap::new(); + let mut labels = HashMap::new(); + + for decl in gateset.declarations() { + *label_counts + .entry(sanitize_metric_label(&decl.name)) + .or_default() += 1; + } + for decl in gateset.declarations() { + let mut label = sanitize_metric_label(&decl.name); + if label_counts.get(&label).copied().unwrap_or_default() > 1 { + label = format!("{label}_{}", short_semantic_id(decl.semantic_id)); + } + labels.insert(decl.semantic_id, label); + } + + Self { labels } + } + + fn label(&self, semantic_id: GateSemanticId) -> String { + self.labels + .get(&semantic_id) + .cloned() + .unwrap_or_else(|| format!("semantic_{}", short_semantic_id(semantic_id))) + } +} + +impl Default for GateMetricLabels { + fn default() -> Self { + Self::from_gateset(&builtin::all()) + } +} + +fn sanitize_metric_label(name: &str) -> String { + let mut label = String::new(); + let mut last_was_separator = false; + + for ch in name.chars() { + if ch.is_ascii_alphanumeric() { + label.push(ch); + last_was_separator = false; + } else if !last_was_separator && !label.is_empty() { + label.push('_'); + last_was_separator = true; + } + } + + while label.ends_with('_') { + label.pop(); + } + if label.is_empty() { + "gate".to_string() + } else { + label + } +} + +fn short_semantic_id(semantic_id: GateSemanticId) -> String { + semantic_id + .bytes + .iter() + .take(4) + .map(|byte| format!("{byte:02x}")) + .collect() +} + +fn write_metric( + encoder: &mut OutputStream, + time_cursor: u64, + key: &str, + value: u64, +) -> Result<(), OutputStreamError> { + encoder.begin_message(time_cursor)?; + encoder.write(key)?; + encoder.write(value)?; + encoder.end_message() +} + +fn user_gate_semantic_id(operation: &Operation) -> Option { + match operation { + Operation::Gate(data) => OwnedGateInstance::deserialize(data) + .ok() + .map(|gate| gate.semantic_id), + _ => None, + } +} #[derive(Default, Debug)] struct UserProgramMetrics { @@ -12,15 +108,13 @@ struct UserProgramMetrics { measure_request_count: u64, measure_leaked_request_count: u64, future_read_count: u64, - rxy_count: u64, - rz_count: u64, - rzz_count: u64, + gate_counts: BTreeMap, global_barrier_count: u64, local_barrier_count: u64, } impl UserProgramMetrics { - pub fn update(&mut self, operation: &Operation) { + pub fn update(&mut self, operation: &Operation, labels: &GateMetricLabels) { match operation { Operation::QAlloc(_) => { self.qalloc_count += 1; @@ -35,71 +129,90 @@ impl UserProgramMetrics { Operation::MeasureRequest(_) => self.measure_request_count += 1, Operation::MeasureLeakedRequest(_) => self.measure_leaked_request_count += 1, Operation::FutureRead(_) => self.future_read_count += 1, - Operation::RXY(..) => self.rxy_count += 1, - Operation::RZ(..) => self.rz_count += 1, - Operation::RZZ(..) => self.rzz_count += 1, Operation::LocalBarrier(..) => self.local_barrier_count += 1, Operation::GlobalBarrier(..) => self.global_barrier_count += 1, _ => {} } + if let Some(semantic_id) = user_gate_semantic_id(operation) { + *self + .gate_counts + .entry(labels.label(semantic_id)) + .or_default() += 1; + } } pub fn write( &mut self, time_cursor: u64, encoder: &mut OutputStream, ) -> Result<(), OutputStreamError> { - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:qalloc_count")?; - encoder.write(self.qalloc_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:qfree_count")?; - encoder.write(self.qfree_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:reset_count")?; - encoder.write(self.reset_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:measure_request_count")?; - encoder.write(self.measure_request_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:measure_leaked_request_count")?; - encoder.write(self.measure_leaked_request_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:measure_read_count")?; - encoder.write(self.future_read_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:rxy_count")?; - encoder.write(self.rxy_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:rzz_count")?; - encoder.write(self.rzz_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:rz_count")?; - encoder.write(self.rz_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:global_barrier_count")?; - encoder.write(self.global_barrier_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:local_barrier_count")?; - encoder.write(self.local_barrier_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:max_allocated")?; - encoder.write(self.max_allocated)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:user_program:currently_allocated")?; - encoder.write(self.currently_allocated)?; - encoder.end_message() + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:qalloc_count", + self.qalloc_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:qfree_count", + self.qfree_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:reset_count", + self.reset_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:measure_request_count", + self.measure_request_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:measure_leaked_request_count", + self.measure_leaked_request_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:measure_read_count", + self.future_read_count, + )?; + for (label, count) in &self.gate_counts { + write_metric( + encoder, + time_cursor, + &format!("METRICS:INT:user_program:gate:{label}:count"), + *count, + )?; + } + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:global_barrier_count", + self.global_barrier_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:local_barrier_count", + self.local_barrier_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:max_allocated", + self.max_allocated, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:user_program:currently_allocated", + self.currently_allocated, + ) } } @@ -113,40 +226,24 @@ struct PostRuntimeMetrics { measure_leaked_individual_count: u64, reset_batch_count: u64, reset_individual_count: u64, - rxy_batch_count: u64, - rxy_individual_count: u64, - rzz_batch_count: u64, - rzz_individual_count: u64, - rz_batch_count: u64, - rz_individual_count: u64, - rpp_individual_count: u64, - rpp_batch_count: u64, + gate_batch_counts: BTreeMap, + gate_individual_counts: BTreeMap, total_duration_ns: u64, } impl PostRuntimeMetrics { - pub fn update(&mut self, batch: &BatchOperation) { - let mut rxy_count = 0; - let mut rzz_count = 0; - let mut rz_count = 0; - let mut rpp_count = 0; + pub fn update(&mut self, batch: &BatchOperation, labels: &GateMetricLabels) { + let mut gate_counts: BTreeMap = BTreeMap::new(); let mut measure_count = 0; let mut measure_leaked_count = 0; let mut reset_count = 0; let mut custom_op_count = 0; for op in batch.iter_ops() { match op { - runtime::Operation::RXYGate { .. } => { - rxy_count += 1; - } - runtime::Operation::RZZGate { .. } => { - rzz_count += 1; - } - runtime::Operation::RZGate { .. } => { - rz_count += 1; - } - runtime::Operation::RPPGate { .. } => { - rpp_count += 1; + runtime::Operation::Gate { gate } => { + *gate_counts + .entry(labels.label(gate.semantic_id)) + .or_default() += 1; } runtime::Operation::Measure { .. } => { measure_count += 1; @@ -160,30 +257,16 @@ impl PostRuntimeMetrics { runtime::Operation::Custom { .. } => { custom_op_count += 1; } - _ => { - // Ignore other operations - } + _ => {} } } if let Some(timing) = batch.runtime_source() { self.total_duration_ns = std::cmp::max(self.total_duration_ns, u64::from(timing.end())); } - if rxy_count > 0 { - self.rxy_batch_count += 1; - self.rxy_individual_count += rxy_count; - } - if rzz_count > 0 { - self.rzz_batch_count += 1; - self.rzz_individual_count += rzz_count; - } - if rz_count > 0 { - self.rz_batch_count += 1; - self.rz_individual_count += rz_count; - } - if rpp_count > 0 { - self.rpp_batch_count += 1; - self.rpp_individual_count += rpp_count; + for (label, count) in gate_counts { + *self.gate_batch_counts.entry(label.clone()).or_default() += 1; + *self.gate_individual_counts.entry(label).or_default() += count; } if measure_count > 0 { self.measure_batch_count += 1; @@ -207,80 +290,102 @@ impl PostRuntimeMetrics { time_cursor: u64, encoder: &mut OutputStream, ) -> Result<(), OutputStreamError> { - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:custom_op_batch_count")?; - encoder.write(self.custom_op_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:custom_op_individual_count")?; - encoder.write(self.custom_op_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:measure_batch_count")?; - encoder.write(self.measure_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:measure_individual_count")?; - encoder.write(self.measure_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:measure_leaked_batch_count")?; - encoder.write(self.measure_leaked_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:measure_leaked_individual_count")?; - encoder.write(self.measure_leaked_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:reset_batch_count")?; - encoder.write(self.reset_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:reset_individual_count")?; - encoder.write(self.reset_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rxy_batch_count")?; - encoder.write(self.rxy_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rxy_individual_count")?; - encoder.write(self.rxy_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rz_batch_count")?; - encoder.write(self.rz_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rz_individual_count")?; - encoder.write(self.rz_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rzz_batch_count")?; - encoder.write(self.rzz_batch_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:rzz_individual_count")?; - encoder.write(self.rzz_individual_count)?; - encoder.end_message()?; - encoder.begin_message(time_cursor)?; - encoder.write("METRICS:INT:post_runtime:total_duration_ns")?; - encoder.write(self.total_duration_ns)?; - encoder.end_message() + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:custom_op_batch_count", + self.custom_op_batch_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:custom_op_individual_count", + self.custom_op_individual_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:measure_batch_count", + self.measure_batch_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:measure_individual_count", + self.measure_individual_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:measure_leaked_batch_count", + self.measure_leaked_batch_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:measure_leaked_individual_count", + self.measure_leaked_individual_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:reset_batch_count", + self.reset_batch_count, + )?; + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:reset_individual_count", + self.reset_individual_count, + )?; + for (label, count) in &self.gate_batch_counts { + write_metric( + encoder, + time_cursor, + &format!("METRICS:INT:post_runtime:gate:{label}:batch_count"), + *count, + )?; + } + for (label, count) in &self.gate_individual_counts { + write_metric( + encoder, + time_cursor, + &format!("METRICS:INT:post_runtime:gate:{label}:individual_count"), + *count, + )?; + } + write_metric( + encoder, + time_cursor, + "METRICS:INT:post_runtime:total_duration_ns", + self.total_duration_ns, + ) } } -#[derive(Default, Debug)] +#[derive(Debug, Default)] pub struct HighLevelMetrics { user_program_metrics: UserProgramMetrics, post_runtime_metrics: PostRuntimeMetrics, + user_gate_labels: GateMetricLabels, + runtime_gate_labels: GateMetricLabels, } impl EventHook for HighLevelMetrics { fn on_user_call(&mut self, operation: &Operation) { - self.user_program_metrics.update(operation); + self.user_program_metrics + .update(operation, &self.user_gate_labels); + } + fn on_gatesets_registered( + &mut self, + user_gateset: &DynamicGateSet, + runtime_gateset: &DynamicGateSet, + ) { + self.user_gate_labels = GateMetricLabels::from_gateset(user_gateset); + self.runtime_gate_labels = GateMetricLabels::from_gateset(runtime_gateset); } fn on_runtime_batch(&mut self, batch: &BatchOperation) { - self.post_runtime_metrics.update(batch); + self.post_runtime_metrics + .update(batch, &self.runtime_gate_labels); } fn write( &mut self, @@ -297,3 +402,46 @@ impl EventHook for HighLevelMetrics { } fn on_shot_end(&mut self) {} } + +#[cfg(test)] +mod tests { + use super::*; + use selene_core::gatewire::{GateDecl, GateValue, OperandKind, OperandSpec}; + + fn custom_gate() -> (DynamicGateSet, OwnedGateInstance) { + let semantic_id = GateSemanticId::from_text("test.selene.metrics.magic.v1"); + let declaration = GateDecl::new( + semantic_id, + "Magic Phase!", + [OperandSpec::new("q0", OperandKind::Qubit)], + 1, + ); + let gateset = DynamicGateSet::from_declarations([declaration]).unwrap(); + let gate = OwnedGateInstance::new(semantic_id, [GateValue::Qubit(0)]); + (gateset, gate) + } + + #[test] + fn user_program_gate_metrics_use_registered_gate_names() { + let (gateset, gate) = custom_gate(); + let labels = GateMetricLabels::from_gateset(&gateset); + let mut metrics = UserProgramMetrics::default(); + + metrics.update(&Operation::Gate(gate.serialize()), &labels); + + assert_eq!(metrics.gate_counts.get("Magic_Phase"), Some(&1)); + } + + #[test] + fn post_runtime_gate_metrics_use_registered_gate_names() { + let (gateset, gate) = custom_gate(); + let labels = GateMetricLabels::from_gateset(&gateset); + let batch = BatchOperation::simulator(vec![runtime::Operation::Gate { gate }]); + let mut metrics = PostRuntimeMetrics::default(); + + metrics.update(&batch, &labels); + + assert_eq!(metrics.gate_batch_counts.get("Magic_Phase"), Some(&1)); + assert_eq!(metrics.gate_individual_counts.get("Magic_Phase"), Some(&1)); + } +} diff --git a/selene-sim/rust/ffi_interface.rs b/selene-sim/rust/ffi_interface.rs index a925acec..acb61254 100644 --- a/selene-sim/rust/ffi_interface.rs +++ b/selene-sim/rust/ffi_interface.rs @@ -1,6 +1,7 @@ use super::selene_instance::SeleneInstance; use crate::selene_instance::configuration::Configuration; use anyhow::Result; +use selene_core::gatewire::DynamicGateSet; #[repr(C)] pub struct VoidResult { @@ -512,46 +513,49 @@ pub unsafe extern "C" fn selene_qfree(instance: *mut SeleneInstance, q: u64) -> } #[unsafe(no_mangle)] -pub unsafe extern "C" fn selene_rxy( +pub unsafe extern "C" fn selene_gate( instance: *mut SeleneInstance, - qubit_id: u64, - theta: f64, - phi: f64, -) -> VoidResult { - with_instance_void(instance, |instance| instance.rxy(qubit_id, theta, phi)) -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn selene_rz( - instance: *mut SeleneInstance, - qubit_id: u64, - theta: f64, -) -> VoidResult { - with_instance_void(instance, |instance| instance.rz(qubit_id, theta)) -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn selene_rzz( - instance: *mut SeleneInstance, - qubit_id: u64, - qubit_id2: u64, - theta: f64, + data: *const u8, + data_len: usize, ) -> VoidResult { with_instance_void(instance, |instance| { - instance.rzz(qubit_id, qubit_id2, theta) + let gate = selene_core::gatewire::OwnedGateInstance::deserialize(unsafe { + std::slice::from_raw_parts(data, data_len) + })?; + instance.gate(&gate) }) } #[unsafe(no_mangle)] -pub unsafe extern "C" fn selene_rpp( +pub unsafe extern "C" fn selene_register_gateset( instance: *mut SeleneInstance, - qubit_id: u64, - qubit_id2: u64, - theta: f64, - phi: f64, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> VoidResult { with_instance_void(instance, |instance| { - instance.rpp(qubit_id, qubit_id2, theta, phi) + if written.is_null() { + anyhow::bail!("written pointer is null"); + } + let input = unsafe { std::slice::from_raw_parts(input, input_len) }; + let incoming = DynamicGateSet::deserialize(input)?; + let outgoing = instance.register_gateset(&incoming)?; + let bytes = outgoing.serialize(); + unsafe { + *written = bytes.len(); + } + if output.is_null() { + return Ok(()); + } + if output_len < bytes.len() { + anyhow::bail!("output buffer is too small"); + } + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), output, bytes.len()); + } + Ok(()) }) } diff --git a/selene-sim/rust/selene_instance/quantum.rs b/selene-sim/rust/selene_instance/quantum.rs index 12245030..ef715047 100644 --- a/selene-sim/rust/selene_instance/quantum.rs +++ b/selene-sim/rust/selene_instance/quantum.rs @@ -1,5 +1,6 @@ use crate::selene_instance::SeleneInstance; use anyhow::Result; +use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; impl SeleneInstance { pub fn qalloc(&mut self) -> Result { @@ -10,21 +11,8 @@ impl SeleneInstance { self.emulator.user_issued_qfree(q) } - pub fn rz(&mut self, qubit_id: u64, theta: f64) -> Result<()> { - self.emulator.user_issued_rz(qubit_id, theta) - } - - pub fn rxy(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - self.emulator.user_issued_rxy(qubit_id, theta, phi) - } - - pub fn rzz(&mut self, qubit_id: u64, qubit_id2: u64, theta: f64) -> Result<()> { - self.emulator.user_issued_rzz(qubit_id, qubit_id2, theta) - } - - pub fn rpp(&mut self, qubit_id: u64, qubit_id2: u64, theta: f64, phi: f64) -> Result<()> { - self.emulator - .user_issued_rpp(qubit_id, qubit_id2, theta, phi) + pub fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + self.emulator.user_issued_gate(gate) } pub fn qubit_reset(&mut self, q: u64) -> Result<()> { @@ -70,4 +58,7 @@ impl SeleneInstance { pub fn simulate_delay(&mut self, delay: u64) -> Result<()> { self.emulator.simulate_delay(delay) } + pub fn register_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + self.emulator.register_gateset(gateset) + } } diff --git a/uv.lock b/uv.lock index 15239e75..2dfe145d 100644 --- a/uv.lock +++ b/uv.lock @@ -65,6 +65,95 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, ] +[[package]] +name = "blake3" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/6a/4cc5a9dd40fd8a6d283fd3761e5f59c490109571ef8e3c73245417e5a305/blake3-1.0.9.tar.gz", hash = "sha256:5fa374fa5070ca084368776c19b420157eb0f2d3f091343d6bc59189929d62e2", size = 116872, upload-time = "2026-06-22T18:02:25.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/2f/5398493cef29d9f216be1ff74a303e809e4958a633a44545035a98af4f60/blake3-1.0.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:38e61d3b0386af16b3c03a18e0db82b626d63796274637a1fef855fd1c778d82", size = 346497, upload-time = "2026-06-22T17:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/8aeca9a40899258353a8f79ad164fba1184bc1554ca18607cab4671952f3/blake3-1.0.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9e1d0392624c2f9d049d786f0dc547ce818d2f2b356bcf1c4d74b6f9cc026b4", size = 335390, upload-time = "2026-06-22T17:59:59.162Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0a/74c67827a9cae097ccab7015018182da9cfec347c686a25ef33faf2f46a1/blake3-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8114fb2a1f6cba9cba5411d62cbcb283b2205b154d0076f20b77e22592eb2719", size = 378100, upload-time = "2026-06-22T18:00:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8e/cef564771169b6fb429d9c52652dd2da8c9bbadb63d2d66f232f8bf045de/blake3-1.0.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b985eb08db76550ec97444e03b10acd737baa03fd98aaf3b8455a1c644c8f5d6", size = 377559, upload-time = "2026-06-22T18:00:01.822Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2df756e410d18aba6fef6392b35b835c76412709739a2cde552d246afa4b/blake3-1.0.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a517f0460007edec3767595115c520ed1f157ddd0ed23dddbf6b9d8b0082afb6", size = 451544, upload-time = "2026-06-22T18:00:03.293Z" }, + { url = "https://files.pythonhosted.org/packages/88/69/44423d63e7c6d09000ce69784dd9fb45bda93237f1d2f611099f5ffe27c7/blake3-1.0.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dad0a8a716dd201860f8e82011a340e6bdd5ee37a8eb4357b48ac64c4e6de1c2", size = 492654, upload-time = "2026-06-22T18:00:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/a2/02/7ca45b504796a755bcd765e54f0c6762c16a1dac1adec3a03a45ae9c2f12/blake3-1.0.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bca166d0b01c00dcf2a936f790ed947bd9079b0a0a7df1b76746f201aa4f4ac4", size = 387295, upload-time = "2026-06-22T18:00:06.026Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e4/c8fa46a0e24cb877fbf28f839d8ceda39418259f677ec55d680ea433b62b/blake3-1.0.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6e5c7533c915a24d840ae4be787e9a6059be7e77944b005b3d967a0257a17d", size = 387632, upload-time = "2026-06-22T18:00:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b3/6315be017515868126e106f3dfe50223fbbb87bed67109bfbf883228f505/blake3-1.0.9-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:24acb1e6f31021fa08b7eb31433035facfcf0d82e964170d5eb85a30ce913ba9", size = 384740, upload-time = "2026-06-22T18:00:08.747Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e8/fe7e40384c0f7995fe8dca57428241768897533b9e17cbc367c1614ef82f/blake3-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:216977b1d592a60150cd5de64d5853dc6afb0eb522cb387723ae7f78f380d947", size = 553251, upload-time = "2026-06-22T18:00:10.192Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/e9ecb843308db2b5ca29d604589a15f50d13c20df792260053bf9f014de4/blake3-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6f2dd643166dfeb7cf4ad53eb2d801f944d247212d3481950b4d5b4a20551461", size = 595209, upload-time = "2026-06-22T18:00:11.644Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/201d43f8fee831693f34f7b384a65e41ab7720e6cd8d775cb57d9da69993/blake3-1.0.9-cp310-cp310-win32.whl", hash = "sha256:c755044ba7bec3d03dae44b968194112f0eb0e8c4523465f3dd9e1a87e178d89", size = 231157, upload-time = "2026-06-22T18:00:13.035Z" }, + { url = "https://files.pythonhosted.org/packages/f2/12/f23a64ba2ef270457345499f857628757fafd83f52274c1588e1b4a5b4c0/blake3-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:8cd10c6a421a7d3c81136658e52e9ef58bfcc1df04193466664eb24981784f4c", size = 220829, upload-time = "2026-06-22T18:00:14.298Z" }, + { url = "https://files.pythonhosted.org/packages/27/12/aa8d72228b6ff61c675bd6f55ab138a91d71499c8a707cc9fb2052f1d2b5/blake3-1.0.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f169519c7ef25ef2c446b05e2f08e7e59fae312d569f98a3134b38d4caf7abd4", size = 346253, upload-time = "2026-06-22T18:00:15.537Z" }, + { url = "https://files.pythonhosted.org/packages/72/3a/820d2f729dfe152d5ebde16390f808c762dce3f21fb764ab033803ff2b1a/blake3-1.0.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5e1f21b49492d01fa5a02084894c491ab9e7a1867fced107f7126c80d067c94", size = 335497, upload-time = "2026-06-22T18:00:16.942Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d6/d5462ec19a7f3d084fe327e08618fa107799ee708df04b3a2d620bd62816/blake3-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ee96daaa850700fd342a811fa10a8780fd2e8464a71b83a1779c7b6becd3dd5", size = 377621, upload-time = "2026-06-22T18:00:18.389Z" }, + { url = "https://files.pythonhosted.org/packages/92/98/dbc433f2a45be1b2344a6035d4212dfb6e6eb45046ad15103ead9c82d491/blake3-1.0.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09deb024cd75cb200e7f647cd038800e6edc8f190c8188e0c69ec1c2b920e125", size = 377495, upload-time = "2026-06-22T18:00:20.067Z" }, + { url = "https://files.pythonhosted.org/packages/e0/3d/c7a699fb60d8ed31f3f28e6aec7658d29e45ec89e7054906b3040ce3ee65/blake3-1.0.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c99afb0459c82dd13e456b6b68d45c4768b539ca998dacd3ed726f1e75e91dc", size = 451158, upload-time = "2026-06-22T18:00:21.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a1/0b1b0dbf2dd772483e372237bb65385602b019e24b67424b1fc9e5447837/blake3-1.0.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28528d1f29e6f3d45faf3482e1197e5e175730eef38bdc74e56ee11b68e0ad0d", size = 491988, upload-time = "2026-06-22T18:00:22.984Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d1/ed319477f6d263a4f6b7e9aa465b06be5235a854923edbc9ea09508b6638/blake3-1.0.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65c0c20014df687694af5ccf0cec3bdb194511da8ebd50c30b0fd55c83fa4fd5", size = 386848, upload-time = "2026-06-22T18:00:24.319Z" }, + { url = "https://files.pythonhosted.org/packages/80/3e/a4cfb269f3e0955598b415a7843c358c4f79e826e3c9118dc9fb1f101ee6/blake3-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:964b642631a3c8fe117b3439c8ae64a9a0981af9444e409656d1f1e464bfa125", size = 387842, upload-time = "2026-06-22T18:00:25.589Z" }, + { url = "https://files.pythonhosted.org/packages/59/0e/d4ee3d89eece42f86eb46663aa42702000516b7ffbc53f60b918efe95b57/blake3-1.0.9-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2fd000708662b04be211a22c1095b65fe399d7276e9f3bb2fd1ef8aacc545791", size = 384317, upload-time = "2026-06-22T18:00:26.891Z" }, + { url = "https://files.pythonhosted.org/packages/3a/aa/317106349d10de3b51332ad1e761f4864ebe887854396b75975304dcfbd1/blake3-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:82ecade6ac425fdfc39a4371d6d9232fd6e5c28748fd8d3489016ead17407014", size = 553005, upload-time = "2026-06-22T18:00:28.246Z" }, + { url = "https://files.pythonhosted.org/packages/39/cc/7fbce61a0b24bda1aac99da674bd74ac2b687b61db071c888ffdb30cb47a/blake3-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b4102ba86b86c992a931b4a88c58a632d6097461e14a1e63ebd2ecb98ff0898f", size = 595086, upload-time = "2026-06-22T18:00:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/6ddc7a8b582a0871f23d6db722f4950a8918096d5fa10f9f0f992c2aea39/blake3-1.0.9-cp311-cp311-win32.whl", hash = "sha256:2f4ce45da903f3d0a7e342fa70c7cce9c10cef6b529eadb4d6213be0ab0eaf84", size = 231230, upload-time = "2026-06-22T18:00:31.247Z" }, + { url = "https://files.pythonhosted.org/packages/23/68/ea698e6df48eeb417671544cfbb18c60f863cb689306cc52f19666dd98f8/blake3-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:d819457dccfd82fe34684ec99e36725f747bd5761a0e17f537387fb31d121193", size = 220622, upload-time = "2026-06-22T18:00:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d2/9bdf8345c70993aaef635398f52edfb915d6e8ad2c000c801204e387c456/blake3-1.0.9-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a70c20542d5e7960983a0ff32999049a2b0e5ef1f22dbbbdfb51cf04828a4156", size = 344587, upload-time = "2026-06-22T18:00:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/be8b1f7f85b12bb45a0fade6ca7bdbf83a507d23d0b6141ba29fe69c8cea/blake3-1.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:72cdecf088a9d25e6ec79948a578995649b0dbee407e7a46c543a9ecc0f6f281", size = 328864, upload-time = "2026-06-22T18:00:35.59Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/66580635d744c826671fd219938caffb16281a26f62c4f856695d4233677/blake3-1.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42fa57bf462285ef16400601b0fd32214c248ba92505bbb94b1221ab9af5a092", size = 373795, upload-time = "2026-06-22T18:00:36.887Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/b5b17d3004bb81a5732c0b176c812703d200ed8c652b3b7713b9633bbe10/blake3-1.0.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b25ccde5a64be070f20e5c7a81da70292db40b164b6c77588cbd6230856badbb", size = 374183, upload-time = "2026-06-22T18:00:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/3c/63/0d209c44b2041bbe130ced12a23c92dd995fbfe5bce7ee77fffea16f5cb0/blake3-1.0.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a800b87433955f37691b5f361ad29c7dd3ee089c9cd109adc5aea8e24bc4c1f", size = 446783, upload-time = "2026-06-22T18:00:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/c5/51/efd1f9b8a9d3e9a0e235f3ced99a738529a1019fe78b3988e29d9c2fbba6/blake3-1.0.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6879739e7904b9c42afbedbcc2e8c36cebe140fb3fc3f5c492993579cf5cd516", size = 487369, upload-time = "2026-06-22T18:00:40.875Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/a8dcaea9e0b26e419a540ca0cd6203c9fbb505e85b02b03c5a59bf9e6a45/blake3-1.0.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6edeb3d49a24c307995899b70dd47aa901d0e9ad51d2f8a79aba4f074f32d8c5", size = 383845, upload-time = "2026-06-22T18:00:42.251Z" }, + { url = "https://files.pythonhosted.org/packages/f6/10/e9907f5b86410d5071982aaf05d149ca4d4fd8acab7e77eebbc9a333c7b4/blake3-1.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd56a7a972c4185070f7042ccc20166927eec3c0f98b8405f375d007b604a0b", size = 383851, upload-time = "2026-06-22T18:00:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/c7863a185550706a9624f6aa7b6d46470aaed0bb46a827c5cda2a7d03151/blake3-1.0.9-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:a288664d08dee154cc496e06e62517fc9e655ecec12b0d7db538d244ac79edf1", size = 380067, upload-time = "2026-06-22T18:00:45.249Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/e7af679c719368b400c9ba9c3460072aac2ba077ddbd4bc806fef28cda03/blake3-1.0.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:91db52a809b68b5bebe7c413ddcd230e1f759398e7fa7a873104595a4fa648b6", size = 549471, upload-time = "2026-06-22T18:00:46.793Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/37c1dd3539b7bd9b6d2eef019802aacdb4a3d48ab484b140603bbf9c5b5a/blake3-1.0.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfaa671b07eb73883162ca940442193868358b0b904cfa266e4b74131ce966da", size = 591396, upload-time = "2026-06-22T18:00:48.122Z" }, + { url = "https://files.pythonhosted.org/packages/ae/55/4f0a23b72795292e74084834130900ea778c0583004519c86698dfffe1a5/blake3-1.0.9-cp312-cp312-win32.whl", hash = "sha256:ae47c3d5729ff89baa6ddf6de47fcfcc915985d39eb1bfcd6db653331f3c6fcc", size = 229271, upload-time = "2026-06-22T18:00:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/7db93e4689f0f145bcb954dc62936e5f5090548a9fa20c6bbebfaeaa648a/blake3-1.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:15566065ff90ab3da46ec0be1417406f00507af902b6fb0fbc6563e77f02fc42", size = 218220, upload-time = "2026-06-22T18:00:50.659Z" }, + { url = "https://files.pythonhosted.org/packages/41/1b/95b473d649f5322e69674622a307ffdb4f0b63adb0a0adcbc5cb8a8833c2/blake3-1.0.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:69ff5aebc7650954443aa701feff2028d7c7ea5b5e18ee265f15e2104e892328", size = 343869, upload-time = "2026-06-22T18:00:51.936Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9d/adec22c719d8451af1dc9e624bf5907008ef1e0afa51aa69fd1e8c91e60e/blake3-1.0.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0cdfeff65488089ef86f7587c76055ff72b28d28d10e427b547f5711477c376d", size = 328482, upload-time = "2026-06-22T18:00:53.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/aa/0a6967ff9a6ae182419a681aed54f7338b34a1f71372e90f787a2afa42e6/blake3-1.0.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:766f1555cbe614f14f399c2fbec0983568d20edb36837ba04040807eb9e1a609", size = 373616, upload-time = "2026-06-22T18:00:54.701Z" }, + { url = "https://files.pythonhosted.org/packages/1c/51/5d4e198bf3ae902c6697ad6ec77d7210736ad8f680980e8b648dcfcd09a0/blake3-1.0.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:128a62136c9a39c7cb9fdaa5fb38471f2418853da7f5a89f31495735d0ba6f2c", size = 374149, upload-time = "2026-06-22T18:00:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/7e/62/d3c7c364925b3f10828e5137376f3947f112c32188e899b42f09c2fde98a/blake3-1.0.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1ea0bf17b184b03444007646d902207d2b4d4f3e91a0cac3836552d83db74b9", size = 446151, upload-time = "2026-06-22T18:00:57.378Z" }, + { url = "https://files.pythonhosted.org/packages/b1/01/55b89389c5036c9d24b1d762d6265e91552e10b76a3c99fece3c4a7a4783/blake3-1.0.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73a48f7e9f0e047f51a445d9b0361ab1907bdc72b6857815a84dacd2e59556f8", size = 487256, upload-time = "2026-06-22T18:00:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7a/a21b52253292ad3e4df63ea4a01ce11d3ee8f4a8a8d80eaf0c7ce92a62bd/blake3-1.0.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b27550ada40f839aca64c66127940e4318bb6ef3e291890ef913017f6f637448", size = 383977, upload-time = "2026-06-22T18:01:00.192Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/fe7188201a29ee9b042616c786a98afd864d537ca96198e64c3fe4ff13a9/blake3-1.0.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c84dbc2a31eda88b55bbf5c5b711037bf0698eba0fd1faf06bdaf313c39048", size = 383615, upload-time = "2026-06-22T18:01:01.557Z" }, + { url = "https://files.pythonhosted.org/packages/22/08/f6a213b950e30fe9ef7d7fc061ec388e66ed62643570226882e6f7136ea3/blake3-1.0.9-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:dab59b324aa65c09e937d6c43de5de85ec9581627f4e79dcc9806d85b54a1c34", size = 380288, upload-time = "2026-06-22T18:01:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/b171e47c1b835483bcf1545ebc289458165f8dc0f5c7f74a9176d7e9af03/blake3-1.0.9-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:eca281fedcbe5c56655bd5a4176e6036eddbbe57df96114a03838fce08b1e0ca", size = 549122, upload-time = "2026-06-22T18:01:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d8/7bf71c2c85a0951e406971f151435e0751716907e3924c6c48a2d6dae0db/blake3-1.0.9-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3cbe7f190164896dc3908e920716ee66bc31d40f1a0fb603ed59ac53290fb9cf", size = 591183, upload-time = "2026-06-22T18:01:06.259Z" }, + { url = "https://files.pythonhosted.org/packages/20/85/34c3ea03cc90b2516628494ab3e0a98aec4ca8b04d037840ccd390e480ca/blake3-1.0.9-cp313-cp313-win32.whl", hash = "sha256:508ccaf8f9377cc47e6026c2897fdc37de61faeb1420dc023b6379cc2474eb65", size = 229053, upload-time = "2026-06-22T18:01:07.638Z" }, + { url = "https://files.pythonhosted.org/packages/db/2e/f09e8ed426f360aa2005206466ceab2f707486eb5d9db7051dbcbae056d1/blake3-1.0.9-cp313-cp313-win_amd64.whl", hash = "sha256:caded2806d2cbeed638c5e2517ed8b2a94165b3452fda35e72896142d22070e0", size = 217589, upload-time = "2026-06-22T18:01:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4b/b2dd7c25378a3b5de30ed908d38e6427bc4c644c0c12e8359361abd3a9ca/blake3-1.0.9-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ab0c030cf6644c30e786b0e785bde4e4596013ae9ea6ce9877e39d52383e25d7", size = 345406, upload-time = "2026-06-22T18:01:10.311Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/c0dab2963ddf04a4a938363f61716f9b75de6d3a9bc4a89e78f0854d4d31/blake3-1.0.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83b4a2336105af3800f7e17ac4b943f293a3927a2d66a6308d50dba944a6953e", size = 330077, upload-time = "2026-06-22T18:01:11.926Z" }, + { url = "https://files.pythonhosted.org/packages/20/f1/d03950a86d105a6332a8c422cb87658a7d247e214f1ea8f29ed09ff04e00/blake3-1.0.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95fc3545f80901b0dcd0508d16bc40f15ae39556709fa6cf86675f742d4f3c9c", size = 375147, upload-time = "2026-06-22T18:01:13.198Z" }, + { url = "https://files.pythonhosted.org/packages/10/75/711b1842e0a90aaad6a1c9a9022e90aa16206ac1f224516118bc24482532/blake3-1.0.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1bd981dc318c05375c3160a99df493b7cc4c83fffa1a34d14b18a071b47b262b", size = 373711, upload-time = "2026-06-22T18:01:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a0/f512799d1d0c0b4718fa6f0e99ccbe108e98bac7bf82c200803a62b57876/blake3-1.0.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:689a7e4069de681d9c5d9445b8b6473ee880ad04d7960a6789c60bd788980250", size = 446993, upload-time = "2026-06-22T18:01:15.924Z" }, + { url = "https://files.pythonhosted.org/packages/60/fb/6636ae8a46fc3352694188f5a5a325567782bc88fd1823b0b67be2c92184/blake3-1.0.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8adb0b0032e53919ee95b3d4f911448d3268316c28cd7df232ff2a1e7c9a4ba4", size = 488478, upload-time = "2026-06-22T18:01:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c5/a2b3c086f7e37c9db6017dc2890a76ad2a729e4a554896e855e511811e6b/blake3-1.0.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32bd4521ec2d477627ad93eb70f9ac4d01e12d1489024159bcaeff79466332f6", size = 384900, upload-time = "2026-06-22T18:01:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b8/1298806dd6c464a6f807df24c9640ad3bf27ee54ff4de82b2b5a823a8aba/blake3-1.0.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65d77eb05331495485048f6804f53885b192b998acb7e6fe1487d941bf08435", size = 384333, upload-time = "2026-06-22T18:01:20.35Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cc/0c29d9404155adfd6db716e9765d36ea6cbed287060759f5d764f0d9d99e/blake3-1.0.9-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ca7dfe8fb197ff8a3f5c915424183ccd52a99e8afb12680f51b2e1f4c9c6c97f", size = 381142, upload-time = "2026-06-22T18:01:21.744Z" }, + { url = "https://files.pythonhosted.org/packages/d6/91/9af20d563f0ced71e08a60fc0ee534146da4e265710ed6792d5d799f4c0f/blake3-1.0.9-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f5c9d57f0dcb92243b6ae575c3065793edc9df9008d0ebd98d8245cdeb7c3f84", size = 550587, upload-time = "2026-06-22T18:01:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fa/06f46fc0aa486b799d776f9a80ed0b3605e2be1570cf48007860948aa5d9/blake3-1.0.9-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:172d44245a19dfec08ab771c1b7a506b97783163cdc65f559fe020007e403c99", size = 591888, upload-time = "2026-06-22T18:01:24.805Z" }, + { url = "https://files.pythonhosted.org/packages/50/68/d6198f4069a7c4a184ed854df45b82cc3e2d4b0be476b2a3ee65ad2344cf/blake3-1.0.9-cp314-cp314-win32.whl", hash = "sha256:249e5964fa9e768924bc7cc3d4efe75a425bb5dd3fb7671c3eda8eeddfa50591", size = 229410, upload-time = "2026-06-22T18:01:26.24Z" }, + { url = "https://files.pythonhosted.org/packages/63/ab/f29af72a8312b3827b50e55491f1bf9ae2347591de5c47365c5cbd2525a9/blake3-1.0.9-cp314-cp314-win_amd64.whl", hash = "sha256:0aba416bb2e3ef0c65e74d5eba21062483c714cd78e7e303c9d03c547fc7d015", size = 218526, upload-time = "2026-06-22T18:01:27.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/7e/d932fe437ccf656cfba77abc466fb3d1a0ce3c31df92e760d9e4c34932b4/blake3-1.0.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5b35abe24a66a7b3db423eb4f8668ed7be1a362aa9c0024ab6483ec0b2c16058", size = 345049, upload-time = "2026-06-22T18:01:29.228Z" }, + { url = "https://files.pythonhosted.org/packages/55/1e/d92fb284fcacf86f5d1083e29d0a8c834b60432786928915238d9760f514/blake3-1.0.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bbdff61e049297ef3180867ce1f079cea7e5b372fd76953c3183da5b8124206", size = 329367, upload-time = "2026-06-22T18:01:30.566Z" }, + { url = "https://files.pythonhosted.org/packages/9d/da/e25fa75d5bfea4527fc21024dde86a9376db798e469a084741968299f215/blake3-1.0.9-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a69fcedf06785bb81d4d3d39f95ee65dbaf2cb246e174cfc9ff64d027f7551", size = 374203, upload-time = "2026-06-22T18:01:31.998Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4d/0224916202b773dfdf08dcbe4ed1ad1018d4ddcd4df7a7e2978d28f89b74/blake3-1.0.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5d5bf0f68cd77108a942c95db98e960d9c3d5643b95172f783822ce22667759", size = 373713, upload-time = "2026-06-22T18:01:33.387Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e5/4ba968831b7afaec431c588c826cef76a96d6d6976188ed07d932072e673/blake3-1.0.9-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9767f16199b99aa022b61ff825ac4dbd39864bf637ae712605a2ce1f8b6a55e0", size = 446574, upload-time = "2026-06-22T18:01:34.687Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f5/08a9099c7177f282d2563abe4f7cc626c636642f7979cf58f2ab7ded2096/blake3-1.0.9-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865a8cfb2b3d7c0baf5267f2fa6816a3384e836cd1bd0caf359f406cb1e8fba", size = 487232, upload-time = "2026-06-22T18:01:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/9392bf1ebc81b5b09ce58b94613fa2d37308e825ff2dc7b54d00ee622c77/blake3-1.0.9-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42609e4adc4b2d7423137f2cb35135bca598b925c5af09d2bc0a2c368b25aeb1", size = 384751, upload-time = "2026-06-22T18:01:37.512Z" }, + { url = "https://files.pythonhosted.org/packages/84/fc/b6e9aef02ca14ef62fa47783b9eeeb5b2d3f73fdf698d8bb94c36f5dd69f/blake3-1.0.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f648fa425138452d1e585ac625c7aefddb946d9765906c4c12d564a1523cd8", size = 384546, upload-time = "2026-06-22T18:01:38.868Z" }, + { url = "https://files.pythonhosted.org/packages/ff/cb/452e92dba9402b36a953aa8b9b06253445ccce43dcd0bcf521c5e3c3e15d/blake3-1.0.9-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:9cef6d4d07a7de0c44f5ba17f6383d55276d9efc8d601f75113538fcaa35008b", size = 380596, upload-time = "2026-06-22T18:01:40.412Z" }, + { url = "https://files.pythonhosted.org/packages/b2/01/7a84a7e10c5d14e6ed8a4403bd7f64c1e01f8ebabea0d6fe5f093b894cbd/blake3-1.0.9-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:28404301de485e9546365d01b30f65eaa835520c4211d6ef61242975b6722b60", size = 550032, upload-time = "2026-06-22T18:01:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/58/7d/7aea0222f59cf84044ec52e2bfdaa0e3c355d221292b0ea1b722cf1edd6c/blake3-1.0.9-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8a99f896e7718050ed033a888245098aab3d6a5338f91cc9450c563b53f90ad5", size = 592244, upload-time = "2026-06-22T18:01:43.426Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e5/b44c230108745ff9c70c7bbafe22563772bc0c22322a8d15c10455f6ca02/blake3-1.0.9-cp314-cp314t-win32.whl", hash = "sha256:021309d760b390706fecf13498f9a25aa8f689bbb65a0896029b8fa223aae18b", size = 229481, upload-time = "2026-06-22T18:01:45.307Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/ac03f37dc9aeebf398d42089720648b3bc8438e733d3e522196c5d12ab39/blake3-1.0.9-cp314-cp314t-win_amd64.whl", hash = "sha256:5ea0c60dd9c1e3d05610606579e4bf80f562854c46ed55f9ee8545e18987a480", size = 217979, upload-time = "2026-06-22T18:01:46.629Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -1229,9 +1318,10 @@ wheels = [ [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "0.3.0a1" source = { editable = "selene-core" } dependencies = [ + { name = "blake3" }, { name = "hugr" }, { name = "lief" }, { name = "llvmlite", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, @@ -1252,6 +1342,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "blake3", specifier = ">=1.0.0" }, { name = "hugr", specifier = ">=0.13.0" }, { name = "lief", specifier = ">=0.16.5" }, { name = "llvmlite", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = "~=0.47" }, @@ -1269,7 +1360,7 @@ dev = [{ name = "qir-qis", specifier = "~=0.1.6" }] [[package]] name = "selene-sim" -version = "0.3.0-alpha.1" +version = "0.3.0a1" source = { editable = "." } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, From 064d182cc05ac2cd0bfe97e25380eb4c855075b8 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:56:08 +0100 Subject: [PATCH 02/19] Update C headers --- selene-core/c/include/selene/core_types.h | 15 +++++++++++++++ selene-core/c/include/selene/error_model.h | 15 +++++++++++++++ selene-core/c/include/selene/gatewire.h | 4 +++- selene-core/c/include/selene/runtime.h | 15 +++++++++++++++ selene-core/c/include/selene/simulator.h | 15 +++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/selene-core/c/include/selene/core_types.h b/selene-core/c/include/selene/core_types.h index fc2e399f..25367ce2 100644 --- a/selene-core/c/include/selene/core_types.h +++ b/selene-core/c/include/selene/core_types.h @@ -8,3 +8,18 @@ typedef struct Operation Operation; typedef int32_t SeleneErrno; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, + size_t *out); + +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, + size_t qubit_index, + uint32_t *out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/selene-core/c/include/selene/error_model.h b/selene-core/c/include/selene/error_model.h index 248ebfbb..60f2639a 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -170,3 +170,18 @@ typedef struct SeleneErrorModelPluginDescriptorV1 { size_t output_len, size_t *written); } SeleneErrorModelPluginDescriptorV1; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, + size_t *out); + +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, + size_t qubit_index, + uint32_t *out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/selene-core/c/include/selene/gatewire.h b/selene-core/c/include/selene/gatewire.h index ad0d7a75..2b1ffc0c 100644 --- a/selene-core/c/include/selene/gatewire.h +++ b/selene-core/c/include/selene/gatewire.h @@ -183,7 +183,9 @@ GwStatus gw_decoded_gate_value_at(const GwDecodedGate *gate, size_t index, GwGat GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); -GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, size_t qubit_index, uint32_t *out); +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, + size_t qubit_index, + uint32_t *out); GwStatus gw_gateset_validate_decoded(const GwGateSet *set, const GwDecodedGate *gate, diff --git a/selene-core/c/include/selene/runtime.h b/selene-core/c/include/selene/runtime.h index 3e68791c..bc4abd9b 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -147,3 +147,18 @@ typedef struct SeleneRuntimePluginDescriptorV1 { size_t output_len, size_t *written); } SeleneRuntimePluginDescriptorV1; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, + size_t *out); + +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, + size_t qubit_index, + uint32_t *out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index 8af24992..a8293580 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -68,3 +68,18 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { size_t output_len, size_t *written); } SeleneSimulatorPluginDescriptorV1; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, + size_t *out); + +GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, + size_t qubit_index, + uint32_t *out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus From e05a0d6b95b092c146ffde621e234571b9e1d135 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:40:50 +0100 Subject: [PATCH 03/19] feat(core): add typed builtin gateset unions --- selene-core/python/selene_core/__init__.py | 6 + selene-core/python/selene_core/gatewire.py | 22 ++- selene-core/rust/gatewire.rs | 6 +- selene-core/rust/gatewire/builtin.rs | 193 ++++++++++++++++++++- selene-core/rust/gatewire/dynamic.rs | 21 ++- selene-core/rust/gatewire/ffi/functions.rs | 2 +- selene-core/rust/gatewire/tests.rs | 41 ++++- selene-core/rust/gatewire/typed.rs | 6 + 8 files changed, 282 insertions(+), 15 deletions(-) diff --git a/selene-core/python/selene_core/__init__.py b/selene-core/python/selene_core/__init__.py index 60cab242..c92c2a9e 100644 --- a/selene-core/python/selene_core/__init__.py +++ b/selene-core/python/selene_core/__init__.py @@ -24,11 +24,14 @@ GateDefinition, GateValue, Gateset, + HeliosGateSet, OperandDefinition, OperandKind, PhasedX, PhasedXX, + QuantinuumGateSet, RZ, + SolGateSet, ZZPhase, builtin_gateset, phased_x, @@ -62,11 +65,14 @@ "GateDefinition", "GateValue", "Gateset", + "HeliosGateSet", "OperandDefinition", "OperandKind", "PhasedX", "PhasedXX", + "QuantinuumGateSet", "RZ", + "SolGateSet", "ZZPhase", "builtin_gateset", "phased_x", diff --git a/selene-core/python/selene_core/gatewire.py b/selene-core/python/selene_core/gatewire.py index 225190b8..35257b38 100644 --- a/selene-core/python/selene_core/gatewire.py +++ b/selene-core/python/selene_core/gatewire.py @@ -169,6 +169,22 @@ def __init__(self, *definitions: GateDefinition | Iterable[GateDefinition]): def definitions(self) -> tuple[GateDefinition, ...]: return self._definitions + def union(self, *others: Gateset) -> Gateset: + definitions = list(self._definitions) + seen = {definition.semantic_id: definition for definition in definitions} + for other in others: + for definition in other: + existing = seen.get(definition.semantic_id) + if existing is not None: + if existing != definition: + raise ValueError( + f"duplicate gate semantic_id for {definition.name}" + ) + continue + definitions.append(definition) + seen[definition.semantic_id] = definition + return Gateset(definitions) + def bind(self, gate: bytes | bytearray | memoryview | GateDefinition) -> BoundGate: return BoundGate(self.definition(gate)) @@ -401,9 +417,13 @@ def _builtin(name: str, operands: Iterable[tuple[str, OperandKind]]) -> GateDefi "PhasedXX", [("q0", QUBIT), ("q1", QUBIT), ("theta", F64), ("phi", F64)] ) +HeliosGateSet: Gateset = Gateset(RZ, PhasedX, ZZPhase) +SolGateSet: Gateset = Gateset(RZ, PhasedX, PhasedXX) +QuantinuumGateSet: Gateset = HeliosGateSet.union(SolGateSet) + def builtin_gateset() -> Gateset: - return Gateset(RZ, PhasedX, ZZPhase, PhasedXX) + return QuantinuumGateSet def rz(q0: int, theta: float) -> Gate: diff --git a/selene-core/rust/gatewire.rs b/selene-core/rust/gatewire.rs index ee02f8e6..61c9f4ee 100644 --- a/selene-core/rust/gatewire.rs +++ b/selene-core/rust/gatewire.rs @@ -31,14 +31,14 @@ pub use operand::{ GW_OPERAND_KIND_U8, GW_OPERAND_KIND_U64, GateOperand, GateValue, OperandKind, Qubit, SmallGateValues, }; -pub use typed::{GateSet, GateSetSpec, GateSpec, TryDecode}; +pub use typed::{GateSet, GateSetSpec, GateSpec, GateView, TryDecode}; pub mod prelude { pub use crate::gatewire::builtin; pub use crate::gatewire::{ Angle, DynamicGateSet, GateDecl, GateError, GateOperand, GateSemanticId, GateSet, - GateSetSpec, GateSpec, GateValue, OperandKind, OperandSpec, OwnedGateInstance, Qubit, - TryDecode, + GateSetSpec, GateSpec, GateValue, GateView, OperandKind, OperandSpec, OwnedGateInstance, + Qubit, TryDecode, }; } diff --git a/selene-core/rust/gatewire/builtin.rs b/selene-core/rust/gatewire/builtin.rs index 66d3e3e9..55f7d25c 100644 --- a/selene-core/rust/gatewire/builtin.rs +++ b/selene-core/rust/gatewire/builtin.rs @@ -11,12 +11,189 @@ crate::define_builtin_gates! { pub struct PhasedXX { q0: Qubit, q1: Qubit, theta: Angle, phi: Angle} } -pub fn all() -> DynamicGateSet { - DynamicGateSet::from_declarations(vec![ - RZ::declaration(), - PhasedX::declaration(), - ZZPhase::declaration(), - PhasedXX::declaration(), - ]) - .expect("builtin gate declarations are unique") +crate::define_gateset! { + pub enum HeliosGateSet { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), + } +} + +crate::define_gateset! { + pub enum SolGateSet { + RZ(RZ), + PhasedX(PhasedX), + PhasedXX(PhasedXX), + } +} + +crate::define_gateset! { + pub enum QuantinuumGateSet { + RZ(RZ), + PhasedX(PhasedX), + ZZPhase(ZZPhase), + PhasedXX(PhasedXX), + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum HeliosGate { + RZ { + qubit_id: u64, + theta: f64, + }, + PhasedX { + qubit_id: u64, + theta: f64, + phi: f64, + }, + ZZPhase { + qubit_id_1: u64, + qubit_id_2: u64, + theta: f64, + }, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SolGate { + RZ { + qubit_id: u64, + theta: f64, + }, + PhasedX { + qubit_id: u64, + theta: f64, + phi: f64, + }, + PhasedXX { + qubit_id_1: u64, + qubit_id_2: u64, + theta: f64, + phi: f64, + }, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum QuantinuumGate { + RZ { + qubit_id: u64, + theta: f64, + }, + PhasedX { + qubit_id: u64, + theta: f64, + phi: f64, + }, + ZZPhase { + qubit_id_1: u64, + qubit_id_2: u64, + theta: f64, + }, + PhasedXX { + qubit_id_1: u64, + qubit_id_2: u64, + theta: f64, + phi: f64, + }, +} + +impl HeliosGateSet { + pub fn dynamic() -> DynamicGateSet { + dynamic_gateset::("Helios") + } +} + +impl SolGateSet { + pub fn dynamic() -> DynamicGateSet { + dynamic_gateset::("Sol") + } +} + +impl QuantinuumGateSet { + pub fn dynamic() -> DynamicGateSet { + HeliosGateSet::dynamic() + .union(&SolGateSet::dynamic()) + .expect("Quantinuum builtin gate declarations are unique") + } +} + +fn dynamic_gateset(name: &str) -> DynamicGateSet { + DynamicGateSet::from_declarations(G::declarations()) + .unwrap_or_else(|_| panic!("{name} builtin gate declarations are unique")) +} + +impl GateView for HeliosGate { + type GateSet = HeliosGateSet; + + fn from_gate(gate: HeliosGateSet) -> Self { + match gate { + HeliosGateSet::RZ(gate) => Self::RZ { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + }, + HeliosGateSet::PhasedX(gate) => Self::PhasedX { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + }, + HeliosGateSet::ZZPhase(gate) => Self::ZZPhase { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + }, + } + } +} + +impl GateView for SolGate { + type GateSet = SolGateSet; + + fn from_gate(gate: SolGateSet) -> Self { + match gate { + SolGateSet::RZ(gate) => Self::RZ { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + }, + SolGateSet::PhasedX(gate) => Self::PhasedX { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + }, + SolGateSet::PhasedXX(gate) => Self::PhasedXX { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + }, + } + } +} + +impl GateView for QuantinuumGate { + type GateSet = QuantinuumGateSet; + + fn from_gate(gate: QuantinuumGateSet) -> Self { + match gate { + QuantinuumGateSet::RZ(gate) => Self::RZ { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + }, + QuantinuumGateSet::PhasedX(gate) => Self::PhasedX { + qubit_id: gate.q0.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + }, + QuantinuumGateSet::ZZPhase(gate) => Self::ZZPhase { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + }, + QuantinuumGateSet::PhasedXX(gate) => Self::PhasedXX { + qubit_id_1: gate.q0.0.into(), + qubit_id_2: gate.q1.0.into(), + theta: gate.theta.0, + phi: gate.phi.0, + }, + } + } } diff --git a/selene-core/rust/gatewire/dynamic.rs b/selene-core/rust/gatewire/dynamic.rs index 7ef121ba..9129c47b 100644 --- a/selene-core/rust/gatewire/dynamic.rs +++ b/selene-core/rust/gatewire/dynamic.rs @@ -4,7 +4,7 @@ use indexmap::IndexMap; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LocalGateId(pub u32); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct DynamicGateSet { declarations: IndexMap, } @@ -36,6 +36,25 @@ impl DynamicGateSet { Ok(local_id) } + pub fn add_all(&mut self, other: &Self) -> Result<(), GateError> { + for decl in other.declarations() { + if let Some(existing) = self.declaration(decl.semantic_id) { + if existing != decl { + return Err(GateError::DuplicateGate(decl.name.clone())); + } + continue; + } + self.add(decl.clone())?; + } + Ok(()) + } + + pub fn union(&self, other: &Self) -> Result { + let mut union = self.clone(); + union.add_all(other)?; + Ok(union) + } + pub fn len(&self) -> usize { self.declarations.len() } diff --git a/selene-core/rust/gatewire/ffi/functions.rs b/selene-core/rust/gatewire/ffi/functions.rs index 15d88cb6..fbaf2eca 100644 --- a/selene-core/rust/gatewire/ffi/functions.rs +++ b/selene-core/rust/gatewire/ffi/functions.rs @@ -82,7 +82,7 @@ pub unsafe extern "C" fn gw_builtin_gateset_new(out: *mut *mut GwGateSet) -> GwS if out.is_null() { return Err(GateError::NullPointer); } - *out = Box::into_raw(Box::new(builtin::all())) as *mut GwGateSet; + *out = Box::into_raw(Box::new(builtin::QuantinuumGateSet::dynamic())) as *mut GwGateSet; Ok(()) }) } diff --git a/selene-core/rust/gatewire/tests.rs b/selene-core/rust/gatewire/tests.rs index 18b94175..1fcab057 100644 --- a/selene-core/rust/gatewire/tests.rs +++ b/selene-core/rust/gatewire/tests.rs @@ -1,5 +1,6 @@ -use super::builtin::{PhasedX, PhasedXX, RZ, ZZPhase}; +use super::builtin::{PhasedX, PhasedXX, QuantinuumGate, RZ, ZZPhase}; use crate::gatewire::{Angle, DynamicGateSet, GateSet, Qubit}; +use crate::runtime::Operation; crate::define_gateset! { enum ExampleGateSet { @@ -60,3 +61,41 @@ fn dynamic_gateset_subset_and_superset_use_semantic_ids() { Some("PhasedXX") ); } + +#[test] +fn dynamic_gateset_union_ignores_identical_duplicates() { + let union = super::builtin::HeliosGateSet::dynamic() + .union(&super::builtin::SolGateSet::dynamic()) + .unwrap(); + + assert!(union.contains(RZ::semantic_id())); + assert!(union.contains(PhasedX::semantic_id())); + assert!(union.contains(ZZPhase::semantic_id())); + assert!(union.contains(PhasedXX::semantic_id())); + assert_eq!(union.len(), 4); + assert_eq!(union, super::builtin::QuantinuumGateSet::dynamic()); +} + +#[test] +fn helios_gateset_is_compatible_with_quantinuum_accepting_plugins() { + let incoming = super::builtin::HeliosGateSet::dynamic(); + let accepted = super::builtin::QuantinuumGateSet::dynamic(); + + assert!(incoming.is_subset_of(&accepted)); + assert_eq!(incoming.first_unsupported_by(&accepted), None); +} + +#[test] +fn builtin_gate_view_decodes_to_plain_fields() { + let op = Operation::phased_xx(1, 2, 0.25, 0.75).unwrap(); + + assert_eq!( + op.as_gate_view::().unwrap(), + Some(QuantinuumGate::PhasedXX { + qubit_id_1: 1, + qubit_id_2: 2, + theta: 0.25, + phi: 0.75, + }) + ); +} diff --git a/selene-core/rust/gatewire/typed.rs b/selene-core/rust/gatewire/typed.rs index 95d50069..36f06127 100644 --- a/selene-core/rust/gatewire/typed.rs +++ b/selene-core/rust/gatewire/typed.rs @@ -20,6 +20,12 @@ pub trait GateSetSpec: Clone + Sized { fn try_from_instance(instance: &OwnedGateInstance) -> Result, GateError>; } +pub trait GateView: Sized { + type GateSet: GateSetSpec; + + fn from_gate(gate: Self::GateSet) -> Self; +} + #[derive(Clone, Debug, PartialEq)] pub enum TryDecode { Decoded(G), From 2538b36669dcab3554206db245de0db439818c73 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:41:09 +0100 Subject: [PATCH 04/19] refactor(core): unify plugin descriptors and operation results --- selene-core/c/include/selene/error_model.h | 131 ++---------- selene-core/c/include/selene/operation.h | 70 +++++++ selene-core/c/include/selene/plugin.h | 21 ++ selene-core/c/include/selene/runtime.h | 61 +----- selene-core/c/include/selene/simulator.h | 71 +++++-- selene-core/cbindgen/error_model.toml | 8 +- selene-core/examples/cbindgen.toml | 8 +- selene-core/examples/error_model/rust/lib.rs | 14 ++ selene-core/examples/runtime/rust/lib.rs | 5 +- selene-core/examples/simulator/rust/lib.rs | 8 + selene-core/rust/error_model.rs | 66 ++++-- selene-core/rust/error_model/helper.rs | 47 +++-- selene-core/rust/error_model/inline.rs | 27 ++- selene-core/rust/error_model/interface.rs | 1 + selene-core/rust/error_model/plugin.rs | 145 +++++-------- selene-core/rust/error_model/version.rs | 2 +- selene-core/rust/lib.rs | 2 +- selene-core/rust/macros.rs | 19 +- selene-core/rust/operation.rs | 26 ++- selene-core/rust/operation/plugin.rs | 86 +++++++- selene-core/rust/plugin.rs | 208 ++++++++++++++++--- selene-core/rust/runtime.rs | 119 ++++++----- selene-core/rust/runtime/helper.rs | 30 ++- selene-core/rust/runtime/inline.rs | 17 +- selene-core/rust/runtime/interface.rs | 1 + selene-core/rust/runtime/plugin.rs | 111 ++++++---- selene-core/rust/simulator.rs | 146 ++++++------- selene-core/rust/simulator/helper.rs | 163 ++++++--------- selene-core/rust/simulator/inline.rs | 122 ++++++++--- selene-core/rust/simulator/interface.rs | 13 +- selene-core/rust/simulator/plugin.rs | 177 +++++++--------- selene-core/rust/simulator/version.rs | 4 +- selene-core/rust/utils.rs | 80 ++++++- 33 files changed, 1225 insertions(+), 784 deletions(-) create mode 100644 selene-core/c/include/selene/operation.h create mode 100644 selene-core/c/include/selene/plugin.h diff --git a/selene-core/c/include/selene/error_model.h b/selene-core/c/include/selene/error_model.h index 60f2639a..827db560 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -1,9 +1,16 @@ +#ifndef SELENE_ERROR_MODEL_H +#define SELENE_ERROR_MODEL_H + #include #include #include #include #include -#include "selene/core_types.h" +#include "selene/gatewire.h" +#include "selene/operation.h" +#include "selene/plugin.h" +#include "selene/runtime.h" +#include "selene/simulator.h" #define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL typedef struct SeleneErrorModelAPIVersion { @@ -27,124 +34,8 @@ typedef struct SeleneErrorModelAPIVersion { typedef void *SeleneErrorModelInstance; -/** - * An instance is provided to `selene_runtime_get_next_operations`, which must - * pass that back to any function it calls in its provided - * [ErrorModelSetResultInterface]. - */ -typedef void *SeleneErrorModelSetResultInstance; - -/** - * A plugin's implementation of `selene_runtime_get_next_operations` is provided - * a pointer to a `ErrorModelSetResultInterface` as well as a - * [ErrorModelSetResultInstance]. It should call the functions - * within to populate a batch. All such calls must pass the instance as the - * first parameter. - */ -typedef struct SeleneErrorModelSetResultInterface { - void (*set_bool_result_fn)(SeleneErrorModelSetResultInstance, - uint64_t, - bool); - void (*set_u64_result_fn)(SeleneErrorModelSetResultInstance, - uint64_t, - uint64_t); -} SeleneErrorModelSetResultInterface; - -typedef int32_t SeleneErrno; - -typedef void *SeleneRuntimeExtractOperationInstance; - -/** - * An instance is provided to `selene_runtime_get_next_operations`, which must - * pass that back to any function it calls in its provided - * [RuntimeGetOperationInterface]. - */ -typedef void *SeleneRuntimeGetOperationInstance; - -typedef struct RuntimeGetOperationInterface { - void (*measure_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*measure_leaked_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*reset_fn)(SeleneRuntimeGetOperationInstance, - uint64_t); - void (*custom_fn)(SeleneRuntimeGetOperationInstance, - size_t, - const void*, - size_t); - void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*gate_fn)(SeleneRuntimeGetOperationInstance, - const uint8_t*, - size_t); -} RuntimeGetOperationInterface; - -typedef struct RuntimeGetOperationHandle { - SeleneRuntimeGetOperationInstance instance; - struct RuntimeGetOperationInterface interface; -} RuntimeGetOperationHandle; - -typedef struct SeleneRuntimeExtractOperationInterface { - void (*extract_fn)(struct RuntimeExtractOperationHandle, - struct RuntimeGetOperationHandle); -} SeleneRuntimeExtractOperationInterface; - -typedef struct RuntimeExtractOperationHandle { - SeleneRuntimeExtractOperationInstance instance; - struct SeleneRuntimeExtractOperationInterface interface; -} RuntimeExtractOperationHandle; - -typedef void *SeleneSimulatorInstance; - -typedef struct SimulatorOperationInterface { - SeleneErrno (*exit_fn)(SeleneSimulatorInstance instance); - SeleneErrno (*shot_start_fn)(SeleneSimulatorInstance instance, - uint64_t shot_id, - uint64_t seed); - SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance instance); - SeleneErrno (*measure_fn)(SeleneSimulatorInstance instance, - uint64_t qubit); - SeleneErrno (*postselect_fn)(SeleneSimulatorInstance instance, - uint64_t qubit, - bool target_value); - SeleneErrno (*reset_fn)(SeleneSimulatorInstance instance, - uint64_t qubit); - SeleneErrno (*get_metric_fn)(SeleneSimulatorInstance instance, - uint8_t nth_metric, - char *tag_ptr, - uint8_t *datatype_ptr, - uint64_t *data_ptr); - SeleneErrno (*dump_state_fn)(SeleneSimulatorInstance instance, - const char *file, - const uint64_t *qubits, - uint64_t n_qubits); - SeleneErrno (*gate_fn)(SeleneSimulatorInstance instance, - const uint8_t *data, - size_t len); - SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance instance, - const uint8_t *input, - size_t input_len, - uint8_t *output, - size_t output_len, - size_t *written); -} SimulatorOperationInterface; - -typedef struct SimulatorHandle { - SeleneSimulatorInstance instance; - struct SimulatorOperationInterface interface; -} SimulatorHandle; - -typedef struct ErrorModelSetResultHandle { - SeleneErrorModelSetResultInstance instance; - struct SeleneErrorModelSetResultInterface interface; -} ErrorModelSetResultHandle; - typedef struct SeleneErrorModelPluginDescriptorV1 { - uint64_t struct_size; - uint64_t api_version; + SelenePluginDescriptorV1 header; SeleneErrno (*init_fn)(SeleneErrorModelInstance *handle, uint64_t n_qubits, uint32_t error_model_argc, @@ -157,7 +48,7 @@ typedef struct SeleneErrorModelPluginDescriptorV1 { SeleneErrno (*handle_operations_fn)(SeleneErrorModelInstance handle, struct RuntimeExtractOperationHandle batch, struct SimulatorHandle simulator, - struct ErrorModelSetResultHandle result); + struct OperationResultHandle result); SeleneErrno (*get_metrics_fn)(SeleneErrorModelInstance handle, uint8_t nth_metric, char *out_tag_str, @@ -185,3 +76,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_ERROR_MODEL_H */ diff --git a/selene-core/c/include/selene/operation.h b/selene-core/c/include/selene/operation.h new file mode 100644 index 00000000..a3131672 --- /dev/null +++ b/selene-core/c/include/selene/operation.h @@ -0,0 +1,70 @@ +#ifndef SELENE_OPERATION_H +#define SELENE_OPERATION_H + +#include +#include +#include + +typedef void *SeleneRuntimeGetOperationInstance; + +typedef struct SeleneRuntimeGetOperationInterface { + void (*measure_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*measure_leaked_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*postselect_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + bool); + void (*reset_fn)(SeleneRuntimeGetOperationInstance, + uint64_t); + void (*custom_fn)(SeleneRuntimeGetOperationInstance, + size_t, + const void*, + size_t); + void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*gate_fn)(SeleneRuntimeGetOperationInstance, + const uint8_t*, + size_t); +} SeleneRuntimeGetOperationInterface; + +typedef void *SeleneRuntimeExtractOperationInstance; + +struct RuntimeExtractOperationHandle; +struct RuntimeGetOperationHandle; + +typedef struct SeleneRuntimeExtractOperationInterface { + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, + struct RuntimeGetOperationHandle); +} SeleneRuntimeExtractOperationInterface; + +typedef struct RuntimeExtractOperationHandle { + SeleneRuntimeExtractOperationInstance instance; + struct SeleneRuntimeExtractOperationInterface interface; +} RuntimeExtractOperationHandle; + +typedef struct RuntimeGetOperationHandle { + SeleneRuntimeGetOperationInstance instance; + struct SeleneRuntimeGetOperationInterface interface; +} RuntimeGetOperationHandle; + +typedef void *SeleneOperationResultInstance; + +typedef struct SeleneOperationResultInterface { + void (*set_bool_result_fn)(SeleneOperationResultInstance, + uint64_t, + bool); + void (*set_u64_result_fn)(SeleneOperationResultInstance, + uint64_t, + uint64_t); +} SeleneOperationResultInterface; + +typedef struct OperationResultHandle { + SeleneOperationResultInstance instance; + struct SeleneOperationResultInterface interface; +} OperationResultHandle; + +#endif /* SELENE_OPERATION_H */ diff --git a/selene-core/c/include/selene/plugin.h b/selene-core/c/include/selene/plugin.h new file mode 100644 index 00000000..557f5be6 --- /dev/null +++ b/selene-core/c/include/selene/plugin.h @@ -0,0 +1,21 @@ +#ifndef SELENE_PLUGIN_H +#define SELENE_PLUGIN_H + +#include +#include +#include +#include +#include + +typedef int32_t SeleneErrno; + +typedef struct SelenePluginDescriptorV1 { + uint64_t struct_size; + uint64_t api_version; + SeleneErrno (*last_error_fn)(char *output, + size_t output_len, + size_t *written); + const char *(*get_name_fn)(void); +} SelenePluginDescriptorV1; + +#endif /* SELENE_PLUGIN_H */ diff --git a/selene-core/c/include/selene/runtime.h b/selene-core/c/include/selene/runtime.h index bc4abd9b..9ec6fd9d 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -1,9 +1,14 @@ +#ifndef SELENE_RUNTIME_H +#define SELENE_RUNTIME_H + #include #include #include #include #include -#include "selene/core_types.h" +#include "selene/gatewire.h" +#include "selene/operation.h" +#include "selene/plugin.h" #define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL typedef struct SeleneRuntimeAPIVersion { @@ -25,58 +30,10 @@ typedef struct SeleneRuntimeAPIVersion { uint8_t patch; } SeleneRuntimeAPIVersion; -/** - * An instance is provided to `selene_runtime_get_next_operations`, which must - * pass that back to any function it calls in its provided - * [RuntimeGetOperationInterface]. - */ -typedef void *SeleneRuntimeGetOperationInstance; - -typedef struct SeleneRuntimeGetOperationInterface { - void (*measure_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*measure_leaked_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*reset_fn)(SeleneRuntimeGetOperationInstance, - uint64_t); - void (*custom_fn)(SeleneRuntimeGetOperationInstance, - size_t, - const void*, - size_t); - void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, - uint64_t, - uint64_t); - void (*gate_fn)(SeleneRuntimeGetOperationInstance, - const uint8_t*, - size_t); -} SeleneRuntimeGetOperationInterface; - -typedef void *SeleneRuntimeExtractOperationInstance; - -typedef struct RuntimeExtractOperationHandle { - SeleneRuntimeExtractOperationInstance instance; - struct SeleneRuntimeExtractOperationInterface interface; -} RuntimeExtractOperationHandle; - -typedef struct RuntimeGetOperationHandle { - SeleneRuntimeGetOperationInstance instance; - struct SeleneRuntimeGetOperationInterface interface; -} RuntimeGetOperationHandle; - -typedef struct SeleneRuntimeExtractOperationInterface { - void (*extract_fn)(struct RuntimeExtractOperationHandle, - struct RuntimeGetOperationHandle); -} SeleneRuntimeExtractOperationInterface; - -typedef int32_t SeleneErrno; - typedef void *RuntimeInstance; typedef struct SeleneRuntimePluginDescriptorV1 { - uint64_t struct_size; - uint64_t api_version; + SelenePluginDescriptorV1 header; SeleneErrno (*init_fn)(RuntimeInstance *handle, uint64_t n_qubits, uint64_t start, @@ -152,6 +109,8 @@ typedef struct SeleneRuntimePluginDescriptorV1 { extern "C" { #endif // __cplusplus +extern SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1; + GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); @@ -162,3 +121,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_RUNTIME_H */ diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index a8293580..d45221b6 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -1,10 +1,15 @@ +#ifndef SELENE_SIMULATOR_H +#define SELENE_SIMULATOR_H + #include #include #include #include #include -#include "selene/core_types.h" -#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL +#include "selene/gatewire.h" +#include "selene/operation.h" +#include "selene/plugin.h" +#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000200ULL typedef struct SeleneSimulatorAPIVersion { /** @@ -27,12 +32,49 @@ typedef struct SeleneSimulatorAPIVersion { typedef void *SeleneSimulatorInstance; -typedef int32_t SeleneErrno; +typedef struct SimulatorOperationInterface { + SeleneErrno (*exit_fn)(SeleneSimulatorInstance instance); + SeleneErrno (*last_error_fn)(char *output, + size_t output_len, + size_t *written); + SeleneErrno (*shot_start_fn)(SeleneSimulatorInstance instance, + uint64_t shot_id, + uint64_t seed); + SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance instance); + SeleneErrno (*handle_operations_fn)(SeleneSimulatorInstance instance, + struct RuntimeExtractOperationHandle batch, + struct OperationResultHandle result); + SeleneErrno (*measure_fn)(SeleneSimulatorInstance instance, + uint64_t qubit); + SeleneErrno (*reset_fn)(SeleneSimulatorInstance instance, + uint64_t qubit); + SeleneErrno (*get_metric_fn)(SeleneSimulatorInstance instance, + uint8_t nth_metric, + char *tag_ptr, + uint8_t *datatype_ptr, + uint64_t *data_ptr); + SeleneErrno (*dump_state_fn)(SeleneSimulatorInstance instance, + const char *file, + const uint64_t *qubits, + uint64_t n_qubits); + SeleneErrno (*gate_fn)(SeleneSimulatorInstance instance, + const uint8_t *data, + size_t len); + SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance instance, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); +} SimulatorOperationInterface; + +typedef struct SimulatorHandle { + SeleneSimulatorInstance instance; + struct SimulatorOperationInterface interface; +} SimulatorHandle; typedef struct SeleneSimulatorPluginDescriptorV1 { - uint64_t struct_size; - uint64_t api_version; - const char *(*get_name_fn)(void); + SelenePluginDescriptorV1 header; SeleneErrno (*init_fn)(SeleneSimulatorInstance *handle, uint64_t n_qubits, uint32_t argc, @@ -42,13 +84,9 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { uint64_t shot_id, uint64_t seed); SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance handle); - SeleneErrno (*measure_fn)(SeleneSimulatorInstance handle, - uint64_t qubit); - SeleneErrno (*postselect_fn)(SeleneSimulatorInstance handle, - uint64_t qubit, - bool target_value); - SeleneErrno (*reset_fn)(SeleneSimulatorInstance handle, - uint64_t qubit); + SeleneErrno (*handle_operations_fn)(SeleneSimulatorInstance handle, + struct RuntimeExtractOperationHandle batch, + struct OperationResultHandle result); SeleneErrno (*get_metrics_fn)(SeleneSimulatorInstance handle, uint8_t nth_metric, char *tag_out, @@ -58,9 +96,6 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { const char *file, const uint64_t *qubits, uint64_t n_qubits); - SeleneErrno (*gate_fn)(SeleneSimulatorInstance handle, - const uint8_t *data, - size_t len); SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance handle, const uint8_t *input, size_t input_len, @@ -73,6 +108,8 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { extern "C" { #endif // __cplusplus +extern SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1; + GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); @@ -83,3 +120,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_SIMULATOR_H */ diff --git a/selene-core/cbindgen/error_model.toml b/selene-core/cbindgen/error_model.toml index 3c3d417d..4afa9313 100644 --- a/selene-core/cbindgen/error_model.toml +++ b/selene-core/cbindgen/error_model.toml @@ -20,8 +20,8 @@ usize_is_size_t = true include = [ "ErrorModelAPIVersion", "ErrorModelInstance", - "ErrorModelSetResultInterface", - "ErrorModelSetResultInstance", + "OperationResultInterface", + "OperationResultInstance", "ErrorModelPluginDescriptorV1", "RuntimeGetOperationInstance", "RuntimeExtractOperationInterface", @@ -95,8 +95,8 @@ renaming_overrides_prefixing = false [export.rename] "ErrorModelAPIVersion" = "SeleneErrorModelAPIVersion" "ErrorModelInstance" = "SeleneErrorModelInstance" -"ErrorModelSetResultInterface" = "SeleneErrorModelSetResultInterface" -"ErrorModelSetResultInstance" = "SeleneErrorModelSetResultInstance" +"OperationResultInterface" = "SeleneOperationResultInterface" +"OperationResultInstance" = "SeleneOperationResultInstance" "ErrorModelPluginDescriptorV1" = "SeleneErrorModelPluginDescriptorV1" "RuntimeGetOperationInstance" = "SeleneRuntimeGetOperationInstance" "RuntimeExtractOperationInterface" = "SeleneRuntimeExtractOperationInterface" diff --git a/selene-core/examples/cbindgen.toml b/selene-core/examples/cbindgen.toml index bb293369..847d798c 100644 --- a/selene-core/examples/cbindgen.toml +++ b/selene-core/examples/cbindgen.toml @@ -25,8 +25,8 @@ include = [ "SimulatorPluginDescriptorV1", "ErrorModelAPIVersion", "ErrorModelInstance", - "ErrorModelSetResultInterface", - "ErrorModelSetResultInstance", + "OperationResultInterface", + "OperationResultInstance", "SimulatorAPIVersion", "SimulatorInstance", "RuntimeAPIVersion", @@ -42,8 +42,8 @@ renaming_overrides_prefixing = false [export.rename] "ErrorModelAPIVersion" = "SeleneErrorModelAPIVersion" "ErrorModelInstance" = "SeleneErrorModelInstance" -"ErrorModelSetResultInterface" = "SeleneErrorModelSetResultInterface" -"ErrorModelSetResultInstance" = "SeleneErrorModelSetResultInstance" +"OperationResultInterface" = "SeleneOperationResultInterface" +"OperationResultInstance" = "SeleneOperationResultInstance" "SimulatorAPIVersion" = "SeleneSimulatorAPIVersion" "SimulatorInstance" = "SeleneSimulatorInstance" "RuntimeAPIVersion" = "SeleneRuntimeAPIVersion" diff --git a/selene-core/examples/error_model/rust/lib.rs b/selene-core/examples/error_model/rust/lib.rs index dc695424..d1da86fa 100644 --- a/selene-core/examples/error_model/rust/lib.rs +++ b/selene-core/examples/error_model/rust/lib.rs @@ -238,6 +238,16 @@ impl ErrorModelInterface for ExampleErrorModel { self.flip_qubit(simulator, qubit_id)?; } } + Operation::Postselect { + qubit_id, + target_value, + } => self.apply_simulator_void( + simulator, + Operation::Postselect { + qubit_id, + target_value, + }, + )?, Operation::Custom { .. } => {} _ => {} } @@ -274,6 +284,10 @@ pub struct ExampleErrorModelFactory; impl ErrorModelInterfaceFactory for ExampleErrorModelFactory { type Interface = ExampleErrorModel; + fn name(&self) -> &str { + "ExampleErrorModel" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-core/examples/runtime/rust/lib.rs b/selene-core/examples/runtime/rust/lib.rs index 7c77ce1d..f3621720 100644 --- a/selene-core/examples/runtime/rust/lib.rs +++ b/selene-core/examples/runtime/rust/lib.rs @@ -63,7 +63,6 @@ impl RuntimeInterface for ExampleRuntime { self.future_results.clear(); Ok(()) } - // Engine ops fn get_next_operations(&mut self) -> Result> { debug_assert!( self.flush_size <= self.operation_queue.len(), @@ -292,6 +291,10 @@ struct ExampleRuntimeFactory; impl RuntimeInterfaceFactory for ExampleRuntimeFactory { type Interface = ExampleRuntime; + fn name(&self) -> &str { + "ExampleRuntime" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-core/examples/simulator/rust/lib.rs b/selene-core/examples/simulator/rust/lib.rs index 910437a1..81c065e4 100644 --- a/selene-core/examples/simulator/rust/lib.rs +++ b/selene-core/examples/simulator/rust/lib.rs @@ -152,6 +152,10 @@ impl SimulatorInterface for ExampleSimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } @@ -193,6 +197,10 @@ pub struct ExampleSimulatorFactory; impl SimulatorInterfaceFactory for ExampleSimulatorFactory { type Interface = ExampleSimulator; + fn name(&self) -> &str { + "ExampleSimulator" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-core/rust/error_model.rs b/selene-core/rust/error_model.rs index 2bd74592..327729f5 100644 --- a/selene-core/rust/error_model.rs +++ b/selene-core/rust/error_model.rs @@ -1,4 +1,4 @@ -use crate::utils::{MetricValue, check_errno, read_raw_metric}; +use crate::utils::{MetricValue, read_raw_metric}; use anyhow::{Result, anyhow}; use std::ffi::OsStr; use std::sync; @@ -15,8 +15,7 @@ pub use inline::{ErrorModelFFIAdapter, ErrorModelHandle, ErrorModelOperationInte pub use interface::{ErrorModelInterface, ErrorModelInterfaceFactory}; pub use version::ErrorModelAPIVersion; -use self::plugin::BatchResultBuilder; -use crate::operation::plugin::BatchExtractor; +use crate::operation::plugin::{BatchExtractor, OperationResultBuilder}; use crate::simulator::{Simulator, SimulatorInterface, inline::borrowed_simulator_interface}; #[derive(Default)] @@ -55,14 +54,23 @@ enum ErrorModelBacking { pub struct ErrorModel { handle: ErrorModelHandle<'static>, backing: ErrorModelBacking, + name: String, } impl ErrorModel { pub fn from_boxed(interface: Box) -> Self { + Self::from_boxed_named("Unknown", interface) + } + + pub fn from_boxed_named( + name: impl Into, + interface: Box, + ) -> Self { let mut adapter = Box::new(ErrorModelFFIAdapter::new(interface)); Self { handle: adapter.ffi_interface(), backing: ErrorModelBacking::Adapter { _adapter: adapter }, + name: name.into(), } } @@ -75,8 +83,9 @@ impl ErrorModel { n_qubits: u64, error_model_args: &[impl AsRef], ) -> Result { + let name = factory.name().to_string(); let interface: Box = factory.init(n_qubits, error_model_args)?; - Ok(Self::from_boxed(interface)) + Ok(Self::from_boxed_named(name, interface)) } pub fn load_from_file( @@ -88,6 +97,22 @@ impl ErrorModel { Self::new(plugin, n_qubits, error_model_args) } + fn context(&self, message: &'static str) -> String { + format!("Error Model ({}): {message}", self.name) + } + + fn label(&self) -> String { + format!("Error Model ({})", self.name) + } + + fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { + plugin_utils::check_plugin_errno_with_context( + errno, + Some(self.handle.interface.last_error_fn), + || anyhow!("{}", self.context(message)), + ) + } + pub fn handle_operations_with_simulator( &mut self, operations: BatchOperation, @@ -95,10 +120,10 @@ impl ErrorModel { ) -> Result { let mut operation_extractor = BatchExtractor::from_batch_operation(operations); let batch = operation_extractor.runtime_batch_extraction(); - let mut result_builder = BatchResultBuilder::default(); - let result = result_builder.error_model_set_result(); + let mut result_builder = OperationResultBuilder::default(); + let result = result_builder.operation_result(); let simulator = simulator.ffi_parts().into_static(); - check_errno( + self.check_errno( unsafe { (self.handle.interface.handle_operations_fn)( self.handle.instance, @@ -107,7 +132,7 @@ impl ErrorModel { result, ) }, - || anyhow!("ErrorModel: handle_operations failed"), + "handle_operations failed", )?; Ok(result_builder.finish()) } @@ -127,7 +152,7 @@ impl AsMut for ErrorModel { impl ErrorModelInterface for ErrorModel { fn shot_start(&mut self, shot_id: u64, error_model_seed: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_start_fn)( self.handle.instance, @@ -135,22 +160,23 @@ impl ErrorModelInterface for ErrorModel { error_model_seed, ) }, - || anyhow!("ErrorModel: shot_start failed"), + "shot_start failed", ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_end_fn)(self.handle.instance) }, - || anyhow!("ErrorModel: shot_end failed"), + "shot_end failed", ) } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { plugin_utils::negotiate_gateset( - "ErrorModel", + &self.label(), self.handle.instance, - Some(self.handle.interface.negotiate_gateset_fn), + self.handle.interface.negotiate_gateset_fn, + Some(self.handle.interface.last_error_fn), gateset, ) } @@ -164,9 +190,9 @@ impl ErrorModelInterface for ErrorModel { let simulator = borrowed_simulator_interface(&mut simulator_ref).into_static(); let mut operation_extractor = BatchExtractor::from_batch_operation(operations); let batch = operation_extractor.runtime_batch_extraction(); - let mut result_builder = BatchResultBuilder::default(); - let result = result_builder.error_model_set_result(); - check_errno( + let mut result_builder = OperationResultBuilder::default(); + let result = result_builder.operation_result(); + self.check_errno( unsafe { (self.handle.interface.handle_operations_fn)( self.handle.instance, @@ -175,16 +201,16 @@ impl ErrorModelInterface for ErrorModel { result, ) }, - || anyhow!("ErrorModel: handle_operations failed"), + "handle_operations failed", )?; Ok(result_builder.finish()) } fn exit(&mut self) -> Result<()> { let _ = &self.backing; - check_errno( + self.check_errno( unsafe { (self.handle.interface.exit_fn)(self.handle.instance) }, - || anyhow!("ErrorModel: exit failed"), + "exit failed", ) } diff --git a/selene-core/rust/error_model/helper.rs b/selene-core/rust/error_model/helper.rs index 91610d96..0ba7063d 100644 --- a/selene-core/rust/error_model/helper.rs +++ b/selene-core/rust/error_model/helper.rs @@ -5,15 +5,18 @@ use super::{ ErrorModelInterface, interface::ErrorModelInterfaceFactory, - plugin::{Errno, ErrorModelInstance, ErrorModelSetResultHandle, ErrorModelSetResultInterface}, + plugin::{Errno, ErrorModelInstance}, }; use crate::operation::plugin::{ - BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInterface, + BatchBuilder, OperationResultHandle, OperationResultInterface, RuntimeExtractOperationHandle, + RuntimeExtractOperationInterface, }; use crate::plugin::write_negotiated_gateset; use crate::simulator::Simulator; use crate::simulator::inline::SimulatorHandle; -use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; +use crate::utils::{ + convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno, set_last_error, +}; use std::{ffi, mem, sync::Arc}; #[derive(Default)] @@ -54,7 +57,7 @@ impl Helper { error_model_argv: *const *const ffi::c_char, ) -> Errno { if instance.is_null() { - eprintln!("cannot initialize error model plugin: provided instance is null"); + set_last_error("cannot initialize error model plugin: provided instance is null"); return -1; } @@ -119,7 +122,7 @@ impl Helper { instance: ErrorModelInstance, batch: RuntimeExtractOperationHandle, simulator: SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno { result_to_errno( "Failed to handle operations", @@ -127,11 +130,11 @@ impl Helper { let mut batch_builder: BatchBuilder = BatchBuilder::default(); let builder = batch_builder.runtime_get_operation(); let RuntimeExtractOperationInterface { extract_fn, .. } = batch.interface; - extract_fn(batch, builder); + extract_fn(&raw const batch, builder); let mut simulator = Simulator::from_raw_parts(simulator); let results = error_model.handle_operations(batch_builder.finish(), &mut simulator)?; - let ErrorModelSetResultInterface { + let OperationResultInterface { set_bool_result_fn, set_u64_result_fn, .. @@ -188,14 +191,13 @@ macro_rules! export_error_model_plugin { ErrorModelInterfaceFactory, plugin::{ Errno, ErrorModelInstance, ErrorModelPluginDescriptorV1, - ErrorModelSetResultHandle, ErrorModelSetResultInstance, - ErrorModelSetResultInterface, }, version::current_api_version, }, operation::plugin::{ - BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInstance, - RuntimeExtractOperationInterface, + BatchBuilder, OperationResultHandle, OperationResultInstance, + OperationResultInterface, RuntimeExtractOperationHandle, + RuntimeExtractOperationInstance, RuntimeExtractOperationInterface, }, }; @@ -210,6 +212,23 @@ macro_rules! export_error_model_plugin { _assert_impl::<$factory_type>(); }; + unsafe extern "C" fn selene_error_model_last_error( + output: *mut c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { selene_core::utils::last_error_message(output, output_len, written) } + } + + unsafe extern "C" fn selene_error_model_get_name() -> *const c_char { + static NAME: std::sync::OnceLock = std::sync::OnceLock::new(); + NAME.get_or_init(|| { + std::ffi::CString::new(<$factory_type>::default().name()) + .expect("plugin names must not contain embedded null bytes") + }) + .as_ptr() + } + /// When Selene is initialised, it is provided with some default arguments /// (the maximum number of qubits, the path to a simulator plugin to use, etc) /// and some custom arguments for the error model and simulator. These arguments @@ -308,13 +327,13 @@ macro_rules! export_error_model_plugin { /// and [RuntimeExtractOperationInstance] for more details. /// /// Likewise, as the results of the operations are also in the form of a container, - /// we provide an ErrorModelSetResultInterface that allows the error model to set + /// we provide an OperationResultInterface that allows the error model to set /// the results of the measurements in the runtime. unsafe extern "C" fn selene_error_model_handle_operations( instance: ErrorModelInstance, batch: RuntimeExtractOperationHandle, simulator: selene_core::simulator::inline::SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno { Helper::handle_operations(instance, batch, simulator, result) } @@ -380,6 +399,8 @@ macro_rules! export_error_model_plugin { ErrorModelPluginDescriptorV1, current_api_version().as_u64(), { + last_error_fn: Some(selene_error_model_last_error), + get_name_fn: selene_error_model_get_name, init_fn: Some(selene_error_model_init), exit_fn: Some(selene_error_model_exit), shot_start_fn: Some(selene_error_model_shot_start), diff --git a/selene-core/rust/error_model/inline.rs b/selene-core/rust/error_model/inline.rs index 12da95e7..976d6db7 100644 --- a/selene-core/rust/error_model/inline.rs +++ b/selene-core/rust/error_model/inline.rs @@ -1,12 +1,13 @@ use super::{ BatchResult, ErrorModelInterface, - plugin::{Errno, ErrorModelInstance, ErrorModelSetResultHandle, ErrorModelSetResultInterface}, + plugin::{Errno, ErrorModelInstance}, }; use crate::{ operation::plugin::{ - BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInterface, + BatchBuilder, OperationResultHandle, OperationResultInterface, + RuntimeExtractOperationHandle, RuntimeExtractOperationInterface, }, - plugin::write_negotiated_gateset, + plugin::{LastErrorFn, write_negotiated_gateset}, simulator::{Simulator, inline::SimulatorHandle}, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -35,6 +36,7 @@ impl ErrorModelFFIAdapter { instance: &raw mut self.error_model as ErrorModelInstance, interface: ErrorModelOperationInterface { exit_fn: Self::exit, + last_error_fn: Self::last_error, shot_start_fn: Self::shot_start, shot_end_fn: Self::shot_end, negotiate_gateset_fn: Self::negotiate_gateset, @@ -60,6 +62,14 @@ impl ErrorModelFFIAdapter { }) } + unsafe extern "C" fn last_error( + output: *mut ffi::c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { crate::utils::last_error_message(output, output_len, written) } + } + unsafe extern "C" fn shot_start( instance: ErrorModelInstance, shot_id: u64, @@ -102,14 +112,14 @@ impl ErrorModelFFIAdapter { instance: ErrorModelInstance, batch: RuntimeExtractOperationHandle, simulator: SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno { result_to_errno("ErrorModelFFIAdapter: handle_operations failed", unsafe { Self::with_error_model(instance, |error_model| { let mut batch_builder = BatchBuilder::default(); let builder = batch_builder.runtime_get_operation(); let RuntimeExtractOperationInterface { extract_fn, .. } = batch.interface; - extract_fn(batch, builder); + extract_fn(&raw const batch, builder); let mut simulator = Simulator::from_raw_parts(simulator); let results = @@ -120,8 +130,8 @@ impl ErrorModelFFIAdapter { }) } - unsafe fn write_results(results: BatchResult, result: ErrorModelSetResultHandle) { - let ErrorModelSetResultInterface { + unsafe fn write_results(results: BatchResult, result: OperationResultHandle) { + let OperationResultInterface { set_bool_result_fn, set_u64_result_fn, .. @@ -160,13 +170,14 @@ impl ErrorModelFFIAdapter { #[non_exhaustive] pub struct ErrorModelOperationInterface<'a> { pub exit_fn: unsafe extern "C" fn(ErrorModelInstance) -> Errno, + pub last_error_fn: LastErrorFn, pub shot_start_fn: unsafe extern "C" fn(ErrorModelInstance, u64, u64) -> Errno, pub shot_end_fn: unsafe extern "C" fn(ErrorModelInstance) -> Errno, pub handle_operations_fn: unsafe extern "C" fn( ErrorModelInstance, RuntimeExtractOperationHandle, SimulatorHandle<'a>, - ErrorModelSetResultHandle, + OperationResultHandle, ) -> Errno, pub get_metrics_fn: unsafe extern "C" fn(ErrorModelInstance, u8, *mut ffi::c_char, *mut u8, *mut u64) -> Errno, diff --git a/selene-core/rust/error_model/interface.rs b/selene-core/rust/error_model/interface.rs index 27a458bd..54e06f8f 100644 --- a/selene-core/rust/error_model/interface.rs +++ b/selene-core/rust/error_model/interface.rs @@ -67,6 +67,7 @@ pub trait ErrorModelInterface { pub trait ErrorModelInterfaceFactory { type Interface: ErrorModelInterface; + fn name(&self) -> &str; fn init( self: Arc, diff --git a/selene-core/rust/error_model/plugin.rs b/selene-core/rust/error_model/plugin.rs index ad8cc4c4..56fb14e3 100644 --- a/selene-core/rust/error_model/plugin.rs +++ b/selene-core/rust/error_model/plugin.rs @@ -1,14 +1,13 @@ -use super::{ - BatchResult, BoolResult, ErrorModelAPIVersion, ErrorModelInterface, ErrorModelInterfaceFactory, - U64Result, -}; +use super::{BatchResult, ErrorModelAPIVersion, ErrorModelInterface, ErrorModelInterfaceFactory}; use crate::gatewire::DynamicGateSet; use crate::operation::BatchOperation; +use crate::operation::plugin::{OperationResultBuilder, OperationResultHandle}; use crate::plugin::{ - NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + LastErrorFn, NegotiateGatesetFn, PluginDescriptorHeaderV1, PluginDescriptorV1, + check_plugin_errno, load_descriptor_v1, load_library, negotiate_gateset, read_plugin_name, require_callback, validate_descriptor_v1, }; -use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; +use crate::utils::{MetricValue, read_raw_metric, with_strings_to_cargs}; use anyhow::{Result, anyhow}; use std::ffi::OsStr; use std::{ffi, sync::Arc}; @@ -19,8 +18,7 @@ pub type Errno = i32; #[repr(C)] #[derive(Clone, Copy)] pub struct ErrorModelPluginDescriptorV1 { - pub struct_size: u64, - pub api_version: u64, + pub header: PluginDescriptorHeaderV1, pub init_fn: Option< unsafe extern "C" fn( handle: *mut ErrorModelInstance, @@ -43,7 +41,7 @@ pub struct ErrorModelPluginDescriptorV1 { handle: ErrorModelInstance, batch: crate::operation::plugin::RuntimeExtractOperationHandle, simulator: crate::simulator::inline::SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno, >, pub get_metrics_fn: Option< @@ -70,12 +68,8 @@ pub struct ErrorModelPluginDescriptorV1 { impl PluginDescriptorV1 for ErrorModelPluginDescriptorV1 { const KIND: &'static str = "Error model"; - fn struct_size(&self) -> u64 { - self.struct_size - } - - fn api_version(&self) -> u64 { - self.api_version + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header } } @@ -91,6 +85,8 @@ impl PluginDescriptorV1 for ErrorModelPluginDescriptorV1 { /// diligence must be done to verify the source and the trustworthiness of the provider. pub struct ErrorModelPluginInterface { _lib: libloading::Library, + last_error_fn: LastErrorFn, + name: String, init_fn: unsafe extern "C" fn( handle: *mut ErrorModelInstance, n_qubits: u64, @@ -104,12 +100,12 @@ pub struct ErrorModelPluginInterface { error_model_seed: u64, ) -> Errno, shot_end_fn: unsafe extern "C" fn(handle: ErrorModelInstance) -> Errno, - negotiate_gateset_fn: Option>, + negotiate_gateset_fn: NegotiateGatesetFn, handle_operations_fn: unsafe extern "C" fn( handle: ErrorModelInstance, batch: crate::operation::plugin::RuntimeExtractOperationHandle, simulator: crate::simulator::inline::SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno, get_metrics_fn: Option< unsafe extern "C" fn( @@ -136,8 +132,17 @@ impl ErrorModelPluginInterface { validate_descriptor_v1(&descriptor, |api_version| { ErrorModelAPIVersion::from(api_version).validate() })?; + let get_name_fn = + require_callback("Error model", "get_name_fn", descriptor.header.get_name_fn)?; + let name = read_plugin_name("Error model", get_name_fn)?; Ok(Arc::new(Self { _lib: lib, + last_error_fn: require_callback( + "Error model", + "last_error_fn", + descriptor.header.last_error_fn, + )?, + name, init_fn: require_callback("Error model", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, shot_start_fn: require_callback( @@ -146,7 +151,11 @@ impl ErrorModelPluginInterface { descriptor.shot_start_fn, )?, shot_end_fn: require_callback("Error model", "shot_end_fn", descriptor.shot_end_fn)?, - negotiate_gateset_fn: descriptor.negotiate_gateset_fn, + negotiate_gateset_fn: require_callback( + "Error model", + "negotiate_gateset_fn", + descriptor.negotiate_gateset_fn, + )?, handle_operations_fn: require_callback( "Error model", "handle_operations_fn", @@ -160,6 +169,10 @@ impl ErrorModelPluginInterface { impl ErrorModelInterfaceFactory for ErrorModelPluginInterface { type Interface = ErrorModelPlugin; + fn name(&self) -> &str { + &self.name + } + fn init( self: Arc, n_qubits: u64, @@ -167,10 +180,11 @@ impl ErrorModelInterfaceFactory for ErrorModelPluginInterface { ) -> Result> { let mut instance = std::ptr::null_mut(); with_strings_to_cargs(error_model_args, |error_model_argc, error_model_argv| { - check_errno( + check_plugin_errno( unsafe { (self.init_fn)(&mut instance, n_qubits, error_model_argc, error_model_argv) }, + Some(self.last_error_fn), || anyhow!("ErrorModelPluginInterface: init failed"), ) })?; @@ -191,19 +205,23 @@ impl ErrorModelInterface for ErrorModelPlugin { let Some(exit_fn) = self.interface.exit_fn else { return Ok(()); }; - check_errno(unsafe { exit_fn(self.instance) }, || { - anyhow!("ErrorModelPlugin: exit failed") - }) + check_plugin_errno( + unsafe { exit_fn(self.instance) }, + Some(self.interface.last_error_fn), + || anyhow!("ErrorModelPlugin: exit failed"), + ) } fn shot_start(&mut self, shot_id: u64, error_model_seed: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, error_model_seed) }, + Some(self.interface.last_error_fn), || anyhow!("ErrorModelPlugin: shot_start failed"), ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, + Some(self.interface.last_error_fn), || anyhow!("ErrorModelPlugin: shot_end failed"), ) } @@ -213,6 +231,7 @@ impl ErrorModelInterface for ErrorModelPlugin { "ErrorModelPlugin", self.instance, self.interface.negotiate_gateset_fn, + Some(self.interface.last_error_fn), gateset, ) } @@ -224,15 +243,16 @@ impl ErrorModelInterface for ErrorModelPlugin { let mut batch_extractor = crate::operation::plugin::BatchExtractor::from_batch_operation(operations); let batch = batch_extractor.runtime_batch_extraction(); - let mut result_builder = BatchResultBuilder::default(); - let result = result_builder.error_model_set_result(); + let mut result_builder = OperationResultBuilder::default(); + let result = result_builder.operation_result(); let mut simulator_ref = simulator; let simulator = crate::simulator::inline::borrowed_simulator_interface(&mut simulator_ref) .into_static(); - check_errno( + check_plugin_errno( unsafe { (self.interface.handle_operations_fn)(self.instance, batch, simulator, result) }, + Some(self.interface.last_error_fn), || anyhow!("ErrorModelPlugin: handle_operations failed"), )?; Ok(result_builder.finish()) @@ -246,74 +266,3 @@ impl ErrorModelInterface for ErrorModelPlugin { }) } } - -#[derive(Default)] -/// A helper type used by the plugin tooling above to implement -/// [ErrorModelSetResultInterface]. -pub(crate) struct BatchResultBuilder(BatchResult); - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct ErrorModelSetResultHandle { - pub instance: ErrorModelSetResultInstance, - pub interface: ErrorModelSetResultInterface, -} - -impl BatchResultBuilder { - unsafe extern "C" fn set_bool_result( - interface: ErrorModelSetResultInstance, - result_id: u64, - value: bool, - ) { - let result = interface as *mut BatchResult; - (unsafe { &mut *result }) - .bool_results - .push(BoolResult { result_id, value }) - } - unsafe extern "C" fn set_u64_result( - interface: ErrorModelSetResultInstance, - result_id: u64, - value: u64, - ) { - let result = interface as *mut BatchResult; - (unsafe { &mut *result }) - .u64_results - .push(U64Result { result_id, value }) - } - - /// The plugin calls this to obtain an instance and an interface. - /// The lifetime parameter of the interface ensures that it cannot outlive the `Vec` - /// that the functions will mutate. - pub(crate) fn error_model_set_result(&mut self) -> ErrorModelSetResultHandle { - ErrorModelSetResultHandle { - instance: &raw mut self.0 as ErrorModelSetResultInstance, - interface: ErrorModelSetResultInterface { - set_bool_result_fn: Self::set_bool_result, - set_u64_result_fn: Self::set_u64_result, - }, - } - } - - /// Consumes the `BatchBuilder` returning the accumulated operations. - pub(crate) fn finish(self) -> BatchResult { - self.0 - } -} - -/// An instance is provided to `selene_runtime_get_next_operations`, which must -/// pass that back to any function it calls in its provided -/// [ErrorModelSetResultInterface]. -pub type ErrorModelSetResultInstance = *mut ffi::c_void; - -#[repr(C)] -#[derive(Clone, Copy)] -#[non_exhaustive] -/// A plugin's implementation of `selene_runtime_get_next_operations` is provided -/// a pointer to a `ErrorModelSetResultInterface` as well as a -/// [ErrorModelSetResultInstance]. It should call the functions -/// within to populate a batch. All such calls must pass the instance as the -/// first parameter. -pub struct ErrorModelSetResultInterface { - pub set_bool_result_fn: unsafe extern "C" fn(ErrorModelSetResultInstance, u64, bool), - pub set_u64_result_fn: unsafe extern "C" fn(ErrorModelSetResultInstance, u64, u64), -} diff --git a/selene-core/rust/error_model/version.rs b/selene-core/rust/error_model/version.rs index 512c8e1e..1e1961cd 100644 --- a/selene-core/rust/error_model/version.rs +++ b/selene-core/rust/error_model/version.rs @@ -46,7 +46,7 @@ pub const fn current_api_version() -> ErrorModelAPIVersion { // Changelog: // 0.1.0: Initial version. // 0.2.0: Replaced set_measurement_result with set_bool_result and set_u64_result in -// ErrorModelSetResultInterface +// OperationResultInterface impl ErrorModelAPIVersion { pub const fn as_u64(self) -> u64 { diff --git a/selene-core/rust/lib.rs b/selene-core/rust/lib.rs index abe643f9..b3c064e4 100644 --- a/selene-core/rust/lib.rs +++ b/selene-core/rust/lib.rs @@ -3,7 +3,7 @@ pub mod error_model; pub mod gatewire; pub mod macros; pub mod operation; -pub(crate) mod plugin; +pub mod plugin; pub mod runtime; pub mod simulator; pub mod time; diff --git a/selene-core/rust/macros.rs b/selene-core/rust/macros.rs index 8cfc116a..71edb390 100644 --- a/selene-core/rust/macros.rs +++ b/selene-core/rust/macros.rs @@ -1,10 +1,23 @@ #[macro_export] macro_rules! export_plugin_descriptor_v1 { - ($symbol:ident, $descriptor_ty:ident, $api_version:expr, { $($field:ident : $value:expr),* $(,)? }) => { + ( + $symbol:ident, + $descriptor_ty:ident, + $api_version:expr, + { + last_error_fn: $last_error_fn:expr, + get_name_fn: $get_name_fn:expr, + $($field:ident : $value:expr),* $(,)? + } + ) => { #[unsafe(no_mangle)] pub static mut $symbol: $descriptor_ty = $descriptor_ty { - struct_size: core::mem::size_of::<$descriptor_ty>() as u64, - api_version: $api_version, + header: $crate::plugin::PluginDescriptorHeaderV1 { + struct_size: core::mem::size_of::<$descriptor_ty>() as u64, + api_version: $api_version, + last_error_fn: $last_error_fn, + get_name_fn: Some($get_name_fn), + }, $($field: $value,)* }; }; diff --git a/selene-core/rust/operation.rs b/selene-core/rust/operation.rs index 32ff28f2..509aecf5 100644 --- a/selene-core/rust/operation.rs +++ b/selene-core/rust/operation.rs @@ -3,11 +3,12 @@ pub mod plugin; use std::collections::HashSet; use std::iter; -use crate::gatewire::{Angle, GateSpec, OwnedGateInstance, Qubit, builtin}; +use crate::gatewire::{Angle, GateSetSpec, GateSpec, GateView, OwnedGateInstance, Qubit, builtin}; use anyhow::{Result, bail}; pub use plugin::{ - BatchBuilder, BatchExtractor, RuntimeExtractOperationInstance, + BatchBuilder, BatchExtractor, OperationResultBuilder, OperationResultHandle, + OperationResultInstance, OperationResultInterface, RuntimeExtractOperationInstance, RuntimeExtractOperationInterface, RuntimeGetOperationInstance, RuntimeGetOperationInterface, }; @@ -84,6 +85,7 @@ pub enum BuiltinGate { #[repr(C)] pub enum Operation { Measure { qubit_id: u64, result_id: u64 }, + Postselect { qubit_id: u64, target_value: bool }, Reset { qubit_id: u64 }, Custom { custom_tag: usize, data: Box<[u8]> }, MeasureLeaked { qubit_id: u64, result_id: u64 }, @@ -150,6 +152,25 @@ impl Operation { } } + pub fn as_gate(&self) -> Result> { + let Self::Gate { gate } = self else { + return Ok(None); + }; + Ok(G::try_from_instance(gate)?) + } + + pub fn gate_as(gate: &OwnedGateInstance) -> Result> { + Ok(G::try_from_instance(gate)?) + } + + pub fn as_gate_view(&self) -> Result> { + Ok(self.as_gate::()?.map(V::from_gate)) + } + + pub fn gate_as_view(gate: &OwnedGateInstance) -> Result> { + Ok(Self::gate_as::(gate)?.map(V::from_gate)) + } + pub fn as_builtin_gate(&self) -> Result> { let Self::Gate { gate } = self else { return Ok(None); @@ -188,6 +209,7 @@ impl Operation { pub fn get_qubit_ids(&self) -> HashSet { match self { Operation::Measure { qubit_id, .. } + | Operation::Postselect { qubit_id, .. } | Operation::Reset { qubit_id } | Operation::MeasureLeaked { qubit_id, .. } => { let mut set = HashSet::new(); diff --git a/selene-core/rust/operation/plugin.rs b/selene-core/rust/operation/plugin.rs index 00cafa7c..294c759a 100644 --- a/selene-core/rust/operation/plugin.rs +++ b/selene-core/rust/operation/plugin.rs @@ -1,4 +1,5 @@ use super::{BatchOperation, Operation, RuntimeBatchSource}; +use crate::error_model::{BatchResult, BoolResult, U64Result}; use core::slice; use std::ffi; @@ -66,6 +67,20 @@ impl BatchBuilder { ) } + unsafe extern "C" fn postselect( + interface: RuntimeGetOperationInstance, + qubit_id: u64, + target_value: bool, + ) { + Self::push( + interface, + Operation::Postselect { + qubit_id, + target_value, + }, + ) + } + unsafe extern "C" fn reset(interface: RuntimeGetOperationInstance, qubit_id: u64) { Self::push(interface, Operation::Reset { qubit_id }) } @@ -109,6 +124,7 @@ impl BatchBuilder { interface: RuntimeGetOperationInterface { measure_fn: Self::measure, measure_leaked_fn: Self::measure_leaked, + postselect_fn: Self::postselect, reset_fn: Self::reset, custom_fn: Self::custom, set_batch_time_fn: Self::set_batch_time, @@ -133,6 +149,7 @@ pub type RuntimeGetOperationInstance = *mut ffi::c_void; pub struct RuntimeGetOperationInterface { pub measure_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, u64), pub measure_leaked_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, u64), + pub postselect_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64, bool), pub reset_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, u64), pub custom_fn: unsafe extern "C" fn(RuntimeGetOperationInstance, usize, *const ffi::c_void, usize), @@ -149,14 +166,16 @@ impl BatchExtractor { } pub unsafe extern "C" fn extract( - input: RuntimeExtractOperationHandle, + input: *const RuntimeExtractOperationHandle, output: RuntimeGetOperationHandle, ) { + let input = unsafe { &*input }; let batch_ptr = input.instance as *const BatchOperation; let batch: &BatchOperation = unsafe { &*batch_ptr }; let RuntimeGetOperationInterface { measure_fn, measure_leaked_fn, + postselect_fn, reset_fn, custom_fn, set_batch_time_fn, @@ -182,6 +201,10 @@ impl BatchExtractor { qubit_id, result_id, } => unsafe { measure_leaked_fn(output.instance, *qubit_id, *result_id) }, + Operation::Postselect { + qubit_id, + target_value, + } => unsafe { postselect_fn(output.instance, *qubit_id, *target_value) }, Operation::Reset { qubit_id } => unsafe { reset_fn(output.instance, *qubit_id) }, Operation::Gate { gate } => { let data = gate.serialize(); @@ -205,11 +228,70 @@ impl BatchExtractor { } } +#[derive(Default)] +pub struct OperationResultBuilder(BatchResult); + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct OperationResultHandle { + pub instance: OperationResultInstance, + pub interface: OperationResultInterface, +} + +impl OperationResultBuilder { + unsafe extern "C" fn set_bool_result( + interface: OperationResultInstance, + result_id: u64, + value: bool, + ) { + let result = interface as *mut BatchResult; + (unsafe { &mut *result }) + .bool_results + .push(BoolResult { result_id, value }) + } + + unsafe extern "C" fn set_u64_result( + interface: OperationResultInstance, + result_id: u64, + value: u64, + ) { + let result = interface as *mut BatchResult; + (unsafe { &mut *result }) + .u64_results + .push(U64Result { result_id, value }) + } + + pub fn operation_result(&mut self) -> OperationResultHandle { + OperationResultHandle { + instance: &raw mut self.0 as OperationResultInstance, + interface: OperationResultInterface { + set_bool_result_fn: Self::set_bool_result, + set_u64_result_fn: Self::set_u64_result, + }, + } + } + + pub fn finish(self) -> BatchResult { + self.0 + } +} + +pub type OperationResultInstance = *mut ffi::c_void; + +#[repr(C)] +#[derive(Clone, Copy)] +#[non_exhaustive] +pub struct OperationResultInterface { + pub set_bool_result_fn: unsafe extern "C" fn(OperationResultInstance, u64, bool), + pub set_u64_result_fn: unsafe extern "C" fn(OperationResultInstance, u64, u64), +} + pub type RuntimeExtractOperationInstance = *mut ffi::c_void; #[repr(C)] #[derive(Clone, Copy)] #[non_exhaustive] pub struct RuntimeExtractOperationInterface { - pub extract_fn: unsafe extern "C" fn(RuntimeExtractOperationHandle, RuntimeGetOperationHandle), + pub extract_fn: + unsafe extern "C" fn(*const RuntimeExtractOperationHandle, RuntimeGetOperationHandle), } diff --git a/selene-core/rust/plugin.rs b/selene-core/rust/plugin.rs index 602e738a..f6dffc0c 100644 --- a/selene-core/rust/plugin.rs +++ b/selene-core/rust/plugin.rs @@ -1,17 +1,37 @@ use anyhow::{Result, anyhow, bail}; use libloading::Library; -use std::ffi::OsStr; +use std::ffi::{OsStr, c_char}; use crate::gatewire::DynamicGateSet; use crate::utils::check_errno; pub type Errno = i32; +pub type LastErrorFn = + unsafe extern "C" fn(output: *mut c_char, output_len: usize, written: *mut usize) -> Errno; +pub type PluginNameFn = unsafe extern "C" fn() -> *const c_char; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct PluginDescriptorHeaderV1 { + pub struct_size: u64, + pub api_version: u64, + pub last_error_fn: Option, + pub get_name_fn: Option, +} + pub(crate) trait PluginDescriptorV1: Copy { const KIND: &'static str; - fn struct_size(&self) -> u64; - fn api_version(&self) -> u64; + fn header(&self) -> &PluginDescriptorHeaderV1; + + fn struct_size(&self) -> u64 { + self.header().struct_size + } + + fn api_version(&self) -> u64 { + self.header().api_version + } } pub(crate) fn load_library(kind: &str, plugin_file: &impl AsRef) -> Result { @@ -80,6 +100,88 @@ pub(crate) fn require_callback( }) } +pub(crate) fn read_last_error(last_error_fn: Option) -> Option { + let last_error_fn = last_error_fn?; + let mut written = 0usize; + check_errno( + unsafe { last_error_fn(std::ptr::null_mut(), 0, &mut written) }, + || (), + ) + .ok()?; + if written == 0 { + return None; + } + + let mut output = vec![0u8; written]; + check_errno( + unsafe { + last_error_fn( + output.as_mut_ptr() as *mut c_char, + output.len(), + &mut written, + ) + }, + || (), + ) + .ok()?; + output.truncate(written); + Some(String::from_utf8_lossy(&output).into_owned()) +} + +pub(crate) fn read_plugin_name(kind: &str, get_name_fn: PluginNameFn) -> Result { + let ptr = unsafe { get_name_fn() }; + if ptr.is_null() { + return Err(anyhow!("{kind} plugin get_name_fn returned null")); + } + let name = unsafe { std::ffi::CStr::from_ptr(ptr) } + .to_string_lossy() + .into_owned(); + if name.trim().is_empty() { + return Err(anyhow!("{kind} plugin get_name_fn returned an empty name")); + } + Ok(name) +} + +pub(crate) fn check_plugin_errno( + errno: Errno, + last_error_fn: Option, + mk_err: impl FnOnce() -> E, +) -> Result<(), anyhow::Error> { + if errno == 0 { + return Ok(()); + } + + if let Some(detail) = read_last_error(last_error_fn) { + let detail = detail.trim(); + if !detail.is_empty() { + return Err(anyhow!("{detail}")); + } + } + + let fallback = mk_err(); + Err(anyhow!("{fallback}")) +} + +pub(crate) fn check_plugin_errno_with_context( + errno: Errno, + last_error_fn: Option, + mk_err: impl FnOnce() -> E, +) -> Result<(), anyhow::Error> { + if errno == 0 { + return Ok(()); + } + + let fallback = mk_err(); + if let Some(detail) = read_last_error(last_error_fn) { + let detail = detail.trim(); + if !detail.is_empty() { + return Err(anyhow!("{fallback}: {detail}")); + } + } + + Err(anyhow!("{fallback}")) +} + pub(crate) type NegotiateGatesetFn = unsafe extern "C" fn( handle: I, input: *const u8, @@ -92,16 +194,13 @@ pub(crate) type NegotiateGatesetFn = unsafe extern "C" fn( pub(crate) fn negotiate_gateset( kind: &str, instance: I, - negotiate_gateset_fn: Option>, + negotiate_gateset_fn: NegotiateGatesetFn, + last_error_fn: Option, gateset: &DynamicGateSet, ) -> Result { - let Some(negotiate_gateset_fn) = negotiate_gateset_fn else { - return Ok(gateset.clone()); - }; - let input = gateset.serialize(); let mut written = 0usize; - check_errno( + check_plugin_errno( unsafe { negotiate_gateset_fn( instance, @@ -112,11 +211,12 @@ pub(crate) fn negotiate_gateset( &mut written, ) }, + last_error_fn, || anyhow!("{kind}: negotiate_gateset failed"), )?; let mut output = vec![0u8; written]; - check_errno( + check_plugin_errno( unsafe { negotiate_gateset_fn( instance, @@ -127,6 +227,7 @@ pub(crate) fn negotiate_gateset( &mut written, ) }, + last_error_fn, || anyhow!("{kind}: negotiate_gateset failed"), )?; @@ -178,27 +279,26 @@ mod tests { #[derive(Clone, Copy)] struct TestDescriptor { - struct_size: u64, - api_version: u64, + header: PluginDescriptorHeaderV1, } impl PluginDescriptorV1 for TestDescriptor { const KIND: &'static str = "Test"; - fn struct_size(&self) -> u64 { - self.struct_size - } - - fn api_version(&self) -> u64 { - self.api_version + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header } } #[test] fn descriptor_validation_checks_version_and_size() { let descriptor = TestDescriptor { - struct_size: core::mem::size_of::() as u64, - api_version: 7, + header: PluginDescriptorHeaderV1 { + struct_size: core::mem::size_of::() as u64, + api_version: 7, + last_error_fn: None, + get_name_fn: None, + }, }; validate_descriptor_v1(&descriptor, |version| { assert_eq!(version, 7); @@ -207,8 +307,12 @@ mod tests { .unwrap(); let descriptor = TestDescriptor { - struct_size: 0, - api_version: 7, + header: PluginDescriptorHeaderV1 { + struct_size: 0, + api_version: 7, + last_error_fn: None, + get_name_fn: None, + }, }; let error = validate_descriptor_v1(&descriptor, |_| Ok(())).unwrap_err(); assert!( @@ -218,8 +322,12 @@ mod tests { ); let descriptor = TestDescriptor { - struct_size: core::mem::size_of::() as u64, - api_version: 7, + header: PluginDescriptorHeaderV1 { + struct_size: core::mem::size_of::() as u64, + api_version: 7, + last_error_fn: None, + get_name_fn: None, + }, }; let error = validate_descriptor_v1(&descriptor, |_| Err(anyhow!("bad version"))).unwrap_err(); @@ -261,22 +369,60 @@ mod tests { #[test] fn negotiate_gateset_uses_two_call_output_protocol() { - let gateset = builtin::all(); + let gateset = builtin::QuantinuumGateSet::dynamic(); let negotiated = - negotiate_gateset("TestPlugin", 0usize, Some(echo_gateset), &gateset).unwrap(); + negotiate_gateset("TestPlugin", 0usize, echo_gateset, None, &gateset).unwrap(); assert_eq!(negotiated.serialize(), gateset.serialize()); } #[test] - fn negotiate_gateset_defaults_to_input_when_callback_is_absent() { - let gateset = builtin::all(); - let negotiated = negotiate_gateset::("TestPlugin", 0usize, None, &gateset).unwrap(); - assert_eq!(negotiated.serialize(), gateset.serialize()); + fn negotiate_gateset_callback_is_required() { + let error = + require_callback::>("Test", "negotiate_gateset_fn", None) + .unwrap_err(); + assert_eq!( + error.to_string(), + "Test plugin descriptor is missing required callback negotiate_gateset_fn" + ); + } + + unsafe extern "C" fn fixed_last_error( + output: *mut c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + let message = b"plugin-specific failure"; + unsafe { + *written = message.len(); + if output.is_null() { + return 0; + } + if output_len < message.len() { + return -1; + } + std::ptr::copy_nonoverlapping(message.as_ptr(), output.cast::(), message.len()); + } + 0 + } + + #[test] + fn check_plugin_errno_prefers_last_error_detail() { + let error = + check_plugin_errno(-1, Some(fixed_last_error), || anyhow!("Host failure")).unwrap_err(); + assert_eq!(error.to_string(), "plugin-specific failure"); + } + + #[test] + fn check_plugin_errno_with_context_includes_public_context_and_last_error_detail() { + let error = + check_plugin_errno_with_context(-1, Some(fixed_last_error), || anyhow!("Host failure")) + .unwrap_err(); + assert_eq!(error.to_string(), "Host failure: plugin-specific failure"); } #[test] fn write_negotiated_gateset_validates_null_pointers() { - let gateset = builtin::all(); + let gateset = builtin::QuantinuumGateSet::dynamic(); let data = gateset.serialize(); let mut written = 0usize; diff --git a/selene-core/rust/runtime.rs b/selene-core/rust/runtime.rs index b5cccdc1..bf15521c 100644 --- a/selene-core/rust/runtime.rs +++ b/selene-core/rust/runtime.rs @@ -15,7 +15,7 @@ pub use inline::{RuntimeFFIAdapter, RuntimeHandle, RuntimeOperationInterface}; pub use interface::{RuntimeInterface, RuntimeInterfaceFactory}; pub use version::RuntimeAPIVersion; -use crate::utils::{MetricValue, check_errno, read_raw_metric}; +use crate::utils::{MetricValue, read_raw_metric}; use anyhow::{Result, anyhow}; use crate::gatewire::{DynamicGateSet, OwnedGateInstance}; @@ -34,14 +34,20 @@ enum RuntimeBacking { pub struct Runtime { handle: RuntimeHandle<'static>, backing: RuntimeBacking, + name: String, } impl Runtime { pub fn from_boxed(interface: Box) -> Self { + Self::from_boxed_named("Unknown", interface) + } + + pub fn from_boxed_named(name: impl Into, interface: Box) -> Self { let mut adapter = Box::new(RuntimeFFIAdapter::new(interface)); Self { handle: adapter.ffi_interface(), backing: RuntimeBacking::Adapter { _adapter: adapter }, + name: name.into(), } } @@ -56,8 +62,9 @@ impl Runtime { start: crate::time::Instant, args: &[impl AsRef], ) -> Result { + let name = factory.name().to_string(); let interface: Box = factory.init(n_qubits, start, args)?; - Ok(Self::from_boxed(interface)) + Ok(Self::from_boxed_named(name, interface)) } pub fn load_from_file( @@ -69,6 +76,22 @@ impl Runtime { let plugin = plugin::RuntimePluginInterface::new_from_file(plugin_path)?; Self::new(plugin, n_qubits, start, args) } + + fn context(&self, message: &'static str) -> String { + format!("Runtime ({}): {message}", self.name) + } + + fn label(&self) -> String { + format!("Runtime ({})", self.name) + } + + fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { + plugin_utils::check_plugin_errno_with_context( + errno, + Some(self.handle.interface.last_error_fn), + || anyhow!("{}", self.context(message)), + ) + } } impl AsRef for Runtime { @@ -86,18 +109,18 @@ impl AsMut for Runtime { impl RuntimeInterface for Runtime { fn exit(&mut self) -> Result<()> { let _ = &self.backing; - check_errno( + self.check_errno( unsafe { (self.handle.interface.exit_fn)(self.handle.instance) }, - || anyhow!("Runtime: exit failed"), + "exit failed", ) } fn get_next_operations(&mut self) -> Result> { let mut batch_builder = BatchBuilder::default(); let ops = batch_builder.runtime_get_operation(); - check_errno( + self.check_errno( unsafe { (self.handle.interface.get_next_operations_fn)(self.handle.instance, ops) }, - || anyhow!("Runtime: get_next_operations failed"), + "get_next_operations failed", )?; let batch = batch_builder.finish(); if batch.is_empty() { @@ -108,31 +131,32 @@ impl RuntimeInterface for Runtime { } fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_start_fn)(self.handle.instance, shot_id, seed) }, - || anyhow!("Runtime: shot_start failed"), + "shot_start failed", ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_end_fn)(self.handle.instance) }, - || anyhow!("Runtime: shot_end failed"), + "shot_end failed", ) } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { plugin_utils::negotiate_gateset( - "Runtime", + &self.label(), self.handle.instance, - Some(self.handle.interface.negotiate_gateset_fn), + self.handle.interface.negotiate_gateset_fn, + Some(self.handle.interface.last_error_fn), gateset, ) } fn custom_call(&mut self, custom_tag: u64, data: &[u8]) -> Result { let mut result = 0; - check_errno( + self.check_errno( unsafe { (self.handle.interface.custom_call_fn)( self.handle.instance, @@ -142,15 +166,15 @@ impl RuntimeInterface for Runtime { &mut result, ) }, - || anyhow!("Runtime: custom_call failed"), + "custom_call failed", )?; Ok(result) } fn simulate_delay(&mut self, delay_ns: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.simulate_delay_fn)(self.handle.instance, delay_ns) }, - || anyhow!("Runtime: simulate_delay failed"), + "simulate_delay failed", ) } @@ -168,44 +192,44 @@ impl RuntimeInterface for Runtime { fn qalloc(&mut self) -> Result { let mut result = 0; - check_errno( + self.check_errno( unsafe { (self.handle.interface.qalloc_fn)(self.handle.instance, &mut result) }, - || anyhow!("Runtime: qalloc failed"), + "qalloc failed", )?; Ok(result) } fn qfree(&mut self, qubit_id: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.qfree_fn)(self.handle.instance, qubit_id) }, - || anyhow!("Runtime: qfree failed"), + "qfree failed", ) } fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { let data = gate.serialize(); - check_errno( + self.check_errno( unsafe { (self.handle.interface.gate_fn)(self.handle.instance, data.as_ptr(), data.len()) }, - || anyhow!("Runtime: gate failed"), + "gate failed", ) } fn measure(&mut self, qubit_id: u64) -> Result { let mut result = 0; - check_errno( + self.check_errno( unsafe { (self.handle.interface.measure_fn)(self.handle.instance, qubit_id, &mut result) }, - || anyhow!("Runtime: measure failed"), + "measure failed", )?; Ok(result) } fn measure_leaked(&mut self, qubit_id: u64) -> Result { let mut result = 0; - check_errno( + self.check_errno( unsafe { (self.handle.interface.measure_leaked_fn)( self.handle.instance, @@ -213,28 +237,28 @@ impl RuntimeInterface for Runtime { &mut result, ) }, - || anyhow!("Runtime: measure_leaked failed"), + "measure_leaked failed", )?; Ok(result) } fn reset(&mut self, qubit_id: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.reset_fn)(self.handle.instance, qubit_id) }, - || anyhow!("Runtime: reset failed"), + "reset failed", ) } fn force_result(&mut self, result_id: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.force_result_fn)(self.handle.instance, result_id) }, - || anyhow!("Runtime: force_result failed"), + "force_result failed", ) } fn get_bool_result(&mut self, result_id: u64) -> Result> { let mut result = -1; - check_errno( + self.check_errno( unsafe { (self.handle.interface.get_bool_result_fn)( self.handle.instance, @@ -242,21 +266,22 @@ impl RuntimeInterface for Runtime { &mut result, ) }, - || anyhow!("Runtime: get_bool_result failed"), + "get_bool_result failed", )?; match result { -1 => Ok(None), 0 => Ok(Some(false)), 1 => Ok(Some(true)), _ => Err(anyhow!( - "Runtime: get_bool_result returned an invalid value" + "{}", + self.context("get_bool_result returned an invalid value") )), } } fn get_u64_result(&mut self, result_id: u64) -> Result> { let mut result = u64::MAX; - check_errno( + self.check_errno( unsafe { (self.handle.interface.get_u64_result_fn)( self.handle.instance, @@ -264,7 +289,7 @@ impl RuntimeInterface for Runtime { &mut result, ) }, - || anyhow!("Runtime: get_u64_result failed"), + "get_u64_result failed", )?; if result == u64::MAX { Ok(None) @@ -274,43 +299,43 @@ impl RuntimeInterface for Runtime { } fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.set_bool_result_fn)(self.handle.instance, result_id, result) }, - || anyhow!("Runtime: set_bool_result failed"), + "set_bool_result failed", ) } fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.set_u64_result_fn)(self.handle.instance, result_id, result) }, - || anyhow!("Runtime: set_u64_result failed"), + "set_u64_result failed", ) } fn increment_future_refcount(&mut self, future: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.increment_future_refcount_fn)(self.handle.instance, future) }, - || anyhow!("Runtime: increment_future_refcount failed"), + "increment_future_refcount failed", ) } fn decrement_future_refcount(&mut self, future: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.decrement_future_refcount_fn)(self.handle.instance, future) }, - || anyhow!("Runtime: decrement_future_refcount failed"), + "decrement_future_refcount failed", ) } fn local_barrier(&mut self, qubits: &[u64], sleep_ns: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.local_barrier_fn)( self.handle.instance, @@ -319,14 +344,14 @@ impl RuntimeInterface for Runtime { sleep_ns, ) }, - || anyhow!("Runtime: local_barrier failed"), + "local_barrier failed", ) } fn global_barrier(&mut self, sleep_ns: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.global_barrier_fn)(self.handle.instance, sleep_ns) }, - || anyhow!("Runtime: global_barrier failed"), + "global_barrier failed", ) } } diff --git a/selene-core/rust/runtime/helper.rs b/selene-core/rust/runtime/helper.rs index 0bbef9c0..98e6b6b3 100644 --- a/selene-core/rust/runtime/helper.rs +++ b/selene-core/rust/runtime/helper.rs @@ -7,7 +7,9 @@ use std::{ffi, mem, sync::Arc}; use crate::gatewire::OwnedGateInstance; use crate::operation::plugin::{RuntimeGetOperationHandle, RuntimeGetOperationInterface}; use crate::plugin::write_negotiated_gateset; -use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; +use crate::utils::{ + convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno, set_last_error, +}; use super::{ Operation, RuntimeInterface, @@ -54,7 +56,7 @@ impl Helper { argv: *const *const ffi::c_char, ) -> i32 { if instance.is_null() { - eprintln!("cannot initialize runtime plugin: provided instance is null"); + set_last_error("cannot initialize runtime plugin: provided instance is null"); return -1; } @@ -99,6 +101,7 @@ impl Helper { let RuntimeGetOperationInterface { measure_fn, measure_leaked_fn, + postselect_fn, reset_fn, custom_fn, set_batch_time_fn, @@ -124,6 +127,10 @@ impl Helper { qubit_id, result_id, } => unsafe { measure_leaked_fn(ops.instance, qubit_id, result_id) }, + Operation::Postselect { + qubit_id, + target_value, + } => unsafe { postselect_fn(ops.instance, qubit_id, target_value) }, Operation::Reset { qubit_id } => unsafe { reset_fn(ops.instance, qubit_id) }, @@ -429,6 +436,23 @@ macro_rules! export_runtime_plugin { _assert_impl::<$factory_type>(); }; + unsafe extern "C" fn selene_runtime_last_error( + output: *mut c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { selene_core::utils::last_error_message(output, output_len, written) } + } + + unsafe extern "C" fn selene_runtime_get_name() -> *const c_char { + static NAME: std::sync::OnceLock = std::sync::OnceLock::new(); + NAME.get_or_init(|| { + std::ffi::CString::new(<$factory_type>::default().name()) + .expect("plugin names must not contain embedded null bytes") + }) + .as_ptr() + } + /// When Selene is initialised, it is provided with a default argument /// (the maximum number of qubits) and some custom arguments for the runtime. /// These arguments are provided to the error model through this initialization @@ -739,6 +763,8 @@ macro_rules! export_runtime_plugin { RuntimePluginDescriptorV1, current_api_version().as_u64(), { + last_error_fn: Some(selene_runtime_last_error), + get_name_fn: selene_runtime_get_name, init_fn: Some(selene_runtime_init), exit_fn: Some(selene_runtime_exit), get_next_operations_fn: Some(selene_runtime_get_next_operations), diff --git a/selene-core/rust/runtime/inline.rs b/selene-core/rust/runtime/inline.rs index 648c67a8..b160d778 100644 --- a/selene-core/rust/runtime/inline.rs +++ b/selene-core/rust/runtime/inline.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ gatewire::OwnedGateInstance, operation::plugin::{RuntimeGetOperationHandle, RuntimeGetOperationInterface}, - plugin::write_negotiated_gateset, + plugin::{LastErrorFn, write_negotiated_gateset}, runtime::Operation, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -34,6 +34,7 @@ impl RuntimeFFIAdapter { instance: &raw mut self.runtime as RuntimeInstance, interface: RuntimeOperationInterface { exit_fn: Self::exit, + last_error_fn: Self::last_error, get_next_operations_fn: Self::get_next_operations, shot_start_fn: Self::shot_start, shot_end_fn: Self::shot_end, @@ -76,6 +77,14 @@ impl RuntimeFFIAdapter { }) } + unsafe extern "C" fn last_error( + output: *mut ffi::c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { crate::utils::last_error_message(output, output_len, written) } + } + unsafe extern "C" fn get_next_operations( instance: RuntimeInstance, ops: RuntimeGetOperationHandle, @@ -88,6 +97,7 @@ impl RuntimeFFIAdapter { let RuntimeGetOperationInterface { measure_fn, measure_leaked_fn, + postselect_fn, reset_fn, custom_fn, set_batch_time_fn, @@ -111,6 +121,10 @@ impl RuntimeFFIAdapter { qubit_id, result_id, } => measure_leaked_fn(ops.instance, *qubit_id, *result_id), + Operation::Postselect { + qubit_id, + target_value, + } => postselect_fn(ops.instance, *qubit_id, *target_value), Operation::Reset { qubit_id } => reset_fn(ops.instance, *qubit_id), Operation::Gate { gate } => { let data = gate.serialize(); @@ -369,6 +383,7 @@ impl RuntimeFFIAdapter { #[non_exhaustive] pub struct RuntimeOperationInterface<'a> { pub exit_fn: unsafe extern "C" fn(RuntimeInstance) -> Errno, + pub last_error_fn: LastErrorFn, pub get_next_operations_fn: unsafe extern "C" fn(RuntimeInstance, RuntimeGetOperationHandle) -> Errno, pub shot_start_fn: unsafe extern "C" fn(RuntimeInstance, u64, u64) -> Errno, diff --git a/selene-core/rust/runtime/interface.rs b/selene-core/rust/runtime/interface.rs index 1b1fe1d7..f8fe0c9b 100644 --- a/selene-core/rust/runtime/interface.rs +++ b/selene-core/rust/runtime/interface.rs @@ -141,6 +141,7 @@ pub trait RuntimeInterface { pub trait RuntimeInterfaceFactory { type Interface: RuntimeInterface; + fn name(&self) -> &str; fn init( self: Arc, n_qubits: u64, diff --git a/selene-core/rust/runtime/plugin.rs b/selene-core/rust/runtime/plugin.rs index 2a3155e1..2a366e1c 100644 --- a/selene-core/rust/runtime/plugin.rs +++ b/selene-core/rust/runtime/plugin.rs @@ -6,10 +6,11 @@ pub use crate::operation::plugin::{ RuntimeGetOperationInterface, }; use crate::plugin::{ - NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + LastErrorFn, NegotiateGatesetFn, PluginDescriptorHeaderV1, PluginDescriptorV1, + check_plugin_errno, load_descriptor_v1, load_library, negotiate_gateset, read_plugin_name, require_callback, validate_descriptor_v1, }; -use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; +use crate::utils::{MetricValue, read_raw_metric, with_strings_to_cargs}; use super::{BatchOperation, RuntimeAPIVersion, RuntimeInterface, RuntimeInterfaceFactory}; use anyhow::{Result, anyhow}; @@ -23,8 +24,7 @@ pub type Errno = i32; #[repr(C)] #[derive(Clone, Copy)] pub struct RuntimePluginDescriptorV1 { - pub struct_size: u64, - pub api_version: u64, + pub header: PluginDescriptorHeaderV1, pub init_fn: Option< unsafe extern "C" fn( handle: *mut RuntimeInstance, @@ -113,12 +113,8 @@ pub struct RuntimePluginDescriptorV1 { impl PluginDescriptorV1 for RuntimePluginDescriptorV1 { const KIND: &'static str = "Runtime"; - fn struct_size(&self) -> u64 { - self.struct_size - } - - fn api_version(&self) -> u64 { - self.api_version + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header } } @@ -132,6 +128,8 @@ impl PluginDescriptorV1 for RuntimePluginDescriptorV1 { /// diligence must be done to verify the source and the trustworthiness of the provider. pub struct RuntimePluginInterface { _lib: libloading::Library, + last_error_fn: LastErrorFn, + name: String, init_fn: unsafe extern "C" fn( handle: *mut RuntimeInstance, n_qubits: u64, @@ -163,7 +161,7 @@ pub struct RuntimePluginInterface { ) -> Errno, global_barrier_fn: unsafe extern "C" fn(handle: RuntimeInstance, sleep_ns: u64) -> Errno, gate_fn: unsafe extern "C" fn(handle: RuntimeInstance, data: *const u8, len: usize) -> Errno, - negotiate_gateset_fn: Option>, + negotiate_gateset_fn: NegotiateGatesetFn, measure_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, result_id: *mut u64) -> Errno, measure_leaked_fn: @@ -210,8 +208,17 @@ impl RuntimePluginInterface { validate_descriptor_v1(&descriptor, |api_version| { RuntimeAPIVersion::from(api_version).validate() })?; + let get_name_fn = + require_callback("Runtime", "get_name_fn", descriptor.header.get_name_fn)?; + let name = read_plugin_name("Runtime", get_name_fn)?; Ok(Arc::new(Self { _lib: lib, + last_error_fn: require_callback( + "Runtime", + "last_error_fn", + descriptor.header.last_error_fn, + )?, + name, init_fn: require_callback("Runtime", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, get_next_operations_fn: require_callback( @@ -235,7 +242,11 @@ impl RuntimePluginInterface { descriptor.global_barrier_fn, )?, gate_fn: require_callback("Runtime", "gate_fn", descriptor.gate_fn)?, - negotiate_gateset_fn: descriptor.negotiate_gateset_fn, + negotiate_gateset_fn: require_callback( + "Runtime", + "negotiate_gateset_fn", + descriptor.negotiate_gateset_fn, + )?, measure_fn: require_callback("Runtime", "measure_fn", descriptor.measure_fn)?, measure_leaked_fn: require_callback( "Runtime", @@ -287,6 +298,10 @@ impl RuntimePluginInterface { impl RuntimeInterfaceFactory for RuntimePluginInterface { type Interface = RuntimePlugin; + fn name(&self) -> &str { + &self.name + } + fn init( self: Arc, n_qubits: u64, @@ -295,8 +310,9 @@ impl RuntimeInterfaceFactory for RuntimePluginInterface { ) -> Result> { let mut instance = std::ptr::null_mut(); with_strings_to_cargs(args, |argc, argv| { - check_errno( + check_plugin_errno( unsafe { (self.init_fn)(&mut instance, n_qubits, start.into(), argc, argv) }, + Some(self.last_error_fn), || anyhow!("RuntimePluginInterface: init failed"), ) })?; @@ -317,31 +333,36 @@ impl RuntimeInterface for RuntimePlugin { let Some(exit_fn) = self.interface.exit_fn else { return Ok(()); }; - check_errno(unsafe { exit_fn(self.instance) }, || { - anyhow!("RuntimePlugin: exit failed") - }) + check_plugin_errno( + unsafe { exit_fn(self.instance) }, + Some(self.interface.last_error_fn), + || anyhow!("RuntimePlugin: exit failed"), + ) } fn get_next_operations(&mut self) -> Result> { let mut batch_builder = BatchBuilder::default(); let ops = batch_builder.runtime_get_operation(); - check_errno( + check_plugin_errno( unsafe { (self.interface.get_next_operations_fn)(self.instance, ops) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: get_next_operations failed"), )?; Ok(Some(batch_builder.finish()).filter(|b| !b.is_empty())) } fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, seed) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: shot_start failed"), ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: shot_end failed"), ) } @@ -351,6 +372,7 @@ impl RuntimeInterface for RuntimePlugin { "RuntimePlugin", self.instance, self.interface.negotiate_gateset_fn, + Some(self.interface.last_error_fn), gateset, ) } @@ -367,23 +389,26 @@ impl RuntimeInterface for RuntimePlugin { fn qalloc(&mut self) -> Result { let mut result = 0; let result_ref = &mut result; - check_errno( + check_plugin_errno( unsafe { (self.interface.qalloc_fn)(self.instance, result_ref as *mut _) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: qalloc failed"), )?; Ok(result) } fn qfree(&mut self, qubit_id: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.qfree_fn)(self.instance, qubit_id) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: qfree failed"), ) } fn global_barrier(&mut self, sleep_ns: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.global_barrier_fn)(self.instance, sleep_ns) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: global barrier failed"), ) } @@ -391,7 +416,7 @@ impl RuntimeInterface for RuntimePlugin { fn local_barrier(&mut self, qubit_ids: &[u64], sleep_ns: u64) -> Result<()> { let qubit_ids_len = qubit_ids.len() as u64; let qubit_ids_ptr = qubit_ids.as_ptr(); - check_errno( + check_plugin_errno( unsafe { (self.interface.local_barrier_fn)( self.instance, @@ -400,14 +425,16 @@ impl RuntimeInterface for RuntimePlugin { sleep_ns, ) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: local barrier failed"), ) } fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { let data = gate.serialize(); - check_errno( + check_plugin_errno( unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: gate failed"), ) } @@ -415,8 +442,9 @@ impl RuntimeInterface for RuntimePlugin { fn measure(&mut self, qubit_id: u64) -> Result { let mut result = 0; let result_ref = &mut result; - check_errno( + check_plugin_errno( unsafe { (self.interface.measure_fn)(self.instance, qubit_id, result_ref as *mut _) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: measure failed"), )?; Ok(result) @@ -425,25 +453,28 @@ impl RuntimeInterface for RuntimePlugin { fn measure_leaked(&mut self, qubit_id: u64) -> Result { let mut result = 0; let result_ref = &mut result; - check_errno( + check_plugin_errno( unsafe { (self.interface.measure_leaked_fn)(self.instance, qubit_id, result_ref as *mut _) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: measure failed"), )?; Ok(result) } fn reset(&mut self, qubit_id: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.reset_fn)(self.instance, qubit_id) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: reset failed"), ) } fn force_result(&mut self, result_id: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.force_result_fn)(self.instance, result_id) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: force_result failed"), ) } @@ -451,10 +482,11 @@ impl RuntimeInterface for RuntimePlugin { fn get_bool_result(&mut self, result_id: u64) -> Result> { let mut result = 0i8; let result_ref = &mut result; - check_errno( + check_plugin_errno( unsafe { (self.interface.get_bool_result_fn)(self.instance, result_id, result_ref as *mut _) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: get_bool_result failed"), )?; // TODO document this @@ -468,10 +500,11 @@ impl RuntimeInterface for RuntimePlugin { fn get_u64_result(&mut self, result_id: u64) -> Result> { let mut result = 0u64; let result_ref = &mut result; - check_errno( + check_plugin_errno( unsafe { (self.interface.get_u64_result_fn)(self.instance, result_id, result_ref as *mut u64) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: get_u64_result failed"), )?; // TODO document this @@ -482,29 +515,33 @@ impl RuntimeInterface for RuntimePlugin { } fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.set_bool_result_fn)(self.instance, result_id, result) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: set_bool_result failed"), ) } fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.set_u64_result_fn)(self.instance, result_id, result) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: set_u64_result failed"), ) } fn increment_future_refcount(&mut self, future_ref: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.increment_future_refcount_fn)(self.instance, future_ref) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: increment_future_refcount failed"), ) } fn decrement_future_refcount(&mut self, future_ref: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.decrement_future_refcount_fn)(self.instance, future_ref) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: decrement_future_refcount failed"), ) } @@ -513,7 +550,7 @@ impl RuntimeInterface for RuntimePlugin { let mut result = 0; let result_ref = &mut result; if let Some(custom_call_fn) = self.interface.custom_call_fn { - check_errno( + check_plugin_errno( unsafe { custom_call_fn( self.instance, @@ -523,6 +560,7 @@ impl RuntimeInterface for RuntimePlugin { result_ref as *mut _, ) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: custom_call failed"), )?; Ok(result) @@ -535,8 +573,9 @@ impl RuntimeInterface for RuntimePlugin { fn simulate_delay(&mut self, delay_ns: u64) -> Result<()> { if let Some(simulate_delay_fn) = self.interface.simulate_delay_fn { - check_errno( + check_plugin_errno( unsafe { simulate_delay_fn(self.instance, delay_ns) }, + Some(self.interface.last_error_fn), || anyhow!("RuntimePlugin: simulate_delay failed"), ) } else { diff --git a/selene-core/rust/simulator.rs b/selene-core/rust/simulator.rs index 0bc32b12..a3f466d2 100644 --- a/selene-core/rust/simulator.rs +++ b/selene-core/rust/simulator.rs @@ -12,13 +12,14 @@ pub use inline::{SimulatorFFIAdapter, SimulatorHandle, SimulatorOperationInterfa pub use interface::{SimulatorInterface, SimulatorInterfaceFactory}; pub use version::SimulatorAPIVersion; -use crate::utils::{MetricValue, check_errno, read_raw_metric}; +use crate::utils::{MetricValue, read_raw_metric}; use anyhow::{Result, anyhow}; use crate::error_model::BatchResult; use crate::gatewire::DynamicGateSet; +use crate::operation::plugin::{BatchExtractor, OperationResultBuilder}; use crate::plugin as plugin_utils; -use crate::runtime::{BatchOperation, Operation}; +use crate::runtime::BatchOperation; enum SimulatorBacking { Adapter { _adapter: Box }, @@ -33,14 +34,23 @@ enum SimulatorBacking { pub struct Simulator { handle: SimulatorHandle<'static>, backing: SimulatorBacking, + name: String, } impl Simulator { pub fn from_boxed(interface: Box) -> Self { + Self::from_boxed_named("Unknown", interface) + } + + pub fn from_boxed_named( + name: impl Into, + interface: Box, + ) -> Self { let mut adapter = Box::new(SimulatorFFIAdapter::new(interface)); Self { handle: adapter.ffi_interface(), backing: SimulatorBacking::Adapter { _adapter: adapter }, + name: name.into(), } } @@ -54,8 +64,9 @@ impl Simulator { n_qubits: u64, args: &[impl AsRef], ) -> Result { + let name = factory.name().to_string(); let interface: Box = factory.init(n_qubits, args)?; - Ok(Self::from_boxed(interface)) + Ok(Self::from_boxed_named(name, interface)) } pub fn load_from_file( @@ -71,12 +82,29 @@ impl Simulator { Self { handle: handle.into_static(), backing: SimulatorBacking::Borrowed, + name: "Borrowed".to_string(), } } pub(crate) fn ffi_parts(&mut self) -> SimulatorHandle<'static> { self.handle } + + fn context(&self, message: &'static str) -> String { + format!("Simulator ({}): {message}", self.name) + } + + fn label(&self) -> String { + format!("Simulator ({})", self.name) + } + + fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { + plugin_utils::check_plugin_errno_with_context( + errno, + Some(self.handle.interface.last_error_fn), + || anyhow!("{}", self.context(message)), + ) + } } impl AsRef for Simulator { @@ -94,93 +122,65 @@ impl AsMut for Simulator { impl SimulatorInterface for Simulator { fn exit(&mut self) -> Result<()> { let _ = &self.backing; - check_errno( + self.check_errno( unsafe { (self.handle.interface.exit_fn)(self.handle.instance) }, - || anyhow!("Simulator: exit failed"), + "exit failed", ) } fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_start_fn)(self.handle.instance, shot_id, seed) }, - || anyhow!("Simulator: shot_start failed"), + "shot_start failed", ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + self.check_errno( unsafe { (self.handle.interface.shot_end_fn)(self.handle.instance) }, - || anyhow!("Simulator: shot_end failed"), + "shot_end failed", ) } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { plugin_utils::negotiate_gateset( - "Simulator", + &self.label(), self.handle.instance, - Some(self.handle.interface.negotiate_gateset_fn), + self.handle.interface.negotiate_gateset_fn, + Some(self.handle.interface.last_error_fn), gateset, ) } fn handle_operations(&mut self, operations: BatchOperation) -> Result { - let mut results = BatchResult::default(); - for operation in operations { - match operation { - Operation::Gate { gate } => { - let data = gate.serialize(); - check_errno( - unsafe { - (self.handle.interface.gate_fn)( - self.handle.instance, - data.as_ptr(), - data.len(), - ) - }, - || anyhow!("Simulator: gate failed"), - )?; - } - Operation::Measure { - qubit_id, - result_id, - } => match unsafe { - (self.handle.interface.measure_fn)(self.handle.instance, qubit_id) - } { - 0 => results.set_bool_result(result_id, false), - 1 => results.set_bool_result(result_id, true), - _ => return Err(anyhow!("Simulator: measure failed")), - }, - Operation::MeasureLeaked { - qubit_id, - result_id, - } => match unsafe { - (self.handle.interface.measure_fn)(self.handle.instance, qubit_id) - } { - 0 => results.set_u64_result(result_id, 0), - 1 => results.set_u64_result(result_id, 1), - _ => return Err(anyhow!("Simulator: measure leaked failed")), - }, - Operation::Reset { qubit_id } => { - check_errno( - unsafe { (self.handle.interface.reset_fn)(self.handle.instance, qubit_id) }, - || anyhow!("Simulator: reset failed"), - )?; - } - Operation::Custom { .. } => { - return Err(anyhow!("Simulator: custom operations are not supported")); - } - } - } - Ok(results) + let mut batch_extractor = BatchExtractor::from_batch_operation(operations); + let batch = batch_extractor.runtime_batch_extraction(); + let mut result_builder = OperationResultBuilder::default(); + let result = result_builder.operation_result(); + self.check_errno( + unsafe { + (self.handle.interface.handle_operations_fn)(self.handle.instance, batch, result) + }, + "handle_operations failed", + )?; + Ok(result_builder.finish()) } fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { - check_errno( - unsafe { - (self.handle.interface.postselect_fn)(self.handle.instance, qubit, target_value) + let results = self.handle_operations(BatchOperation::simulator(vec![ + crate::runtime::Operation::Postselect { + qubit_id: qubit, + target_value, }, - || anyhow!("Simulator: postselect failed"), - ) + ]))?; + if results.bool_results.is_empty() && results.u64_results.is_empty() { + Ok(()) + } else { + Err(anyhow!( + "{}", + self.context("postselect unexpectedly produced results") + )) + } } fn get_metric(&mut self, nth_metric: u8) -> Result> { @@ -196,13 +196,19 @@ impl SimulatorInterface for Simulator { } fn dump_state(&mut self, file: &std::path::Path, qubits: &[u64]) -> Result<()> { - let filename = file - .to_str() - .ok_or_else(|| anyhow!("Simulator: dump_state failed due to invalid UTF-8 path"))?; + let filename = file.to_str().ok_or_else(|| { + anyhow!( + "{}", + self.context("dump_state failed due to invalid UTF-8 path") + ) + })?; let filename = CString::new(filename).map_err(|_| { - anyhow!("Simulator: dump_state failed due to embedded null byte in path") + anyhow!( + "{}", + self.context("dump_state failed due to embedded null byte in path") + ) })?; - check_errno( + self.check_errno( unsafe { (self.handle.interface.dump_state_fn)( self.handle.instance, @@ -211,7 +217,7 @@ impl SimulatorInterface for Simulator { qubits.len() as u64, ) }, - || anyhow!("Simulator: dump_state failed"), + "dump_state failed", ) } } diff --git a/selene-core/rust/simulator/helper.rs b/selene-core/rust/simulator/helper.rs index 20eadb74..5c293f09 100644 --- a/selene-core/rust/simulator/helper.rs +++ b/selene-core/rust/simulator/helper.rs @@ -9,10 +9,13 @@ use super::{ interface::SimulatorInterfaceFactory, plugin::{Errno, SimulatorInstance}, }; -use crate::gatewire::OwnedGateInstance; +use crate::operation::plugin::{ + BatchBuilder, OperationResultHandle, RuntimeExtractOperationHandle, +}; use crate::plugin::write_negotiated_gateset; -use crate::runtime::{BatchOperation, Operation}; -use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; +use crate::utils::{ + convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno, set_last_error, +}; #[derive(Default)] /// A helper struct used by [crate::export_simulator_plugin] to implement the simulator @@ -21,10 +24,6 @@ use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to pub struct Helper(Arc); impl Helper { - fn singleton_batch(op: Operation) -> BatchOperation { - BatchOperation::simulator(vec![op]) - } - fn into_simulator_instance(s: Box) -> SimulatorInstance { Box::into_raw(s) as SimulatorInstance } @@ -56,7 +55,7 @@ impl Helper { argv: *const *const ffi::c_char, ) -> Errno { if instance.is_null() { - eprintln!("cannot initialize plugin: provided instance is null"); + set_last_error("cannot initialize plugin: provided instance is null"); return -1; } @@ -147,67 +146,37 @@ impl Helper { }), ) } - pub unsafe fn gate(instance: SimulatorInstance, data: *const u8, data_len: usize) -> Errno { + pub unsafe fn handle_operations( + instance: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno { result_to_errno( - "Failed to apply gate", + "Failed to handle simulator operations", Self::with_simulator_instance(instance, |simulator| { - let gate = OwnedGateInstance::deserialize(unsafe { - std::slice::from_raw_parts(data, data_len) - })?; - let results = simulator.handle_operations(Self::singleton_batch( - Operation::from_gate_instance(gate)?, - ))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("Gate unexpectedly produced results") + let mut batch_builder = BatchBuilder::default(); + let operations = batch_builder.runtime_get_operation(); + unsafe { (batch.interface.extract_fn)(&raw const batch, operations) }; + let results = simulator.handle_operations(batch_builder.finish())?; + for bool_result in results.bool_results { + unsafe { + (result.interface.set_bool_result_fn)( + result.instance, + bool_result.result_id, + bool_result.value, + ) + }; } - }), - ) - } - pub unsafe fn measure(instance: SimulatorInstance, qubit: u64) -> Errno { - let result = Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::Measure { - qubit_id: qubit, - result_id: 0, - }))?; - if results.u64_results.is_empty() && results.bool_results.len() == 1 { - Ok(results.bool_results[0].value) - } else { - anyhow::bail!("Measure expected exactly one bool result") - } - }); - match result { - Ok(false) => 0, - Ok(true) => 1, - Err(e) => { - eprintln!("Failed to measure qubit {qubit}: {e:?}"); - -1 - } - } - } - pub unsafe fn postselect(instance: SimulatorInstance, qubit: u64, target_value: bool) -> Errno { - result_to_errno( - "Failed to postselect qubit", - Self::with_simulator_instance(instance, |simulator| { - simulator.postselect(qubit, target_value) - }), - ) - } - pub unsafe fn reset(instance: SimulatorInstance, qubit: u64) -> Errno { - result_to_errno( - "Failed to reset qubit", - Self::with_simulator_instance(instance, |simulator| { - let results = - simulator.handle_operations(Self::singleton_batch(Operation::Reset { - qubit_id: qubit, - }))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - anyhow::bail!("Reset unexpectedly produced results") + for u64_result in results.u64_results { + unsafe { + (result.interface.set_u64_result_fn)( + result.instance, + u64_result.result_id, + u64_result.value, + ) + }; } + Ok::<(), anyhow::Error>(()) }), ) } @@ -236,6 +205,7 @@ macro_rules! export_simulator_plugin { }, version::current_api_version, }; + use selene_core::operation::plugin::{OperationResultHandle, RuntimeExtractOperationHandle}; use std::cell::LazyCell; use std::ffi::c_char; @@ -249,6 +219,23 @@ macro_rules! export_simulator_plugin { _assert_impl::<$factory_type>(); }; + unsafe extern "C" fn selene_simulator_last_error( + output: *mut c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { selene_core::utils::last_error_message(output, output_len, written) } + } + + unsafe extern "C" fn selene_simulator_get_name() -> *const c_char { + static NAME: std::sync::OnceLock = std::sync::OnceLock::new(); + NAME.get_or_init(|| { + std::ffi::CString::new(<$factory_type>::default().name()) + .expect("plugin names must not contain embedded null bytes") + }) + .as_ptr() + } + /// When Selene is initialised, it is provided with some default arguments /// (the maximum number of qubits, the path to a simulator plugin to use, etc) /// and some custom arguments for the simulator. These arguments are provided @@ -325,41 +312,13 @@ macro_rules! export_simulator_plugin { Helper::negotiate_gateset(instance, input, input_len, output, output_len, written) } - /// Apply a gate encoded with gatewire. - unsafe extern "C" fn selene_simulator_operation_gate( - instance: SimulatorInstance, - data: *const u8, - data_len: usize, - ) -> i32 { - Helper::gate(instance, data, data_len) - } - - /// Measure the qubit at the requested index. This is a destructive - /// operation. - unsafe extern "C" fn selene_simulator_operation_measure( - instance: SimulatorInstance, - qubit: u64, - ) -> i32 { - Helper::measure(instance, qubit) - } - - /// Postselect the qubit at the requested index. Some simulators may - /// choose to not support post-selection, in which case this function - /// should return an error. - unsafe extern "C" fn selene_simulator_operation_postselect( - instance: SimulatorInstance, - qubit: u64, - target_value: bool, - ) -> i32 { - Helper::postselect(instance, qubit, target_value) - } - - /// Reset the qubit at the requested index to the |0> state. - unsafe extern "C" fn selene_simulator_operation_reset( + /// Handle a batch of simulator operations. + unsafe extern "C" fn selene_simulator_handle_operations( instance: SimulatorInstance, - qubit: u64, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, ) -> i32 { - Helper::reset(instance, qubit) + Helper::handle_operations(instance, batch, result) } /// Get a metric from the simulator instance. @@ -405,16 +364,14 @@ macro_rules! export_simulator_plugin { SimulatorPluginDescriptorV1, current_api_version().as_u64(), { - get_name_fn: None, + last_error_fn: Some(selene_simulator_last_error), + get_name_fn: selene_simulator_get_name, init_fn: Some(selene_simulator_init), exit_fn: Some(selene_simulator_exit), shot_start_fn: Some(selene_simulator_shot_start), shot_end_fn: Some(selene_simulator_shot_end), + handle_operations_fn: Some(selene_simulator_handle_operations), negotiate_gateset_fn: Some(selene_simulator_negotiate_gateset), - gate_fn: Some(selene_simulator_operation_gate), - measure_fn: Some(selene_simulator_operation_measure), - postselect_fn: Some(selene_simulator_operation_postselect), - reset_fn: Some(selene_simulator_operation_reset), get_metrics_fn: Some(selene_simulator_get_metrics), dump_state_fn: Some(selene_simulator_dump_state), } diff --git a/selene-core/rust/simulator/inline.rs b/selene-core/rust/simulator/inline.rs index 67e44f4d..d2527e82 100644 --- a/selene-core/rust/simulator/inline.rs +++ b/selene-core/rust/simulator/inline.rs @@ -1,10 +1,11 @@ use super::{SimulatorInterface, plugin::SimulatorInstance}; use crate::{ gatewire::OwnedGateInstance, - plugin::write_negotiated_gateset, + operation::plugin::{BatchBuilder, OperationResultHandle, RuntimeExtractOperationHandle}, + plugin::{LastErrorFn, write_negotiated_gateset}, runtime::{BatchOperation, Operation}, simulator::plugin::Errno, - utils::{result_of_errno_to_errno, result_to_errno}, + utils::{result_of_errno_to_errno, result_to_errno, set_last_error}, }; use std::{ffi, marker::PhantomData}; @@ -28,12 +29,13 @@ pub fn borrowed_simulator_interface( instance: simulator as *mut &mut dyn SimulatorInterface as SimulatorInstance, interface: SimulatorOperationInterface { exit_fn: BorrowedSimulatorBridge::exit, + last_error_fn: BorrowedSimulatorBridge::last_error, shot_start_fn: BorrowedSimulatorBridge::shot_start, shot_end_fn: BorrowedSimulatorBridge::shot_end, negotiate_gateset_fn: BorrowedSimulatorBridge::negotiate_gateset, + handle_operations_fn: BorrowedSimulatorBridge::handle_operations, gate_fn: BorrowedSimulatorBridge::gate, measure_fn: BorrowedSimulatorBridge::measure, - postselect_fn: BorrowedSimulatorBridge::postselect, reset_fn: BorrowedSimulatorBridge::reset, get_metric_fn: BorrowedSimulatorBridge::get_metric, dump_state_fn: BorrowedSimulatorBridge::dump_state, @@ -61,6 +63,13 @@ impl BorrowedSimulatorBridge { Self::with_simulator(instance, |simulator| simulator.exit()) }) } + unsafe extern "C" fn last_error( + output: *mut ffi::c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { crate::utils::last_error_message(output, output_len, written) } + } unsafe extern "C" fn shot_start(instance: SimulatorInstance, shot_id: u64, seed: u64) -> Errno { result_to_errno("BorrowedSimulatorBridge: shot_start failed", unsafe { Self::with_simulator(instance, |simulator| simulator.shot_start(shot_id, seed)) @@ -95,6 +104,38 @@ impl BorrowedSimulatorBridge { }, ) } + unsafe extern "C" fn handle_operations( + instance: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno { + result_to_errno( + "BorrowedSimulatorBridge: handle_operations failed", + unsafe { + Self::with_simulator(instance, |simulator| { + let mut batch_builder = BatchBuilder::default(); + let operations = batch_builder.runtime_get_operation(); + (batch.interface.extract_fn)(&raw const batch, operations); + let results = simulator.handle_operations(batch_builder.finish())?; + for bool_result in results.bool_results { + (result.interface.set_bool_result_fn)( + result.instance, + bool_result.result_id, + bool_result.value, + ); + } + for u64_result in results.u64_results { + (result.interface.set_u64_result_fn)( + result.instance, + u64_result.result_id, + u64_result.value, + ); + } + Ok::<(), anyhow::Error>(()) + }) + }, + ) + } unsafe extern "C" fn gate( instance: SimulatorInstance, data: *const u8, @@ -135,15 +176,6 @@ impl BorrowedSimulatorBridge { Err(_) => -1, } } - unsafe extern "C" fn postselect( - instance: SimulatorInstance, - qubit: u64, - target: bool, - ) -> Errno { - result_to_errno("BorrowedSimulatorBridge: postselect failed", unsafe { - Self::with_simulator(instance, |simulator| simulator.postselect(qubit, target)) - }) - } unsafe extern "C" fn reset(instance: SimulatorInstance, qubit: u64) -> Errno { result_to_errno("BorrowedSimulatorBridge: reset failed", unsafe { Self::with_simulator(instance, |simulator| { @@ -205,12 +237,13 @@ impl SimulatorFFIAdapter { instance: &raw mut self.simulator as SimulatorInstance, interface: SimulatorOperationInterface { exit_fn: Self::exit, + last_error_fn: Self::last_error, shot_start_fn: Self::shot_start, shot_end_fn: Self::shot_end, negotiate_gateset_fn: Self::negotiate_gateset, + handle_operations_fn: Self::handle_operations, gate_fn: Self::gate, measure_fn: Self::measure, - postselect_fn: Self::postselect, reset_fn: Self::reset, get_metric_fn: Self::get_metric, dump_state_fn: Self::dump_state, @@ -234,6 +267,14 @@ impl SimulatorFFIAdapter { }) } + unsafe extern "C" fn last_error( + output: *mut ffi::c_char, + output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { crate::utils::last_error_message(output, output_len, written) } + } + unsafe extern "C" fn shot_start(instance: SimulatorInstance, shot_id: u64, seed: u64) -> Errno { result_to_errno( format!("SimulatorFFIAdapter: failed to start shot {shot_id}"), @@ -266,6 +307,36 @@ impl SimulatorFFIAdapter { }) } + unsafe extern "C" fn handle_operations( + instance: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno { + result_to_errno("SimulatorFFIAdapter: handle_operations failed", unsafe { + Self::with_simulator(instance, |simulator| { + let mut batch_builder = BatchBuilder::default(); + let operations = batch_builder.runtime_get_operation(); + (batch.interface.extract_fn)(&raw const batch, operations); + let results = simulator.handle_operations(batch_builder.finish())?; + for bool_result in results.bool_results { + (result.interface.set_bool_result_fn)( + result.instance, + bool_result.result_id, + bool_result.value, + ); + } + for u64_result in results.u64_results { + (result.interface.set_u64_result_fn)( + result.instance, + u64_result.result_id, + u64_result.value, + ); + } + Ok::<(), anyhow::Error>(()) + }) + }) + } + unsafe extern "C" fn gate( instance: SimulatorInstance, data: *const u8, @@ -306,24 +377,12 @@ impl SimulatorFFIAdapter { Ok(false) => 0, Ok(true) => 1, Err(e) => { - eprintln!("SimulatorFFIAdapter: measure failed: {e:#}"); + set_last_error(format!("{e:#}")); -1 } } } - unsafe extern "C" fn postselect( - instance: SimulatorInstance, - qubit: u64, - target_value: bool, - ) -> Errno { - result_to_errno("SimulatorFFIAdapter: postselect failed", unsafe { - Self::with_simulator(instance, |simulator| { - simulator.postselect(qubit, target_value) - }) - }) - } - unsafe extern "C" fn reset(instance: SimulatorInstance, qubit: u64) -> Errno { result_to_errno("SimulatorFFIAdapter: reset failed", unsafe { Self::with_simulator(instance, |simulator| { @@ -382,12 +441,16 @@ impl SimulatorFFIAdapter { #[non_exhaustive] pub struct SimulatorOperationInterface<'a> { pub exit_fn: unsafe extern "C" fn(instance: SimulatorInstance) -> Errno, + pub last_error_fn: LastErrorFn, pub shot_start_fn: unsafe extern "C" fn(instance: SimulatorInstance, shot_id: u64, seed: u64) -> Errno, pub shot_end_fn: unsafe extern "C" fn(instance: SimulatorInstance) -> Errno, + pub handle_operations_fn: unsafe extern "C" fn( + instance: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno, pub measure_fn: unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64) -> Errno, - pub postselect_fn: - unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, pub reset_fn: unsafe extern "C" fn(instance: SimulatorInstance, qubit: u64) -> Errno, pub get_metric_fn: unsafe extern "C" fn( instance: SimulatorInstance, @@ -419,10 +482,11 @@ impl SimulatorOperationInterface<'_> { pub fn into_static(self) -> SimulatorOperationInterface<'static> { SimulatorOperationInterface { exit_fn: self.exit_fn, + last_error_fn: self.last_error_fn, shot_start_fn: self.shot_start_fn, shot_end_fn: self.shot_end_fn, + handle_operations_fn: self.handle_operations_fn, measure_fn: self.measure_fn, - postselect_fn: self.postselect_fn, reset_fn: self.reset_fn, get_metric_fn: self.get_metric_fn, dump_state_fn: self.dump_state_fn, diff --git a/selene-core/rust/simulator/interface.rs b/selene-core/rust/simulator/interface.rs index 992c233d..d4d061b9 100644 --- a/selene-core/rust/simulator/interface.rs +++ b/selene-core/rust/simulator/interface.rs @@ -21,7 +21,7 @@ pub trait SimulatorInterface { // Validate the gates this simulator may receive. fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let supported = builtin::all(); + let supported = builtin::QuantinuumGateSet::dynamic(); if let Some(decl) = gateset.first_unsupported_by(&supported) { bail!("The chosen simulator does not support gate {}", decl.name); } @@ -36,8 +36,14 @@ pub trait SimulatorInterface { // If the post-selection isn't deemed possible, return an error. // This is optional functionality, and the default is to raise an // error. - fn postselect(&mut self, _qubit: u64, _target_value: bool) -> Result<()> { - bail!("Post-selection is not supported on the chosen simulator."); + fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { + self.handle_operations(BatchOperation::simulator(vec![ + crate::runtime::Operation::Postselect { + qubit_id: qubit, + target_value, + }, + ]))?; + Ok(()) } // Provide a metric to the output stream. @@ -57,6 +63,7 @@ pub trait SimulatorInterface { pub trait SimulatorInterfaceFactory { type Interface: SimulatorInterface; + fn name(&self) -> &str; fn init( self: Arc, n_qubits: u64, diff --git a/selene-core/rust/simulator/plugin.rs b/selene-core/rust/simulator/plugin.rs index 595bf06b..16cb5e50 100644 --- a/selene-core/rust/simulator/plugin.rs +++ b/selene-core/rust/simulator/plugin.rs @@ -1,13 +1,17 @@ use super::{SimulatorAPIVersion, SimulatorInterface, SimulatorInterfaceFactory}; use crate::error_model::BatchResult; use crate::gatewire::DynamicGateSet; +use crate::operation::plugin::{ + BatchExtractor, OperationResultBuilder, OperationResultHandle, RuntimeExtractOperationHandle, +}; use crate::plugin::{ - NegotiateGatesetFn, PluginDescriptorV1, load_descriptor_v1, load_library, negotiate_gateset, + LastErrorFn, NegotiateGatesetFn, PluginDescriptorHeaderV1, PluginDescriptorV1, + check_plugin_errno, load_descriptor_v1, load_library, negotiate_gateset, read_plugin_name, require_callback, validate_descriptor_v1, }; -use crate::runtime::{BatchOperation, Operation}; -use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; -use anyhow::{Result, anyhow, bail}; +use crate::runtime::BatchOperation; +use crate::utils::{MetricValue, read_raw_metric, with_strings_to_cargs}; +use anyhow::{Result, anyhow}; use std::ffi::OsStr; use std::ffi::c_char; use std::sync::Arc; @@ -19,9 +23,7 @@ pub type Errno = i32; #[repr(C)] #[derive(Clone, Copy)] pub struct SimulatorPluginDescriptorV1 { - pub struct_size: u64, - pub api_version: u64, - pub get_name_fn: Option *const c_char>, + pub header: PluginDescriptorHeaderV1, pub init_fn: Option< unsafe extern "C" fn( handle: *mut SimulatorInstance, @@ -34,11 +36,13 @@ pub struct SimulatorPluginDescriptorV1 { pub shot_start_fn: Option Errno>, pub shot_end_fn: Option Errno>, - pub measure_fn: Option Errno>, - pub postselect_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, + pub handle_operations_fn: Option< + unsafe extern "C" fn( + handle: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno, >, - pub reset_fn: Option Errno>, pub get_metrics_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, @@ -56,9 +60,6 @@ pub struct SimulatorPluginDescriptorV1 { n_qubits: u64, ) -> Errno, >, - pub gate_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, data: *const u8, len: usize) -> Errno, - >, pub negotiate_gateset_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, @@ -74,12 +75,8 @@ pub struct SimulatorPluginDescriptorV1 { impl PluginDescriptorV1 for SimulatorPluginDescriptorV1 { const KIND: &'static str = "Simulator"; - fn struct_size(&self) -> u64 { - self.struct_size - } - - fn api_version(&self) -> u64 { - self.api_version + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header } } @@ -97,6 +94,7 @@ impl PluginDescriptorV1 for SimulatorPluginDescriptorV1 { /// trustworthiness of the provider. pub struct SimulatorPluginInterface { _lib: libloading::Library, + last_error_fn: LastErrorFn, name: String, init_fn: unsafe extern "C" fn( handle: *mut SimulatorInstance, @@ -108,13 +106,12 @@ pub struct SimulatorPluginInterface { shot_start_fn: unsafe extern "C" fn(handle: SimulatorInstance, shot_id: u64, seed: u64) -> Errno, shot_end_fn: unsafe extern "C" fn(handle: SimulatorInstance) -> Errno, - negotiate_gateset_fn: Option>, - gate_fn: unsafe extern "C" fn(handle: SimulatorInstance, data: *const u8, len: usize) -> Errno, - measure_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, - postselect_fn: Option< - unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64, target_value: bool) -> Errno, - >, - reset_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, + negotiate_gateset_fn: NegotiateGatesetFn, + handle_operations_fn: unsafe extern "C" fn( + handle: SimulatorInstance, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, + ) -> Errno, get_metrics_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, @@ -146,21 +143,16 @@ impl SimulatorPluginInterface { validate_descriptor_v1(&descriptor, |api_version| { SimulatorAPIVersion::from(api_version).validate() })?; - let name = unsafe { - descriptor - .get_name_fn - .and_then(|f| f().as_ref()) - .map_or_else( - || "Unknown".to_string(), - |name| { - std::ffi::CStr::from_ptr(name) - .to_string_lossy() - .into_owned() - }, - ) - }; + let get_name_fn = + require_callback("Simulator", "get_name_fn", descriptor.header.get_name_fn)?; + let name = read_plugin_name("Simulator", get_name_fn)?; Ok(Arc::new(Self { _lib: lib, + last_error_fn: require_callback( + "Simulator", + "last_error_fn", + descriptor.header.last_error_fn, + )?, name, init_fn: require_callback("Simulator", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, @@ -170,11 +162,16 @@ impl SimulatorPluginInterface { descriptor.shot_start_fn, )?, shot_end_fn: require_callback("Simulator", "shot_end_fn", descriptor.shot_end_fn)?, - negotiate_gateset_fn: descriptor.negotiate_gateset_fn, - gate_fn: require_callback("Simulator", "gate_fn", descriptor.gate_fn)?, - measure_fn: require_callback("Simulator", "measure_fn", descriptor.measure_fn)?, - postselect_fn: descriptor.postselect_fn, - reset_fn: require_callback("Simulator", "reset_fn", descriptor.reset_fn)?, + negotiate_gateset_fn: require_callback( + "Simulator", + "negotiate_gateset_fn", + descriptor.negotiate_gateset_fn, + )?, + handle_operations_fn: require_callback( + "Simulator", + "handle_operations_fn", + descriptor.handle_operations_fn, + )?, get_metrics_fn: descriptor.get_metrics_fn, dump_state_fn: require_callback( "Simulator", @@ -190,38 +187,13 @@ pub struct SimulatorPlugin { instance: SimulatorInstance, } -impl SimulatorPlugin { - fn gate(&mut self, gate: &crate::gatewire::OwnedGateInstance) -> Result<()> { - let data = gate.serialize(); - check_errno( - unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, - || anyhow!("SimulatorPlugin({}): gate failed", &self.interface.name), - ) - } - - fn measure(&mut self, qubit: u64) -> Result { - let result = unsafe { (self.interface.measure_fn)(self.instance, qubit) }; - match result { - 0 => Ok(false), - 1 => Ok(true), - _ => Err(anyhow!( - "SimulatorPlugin({}): measure failed", - &self.interface.name - )), - } - } - - fn reset(&mut self, qubit: u64) -> Result<()> { - check_errno( - unsafe { (self.interface.reset_fn)(self.instance, qubit) }, - || anyhow!("SimulatorPlugin({}): reset failed", &self.interface.name), - ) - } -} - impl SimulatorInterfaceFactory for SimulatorPluginInterface { type Interface = SimulatorPlugin; + fn name(&self) -> &str { + &self.name + } + fn init( self: Arc, n_qubits: u64, @@ -229,8 +201,9 @@ impl SimulatorInterfaceFactory for SimulatorPluginInterface { ) -> Result> { let mut instance = std::ptr::null_mut(); with_strings_to_cargs(args, |argc, argv| { - check_errno( + check_plugin_errno( unsafe { (self.init_fn)(&mut instance, n_qubits, argc, argv) }, + Some(self.last_error_fn), || anyhow!("SimulatorPlugin: init failed"), ) })?; @@ -246,13 +219,16 @@ impl SimulatorInterface for SimulatorPlugin { let Some(exit_fn) = self.interface.exit_fn else { return Ok(()); }; - check_errno(unsafe { exit_fn(self.instance) }, || { - anyhow!("SimulatorPlugin: exit failed") - }) + check_plugin_errno( + unsafe { exit_fn(self.instance) }, + Some(self.interface.last_error_fn), + || anyhow!("SimulatorPlugin: exit failed"), + ) } fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, seed) }, + Some(self.interface.last_error_fn), || { anyhow!( "SimulatorPlugin({}): shot_start failed", @@ -262,8 +238,9 @@ impl SimulatorInterface for SimulatorPlugin { ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, + Some(self.interface.last_error_fn), || anyhow!("SimulatorPlugin({}): shot_end failed", &self.interface.name), ) } @@ -274,41 +251,26 @@ impl SimulatorInterface for SimulatorPlugin { &plugin, self.instance, self.interface.negotiate_gateset_fn, + Some(self.interface.last_error_fn), gateset, ) } fn handle_operations(&mut self, operations: BatchOperation) -> Result { - let mut results = BatchResult::default(); - for operation in operations { - match operation { - Operation::Gate { gate } => self.gate(&gate)?, - Operation::Measure { - qubit_id, - result_id, - } => results.set_bool_result(result_id, self.measure(qubit_id)?), - Operation::MeasureLeaked { - qubit_id, - result_id, - } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), - Operation::Reset { qubit_id } => self.reset(qubit_id)?, - Operation::Custom { .. } => {} - } - } - Ok(results) - } - fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { - let Some(postselect_fn) = self.interface.postselect_fn else { - bail!("The chosen simulator does not support postselection"); - }; - check_errno( - unsafe { postselect_fn(self.instance, qubit, target_value) }, + let mut batch_extractor = BatchExtractor::from_batch_operation(operations); + let batch = batch_extractor.runtime_batch_extraction(); + let mut result_builder = OperationResultBuilder::default(); + let result = result_builder.operation_result(); + check_plugin_errno( + unsafe { (self.interface.handle_operations_fn)(self.instance, batch, result) }, + Some(self.interface.last_error_fn), || { anyhow!( - "SimulatorPlugin({}): postselect failed", + "SimulatorPlugin({}): handle_operations failed", &self.interface.name ) }, - ) + )?; + Ok(result_builder.finish()) } fn get_metric(&mut self, nth_metric: u8) -> Result> { let Some(get_metrics_fn) = self.interface.get_metrics_fn else { @@ -328,7 +290,7 @@ impl SimulatorInterface for SimulatorPlugin { ) })?; let safe_filename = std::ffi::CString::new(filename).unwrap(); - check_errno( + check_plugin_errno( unsafe { (self.interface.dump_state_fn)( self.instance, @@ -337,6 +299,7 @@ impl SimulatorInterface for SimulatorPlugin { qubit_count, ) }, + Some(self.interface.last_error_fn), || { anyhow!( "SimulatorPlugin({}): dump_state failed", diff --git a/selene-core/rust/simulator/version.rs b/selene-core/rust/simulator/version.rs index d931951a..e73789d8 100644 --- a/selene-core/rust/simulator/version.rs +++ b/selene-core/rust/simulator/version.rs @@ -34,8 +34,8 @@ impl From for u64 { pub(crate) const CURRENT_API_VERSION: SimulatorAPIVersion = SimulatorAPIVersion { reserved: 0, major: 0, - minor: 1, - patch: 1, + minor: 2, + patch: 0, }; pub const fn current_api_version() -> SimulatorAPIVersion { diff --git a/selene-core/rust/utils.rs b/selene-core/rust/utils.rs index 3e453d10..cfa6d13f 100644 --- a/selene-core/rust/utils.rs +++ b/selene-core/rust/utils.rs @@ -1,10 +1,62 @@ -use core::{ffi, fmt}; +use core::{cell::RefCell, ffi, fmt}; use std::ffi::CString; use anyhow::bail; use crate::runtime::plugin::Errno; +thread_local! { + static LAST_ERROR: RefCell> = const { RefCell::new(None) }; +} + +pub fn set_last_error(message: impl Into) { + LAST_ERROR.with(|last_error| { + *last_error.borrow_mut() = Some(message.into()); + }); +} + +fn clear_last_error() { + LAST_ERROR.with(|last_error| { + *last_error.borrow_mut() = None; + }); +} + +/// Write the last plugin error captured by Selene's Rust helper ABI. +/// +/// `written` is set to the number of UTF-8 bytes in the message, excluding any +/// trailing NUL. If `output` is non-null, up to `output_len` bytes are copied +/// into it. The function returns -1 only when `written` is null or the provided +/// output buffer is too small. +/// +/// # Safety +/// +/// If `output` is non-null it must be valid for writes of `output_len` bytes. +/// `written` must be valid for one `usize` write. +pub unsafe extern "C" fn last_error_message( + output: *mut ffi::c_char, + output_len: usize, + written: *mut usize, +) -> Errno { + if written.is_null() { + return -1; + } + let message = LAST_ERROR.with(|last_error| last_error.borrow().clone().unwrap_or_default()); + let bytes = message.as_bytes(); + unsafe { + *written = bytes.len(); + } + if output.is_null() { + return 0; + } + if output_len < bytes.len() { + return -1; + } + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), output as *mut u8, bytes.len()); + } + 0 +} + #[repr(C)] #[derive(Copy, Clone)] pub struct SeleneStr { @@ -147,17 +199,20 @@ pub fn check_errno(errno: Errno, mut mk_err: impl FnMut() -> E) -> Result<(), /// /// # Arguments /// -/// * `msg` - A message to print if the result is an error. +/// * `msg` - Retained for call-site context; user-facing errors come from `r`. /// * `r` - The result to convert. /// /// # Returns /// -/// Returns `0` if the result is `Ok`. Otherwise, prints the error message to -/// stderr and returns `-1`. -pub fn result_to_errno(msg: impl AsRef, r: Result<(), E>) -> Errno { - let Err(e) = r else { return 0 }; +/// Returns `0` if the result is `Ok`. Otherwise, stores the error chain for +/// the plugin's `last_error` callback and returns `-1`. +pub fn result_to_errno(_msg: impl AsRef, r: Result<(), E>) -> Errno { + let Err(e) = r else { + clear_last_error(); + return 0; + }; - eprintln!("Error: {}\n{e}", msg.as_ref()); + set_last_error(format!("{e:#}")); -1 } /// Converts a ~Result~ of ~errno~ to an errno value. If the @@ -169,7 +224,7 @@ pub fn result_to_errno(msg: impl AsRef, r: Result<(), E>) /// /// # Arguments /// -/// * `msg` - A message to print if the result is an error. +/// * `msg` - Retained for call-site context; user-facing errors come from `r`. /// * `r` - The result to convert. /// /// # Returns @@ -177,13 +232,16 @@ pub fn result_to_errno(msg: impl AsRef, r: Result<(), E>) /// Returns `n` if the result is `Ok(n)`. /// Returnns `-1` if the result is Err pub fn result_of_errno_to_errno( - msg: impl AsRef, + _msg: impl AsRef, r: Result, ) -> Errno { match r { - Ok(n) => n, + Ok(n) => { + clear_last_error(); + n + } Err(e) => { - eprintln!("Error: {}\n{e}", msg.as_ref()); + set_last_error(format!("{e:#}")); -1 } } From 9995f9043cba412faddf446163ed2230e7ff34b5 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:41:26 +0100 Subject: [PATCH 05/19] refactor(ext): update builtin plugins for negotiated gates --- .../error-models/depolarizing/rust/lib.rs | 57 ++++++++------- selene-ext/error-models/ideal/rust/lib.rs | 13 ++-- .../error-models/simple-leakage/rust/lib.rs | 28 ++++++-- .../c/include/helios_qis/interface.h | 8 +++ .../interfaces/helios_qis/c/src/interface.c | 18 ++++- selene-ext/interfaces/helios_qis/c/src/main.c | 8 ++- .../sol_qis/c/include/sol_qis/interface.h | 8 +++ .../interfaces/sol_qis/c/src/interface.c | 18 ++++- selene-ext/interfaces/sol_qis/c/src/main.c | 8 ++- selene-ext/runtimes/simple/rust/lib.rs | 32 ++++----- selene-ext/runtimes/soft_rz/rust/lib.rs | 68 ++++++++++-------- .../simulators/classical-replay/rust/lib.rs | 59 ++++++++++------ selene-ext/simulators/coinflip/rust/lib.rs | 55 +++++++++------ .../simulators/quantum-replay/rust/lib.rs | 55 +++++++++------ .../selene_stim_c_interface/interface.h | 4 +- .../stim/c_interface/src/c_interface.cpp | 27 ++++++-- .../c_interface/src/tableau_simulator_min.h | 26 ++++--- selene-ext/simulators/stim/rust/bindings.rs | 2 + selene-ext/simulators/stim/rust/lib.rs | 69 ++++++++++++------- selene-ext/simulators/stim/rust/tests.rs | 26 +++++++ selene-ext/simulators/stim/rust/wrapper.rs | 30 +++++--- selene-sim/python/tests/test_qis.py | 25 ++++++- selene-sim/python/tests/test_replay.py | 3 +- selene-sim/rust/event_hooks/metrics.rs | 2 +- 24 files changed, 447 insertions(+), 202 deletions(-) diff --git a/selene-ext/error-models/depolarizing/rust/lib.rs b/selene-ext/error-models/depolarizing/rust/lib.rs index 59002b16..6e1c4425 100644 --- a/selene-ext/error-models/depolarizing/rust/lib.rs +++ b/selene-ext/error-models/depolarizing/rust/lib.rs @@ -5,7 +5,7 @@ use rand_pcg::Pcg64Mcg; use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; -use selene_core::gatewire::{DynamicGateSet, GateDecl, builtin}; +use selene_core::gatewire::{DynamicGateSet, builtin}; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -60,6 +60,22 @@ pub enum ErrorType { Z, } +selene_core::define_gateset! { + enum DepolarizingCorrectionGateSet { + RZ(builtin::RZ), + PhasedX(builtin::PhasedX), + } +} + +impl DepolarizingCorrectionGateSet { + fn dynamic() -> DynamicGateSet { + DynamicGateSet::from_declarations( + ::declarations(), + ) + .expect("depolarizing correction gate declarations are unique") + } +} + pub struct DepolarizingErrorModel { n_qubits: u64, rng: Pcg64Mcg, @@ -209,24 +225,6 @@ impl DepolarizingErrorModel { } Ok(None) } - - fn ensure_output_gate(declarations: &mut Vec, required: GateDecl) -> Result<()> { - match declarations - .iter_mut() - .find(|decl| decl.semantic_id == required.semantic_id) - { - Some(existing) if *existing == required => Ok(()), - Some(existing) => bail!( - "DepolarizingErrorModel requires builtin gate {}, but the incoming gateset uses the same semantic ID for incompatible gate {}", - required.name, - existing.name - ), - None => { - declarations.push(required); - Ok(()) - } - } - } } impl ErrorModelInterface for DepolarizingErrorModel { @@ -240,10 +238,9 @@ impl ErrorModelInterface for DepolarizingErrorModel { } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let mut declarations: Vec<_> = gateset.declarations().cloned().collect(); - Self::ensure_output_gate(&mut declarations, builtin::RZ::declaration())?; - Self::ensure_output_gate(&mut declarations, builtin::PhasedX::declaration())?; - DynamicGateSet::from_declarations(declarations).map_err(Into::into) + gateset + .union(&DepolarizingCorrectionGateSet::dynamic()) + .map_err(Into::into) } fn exit(&mut self) -> Result<()> { @@ -320,6 +317,16 @@ impl ErrorModelInterface for DepolarizingErrorModel { pending.push(error); } } + Operation::Postselect { + qubit_id, + target_value, + } => { + pending.push(Operation::Postselect { + qubit_id, + target_value, + }); + self.flush_pending(&mut pending, simulator, &mut results)?; + } Operation::Custom { .. } => { // Passively ignore custom operations } @@ -441,6 +448,10 @@ pub struct DepolarizingErrorModelFactory; impl ErrorModelInterfaceFactory for DepolarizingErrorModelFactory { type Interface = DepolarizingErrorModel; + fn name(&self) -> &str { + "Depolarizing" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/error-models/ideal/rust/lib.rs b/selene-ext/error-models/ideal/rust/lib.rs index 17350fac..be9da33d 100644 --- a/selene-ext/error-models/ideal/rust/lib.rs +++ b/selene-ext/error-models/ideal/rust/lib.rs @@ -24,7 +24,7 @@ impl ErrorModelInterface for IdealErrorModel { } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let supported = builtin::all(); + let supported = builtin::QuantinuumGateSet::dynamic(); if let Some(decl) = gateset.first_unsupported_by(&supported) { bail!("IdealErrorModel does not support gate {}", decl.name); } @@ -40,9 +40,10 @@ impl ErrorModelInterface for IdealErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::Gate { .. } | Operation::Measure { .. } | Operation::Reset { .. } => { - pending.push(op) - } + Operation::Gate { .. } + | Operation::Measure { .. } + | Operation::Postselect { .. } + | Operation::Reset { .. } => pending.push(op), Operation::MeasureLeaked { qubit_id, result_id, @@ -90,6 +91,10 @@ pub struct IdealErrorModelFactory; impl ErrorModelInterfaceFactory for IdealErrorModelFactory { type Interface = IdealErrorModel; + fn name(&self) -> &str { + "Ideal" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/error-models/simple-leakage/rust/lib.rs b/selene-ext/error-models/simple-leakage/rust/lib.rs index 5bd7e0c0..025f4c08 100644 --- a/selene-ext/error-models/simple-leakage/rust/lib.rs +++ b/selene-ext/error-models/simple-leakage/rust/lib.rs @@ -6,7 +6,7 @@ use selene_core::error_model::interface::ErrorModelInterfaceFactory; use selene_core::error_model::{BatchResult, ErrorModelInterface}; use selene_core::export_error_model_plugin; use selene_core::gatewire::{DynamicGateSet, builtin}; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -102,7 +102,7 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let supported = builtin::all(); + let supported = builtin::QuantinuumGateSet::dynamic(); if let Some(decl) = gateset.first_unsupported_by(&supported) { bail!( "SimpleLeakageErrorModel does not support gate {}", @@ -125,8 +125,8 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::Gate { .. } => match op.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { + Operation::Gate { .. } => match op.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { qubit_id, theta, phi, @@ -134,11 +134,11 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { self.maybe_leak(qubit_id)?; pending.push(Operation::phased_x(qubit_id, theta, phi)?); } - Some(BuiltinGate::RZ { qubit_id, theta }) => { + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { self.maybe_leak(qubit_id)?; pending.push(Operation::rz(qubit_id, theta)?); } - Some(BuiltinGate::ZZPhase { + Some(builtin::QuantinuumGate::ZZPhase { qubit_id_1, qubit_id_2, theta, @@ -148,7 +148,7 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { self.spread_leakage(qubit_id_1, qubit_id_2)?; pending.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); } - Some(BuiltinGate::PhasedXX { + Some(builtin::QuantinuumGate::PhasedXX { qubit_id_1, qubit_id_2, theta, @@ -214,6 +214,16 @@ impl ErrorModelInterface for SimpleLeakageErrorModel { self.leak_register[qubit_id as usize] = false; pending.push(Operation::Reset { qubit_id }); } + Operation::Postselect { + qubit_id, + target_value, + } => { + self.flush_pending(&mut pending, simulator, &mut results)?; + simulator.handle_operations(Self::singleton_batch(Operation::Postselect { + qubit_id, + target_value, + }))?; + } Operation::Custom { .. } => { // Passively ignore custom operations } @@ -247,6 +257,10 @@ pub struct SimpleLeakageErrorModelFactory; impl ErrorModelInterfaceFactory for SimpleLeakageErrorModelFactory { type Interface = SimpleLeakageErrorModel; + fn name(&self) -> &str { + "SimpleLeakage" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h b/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h index cdae8d8f..649520f1 100644 --- a/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h +++ b/selene-ext/interfaces/helios_qis/c/include/helios_qis/interface.h @@ -3,8 +3,16 @@ #include #include +#include +typedef struct selene_void_result_t (*selene_utility_registrar_t)(SeleneInstance* instance); EXPORT int selene_helios_run(int argc, char** argv, user_program_t entrypoint); +EXPORT int selene_helios_run_with_utilities( + int argc, + char** argv, + user_program_t entrypoint, + selene_utility_registrar_t register_utilities +); #endif diff --git a/selene-ext/interfaces/helios_qis/c/src/interface.c b/selene-ext/interfaces/helios_qis/c/src/interface.c index fc329297..3e776128 100644 --- a/selene-ext/interfaces/helios_qis/c/src/interface.c +++ b/selene-ext/interfaces/helios_qis/c/src/interface.c @@ -76,7 +76,12 @@ static int register_helios_gateset(void) { -int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { +int selene_helios_run_with_utilities( + int argc, + char** argv, + uint64_t (*entrypoint)(uint64_t), + selene_utility_registrar_t register_utilities +) { DIAGNOSTIC("selene_init() with args:\n"); for (int i = 0; i < argc; ++i) { DIAGNOSTIC(" %d: %s\n", i, argv[i]); @@ -95,6 +100,13 @@ int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { if (register_result != 0) { return register_result; } + if (register_utilities != NULL) { + void_result = register_utilities(selene_instance); + if (void_result.error_code != 0) { + ERROR("Error registering linked utilities: error code %" PRIu32 "\n", void_result.error_code); + return void_result.error_code; + } + } struct selene_u64_result_t n_shots = selene_shot_count(selene_instance); if (n_shots.error_code != 0) { ERROR("Error fetching shot count from selene: error code %" PRIu32 "\n", void_result.error_code); @@ -124,3 +136,7 @@ int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { selene_exit(selene_instance); return 0; } + +int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { + return selene_helios_run_with_utilities(argc, argv, entrypoint, NULL); +} diff --git a/selene-ext/interfaces/helios_qis/c/src/main.c b/selene-ext/interfaces/helios_qis/c/src/main.c index 4e6d4fa3..57b690bd 100644 --- a/selene-ext/interfaces/helios_qis/c/src/main.c +++ b/selene-ext/interfaces/helios_qis/c/src/main.c @@ -4,7 +4,13 @@ // compiled user program entrypoint extern uint64_t qmain(uint64_t); +extern struct selene_void_result_t selene_register_linked_utilities(SeleneInstance* instance); int main(int argc, char** argv) { - return selene_helios_run(argc, argv, qmain); + return selene_helios_run_with_utilities( + argc, + argv, + qmain, + selene_register_linked_utilities + ); } diff --git a/selene-ext/interfaces/sol_qis/c/include/sol_qis/interface.h b/selene-ext/interfaces/sol_qis/c/include/sol_qis/interface.h index 053d0ff8..c8331408 100644 --- a/selene-ext/interfaces/sol_qis/c/include/sol_qis/interface.h +++ b/selene-ext/interfaces/sol_qis/c/include/sol_qis/interface.h @@ -3,8 +3,16 @@ #include #include +#include +typedef struct selene_void_result_t (*selene_utility_registrar_t)(SeleneInstance* instance); EXPORT int selene_sol_run(int argc, char** argv, user_program_t entrypoint); +EXPORT int selene_sol_run_with_utilities( + int argc, + char** argv, + user_program_t entrypoint, + selene_utility_registrar_t register_utilities +); #endif diff --git a/selene-ext/interfaces/sol_qis/c/src/interface.c b/selene-ext/interfaces/sol_qis/c/src/interface.c index 1dbd132d..6a80f4de 100644 --- a/selene-ext/interfaces/sol_qis/c/src/interface.c +++ b/selene-ext/interfaces/sol_qis/c/src/interface.c @@ -76,7 +76,12 @@ static int register_sol_gateset(void) { -int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { +int selene_sol_run_with_utilities( + int argc, + char** argv, + uint64_t (*entrypoint)(uint64_t), + selene_utility_registrar_t register_utilities +) { DIAGNOSTIC("selene_init() with args:\n"); for (int i = 0; i < argc; ++i) { DIAGNOSTIC(" %d: %s\n", i, argv[i]); @@ -95,6 +100,13 @@ int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { if (register_result != 0) { return register_result; } + if (register_utilities != NULL) { + void_result = register_utilities(selene_instance); + if (void_result.error_code != 0) { + ERROR("Error registering linked utilities: error code %" PRIu32 "\n", void_result.error_code); + return void_result.error_code; + } + } struct selene_u64_result_t n_shots = selene_shot_count(selene_instance); if (n_shots.error_code != 0) { ERROR("Error fetching shot count from selene: error code %" PRIu32 "\n", void_result.error_code); @@ -124,3 +136,7 @@ int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { selene_exit(selene_instance); return 0; } + +int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { + return selene_sol_run_with_utilities(argc, argv, entrypoint, NULL); +} diff --git a/selene-ext/interfaces/sol_qis/c/src/main.c b/selene-ext/interfaces/sol_qis/c/src/main.c index 31d3e186..0be48f9a 100644 --- a/selene-ext/interfaces/sol_qis/c/src/main.c +++ b/selene-ext/interfaces/sol_qis/c/src/main.c @@ -4,7 +4,13 @@ // compiled user program entrypoint extern uint64_t qmain(uint64_t); +extern struct selene_void_result_t selene_register_linked_utilities(SeleneInstance* instance); int main(int argc, char** argv) { - return selene_sol_run(argc, argv, qmain); + return selene_sol_run_with_utilities( + argc, + argv, + qmain, + selene_register_linked_utilities + ); } diff --git a/selene-ext/runtimes/simple/rust/lib.rs b/selene-ext/runtimes/simple/rust/lib.rs index 4d5db5b7..54708a6c 100644 --- a/selene-ext/runtimes/simple/rust/lib.rs +++ b/selene-ext/runtimes/simple/rust/lib.rs @@ -5,10 +5,7 @@ use clap::Parser; use selene_core::{ export_runtime_plugin, gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, - runtime::{ - BatchOperation, BuiltinGate, Operation, RuntimeInterface, - interface::RuntimeInterfaceFactory, - }, + runtime::{BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory}, utils::MetricValue, }; @@ -65,11 +62,11 @@ impl SimpleRuntime { } pub fn push(&mut self, op: Operation) { - let duration_ns = match op.as_builtin_gate() { - Ok(Some(BuiltinGate::PhasedX { .. })) => self.params.duration_ns_phased_x, - Ok(Some(BuiltinGate::ZZPhase { .. })) => self.params.duration_ns_zz_phase, - Ok(Some(BuiltinGate::RZ { .. })) => self.params.duration_ns_rz, - Ok(Some(BuiltinGate::PhasedXX { .. })) => self.params.duration_ns_phased_xx, + let duration_ns = match op.as_gate_view::() { + Ok(Some(builtin::QuantinuumGate::PhasedX { .. })) => self.params.duration_ns_phased_x, + Ok(Some(builtin::QuantinuumGate::ZZPhase { .. })) => self.params.duration_ns_zz_phase, + Ok(Some(builtin::QuantinuumGate::RZ { .. })) => self.params.duration_ns_rz, + Ok(Some(builtin::QuantinuumGate::PhasedXX { .. })) => self.params.duration_ns_phased_xx, _ => match op { Operation::Measure { .. } => self.params.duration_ns_measure, Operation::Reset { .. } => self.params.duration_ns_reset, @@ -93,7 +90,6 @@ impl RuntimeInterface for SimpleRuntime { self.future_results.clear(); Ok(()) } - // Engine ops fn get_next_operations(&mut self) -> Result> { Ok(self.operation_queue.pop_front()) } @@ -108,7 +104,7 @@ impl RuntimeInterface for SimpleRuntime { Ok(()) } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let supported = builtin::all(); + let supported = builtin::QuantinuumGateSet::dynamic(); if let Some(decl) = gateset.first_unsupported_by(&supported) { bail!("SimpleRuntime does not support gate {}", decl.name); } @@ -143,8 +139,8 @@ impl RuntimeInterface for SimpleRuntime { } } fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { - match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { + match Operation::gate_as_view::(gate)? { + Some(builtin::QuantinuumGate::PhasedX { qubit_id, theta, phi, @@ -158,7 +154,7 @@ impl RuntimeInterface for SimpleRuntime { self.push(Operation::phased_x(qubit_id, theta, phi)?); Ok(()) } - Some(BuiltinGate::ZZPhase { + Some(builtin::QuantinuumGate::ZZPhase { qubit_id_1, qubit_id_2, theta, @@ -172,7 +168,7 @@ impl RuntimeInterface for SimpleRuntime { self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); Ok(()) } - Some(BuiltinGate::RZ { qubit_id, theta }) => { + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { if qubit_id >= self.qubits.len() as u64 { bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); } @@ -182,7 +178,7 @@ impl RuntimeInterface for SimpleRuntime { self.push(Operation::rz(qubit_id, theta)?); Ok(()) } - Some(BuiltinGate::PhasedXX { + Some(builtin::QuantinuumGate::PhasedXX { qubit_id_1, qubit_id_2, theta, @@ -313,6 +309,10 @@ struct SimpleRuntimeFactory; impl RuntimeInterfaceFactory for SimpleRuntimeFactory { type Interface = SimpleRuntime; + fn name(&self) -> &str { + "SimpleRuntime" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/runtimes/soft_rz/rust/lib.rs b/selene-ext/runtimes/soft_rz/rust/lib.rs index 862f868c..a669374c 100644 --- a/selene-ext/runtimes/soft_rz/rust/lib.rs +++ b/selene-ext/runtimes/soft_rz/rust/lib.rs @@ -5,13 +5,26 @@ use clap::Parser; use selene_core::{ export_runtime_plugin, gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, - runtime::{ - BatchOperation, BuiltinGate, Operation, RuntimeInterface, - interface::RuntimeInterfaceFactory, - }, + runtime::{BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory}, utils::MetricValue, }; +selene_core::define_gateset! { + enum SoftRZEmittedGateSet { + PhasedX(builtin::PhasedX), + ZZPhase(builtin::ZZPhase), + } +} + +impl SoftRZEmittedGateSet { + fn dynamic() -> DynamicGateSet { + DynamicGateSet::from_declarations( + ::declarations(), + ) + .expect("SoftRZ emitted gate declarations are unique") + } +} + #[derive(Parser, Debug)] struct Params { #[arg(long)] @@ -89,9 +102,9 @@ impl SoftRZRuntime { self.operation_queue[append_idx].add_operation(op); } else { // We didn't find a batch to append to, so we need to create a new batch for this operation. - let duration = match op.as_builtin_gate() { - Ok(Some(BuiltinGate::PhasedX { .. })) => self.params.duration_ns_phased_x, - Ok(Some(BuiltinGate::ZZPhase { .. })) => self.params.duration_ns_zz_phase, + let duration = match op.as_gate::() { + Ok(Some(SoftRZEmittedGateSet::PhasedX(_))) => self.params.duration_ns_phased_x, + Ok(Some(SoftRZEmittedGateSet::ZZPhase(_))) => self.params.duration_ns_zz_phase, _ => match op { Operation::Measure { .. } => self.params.duration_ns_measure, Operation::Reset { .. } => self.params.duration_ns_reset, @@ -133,13 +146,16 @@ impl SoftRZRuntime { let same_type = batch.iter_ops().all(|batch_op| match (batch_op, op) { (Operation::Gate { .. }, Operation::Gate { .. }) => { matches!( - (batch_op.as_builtin_gate(), op.as_builtin_gate()), ( - Ok(Some(BuiltinGate::PhasedX { .. })), - Ok(Some(BuiltinGate::PhasedX { .. })) + batch_op.as_gate::(), + op.as_gate::() + ), + ( + Ok(Some(SoftRZEmittedGateSet::PhasedX(_))), + Ok(Some(SoftRZEmittedGateSet::PhasedX(_))) ) | ( - Ok(Some(BuiltinGate::ZZPhase { .. })), - Ok(Some(BuiltinGate::ZZPhase { .. })) + Ok(Some(SoftRZEmittedGateSet::ZZPhase(_))), + Ok(Some(SoftRZEmittedGateSet::ZZPhase(_))) ) ) } @@ -171,7 +187,6 @@ impl RuntimeInterface for SoftRZRuntime { self.future_results.clear(); Ok(()) } - // Engine ops fn get_next_operations(&mut self) -> Result> { debug_assert!( self.flush_size <= self.operation_queue.len(), @@ -195,19 +210,11 @@ impl RuntimeInterface for SoftRZRuntime { Ok(()) } fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { - let accepted = DynamicGateSet::from_declarations([ - builtin::RZ::declaration(), - builtin::PhasedX::declaration(), - builtin::ZZPhase::declaration(), - ])?; + let accepted = builtin::HeliosGateSet::dynamic(); if let Some(decl) = gateset.first_unsupported_by(&accepted) { bail!("SoftRZRuntime does not support gate {}", decl.name); } - DynamicGateSet::from_declarations([ - builtin::PhasedX::declaration(), - builtin::ZZPhase::declaration(), - ]) - .map_err(Into::into) + Ok(SoftRZEmittedGateSet::dynamic()) } fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { self.flush_size = self.operation_queue.len(); @@ -250,8 +257,8 @@ impl RuntimeInterface for SoftRZRuntime { } } fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { - match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { + match Operation::gate_as_view::(gate)? { + Some(builtin::HeliosGate::PhasedX { qubit_id, theta, phi, @@ -265,7 +272,7 @@ impl RuntimeInterface for SoftRZRuntime { self.push(Operation::phased_x(qubit_id, theta, phi - phase)?); Ok(()) } - Some(BuiltinGate::ZZPhase { + Some(builtin::HeliosGate::ZZPhase { qubit_id_1, qubit_id_2, theta, @@ -279,7 +286,7 @@ impl RuntimeInterface for SoftRZRuntime { self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); Ok(()) } - Some(BuiltinGate::RZ { qubit_id, theta }) => { + Some(builtin::HeliosGate::RZ { qubit_id, theta }) => { if qubit_id >= self.qubits.len() as u64 { bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); } @@ -291,9 +298,6 @@ impl RuntimeInterface for SoftRZRuntime { }; Ok(()) } - Some(BuiltinGate::PhasedXX { .. }) => { - bail!("The PhasedXX gate is not compatible with the SoftRZRuntime") - } None => bail!("SoftRZRuntime does not support this gate"), } } @@ -415,6 +419,10 @@ struct SoftRZRuntimeFactory; impl RuntimeInterfaceFactory for SoftRZRuntimeFactory { type Interface = SoftRZRuntime; + fn name(&self) -> &str { + "SoftRZ" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/simulators/classical-replay/rust/lib.rs b/selene-ext/simulators/classical-replay/rust/lib.rs index d215570c..ec5f420e 100644 --- a/selene-ext/simulators/classical-replay/rust/lib.rs +++ b/selene-ext/simulators/classical-replay/rust/lib.rs @@ -2,7 +2,8 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::gatewire::builtin; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -144,26 +145,30 @@ impl SimulatorInterface for ClassicalReplaySimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, + Operation::Gate { .. } => { + match operation.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(builtin::QuantinuumGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.rz(qubit_id, theta)? + } + Some(builtin::QuantinuumGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + } + } Operation::Measure { qubit_id, result_id, @@ -173,6 +178,14 @@ impl SimulatorInterface for ClassicalReplaySimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => { + if self.measure(qubit_id)? != target_value { + anyhow::bail!("classical replay postselection failed for qubit {qubit_id}"); + } + } Operation::Custom { .. } => {} _ => {} } @@ -197,6 +210,10 @@ pub struct ClassicalReplaySimulatorFactory; impl SimulatorInterfaceFactory for ClassicalReplaySimulatorFactory { type Interface = ClassicalReplaySimulator; + fn name(&self) -> &str { + "ClassicalReplay" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/simulators/coinflip/rust/lib.rs b/selene-ext/simulators/coinflip/rust/lib.rs index 0ee49da7..218af125 100644 --- a/selene-ext/simulators/coinflip/rust/lib.rs +++ b/selene-ext/simulators/coinflip/rust/lib.rs @@ -4,7 +4,8 @@ use rand::{Rng, SeedableRng}; use rand_pcg::Pcg64Mcg; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::gatewire::builtin; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -126,26 +127,30 @@ impl SimulatorInterface for CoinflipSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, + Operation::Gate { .. } => { + match operation.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(builtin::QuantinuumGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.rz(qubit_id, theta)? + } + Some(builtin::QuantinuumGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + } + } Operation::Measure { qubit_id, result_id, @@ -155,6 +160,10 @@ impl SimulatorInterface for CoinflipSimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } @@ -196,6 +205,10 @@ pub struct CoinflipSimulatorFactory; impl SimulatorInterfaceFactory for CoinflipSimulatorFactory { type Interface = CoinflipSimulator; + fn name(&self) -> &str { + "Coinflip" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/simulators/quantum-replay/rust/lib.rs b/selene-ext/simulators/quantum-replay/rust/lib.rs index ea06a4c6..28ddf7f6 100644 --- a/selene-ext/simulators/quantum-replay/rust/lib.rs +++ b/selene-ext/simulators/quantum-replay/rust/lib.rs @@ -2,7 +2,8 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::gatewire::builtin; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::simulator::{Simulator, SimulatorInterface}; use selene_core::utils::MetricValue; @@ -196,26 +197,30 @@ impl SimulatorInterface for QuantumReplaySimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, + Operation::Gate { .. } => { + match operation.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(builtin::QuantinuumGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.rz(qubit_id, theta)? + } + Some(builtin::QuantinuumGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + } + } Operation::Measure { qubit_id, result_id, @@ -225,6 +230,10 @@ impl SimulatorInterface for QuantumReplaySimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => self.wrapped.postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } @@ -256,6 +265,10 @@ pub struct QuantumReplaySimulatorFactory; impl SimulatorInterfaceFactory for QuantumReplaySimulatorFactory { type Interface = QuantumReplaySimulator; + fn name(&self) -> &str { + "QuantumReplay" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/simulators/stim/c_interface/include/selene_stim_c_interface/interface.h b/selene-ext/simulators/stim/c_interface/include/selene_stim_c_interface/interface.h index 933975f7..2f0b63ce 100644 --- a/selene-ext/simulators/stim/c_interface/include/selene_stim_c_interface/interface.h +++ b/selene-ext/simulators/stim/c_interface/include/selene_stim_c_interface/interface.h @@ -32,9 +32,11 @@ bool stim_tableausimulator_min_do_MZ(TableauSimulatorMin* sim, unsigned int q); bool stim_tableausimulator_min_do_POSTSELECT_Z(TableauSimulatorMin* sim,unsigned int q, bool target_result); void stim_tableausimulator_min_get_stabilizers(TableauSimulatorMin* sim, char** write); void stim_tableausimulator_min_free_stabilizers(char* written); +void stim_tableausimulator_min_get_last_error(TableauSimulatorMin* sim, char** write); +void stim_tableausimulator_min_free_last_error(char* written); #ifdef __cplusplus } #endif -#endif // SELENE_STIM_C_WRAPPER_H \ No newline at end of file +#endif // SELENE_STIM_C_WRAPPER_H diff --git a/selene-ext/simulators/stim/c_interface/src/c_interface.cpp b/selene-ext/simulators/stim/c_interface/src/c_interface.cpp index 2bc7d853..320da7c0 100644 --- a/selene-ext/simulators/stim/c_interface/src/c_interface.cpp +++ b/selene-ext/simulators/stim/c_interface/src/c_interface.cpp @@ -1,6 +1,17 @@ #include "selene_stim_c_interface/interface.h" #include "tableau_simulator_min.h" +namespace { + +void write_owned_string(std::string const& str, char** write) { + char* cstr = new char[str.size() + 1]; + std::copy(str.begin(), str.end(), cstr); + cstr[str.size()] = '\0'; + *write = cstr; +} + +} // namespace + extern "C" { TableauSimulatorMin* stim_tableausimulator_min_create( @@ -98,15 +109,19 @@ bool stim_tableausimulator_min_do_POSTSELECT_Z(TableauSimulatorMin* sim, unsigne } void stim_tableausimulator_min_get_stabilizers(TableauSimulatorMin* sim, char** write) { - std::string str = sim->get_stabilizers(); - char* cstr = new char[str.size() + 1]; - std::copy(str.begin(), str.end(), cstr); - cstr[str.size()] = '\0'; - *write = cstr; + write_owned_string(sim->get_stabilizers(), write); } void stim_tableausimulator_min_free_stabilizers(char* written) { delete[] written; } -} // extern "C" \ No newline at end of file +void stim_tableausimulator_min_get_last_error(TableauSimulatorMin* sim, char** write) { + write_owned_string(sim->get_last_error(), write); +} + +void stim_tableausimulator_min_free_last_error(char* written) { + delete[] written; +} + +} // extern "C" diff --git a/selene-ext/simulators/stim/c_interface/src/tableau_simulator_min.h b/selene-ext/simulators/stim/c_interface/src/tableau_simulator_min.h index 789532c9..6f559cc8 100644 --- a/selene-ext/simulators/stim/c_interface/src/tableau_simulator_min.h +++ b/selene-ext/simulators/stim/c_interface/src/tableau_simulator_min.h @@ -11,10 +11,13 @@ #define SELENE_STIM_TABLEAU_SIMULATOR_MIN_H #include "stim.h" +#include +#include struct TableauSimulatorMin{ stim::Tableau<64> inverse_state; std::mt19937_64 rng; + std::string last_error; TableauSimulatorMin(size_t n_qubits, uint64_t random_seed) : inverse_state{stim::Tableau<64>::identity(n_qubits)}, @@ -108,23 +111,24 @@ struct TableauSimulatorMin{ return result; } bool do_POSTSELECT_Z(unsigned int q, bool target_result) { + last_error.clear(); bool result = collapse_qubit_z(q, target_result ? -1 : +1); if (result == target_result) { return true; } - // Can't postselect - write an error and return false. - fprintf( - stderr, - "Error: Postselection impossible.\n" - "Qubit %u was asked to postselect to state |%d>, " - "but was in the perpendicular state |%d>.", - q, - target_result ? 1 : 0, - target_result ? 0 : 1 - ); + std::stringstream ss; + ss << "Postselection impossible.\n" + << "Qubit " << q << " was asked to postselect to state |" + << (target_result ? 1 : 0) << ">, but was in the perpendicular state |" + << (target_result ? 0 : 1) << ">."; + last_error = ss.str(); return false; } + std::string get_last_error() { + return last_error; + } + std::string get_stabilizers() { std::stringstream ss; for(auto const& pauli_string : inverse_state.inverse().stabilizers(true)){ @@ -174,4 +178,4 @@ struct TableauSimulatorMin{ } }; -#endif // SELENE_STIM_TABLEAU_SIMULATOR_MIN_H \ No newline at end of file +#endif // SELENE_STIM_TABLEAU_SIMULATOR_MIN_H diff --git a/selene-ext/simulators/stim/rust/bindings.rs b/selene-ext/simulators/stim/rust/bindings.rs index af7b7fe5..9caf8d64 100644 --- a/selene-ext/simulators/stim/rust/bindings.rs +++ b/selene-ext/simulators/stim/rust/bindings.rs @@ -33,4 +33,6 @@ unsafe extern "C" { ) -> bool; pub fn stim_tableausimulator_min_get_stabilizers(rawptr: *mut c_void, write: *mut *mut c_char); pub fn stim_tableausimulator_min_free_stabilizers(written: *mut c_char); + pub fn stim_tableausimulator_min_get_last_error(rawptr: *mut c_void, write: *mut *mut c_char); + pub fn stim_tableausimulator_min_free_last_error(written: *mut c_char); } diff --git a/selene-ext/simulators/stim/rust/lib.rs b/selene-ext/simulators/stim/rust/lib.rs index 5e1c8abc..a10f5faa 100644 --- a/selene-ext/simulators/stim/rust/lib.rs +++ b/selene-ext/simulators/stim/rust/lib.rs @@ -9,7 +9,8 @@ use clap::Parser; use num_enum::{IntoPrimitive, TryFromPrimitive}; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::gatewire::builtin; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; @@ -95,26 +96,30 @@ impl SimulatorInterface for StimSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, + Operation::Gate { .. } => { + match operation.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(builtin::QuantinuumGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.rz(qubit_id, theta)? + } + Some(builtin::QuantinuumGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + } + } Operation::Measure { qubit_id, result_id, @@ -124,6 +129,10 @@ impl SimulatorInterface for StimSimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => self.do_postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } @@ -515,9 +524,17 @@ impl StimSimulator { let q_u32: u32 = qubit.try_into()?; match self.simulator.postselect_z(q_u32, target_value) { true => Ok(()), - false => Err(anyhow!( - "Postselect(qubit={qubit}, target_value={target_value}) failed.", - )), + false => { + let detail = self.simulator.last_error(); + let detail = detail.trim(); + if detail.is_empty() { + Err(anyhow!( + "Postselect(qubit={qubit}, target_value={target_value}) failed.", + )) + } else { + Err(anyhow!("{detail}")) + } + } } } } @@ -563,6 +580,10 @@ pub struct StimSimulatorFactory; impl SimulatorInterfaceFactory for StimSimulatorFactory { type Interface = StimSimulator; + fn name(&self) -> &str { + "Stim" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/selene-ext/simulators/stim/rust/tests.rs b/selene-ext/simulators/stim/rust/tests.rs index f37da4ab..dd8741a0 100644 --- a/selene-ext/simulators/stim/rust/tests.rs +++ b/selene-ext/simulators/stim/rust/tests.rs @@ -1,9 +1,35 @@ use crate::StimSimulatorFactory; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::conformance_testing::run_basic_tests; +use selene_core::simulator::{Simulator, SimulatorInterface}; use std::sync::Arc; + #[test] fn basic_conformance_test() { let interface = Arc::new(StimSimulatorFactory); let args = vec!["".to_string(), "--angle-threshold=0.001".to_string()]; run_basic_tests(interface, args); } + +#[test] +fn impossible_postselection_reports_simulator_detail() { + let mut simulator = Simulator::new( + Arc::new(StimSimulatorFactory), + 1, + &["", "--angle-threshold=0.001"], + ) + .unwrap(); + simulator + .handle_operations(BatchOperation::simulator(vec![ + Operation::phased_x(0, std::f64::consts::PI, 0.0).unwrap(), + ])) + .unwrap(); + + let error = simulator.postselect(0, false).unwrap_err(); + + assert_eq!( + error.to_string(), + "Simulator (Stim): postselect failed: Postselection impossible.\n\ + Qubit 0 was asked to postselect to state |0>, but was in the perpendicular state |1>." + ); +} diff --git a/selene-ext/simulators/stim/rust/wrapper.rs b/selene-ext/simulators/stim/rust/wrapper.rs index 75527391..69f27e1e 100644 --- a/selene-ext/simulators/stim/rust/wrapper.rs +++ b/selene-ext/simulators/stim/rust/wrapper.rs @@ -77,18 +77,30 @@ impl TableauSimulatorMin { pub fn get_stabilisers(&mut self) -> String { let mut stringptr = std::ptr::null_mut(); unsafe { bindings::stim_tableausimulator_min_get_stabilizers(self.ptr, &mut stringptr) }; - let result: String = unsafe { - if stringptr.is_null() { - String::new() - } else { - std::ffi::CStr::from_ptr(stringptr) - .to_string_lossy() - .into_owned() - } - }; + let result = owned_c_string(stringptr); unsafe { bindings::stim_tableausimulator_min_free_stabilizers(stringptr) }; result } + + pub fn last_error(&mut self) -> String { + let mut stringptr = std::ptr::null_mut(); + unsafe { bindings::stim_tableausimulator_min_get_last_error(self.ptr, &mut stringptr) }; + let result = owned_c_string(stringptr); + unsafe { bindings::stim_tableausimulator_min_free_last_error(stringptr) }; + result + } +} + +fn owned_c_string(stringptr: *mut std::ffi::c_char) -> String { + unsafe { + if stringptr.is_null() { + String::new() + } else { + std::ffi::CStr::from_ptr(stringptr) + .to_string_lossy() + .into_owned() + } + } } impl Drop for TableauSimulatorMin { diff --git a/selene-sim/python/tests/test_qis.py b/selene-sim/python/tests/test_qis.py index ef9c65fa..abf9771c 100644 --- a/selene-sim/python/tests/test_qis.py +++ b/selene-sim/python/tests/test_qis.py @@ -3,8 +3,9 @@ import yaml from selene_sim.event_hooks import CircuitExtractor, MetricStore, MultiEventHook -from selene_sim import Quest +from selene_sim import Quest, SoftRZRuntime from selene_sim.build import build +from selene_sim.exceptions import SeleneStartupError from selene_helios_qis_plugin import HeliosInterface from selene_sol_qis_plugin import SolInterface @@ -93,6 +94,28 @@ def test_qis_circuit_log(snapshot, program_name: str): snapshot.assert_match(yaml.dump(circuits), f"{program_name}_circuits.yaml") +def test_full_stack_gateset_handshake_rejects_unsupported_downstream_gate(): + sol_file = QIS_RESOURCE_DIR / "sol" / "add_3_11-any.ll" + assert sol_file.exists() + + runner = build(sol_file, interface=SolInterface()) + + with pytest.raises(SeleneStartupError) as exc_info: + list( + list(shot) + for shot in runner.run_shots( + Quest(), + runtime=SoftRZRuntime(), + n_qubits=10, + n_shots=1, + random_seed=1024, + ) + ) + + error = exc_info.value + assert "SoftRZRuntime does not support gate PhasedXX" in error.message + + def test_simulate_delay(): filename = "simulate_delay-any.ll" helios_file = QIS_RESOURCE_DIR / "helios" / filename diff --git a/selene-sim/python/tests/test_replay.py b/selene-sim/python/tests/test_replay.py index 527403f7..ad49d95a 100644 --- a/selene-sim/python/tests/test_replay.py +++ b/selene-sim/python/tests/test_replay.py @@ -206,7 +206,7 @@ def main() -> None: measurements=invalid_replay_measurements, ) - with pytest.raises(SelenePanicError) as exception_info: + with pytest.raises(SelenePanicError, match="impossible|unlikely") as exception_info: s = list( list(x) for x in runner.run_shots( @@ -217,4 +217,3 @@ def main() -> None: verbose=True, ) ) - assert any(x in str(exception_info.value) for x in ["impossible", "too unlikely"]) diff --git a/selene-sim/rust/event_hooks/metrics.rs b/selene-sim/rust/event_hooks/metrics.rs index 610c477f..b58317fd 100644 --- a/selene-sim/rust/event_hooks/metrics.rs +++ b/selene-sim/rust/event_hooks/metrics.rs @@ -40,7 +40,7 @@ impl GateMetricLabels { impl Default for GateMetricLabels { fn default() -> Self { - Self::from_gateset(&builtin::all()) + Self::from_gateset(&builtin::QuantinuumGateSet::dynamic()) } } From 1d5f3acea355134680afd3a8f5634b9a30466702 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:41:32 +0100 Subject: [PATCH 06/19] feat(ext): adapt 0.2 plugins to the 0.3 ABI --- selene-ext/compat/v02-common/src/lib.rs | 24 +++++++------------- selene-ext/compat/v02-error-model/src/lib.rs | 12 ++++++---- selene-ext/compat/v02-runtime/src/lib.rs | 4 ++++ selene-ext/compat/v02-simulator/src/lib.rs | 9 +++++++- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/selene-ext/compat/v02-common/src/lib.rs b/selene-ext/compat/v02-common/src/lib.rs index 4817f685..abc6394f 100644 --- a/selene-ext/compat/v02-common/src/lib.rs +++ b/selene-ext/compat/v02-common/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow, bail}; use libloading::Library; use selene_core::{ gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, - runtime::{BuiltinGate, Operation}, + runtime::Operation, }; pub type Errno = i32; @@ -15,12 +15,7 @@ pub const V02_RUNTIME_API_VERSION: u64 = 0x0000_0201; pub const V02_ERROR_MODEL_API_VERSION: u64 = 0x0000_0200; pub fn legacy_gateset() -> DynamicGateSet { - DynamicGateSet::from_declarations([ - builtin::RZ::declaration(), - builtin::PhasedX::declaration(), - builtin::ZZPhase::declaration(), - ]) - .expect("legacy builtin declarations are unique") + builtin::HeliosGateSet::dynamic() } pub fn negotiate_legacy_gateset(kind: &str, gateset: &DynamicGateSet) -> Result { @@ -54,13 +49,12 @@ pub enum LegacyGate { impl LegacyGate { pub fn from_gate_instance(gate: &OwnedGateInstance) -> Result { - let op = Operation::from_gate_instance(gate.clone())?; - match op.as_builtin_gate()? { - Some(BuiltinGate::RZ { qubit_id, theta }) => Ok(Self::Rz { + match Operation::gate_as_view::(gate)? { + Some(builtin::HeliosGate::RZ { qubit_id, theta }) => Ok(Self::Rz { qubit: qubit_id, theta, }), - Some(BuiltinGate::PhasedX { + Some(builtin::HeliosGate::PhasedX { qubit_id, theta, phi, @@ -69,7 +63,7 @@ impl LegacyGate { theta, phi, }), - Some(BuiltinGate::ZZPhase { + Some(builtin::HeliosGate::ZZPhase { qubit_id_1, qubit_id_2, theta, @@ -78,9 +72,7 @@ impl LegacyGate { qubit1: qubit_id_2, theta, }), - Some(BuiltinGate::PhasedXX { .. }) | None => { - bail!("0.2 compatibility adapter cannot translate this gate") - } + None => bail!("0.2 compatibility adapter cannot translate this gate"), } } } @@ -146,7 +138,7 @@ pub struct LegacyRuntimeExtractOperationInterface { #[repr(C)] #[derive(Clone, Copy)] -pub struct LegacyErrorModelSetResultInterface { +pub struct LegacyOperationResultInterface { pub set_bool_result_fn: unsafe extern "C" fn(Instance, u64, bool), pub set_u64_result_fn: unsafe extern "C" fn(Instance, u64, u64), } diff --git a/selene-ext/compat/v02-error-model/src/lib.rs b/selene-ext/compat/v02-error-model/src/lib.rs index c5238f87..2997d584 100644 --- a/selene-ext/compat/v02-error-model/src/lib.rs +++ b/selene-ext/compat/v02-error-model/src/lib.rs @@ -16,7 +16,7 @@ use selene_core::{ utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}, }; use selene_v02_compat_common::{ - Errno, Instance, LegacyErrorModelSetResultInterface, LegacyGate, + Errno, Instance, LegacyGate, LegacyOperationResultInterface, LegacyRuntimeExtractOperationInterface, LegacyRuntimeGetOperationInterface, V02_ERROR_MODEL_API_VERSION, V02_SIMULATOR_API_VERSION, load_library, negotiate_legacy_gateset, optional_symbol, required_symbol, validate_api_version, @@ -39,7 +39,7 @@ type HandleOperationsFn = unsafe extern "C" fn( Instance, *const LegacyRuntimeExtractOperationInterface, Instance, - *const LegacyErrorModelSetResultInterface, + *const LegacyOperationResultInterface, ) -> Errno; type MetricFn = unsafe extern "C" fn(Instance, u8, *mut c_char, *mut u8, *mut u64) -> Errno; @@ -216,10 +216,10 @@ impl LegacyResultBuilder { result.set_u64_result(result_id, value); } - fn interface(&mut self) -> (Instance, LegacyErrorModelSetResultInterface) { + fn interface(&mut self) -> (Instance, LegacyOperationResultInterface) { ( &mut self.0 as *mut BatchResult as Instance, - LegacyErrorModelSetResultInterface { + LegacyOperationResultInterface { set_bool_result_fn: Self::set_bool_result, set_u64_result_fn: Self::set_u64_result, }, @@ -325,6 +325,10 @@ struct LegacyErrorModelFactory; impl ErrorModelInterfaceFactory for LegacyErrorModelFactory { type Interface = LegacyErrorModel; + fn name(&self) -> &str { + "v0.2 Error Model Compat" + } + fn init( self: Arc, n_qubits: u64, diff --git a/selene-ext/compat/v02-runtime/src/lib.rs b/selene-ext/compat/v02-runtime/src/lib.rs index f3b1c839..7269b1c3 100644 --- a/selene-ext/compat/v02-runtime/src/lib.rs +++ b/selene-ext/compat/v02-runtime/src/lib.rs @@ -490,6 +490,10 @@ struct LegacyRuntimeFactory; impl RuntimeInterfaceFactory for LegacyRuntimeFactory { type Interface = LegacyRuntime; + fn name(&self) -> &str { + "v0.2 Runtime Compat" + } + fn init( self: Arc, n_qubits: u64, diff --git a/selene-ext/compat/v02-simulator/src/lib.rs b/selene-ext/compat/v02-simulator/src/lib.rs index 646454ed..f1439bb4 100644 --- a/selene-ext/compat/v02-simulator/src/lib.rs +++ b/selene-ext/compat/v02-simulator/src/lib.rs @@ -182,8 +182,11 @@ impl SimulatorInterface for LegacySimulator { unsafe { (self.library.reset)(self.instance, qubit_id) }, || anyhow!("v02 simulator adapter: legacy reset failed"), )?, + Operation::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} - _ => bail!("v02 simulator adapter: unsupported operation kind"), } } Ok(results) @@ -233,6 +236,10 @@ struct LegacySimulatorFactory; impl SimulatorInterfaceFactory for LegacySimulatorFactory { type Interface = LegacySimulator; + fn name(&self) -> &str { + "v0.2 Simulator Compat" + } + fn init( self: Arc, n_qubits: u64, From abd37355380dc2cb13efeae08f5da7fcda9f83ba Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:41:46 +0100 Subject: [PATCH 07/19] feat(sim): surface plugin errors and utility shot callbacks --- selene-sim/c/include/selene/selene.h | 9 +++++ selene-sim/python/selene_sim/exceptions.py | 6 +--- selene-sim/python/tests/test_exceptions.py | 13 +++++++ selene-sim/rust/emulator.rs | 7 +++- selene-sim/rust/event_hooks.rs | 8 +++++ selene-sim/rust/ffi_interface.rs | 23 ++++++++++++ selene-sim/rust/selene_instance.rs | 42 +++++++++++++++++++++- 7 files changed, 101 insertions(+), 7 deletions(-) diff --git a/selene-sim/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index 6cb0dc93..fc24144d 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -20,6 +20,12 @@ typedef struct selene_void_result_t { uint32_t error_code; } selene_void_result_t; +typedef struct SeleneUtilityEventCallbacksV1 { + void *context; + struct selene_void_result_t (*on_shot_start)(void *context, uint64_t shot_id); + struct selene_void_result_t (*on_shot_end)(void *context, uint64_t shot_id); +} SeleneUtilityEventCallbacksV1; + typedef struct selene_string_t { const char *data; uint64_t length; @@ -189,6 +195,9 @@ struct selene_u64_result_t selene_qalloc(struct SeleneInstance *instance); struct selene_void_result_t selene_qfree(struct SeleneInstance *instance, uint64_t q); +struct selene_void_result_t selene_register_utility_event_callbacks(struct SeleneInstance *instance, + struct SeleneUtilityEventCallbacksV1 callbacks); + /** * Performs a lazy measurement */ diff --git a/selene-sim/python/selene_sim/exceptions.py b/selene-sim/python/selene_sim/exceptions.py index e79c8af6..2a0f04ed 100644 --- a/selene-sim/python/selene_sim/exceptions.py +++ b/selene-sim/python/selene_sim/exceptions.py @@ -90,11 +90,7 @@ def __reduce__(self): return (self.__class__, (self.message, self.code, self.stdout, self.stderr)) def __str__(self): - return ( - f"Panic (#{self.code}): {self.message}" - + maybe_provide_log("stdout", self.stdout) - + maybe_provide_log("stderr", self.stderr) - ) + return f"Panic (#{self.code}): {self.message}" class SeleneTimeoutError(Exception): diff --git a/selene-sim/python/tests/test_exceptions.py b/selene-sim/python/tests/test_exceptions.py index 7f964b75..34078be3 100644 --- a/selene-sim/python/tests/test_exceptions.py +++ b/selene-sim/python/tests/test_exceptions.py @@ -55,3 +55,16 @@ def test_pickle_and_unpickle_selene_startup_error(error_class, kwargs): for k, v in kwargs.items(): assert getattr(unpickled, k) == v + + +def test_panic_error_str_omits_process_logs(): + error = SelenePanicError( + "Postselection impossible.", + 100001, + stdout="debug stdout", + stderr="debug stderr", + ) + + assert str(error) == "Panic (#100001): Postselection impossible." + assert error.stdout == "debug stdout" + assert error.stderr == "debug stderr" diff --git a/selene-sim/rust/emulator.rs b/selene-sim/rust/emulator.rs index ce6bc3ab..260c6a21 100644 --- a/selene-sim/rust/emulator.rs +++ b/selene-sim/rust/emulator.rs @@ -70,7 +70,12 @@ impl SimulatorInterface for HookedSimulator { fn postselect(&mut self, qubit: u64, target_value: bool) -> Result<()> { let operation = Operation::Postselect(qubit, target_value); time_simulator_call(&self.event_hooks, &operation, || { - self.inner.postselect(qubit, target_value) + self.inner + .handle_operations(singleton_batch(RuntimeOperation::Postselect { + qubit_id: qubit, + target_value, + }))?; + Ok(()) }) } diff --git a/selene-sim/rust/event_hooks.rs b/selene-sim/rust/event_hooks.rs index d218b761..601c49ca 100644 --- a/selene-sim/rust/event_hooks.rs +++ b/selene-sim/rust/event_hooks.rs @@ -41,6 +41,10 @@ impl Operation { selene_core::runtime::Operation::MeasureLeaked { qubit_id, .. } => { Operation::FutureRead(*qubit_id) } + selene_core::runtime::Operation::Postselect { + qubit_id, + target_value, + } => Operation::Postselect(*qubit_id, *target_value), selene_core::runtime::Operation::Custom { custom_tag, data } => { Operation::Custom(*custom_tag as u64, data.to_vec()) } @@ -61,6 +65,10 @@ impl Operation { selene_core::runtime::Operation::MeasureLeaked { qubit_id, .. } => { Operation::MeasureLeakedRequest(*qubit_id) } + selene_core::runtime::Operation::Postselect { + qubit_id, + target_value, + } => Operation::Postselect(*qubit_id, *target_value), selene_core::runtime::Operation::Custom { custom_tag, data } => { Operation::Custom(*custom_tag as u64, data.to_vec()) } diff --git a/selene-sim/rust/ffi_interface.rs b/selene-sim/rust/ffi_interface.rs index acb61254..d6b631fa 100644 --- a/selene-sim/rust/ffi_interface.rs +++ b/selene-sim/rust/ffi_interface.rs @@ -2,6 +2,7 @@ use super::selene_instance::SeleneInstance; use crate::selene_instance::configuration::Configuration; use anyhow::Result; use selene_core::gatewire::DynamicGateSet; +use std::ffi::c_void; #[repr(C)] pub struct VoidResult { @@ -15,6 +16,17 @@ impl VoidResult { VoidResult { error_code } } } + +pub type UtilityShotEventFn = + Option VoidResult>; + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct SeleneUtilityEventCallbacksV1 { + pub context: *mut c_void, + pub on_shot_start: UtilityShotEventFn, + pub on_shot_end: UtilityShotEventFn, +} #[repr(C)] pub struct U64Result { pub error_code: u32, @@ -202,6 +214,17 @@ where VoidResult::ok() } } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn selene_register_utility_event_callbacks( + instance: *mut SeleneInstance, + callbacks: SeleneUtilityEventCallbacksV1, +) -> VoidResult { + with_instance_void(instance, |instance| { + instance.register_utility_event_callbacks(callbacks); + Ok(()) + }) +} fn with_instance_bool(instance: *mut SeleneInstance, f: F) -> BoolResult where F: FnOnce(&mut SeleneInstance) -> Result, diff --git a/selene-sim/rust/selene_instance.rs b/selene-sim/rust/selene_instance.rs index 978e4c52..4230729f 100644 --- a/selene-sim/rust/selene_instance.rs +++ b/selene-sim/rust/selene_instance.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Result, bail}; pub mod configuration; pub mod metadata; @@ -9,6 +9,7 @@ pub mod state_dump; use configuration::Configuration; use crate::emulator::Emulator; +use crate::ffi_interface::SeleneUtilityEventCallbacksV1; use rand_pcg::Pcg32; use selene_core::encoder::OutputStream; @@ -19,6 +20,7 @@ pub struct SeleneInstance { pub time_cursor: u64, pub shot_number: u64, pub prng: Option, + utility_event_callbacks: Vec, } impl SeleneInstance { @@ -56,9 +58,44 @@ impl SeleneInstance { time_cursor: 0, shot_number: shot_offset, prng: None, + utility_event_callbacks: Vec::new(), }) } + pub fn register_utility_event_callbacks(&mut self, callbacks: SeleneUtilityEventCallbacksV1) { + self.utility_event_callbacks.push(callbacks); + } + + fn notify_utility_shot_start(&mut self, shot_id: u64) -> Result<()> { + for callbacks in &self.utility_event_callbacks { + if let Some(on_shot_start) = callbacks.on_shot_start { + let result = unsafe { on_shot_start(callbacks.context, shot_id) }; + if result.error_code != 0 { + bail!( + "Utility callback on_shot_start failed with error code {}", + result.error_code + ); + } + } + } + Ok(()) + } + + fn notify_utility_shot_end(&mut self, shot_id: u64) -> Result<()> { + for callbacks in self.utility_event_callbacks.iter().rev() { + if let Some(on_shot_end) = callbacks.on_shot_end { + let result = unsafe { on_shot_end(callbacks.context, shot_id) }; + if result.error_code != 0 { + bail!( + "Utility callback on_shot_end failed with error code {}", + result.error_code + ); + } + } + } + Ok(()) + } + /// Upon termination of the simulator instance, we signal to the output stream /// that we are closing out gracefully, and flush all remaining data. pub fn exit(&mut self) -> Result<()> { @@ -84,6 +121,8 @@ impl SeleneInstance { let error_model_seed = self.config.error_model.seed + shot_id; let simulator_seed = self.config.simulator.seed + shot_id; + self.notify_utility_shot_start(shot_id)?; + // Now we fire off any shot start event hooks and prepare the // runtime and error model for the new shot. self.emulator @@ -103,6 +142,7 @@ impl SeleneInstance { self.write_metrics()?; } self.emulator.shot_end()?; + self.notify_utility_shot_end(self.shot_number)?; self.write_metadata()?; self.print_shot_end()?; Ok(()) From 6753aee6b70a5d712497c4486a0f8a753b8445e7 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:42:03 +0100 Subject: [PATCH 08/19] refactor(py): share CFFI plugin bindings for interactive mode --- selene-core/pyproject.toml | 1 + .../python/selene_core/c_abi/__init__.py | 11 + selene-core/python/selene_core/c_abi/_cffi.py | 52 ++ .../python/selene_core/c_abi/runtime.py | 26 + .../python/selene_core/c_abi/selene.py | 14 + .../python/selene_core/c_abi/simulator.py | 20 + .../python/selene_sim/interactive/_plugin.py | 22 + .../selene_sim/interactive/full_stack.py | 377 ++++------- .../python/selene_sim/interactive/runtime.py | 592 ++++++++---------- .../selene_sim/interactive/simulator.py | 419 +++++++------ uv.lock | 93 +++ 11 files changed, 846 insertions(+), 781 deletions(-) create mode 100644 selene-core/python/selene_core/c_abi/__init__.py create mode 100644 selene-core/python/selene_core/c_abi/_cffi.py create mode 100644 selene-core/python/selene_core/c_abi/runtime.py create mode 100644 selene-core/python/selene_core/c_abi/selene.py create mode 100644 selene-core/python/selene_core/c_abi/simulator.py create mode 100644 selene-sim/python/selene_sim/interactive/_plugin.py diff --git a/selene-core/pyproject.toml b/selene-core/pyproject.toml index d54acc86..995c41bc 100644 --- a/selene-core/pyproject.toml +++ b/selene-core/pyproject.toml @@ -6,6 +6,7 @@ description = "The core interop library for Selene python interfaces" readme = "python/selene_core/README.md" dependencies = [ "blake3>=1.0.0", + "cffi>=1.17.1", "hugr>=0.13.0", # required for inspecting object files to find defined and declared symbols "lief>=0.16.5", diff --git a/selene-core/python/selene_core/c_abi/__init__.py b/selene-core/python/selene_core/c_abi/__init__.py new file mode 100644 index 00000000..b69c6cdf --- /dev/null +++ b/selene-core/python/selene_core/c_abi/__init__.py @@ -0,0 +1,11 @@ +from ._cffi import ffi +from .runtime import RuntimeCTypes +from .selene import SeleneCTypes +from .simulator import SimulatorCTypes + +__all__ = [ + "RuntimeCTypes", + "SeleneCTypes", + "SimulatorCTypes", + "ffi", +] diff --git a/selene-core/python/selene_core/c_abi/_cffi.py b/selene-core/python/selene_core/c_abi/_cffi.py new file mode 100644 index 00000000..60ffe80a --- /dev/null +++ b/selene-core/python/selene_core/c_abi/_cffi.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from functools import cache +from pathlib import Path + +from cffi import FFI + +from selene_core.headers import get_include_directory + + +def _source_root() -> Path: + return Path(__file__).parents[3] + + +def _header_path(name: str) -> Path: + source = _source_root() / "c/include/selene" / name + if source.exists(): + return source + bundled = get_include_directory() / "selene" / name + if bundled.exists(): + return bundled + raise FileNotFoundError(f"Could not find Selene C header {name!r}") + + +def _cdef_header(path: Path) -> str: + lines = [] + for line in path.read_text().splitlines(): + stripped = line.strip() + if stripped.startswith("#"): + continue + if stripped.startswith('extern "C"'): + continue + if stripped.startswith("} // extern"): + continue + lines.append(line) + return "\n".join(lines) + + +@cache +def ffi(extra_headers: tuple[Path, ...] = ()) -> FFI: + result = FFI() + for builtin_header in ( + "gatewire.h", + "operation.h", + "plugin.h", + "simulator.h", + "runtime.h", + ): + result.cdef(_cdef_header(_header_path(builtin_header)), override=True) + for extra_header in extra_headers: + result.cdef(_cdef_header(extra_header), override=True) + return result diff --git a/selene-core/python/selene_core/c_abi/runtime.py b/selene-core/python/selene_core/c_abi/runtime.py new file mode 100644 index 00000000..4214a1b6 --- /dev/null +++ b/selene-core/python/selene_core/c_abi/runtime.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Any + + +class RuntimeCTypes: + def __init__(self, cffi: Any) -> None: + self.runtime_instance_ptr = cffi.typeof("RuntimeInstance *") + self.runtime_get_operation_handle = cffi.typeof("RuntimeGetOperationHandle") + self.runtime_get_operation_handle_ptr = cffi.typeof( + "RuntimeGetOperationHandle *" + ) + self.runtime_get_operation_interface = cffi.typeof( + "SeleneRuntimeGetOperationInterface" + ) + self.runtime_get_operation_interface_ptr = cffi.typeof( + "SeleneRuntimeGetOperationInterface *" + ) + self.char_array = cffi.typeof("char[]") + self.char_ptr_array = cffi.typeof("char *[]") + self.size_ptr = cffi.typeof("size_t *") + self.uint8_array = cffi.typeof("uint8_t[]") + self.uint8_ptr = cffi.typeof("uint8_t *") + self.uint64_array = cffi.typeof("uint64_t[]") + self.uint64_ptr = cffi.typeof("uint64_t *") + self.int8_ptr = cffi.typeof("int8_t *") diff --git a/selene-core/python/selene_core/c_abi/selene.py b/selene-core/python/selene_core/c_abi/selene.py new file mode 100644 index 00000000..96d16b1b --- /dev/null +++ b/selene-core/python/selene_core/c_abi/selene.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import Any + + +class SeleneCTypes: + def __init__(self, cffi: Any) -> None: + self.instance_ptr = cffi.typeof("SeleneInstance *") + self.instance_ptr_ptr = cffi.typeof("SeleneInstance **") + self.string = cffi.typeof("selene_string_t") + self.string_ptr = cffi.typeof("selene_string_t *") + self.size_ptr = cffi.typeof("size_t *") + self.uint8_array = cffi.typeof("uint8_t[]") + self.uint64_array = cffi.typeof("uint64_t[]") diff --git a/selene-core/python/selene_core/c_abi/simulator.py b/selene-core/python/selene_core/c_abi/simulator.py new file mode 100644 index 00000000..aec1b287 --- /dev/null +++ b/selene-core/python/selene_core/c_abi/simulator.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Any + + +class SimulatorCTypes: + def __init__(self, cffi: Any) -> None: + self.simulator_instance_ptr = cffi.typeof("SeleneSimulatorInstance *") + self.char_array = cffi.typeof("char[]") + self.char_ptr_array = cffi.typeof("char *[]") + self.size_ptr = cffi.typeof("size_t *") + self.runtime_extract_operation_handle = cffi.typeof( + "RuntimeExtractOperationHandle *" + ) + self.runtime_get_operation_handle = cffi.typeof("RuntimeGetOperationHandle *") + self.operation_result_handle = cffi.typeof("OperationResultHandle *") + self.uint8_array = cffi.typeof("uint8_t[]") + self.uint8_ptr = cffi.typeof("uint8_t *") + self.uint64_array = cffi.typeof("uint64_t[]") + self.uint64_ptr = cffi.typeof("uint64_t *") diff --git a/selene-sim/python/selene_sim/interactive/_plugin.py b/selene-sim/python/selene_sim/interactive/_plugin.py new file mode 100644 index 00000000..ba922b3f --- /dev/null +++ b/selene-sim/python/selene_sim/interactive/_plugin.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import ctypes + + +Errno = ctypes.c_int32 +LastErrorFn = ctypes.CFUNCTYPE( + Errno, + ctypes.POINTER(ctypes.c_char), + ctypes.c_size_t, + ctypes.POINTER(ctypes.c_size_t), +) +PluginGetNameFn = ctypes.CFUNCTYPE(ctypes.c_char_p) + + +class PluginDescriptorHeaderV1(ctypes.Structure): + _fields_ = [ + ("struct_size", ctypes.c_uint64), + ("api_version", ctypes.c_uint64), + ("last_error_fn", LastErrorFn), + ("get_name_fn", PluginGetNameFn), + ] diff --git a/selene-sim/python/selene_sim/interactive/full_stack.py b/selene-sim/python/selene_sim/interactive/full_stack.py index 44cb8b60..59e862e8 100644 --- a/selene-sim/python/selene_sim/interactive/full_stack.py +++ b/selene-sim/python/selene_sim/interactive/full_stack.py @@ -1,6 +1,5 @@ from __future__ import annotations -import ctypes import os import tempfile from pathlib import Path @@ -15,6 +14,7 @@ SeleneComponent, Simulator, ) +from selene_core.c_abi import SeleneCTypes, ffi from selene_sim import dist_dir as selene_dist from selene_sim.backends import IdealErrorModel, SimpleRuntime @@ -30,6 +30,12 @@ DEFAULT_SHOT_SPEC = ShotSpec(count=1000, offset=0, increment=1) +_SELENE_HEADER = Path(__file__).parents[2] / "_dist/include/selene/selene.h" +_SOURCE_SELENE_HEADER = Path(__file__).parents[4] / "selene-sim/c/include/selene/selene.h" +if _SOURCE_SELENE_HEADER.exists(): + _SELENE_HEADER = _SOURCE_SELENE_HEADER +_FFI = ffi((_SELENE_HEADER,)) + def _component_config(component: SeleneComponent, default_seed: int | None) -> dict: full_name = ".".join( @@ -62,90 +68,6 @@ def __init__(self, error_code: int): self.error_code = error_code -class SeleneInstance(ctypes.Structure): - pass - - -SeleneInstancePtr = ctypes.POINTER(SeleneInstance) -SeleneInstancePtrPtr = ctypes.POINTER(SeleneInstancePtr) - - -class selene_void_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32)] - - def unwrap(self) -> None: - if self.error_code != 0: - raise SeleneError(self.error_code) - return None - - -class selene_u64_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32), ("value", ctypes.c_uint64)] - - def unwrap(self) -> int: - if self.error_code != 0: - raise SeleneError(self.error_code) - return int(self.value) - - -class selene_u32_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32), ("value", ctypes.c_uint32)] - - def unwrap(self) -> int: - if self.error_code != 0: - raise SeleneError(self.error_code) - return int(self.value) - - -class selene_f64_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32), ("value", ctypes.c_double)] - - def unwrap(self) -> float: - if self.error_code != 0: - raise SeleneError(self.error_code) - return float(self.value) - - -class selene_bool_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32), ("value", ctypes.c_bool)] - - def unwrap(self) -> bool: - if self.error_code != 0: - raise SeleneError(self.error_code) - return bool(self.value) - - -class selene_future_result_t(ctypes.Structure): - _fields_ = [("error_code", ctypes.c_uint32), ("reference", ctypes.c_uint64)] - - def unwrap(self) -> int: - if self.error_code != 0: - raise SeleneError(self.error_code) - return int(self.reference) - - -class selene_string_t(ctypes.Structure): - _fields_ = [ - ("data", ctypes.c_char_p), - ("length", ctypes.c_uint64), - ("owned", ctypes.c_bool), - ] - - @staticmethod - def from_str(s: str) -> selene_string_t: - encoded = s.encode("utf-8") - return selene_string_t( - ctypes.c_char_p(encoded), ctypes.c_uint64(len(encoded)), True - ) - - -BytePtr = ctypes.POINTER(ctypes.c_uint8) -UInt64Ptr = ctypes.POINTER(ctypes.c_uint64) -BoolPtr = ctypes.POINTER(ctypes.c_bool) -Int64Ptr = ctypes.POINTER(ctypes.c_int64) -DoublePtr = ctypes.POINTER(ctypes.c_double) - - def _encode_text(value: PathLike) -> bytes: if isinstance(value, (bytes, bytearray)): return bytes(value) @@ -156,111 +78,110 @@ def _encode_text(value: PathLike) -> bytes: def _uint8_buffer( payload: bytes | bytearray | memoryview | None, -) -> tuple[ctypes.Array | None, int]: +) -> tuple[object, int]: if payload is None: - return None, 0 + return _FFI.NULL, 0 raw = bytes(payload) if not raw: - return None, 0 - array_type = ctypes.c_uint8 * len(raw) - return array_type(*raw), len(raw) + return _FFI.NULL, 0 + return _FFI.new("uint8_t[]", raw), len(raw) + + +def _unwrap(result): + if result.error_code != 0: + raise SeleneError(int(result.error_code)) + return result + + +def _unwrap_void(result) -> None: + _unwrap(result) + +def _unwrap_value(result) -> int | float | bool: + return _unwrap(result).value -class SeleneSimLib(ctypes.CDLL): + +def _unwrap_future(result) -> int: + return int(_unwrap(result).reference) + + +class SeleneSimLib: def __init__(self) -> None: - super().__init__(str(selene_library_path()), mode=ctypes.RTLD_GLOBAL) - self._configure_signatures() - - def _configure_signatures(self): - self.selene_custom_runtime_call.argtypes = [ - SeleneInstancePtr, - ctypes.c_uint64, - BytePtr, - ctypes.c_uint64, - ] - self.selene_custom_runtime_call.restype = selene_u64_result_t - self.selene_dump_state.argtypes = [ - SeleneInstancePtr, - selene_string_t, - UInt64Ptr, - ctypes.c_uint64, - ] - self.selene_dump_state.restype = selene_void_result_t - self.selene_exit.argtypes = [SeleneInstancePtr] - self.selene_exit.restype = selene_void_result_t - self.selene_future_read_bool.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_future_read_bool.restype = selene_bool_result_t - self.selene_future_read_u64.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_future_read_u64.restype = selene_u64_result_t - self.selene_get_current_shot.argtypes = [SeleneInstancePtr] - self.selene_get_current_shot.restype = selene_u64_result_t - self.selene_get_tc.argtypes = [SeleneInstancePtr] - self.selene_get_tc.restype = selene_u64_result_t - self.selene_load_config.argtypes = [SeleneInstancePtrPtr, ctypes.c_char_p] - self.selene_load_config.restype = selene_void_result_t - self.selene_on_shot_end.argtypes = [SeleneInstancePtr] - self.selene_on_shot_end.restype = selene_void_result_t - self.selene_on_shot_start.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_on_shot_start.restype = selene_void_result_t - self.selene_qalloc.argtypes = [SeleneInstancePtr] - self.selene_qalloc.restype = selene_u64_result_t - self.selene_qfree.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_qfree.restype = selene_void_result_t - self.selene_qubit_lazy_measure.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_qubit_lazy_measure.restype = selene_future_result_t - self.selene_qubit_lazy_measure_leaked.argtypes = [ - SeleneInstancePtr, - ctypes.c_uint64, - ] - self.selene_qubit_lazy_measure_leaked.restype = selene_future_result_t - self.selene_qubit_measure.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_qubit_measure.restype = selene_bool_result_t - self.selene_qubit_reset.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_qubit_reset.restype = selene_void_result_t - self.selene_random_advance.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_random_advance.restype = selene_void_result_t - self.selene_random_f64.argtypes = [SeleneInstancePtr] - self.selene_random_f64.restype = selene_f64_result_t - self.selene_random_seed.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_random_seed.restype = selene_void_result_t - self.selene_random_u32.argtypes = [SeleneInstancePtr] - self.selene_random_u32.restype = selene_u32_result_t - self.selene_random_u32_bounded.argtypes = [SeleneInstancePtr, ctypes.c_uint32] - self.selene_random_u32_bounded.restype = selene_u32_result_t - self.selene_refcount_decrement.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_refcount_decrement.restype = selene_void_result_t - self.selene_refcount_increment.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_refcount_increment.restype = selene_void_result_t - self.selene_set_tc.argtypes = [SeleneInstancePtr, ctypes.c_uint64] - self.selene_set_tc.restype = selene_void_result_t - self.selene_shot_count.argtypes = [SeleneInstancePtr] - self.selene_shot_count.restype = selene_u64_result_t - self.selene_write_metadata.argtypes = [SeleneInstancePtr] - self.selene_write_metadata.restype = selene_void_result_t - self.selene_dump_state.argtypes = [ - SeleneInstancePtr, - selene_string_t, - UInt64Ptr, - ctypes.c_uint64, - ] - self.selene_dump_state.restype = selene_void_result_t - self.selene_fetch_output.argtypes = [ - SeleneInstancePtr, - BytePtr, - ctypes.c_uint64, - ] - self.selene_fetch_output.restype = selene_u64_result_t - self.selene_gate.argtypes = [SeleneInstancePtr, BytePtr, ctypes.c_size_t] - self.selene_gate.restype = selene_void_result_t - self.selene_register_gateset.argtypes = [ - SeleneInstancePtr, - BytePtr, - ctypes.c_size_t, - BytePtr, - ctypes.c_size_t, - ctypes.POINTER(ctypes.c_size_t), - ] - self.selene_register_gateset.restype = selene_void_result_t + self.ffi = _FFI + self.types = SeleneCTypes(self.ffi) + self.lib = self.ffi.dlopen(str(selene_library_path()), self.ffi.RTLD_GLOBAL) + + def load_config(self, config_path: PathLike): + instance = self.ffi.new(self.types.instance_ptr_ptr) + config_bytes = _encode_text(config_path) + config = self.ffi.new("char[]", config_bytes) + _unwrap_void(self.lib.selene_load_config(instance, config)) + return instance[0] + + def fetch_output(self, instance, chunk_size: int) -> bytes: + chunk = self.ffi.new(self.types.uint8_array, chunk_size) + bytes_read = int(_unwrap_value(self.lib.selene_fetch_output(instance, chunk, chunk_size))) + if bytes_read == 0: + raise BlockingIOError + return bytes(self.ffi.buffer(chunk, bytes_read)) + + def write_metadata(self, instance) -> None: + _unwrap_void(self.lib.selene_write_metadata(instance)) + + def exit(self, instance) -> None: + _unwrap_void(self.lib.selene_exit(instance)) + + def _string_ptr(self, value: str): + encoded = value.encode("utf-8") + data = self.ffi.new("char[]", encoded) + result = self.ffi.new(self.types.string_ptr) + result.data = data + result.length = len(encoded) + result.owned = False + return result, data + + def uint8_buffer(self, payload: bytes | bytearray | memoryview | None): + return _uint8_buffer(payload) + + def uint64_array(self, values: list[int]): + return self.ffi.new(self.types.uint64_array, values) + + def dump_state(self, instance, tag: str, qubit_ids) -> None: + tag_ptr, _tag_data = self._string_ptr(tag) + _unwrap_void( + self.lib.selene_dump_state( + instance, + tag_ptr[0], + qubit_ids, + len(qubit_ids), + ) + ) + + def register_gateset(self, instance, payload: bytes) -> bytes: + input_data = self.ffi.new(self.types.uint8_array, payload) + written = self.ffi.new(self.types.size_ptr) + _unwrap_void( + self.lib.selene_register_gateset( + instance, + input_data, + len(payload), + self.ffi.NULL, + 0, + written, + ) + ) + output_data = self.ffi.new(self.types.uint8_array, written[0]) + _unwrap_void( + self.lib.selene_register_gateset( + instance, + input_data, + len(payload), + output_data, + written[0], + written, + ) + ) + return bytes(self.ffi.buffer(output_data, written[0])) class InternalOutputStream(DataStream): @@ -275,13 +196,11 @@ def read_chunk(self, length: int) -> bytes: if len(self._buffer) < length: # make a new buffer to read into chunk_size = max(length - len(self._buffer), 4096) - chunk_buffer = (ctypes.c_uint8 * chunk_size)() - bytes_read = self._full_stack._lib.selene_fetch_output( - self._full_stack._instance, chunk_buffer, chunk_size - ).unwrap() - if bytes_read == 0: - raise BlockingIOError - self._buffer.extend(chunk_buffer[:bytes_read]) + self._buffer.extend( + self._full_stack._lib.fetch_output( + self._full_stack._instance, chunk_size + ) + ) result, self._buffer = self._buffer[:length], self._buffer[length:] return bytes(result) @@ -315,7 +234,6 @@ def __init__( gateset: Gateset | None = None, ): self._lib = self.load_library() - self._instance = SeleneInstancePtr() self._event_hook = event_hook or NoEventHook() self._shot_spec = DEFAULT_SHOT_SPEC self._shot_index = self._shot_spec.offset @@ -358,12 +276,7 @@ def __init__( self._configuration = config_data self._config_path.write_text(yaml.safe_dump(config_data)) - instance_ptr = SeleneInstancePtr() - config_bytes = _encode_text(self._config_path) - self._lib.selene_load_config( - ctypes.byref(instance_ptr), ctypes.c_char_p(config_bytes) - ).unwrap() - self._instance = instance_ptr + self._instance = self._lib.load_config(self._config_path) if self.gateset is not None: self.emitted_gateset = self.register_gateset(self.gateset) except Exception: @@ -450,33 +363,33 @@ def __del__(self): self._on_shot_end() # Here we invoke selene_exit directly, as the call helpers request metadata to be pushed, # which isn't valid after the instance has been destroyed - self._lib.selene_exit(self._instance).unwrap() + self._lib.exit(self._instance) def _invoke(self, func_name: str, *args): - result = getattr(self._lib, func_name)(self._instance, *args) + result = getattr(self._lib.lib, func_name)(self._instance, *args) if self._auto_poll_metadata: assert self._lib is not None - self._lib.selene_write_metadata(self._instance) + self._lib.write_metadata(self._instance) self._poll_results() return result def _call_void(self, func_name: str, *args): - self._invoke(func_name, *args).unwrap() + _unwrap_void(self._invoke(func_name, *args)) def _call_u64(self, func_name: str, *args) -> int: - return self._invoke(func_name, *args).unwrap() + return int(_unwrap_value(self._invoke(func_name, *args))) def _call_bool(self, func_name: str, *args) -> bool: - return self._invoke(func_name, *args).unwrap() + return bool(_unwrap_value(self._invoke(func_name, *args))) def _call_future(self, func_name: str, *args) -> int: - return self._invoke(func_name, *args).unwrap() + return _unwrap_future(self._invoke(func_name, *args)) def _call_f64(self, func_name: str, *args) -> float: - return self._invoke(func_name, *args).unwrap() + return float(_unwrap_value(self._invoke(func_name, *args))) def _call_u32(self, func_name: str, *args) -> int: - return self._invoke(func_name, *args).unwrap() + return int(_unwrap_value(self._invoke(func_name, *args))) def _on_shot_start(self, shot_index: int) -> None: self._call_void("selene_on_shot_start", shot_index) @@ -488,8 +401,7 @@ def custom_runtime_call( self, tag: int, payload: bytes | bytearray | memoryview | None = None ) -> int: buffer, length = _uint8_buffer(payload) - pointer = ctypes.cast(buffer, BytePtr) if buffer is not None else None - return self._call_u64("selene_custom_runtime_call", tag, pointer, length) + return self._call_u64("selene_custom_runtime_call", tag, buffer, length) def future_read_bool(self, reference: int) -> bool: return self._call_bool("selene_future_read_bool", reference) @@ -531,7 +443,7 @@ def random_u32(self) -> int: return self._call_u32("selene_random_u32") def random_u32_bounded(self, bound: int) -> int: - return self._call_u32("selene_random_u32_bounded", ctypes.c_uint32(bound)) + return self._call_u32("selene_random_u32_bounded", bound) def refcount_decrement(self, reference: int) -> None: self._call_void("selene_refcount_decrement", reference) @@ -541,37 +453,14 @@ def refcount_increment(self, reference: int) -> None: def register_gateset(self, gateset: Gateset) -> Gateset: payload = gateset.serialize() - input_buffer = (ctypes.c_uint8 * len(payload))(*payload) - input_ptr = ctypes.cast(input_buffer, BytePtr) - written = ctypes.c_size_t() - self._lib.selene_register_gateset( - self._instance, - input_ptr, - len(payload), - None, - 0, - ctypes.byref(written), - ).unwrap() - output_buffer = (ctypes.c_uint8 * written.value)() - output_ptr = ctypes.cast(output_buffer, BytePtr) - self._lib.selene_register_gateset( - self._instance, - input_ptr, - len(payload), - output_ptr, - written.value, - ctypes.byref(written), - ).unwrap() - return Gateset.deserialize(bytes(output_buffer[: written.value])) + return Gateset.deserialize(self._lib.register_gateset(self._instance, payload)) def gate(self, gate: Gate) -> None: payload = gate.serialize() - buffer = (ctypes.c_uint8 * len(payload))(*payload) - self._lib.selene_gate( - self._instance, ctypes.cast(buffer, BytePtr), len(payload) - ).unwrap() + buffer, length = self._lib.uint8_buffer(payload) + _unwrap_void(self._lib.lib.selene_gate(self._instance, buffer, length)) if self._auto_poll_metadata: - self._lib.selene_write_metadata(self._instance) + self._lib.write_metadata(self._instance) self._poll_results() def get_state(self, qubits: list[Qubit]): @@ -579,16 +468,12 @@ def get_state(self, qubits: list[Qubit]): raise AttributeError( "Simulator must implement extract_states to use get_state" ) - qubit_ids_t = ctypes.c_uint64 * len(qubits) - qubit_ids = qubit_ids_t(*[q.id for q in qubits]) - num_qubits = ctypes.c_uint64(len(qubits)) + qubit_ids = self._lib.uint64_array([q.id for q in qubits]) random_tag = "USER:STATE:" + os.urandom(8).hex() - self._call_void( - "selene_dump_state", - selene_string_t.from_str(random_tag), - ctypes.cast(qubit_ids, UInt64Ptr), - num_qubits, - ) + self._lib.dump_state(self._instance, random_tag, qubit_ids) + if self._auto_poll_metadata: + self._lib.write_metadata(self._instance) + self._poll_results() tagged_results = [ (v.tag.replace("USER:", ""), v.values[0]) for v in self.drain_state_dumps() ] diff --git a/selene-sim/python/selene_sim/interactive/runtime.py b/selene-sim/python/selene_sim/interactive/runtime.py index 4010ed70..3a6a74ab 100644 --- a/selene-sim/python/selene_sim/interactive/runtime.py +++ b/selene-sim/python/selene_sim/interactive/runtime.py @@ -1,62 +1,16 @@ from __future__ import annotations from dataclasses import dataclass -import ctypes +import struct from selene_core import Gate, Gateset, Runtime +from selene_core.c_abi import RuntimeCTypes, ffi import random from ._library import load_selene_global -class SeleneRuntimeInstance(ctypes.Structure): - pass - - -SeleneRuntimeGetOperationInstance = ctypes.c_void_p - -GATE_CB = ctypes.CFUNCTYPE( - None, - SeleneRuntimeGetOperationInstance, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_size_t, -) - -MEASURE_CB = ctypes.CFUNCTYPE( - None, SeleneRuntimeGetOperationInstance, ctypes.c_uint64, ctypes.c_uint64 -) -MEASURE_LEAKED_CB = ctypes.CFUNCTYPE( - None, SeleneRuntimeGetOperationInstance, ctypes.c_uint64, ctypes.c_uint64 -) -RESET_CB = ctypes.CFUNCTYPE(None, SeleneRuntimeGetOperationInstance, ctypes.c_uint64) -CUSTOM_CB = ctypes.CFUNCTYPE( - None, - SeleneRuntimeGetOperationInstance, - ctypes.c_size_t, - ctypes.c_void_p, - ctypes.c_size_t, -) -SET_BATCH_TIME_CB = ctypes.CFUNCTYPE( - None, SeleneRuntimeGetOperationInstance, ctypes.c_uint64, ctypes.c_uint64 -) - - -class SeleneRuntimeGetOperationInterface(ctypes.Structure): - _fields_ = [ - ("measure_fn", MEASURE_CB), - ("measure_leaked_fn", MEASURE_LEAKED_CB), - ("reset_fn", RESET_CB), - ("custom_fn", CUSTOM_CB), - ("set_batch_time_fn", SET_BATCH_TIME_CB), - ("gate_fn", GATE_CB), - ] - - -class SeleneRuntimeGetOperationHandle(ctypes.Structure): - _fields_ = [ - ("instance", SeleneRuntimeGetOperationInstance), - ("interface", SeleneRuntimeGetOperationInterface), - ] +_FFI = ffi() @dataclass @@ -87,12 +41,19 @@ class MeasureLeakedOperation: result_id: int +@dataclass +class PostselectOperation: + qubit_id: int + target_value: bool + + RuntimeOperation = ( MeasureOperation | ResetOperation | GateOperation | CustomOperation | MeasureLeakedOperation + | PostselectOperation ) @@ -120,6 +81,10 @@ def measure_leaked(self, qubit_id: int, result_id: int): self.operations.append(MeasureLeakedOperation(qubit_id, result_id)) self.invoked = True + def postselect(self, qubit_id: int, target_value: bool): + self.operations.append(PostselectOperation(qubit_id, target_value)) + self.invoked = True + def reset(self, qubit_id: int): self.operations.append(ResetOperation(qubit_id)) self.invoked = True @@ -132,222 +97,241 @@ def __repr__(self) -> str: return f"OperationBatch(start_time_nanos={self.start_time_nanos}, duration_nanos={self.duration_nanos}, operations={self.operations})" @staticmethod - def from_ptr(ptr: SeleneRuntimeGetOperationInstance) -> OperationBatch: - return ctypes.cast(ptr, ctypes.POINTER(ctypes.py_object)).contents.value + def from_handle(handle) -> OperationBatch: + return _FFI.from_handle(handle) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, const uint8_t *, size_t)") def callback_gate( - instance: SeleneRuntimeGetOperationInstance, - data_ptr: ctypes.c_void_p, + instance, + data_ptr, data_len: int, ): - OperationBatch.from_ptr(instance).gate( - Gate.deserialize(ctypes.string_at(data_ptr, data_len)) + OperationBatch.from_handle(instance).gate( + Gate.deserialize(bytes(_FFI.buffer(data_ptr, data_len))) ) -def callback_measure( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, result_id: int -): - OperationBatch.from_ptr(instance).measure(qubit_id, result_id) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t)") +def callback_measure(instance, qubit_id: int, result_id: int): + OperationBatch.from_handle(instance).measure(qubit_id, result_id) -def callback_measure_leaked( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, result_id: int -): - OperationBatch.from_ptr(instance).measure_leaked(qubit_id, result_id) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t)") +def callback_measure_leaked(instance, qubit_id: int, result_id: int): + OperationBatch.from_handle(instance).measure_leaked(qubit_id, result_id) -def callback_reset(instance: SeleneRuntimeGetOperationInstance, qubit_id: int): - OperationBatch.from_ptr(instance).reset(qubit_id) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, uint64_t, bool)") +def callback_postselect(instance, qubit_id: int, target_value: bool): + OperationBatch.from_handle(instance).postselect(qubit_id, target_value) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, uint64_t)") +def callback_reset(instance, qubit_id: int): + OperationBatch.from_handle(instance).reset(qubit_id) + + +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, size_t, const void *, size_t)") def callback_custom( - instance: SeleneRuntimeGetOperationInstance, + instance, tag: int, - data_ptr: ctypes.c_void_p, + data_ptr, data_len: int, ): - data = ctypes.string_at(data_ptr, data_len) - OperationBatch.from_ptr(instance).custom(tag, data) - - -def callback_set_batch_time( - instance: SeleneRuntimeGetOperationInstance, start_time: int, duration: int -): - OperationBatch.from_ptr(instance).set_time(start_time, duration) + data = bytes(_FFI.buffer(data_ptr, data_len)) + OperationBatch.from_handle(instance).custom(tag, data) -OPERATION_BATCH_CALLBACKS = SeleneRuntimeGetOperationInterface( - gate_fn=GATE_CB(callback_gate), - measure_fn=MEASURE_CB(callback_measure), - measure_leaked_fn=MEASURE_LEAKED_CB(callback_measure_leaked), - reset_fn=RESET_CB(callback_reset), - custom_fn=CUSTOM_CB(callback_custom), - set_batch_time_fn=SET_BATCH_TIME_CB(callback_set_batch_time), -) - -SeleneRuntimeInstancePtr = ctypes.POINTER(SeleneRuntimeInstance) -SeleneRuntimeInstancePtrPtr = ctypes.POINTER(SeleneRuntimeInstancePtr) -Errno = ctypes.c_int32 - -RuntimeInitFn = ctypes.CFUNCTYPE( - Errno, - SeleneRuntimeInstancePtrPtr, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_uint32, - ctypes.POINTER(ctypes.c_char_p), -) -RuntimeExitFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr) -RuntimeShotStartFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.c_uint64 -) -RuntimeShotEndFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr) -RuntimeGetNextOpsFn = ctypes.CFUNCTYPE( - Errno, - SeleneRuntimeInstancePtr, - SeleneRuntimeGetOperationHandle, -) -RuntimeQallocFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.POINTER(ctypes.c_uint64) -) -RuntimeQfreeFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64) -RuntimeMeasureFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64) -) -RuntimeMeasureLeakedFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64) -) -RuntimeResetFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64) -RuntimeForceResultFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64 -) -RuntimeGetBoolResultFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.POINTER(ctypes.c_int8) -) -RuntimeGetU64ResultFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.POINTER(ctypes.c_uint64) -) -RuntimeSetBoolResultFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.c_bool -) -RuntimeSetU64ResultFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64, ctypes.c_uint64 -) -RuntimeIncFutureFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64) -RuntimeDecFutureFn = ctypes.CFUNCTYPE(Errno, SeleneRuntimeInstancePtr, ctypes.c_uint64) -RuntimeCustomCallFn = ctypes.CFUNCTYPE( - Errno, - SeleneRuntimeInstancePtr, - ctypes.c_uint64, - ctypes.c_void_p, - ctypes.c_size_t, - ctypes.POINTER(ctypes.c_uint64), -) -RuntimeGateFn = ctypes.CFUNCTYPE( - Errno, SeleneRuntimeInstancePtr, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t -) -RuntimeNegotiateGatesetFn = ctypes.CFUNCTYPE( - Errno, - SeleneRuntimeInstancePtr, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_size_t, - ctypes.POINTER(ctypes.c_uint8), - ctypes.c_size_t, - ctypes.POINTER(ctypes.c_size_t), -) +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t)") +def callback_set_batch_time(instance, start_time: int, duration: int): + OperationBatch.from_handle(instance).set_time(start_time, duration) -class RuntimePluginDescriptorV1(ctypes.Structure): - _fields_ = [ - ("struct_size", ctypes.c_uint64), - ("api_version", ctypes.c_uint64), - ("init_fn", RuntimeInitFn), - ("exit_fn", RuntimeExitFn), - ("get_next_operations_fn", RuntimeGetNextOpsFn), - ("shot_start_fn", RuntimeShotStartFn), - ("shot_end_fn", RuntimeShotEndFn), - ("get_metrics_fn", ctypes.c_void_p), - ("qalloc_fn", RuntimeQallocFn), - ("qfree_fn", RuntimeQfreeFn), - ("local_barrier_fn", ctypes.c_void_p), - ("global_barrier_fn", ctypes.c_void_p), - ("measure_fn", RuntimeMeasureFn), - ("measure_leaked_fn", RuntimeMeasureLeakedFn), - ("reset_fn", RuntimeResetFn), - ("force_result_fn", RuntimeForceResultFn), - ("get_bool_result_fn", RuntimeGetBoolResultFn), - ("get_u64_result_fn", RuntimeGetU64ResultFn), - ("set_bool_result_fn", RuntimeSetBoolResultFn), - ("set_u64_result_fn", RuntimeSetU64ResultFn), - ("increment_future_refcount_fn", RuntimeIncFutureFn), - ("decrement_future_refcount_fn", RuntimeDecFutureFn), - ("custom_call_fn", RuntimeCustomCallFn), - ("simulate_delay_fn", ctypes.c_void_p), - ("gate_fn", RuntimeGateFn), - ("negotiate_gateset_fn", RuntimeNegotiateGatesetFn), - ] - - -GetRuntimeDescriptorFn = ctypes.CFUNCTYPE(ctypes.POINTER(RuntimePluginDescriptorV1)) - - -class SeleneSimRuntimeLib(ctypes.CDLL): +class SeleneSimRuntimeLib: def __init__(self, runtime: Runtime) -> None: load_selene_global() - super().__init__(str(runtime.library_file)) - self._configure_signatures() - - def _configure_signatures(self): - descriptor: RuntimePluginDescriptorV1 | None = None + self.ffi = ffi() + self.types = RuntimeCTypes(self.ffi) + self.lib = self.ffi.dlopen(str(runtime.library_file)) try: - descriptor = RuntimePluginDescriptorV1.in_dll( - self, "selene_runtime_plugin_descriptor_v1" - ) - except ValueError: - getter = GetRuntimeDescriptorFn( - ("selene_runtime_get_plugin_descriptor_v1", self) - ) - descriptor_ptr = getter() - if bool(descriptor_ptr): - descriptor = descriptor_ptr.contents - if descriptor is None: + self.descriptor = self.lib.selene_runtime_plugin_descriptor_v1 + except AttributeError as exc: raise RuntimeError( - "Runtime plugin did not expose descriptor symbol or accessor" - ) + "Runtime plugin did not expose descriptor symbol" + ) from exc - self.selene_runtime_init = descriptor.init_fn - self.selene_runtime_exit = descriptor.exit_fn - self.selene_runtime_shot_start = descriptor.shot_start_fn - self.selene_runtime_shot_end = descriptor.shot_end_fn - self.selene_runtime_custom_call = descriptor.custom_call_fn - self.selene_runtime_get_next_operations = descriptor.get_next_operations_fn - self.selene_runtime_qalloc = descriptor.qalloc_fn - self.selene_runtime_qfree = descriptor.qfree_fn - self.selene_runtime_measure = descriptor.measure_fn - self.selene_runtime_measure_leaked = descriptor.measure_leaked_fn - self.selene_runtime_reset = descriptor.reset_fn - self.selene_runtime_force_result = descriptor.force_result_fn - self.selene_runtime_get_bool_result = descriptor.get_bool_result_fn - self.selene_runtime_get_u64_result = descriptor.get_u64_result_fn - self.selene_runtime_set_bool_result = descriptor.set_bool_result_fn - self.selene_runtime_set_u64_result = descriptor.set_u64_result_fn - self.selene_runtime_increment_future_refcount = ( - descriptor.increment_future_refcount_fn + self.last_error_fn = self._required( + self.descriptor.header.last_error_fn, "last_error_fn" + ) + self.get_name_fn = self._required( + self.descriptor.header.get_name_fn, "get_name_fn" + ) + self.init_fn = self._required(self.descriptor.init_fn, "init_fn") + self.get_next_operations_fn = self._required( + self.descriptor.get_next_operations_fn, "get_next_operations_fn" + ) + self.shot_start_fn = self._required( + self.descriptor.shot_start_fn, "shot_start_fn" + ) + self.shot_end_fn = self._required(self.descriptor.shot_end_fn, "shot_end_fn") + self.qalloc_fn = self._required(self.descriptor.qalloc_fn, "qalloc_fn") + self.qfree_fn = self._required(self.descriptor.qfree_fn, "qfree_fn") + self.local_barrier_fn = self._required( + self.descriptor.local_barrier_fn, "local_barrier_fn" + ) + self.global_barrier_fn = self._required( + self.descriptor.global_barrier_fn, "global_barrier_fn" + ) + self.measure_fn = self._required(self.descriptor.measure_fn, "measure_fn") + self.measure_leaked_fn = self._required( + self.descriptor.measure_leaked_fn, "measure_leaked_fn" ) - self.selene_runtime_decrement_future_refcount = ( - descriptor.decrement_future_refcount_fn + self.reset_fn = self._required(self.descriptor.reset_fn, "reset_fn") + self.force_result_fn = self._required( + self.descriptor.force_result_fn, "force_result_fn" + ) + self.get_bool_result_fn = self._required( + self.descriptor.get_bool_result_fn, "get_bool_result_fn" + ) + self.get_u64_result_fn = self._required( + self.descriptor.get_u64_result_fn, "get_u64_result_fn" + ) + self.set_bool_result_fn = self._required( + self.descriptor.set_bool_result_fn, "set_bool_result_fn" + ) + self.set_u64_result_fn = self._required( + self.descriptor.set_u64_result_fn, "set_u64_result_fn" + ) + self.increment_future_refcount_fn = self._required( + self.descriptor.increment_future_refcount_fn, + "increment_future_refcount_fn", + ) + self.decrement_future_refcount_fn = self._required( + self.descriptor.decrement_future_refcount_fn, + "decrement_future_refcount_fn", + ) + self.gate_fn = self._required(self.descriptor.gate_fn, "gate_fn") + self.negotiate_gateset_fn = self._required( + self.descriptor.negotiate_gateset_fn, "negotiate_gateset_fn" ) - self.selene_runtime_gate = descriptor.gate_fn - self.selene_runtime_negotiate_gateset = descriptor.negotiate_gateset_fn - if self.selene_runtime_custom_call is None: - raise RuntimeError("Runtime plugin does not expose custom_call_fn") - if self.selene_runtime_gate is None: - raise RuntimeError("Runtime plugin does not expose gate_fn") - if self.selene_runtime_negotiate_gateset is None: - raise RuntimeError("Runtime plugin does not expose negotiate_gateset_fn") + self.exit_fn = self.descriptor.exit_fn + self.get_metrics_fn = self.descriptor.get_metrics_fn + self.custom_call_fn = self.descriptor.custom_call_fn + self.simulate_delay_fn = self.descriptor.simulate_delay_fn + self.operation_callbacks = self._operation_callbacks() + + def _required(self, function, function_name: str): + if function == self.ffi.NULL: + raise RuntimeError(f"Runtime plugin does not expose {function_name}") + return function + + def _operation_callbacks(self): + callbacks = self.ffi.new(self.types.runtime_get_operation_interface_ptr) + callbacks.measure_fn = callback_measure + callbacks.measure_leaked_fn = callback_measure_leaked + callbacks.postselect_fn = callback_postselect + callbacks.reset_fn = callback_reset + callbacks.custom_fn = callback_custom + callbacks.set_batch_time_fn = callback_set_batch_time + callbacks.gate_fn = callback_gate + return callbacks[0] + + def init(self, n_qubits: int, start_time_nanos: int, args: list[str]): + handle = self.ffi.new(self.types.runtime_instance_ptr) + encoded_args = [ + self.ffi.new(self.types.char_array, arg.encode("utf-8")) for arg in args + ] + argv = self.ffi.new(self.types.char_ptr_array, encoded_args) + errno = self.init_fn(handle, n_qubits, start_time_nanos, len(args), argv) + if errno != 0: + raise RuntimeError("Failed to initialize Selene runtime") + return handle[0] + + def shot_start(self, instance, shot_id: int, seed: int): + return self.shot_start_fn(instance, shot_id, seed) + + def shot_end(self, instance): + return self.shot_end_fn(instance) + + def negotiate_gateset(self, instance, payload: bytes) -> bytes: + input_data = self.ffi.new(self.types.uint8_array, payload) + written = self.ffi.new(self.types.size_ptr) + errno = self.negotiate_gateset_fn( + instance, input_data, len(payload), self.ffi.NULL, 0, written + ) + if errno != 0: + raise RuntimeError("Failed to negotiate gateset with Selene runtime") + output_data = self.ffi.new(self.types.uint8_array, written[0]) + errno = self.negotiate_gateset_fn( + instance, input_data, len(payload), output_data, written[0], written + ) + if errno != 0: + raise RuntimeError("Failed to negotiate gateset with Selene runtime") + return bytes(self.ffi.buffer(output_data, written[0])) + + def get_next_operations(self, instance, batch: OperationBatch): + batch_handle = self.ffi.new_handle(batch) + handle = self.ffi.new(self.types.runtime_get_operation_handle_ptr) + handle.instance = batch_handle + handle.interface = self.operation_callbacks + return self.get_next_operations_fn(instance, handle[0]) + + def qalloc(self, instance): + qubit_id = self.ffi.new(self.types.uint64_ptr) + errno = self.qalloc_fn(instance, qubit_id) + return errno, int(qubit_id[0]) + + def qfree(self, instance, qubit_id: int): + return self.qfree_fn(instance, qubit_id) + + def gate(self, instance, payload: bytes): + data = self.ffi.new(self.types.uint8_array, payload) + return self.gate_fn(instance, data, len(payload)) + + def measure(self, instance, qubit: int): + future_ref = self.ffi.new(self.types.uint64_ptr) + errno = self.measure_fn(instance, qubit, future_ref) + return errno, int(future_ref[0]) + + def measure_leaked(self, instance, qubit: int): + future_ref = self.ffi.new(self.types.uint64_ptr) + errno = self.measure_leaked_fn(instance, qubit, future_ref) + return errno, int(future_ref[0]) + + def reset(self, instance, qubit: int): + return self.reset_fn(instance, qubit) + + def force_result(self, instance, result_id: int): + return self.force_result_fn(instance, result_id) + + def get_bool_result(self, instance, result_id: int): + value = self.ffi.new(self.types.int8_ptr) + errno = self.get_bool_result_fn(instance, result_id, value) + return errno, int(value[0]) + + def get_u64_result(self, instance, result_id: int): + value = self.ffi.new(self.types.uint64_ptr) + errno = self.get_u64_result_fn(instance, result_id, value) + return errno, int(value[0]) + + def set_bool_result(self, instance, result_id: int, value: bool): + return self.set_bool_result_fn(instance, result_id, value) + + def set_u64_result(self, instance, result_id: int, value: int): + return self.set_u64_result_fn(instance, result_id, value) + + def get_metric(self, instance, nth_metric: int): + if self.get_metrics_fn == self.ffi.NULL: + return None + name = self.ffi.new(self.types.char_array, 256) + datatype = self.ffi.new(self.types.uint8_ptr) + value = self.ffi.new(self.types.uint64_ptr) + errno = self.get_metrics_fn(instance, nth_metric, name, datatype, value) + if errno != 0: + return None + return self.ffi.string(name).decode("utf-8"), int(datatype[0]), int(value[0]) class InteractiveRuntime: @@ -360,7 +344,6 @@ def __init__( gateset: Gateset | None = None, ): self._lib = SeleneSimRuntimeLib(runtime) - self._instance = SeleneRuntimeInstancePtr() if runtime.random_seed is None: runtime.random_seed = random.randint(0, 2**64 - 1) self.runtime = runtime @@ -369,46 +352,26 @@ def __init__( self.n_qubits = n_qubits self.shot_id = 0 arguments = runtime.get_init_args() - # create an array of c_char_p from the list of strings - argc = len(arguments) - argv = (ctypes.c_char_p * argc)(*(arg.encode("utf-8") for arg in arguments)) - if 0 != self._lib.selene_runtime_init( - ctypes.byref(self._instance), n_qubits, start_time_nanos, argc, argv - ): - raise RuntimeError("Failed to initialize Selene runtime") + self._instance = self._lib.init(n_qubits, start_time_nanos, arguments) if self.gateset is not None: self.emitted_gateset = self.register_gateset(self.gateset) - if 0 != self._lib.selene_runtime_shot_start( - self._instance, self.shot_id, self.runtime.random_seed - ): + seed = self.runtime.random_seed + assert seed is not None + if 0 != self._lib.shot_start(self._instance, self.shot_id, seed): raise RuntimeError("Failed to start first shot on Selene runtime") def register_gateset(self, gateset: Gateset) -> Gateset: - data = gateset.serialize() - input_buffer = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) - written = ctypes.c_size_t() - if 0 != self._lib.selene_runtime_negotiate_gateset( - self._instance, input_buffer, len(data), None, 0, ctypes.byref(written) - ): - raise RuntimeError("Failed to negotiate gateset with Selene runtime") - output_buffer = (ctypes.c_uint8 * written.value)() - if 0 != self._lib.selene_runtime_negotiate_gateset( - self._instance, - input_buffer, - len(data), - output_buffer, - written.value, - ctypes.byref(written), - ): - raise RuntimeError("Failed to negotiate gateset with Selene runtime") - return Gateset.deserialize(bytes(output_buffer[: written.value])) + payload = gateset.serialize() + return Gateset.deserialize(self._lib.negotiate_gateset(self._instance, payload)) def next_shot(self): - if 0 != self._lib.selene_runtime_shot_end(self._instance): + if 0 != self._lib.shot_end(self._instance): raise RuntimeError("Failed to end current shot on Selene runtime") self.shot_id += 1 - if 0 != self._lib.selene_runtime_shot_start( - self._instance, self.shot_id, self.runtime.random_seed + self.shot_id + seed = self.runtime.random_seed + assert seed is not None + if 0 != self._lib.shot_start( + self._instance, self.shot_id, seed + self.shot_id ): raise RuntimeError("Failed to start next shot on Selene runtime") @@ -416,16 +379,7 @@ def get_operations(self) -> list[OperationBatch]: batches = [] while True: batch = OperationBatch() - batch_ref = ctypes.py_object(batch) - instance = ctypes.cast( - ctypes.pointer(batch_ref), SeleneRuntimeGetOperationInstance - ) - handle = SeleneRuntimeGetOperationHandle( - instance=instance, interface=OPERATION_BATCH_CALLBACKS - ) - if 0 != self._lib.selene_runtime_get_next_operations( - self._instance, handle - ): + if 0 != self._lib.get_next_operations(self._instance, batch): raise RuntimeError("Failed to get next operations from Selene runtime") if not batch.invoked: break @@ -433,79 +387,66 @@ def get_operations(self) -> list[OperationBatch]: return batches def qalloc(self) -> int: - qubit_id = ctypes.c_uint64() - if 0 != self._lib.selene_runtime_qalloc(self._instance, ctypes.byref(qubit_id)): + errno, qubit_id = self._lib.qalloc(self._instance) + if errno != 0: raise RuntimeError("Failed to allocate qubit on Selene runtime") - if qubit_id.value == 2**64 - 1: + if qubit_id == 2**64 - 1: raise RuntimeError( "Runtime returned UINT64_MAX, which is reserved to indicate failure (e.g. out of qubits), when trying to allocate a qubit" ) - return qubit_id.value + return qubit_id def qfree(self, qubit_id: int): - if 0 != self._lib.selene_runtime_qfree(self._instance, qubit_id): + if 0 != self._lib.qfree(self._instance, qubit_id): raise RuntimeError("Failed to free qubit on Selene runtime") def gate(self, gate: Gate): - data = gate.serialize() - input_buffer = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) - if 0 != self._lib.selene_runtime_gate(self._instance, input_buffer, len(data)): + payload = gate.serialize() + if 0 != self._lib.gate(self._instance, payload): raise RuntimeError( "Failed to apply generic gate operation on Selene runtime" ) def measure(self, qubit: int) -> int: - future_ref = ctypes.c_uint64() - if 0 != self._lib.selene_runtime_measure( - self._instance, qubit, ctypes.byref(future_ref) - ): + errno, future_ref = self._lib.measure(self._instance, qubit) + if errno != 0: raise RuntimeError("Failed to apply measure operation on Selene runtime") - return future_ref.value + return future_ref def measure_leaked(self, qubit: int) -> int: - future_ref = ctypes.c_uint64() - if 0 != self._lib.selene_runtime_measure_leaked( - self._instance, qubit, ctypes.byref(future_ref) - ): + errno, future_ref = self._lib.measure_leaked(self._instance, qubit) + if errno != 0: raise RuntimeError( "Failed to apply measure_leaked operation on Selene runtime" ) - return future_ref.value + return future_ref def reset(self, qubit: int): - if 0 != self._lib.selene_runtime_reset(self._instance, qubit): + if 0 != self._lib.reset(self._instance, qubit): raise RuntimeError("Failed to apply RESET operation on Selene runtime") def force_result(self, result_id: int): - if 0 != self._lib.selene_runtime_force_result(self._instance, result_id): + if 0 != self._lib.force_result(self._instance, result_id): raise RuntimeError("Failed to force result on Selene runtime") def get_bool_result(self, result_id: int) -> bool: - value = ctypes.c_uint8() - if 0 != self._lib.selene_runtime_get_bool_result( - self._instance, result_id, ctypes.byref(value) - ): + errno, value = self._lib.get_bool_result(self._instance, result_id) + if errno != 0: raise RuntimeError("Failed to get bool result from Selene runtime") - return bool(value.value) + return bool(value) def get_u64_result(self, result_id: int) -> int: - value = ctypes.c_uint64() - if 0 != self._lib.selene_runtime_get_u64_result( - self._instance, result_id, ctypes.byref(value) - ): + errno, value = self._lib.get_u64_result(self._instance, result_id) + if errno != 0: raise RuntimeError("Failed to get u64 result from Selene runtime") - return value.value + return value def set_bool_result(self, result_id: int, value: bool): - if 0 != self._lib.selene_runtime_set_bool_result( - self._instance, result_id, value - ): + if 0 != self._lib.set_bool_result(self._instance, result_id, value): raise RuntimeError("Failed to set bool result on Selene runtime") def set_u64_result(self, result_id: int, value: int): - if 0 != self._lib.selene_runtime_set_u64_result( - self._instance, result_id, value - ): + if 0 != self._lib.set_u64_result(self._instance, result_id, value): raise RuntimeError("Failed to set u64 result on Selene runtime") def get_metrics(self) -> dict[str, int | float | bool]: @@ -517,31 +458,20 @@ def get_metrics(self) -> dict[str, int | float | bool]: # if it returns 0, read the name, type and value, and add it to the results dict. If it returns nonzero, stop and return the results dict. results: dict[str, int | float | bool] = {} for i in range(256): - name_buffer = ctypes.create_string_buffer(256) - type_buffer = ctypes.c_uint8() - value_buffer = ctypes.c_uint64() - if 0 != self._lib.selene_runtime_get_metrics( - self._instance, - i, - name_buffer, - ctypes.byref(type_buffer), - ctypes.byref(value_buffer), - ): + metric = self._lib.get_metric(self._instance, i) + if metric is None: break - name = name_buffer.value.decode("utf-8") - type_ = type_buffer.value - value = value_buffer.value + name, type_, value = metric if type_ == 0: results[name] = bool(value) elif type_ == 1: - results[name] = ctypes.c_int64(value).value + results[name] = int.from_bytes( + value.to_bytes(8, "little"), "little", signed=True + ) elif type_ == 2: results[name] = value elif type_ == 3: - results[name] = ctypes.cast( - ctypes.pointer(ctypes.c_uint64(value)), - ctypes.POINTER(ctypes.c_double), - ).contents.value + results[name] = struct.unpack(" None: load_selene_global() - super().__init__(str(simulator.library_file)) - self._configure_signatures() - - def _configure_signatures(self): - descriptor: SimulatorPluginDescriptorV1 | None = None + self.ffi = ffi() + self.types = SimulatorCTypes(self.ffi) + self.lib = self.ffi.dlopen(str(simulator.library_file)) try: - descriptor = SimulatorPluginDescriptorV1.in_dll( - self, "selene_simulator_plugin_descriptor_v1" - ) - except ValueError: - getter = GetSimulatorDescriptorFn( - ("selene_simulator_get_plugin_descriptor_v1", self) - ) - descriptor_ptr = getter() - if bool(descriptor_ptr): - descriptor = descriptor_ptr.contents - if descriptor is None: + self.descriptor = self.lib.selene_simulator_plugin_descriptor_v1 + except AttributeError as exc: raise RuntimeError( - "Simulator plugin did not expose descriptor symbol or accessor" - ) + "Simulator plugin did not expose descriptor symbol" + ) from exc + + self.last_error_fn = self._required( + self.descriptor.header.last_error_fn, "last_error_fn" + ) + self.get_name_fn = self._required( + self.descriptor.header.get_name_fn, "get_name_fn" + ) + self.init_fn = self._required(self.descriptor.init_fn, "init_fn") + self.shot_start_fn = self._required( + self.descriptor.shot_start_fn, "shot_start_fn" + ) + self.shot_end_fn = self._required(self.descriptor.shot_end_fn, "shot_end_fn") + self.handle_operations_fn = self._required( + self.descriptor.handle_operations_fn, "handle_operations_fn" + ) + self.negotiate_gateset_fn = self._required( + self.descriptor.negotiate_gateset_fn, "negotiate_gateset_fn" + ) + self.dump_state_fn = self._required( + self.descriptor.dump_state_fn, "dump_state_fn" + ) + + self.exit_fn = self.descriptor.exit_fn + self.get_metrics_fn = self.descriptor.get_metrics_fn + + def _required(self, function, function_name: str): + if function == self.ffi.NULL: + raise RuntimeError(f"Simulator plugin does not expose {function_name}") + return function - self.selene_simulator_init = descriptor.init_fn - self.selene_simulator_exit = descriptor.exit_fn - self.selene_simulator_shot_start = descriptor.shot_start_fn - self.selene_simulator_shot_end = descriptor.shot_end_fn - self.selene_simulator_operation_measure = descriptor.measure_fn - self.selene_simulator_operation_postselect = descriptor.postselect_fn - self.selene_simulator_operation_reset = descriptor.reset_fn - self.selene_simulator_get_metrics = descriptor.get_metrics_fn - self.selene_simulator_dump_state = descriptor.dump_state_fn - self.selene_simulator_operation_gate = descriptor.gate_fn - self.selene_simulator_negotiate_gateset = descriptor.negotiate_gateset_fn - - if self.selene_simulator_operation_postselect is None: - raise RuntimeError("Simulator plugin does not expose postselect_fn") - if self.selene_simulator_get_metrics is None: - raise RuntimeError("Simulator plugin does not expose get_metrics_fn") - if self.selene_simulator_operation_gate is None: - raise RuntimeError("Simulator plugin does not expose gate_fn") - if self.selene_simulator_negotiate_gateset is None: - raise RuntimeError("Simulator plugin does not expose negotiate_gateset_fn") + def init(self, n_qubits: int, args: list[str]): + handle = self.ffi.new(self.types.simulator_instance_ptr) + encoded_args = [ + self.ffi.new(self.types.char_array, arg.encode("utf-8")) for arg in args + ] + argv = self.ffi.new(self.types.char_ptr_array, encoded_args) + errno = self.init_fn(handle, n_qubits, len(args), argv) + if errno != 0: + raise RuntimeError("Failed to initialize Selene simulator") + return handle[0] + + def shot_start(self, instance, shot_id: int, seed: int): + return self.shot_start_fn(instance, shot_id, seed) + + def shot_end(self, instance): + return self.shot_end_fn(instance) + + def _batch_handle(self, operations: list[Any]): + refs: list[Any] = [] + + @self.ffi.callback( + "void(const RuntimeExtractOperationHandle *, RuntimeGetOperationHandle)" + ) + def extract(input_handle, output_handle): + batch = self.ffi.from_handle(input_handle[0].instance) + for operation in batch: + kind = operation[0] + if kind == "gate": + payload = operation[1] + data = self.ffi.new(self.types.uint8_array, payload) + refs.append(data) + output_handle.interface.gate_fn( + output_handle.instance, data, len(payload) + ) + elif kind == "measure": + output_handle.interface.measure_fn( + output_handle.instance, operation[1], operation[2] + ) + elif kind == "postselect": + output_handle.interface.postselect_fn( + output_handle.instance, operation[1], operation[2] + ) + elif kind == "reset": + output_handle.interface.reset_fn(output_handle.instance, operation[1]) + else: + raise RuntimeError(f"unsupported simulator operation {kind!r}") + + batch_ref = self.ffi.new_handle(operations) + refs.extend([batch_ref, extract]) + return self.ffi.new( + self.types.runtime_extract_operation_handle, + {"instance": batch_ref, "interface": {"extract_fn": extract}}, + ), refs + + def _result_handle(self): + result = {"bool": {}, "u64": {}} + refs: list[Any] = [] + + @self.ffi.callback("void(SeleneOperationResultInstance, uint64_t, bool)") + def set_bool(instance, result_id, value): + target = self.ffi.from_handle(instance) + target["bool"][int(result_id)] = bool(value) + + @self.ffi.callback("void(SeleneOperationResultInstance, uint64_t, uint64_t)") + def set_u64(instance, result_id, value): + target = self.ffi.from_handle(instance) + target["u64"][int(result_id)] = int(value) + + result_ref = self.ffi.new_handle(result) + refs.extend([result_ref, set_bool, set_u64]) + return self.ffi.new( + self.types.operation_result_handle, + { + "instance": result_ref, + "interface": { + "set_bool_result_fn": set_bool, + "set_u64_result_fn": set_u64, + }, + }, + ), result, refs + + def _handle_operations(self, instance, operations: list[Any]): + batch, batch_refs = self._batch_handle(operations) + result, result_data, result_refs = self._result_handle() + refs = [batch, result, *batch_refs, *result_refs] + try: + errno = self.handle_operations_fn(instance, batch[0], result[0]) + finally: + refs.clear() + return errno, result_data + + def gate(self, instance, payload: bytes): + errno, result = self._handle_operations(instance, [("gate", payload)]) + if errno == 0 and (result["bool"] or result["u64"]): + return -1 + return errno + + def measure(self, instance, qubit: int): + errno, result = self._handle_operations(instance, [("measure", qubit, 0)]) + if errno != 0: + return errno + bool_results = result["bool"] + if set(bool_results) != {0} or result["u64"]: + return -1 + return 1 if bool_results[0] else 0 + + def reset(self, instance, qubit: int): + errno, result = self._handle_operations(instance, [("reset", qubit)]) + if errno == 0 and (result["bool"] or result["u64"]): + return -1 + return errno + + def postselect(self, instance, qubit: int, value: bool): + errno, result = self._handle_operations(instance, [("postselect", qubit, value)]) + if errno == 0 and (result["bool"] or result["u64"]): + return -1 + return errno + + def negotiate_gateset(self, instance, payload: bytes) -> bytes: + input_data = self.ffi.new(self.types.uint8_array, payload) + written = self.ffi.new(self.types.size_ptr) + errno = self.negotiate_gateset_fn( + instance, input_data, len(payload), self.ffi.NULL, 0, written + ) + if errno != 0: + raise RuntimeError("Failed to negotiate gateset with Selene simulator") + output_data = self.ffi.new(self.types.uint8_array, written[0]) + errno = self.negotiate_gateset_fn( + instance, input_data, len(payload), output_data, written[0], written + ) + if errno != 0: + raise RuntimeError("Failed to negotiate gateset with Selene simulator") + return bytes(self.ffi.buffer(output_data, written[0])) + + def get_metric(self, instance, nth_metric: int): + if self.get_metrics_fn == self.ffi.NULL: + return None + name = self.ffi.new(self.types.char_array, 256) + datatype = self.ffi.new(self.types.uint8_ptr) + value = self.ffi.new(self.types.uint64_ptr) + errno = self.get_metrics_fn(instance, nth_metric, name, datatype, value) + if errno != 0: + return None + return self.ffi.string(name).decode("utf-8"), int(datatype[0]), int(value[0]) + + def dump_state(self, instance, outfile: Path, qubits: list[int]): + path = self.ffi.new(self.types.char_array, str(outfile).encode("utf-8")) + qubit_data = self.ffi.new(self.types.uint64_array, qubits) + return self.dump_state_fn(instance, path, qubit_data, len(qubits)) class InteractiveSimulator: @@ -148,7 +213,6 @@ def __init__( gateset: Gateset | None = None, ): self._lib = SeleneSimSimulatorLib(simulator) - self._instance = SeleneSimulatorInstancePtr() if simulator.random_seed is None: simulator.random_seed = random.randint(0, 2**64 - 1) self.simulator = simulator @@ -157,77 +221,49 @@ def __init__( self.n_qubits = n_qubits self.shot_id = 0 arguments = simulator.get_init_args() - # create an array of c_char_p from the list of strings - argc = len(arguments) - argv = (ctypes.c_char_p * argc)(*(arg.encode("utf-8") for arg in arguments)) - if 0 != self._lib.selene_simulator_init( - ctypes.byref(self._instance), n_qubits, argc, argv - ): - raise RuntimeError("Failed to initialize Selene simulator") + self._instance = self._lib.init(n_qubits, arguments) if self.gateset is not None: self.emitted_gateset = self.register_gateset(self.gateset) - if 0 != self._lib.selene_simulator_shot_start( - self._instance, self.shot_id, self.simulator.random_seed - ): + seed = self.simulator.random_seed + assert seed is not None + if 0 != self._lib.shot_start(self._instance, self.shot_id, seed): raise RuntimeError("Failed to start first shot on Selene simulator") - def _apply_void_operation(self, func, operation_name: str, *args): + def _apply_void_operation(self, errno: int, operation_name: str): # The Python surface stays single-operation for ergonomics, while the # native bridge now executes each call via the simulator's batch API. - if 0 != func(self._instance, *args): + if errno != 0: raise RuntimeError( f"Failed to apply {operation_name} operation on Selene simulator" ) def register_gateset(self, gateset: Gateset) -> Gateset: payload = gateset.serialize() - input_buffer = (ctypes.c_uint8 * len(payload))(*payload) - input_ptr = ctypes.cast(input_buffer, ctypes.POINTER(ctypes.c_uint8)) - written = ctypes.c_size_t() - if 0 != self._lib.selene_simulator_negotiate_gateset( - self._instance, - input_ptr, - len(payload), - None, - 0, - ctypes.byref(written), - ): - raise RuntimeError("Failed to negotiate gateset with Selene simulator") - output_buffer = (ctypes.c_uint8 * written.value)() - output_ptr = ctypes.cast(output_buffer, ctypes.POINTER(ctypes.c_uint8)) - if 0 != self._lib.selene_simulator_negotiate_gateset( - self._instance, - input_ptr, - len(payload), - output_ptr, - written.value, - ctypes.byref(written), - ): - raise RuntimeError("Failed to negotiate gateset with Selene simulator") - return Gateset.deserialize(bytes(output_buffer[: written.value])) + return Gateset.deserialize(self._lib.negotiate_gateset(self._instance, payload)) def gate(self, gate: Gate): payload = gate.serialize() - buffer = (ctypes.c_uint8 * len(payload))(*payload) self._apply_void_operation( - self._lib.selene_simulator_operation_gate, + self._lib.gate(self._instance, payload), "GATE", - ctypes.cast(buffer, ctypes.POINTER(ctypes.c_uint8)), - len(payload), ) def _apply_measure_operation(self, qubit: int) -> bool: - result = self._lib.selene_simulator_operation_measure(self._instance, qubit) + result = self._lib.measure(self._instance, qubit) if result not in (0, 1): raise RuntimeError("Failed to apply MEASURE operation on Selene simulator") return bool(result) def next_shot(self): - if 0 != self._lib.selene_simulator_shot_end(self._instance): + if 0 != self._lib.shot_end(self._instance): raise RuntimeError("Failed to end current shot on Selene simulator") self.shot_id += 1 - if 0 != self._lib.selene_simulator_shot_start( - self._instance, self.shot_id, self.simulator.random_seed + self.shot_id + seed = self.simulator.random_seed + assert seed is not None + if 0 != self._lib.shot_start( + self._instance, + self.shot_id, + seed + self.shot_id, ): raise RuntimeError("Failed to start next shot on Selene simulator") @@ -236,15 +272,13 @@ def measure(self, qubit: int) -> bool: def reset(self, qubit: int): self._apply_void_operation( - self._lib.selene_simulator_operation_reset, "RESET", qubit + self._lib.reset(self._instance, qubit), "RESET" ) def postselect(self, qubit: int, value: bool): self._apply_void_operation( - self._lib.selene_simulator_operation_postselect, + self._lib.postselect(self._instance, qubit, value), "POSTSELECT", - qubit, - value, ) def get_metrics(self) -> dict[str, int | float | bool]: @@ -256,47 +290,24 @@ def get_metrics(self) -> dict[str, int | float | bool]: # if it returns 0, read the name, type and value, and add it to the results dict. If it returns nonzero, stop and return the results dict. results: dict[str, int | float | bool] = {} for i in range(256): - name_buffer = ctypes.create_string_buffer(256) - type_buffer = ctypes.c_uint8() - value_buffer = ctypes.c_uint64() - if 0 != self._lib.selene_simulator_get_metrics( - self._instance, - i, - name_buffer, - ctypes.byref(type_buffer), - ctypes.byref(value_buffer), - ): + metric = self._lib.get_metric(self._instance, i) + if metric is None: break - name = name_buffer.value.decode("utf-8") - type_ = type_buffer.value - value = value_buffer.value + name, type_, value = metric if type_ == 0: results[name] = bool(value) elif type_ == 1: - results[name] = ctypes.c_int64(value).value + results[name] = int.from_bytes( + value.to_bytes(8, "little"), "little", signed=True + ) elif type_ == 2: results[name] = value elif type_ == 3: - results[name] = ctypes.cast( - ctypes.pointer(ctypes.c_uint64(value)), - ctypes.POINTER(ctypes.c_double), - ).contents.value + results[name] = struct.unpack(" None: - qubit_array = (ctypes.c_uint64 * len(qubits))(*qubits) - outfile_str = str(outfile).encode("utf-8") - len_qubits = len(qubits) - pointer_to_qubit_array = ctypes.cast( - ctypes.pointer(qubit_array), ctypes.POINTER(ctypes.c_uint64) - ) - - if 0 != self._lib.selene_simulator_dump_state( - self._instance, - outfile_str, - pointer_to_qubit_array, - ctypes.c_uint64(len_qubits), - ): + if 0 != self._lib.dump_state(self._instance, outfile, qubits): raise RuntimeError("Failed to dump state on Selene simulator") diff --git a/uv.lock b/uv.lock index 2dfe145d..2a76171d 100644 --- a/uv.lock +++ b/uv.lock @@ -154,6 +154,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/a6/ac03f37dc9aeebf398d42089720648b3bc8438e733d3e522196c5d12ab39/blake3-1.0.9-cp314-cp314t-win_amd64.whl", hash = "sha256:5ea0c60dd9c1e3d05610606579e4bf80f562854c46ed55f9ee8545e18987a480", size = 217979, upload-time = "2026-06-22T18:01:46.629Z" }, ] +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -826,6 +908,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pydantic" version = "2.13.4" @@ -1322,6 +1413,7 @@ version = "0.3.0a1" source = { editable = "selene-core" } dependencies = [ { name = "blake3" }, + { name = "cffi" }, { name = "hugr" }, { name = "lief" }, { name = "llvmlite", version = "0.45.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, @@ -1343,6 +1435,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "blake3", specifier = ">=1.0.0" }, + { name = "cffi", specifier = ">=1.17.1" }, { name = "hugr", specifier = ">=0.13.0" }, { name = "lief", specifier = ">=0.16.5" }, { name = "llvmlite", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = "~=0.47" }, From bbf27cf2c361f5a7ec8185f73f44924e5a81de5c Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:42:18 +0100 Subject: [PATCH 09/19] feat(sim): register utilities during linked builds --- .../build_utils/builtins/selene.py | 3 + selene-core/python/selene_core/utility.py | 26 +++ .../python/selene_argreader_plugin/plugin.py | 4 + .../selene_argreader_plugin/provider.py | 4 +- .../resources/argreader_zero_length_arrays.ll | 44 ++++ .../interface0/trace.json | 60 ++++++ .../interface1/trace.json | 60 ++++++ .../argreader/python/tests/test_argreader.py | 27 +++ selene-ext/utilities/argreader/rust/lib.rs | 204 +++++++++++++++--- selene-sim/python/selene_sim/build.py | 66 +++++- .../python/tests/test_build_validation.py | 2 +- 11 files changed, 462 insertions(+), 38 deletions(-) create mode 100644 selene-ext/utilities/argreader/python/tests/resources/argreader_zero_length_arrays.ll create mode 100644 selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface0/trace.json create mode 100644 selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface1/trace.json diff --git a/selene-core/python/selene_core/build_utils/builtins/selene.py b/selene-core/python/selene_core/build_utils/builtins/selene.py index 3a70ae41..6d6bfa71 100644 --- a/selene-core/python/selene_core/build_utils/builtins/selene.py +++ b/selene-core/python/selene_core/build_utils/builtins/selene.py @@ -127,9 +127,11 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: raise RuntimeError(f"Unsupported OS {sys.platform}") link_flags = [] library_search_dirs = [selene_lib_dir] + libraries = [] for dep in build_ctx.deps: link_flags.extend(dep.link_flags) library_search_dirs.extend(dep.library_search_dirs) + libraries.append(dep.path) if build_ctx.verbose: print("Linking selene object file with selene core library") @@ -141,6 +143,7 @@ def apply(cls, build_ctx: BuildCtx, input_artifact: Artifact) -> Artifact: out_path, input_artifact.resource, selene_lib, + *libraries, *link_flags, cache_dir=zig_cache_dir, ) diff --git a/selene-core/python/selene_core/utility.py b/selene-core/python/selene_core/utility.py index 9aba3b32..b63f8ce2 100644 --- a/selene-core/python/selene_core/utility.py +++ b/selene-core/python/selene_core/utility.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from pathlib import Path +import re @dataclass @@ -37,3 +38,28 @@ def library_search_dirs(self) -> list[Path]: Returns the paths to any additional libraries required by the plugin. """ return [] + + @property + def registration_symbol(self) -> str | None: + """ + Optional C symbol used to register runtime event callbacks. + + Utility libraries are linked into the final executable rather than + loaded from Selene's plugin configuration. If this returns a symbol, + Selene's build step generates a tiny registration object that calls + the symbol once after Selene has been configured. The function must + have this C signature: + + struct selene_void_result_t symbol(SeleneInstance *instance); + + The utility may then call selene_register_utility_event_callbacks(). + """ + return None + + def validate_registration_symbol(self) -> str | None: + symbol = self.registration_symbol + if symbol is None: + return None + if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", symbol): + raise ValueError(f"Invalid utility registration symbol: {symbol!r}") + return symbol diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py index 9084d412..03452db7 100644 --- a/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/plugin.py @@ -33,3 +33,7 @@ def link_flags(self) -> list[str]: ] else: return [] + + @property + def registration_symbol(self) -> str: + return "selene_argreader_register_utility" diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py index e28cd667..14e0531f 100644 --- a/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py @@ -30,8 +30,8 @@ def __post_init__(self): raise ValueError( f"All items in the list for key '{key}' must be int, float, or bool" ) - if isinstance(value, list) and len(value) == 0: - raise ValueError(f"List for key '{key}' cannot be empty") + #if isinstance(value, list) and len(value) == 0: + # raise ValueError(f"List for key '{key}' cannot be empty") if not isinstance(key, str): raise ValueError(f"Key '{key}' must be a string") if len(key) == 0: diff --git a/selene-ext/utilities/argreader/python/tests/resources/argreader_zero_length_arrays.ll b/selene-ext/utilities/argreader/python/tests/resources/argreader_zero_length_arrays.ll new file mode 100644 index 00000000..40ecae8a --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/resources/argreader_zero_length_arrays.ll @@ -0,0 +1,44 @@ +; ModuleID = 'custom' +source_filename = "custom" +target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" +target triple = "aarch64-unknown-linux-gnu" + +; standard QIS +declare void @setup(i64) local_unnamed_addr +declare i64 @teardown() local_unnamed_addr +declare void @print_uint(i8*, i64, i64) local_unnamed_addr + +; arg reader plugin functions +declare void @argreader_get_bool_array(i8*, i8*, i64) local_unnamed_addr +declare void @argreader_get_u64_array(i8*, i64*, i64) local_unnamed_addr +declare void @argreader_get_i64_array(i8*, i64*, i64) local_unnamed_addr +declare void @argreader_get_f64_array(i8*, double*, i64) local_unnamed_addr + +@bool_array_label = private constant [17 x i8] c"\10input_bool_array" +@u64_array_label = private constant [16 x i8] c"\0Finput_u64_array" +@i64_array_label = private constant [16 x i8] c"\0Finput_i64_array" +@f64_array_label = private constant [16 x i8] c"\0Finput_f64_array" +@done_tag = private constant [14 x i8] c"\0DUSER:INT:done" + +define private void @main_inner() unnamed_addr { +alloca_block: + call void @argreader_get_bool_array(ptr @bool_array_label, ptr null, i64 0) + call void @argreader_get_u64_array(ptr @u64_array_label, ptr null, i64 0) + call void @argreader_get_i64_array(ptr @i64_array_label, ptr null, i64 0) + call void @argreader_get_f64_array(ptr @f64_array_label, ptr null, i64 0) + + call void @print_uint(ptr @done_tag, i64 0, i64 0) + ret void +} + +define i64 @qmain(i64 %0) local_unnamed_addr { +entry: + tail call void @setup(i64 %0) + tail call fastcc void @main_inner() + %1 = tail call i64 @teardown() + ret i64 %1 +} + +!name = !{!0} + +!0 = !{!"mainlib"} diff --git a/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface0/trace.json b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface0/trace.json new file mode 100644 index 00000000..fceb5df8 --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface0/trace.json @@ -0,0 +1,60 @@ +{ + "events": [ + { + "source": { + "kind": "UserProgram", + "index": 0 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9ib29sX2FycmF5CnZhbHVlOiBbXQo=" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 1 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF91NjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 2 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9pNjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 3 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9mNjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + } + ] +} \ No newline at end of file diff --git a/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface1/trace.json b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface1/trace.json new file mode 100644 index 00000000..fceb5df8 --- /dev/null +++ b/selene-ext/utilities/argreader/python/tests/snapshots/test_argreader/test_arg_reader_zero_length_arrays/interface1/trace.json @@ -0,0 +1,60 @@ +{ + "events": [ + { + "source": { + "kind": "UserProgram", + "index": 0 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9ib29sX2FycmF5CnZhbHVlOiBbXQo=" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 1 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF91NjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 2 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9pNjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + }, + { + "source": { + "kind": "UserProgram", + "index": 3 + }, + "event": { + "kind": "Custom", + "payload": { + "kind": "OpaquePayload", + "tag": 11616494188317837126, + "data": "a2V5OiBpbnB1dF9mNjRfYXJyYXkKdmFsdWU6IFtdCg==" + } + } + } + ] +} \ No newline at end of file diff --git a/selene-ext/utilities/argreader/python/tests/test_argreader.py b/selene-ext/utilities/argreader/python/tests/test_argreader.py index 8634ba41..95d796cf 100644 --- a/selene-ext/utilities/argreader/python/tests/test_argreader.py +++ b/selene-ext/utilities/argreader/python/tests/test_argreader.py @@ -236,3 +236,30 @@ def test_arg_reader_trace(snapshot, interface): trace = extractor.shots[0].get_trace() json = trace.model_dump_json(indent=2) snapshot.assert_match(json, "trace.json") + +@pytest.mark.parametrize("interface", [HeliosInterface(), SolInterface()]) +def test_arg_reader_zero_length_arrays(snapshot, interface): + llvm_file = Path(__file__).parent / "resources/argreader_zero_length_arrays.ll" + instance = build(llvm_file, utilities=[ArgReaderPlugin()], interface=interface) + + arg_provider = ArgProvider() + arg_provider.set_constant_args( + input_bool_array=[], + input_u64_array=[], + input_i64_array=[], + input_f64_array=[], + ) + + extractor = CircuitExtractor() + + with arg_provider: + result = list( + list(r) + for r in instance.run_shots( + n_qubits=1, n_shots=1, simulator=Coinflip(), event_hook=extractor + ) + ) + + trace = extractor.shots[0].get_trace() + json = trace.model_dump_json(indent=2) + snapshot.assert_match(json, "trace.json") diff --git a/selene-ext/utilities/argreader/rust/lib.rs b/selene-ext/utilities/argreader/rust/lib.rs index 1ade1530..60d7bcc1 100644 --- a/selene-ext/utilities/argreader/rust/lib.rs +++ b/selene-ext/utilities/argreader/rust/lib.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::BTreeMap; +use std::ffi::c_void; use std::{fs, io}; /// When logging via selene's log_utility_call function, @@ -49,9 +50,31 @@ impl RunInputs { thread_local! { pub static INPUTS: RefCell> = const{ RefCell::new(None) }; + pub static ACTIVE_SHOT: RefCell> = const{ RefCell::new(None) }; pub static SHOT_INPUT_CACHE: RefCell> = const{ RefCell::new(None) }; } +#[repr(C)] +pub struct SeleneVoidResult { + error_code: u32, +} + +impl SeleneVoidResult { + fn ok() -> Self { + Self { error_code: 0 } + } +} + +type SeleneUtilityShotEventFn = + Option SeleneVoidResult>; + +#[repr(C)] +pub struct SeleneUtilityEventCallbacksV1 { + context: *mut c_void, + on_shot_start: SeleneUtilityShotEventFn, + on_shot_end: SeleneUtilityShotEventFn, +} + unsafe extern "C" { // this is imported from selene at link time. // note that we can't use panic() -> this conflicts with libSystem on Darwin, @@ -60,8 +83,11 @@ unsafe extern "C" { // Instead we use panic_str, an exposed function for registering a panic with // the selene result stream based on a C string rather than a CL string. fn panic_str(error_code: u32, message: *const std::ffi::c_char) -> !; - fn get_current_shot() -> u64; fn log_utility_call(tag: u64, data_ptr: *const u8, data_len: u64); + fn selene_register_utility_event_callbacks( + instance: *mut c_void, + callbacks: SeleneUtilityEventCallbacksV1, + ) -> SeleneVoidResult; } fn selene_panic(message: String) -> ! { @@ -114,6 +140,76 @@ fn init() { }); } +fn cache_inputs_for_shot(shot_id: u64) { + let inputs_are_loaded = INPUTS.with(|cell| cell.borrow().is_some()); + if !inputs_are_loaded { + return; + } + + let shot_inputs = INPUTS.with(|cell| { + let inputs = cell.borrow(); + + inputs + .as_ref() + .expect("runtime inputs should be initialised") + .get_inputs_for_shot(shot_id) + .unwrap_or_else(|| { + selene_panic(format!( + "No runtime arguments provided for shot {shot_id} (0-indexed)" + )) + }) + .clone() + }); + + SHOT_INPUT_CACHE.with(|cache_cell| { + *cache_cell.borrow_mut() = Some((shot_id, shot_inputs)); + }); +} + +unsafe extern "C" fn argreader_on_shot_start( + _context: *mut c_void, + shot_id: u64, +) -> SeleneVoidResult { + ACTIVE_SHOT.with(|shot_cell| { + *shot_cell.borrow_mut() = Some(shot_id); + }); + SHOT_INPUT_CACHE.with(|cache_cell| { + *cache_cell.borrow_mut() = None; + }); + cache_inputs_for_shot(shot_id); + SeleneVoidResult::ok() +} + +unsafe extern "C" fn argreader_on_shot_end( + _context: *mut c_void, + _shot_id: u64, +) -> SeleneVoidResult { + ACTIVE_SHOT.with(|shot_cell| { + *shot_cell.borrow_mut() = None; + }); + SHOT_INPUT_CACHE.with(|cache_cell| { + *cache_cell.borrow_mut() = None; + }); + SeleneVoidResult::ok() +} + +#[unsafe(no_mangle)] +/// Register argreader's per-shot event callbacks with a Selene instance. +/// +/// # Safety +/// The provided instance pointer must be a valid Selene instance pointer supplied by Selene during +/// utility registration. +pub unsafe extern "C" fn selene_argreader_register_utility( + instance: *mut c_void, +) -> SeleneVoidResult { + let callbacks = SeleneUtilityEventCallbacksV1 { + context: std::ptr::null_mut(), + on_shot_start: Some(argreader_on_shot_start), + on_shot_end: Some(argreader_on_shot_end), + }; + unsafe { selene_register_utility_event_callbacks(instance, callbacks) } +} + fn get_key(key_ptr: *const u8) -> String { // As with result() calls and panic() calls, the format of the key starts with // byte providing the length of the string that follows. @@ -131,47 +227,50 @@ fn get_key(key_ptr: *const u8) -> String { } } -unsafe fn value_helper(key: &String) -> InputRecord { +fn active_shot() -> u64 { + ACTIVE_SHOT.with(|shot_cell| { + let shot = *shot_cell.borrow(); + shot.unwrap_or_else(|| { + selene_panic( + "Runtime arguments can only be read while Selene is executing a shot".to_string(), + ) + }) + }) +} + +fn value_helper(key: &String) -> InputRecord { if INPUTS.with(|cell| cell.borrow().is_none()) { init(); } - let current_shot = unsafe { get_current_shot() }; + let current_shot = active_shot(); // If we have cached inputs for the current shot, use them. Otherwise, look them up and cache // them. let shot_inputs = SHOT_INPUT_CACHE.with(|cache_cell| { let cached = cache_cell.borrow(); - if let Some((cached_shot, cached_inputs)) = cached.as_ref() && *cached_shot == current_shot { + if let Some((cached_shot, cached_inputs)) = cached.as_ref() + && *cached_shot == current_shot + { return cached_inputs.clone(); } drop(cached); + cache_inputs_for_shot(current_shot); + let cached = cache_cell.borrow(); - let shot_inputs = INPUTS.with(|cell| { - let inputs = cell.borrow(); - - inputs - .as_ref() - .expect("runtime inputs should be initialised") - .get_inputs_for_shot(current_shot) - .unwrap_or_else(|| { - selene_panic(format!( - "No runtime arguments provided for shot {current_shot} (0-indexed)" - )) - }) - .clone() - }); - - *cache_cell.borrow_mut() = Some((current_shot, shot_inputs.clone())); - - shot_inputs + cached + .as_ref() + .expect("runtime inputs should be cached") + .1 + .clone() }); - let record = shot_inputs.records.get(key).unwrap_or_else(|| { - selene_panic(format!("Missing runtime argument '{key}'")) - }); + let record = shot_inputs + .records + .get(key) + .unwrap_or_else(|| selene_panic(format!("Missing runtime argument '{key}'"))); log(key, record); @@ -186,7 +285,7 @@ unsafe fn value_helper(key: &String) -> InputRecord { #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_bool(key_ptr: *const u8) -> bool { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { InputRecord::Bool(value) => value, InputRecord::U64(value) => { if value == 0 { @@ -237,7 +336,7 @@ pub unsafe extern "C" fn argreader_get_bool(key_ptr: *const u8) -> bool { #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_u64(key_ptr: *const u8) -> u64 { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { InputRecord::U64(value) => value, InputRecord::I64(value) => { if value < 0 { @@ -276,7 +375,7 @@ pub unsafe extern "C" fn argreader_get_u64(key_ptr: *const u8) -> u64 { #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_i64(key_ptr: *const u8) -> i64 { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { InputRecord::I64(value) => value, InputRecord::U64(value) => { if value > i64::MAX as u64 { @@ -315,7 +414,7 @@ pub unsafe extern "C" fn argreader_get_i64(key_ptr: *const u8) -> i64 { #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_f64(key_ptr: *const u8) -> f64 { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { InputRecord::F64(value) => value, InputRecord::U64(value) => value as f64, InputRecord::I64(value) => value as f64, @@ -338,7 +437,19 @@ pub unsafe extern "C" fn argreader_get_f64(key_ptr: *const u8) -> f64 { #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_u64_array(key_ptr: *const u8, out_ptr: *mut u64, len: u64) { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { + InputRecord::BoolArray(values) => { + if values.len() != len as usize { + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} unsigned integers, but was provided a boolean array {values:?} of length {}", + values.len() + )); + } + let u64_values: Vec = values.into_iter().map(|v| if v { 1 } else { 0 }).collect(); + unsafe { + std::ptr::copy_nonoverlapping(u64_values.as_ptr(), out_ptr, u64_values.len()); + } + } InputRecord::U64Array(values) => { if values.len() != len as usize { selene_panic(format!( @@ -409,7 +520,19 @@ pub unsafe extern "C" fn argreader_get_u64_array(key_ptr: *const u8, out_ptr: *m #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_i64_array(key_ptr: *const u8, out_ptr: *mut i64, len: u64) { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { + InputRecord::BoolArray(values) => { + if values.len() != len as usize { + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} integers, but was provided a boolean array {values:?} of length {}", + values.len() + )); + } + let i64_values: Vec = values.into_iter().map(|v| if v { 1 } else { 0 }).collect(); + unsafe { + std::ptr::copy_nonoverlapping(i64_values.as_ptr(), out_ptr, i64_values.len()); + } + } InputRecord::I64Array(values) => { if values.len() != len as usize { let key = get_key(key_ptr); @@ -484,7 +607,22 @@ pub unsafe extern "C" fn argreader_get_i64_array(key_ptr: *const u8, out_ptr: *m #[unsafe(no_mangle)] pub unsafe extern "C" fn argreader_get_f64_array(key_ptr: *const u8, out_ptr: *mut f64, len: u64) { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { + InputRecord::BoolArray(values) => { + if values.len() != len as usize { + selene_panic(format!( + "Runtime argument '{key}' expects an array of {len} floats, but was provided a boolean array {values:?} of length {}", + values.len() + )); + } + let f64_values: Vec = values + .into_iter() + .map(|v| if v { 1.0 } else { 0.0 }) + .collect(); + unsafe { + std::ptr::copy_nonoverlapping(f64_values.as_ptr(), out_ptr, f64_values.len()); + } + } InputRecord::F64Array(values) => { if values.len() != len as usize { selene_panic(format!( @@ -544,7 +682,7 @@ pub unsafe extern "C" fn argreader_get_bool_array( len: u64, ) { let key = get_key(key_ptr); - match unsafe { value_helper(&key) } { + match value_helper(&key) { InputRecord::BoolArray(values) => { if values.len() != len as usize { selene_panic(format!( diff --git a/selene-sim/python/selene_sim/build.py b/selene-sim/python/selene_sim/build.py index 622e019d..1704a854 100644 --- a/selene-sim/python/selene_sim/build.py +++ b/selene-sim/python/selene_sim/build.py @@ -16,6 +16,7 @@ DEFAULT_BUILD_PLANNER, ) from selene_core.build_utils.builtins import SeleneExecutableKind +from selene_core.build_utils.utils import invoke_zig from .instance import SeleneInstance @@ -55,6 +56,8 @@ def _collect_libdeps( planner: BuildPlanner, interface: QuantumInterface | None, utilities: Sequence[Utility] | None, + artifact_dir: Path, + verbose: bool, ) -> list[LibDep]: """ Collects the library dependencies for the selene build process, @@ -66,11 +69,70 @@ def _collect_libdeps( interface = interface or HeliosInterface() deps = LibDep.from_plugin(interface) interface.register_build_steps(planner) + deps.append(_build_utility_registration_dep(artifact_dir, utilities or [], verbose)) for u in utilities or []: deps.extend(LibDep.from_plugin(u)) return deps +def _build_utility_registration_dep( + artifact_dir: Path, + utilities: Sequence[Utility], + verbose: bool, +) -> LibDep: + symbols = [ + symbol + for utility in utilities + if (symbol := utility.validate_registration_symbol()) is not None + ] + + declarations = "\n".join( + f"extern struct selene_void_result_t {symbol}(SeleneInstance *instance);" + for symbol in symbols + ) + calls = "\n".join( + f""" + result = {symbol}(instance); + if (result.error_code != 0) {{ + return result; + }}""" + for symbol in symbols + ) + source = f"""#include + +typedef struct SeleneInstance SeleneInstance; + +struct selene_void_result_t {{ + uint32_t error_code; +}}; + +{declarations} + +struct selene_void_result_t selene_register_linked_utilities(SeleneInstance *instance) {{ + struct selene_void_result_t result = {{0}}; +{calls} + return result; +}} +""" + support_dir = artifact_dir / "selene-support" + support_dir.mkdir(exist_ok=True) + source_path = support_dir / "selene_utility_registration.c" + object_path = support_dir / "selene_utility_registration.o" + zig_cache_dir = support_dir / "zig-cache" + zig_cache_dir.mkdir(exist_ok=True) + source_path.write_text(source) + invoke_zig( + "cc", + "-c", + source_path, + "-o", + object_path, + verbose=verbose, + cache_dir=zig_cache_dir, + ) + return LibDep(path=object_path) + + def build( src: Any, name: str | None = None, @@ -174,7 +236,7 @@ def build( # from the interface and utilities passed in. This is necessary for # interfaces and utilities to be able to customise the final build, # e.g. adding link path arguments. - deps = _collect_libdeps(planner, interface, utilities) + deps = _collect_libdeps(planner, interface, utilities, artifact_dir, verbose) if "build_method" not in cfg: # If the build method is not provided, default to VIA_LLVM_BITCODE @@ -207,7 +269,7 @@ def build( # a `new_shiny_platform__` prefix? input_kind = planner.identify_kind(src) if input_kind is None: - raise ValueError(f"Unknown resource type: {type(src)}") + raise ValueError(f"Unknown resource type: {type(src)}: {src}") if save_planner: _log.info("Saving planner to %s", instance_root / "planner.dot") diff --git a/selene-sim/python/tests/test_build_validation.py b/selene-sim/python/tests/test_build_validation.py index f73bd370..81a9e4dd 100644 --- a/selene-sim/python/tests/test_build_validation.py +++ b/selene-sim/python/tests/test_build_validation.py @@ -59,7 +59,7 @@ def main() -> None: guppy_source=guppy_source, ) - runner = build(llvm_file, strict=True, **build_config["kwargs"]) + runner = build(llvm_file, strict=True, **build_config["kwargs"], verbose=True) got = list(runner.run(Quest(), n_qubits=1)) assert len(got) == 0 From b50ba840e41ae5cf3e6c903ca344a876cc3ae767 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:42:25 +0100 Subject: [PATCH 10/19] refactor(quest): bind directly through a local C interface --- Cargo.lock | 12 +- selene-ext/simulators/quest/Cargo.toml | 3 +- selene-ext/simulators/quest/build.rs | 34 ++ .../quest/c_interface/CMakeLists.txt | 66 ++++ .../SeleneQuestCInterfaceConfig.cmake.in | 3 + .../selene_quest_c_interface/interface.h | 66 ++++ .../quest/c_interface/src/c_interface.cpp | 332 ++++++++++++++++++ selene-ext/simulators/quest/rust/bindings.rs | 53 +++ .../simulators/quest/rust/legacy_rng.rs | 128 +++++++ selene-ext/simulators/quest/rust/lib.rs | 262 ++++++-------- selene-ext/simulators/quest/rust/wrapper.rs | 104 ++++++ 11 files changed, 903 insertions(+), 160 deletions(-) create mode 100644 selene-ext/simulators/quest/build.rs create mode 100644 selene-ext/simulators/quest/c_interface/CMakeLists.txt create mode 100644 selene-ext/simulators/quest/c_interface/cmake/SeleneQuestCInterfaceConfig.cmake.in create mode 100644 selene-ext/simulators/quest/c_interface/include/selene_quest_c_interface/interface.h create mode 100644 selene-ext/simulators/quest/c_interface/src/c_interface.cpp create mode 100644 selene-ext/simulators/quest/rust/bindings.rs create mode 100644 selene-ext/simulators/quest/rust/legacy_rng.rs create mode 100644 selene-ext/simulators/quest/rust/wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 54fb2947..e833aa08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,15 +572,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quest-sys" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3193d99e88ae9aa9e058b786727d139e366364e5bc15fb1b9041716783d3bead" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.45" @@ -761,8 +752,7 @@ version = "0.3.0-alpha.1" dependencies = [ "anyhow", "bytesize", - "cc", - "quest-sys", + "cmake", "selene-core", "sysinfo", ] diff --git a/selene-ext/simulators/quest/Cargo.toml b/selene-ext/simulators/quest/Cargo.toml index e980ab51..fd3114b3 100644 --- a/selene-ext/simulators/quest/Cargo.toml +++ b/selene-ext/simulators/quest/Cargo.toml @@ -10,11 +10,10 @@ doctest = false crate-type = ["cdylib"] [build-dependencies] -cc = { version="1.2" } +cmake = "0.1" [dependencies] anyhow = { workspace = true } -quest-sys = {version = "0.16"} selene-core = { path = "../../../selene-core" } sysinfo = { version = "0.35" } bytesize = { version = "2.3" } diff --git a/selene-ext/simulators/quest/build.rs b/selene-ext/simulators/quest/build.rs new file mode 100644 index 00000000..e8a38786 --- /dev/null +++ b/selene-ext/simulators/quest/build.rs @@ -0,0 +1,34 @@ +fn main() { + if std::env::var("CLIPPY_ARGS").is_ok() { + println!("cargo:warning=Skipping external C build in Clippy mode"); + return; + } + + println!("cargo:rerun-if-changed=c_interface"); + let dst = cmake::Config::new("c_interface").build(); + println!( + "cargo:rustc-link-search=native={}", + dst.join("lib").display() + ); + println!( + "cargo:rustc-link-search=native={}", + dst.join("build") + .join("_deps") + .join("quest-build") + .display() + ); + println!("cargo:rustc-link-lib=static=selene_quest_c_interface"); + println!("cargo:rustc-link-lib=static=QuEST"); + + let target_triple = std::env::var("TARGET").unwrap(); + if target_triple.contains("linux-gnu") { + println!("cargo:rustc-link-lib=stdc++"); + } else if target_triple.contains("windows-gnu") { + println!("cargo:rustc-link-lib=static=stdc++"); + println!("cargo:rustc-link-lib=static=winpthread"); + println!("cargo:rustc-link-arg=-static-libstdc++"); + println!("cargo:rustc-link-arg=-static-libgcc"); + } else if target_triple.contains("apple-darwin") { + println!("cargo:rustc-link-lib=c++"); + } +} diff --git a/selene-ext/simulators/quest/c_interface/CMakeLists.txt b/selene-ext/simulators/quest/c_interface/CMakeLists.txt new file mode 100644 index 00000000..e7502835 --- /dev/null +++ b/selene-ext/simulators/quest/c_interface/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.21) +project(SeleneQuestCInterface LANGUAGES C CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_C_STANDARD 11) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(FetchContent) + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(ENABLE_MULTITHREADING OFF CACHE BOOL "" FORCE) +set(ENABLE_DISTRIBUTION OFF CACHE BOOL "" FORCE) +set(ENABLE_CUDA OFF CACHE BOOL "" FORCE) +set(ENABLE_CUQUANTUM OFF CACHE BOOL "" FORCE) +set(ENABLE_HIP OFF CACHE BOOL "" FORCE) +set(ENABLE_DEPRECATED_API OFF CACHE BOOL "" FORCE) +set(FLOAT_PRECISION 2 CACHE STRING "" FORCE) + +FetchContent_Declare( + quest + GIT_REPOSITORY https://github.com/QuEST-Kit/QuEST.git + GIT_TAG 9d7618d7263e3bfba433b88cf1eac0647f08fa0a + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(quest) + +add_library(selene_quest_c_interface STATIC + src/c_interface.cpp +) + +target_include_directories(selene_quest_c_interface PUBLIC + "$" + "$" +) +target_link_libraries(selene_quest_c_interface PRIVATE QuEST::QuEST) + +install(TARGETS selene_quest_c_interface + EXPORT SeleneQuestCInterfaceTargets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) + +install(DIRECTORY include/selene_quest_c_interface + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(EXPORT SeleneQuestCInterfaceTargets + FILE SeleneQuestCInterfaceTargets.cmake + NAMESPACE SeleneQuest:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SeleneQuestCInterface +) + +configure_package_config_file( + cmake/SeleneQuestCInterfaceConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/SeleneQuestCInterfaceConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SeleneQuestCInterface +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/SeleneQuestCInterfaceConfig.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/SeleneQuestCInterface +) diff --git a/selene-ext/simulators/quest/c_interface/cmake/SeleneQuestCInterfaceConfig.cmake.in b/selene-ext/simulators/quest/c_interface/cmake/SeleneQuestCInterfaceConfig.cmake.in new file mode 100644 index 00000000..f21fd0b6 --- /dev/null +++ b/selene-ext/simulators/quest/c_interface/cmake/SeleneQuestCInterfaceConfig.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/SeleneQuestCInterfaceTargets.cmake") diff --git a/selene-ext/simulators/quest/c_interface/include/selene_quest_c_interface/interface.h b/selene-ext/simulators/quest/c_interface/include/selene_quest_c_interface/interface.h new file mode 100644 index 00000000..87cec743 --- /dev/null +++ b/selene-ext/simulators/quest/c_interface/include/selene_quest_c_interface/interface.h @@ -0,0 +1,66 @@ +#ifndef SELENE_QUEST_C_INTERFACE_H +#define SELENE_QUEST_C_INTERFACE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SeleneQuestSimulator SeleneQuestSimulator; + +const char* selene_quest_last_error(void); +void selene_quest_clear_error(void); + +bool selene_quest_create(uint32_t num_qubits, SeleneQuestSimulator** out); +void selene_quest_destroy(SeleneQuestSimulator* sim); + +bool selene_quest_init_zero_state(SeleneQuestSimulator* sim); +bool selene_quest_apply_rotate_z(SeleneQuestSimulator* sim, uint32_t q, double theta); +bool selene_quest_apply_pauli_x(SeleneQuestSimulator* sim, uint32_t q); +bool selene_quest_apply_matrix1( + SeleneQuestSimulator* sim, + uint32_t q, + const double* real, + const double* imag +); +bool selene_quest_apply_matrix2( + SeleneQuestSimulator* sim, + uint32_t q0, + uint32_t q1, + const double* real, + const double* imag +); +bool selene_quest_apply_diag_matrix2( + SeleneQuestSimulator* sim, + uint32_t q0, + uint32_t q1, + const double* real, + const double* imag +); +bool selene_quest_prob_of_outcome( + SeleneQuestSimulator* sim, + uint32_t q, + bool outcome, + double* out +); +bool selene_quest_collapse_to_outcome( + SeleneQuestSimulator* sim, + uint32_t q, + bool outcome, + double* probability +); +bool selene_quest_get_amp( + const SeleneQuestSimulator* sim, + uint64_t index, + double* real, + double* imag +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/selene-ext/simulators/quest/c_interface/src/c_interface.cpp b/selene-ext/simulators/quest/c_interface/src/c_interface.cpp new file mode 100644 index 00000000..8fabec92 --- /dev/null +++ b/selene-ext/simulators/quest/c_interface/src/c_interface.cpp @@ -0,0 +1,332 @@ +#include "selene_quest_c_interface/interface.h" + +#include "quest.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +thread_local std::string last_error; + +std::mutex env_mutex; +bool env_started = false; + +class QuestError : public std::runtime_error { + public: + QuestError(const char* func, const char* msg) + : std::runtime_error(format(func, msg)) {} + + private: + static std::string format(const char* func, const char* msg) { + std::string out = func == nullptr ? "unknown QuEST function" : func; + out += ": "; + out += msg == nullptr ? "unknown QuEST error" : msg; + return out; + } +}; + +void error_handler(const char* func, const char* msg) { + throw QuestError(func, msg); +} + +void set_error(const char* msg) { + last_error = msg == nullptr ? "unknown QuEST error" : msg; +} + +void set_error(const std::exception& err) { + last_error = err.what(); +} + +void set_error_bad_alloc() { + last_error = "QuEST allocation failed"; +} + +template +bool capture_errors(F&& f) { + try { + last_error.clear(); + f(); + return true; + } catch (const std::bad_alloc&) { + set_error_bad_alloc(); + return false; + } catch (const std::exception& err) { + set_error(err); + return false; + } catch (...) { + set_error("unknown QuEST error"); + return false; + } +} + +qcomp make_qcomp(double real, double imag) { + return qcomp(static_cast(real), static_cast(imag)); +} + +CompMatr1 make_matrix1(const double* real, const double* imag) { + qcomp elems[2][2] = { + {make_qcomp(real[0], imag[0]), make_qcomp(real[1], imag[1])}, + {make_qcomp(real[2], imag[2]), make_qcomp(real[3], imag[3])}, + }; + return getCompMatr1(elems); +} + +CompMatr2 make_matrix2(const double* real, const double* imag) { + qcomp elems[4][4] = { + { + make_qcomp(real[0], imag[0]), + make_qcomp(real[1], imag[1]), + make_qcomp(real[2], imag[2]), + make_qcomp(real[3], imag[3]), + }, + { + make_qcomp(real[4], imag[4]), + make_qcomp(real[5], imag[5]), + make_qcomp(real[6], imag[6]), + make_qcomp(real[7], imag[7]), + }, + { + make_qcomp(real[8], imag[8]), + make_qcomp(real[9], imag[9]), + make_qcomp(real[10], imag[10]), + make_qcomp(real[11], imag[11]), + }, + { + make_qcomp(real[12], imag[12]), + make_qcomp(real[13], imag[13]), + make_qcomp(real[14], imag[14]), + make_qcomp(real[15], imag[15]), + }, + }; + return getCompMatr2(elems); +} + +DiagMatr2 make_diag_matrix2(const double* real, const double* imag) { + qcomp elems[4] = { + make_qcomp(real[0], imag[0]), + make_qcomp(real[1], imag[1]), + make_qcomp(real[2], imag[2]), + make_qcomp(real[3], imag[3]), + }; + return getDiagMatr2(elems); +} + +bool init_environment() { + std::lock_guard guard(env_mutex); + return capture_errors([] { + if (!env_started) { + initQuESTEnv(); + env_started = true; + } + setInputErrorHandler(error_handler); + }); +} + +bool require_sim(const SeleneQuestSimulator* sim, const char* func) { + if (sim != nullptr) { + return true; + } + std::string message = func; + message += " received a null simulator pointer"; + set_error(message.c_str()); + return false; +} + +} // namespace + +struct SeleneQuestSimulator { + Qureg qureg; +}; + +extern "C" { + +const char* selene_quest_last_error(void) { + return last_error.c_str(); +} + +void selene_quest_clear_error(void) { + last_error.clear(); +} + +bool selene_quest_create(uint32_t num_qubits, SeleneQuestSimulator** out) { + if (out == nullptr) { + set_error("selene_quest_create received a null output pointer"); + return false; + } + *out = nullptr; + + if (!init_environment()) { + return false; + } + + bool ok = capture_errors([&] { + auto* sim = new SeleneQuestSimulator{createQureg(static_cast(num_qubits))}; + *out = sim; + }); + + if (!ok) { + delete *out; + *out = nullptr; + } + return ok; +} + +void selene_quest_destroy(SeleneQuestSimulator* sim) { + if (sim == nullptr) { + return; + } + capture_errors([&] { + destroyQureg(sim->qureg); + delete sim; + }); +} + +bool selene_quest_init_zero_state(SeleneQuestSimulator* sim) { + if (!require_sim(sim, __func__)) { + return false; + } + return capture_errors([&] { initZeroState(sim->qureg); }); +} + +bool selene_quest_apply_rotate_z(SeleneQuestSimulator* sim, uint32_t q, double theta) { + if (!require_sim(sim, __func__)) { + return false; + } + return capture_errors([&] { applyRotateZ(sim->qureg, static_cast(q), theta); }); +} + +bool selene_quest_apply_pauli_x(SeleneQuestSimulator* sim, uint32_t q) { + if (!require_sim(sim, __func__)) { + return false; + } + return capture_errors([&] { applyPauliX(sim->qureg, static_cast(q)); }); +} + +bool selene_quest_apply_matrix1( + SeleneQuestSimulator* sim, + uint32_t q, + const double* real, + const double* imag +) { + if (!require_sim(sim, __func__)) { + return false; + } + if (real == nullptr || imag == nullptr) { + set_error("selene_quest_apply_matrix1 received a null matrix pointer"); + return false; + } + return capture_errors([&] { + applyCompMatr1(sim->qureg, static_cast(q), make_matrix1(real, imag)); + }); +} + +bool selene_quest_apply_matrix2( + SeleneQuestSimulator* sim, + uint32_t q0, + uint32_t q1, + const double* real, + const double* imag +) { + if (!require_sim(sim, __func__)) { + return false; + } + if (real == nullptr || imag == nullptr) { + set_error("selene_quest_apply_matrix2 received a null matrix pointer"); + return false; + } + return capture_errors([&] { + applyCompMatr2( + sim->qureg, + static_cast(q0), + static_cast(q1), + make_matrix2(real, imag) + ); + }); +} + +bool selene_quest_apply_diag_matrix2( + SeleneQuestSimulator* sim, + uint32_t q0, + uint32_t q1, + const double* real, + const double* imag +) { + if (!require_sim(sim, __func__)) { + return false; + } + if (real == nullptr || imag == nullptr) { + set_error("selene_quest_apply_diag_matrix2 received a null matrix pointer"); + return false; + } + return capture_errors([&] { + applyDiagMatr2( + sim->qureg, + static_cast(q0), + static_cast(q1), + make_diag_matrix2(real, imag) + ); + }); +} + +bool selene_quest_prob_of_outcome( + SeleneQuestSimulator* sim, + uint32_t q, + bool outcome, + double* out +) { + if (!require_sim(sim, __func__)) { + return false; + } + if (out == nullptr) { + set_error("selene_quest_prob_of_outcome received a null output pointer"); + return false; + } + return capture_errors([&] { + *out = calcProbOfQubitOutcome(sim->qureg, static_cast(q), outcome ? 1 : 0); + }); +} + +bool selene_quest_collapse_to_outcome( + SeleneQuestSimulator* sim, + uint32_t q, + bool outcome, + double* probability +) { + if (!require_sim(sim, __func__)) { + return false; + } + return capture_errors([&] { + qreal prob = applyForcedQubitMeasurement(sim->qureg, static_cast(q), outcome ? 1 : 0); + if (probability != nullptr) { + *probability = prob; + } + }); +} + +bool selene_quest_get_amp( + const SeleneQuestSimulator* sim, + uint64_t index, + double* real, + double* imag +) { + if (!require_sim(sim, __func__)) { + return false; + } + if (real == nullptr || imag == nullptr) { + set_error("selene_quest_get_amp received a null output pointer"); + return false; + } + return capture_errors([&] { + qcomp amp = getQuregAmp(sim->qureg, static_cast(index)); + *real = std::real(amp); + *imag = std::imag(amp); + }); +} + +} // extern "C" diff --git a/selene-ext/simulators/quest/rust/bindings.rs b/selene-ext/simulators/quest/rust/bindings.rs new file mode 100644 index 00000000..42bf7694 --- /dev/null +++ b/selene-ext/simulators/quest/rust/bindings.rs @@ -0,0 +1,53 @@ +#![allow(unused)] + +use std::os::raw::{c_char, c_double, c_void}; + +unsafe extern "C" { + pub fn selene_quest_last_error() -> *const c_char; + pub fn selene_quest_clear_error(); + + pub fn selene_quest_create(num_qubits: u32, out: *mut *mut c_void) -> bool; + pub fn selene_quest_destroy(sim: *mut c_void); + + pub fn selene_quest_init_zero_state(sim: *mut c_void) -> bool; + pub fn selene_quest_apply_rotate_z(sim: *mut c_void, q: u32, theta: c_double) -> bool; + pub fn selene_quest_apply_pauli_x(sim: *mut c_void, q: u32) -> bool; + pub fn selene_quest_apply_matrix1( + sim: *mut c_void, + q: u32, + real: *const c_double, + imag: *const c_double, + ) -> bool; + pub fn selene_quest_apply_matrix2( + sim: *mut c_void, + q0: u32, + q1: u32, + real: *const c_double, + imag: *const c_double, + ) -> bool; + pub fn selene_quest_apply_diag_matrix2( + sim: *mut c_void, + q0: u32, + q1: u32, + real: *const c_double, + imag: *const c_double, + ) -> bool; + pub fn selene_quest_prob_of_outcome( + sim: *mut c_void, + q: u32, + outcome: bool, + out: *mut c_double, + ) -> bool; + pub fn selene_quest_collapse_to_outcome( + sim: *mut c_void, + q: u32, + outcome: bool, + probability: *mut c_double, + ) -> bool; + pub fn selene_quest_get_amp( + sim: *const c_void, + index: u64, + real: *mut c_double, + imag: *mut c_double, + ) -> bool; +} diff --git a/selene-ext/simulators/quest/rust/legacy_rng.rs b/selene-ext/simulators/quest/rust/legacy_rng.rs new file mode 100644 index 00000000..522cc487 --- /dev/null +++ b/selene-ext/simulators/quest/rust/legacy_rng.rs @@ -0,0 +1,128 @@ +const N: usize = 624; +const M: usize = 397; +const MATRIX_A: u64 = 0x9908_b0df; +const UPPER_MASK: u64 = 0x8000_0000; +const LOWER_MASK: u64 = 0x7fff_ffff; +const WORD_MASK: u64 = 0xffff_ffff; + +/// Reproduces the old QuEST MT19937 measurement RNG on 64-bit Unix platforms. +/// +/// The old plugin passed Selene's `u64` shot seed as a single C `unsigned long`. +/// QuEST then ran the reference MT19937 implementation whose state is stored in +/// `unsigned long` but masked to 32 bits after each state update. +pub struct LegacyQuestRng { + mt: [u64; N], + mti: usize, +} + +impl LegacyQuestRng { + pub fn seed_from_u64(seed: u64) -> Self { + let mut rng = Self { + mt: [0; N], + mti: N + 1, + }; + rng.init_by_array(&[seed]); + rng + } + + fn init_genrand(&mut self, seed: u64) { + self.mt[0] = seed & WORD_MASK; + for i in 1..N { + self.mt[i] = (1_812_433_253_u64 + .wrapping_mul(self.mt[i - 1] ^ (self.mt[i - 1] >> 30)) + .wrapping_add(i as u64)) + & WORD_MASK; + } + self.mti = N; + } + + fn init_by_array(&mut self, keys: &[u64]) { + self.init_genrand(19_650_218); + let mut i = 1; + let mut j = 0; + for _ in 0..N.max(keys.len()) { + self.mt[i] = ((self.mt[i] + ^ (self.mt[i - 1] ^ (self.mt[i - 1] >> 30)).wrapping_mul(1_664_525)) + .wrapping_add(keys[j]) + .wrapping_add(j as u64)) + & WORD_MASK; + i += 1; + j += 1; + if i >= N { + self.mt[0] = self.mt[N - 1]; + i = 1; + } + if j >= keys.len() { + j = 0; + } + } + for _ in 0..(N - 1) { + self.mt[i] = ((self.mt[i] + ^ (self.mt[i - 1] ^ (self.mt[i - 1] >> 30)).wrapping_mul(1_566_083_941)) + .wrapping_sub(i as u64)) + & WORD_MASK; + i += 1; + if i >= N { + self.mt[0] = self.mt[N - 1]; + i = 1; + } + } + self.mt[0] = UPPER_MASK; + } + + fn genrand_int32(&mut self) -> u64 { + if self.mti >= N { + if self.mti == N + 1 { + self.init_genrand(5489); + } + + for kk in 0..(N - M) { + let y = (self.mt[kk] & UPPER_MASK) | (self.mt[kk + 1] & LOWER_MASK); + self.mt[kk] = self.mt[kk + M] ^ (y >> 1) ^ if y & 1 == 0 { 0 } else { MATRIX_A }; + } + for kk in (N - M)..(N - 1) { + let y = (self.mt[kk] & UPPER_MASK) | (self.mt[kk + 1] & LOWER_MASK); + self.mt[kk] = + self.mt[kk + M - N] ^ (y >> 1) ^ if y & 1 == 0 { 0 } else { MATRIX_A }; + } + let y = (self.mt[N - 1] & UPPER_MASK) | (self.mt[0] & LOWER_MASK); + self.mt[N - 1] = self.mt[M - 1] ^ (y >> 1) ^ if y & 1 == 0 { 0 } else { MATRIX_A }; + self.mti = 0; + } + + let mut y = self.mt[self.mti]; + self.mti += 1; + + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c_5680; + y ^= (y << 15) & 0xefc6_0000; + y ^= y >> 18; + y & WORD_MASK + } + + pub fn genrand_real1(&mut self) -> f64 { + self.genrand_int32() as f64 * (1.0 / 4_294_967_295.0) + } +} + +#[cfg(test)] +mod tests { + use super::LegacyQuestRng; + + #[test] + fn matches_legacy_quest_linux_sequence() { + let mut rng = LegacyQuestRng::seed_from_u64(1_234_567_890_123_456_789); + assert_eq!(rng.genrand_int32(), 2_637_397_608); + assert_eq!(rng.genrand_int32(), 835_775_448); + assert_eq!(rng.genrand_int32(), 1_010_521_472); + assert_eq!(rng.genrand_int32(), 3_788_146_913); + assert_eq!(rng.genrand_int32(), 4_227_692_770); + + let mut rng = LegacyQuestRng::seed_from_u64(1_234_567_890_123_456_789); + assert_eq!(rng.genrand_real1(), 0.6140669827847898); + assert_eq!(rng.genrand_real1(), 0.19459413555324873); + assert_eq!(rng.genrand_real1(), 0.23528036480659628); + assert_eq!(rng.genrand_real1(), 0.8819966842145651); + assert_eq!(rng.genrand_real1(), 0.9843364290390947); + } +} diff --git a/selene-ext/simulators/quest/rust/lib.rs b/selene-ext/simulators/quest/rust/lib.rs index 83fd5cd5..80c998bb 100644 --- a/selene-ext/simulators/quest/rust/lib.rs +++ b/selene-ext/simulators/quest/rust/lib.rs @@ -1,94 +1,44 @@ -/// Quest simulator plugin for Selene, implemented using the quest_sys crate. -// -// The version of quest_sys used here is only valid up until 0.17, after which -// the crate removed support for almost everything we use. It is likely that we -// will need to roll our own support later on, or move to using the QuEST C API -// directly if we wish to continue using QuEST as a simulator backend. +/// QuEST simulator plugin for Selene. // // Definitions of the various gates implemented here can be found in the accompanying // gate_definitions.py file, which provides the matrices used for each gate, as well // as giving their real/imaginary parts for simplicity. The outputs are provided // in the comments within the implementation of each gate within this source file. +mod bindings; +mod legacy_rng; +mod wrapper; + use anyhow::{Result, anyhow, bail}; +use legacy_rng::LegacyQuestRng; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; +use selene_core::gatewire::builtin; +use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::utils::MetricValue; use std::io::Write; - -use quest_sys::Qureg; -#[cfg(all(target_os = "windows", target_env = "gnu"))] -use std::ffi::{CStr, c_char}; -use std::mem::size_of; -use std::os::raw::{c_int, c_ulong}; +use wrapper::QuestBackend; #[cfg(test)] mod tests; -#[cfg(all(target_os = "windows", target_env = "gnu"))] -#[unsafe(no_mangle)] -pub unsafe extern "C" fn invalidQuESTInputError(err_msg: *const c_char, err_func: *const c_char) { - let err_msg = if err_msg.is_null() { - "Unknown QuEST error" - } else { - // SAFETY: `err_msg` is expected to be a valid null-terminated C string from QuEST. - CStr::from_ptr(err_msg) - .to_str() - .unwrap_or("Invalid UTF-8 in QuEST error message") - }; - let err_func = if err_func.is_null() { - "unknown" - } else { - // SAFETY: `err_func` is expected to be a valid null-terminated C string from QuEST. - CStr::from_ptr(err_func) - .to_str() - .unwrap_or("Invalid UTF-8 in QuEST function name") - }; - eprintln!("!!!"); - eprintln!("QuEST Error in function {err_func}: {err_msg}"); - eprintln!("!!!"); - eprintln!("Exiting..."); - std::process::exit(1); -} - pub struct QuestSimulator { - environment: quest_sys::QuESTEnv, - qureg: Qureg, + backend: QuestBackend, + rng: LegacyQuestRng, n_qubits: u64, cumulative_postselect_probability: f64, } -impl QuestSimulator { - fn seed(&mut self, seed: u64) { - // seedQuest accepts an array of 'c_ulong's, so we need to split the - // provided seed accordingly. - // - // c_ulong does not have a standard size, so we find out how many c_ulongs we - // need and populate them with (low endian) bytes from the seed. - const N_ULONGS: usize = size_of::().div_ceil(size_of::()); - let mut seed_bytes = [0_u8; N_ULONGS * size_of::()]; - seed_bytes[..size_of::()].copy_from_slice(&seed.to_le_bytes()); - unsafe { - quest_sys::seedQuEST( - &mut self.environment, - seed_bytes.as_mut_ptr() as *mut c_ulong, - N_ULONGS as i32, - ); - } - } -} - impl SimulatorInterface for QuestSimulator { fn exit(&mut self) -> Result<()> { Ok(()) } fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { - unsafe { quest_sys::initClassicalState(self.qureg, 0) }; + self.backend.init_zero_state()?; self.cumulative_postselect_probability = 1.0; - self.seed(seed); + self.rng = LegacyQuestRng::seed_from_u64(seed); Ok(()) } @@ -100,26 +50,30 @@ impl SimulatorInterface for QuestSimulator { let mut results = BatchResult::default(); for operation in operations { match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, + Operation::Gate { .. } => { + match operation.as_gate_view::()? { + Some(builtin::QuantinuumGate::PhasedX { + qubit_id, + theta, + phi, + }) => self.phased_x(qubit_id, theta, phi)?, + Some(builtin::QuantinuumGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, + Some(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.rz(qubit_id, theta)? + } + Some(builtin::QuantinuumGate::PhasedXX { + qubit_id_1, + qubit_id_2, + theta, + phi, + }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, + None => {} + } + } Operation::Measure { qubit_id, result_id, @@ -129,6 +83,10 @@ impl SimulatorInterface for QuestSimulator { result_id, } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), Operation::Reset { qubit_id } => self.reset(qubit_id)?, + Operation::Postselect { + qubit_id, + target_value, + } => self.do_postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } @@ -155,8 +113,7 @@ impl QuestSimulator { )) } else { // Use the built-in from QuEST - unsafe { quest_sys::rotateZ(self.qureg, q0 as c_int, theta) }; - Ok(()) + self.backend.rotate_z(q0 as u32, theta) } } @@ -193,15 +150,14 @@ impl QuestSimulator { let sin_theta_2 = (theta / 2.0).sin(); let cos_phi = phi.cos(); let sin_phi = phi.sin(); - let u = quest_sys::ComplexMatrix2 { - real: [ - [cos_theta_2, -sin_phi * sin_theta_2], - [sin_phi * sin_theta_2, cos_theta_2], - ], - imag: [[0.0, -sin_theta_2 * cos_phi], [-sin_theta_2 * cos_phi, 0.0]], - }; - unsafe { quest_sys::unitary(self.qureg, q0 as c_int, u) }; - Ok(()) + let real = [ + cos_theta_2, + -sin_phi * sin_theta_2, + sin_phi * sin_theta_2, + cos_theta_2, + ]; + let imag = [0.0, -sin_theta_2 * cos_phi, -sin_theta_2 * cos_phi, 0.0]; + self.backend.matrix1(q0 as u32, &real, &imag) } } @@ -238,24 +194,17 @@ impl QuestSimulator { // We implement this using a sub-diagonal operator in QuEST. let cos = theta.cos(); let sin = theta.sin(); - let mut targets: [c_int; 2] = [q0 as c_int, q1 as c_int]; let diag_real: [f64; 4] = [1.0, cos, cos, 1.0]; let diag_imag: [f64; 4] = [0.0, sin, sin, 0.0]; - unsafe { - let op = quest_sys::createSubDiagonalOp(2); - std::ptr::copy_nonoverlapping(diag_real.as_ptr(), op.real, 4); - std::ptr::copy_nonoverlapping(diag_imag.as_ptr(), op.imag, 4); - quest_sys::applySubDiagonalOp(self.qureg, targets.as_mut_ptr(), 2, op); - quest_sys::destroySubDiagonalOp(op); - } - Ok(()) + self.backend + .diag_matrix2(q0 as u32, q1 as u32, &diag_real, &diag_imag) } } fn phased_xx(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { - if q0 >= self.n_qubits { + if q0 >= self.n_qubits || q1 >= self.n_qubits { Err(anyhow!( - "PhasedXX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", + "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", self.n_qubits )) } else { @@ -300,22 +249,43 @@ impl QuestSimulator { let sin_theta_2 = (theta / 2.0).sin(); let cos_2phi = (2.0 * phi).cos(); let sin_2phi = (2.0 * phi).sin(); - let u = quest_sys::ComplexMatrix4 { - real: [ - [cos_theta_2, 0.0, 0.0, -sin_2phi * sin_theta_2], - [0.0, cos_theta_2, 0.0, 0.0], - [0.0, 0.0, cos_theta_2, 0.0], - [sin_2phi * sin_theta_2, 0.0, 0.0, cos_theta_2], - ], - imag: [ - [0.0, 0.0, 0.0, -sin_theta_2 * cos_2phi], - [0.0, 0.0, -sin_theta_2, 0.0], - [0.0, -sin_theta_2, 0.0, 0.0], - [-sin_theta_2 * cos_2phi, 0.0, 0.0, 0.0], - ], - }; - unsafe { quest_sys::twoQubitUnitary(self.qureg, q0 as c_int, q1 as c_int, u) }; - Ok(()) + let real = [ + cos_theta_2, + 0.0, + 0.0, + -sin_2phi * sin_theta_2, + 0.0, + cos_theta_2, + 0.0, + 0.0, + 0.0, + 0.0, + cos_theta_2, + 0.0, + sin_2phi * sin_theta_2, + 0.0, + 0.0, + cos_theta_2, + ]; + let imag = [ + 0.0, + 0.0, + 0.0, + -sin_theta_2 * cos_2phi, + 0.0, + 0.0, + -sin_theta_2, + 0.0, + 0.0, + -sin_theta_2, + 0.0, + 0.0, + -sin_theta_2 * cos_2phi, + 0.0, + 0.0, + 0.0, + ]; + self.backend.matrix2(q0 as u32, q1 as u32, &real, &imag) } } @@ -326,7 +296,17 @@ impl QuestSimulator { self.n_qubits )) } else { - Ok(unsafe { quest_sys::measure(self.qureg, q0 as i32) } > 0) + const REAL_EPS: f64 = 1e-13; + let probability_zero = self.backend.prob_of_outcome(q0 as u32, false)?; + let outcome = if probability_zero < REAL_EPS { + true + } else if 1.0 - probability_zero < REAL_EPS { + false + } else { + self.rng.genrand_real1() > probability_zero + }; + self.backend.collapse_to_outcome(q0 as u32, outcome)?; + Ok(outcome) } } @@ -337,26 +317,14 @@ impl QuestSimulator { self.n_qubits )) } else { - let target_value = if target_value { 1 } else { 0 }; - unsafe { - quest_sys::applyProjector(self.qureg, q0 as i32, target_value); - } - let postselect_probability = unsafe { quest_sys::calcTotalProb(self.qureg) }; + let postselect_probability = + self.backend.collapse_to_outcome(q0 as u32, target_value)?; self.cumulative_postselect_probability *= postselect_probability; if postselect_probability < 1e-10 { return Err(anyhow!( "Postselection of {target_value} on qubit {q0} is too unlikely to postselect. The probability of this outcome is {postselect_probability:.2e}.", )); } - let scale = 1.0 / postselect_probability.sqrt(); - // Rescale the state vector to maintain normalization - let mat = quest_sys::ComplexMatrix2 { - real: [[scale, 0.0], [0.0, scale]], - imag: [[0.0, 0.0], [0.0, 0.0]], - }; - unsafe { - quest_sys::applyMatrix2(self.qureg, q0 as i32, mat); - } Ok(()) } } @@ -368,9 +336,9 @@ impl QuestSimulator { self.n_qubits )) } else { - let outcome = unsafe { quest_sys::measure(self.qureg, q0 as i32) }; - if outcome == 1 { - unsafe { quest_sys::pauliX(self.qureg, q0 as i32) }; + let outcome = self.measure(q0)?; + if outcome { + self.backend.pauli_x(q0 as u32)?; } Ok(()) } @@ -393,11 +361,8 @@ impl QuestSimulator { for &q in qubits { writer.write_all(q.to_le_bytes().as_slice())?; } - let reals: *const f64 = self.qureg.stateVec.real; - let imags: *const f64 = self.qureg.stateVec.imag; for i in 0..(1 << self.n_qubits) { - let real: f64 = unsafe { *reals.add(i as usize) }; - let imag: f64 = unsafe { *imags.add(i as usize) }; + let (real, imag) = self.backend.amp(i)?; writer.write_all(real.to_le_bytes().as_slice())?; writer.write_all(imag.to_le_bytes().as_slice())?; } @@ -444,6 +409,10 @@ fn check_memory(n_qubits: u64) -> Result<()> { impl SimulatorInterfaceFactory for QuestSimulatorFactory { type Interface = QuestSimulator; + fn name(&self) -> &str { + "Quest" + } + fn init( self: std::sync::Arc, n_qubits: u64, @@ -458,11 +427,10 @@ impl SimulatorInterfaceFactory for QuestSimulatorFactory { ); } check_memory(n_qubits)?; - let environment = unsafe { quest_sys::createQuESTEnv() }; - let qureg = unsafe { quest_sys::createQureg(n_qubits.try_into().unwrap(), environment) }; + let backend = QuestBackend::new(n_qubits.try_into().unwrap())?; Ok(Box::new(QuestSimulator { - environment, - qureg, + backend, + rng: LegacyQuestRng::seed_from_u64(0), n_qubits, cumulative_postselect_probability: 1.0, })) diff --git a/selene-ext/simulators/quest/rust/wrapper.rs b/selene-ext/simulators/quest/rust/wrapper.rs new file mode 100644 index 00000000..d6b7683a --- /dev/null +++ b/selene-ext/simulators/quest/rust/wrapper.rs @@ -0,0 +1,104 @@ +use crate::bindings; +use anyhow::{Result, anyhow}; +use std::ffi::CStr; + +pub struct QuestBackend { + ptr: *mut std::ffi::c_void, +} + +impl QuestBackend { + pub fn new(num_qubits: u32) -> Result { + let mut ptr = std::ptr::null_mut(); + check(unsafe { bindings::selene_quest_create(num_qubits, &mut ptr) })?; + if ptr.is_null() { + return Err(anyhow!("QuEST returned a null simulator handle")); + } + Ok(Self { ptr }) + } + + pub fn init_zero_state(&mut self) -> Result<()> { + check(unsafe { bindings::selene_quest_init_zero_state(self.ptr) }) + } + + pub fn rotate_z(&mut self, q: u32, theta: f64) -> Result<()> { + check(unsafe { bindings::selene_quest_apply_rotate_z(self.ptr, q, theta) }) + } + + pub fn pauli_x(&mut self, q: u32) -> Result<()> { + check(unsafe { bindings::selene_quest_apply_pauli_x(self.ptr, q) }) + } + + pub fn matrix1(&mut self, q: u32, real: &[f64; 4], imag: &[f64; 4]) -> Result<()> { + check(unsafe { + bindings::selene_quest_apply_matrix1(self.ptr, q, real.as_ptr(), imag.as_ptr()) + }) + } + + pub fn matrix2(&mut self, q0: u32, q1: u32, real: &[f64; 16], imag: &[f64; 16]) -> Result<()> { + check(unsafe { + bindings::selene_quest_apply_matrix2(self.ptr, q0, q1, real.as_ptr(), imag.as_ptr()) + }) + } + + pub fn diag_matrix2( + &mut self, + q0: u32, + q1: u32, + real: &[f64; 4], + imag: &[f64; 4], + ) -> Result<()> { + check(unsafe { + bindings::selene_quest_apply_diag_matrix2( + self.ptr, + q0, + q1, + real.as_ptr(), + imag.as_ptr(), + ) + }) + } + + pub fn prob_of_outcome(&mut self, q: u32, outcome: bool) -> Result { + let mut probability = 0.0; + check(unsafe { + bindings::selene_quest_prob_of_outcome(self.ptr, q, outcome, &mut probability) + })?; + Ok(probability) + } + + pub fn collapse_to_outcome(&mut self, q: u32, outcome: bool) -> Result { + let mut probability = 0.0; + check(unsafe { + bindings::selene_quest_collapse_to_outcome(self.ptr, q, outcome, &mut probability) + })?; + Ok(probability) + } + + pub fn amp(&self, index: u64) -> Result<(f64, f64)> { + let mut real = 0.0; + let mut imag = 0.0; + check(unsafe { bindings::selene_quest_get_amp(self.ptr, index, &mut real, &mut imag) })?; + Ok((real, imag)) + } +} + +impl Drop for QuestBackend { + fn drop(&mut self) { + unsafe { bindings::selene_quest_destroy(self.ptr) }; + } +} + +fn check(ok: bool) -> Result<()> { + if ok { + return Ok(()); + } + let message = unsafe { + let ptr = bindings::selene_quest_last_error(); + if ptr.is_null() { + "unknown QuEST error".to_string() + } else { + CStr::from_ptr(ptr).to_string_lossy().into_owned() + } + }; + Err(anyhow!(message)) +} From 16cc0e547c17a5a742ed51e5d7fd4ff0811eca6e Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:42:30 +0100 Subject: [PATCH 11/19] test(example): exercise the Clifford T stack with plugin descriptors --- .../cpp/src/qrack_simulator.cpp | 222 ++++++++++++------ examples/clifford_t_stack/src/error_model.rs | 13 + examples/clifford_t_stack/src/runtime.rs | 4 + examples/clifford_t_stack/src/simulator.rs | 4 + 4 files changed, 167 insertions(+), 76 deletions(-) diff --git a/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp b/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp index 97875029..15f1e4ce 100644 --- a/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp +++ b/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -193,14 +192,18 @@ GateIds parse_gateset(const uint8_t* data, size_t len) { return ids; } +thread_local std::string last_error_message; + int fail(const char* context, const std::exception& error) { - std::cerr << "selene_example_clifford_t_qrack: " << context << ": " << error.what() << "\n"; + last_error_message = + std::string("selene_example_clifford_t_qrack: ") + context + ": " + error.what(); return -1; } template int wrap_errno(const char* context, Fn fn) { try { fn(); + last_error_message.clear(); return 0; } catch (const std::exception& error) { return fail(context, error); @@ -220,6 +223,21 @@ using namespace selene_example_qrack; extern "C" const char* qrack_get_name() { return "Qrack Clifford+T stabilizer-hybrid simulator"; } +extern "C" SeleneErrno qrack_last_error(char* output, size_t output_len, size_t* written) { + if (written == nullptr) { + return -1; + } + *written = last_error_message.size(); + if (output == nullptr) { + return 0; + } + if (output_len < last_error_message.size()) { + return -1; + } + std::memcpy(output, last_error_message.data(), last_error_message.size()); + return 0; +} + extern "C" SeleneErrno qrack_init( SeleneSimulatorInstance* handle, uint64_t n_qubits, uint32_t, const char* const*) { return wrap_errno("init", [&]() { @@ -255,72 +273,130 @@ extern "C" SeleneErrno qrack_shot_end(SeleneSimulatorInstance handle) { return wrap_errno("shot_end", [&]() { ensure_no_engine_fallback(instance(handle)); }); } -extern "C" SeleneErrno qrack_gate(SeleneSimulatorInstance handle, const uint8_t* data, size_t len) { - return wrap_errno("gate", [&]() { - auto& simulator = instance(handle); - invalidate_measured_register_sample(simulator); - auto gate = decode_gate(data, len); - GwSemanticId gate_id{}; - check_gw(gw_decoded_gate_semantic_id(gate.get(), &gate_id), "read gate semantic id"); - - if (id_eq(simulator.ids.h, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->H(static_cast(q)); - } else if (id_eq(simulator.ids.s, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->S(static_cast(q)); - } else if (id_eq(simulator.ids.sdg, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->IS(static_cast(q)); - } else if (id_eq(simulator.ids.t, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->T(static_cast(q)); - } else if (id_eq(simulator.ids.tdg, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->IT(static_cast(q)); - } else if (id_eq(simulator.ids.x, gate_id)) { - const auto q = qubit_operand(gate.get(), 0); - ensure_qubit(simulator, q); - simulator.sim->X(static_cast(q)); - } else if (id_eq(simulator.ids.cnot, gate_id)) { - const auto control = qubit_operand(gate.get(), 0); - const auto target = qubit_operand(gate.get(), 1); - ensure_qubit(simulator, control); - ensure_qubit(simulator, target); - simulator.sim->CNOT( - static_cast(control), static_cast(target)); - } else { - throw std::runtime_error("gate is not in the negotiated Clifford+T gateset"); - } +void apply_gate(QrackSimulator& simulator, const uint8_t* data, size_t len) { + invalidate_measured_register_sample(simulator); + auto gate = decode_gate(data, len); + GwSemanticId gate_id{}; + check_gw(gw_decoded_gate_semantic_id(gate.get(), &gate_id), "read gate semantic id"); + + if (id_eq(simulator.ids.h, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->H(static_cast(q)); + } else if (id_eq(simulator.ids.s, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->S(static_cast(q)); + } else if (id_eq(simulator.ids.sdg, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->IS(static_cast(q)); + } else if (id_eq(simulator.ids.t, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->T(static_cast(q)); + } else if (id_eq(simulator.ids.tdg, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->IT(static_cast(q)); + } else if (id_eq(simulator.ids.x, gate_id)) { + const auto q = qubit_operand(gate.get(), 0); + ensure_qubit(simulator, q); + simulator.sim->X(static_cast(q)); + } else if (id_eq(simulator.ids.cnot, gate_id)) { + const auto control = qubit_operand(gate.get(), 0); + const auto target = qubit_operand(gate.get(), 1); + ensure_qubit(simulator, control); + ensure_qubit(simulator, target); + simulator.sim->CNOT(static_cast(control), static_cast(target)); + } else { + throw std::runtime_error("gate is not in the negotiated Clifford+T gateset"); + } - ++simulator.gates_seen; - ensure_no_engine_fallback(simulator); - }); + ++simulator.gates_seen; + ensure_no_engine_fallback(simulator); } -extern "C" SeleneErrno qrack_measure(SeleneSimulatorInstance handle, uint64_t qubit) { - try { - auto& simulator = instance(handle); - ensure_qubit(simulator, qubit); - bool result = false; - - if (!simulator.measured_register_sample.has_value()) { - const bitCapInt sample = simulator.sim->MAll(); - simulator.measured_register_sample = sample; - } - result = sample_bit(*simulator.measured_register_sample, qubit); +bool apply_measure(QrackSimulator& simulator, uint64_t qubit) { + ensure_qubit(simulator, qubit); - ++simulator.measurements; - ensure_no_engine_fallback(simulator); - return result ? 1 : 0; - } catch (const std::exception& error) { - return fail("measure", error); + if (!simulator.measured_register_sample.has_value()) { + const bitCapInt sample = simulator.sim->MAll(); + simulator.measured_register_sample = sample; } + const bool result = sample_bit(*simulator.measured_register_sample, qubit); + + ++simulator.measurements; + ensure_no_engine_fallback(simulator); + return result; +} + +void apply_reset(QrackSimulator& simulator, uint64_t qubit) { + invalidate_measured_register_sample(simulator); + ensure_qubit(simulator, qubit); + simulator.sim->SetBit(static_cast(qubit), false); + ensure_no_engine_fallback(simulator); +} + +struct OperationSink { + QrackSimulator* simulator; + OperationResultHandle result; +}; + +void sink_gate(SeleneRuntimeGetOperationInstance instance, const uint8_t* data, size_t len) { + auto* sink = static_cast(instance); + apply_gate(*sink->simulator, data, len); +} + +void sink_measure(SeleneRuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { + auto* sink = static_cast(instance); + const bool result = apply_measure(*sink->simulator, qubit); + sink->result.interface.set_bool_result_fn(sink->result.instance, result_id, result); +} + +void sink_measure_leaked(SeleneRuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { + auto* sink = static_cast(instance); + const bool result = apply_measure(*sink->simulator, qubit); + sink->result.interface.set_u64_result_fn(sink->result.instance, result_id, result ? 1 : 0); +} + +void sink_postselect(SeleneRuntimeGetOperationInstance instance, uint64_t qubit, bool target_value) { + auto* sink = static_cast(instance); + invalidate_measured_register_sample(*sink->simulator); + ensure_qubit(*sink->simulator, qubit); + (void)sink->simulator->sim->ForceM(static_cast(qubit), target_value, true, true); + ensure_no_engine_fallback(*sink->simulator); +} + +void sink_reset(SeleneRuntimeGetOperationInstance instance, uint64_t qubit) { + auto* sink = static_cast(instance); + apply_reset(*sink->simulator, qubit); +} + +void sink_custom(SeleneRuntimeGetOperationInstance, size_t, const void*, size_t) { + throw std::runtime_error("custom operations are not supported by the Qrack simulator"); +} + +void sink_set_batch_time(SeleneRuntimeGetOperationInstance, uint64_t, uint64_t) {} + +extern "C" SeleneErrno qrack_handle_operations( + SeleneSimulatorInstance handle, RuntimeExtractOperationHandle batch, OperationResultHandle result) { + return wrap_errno("handle_operations", [&]() { + OperationSink sink{&instance(handle), result}; + RuntimeGetOperationHandle output{ + &sink, + { + sink_measure, + sink_measure_leaked, + sink_postselect, + sink_reset, + sink_custom, + sink_set_batch_time, + sink_gate, + }, + }; + batch.interface.extract_fn(&batch, output); + }); } extern "C" SeleneErrno qrack_postselect( @@ -335,13 +411,7 @@ extern "C" SeleneErrno qrack_postselect( } extern "C" SeleneErrno qrack_reset(SeleneSimulatorInstance handle, uint64_t qubit) { - return wrap_errno("reset", [&]() { - auto& simulator = instance(handle); - invalidate_measured_register_sample(simulator); - ensure_qubit(simulator, qubit); - simulator.sim->SetBit(static_cast(qubit), false); - ensure_no_engine_fallback(simulator); - }); + return wrap_errno("reset", [&]() { apply_reset(instance(handle), qubit); }); } void write_metric(const char* tag, uint8_t datatype, uint64_t value, char* tag_out, uint8_t* datatype_out, @@ -358,12 +428,15 @@ extern "C" SeleneErrno qrack_get_metrics( switch (nth_metric) { case 0: write_metric("gates_seen", 2, simulator.gates_seen, tag_out, datatype_out, value_out); + last_error_message.clear(); return 0; case 1: write_metric("measurements", 2, simulator.measurements, tag_out, datatype_out, value_out); + last_error_message.clear(); return 0; case 2: write_metric("engine_fallbacks", 2, simulator.engine_fallbacks, tag_out, datatype_out, value_out); + last_error_message.clear(); return 0; default: return 1; @@ -374,8 +447,7 @@ extern "C" SeleneErrno qrack_get_metrics( } extern "C" SeleneErrno qrack_dump_state(SeleneSimulatorInstance, const char*, const uint64_t*, uint64_t) { - std::cerr << "selene_example_clifford_t_qrack: dump_state is not implemented\n"; - return -1; + return wrap_errno("dump_state", []() { throw std::runtime_error("dump_state is not implemented"); }); } extern "C" SeleneErrno qrack_negotiate_gateset(SeleneSimulatorInstance handle, const uint8_t* input, @@ -402,17 +474,15 @@ extern "C" SeleneErrno qrack_negotiate_gateset(SeleneSimulatorInstance handle, c extern "C" SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1 = { sizeof(SeleneSimulatorPluginDescriptorV1), SELENE_SIMULATOR_CURRENT_API_VERSION, + qrack_last_error, qrack_get_name, qrack_init, qrack_exit, qrack_shot_start, qrack_shot_end, - qrack_measure, - qrack_postselect, - qrack_reset, + qrack_handle_operations, qrack_get_metrics, qrack_dump_state, - qrack_gate, qrack_negotiate_gateset, }; diff --git a/examples/clifford_t_stack/src/error_model.rs b/examples/clifford_t_stack/src/error_model.rs index 9f8e4688..3da322ea 100644 --- a/examples/clifford_t_stack/src/error_model.rs +++ b/examples/clifford_t_stack/src/error_model.rs @@ -126,6 +126,15 @@ impl ErrorModelInterface for CliffordTErrorModel { Operation::Reset { qubit_id } => { Self::send(simulator, Operation::Reset { qubit_id })?; } + Operation::Postselect { + qubit_id, + target_value, + } => { + Self::send(simulator, Operation::Postselect { + qubit_id, + target_value, + })?; + } Operation::Custom { .. } => {} _ => {} } @@ -150,6 +159,10 @@ pub struct CliffordTErrorModelFactory; impl ErrorModelInterfaceFactory for CliffordTErrorModelFactory { type Interface = CliffordTErrorModel; + fn name(&self) -> &str { + "CliffordT" + } + fn init( self: std::sync::Arc, _n_qubits: u64, diff --git a/examples/clifford_t_stack/src/runtime.rs b/examples/clifford_t_stack/src/runtime.rs index 43c5fc8c..f51987ad 100644 --- a/examples/clifford_t_stack/src/runtime.rs +++ b/examples/clifford_t_stack/src/runtime.rs @@ -217,6 +217,10 @@ pub struct CliffordTRuntimeFactory; impl RuntimeInterfaceFactory for CliffordTRuntimeFactory { type Interface = CliffordTRuntime; + fn name(&self) -> &str { + "CliffordT" + } + fn init( self: std::sync::Arc, n_qubits: u64, diff --git a/examples/clifford_t_stack/src/simulator.rs b/examples/clifford_t_stack/src/simulator.rs index 4bc658fd..96809879 100644 --- a/examples/clifford_t_stack/src/simulator.rs +++ b/examples/clifford_t_stack/src/simulator.rs @@ -150,6 +150,10 @@ impl SimulatorInterface for CliffordTTraceSimulator { self.classical_state[qubit_id as usize] = false; self.trace.push(TraceEntry(format!("RESET q{qubit_id}"))); } + Operation::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, Operation::Custom { .. } => {} _ => {} } From 9457c360fbad75668f3da0a482b8779b82dc3d8e Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:42:42 +0100 Subject: [PATCH 12/19] docs: update 0.3 plugin and migration guides --- docs/extensibility/error-model/c.md | 14 ++- docs/extensibility/error-model/rust.md | 4 + docs/extensibility/fundamentals.md | 45 ++++++-- docs/extensibility/gates/c.md | 4 +- docs/extensibility/runtime/c.md | 9 +- docs/extensibility/runtime/rust.md | 11 +- docs/extensibility/simulator/c.md | 141 ++++++++++++++++++++----- docs/extensibility/simulator/rust.md | 10 +- docs/migration/0.3.md | 16 ++- 9 files changed, 195 insertions(+), 59 deletions(-) diff --git a/docs/extensibility/error-model/c.md b/docs/extensibility/error-model/c.md index 9eda7ead..3db9d3f2 100644 --- a/docs/extensibility/error-model/c.md +++ b/docs/extensibility/error-model/c.md @@ -107,7 +107,7 @@ A minimal collector looks like this: ```c typedef struct { struct SimulatorHandle simulator; - struct ErrorModelSetResultHandle results; + struct OperationResultHandle results; MyErrorModel *model; } Collector; @@ -200,7 +200,7 @@ static SeleneErrno my_error_model_handle_operations( SeleneErrorModelInstance handle, struct RuntimeExtractOperationHandle batch, struct SimulatorHandle simulator, - struct ErrorModelSetResultHandle results + struct OperationResultHandle results ) { Collector collector = { .simulator = simulator, @@ -222,7 +222,7 @@ static SeleneErrno my_error_model_handle_operations( .interface = interface, }; - batch.interface.extract_fn(batch, sink); + batch.interface.extract_fn(&batch, sink); return 0; } ``` @@ -316,8 +316,12 @@ static SeleneErrno my_error_model_get_metrics(SeleneErrorModelInstance handle, ```c const SeleneErrorModelPluginDescriptorV1 selene_error_model_plugin_descriptor_v1 = { - .struct_size = sizeof(SeleneErrorModelPluginDescriptorV1), - .api_version = SELENE_ERROR_MODEL_CURRENT_API_VERSION, + .header = { + .struct_size = sizeof(SeleneErrorModelPluginDescriptorV1), + .api_version = SELENE_ERROR_MODEL_CURRENT_API_VERSION, + .last_error_fn = my_error_model_last_error, + .get_name_fn = my_error_model_get_name, + }, .init_fn = my_error_model_init, .exit_fn = my_error_model_exit, .shot_start_fn = my_error_model_shot_start, diff --git a/docs/extensibility/error-model/rust.md b/docs/extensibility/error-model/rust.md index ec13cbbb..6504c61a 100644 --- a/docs/extensibility/error-model/rust.md +++ b/docs/extensibility/error-model/rust.md @@ -207,6 +207,10 @@ struct MyErrorModelFactory; impl ErrorModelInterfaceFactory for MyErrorModelFactory { type Interface = MyErrorModel; + fn name(&self) -> &str { + "MyErrorModel" + } + fn init( self: Arc, _n_qubits: u64, diff --git a/docs/extensibility/fundamentals.md b/docs/extensibility/fundamentals.md index a07dd188..d0313d05 100644 --- a/docs/extensibility/fundamentals.md +++ b/docs/extensibility/fundamentals.md @@ -101,14 +101,19 @@ implementing: #include const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { - .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), - .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + .header = { + .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), + .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + .last_error_fn = my_runtime_last_error, + .get_name_fn = my_runtime_get_name, + }, /* callbacks */ }; ``` -For Rust plugins, use the export macro. It fills in `struct_size` and the -current API version for you. +For Rust plugins, use the export macro. It fills in the shared descriptor header +for you from the factory's `name()` method and the plugin type's current API +version. ## Instances and Ownership @@ -124,6 +129,25 @@ every callback, and frees it during `exit`. The Rust helpers map that pattern onto boxed trait objects. Your plugin owns a normal Rust struct; the export macro handles the opaque pointer conversion. +## Reporting Errors + +Callbacks return `0` on success and a nonzero errno on failure. C and C++ +plugins should not print callback failures to stderr. Instead, store a short +thread-local error message and expose it through the descriptor's +`last_error_fn` callback. Selene calls `last_error_fn` after a failing callback +and includes that message in the host-side error. + +`last_error_fn` uses the same two-call buffer shape as gateset negotiation: + +1. Selene calls it with `output == NULL` and `output_len == 0`. + The plugin writes the number of bytes in the UTF-8 message to `written`. +2. Selene allocates a buffer and calls again. The plugin writes the message + bytes into `output` and writes the byte count to `written`. + +Rust plugins exported with Selene's helper macros get this behavior +automatically: any error returned from a callback is captured and reported +through `last_error_fn`. + ## Gatewire and Gatesets Gatewire is the gate transport layer. It solves two problems: @@ -157,7 +181,7 @@ gate before calling Selene. ## Gateset Negotiation -Every plugin type can implement `negotiate_gateset`. +Every plugin type must provide `negotiate_gateset_fn`. The input is the gateset the previous layer may send. The output is the gateset this plugin may send to the next layer. A plugin can: @@ -178,10 +202,10 @@ The C ABI uses a two-call buffer protocol for gateset negotiation: 2. Selene allocates a buffer and calls again. The plugin writes serialized gateset bytes into `output` and writes the byte count to `written`. -If the plugin leaves `negotiate_gateset_fn` null, Selene treats that as -"identity negotiation": the plugin accepts and emits the same gateset. Prefer an -explicit implementation for public plugins, because it documents the gates you -actually support. +`negotiate_gateset_fn` is mandatory. A plugin that accepts and emits the same +gateset should still implement the callback explicitly by validating the input +and returning it unchanged. This keeps the supported gates visible in the plugin +contract instead of relying on an implicit fallback. ## Operations and Batches @@ -208,7 +232,7 @@ Runtimes must maintain reference counts for result IDs. Once a result's reference count reaches zero, it is invalid to refer to it again. Error models report measurement results back through an -`ErrorModelSetResultHandle`. Simulators return measurement outcomes to the error +`OperationResultHandle`. Simulators return measurement outcomes to the error model immediately. ## Randomness and Reproducibility @@ -247,4 +271,3 @@ multiple instances, and future execution modes may run independent instances at the same time. Keep instance state behind the instance pointer or Rust struct. If you use process-global state, protect it explicitly and document why it is shared. - diff --git a/docs/extensibility/gates/c.md b/docs/extensibility/gates/c.md index f094f8cd..734ee431 100644 --- a/docs/extensibility/gates/c.md +++ b/docs/extensibility/gates/c.md @@ -58,8 +58,8 @@ negotiation. ## Decode an Incoming Gate -Runtime and simulator `gate_fn` callbacks receive gate bytes. Decode them before -inspecting operands: +Runtime callbacks and operation-batch collectors receive gate bytes. Decode them +before inspecting operands: ```c static int handle_gate(const uint8_t *data, size_t len) { diff --git a/docs/extensibility/runtime/c.md b/docs/extensibility/runtime/c.md index 6de4cc23..31ad7453 100644 --- a/docs/extensibility/runtime/c.md +++ b/docs/extensibility/runtime/c.md @@ -214,8 +214,12 @@ The descriptor is the public ABI Selene loads: ```c const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { - .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), - .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + .header = { + .struct_size = sizeof(SeleneRuntimePluginDescriptorV1), + .api_version = SELENE_RUNTIME_CURRENT_API_VERSION, + .last_error_fn = my_runtime_last_error, + .get_name_fn = my_runtime_get_name, + }, .init_fn = my_runtime_init, .exit_fn = my_runtime_exit, .get_next_operations_fn = my_runtime_get_next_operations, @@ -245,4 +249,3 @@ const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { Use clear failures for unsupported features. For example, if your runtime does not support `custom_call`, return nonzero rather than silently ignoring it. - diff --git a/docs/extensibility/runtime/rust.md b/docs/extensibility/runtime/rust.md index 8024338a..f0ca5da2 100644 --- a/docs/extensibility/runtime/rust.md +++ b/docs/extensibility/runtime/rust.md @@ -93,9 +93,10 @@ impl RuntimeInterface for MyRuntime { } ``` -If you omit `negotiate_gateset`, Selene assumes identity negotiation. Public -runtimes should implement it explicitly so unsupported gates fail at -configuration time. +Rust trait implementations may use helper defaults while prototyping, but +exported plugins must provide `negotiate_gateset_fn` in their descriptor. A +runtime that accepts and emits the same gateset should still validate the input +and return it explicitly so unsupported gates fail at configuration time. ## 5. Accept Generic Gates @@ -228,6 +229,10 @@ struct MyRuntimeFactory; impl RuntimeInterfaceFactory for MyRuntimeFactory { type Interface = MyRuntime; + fn name(&self) -> &str { + "MyRuntime" + } + fn init( self: Arc, n_qubits: u64, diff --git a/docs/extensibility/simulator/c.md b/docs/extensibility/simulator/c.md index d4548827..521d9fb9 100644 --- a/docs/extensibility/simulator/c.md +++ b/docs/extensibility/simulator/c.md @@ -100,15 +100,20 @@ static SeleneErrno my_simulator_negotiate_gateset(SeleneSimulatorInstance handle For a restricted simulator, build a smaller supported set and reject anything outside it. -## 3. Decode and Apply Gates +## 3. Handle Operation Batches -`gate_fn` receives serialized gate bytes: +The simulator's first-class operation entry point is `handle_operations_fn`. +Selene passes a batch extractor and a result writer. Your simulator provides a +small collector, asks Selene to replay the batch into it, and writes measurement +results as they are produced. ```c -static SeleneErrno my_simulator_gate(SeleneSimulatorInstance handle, - const uint8_t *data, - size_t len) { - MySimulator *sim = (MySimulator *)handle; +typedef struct { + MySimulator *sim; + struct OperationResultHandle results; +} SimulatorCollector; + +static int apply_gate(MySimulator *sim, const uint8_t *data, size_t len) { GwDecodedGate *gate = NULL; if (gw_gate_deserialize(data, len, &gate) != GW_STATUS_OK) { return 1; @@ -134,28 +139,107 @@ static SeleneErrno my_simulator_gate(SeleneSimulatorInstance handle, gw_decoded_gate_free(gate); return 0; } -``` -Validate operand kinds before using them in production code. +static bool apply_measure(MySimulator *sim, uint64_t qubit) { + (void)qubit; + sim->measurements++; + return false; +} -## 4. Measure, Reset, and Postselect +static void collect_gate(SeleneRuntimeGetOperationInstance instance, + const uint8_t *data, + size_t len) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + (void)apply_gate(collector->sim, data, len); +} -Simulator measurement returns the value directly as the function return code: +static void collect_measure(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + uint64_t result_id) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + bool result = apply_measure(collector->sim, qubit); + collector->results.interface.set_bool_result_fn( + collector->results.instance, + result_id, + result + ); +} -```c -static SeleneErrno my_simulator_measure(SeleneSimulatorInstance handle, - uint64_t qubit) { - MySimulator *sim = (MySimulator *)handle; - sim->measurements++; - /* Return 0 for false, 1 for true, or another nonzero value for error. */ - return backend_measure(sim, qubit) ? 1 : 0; +static void collect_measure_leaked(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + uint64_t result_id) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + bool result = apply_measure(collector->sim, qubit); + collector->results.interface.set_u64_result_fn( + collector->results.instance, + result_id, + result ? 1 : 0 + ); +} + +static void collect_postselect(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + bool target_value) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + (void)collector; + (void)qubit; + (void)target_value; + /* backend_postselect(collector->sim, qubit, target_value); */ +} + +static void collect_reset(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + (void)collector; + (void)qubit; + /* backend_reset(collector->sim, qubit); */ +} + +static void collect_custom(SeleneRuntimeGetOperationInstance instance, + size_t tag, + const void *data, + size_t len) { + (void)instance; + (void)tag; + (void)data; + (void)len; +} + +static void collect_batch_time(SeleneRuntimeGetOperationInstance instance, + uint64_t start, + uint64_t duration) { + (void)instance; + (void)start; + (void)duration; +} + +static SeleneErrno my_simulator_handle_operations( + SeleneSimulatorInstance handle, + struct RuntimeExtractOperationHandle batch, + struct OperationResultHandle results +) { + SimulatorCollector collector = {(MySimulator *)handle, results}; + struct RuntimeGetOperationHandle output = { + .instance = &collector, + .interface = { + .measure_fn = collect_measure, + .measure_leaked_fn = collect_measure_leaked, + .postselect_fn = collect_postselect, + .reset_fn = collect_reset, + .custom_fn = collect_custom, + .set_batch_time_fn = collect_batch_time, + .gate_fn = collect_gate, + }, + }; + batch.interface.extract_fn(&batch, output); + return 0; } ``` -Reset should apply a physical reset in the backend. Postselection is optional: -return a nonzero error if unsupported. +Validate operand kinds before using them in production code. Postselection is +part of the same operation batch as gates, measurements, and resets. -## 5. Metrics and Lifecycle +## 4. Metrics and Lifecycle `shot_start` should initialize the quantum state for a shot and seed any RNG. `shot_end` should validate and clean up per-shot state. `get_metrics_fn` is @@ -178,27 +262,26 @@ static SeleneErrno my_simulator_get_metrics(SeleneSimulatorInstance handle, } ``` -## 6. Export the Descriptor +## 5. Export the Descriptor ```c const SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1 = { - .struct_size = sizeof(SeleneSimulatorPluginDescriptorV1), - .api_version = SELENE_SIMULATOR_CURRENT_API_VERSION, - .get_name_fn = my_simulator_get_name, + .header = { + .struct_size = sizeof(SeleneSimulatorPluginDescriptorV1), + .api_version = SELENE_SIMULATOR_CURRENT_API_VERSION, + .last_error_fn = my_simulator_last_error, + .get_name_fn = my_simulator_get_name, + }, .init_fn = my_simulator_init, .exit_fn = my_simulator_exit, .shot_start_fn = my_simulator_shot_start, .shot_end_fn = my_simulator_shot_end, - .measure_fn = my_simulator_measure, - .postselect_fn = my_simulator_postselect, - .reset_fn = my_simulator_reset, + .handle_operations_fn = my_simulator_handle_operations, .get_metrics_fn = my_simulator_get_metrics, .dump_state_fn = my_simulator_dump_state, - .gate_fn = my_simulator_gate, .negotiate_gateset_fn = my_simulator_negotiate_gateset, }; ``` Use `NULL` only for callbacks documented as optional. Required callbacks are validated when Selene loads the plugin. - diff --git a/docs/extensibility/simulator/rust.md b/docs/extensibility/simulator/rust.md index cee3c5bb..536122a1 100644 --- a/docs/extensibility/simulator/rust.md +++ b/docs/extensibility/simulator/rust.md @@ -149,8 +149,9 @@ impl MySimulator { should validate and release per-shot resources. `exit` should make the instance unusable. -Postselection and state dumping are optional. If unsupported, return a clear -error. Metrics are dynamic: +Postselection is represented as an operation in `handle_operations`. State +dumping remains optional; if unsupported, return a clear error. Metrics are +dynamic: ```rust fn get_metric(&mut self, nth_metric: u8) -> Result> { @@ -172,6 +173,10 @@ struct MySimulatorFactory; impl SimulatorInterfaceFactory for MySimulatorFactory { type Interface = MySimulator; + fn name(&self) -> &str { + "MySimulator" + } + fn init( self: Arc, n_qubits: u64, @@ -188,4 +193,3 @@ selene_core::export_simulator_plugin!(MySimulatorFactory); ``` After building the shared library, configure Selene to load it as a simulator. - diff --git a/docs/migration/0.3.md b/docs/migration/0.3.md index 6ecfb3a2..26f0efb1 100644 --- a/docs/migration/0.3.md +++ b/docs/migration/0.3.md @@ -174,16 +174,16 @@ from selene_core import ( Gateset, OperandDefinition, PhasedX, + QuantinuumGateSet, RZ, ZZPhase, - builtin_gateset, ) ``` Use builtin definitions for Selene's builtin gates: ```python -gates = builtin_gateset() +gates = QuantinuumGateSet gate = gates.PhasedX(q0=0, theta=1.57079632679, phi=0.0) ``` @@ -412,6 +412,7 @@ exported as a top-level symbol. Simulator descriptors now include: +- `last_error_fn(output, output_len, written)`; - `gate_fn(handle, const uint8_t *data, size_t len)`; - `negotiate_gateset_fn(handle, input, input_len, output, output_len, written)`; - measurement, postselect, reset, metrics, state dump, shot lifecycle, and @@ -419,14 +420,23 @@ Simulator descriptors now include: Runtime descriptors now include: +- `last_error_fn(output, output_len, written)`; - `gate_fn(handle, const uint8_t *data, size_t len)`; - `negotiate_gateset_fn(...)`; - generic output callback `gate_fn` in `SeleneRuntimeGetOperationInterface`. +Simulator descriptors now include: + +- `last_error_fn(output, output_len, written)`; +- `handle_operations_fn(handle, RuntimeExtractOperationHandle, + OperationResultHandle)`; +- `negotiate_gateset_fn(...)`. + Error-model descriptors now include: +- `last_error_fn(output, output_len, written)`; - `handle_operations_fn(handle, RuntimeExtractOperationHandle, SimulatorHandle, - ErrorModelSetResultHandle)`; + OperationResultHandle)`; - `negotiate_gateset_fn(...)`; - init without simulator plugin path/arguments. From 6b36bc7048271eed2253ac4b2dbefb0173288661 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:09:53 +0100 Subject: [PATCH 13/19] Ruff format, skip mypy in tests --- devenv.nix | 1 + pyproject.toml | 2 +- .../selene_argreader_plugin/provider.py | 2 +- .../argreader/python/tests/test_argreader.py | 1 + .../selene_sim/interactive/full_stack.py | 8 +++-- .../python/selene_sim/interactive/runtime.py | 4 +-- .../selene_sim/interactive/simulator.py | 34 +++++++++++-------- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/devenv.nix b/devenv.nix index 1415465f..df4ea185 100644 --- a/devenv.nix +++ b/devenv.nix @@ -70,6 +70,7 @@ ]; excludes = [ "selene-sim/python/tests" + "selene-ext/utilities/argreader/python/tests" "selene-ext/simulators/quest/python/gate_definitions.py" "selene-ext/simulators/stim/python/gate_definitions.py" ]; diff --git a/pyproject.toml b/pyproject.toml index 40379f36..6503d0ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ extend-exclude = ["target"] [tool.mypy] ignore_missing_imports = true -exclude = ["test_.*.py$", "^target/.*$", "^selene-core/.*$", "^scratch/.*$"] +exclude = ["^.*/tests/.*.py$", "^target/.*$", "^selene-core/.*$", "^scratch/.*$"] [tool.pytest.ini_options] norecursedirs = ['*.egg', '.*', 'build', 'target', 'dist', 'venv', 'examples'] diff --git a/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py index 14e0531f..9ed90269 100644 --- a/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py +++ b/selene-ext/utilities/argreader/python/selene_argreader_plugin/provider.py @@ -30,7 +30,7 @@ def __post_init__(self): raise ValueError( f"All items in the list for key '{key}' must be int, float, or bool" ) - #if isinstance(value, list) and len(value) == 0: + # if isinstance(value, list) and len(value) == 0: # raise ValueError(f"List for key '{key}' cannot be empty") if not isinstance(key, str): raise ValueError(f"Key '{key}' must be a string") diff --git a/selene-ext/utilities/argreader/python/tests/test_argreader.py b/selene-ext/utilities/argreader/python/tests/test_argreader.py index 95d796cf..72bd4025 100644 --- a/selene-ext/utilities/argreader/python/tests/test_argreader.py +++ b/selene-ext/utilities/argreader/python/tests/test_argreader.py @@ -237,6 +237,7 @@ def test_arg_reader_trace(snapshot, interface): json = trace.model_dump_json(indent=2) snapshot.assert_match(json, "trace.json") + @pytest.mark.parametrize("interface", [HeliosInterface(), SolInterface()]) def test_arg_reader_zero_length_arrays(snapshot, interface): llvm_file = Path(__file__).parent / "resources/argreader_zero_length_arrays.ll" diff --git a/selene-sim/python/selene_sim/interactive/full_stack.py b/selene-sim/python/selene_sim/interactive/full_stack.py index 59e862e8..74888985 100644 --- a/selene-sim/python/selene_sim/interactive/full_stack.py +++ b/selene-sim/python/selene_sim/interactive/full_stack.py @@ -31,7 +31,9 @@ DEFAULT_SHOT_SPEC = ShotSpec(count=1000, offset=0, increment=1) _SELENE_HEADER = Path(__file__).parents[2] / "_dist/include/selene/selene.h" -_SOURCE_SELENE_HEADER = Path(__file__).parents[4] / "selene-sim/c/include/selene/selene.h" +_SOURCE_SELENE_HEADER = ( + Path(__file__).parents[4] / "selene-sim/c/include/selene/selene.h" +) if _SOURCE_SELENE_HEADER.exists(): _SELENE_HEADER = _SOURCE_SELENE_HEADER _FFI = ffi((_SELENE_HEADER,)) @@ -120,7 +122,9 @@ def load_config(self, config_path: PathLike): def fetch_output(self, instance, chunk_size: int) -> bytes: chunk = self.ffi.new(self.types.uint8_array, chunk_size) - bytes_read = int(_unwrap_value(self.lib.selene_fetch_output(instance, chunk, chunk_size))) + bytes_read = int( + _unwrap_value(self.lib.selene_fetch_output(instance, chunk, chunk_size)) + ) if bytes_read == 0: raise BlockingIOError return bytes(self.ffi.buffer(chunk, bytes_read)) diff --git a/selene-sim/python/selene_sim/interactive/runtime.py b/selene-sim/python/selene_sim/interactive/runtime.py index 3a6a74ab..52b768aa 100644 --- a/selene-sim/python/selene_sim/interactive/runtime.py +++ b/selene-sim/python/selene_sim/interactive/runtime.py @@ -370,9 +370,7 @@ def next_shot(self): self.shot_id += 1 seed = self.runtime.random_seed assert seed is not None - if 0 != self._lib.shot_start( - self._instance, self.shot_id, seed + self.shot_id - ): + if 0 != self._lib.shot_start(self._instance, self.shot_id, seed + self.shot_id): raise RuntimeError("Failed to start next shot on Selene runtime") def get_operations(self) -> list[OperationBatch]: diff --git a/selene-sim/python/selene_sim/interactive/simulator.py b/selene-sim/python/selene_sim/interactive/simulator.py index a90c36c1..507773e6 100644 --- a/selene-sim/python/selene_sim/interactive/simulator.py +++ b/selene-sim/python/selene_sim/interactive/simulator.py @@ -96,7 +96,9 @@ def extract(input_handle, output_handle): output_handle.instance, operation[1], operation[2] ) elif kind == "reset": - output_handle.interface.reset_fn(output_handle.instance, operation[1]) + output_handle.interface.reset_fn( + output_handle.instance, operation[1] + ) else: raise RuntimeError(f"unsupported simulator operation {kind!r}") @@ -123,16 +125,20 @@ def set_u64(instance, result_id, value): result_ref = self.ffi.new_handle(result) refs.extend([result_ref, set_bool, set_u64]) - return self.ffi.new( - self.types.operation_result_handle, - { - "instance": result_ref, - "interface": { - "set_bool_result_fn": set_bool, - "set_u64_result_fn": set_u64, + return ( + self.ffi.new( + self.types.operation_result_handle, + { + "instance": result_ref, + "interface": { + "set_bool_result_fn": set_bool, + "set_u64_result_fn": set_u64, + }, }, - }, - ), result, refs + ), + result, + refs, + ) def _handle_operations(self, instance, operations: list[Any]): batch, batch_refs = self._batch_handle(operations) @@ -166,7 +172,9 @@ def reset(self, instance, qubit: int): return errno def postselect(self, instance, qubit: int, value: bool): - errno, result = self._handle_operations(instance, [("postselect", qubit, value)]) + errno, result = self._handle_operations( + instance, [("postselect", qubit, value)] + ) if errno == 0 and (result["bool"] or result["u64"]): return -1 return errno @@ -271,9 +279,7 @@ def measure(self, qubit: int) -> bool: return self._apply_measure_operation(qubit) def reset(self, qubit: int): - self._apply_void_operation( - self._lib.reset(self._instance, qubit), "RESET" - ) + self._apply_void_operation(self._lib.reset(self._instance, qubit), "RESET") def postselect(self, qubit: int, value: bool): self._apply_void_operation( From 1a8108f32c03e171532015d57c0a3a442b3be99f Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:12:37 +0100 Subject: [PATCH 14/19] Correct expected error message from impossible postselection in stim --- selene-ext/simulators/stim/rust/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selene-ext/simulators/stim/rust/tests.rs b/selene-ext/simulators/stim/rust/tests.rs index dd8741a0..d3a3187e 100644 --- a/selene-ext/simulators/stim/rust/tests.rs +++ b/selene-ext/simulators/stim/rust/tests.rs @@ -29,7 +29,7 @@ fn impossible_postselection_reports_simulator_detail() { assert_eq!( error.to_string(), - "Simulator (Stim): postselect failed: Postselection impossible.\n\ + "Simulator (Stim): handle_operations failed: Postselection impossible.\n\ Qubit 0 was asked to postselect to state |0>, but was in the perpendicular state |1>." ); } From 30007c81c3e0e6e97b839c8abc1a9c3eed0f4009 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:15:57 +0100 Subject: [PATCH 15/19] Regenerate C headers --- selene-core/c/include/selene/error_model.h | 135 +++++++++++++++++++-- selene-core/c/include/selene/runtime.h | 74 +++++++++-- selene-core/c/include/selene/simulator.h | 129 ++++++++++++-------- selene-sim/c/include/selene/selene.h | 20 +-- 4 files changed, 276 insertions(+), 82 deletions(-) diff --git a/selene-core/c/include/selene/error_model.h b/selene-core/c/include/selene/error_model.h index 827db560..f07cdd51 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -1,18 +1,15 @@ -#ifndef SELENE_ERROR_MODEL_H -#define SELENE_ERROR_MODEL_H - #include #include #include #include #include -#include "selene/gatewire.h" -#include "selene/operation.h" -#include "selene/plugin.h" -#include "selene/runtime.h" -#include "selene/simulator.h" +#include "selene/core_types.h" #define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL +typedef struct Option_last_error_fn Option_last_error_fn; + +typedef struct Option_plugin_name_fn Option_plugin_name_fn; + typedef struct SeleneErrorModelAPIVersion { /** * Reserved for future use, must be 0. @@ -34,8 +31,126 @@ typedef struct SeleneErrorModelAPIVersion { typedef void *SeleneErrorModelInstance; +typedef void *SeleneOperationResultInstance; + +typedef struct SeleneOperationResultInterface { + void (*set_bool_result_fn)(SeleneOperationResultInstance, + uint64_t, + bool); + void (*set_u64_result_fn)(SeleneOperationResultInstance, + uint64_t, + uint64_t); +} SeleneOperationResultInterface; + +typedef struct PluginDescriptorHeaderV1 { + uint64_t struct_size; + uint64_t api_version; + struct Option_last_error_fn last_error_fn; + struct Option_plugin_name_fn get_name_fn; +} PluginDescriptorHeaderV1; + +typedef int32_t SeleneErrno; + +typedef void *SeleneRuntimeExtractOperationInstance; + +/** + * An instance is provided to `selene_runtime_get_next_operations`, which must + * pass that back to any function it calls in its provided + * [RuntimeGetOperationInterface]. + */ +typedef void *SeleneRuntimeGetOperationInstance; + +typedef struct RuntimeGetOperationInterface { + void (*measure_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*measure_leaked_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*postselect_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + bool); + void (*reset_fn)(SeleneRuntimeGetOperationInstance, + uint64_t); + void (*custom_fn)(SeleneRuntimeGetOperationInstance, + size_t, + const void*, + size_t); + void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*gate_fn)(SeleneRuntimeGetOperationInstance, + const uint8_t*, + size_t); +} RuntimeGetOperationInterface; + +typedef struct RuntimeGetOperationHandle { + SeleneRuntimeGetOperationInstance instance; + struct RuntimeGetOperationInterface interface; +} RuntimeGetOperationHandle; + +typedef struct SeleneRuntimeExtractOperationInterface { + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, + struct RuntimeGetOperationHandle); +} SeleneRuntimeExtractOperationInterface; + +typedef struct RuntimeExtractOperationHandle { + SeleneRuntimeExtractOperationInstance instance; + struct SeleneRuntimeExtractOperationInterface interface; +} RuntimeExtractOperationHandle; + +typedef void *SeleneSimulatorInstance; + +typedef SeleneErrno (*LastErrorFn)(char *output, + size_t output_len, + size_t *written); + +typedef struct OperationResultHandle { + SeleneOperationResultInstance instance; + struct SeleneOperationResultInterface interface; +} OperationResultHandle; + +typedef struct SimulatorOperationInterface { + SeleneErrno (*exit_fn)(SeleneSimulatorInstance instance); + LastErrorFn last_error_fn; + SeleneErrno (*shot_start_fn)(SeleneSimulatorInstance instance, + uint64_t shot_id, + uint64_t seed); + SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance instance); + SeleneErrno (*handle_operations_fn)(SeleneSimulatorInstance instance, + struct RuntimeExtractOperationHandle batch, + struct OperationResultHandle result); + SeleneErrno (*measure_fn)(SeleneSimulatorInstance instance, + uint64_t qubit); + SeleneErrno (*reset_fn)(SeleneSimulatorInstance instance, + uint64_t qubit); + SeleneErrno (*get_metric_fn)(SeleneSimulatorInstance instance, + uint8_t nth_metric, + char *tag_ptr, + uint8_t *datatype_ptr, + uint64_t *data_ptr); + SeleneErrno (*dump_state_fn)(SeleneSimulatorInstance instance, + const char *file, + const uint64_t *qubits, + uint64_t n_qubits); + SeleneErrno (*gate_fn)(SeleneSimulatorInstance instance, + const uint8_t *data, + size_t len); + SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance instance, + const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_len, + size_t *written); +} SimulatorOperationInterface; + +typedef struct SimulatorHandle { + SeleneSimulatorInstance instance; + struct SimulatorOperationInterface interface; +} SimulatorHandle; + typedef struct SeleneErrorModelPluginDescriptorV1 { - SelenePluginDescriptorV1 header; + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(SeleneErrorModelInstance *handle, uint64_t n_qubits, uint32_t error_model_argc, @@ -76,5 +191,3 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus - -#endif /* SELENE_ERROR_MODEL_H */ diff --git a/selene-core/c/include/selene/runtime.h b/selene-core/c/include/selene/runtime.h index 9ec6fd9d..a2a4a075 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -1,16 +1,15 @@ -#ifndef SELENE_RUNTIME_H -#define SELENE_RUNTIME_H - #include #include #include #include #include -#include "selene/gatewire.h" -#include "selene/operation.h" -#include "selene/plugin.h" +#include "selene/core_types.h" #define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL +typedef struct Option_last_error_fn Option_last_error_fn; + +typedef struct Option_plugin_name_fn Option_plugin_name_fn; + typedef struct SeleneRuntimeAPIVersion { /** * Reserved for future use, must be 0. @@ -30,10 +29,67 @@ typedef struct SeleneRuntimeAPIVersion { uint8_t patch; } SeleneRuntimeAPIVersion; +/** + * An instance is provided to `selene_runtime_get_next_operations`, which must + * pass that back to any function it calls in its provided + * [RuntimeGetOperationInterface]. + */ +typedef void *SeleneRuntimeGetOperationInstance; + +typedef struct SeleneRuntimeGetOperationInterface { + void (*measure_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*measure_leaked_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*postselect_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + bool); + void (*reset_fn)(SeleneRuntimeGetOperationInstance, + uint64_t); + void (*custom_fn)(SeleneRuntimeGetOperationInstance, + size_t, + const void*, + size_t); + void (*set_batch_time_fn)(SeleneRuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*gate_fn)(SeleneRuntimeGetOperationInstance, + const uint8_t*, + size_t); +} SeleneRuntimeGetOperationInterface; + +typedef void *SeleneRuntimeExtractOperationInstance; + +typedef struct RuntimeExtractOperationHandle { + SeleneRuntimeExtractOperationInstance instance; + struct SeleneRuntimeExtractOperationInterface interface; +} RuntimeExtractOperationHandle; + +typedef struct RuntimeGetOperationHandle { + SeleneRuntimeGetOperationInstance instance; + struct SeleneRuntimeGetOperationInterface interface; +} RuntimeGetOperationHandle; + +typedef struct SeleneRuntimeExtractOperationInterface { + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, + struct RuntimeGetOperationHandle); +} SeleneRuntimeExtractOperationInterface; + +typedef struct PluginDescriptorHeaderV1 { + uint64_t struct_size; + uint64_t api_version; + struct Option_last_error_fn last_error_fn; + struct Option_plugin_name_fn get_name_fn; +} PluginDescriptorHeaderV1; + +typedef int32_t SeleneErrno; + typedef void *RuntimeInstance; typedef struct SeleneRuntimePluginDescriptorV1 { - SelenePluginDescriptorV1 header; + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(RuntimeInstance *handle, uint64_t n_qubits, uint64_t start, @@ -109,8 +165,6 @@ typedef struct SeleneRuntimePluginDescriptorV1 { extern "C" { #endif // __cplusplus -extern SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1; - GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); @@ -121,5 +175,3 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus - -#endif /* SELENE_RUNTIME_H */ diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index d45221b6..07ee5773 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -1,15 +1,14 @@ -#ifndef SELENE_SIMULATOR_H -#define SELENE_SIMULATOR_H - #include #include #include #include #include -#include "selene/gatewire.h" -#include "selene/operation.h" -#include "selene/plugin.h" -#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000200ULL +#include "selene/core_types.h" +#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL + +typedef struct Option_last_error_fn Option_last_error_fn; + +typedef struct Option_plugin_name_fn Option_plugin_name_fn; typedef struct SeleneSimulatorAPIVersion { /** @@ -32,49 +31,81 @@ typedef struct SeleneSimulatorAPIVersion { typedef void *SeleneSimulatorInstance; -typedef struct SimulatorOperationInterface { - SeleneErrno (*exit_fn)(SeleneSimulatorInstance instance); - SeleneErrno (*last_error_fn)(char *output, - size_t output_len, - size_t *written); - SeleneErrno (*shot_start_fn)(SeleneSimulatorInstance instance, - uint64_t shot_id, - uint64_t seed); - SeleneErrno (*shot_end_fn)(SeleneSimulatorInstance instance); - SeleneErrno (*handle_operations_fn)(SeleneSimulatorInstance instance, - struct RuntimeExtractOperationHandle batch, - struct OperationResultHandle result); - SeleneErrno (*measure_fn)(SeleneSimulatorInstance instance, - uint64_t qubit); - SeleneErrno (*reset_fn)(SeleneSimulatorInstance instance, - uint64_t qubit); - SeleneErrno (*get_metric_fn)(SeleneSimulatorInstance instance, - uint8_t nth_metric, - char *tag_ptr, - uint8_t *datatype_ptr, - uint64_t *data_ptr); - SeleneErrno (*dump_state_fn)(SeleneSimulatorInstance instance, - const char *file, - const uint64_t *qubits, - uint64_t n_qubits); - SeleneErrno (*gate_fn)(SeleneSimulatorInstance instance, - const uint8_t *data, - size_t len); - SeleneErrno (*negotiate_gateset_fn)(SeleneSimulatorInstance instance, - const uint8_t *input, - size_t input_len, - uint8_t *output, - size_t output_len, - size_t *written); -} SimulatorOperationInterface; +typedef struct PluginDescriptorHeaderV1 { + uint64_t struct_size; + uint64_t api_version; + struct Option_last_error_fn last_error_fn; + struct Option_plugin_name_fn get_name_fn; +} PluginDescriptorHeaderV1; + +typedef int32_t SeleneErrno; + +typedef void *RuntimeExtractOperationInstance; + +/** + * An instance is provided to `selene_runtime_get_next_operations`, which must + * pass that back to any function it calls in its provided + * [RuntimeGetOperationInterface]. + */ +typedef void *RuntimeGetOperationInstance; + +typedef struct RuntimeGetOperationInterface { + void (*measure_fn)(RuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*measure_leaked_fn)(RuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*postselect_fn)(RuntimeGetOperationInstance, + uint64_t, + bool); + void (*reset_fn)(RuntimeGetOperationInstance, + uint64_t); + void (*custom_fn)(RuntimeGetOperationInstance, + size_t, + const void*, + size_t); + void (*set_batch_time_fn)(RuntimeGetOperationInstance, + uint64_t, + uint64_t); + void (*gate_fn)(RuntimeGetOperationInstance, + const uint8_t*, + size_t); +} RuntimeGetOperationInterface; + +typedef struct RuntimeGetOperationHandle { + RuntimeGetOperationInstance instance; + struct RuntimeGetOperationInterface interface; +} RuntimeGetOperationHandle; -typedef struct SimulatorHandle { - SeleneSimulatorInstance instance; - struct SimulatorOperationInterface interface; -} SimulatorHandle; +typedef struct RuntimeExtractOperationInterface { + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, + struct RuntimeGetOperationHandle); +} RuntimeExtractOperationInterface; + +typedef struct RuntimeExtractOperationHandle { + RuntimeExtractOperationInstance instance; + struct RuntimeExtractOperationInterface interface; +} RuntimeExtractOperationHandle; + +typedef void *OperationResultInstance; + +typedef struct OperationResultInterface { + void (*set_bool_result_fn)(OperationResultInstance, + uint64_t, + bool); + void (*set_u64_result_fn)(OperationResultInstance, + uint64_t, + uint64_t); +} OperationResultInterface; + +typedef struct OperationResultHandle { + OperationResultInstance instance; + struct OperationResultInterface interface; +} OperationResultHandle; typedef struct SeleneSimulatorPluginDescriptorV1 { - SelenePluginDescriptorV1 header; + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(SeleneSimulatorInstance *handle, uint64_t n_qubits, uint32_t argc, @@ -108,8 +139,6 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { extern "C" { #endif // __cplusplus -extern SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1; - GwStatus gw_decoded_gate_qubit_operand_count(const GwDecodedGate *gate, size_t *out); @@ -120,5 +149,3 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus - -#endif /* SELENE_SIMULATOR_H */ diff --git a/selene-sim/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index fc24144d..90984e01 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -20,12 +20,6 @@ typedef struct selene_void_result_t { uint32_t error_code; } selene_void_result_t; -typedef struct SeleneUtilityEventCallbacksV1 { - void *context; - struct selene_void_result_t (*on_shot_start)(void *context, uint64_t shot_id); - struct selene_void_result_t (*on_shot_end)(void *context, uint64_t shot_id); -} SeleneUtilityEventCallbacksV1; - typedef struct selene_string_t { const char *data; uint64_t length; @@ -52,6 +46,14 @@ typedef struct selene_u32_result_t { uint32_t value; } selene_u32_result_t; +typedef struct selene_void_result_t (*UtilityShotEventFn)(void *context, uint64_t shot_id); + +typedef struct SeleneUtilityEventCallbacksV1 { + void *context; + UtilityShotEventFn on_shot_start; + UtilityShotEventFn on_shot_end; +} SeleneUtilityEventCallbacksV1; + /** * Some runtimes have additional capabilities outside of the core API. These can be triggered * by a frontend by passing in opaque data blobs with an identification tag. The runtime determines @@ -195,9 +197,6 @@ struct selene_u64_result_t selene_qalloc(struct SeleneInstance *instance); struct selene_void_result_t selene_qfree(struct SeleneInstance *instance, uint64_t q); -struct selene_void_result_t selene_register_utility_event_callbacks(struct SeleneInstance *instance, - struct SeleneUtilityEventCallbacksV1 callbacks); - /** * Performs a lazy measurement */ @@ -274,6 +273,9 @@ struct selene_void_result_t selene_register_gateset(struct SeleneInstance *insta size_t output_len, size_t *written); +struct selene_void_result_t selene_register_utility_event_callbacks(struct SeleneInstance *instance, + struct SeleneUtilityEventCallbacksV1 callbacks); + struct selene_void_result_t selene_set_tc(struct SeleneInstance *instance, uint64_t tc); struct selene_u64_result_t selene_shot_count(struct SeleneInstance *instance); From eca4d2c976ab833c8e5dce3c8e50250be0dbbf69 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 10:42:07 +0100 Subject: [PATCH 16/19] Add header generation back to selene-core --- hatch_build.py | 5 + justfile | 7 + selene-core/c/include/selene/core_types.h | 5 + selene-core/c/include/selene/error_model.h | 27 +- selene-core/c/include/selene/runtime.h | 23 +- selene-core/c/include/selene/simulator.h | 23 +- selene-core/cbindgen/core_types.toml | 2 +- selene-core/cbindgen/error_model.toml | 1 + selene-core/cbindgen/runtime.toml | 1 + selene-core/cbindgen/simulator.toml | 1 + selene-core/examples/cbindgen.toml | 77 --- selene-core/examples/error_model/Cargo.lock | 588 ----------------- selene-core/examples/error_model/Cargo.toml | 18 - .../__init__.py | 3 - .../plugin.py | 34 - selene-core/examples/error_model/rust/lib.rs | 311 --------- selene-core/examples/runtime/.gitignore | 72 -- selene-core/examples/runtime/Cargo.lock | 347 ---------- selene-core/examples/runtime/Cargo.toml | 15 - .../python/selene_example_runtime/__init__.py | 3 - .../python/selene_example_runtime/plugin.py | 36 - selene-core/examples/runtime/rust/lib.rs | 308 --------- selene-core/examples/simulator/Cargo.lock | 613 ------------------ selene-core/examples/simulator/Cargo.toml | 19 - .../__init__.py | 3 - .../selene_example_simulator_plugin/plugin.py | 38 -- selene-core/examples/simulator/rust/lib.rs | 224 ------- selene-core/pyproject.toml | 3 - selene-core/python/selene_core/c_abi/_cffi.py | 27 +- .../python/selene_core/c_abi/simulator.py | 1 - selene-core/rust/error_model.rs | 4 +- selene-core/rust/error_model/helper.rs | 2 +- selene-core/rust/error_model/plugin.rs | 22 +- selene-core/rust/macros.rs | 2 +- selene-core/rust/plugin.rs | 46 +- selene-core/rust/runtime.rs | 4 +- selene-core/rust/runtime/helper.rs | 2 +- selene-core/rust/runtime/plugin.rs | 56 +- selene-core/rust/simulator.rs | 4 +- selene-core/rust/simulator/helper.rs | 2 +- selene-core/rust/simulator/plugin.rs | 24 +- .../interfaces/helios_qis/c/CMakeLists.txt | 5 + .../interfaces/helios_qis/c/src/helios_ops.c | 1 + .../interfaces/helios_qis/c/src/interface.c | 1 + .../interfaces/sol_qis/c/CMakeLists.txt | 5 + .../interfaces/sol_qis/c/src/interface.c | 1 + selene-ext/interfaces/sol_qis/c/src/sol_ops.c | 1 + selene-sim/c/CMakeLists.txt | 3 - selene-sim/c/include/selene/selene.h | 1 - selene-sim/cbindgen.toml | 2 +- .../selene_sim/interactive/full_stack.py | 9 +- .../python/selene_sim/interactive/runtime.py | 25 +- .../selene_sim/interactive/simulator.py | 27 +- 53 files changed, 220 insertions(+), 2864 deletions(-) delete mode 100644 selene-core/examples/cbindgen.toml delete mode 100644 selene-core/examples/error_model/Cargo.lock delete mode 100644 selene-core/examples/error_model/Cargo.toml delete mode 100644 selene-core/examples/error_model/python/selene_example_error_model_plugin/__init__.py delete mode 100644 selene-core/examples/error_model/python/selene_example_error_model_plugin/plugin.py delete mode 100644 selene-core/examples/error_model/rust/lib.rs delete mode 100644 selene-core/examples/runtime/.gitignore delete mode 100644 selene-core/examples/runtime/Cargo.lock delete mode 100644 selene-core/examples/runtime/Cargo.toml delete mode 100644 selene-core/examples/runtime/python/selene_example_runtime/__init__.py delete mode 100644 selene-core/examples/runtime/python/selene_example_runtime/plugin.py delete mode 100644 selene-core/examples/runtime/rust/lib.rs delete mode 100644 selene-core/examples/simulator/Cargo.lock delete mode 100644 selene-core/examples/simulator/Cargo.toml delete mode 100644 selene-core/examples/simulator/python/selene_example_simulator_plugin/__init__.py delete mode 100644 selene-core/examples/simulator/python/selene_example_simulator_plugin/plugin.py delete mode 100644 selene-core/examples/simulator/rust/lib.rs diff --git a/hatch_build.py b/hatch_build.py index 0f49b681..8584c112 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -421,6 +421,9 @@ def build_selene_c_interface(self): selene_sim_dir = Path(self.root) / "selene-sim" dist_dir = selene_sim_dir / "python/selene_sim/_dist" dist_dir.mkdir(parents=True, exist_ok=True) + include_dir = dist_dir / "include" + if include_dir.exists(): + shutil.rmtree(include_dir) cmake_source_dir = selene_sim_dir / "c" @@ -571,6 +574,7 @@ def build_platform_qis(self, platform: str): cmake_build_dir.mkdir(parents=True, exist_ok=True) dist_dir = platform_qis_dir / f"python/selene_{platform}_qis_plugin/_dist" dist_dir.mkdir(parents=True, exist_ok=True) + selene_core_include_dir = Path(self.root) / "selene-core/c/include" selene_sim_dist_dir = Path(self.root) / "selene-sim/python/selene_sim/_dist" base_qis_dist_dir = ( Path(self.root) @@ -616,6 +620,7 @@ def build_platform_qis(self, platform: str): ] ), "-DCMAKE_BUILD_TYPE=Release", + f"-DSELENE_CORE_INCLUDE_DIR={selene_core_include_dir}", f"-DCMAKE_PREFIX_PATH={selene_sim_dist_dir};{base_qis_dist_dir}", f"{cmake_source_dir}", ] diff --git a/justfile b/justfile index 58d62fac..1a5331ea 100644 --- a/justfile +++ b/justfile @@ -56,6 +56,13 @@ generate-selene-core-headers: --crate selene-core \ --output selene-core/c/include/selene/gatewire.h + just sync-selene-core-headers + +sync-selene-core-headers: + rm -rf selene-core/python/selene_core/_dist/include + mkdir -p selene-core/python/selene_core/_dist + cp -R selene-core/c/include selene-core/python/selene_core/_dist/include + generate-headers: just generate-selene-core-headers just generate-selene-sim-headers diff --git a/selene-core/c/include/selene/core_types.h b/selene-core/c/include/selene/core_types.h index 25367ce2..4ec003d9 100644 --- a/selene-core/c/include/selene/core_types.h +++ b/selene-core/c/include/selene/core_types.h @@ -1,3 +1,6 @@ +#ifndef SELENE_CORE_TYPES_H +#define SELENE_CORE_TYPES_H + #include #include #include @@ -23,3 +26,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_CORE_TYPES_H */ diff --git a/selene-core/c/include/selene/error_model.h b/selene-core/c/include/selene/error_model.h index f07cdd51..e7a9e80a 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -1,3 +1,6 @@ +#ifndef SELENE_ERROR_MODEL_H +#define SELENE_ERROR_MODEL_H + #include #include #include @@ -6,10 +9,6 @@ #include "selene/core_types.h" #define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL -typedef struct Option_last_error_fn Option_last_error_fn; - -typedef struct Option_plugin_name_fn Option_plugin_name_fn; - typedef struct SeleneErrorModelAPIVersion { /** * Reserved for future use, must be 0. @@ -42,15 +41,21 @@ typedef struct SeleneOperationResultInterface { uint64_t); } SeleneOperationResultInterface; +typedef int32_t SeleneErrno; + +typedef SeleneErrno (*LastErrorFn)(char *output, + size_t output_len, + size_t *written); + +typedef const char *(*PluginNameFn)(void); + typedef struct PluginDescriptorHeaderV1 { uint64_t struct_size; uint64_t api_version; - struct Option_last_error_fn last_error_fn; - struct Option_plugin_name_fn get_name_fn; + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; } PluginDescriptorHeaderV1; -typedef int32_t SeleneErrno; - typedef void *SeleneRuntimeExtractOperationInstance; /** @@ -101,10 +106,6 @@ typedef struct RuntimeExtractOperationHandle { typedef void *SeleneSimulatorInstance; -typedef SeleneErrno (*LastErrorFn)(char *output, - size_t output_len, - size_t *written); - typedef struct OperationResultHandle { SeleneOperationResultInstance instance; struct SeleneOperationResultInterface interface; @@ -191,3 +192,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_ERROR_MODEL_H */ diff --git a/selene-core/c/include/selene/runtime.h b/selene-core/c/include/selene/runtime.h index a2a4a075..dbdcd1bd 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -1,3 +1,6 @@ +#ifndef SELENE_RUNTIME_H +#define SELENE_RUNTIME_H + #include #include #include @@ -6,10 +9,6 @@ #include "selene/core_types.h" #define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL -typedef struct Option_last_error_fn Option_last_error_fn; - -typedef struct Option_plugin_name_fn Option_plugin_name_fn; - typedef struct SeleneRuntimeAPIVersion { /** * Reserved for future use, must be 0. @@ -77,15 +76,21 @@ typedef struct SeleneRuntimeExtractOperationInterface { struct RuntimeGetOperationHandle); } SeleneRuntimeExtractOperationInterface; +typedef int32_t SeleneErrno; + +typedef SeleneErrno (*LastErrorFn)(char *output, + size_t output_len, + size_t *written); + +typedef const char *(*PluginNameFn)(void); + typedef struct PluginDescriptorHeaderV1 { uint64_t struct_size; uint64_t api_version; - struct Option_last_error_fn last_error_fn; - struct Option_plugin_name_fn get_name_fn; + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; } PluginDescriptorHeaderV1; -typedef int32_t SeleneErrno; - typedef void *RuntimeInstance; typedef struct SeleneRuntimePluginDescriptorV1 { @@ -175,3 +180,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_RUNTIME_H */ diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index 07ee5773..5673e078 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -1,3 +1,6 @@ +#ifndef SELENE_SIMULATOR_H +#define SELENE_SIMULATOR_H + #include #include #include @@ -6,10 +9,6 @@ #include "selene/core_types.h" #define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL -typedef struct Option_last_error_fn Option_last_error_fn; - -typedef struct Option_plugin_name_fn Option_plugin_name_fn; - typedef struct SeleneSimulatorAPIVersion { /** * Reserved for future use, must be 0. @@ -31,15 +30,21 @@ typedef struct SeleneSimulatorAPIVersion { typedef void *SeleneSimulatorInstance; +typedef int32_t SeleneErrno; + +typedef SeleneErrno (*LastErrorFn)(char *output, + size_t output_len, + size_t *written); + +typedef const char *(*PluginNameFn)(void); + typedef struct PluginDescriptorHeaderV1 { uint64_t struct_size; uint64_t api_version; - struct Option_last_error_fn last_error_fn; - struct Option_plugin_name_fn get_name_fn; + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; } PluginDescriptorHeaderV1; -typedef int32_t SeleneErrno; - typedef void *RuntimeExtractOperationInstance; /** @@ -149,3 +154,5 @@ GwStatus gw_decoded_gate_qubit_operand_at(const GwDecodedGate *gate, #ifdef __cplusplus } // extern "C" #endif // __cplusplus + +#endif /* SELENE_SIMULATOR_H */ diff --git a/selene-core/cbindgen/core_types.toml b/selene-core/cbindgen/core_types.toml index 8b259086..83bb35fb 100644 --- a/selene-core/cbindgen/core_types.toml +++ b/selene-core/cbindgen/core_types.toml @@ -1,6 +1,6 @@ language = "C" - include_version = false +include_guard = "SELENE_CORE_TYPES_H" namespaces = [] using_namespaces = [] sys_includes = [] diff --git a/selene-core/cbindgen/error_model.toml b/selene-core/cbindgen/error_model.toml index 4afa9313..50259a70 100644 --- a/selene-core/cbindgen/error_model.toml +++ b/selene-core/cbindgen/error_model.toml @@ -2,6 +2,7 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_ERROR_MODEL_H" no_includes = false cpp_compat = true after_includes = "#define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL" diff --git a/selene-core/cbindgen/runtime.toml b/selene-core/cbindgen/runtime.toml index 6da1b95b..098dc478 100644 --- a/selene-core/cbindgen/runtime.toml +++ b/selene-core/cbindgen/runtime.toml @@ -2,6 +2,7 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_RUNTIME_H" no_includes = false cpp_compat = true after_includes = "#define SELENE_RUNTIME_CURRENT_API_VERSION 0x00000300ULL" diff --git a/selene-core/cbindgen/simulator.toml b/selene-core/cbindgen/simulator.toml index d381d231..74b68e4d 100644 --- a/selene-core/cbindgen/simulator.toml +++ b/selene-core/cbindgen/simulator.toml @@ -2,6 +2,7 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_SIMULATOR_H" no_includes = false cpp_compat = true after_includes = "#define SELENE_SIMULATOR_CURRENT_API_VERSION 0x00000101ULL" diff --git a/selene-core/examples/cbindgen.toml b/selene-core/examples/cbindgen.toml deleted file mode 100644 index 847d798c..00000000 --- a/selene-core/examples/cbindgen.toml +++ /dev/null @@ -1,77 +0,0 @@ -language = "C" - -include_version = false -includes = ["selene/core_types.h"] -no_includes = false -cpp_compat = true -after_includes = "" -############################ Code Style Options ################################ -braces = "SameLine" -line_length = 100 -tab_width = 2 -documentation = true -documentation_style = "auto" -documentation_length = "full" -line_endings = "LF" # also "CR", "CRLF", "Native" -############################# Codegen Options ################################## -style = "both" -sort_by = "None" -usize_is_size_t = true - -[export] -include = [ - "RuntimePluginDescriptorV1", - "ErrorModelPluginDescriptorV1", - "SimulatorPluginDescriptorV1", - "ErrorModelAPIVersion", - "ErrorModelInstance", - "OperationResultInterface", - "OperationResultInstance", - "SimulatorAPIVersion", - "SimulatorInstance", - "RuntimeAPIVersion", - "RuntimeGetOperationInterface", - "RuntimeGetOperationInstance", - "RuntimeExtractOperationInterface", - "RuntimeExtractOperationInstance", - "Errno", -] -item_types = ["functions", "structs", "opaque", "enums", "typedefs"] -renaming_overrides_prefixing = false - -[export.rename] -"ErrorModelAPIVersion" = "SeleneErrorModelAPIVersion" -"ErrorModelInstance" = "SeleneErrorModelInstance" -"OperationResultInterface" = "SeleneOperationResultInterface" -"OperationResultInstance" = "SeleneOperationResultInstance" -"SimulatorAPIVersion" = "SeleneSimulatorAPIVersion" -"SimulatorInstance" = "SeleneSimulatorInstance" -"RuntimeAPIVersion" = "SeleneRuntimeAPIVersion" -"RuntimeGetOperationInterface" = "SeleneRuntimeGetOperationInterface" -"RuntimeGetOperationInstance" = "SeleneRuntimeGetOperationInstance" -"RuntimeExtractOperationInterface" = "SeleneRuntimeExtractOperationInterface" -"RuntimeExtractOperationInstance" = "SeleneRuntimeExtractOperationInstance" -"Errno" = "SeleneErrno" - -[export.mangle] -rename_types = "SnakeCase" - -[fn] -rename_args = "None" -args = "vertical" - -[enum] -rename_variants = "None" -add_sentinel = false -prefix_with_name = false -derive_helper_methods = false -derive_const_casts = false -derive_mut_casts = false -derive_tagged_enum_destructor = false -derive_tagged_enum_copy_constructor = false -enum_class = true -private_default_tagged_enum_constructor = false - -[const] -allow_static_const = true -allow_constexpr = false diff --git a/selene-core/examples/error_model/Cargo.lock b/selene-core/examples/error_model/Cargo.lock deleted file mode 100644 index 116962f2..00000000 --- a/selene-core/examples/error_model/Cargo.lock +++ /dev/null @@ -1,588 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures", -] - -[[package]] -name = "cc" -version = "1.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "clap" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "delegate" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_pcg" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "selene-core" -version = "0.3.0-alpha.1" -dependencies = [ - "anyhow", - "bitflags", - "blake3", - "delegate", - "derive_more", - "indexmap", - "libloading", - "smallvec", - "static_assertions", - "thiserror", -] - -[[package]] -name = "selene-error-model-example" -version = "0.2.0" -dependencies = [ - "anyhow", - "clap", - "rand", - "rand_pcg", - "selene-core", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "zerocopy" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/selene-core/examples/error_model/Cargo.toml b/selene-core/examples/error_model/Cargo.toml deleted file mode 100644 index 375d0208..00000000 --- a/selene-core/examples/error_model/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[workspace] - -[package] -name = "selene-error-model-example" -version = "0.2.0" -edition = "2024" - -[lib] -name = "selene_error_model_example" -path = "rust/lib.rs" -crate-type = ["cdylib"] - -[dependencies] -anyhow = "1.0" -rand = "0.9" -rand_pcg = "0.9" -selene-core = { path = "../../" } -clap = { version = "4.5", features = ["derive"] } diff --git a/selene-core/examples/error_model/python/selene_example_error_model_plugin/__init__.py b/selene-core/examples/error_model/python/selene_example_error_model_plugin/__init__.py deleted file mode 100644 index e746dd0c..00000000 --- a/selene-core/examples/error_model/python/selene_example_error_model_plugin/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import ExampleErrorModel - -__all__ = ["ExampleErrorModel"] diff --git a/selene-core/examples/error_model/python/selene_example_error_model_plugin/plugin.py b/selene-core/examples/error_model/python/selene_example_error_model_plugin/plugin.py deleted file mode 100644 index 701b7716..00000000 --- a/selene-core/examples/error_model/python/selene_example_error_model_plugin/plugin.py +++ /dev/null @@ -1,34 +0,0 @@ -import platform -from dataclasses import dataclass -from pathlib import Path - -from selene_core import ErrorModel - - -@dataclass -class ExampleErrorModel(ErrorModel): - """ - A plugin for simulating an example error model. - """ - - flip_probability: float = 0.0 - angle_mutation: float = 0.0 - - def get_init_args(self): - return [ - f"--flip_probability={self.flip_probability}", - f"--angle_mutation={self.angle_mutation}", - ] - - @property - def library_file(self): - libdir = Path(__file__).parent / "_dist/lib/" - match platform.system(): - case "Linux": - return libdir / "libselene_error_model_example.so" - case "Darwin": - return libdir / "libselene_error_model_example.dylib" - case "Windows": - return libdir / "selene_error_model_example.dll" - case _: - raise RuntimeError(f"Unsupported platform: {platform.system()}") diff --git a/selene-core/examples/error_model/rust/lib.rs b/selene-core/examples/error_model/rust/lib.rs deleted file mode 100644 index d1da86fa..00000000 --- a/selene-core/examples/error_model/rust/lib.rs +++ /dev/null @@ -1,311 +0,0 @@ -use anyhow::{Result, anyhow}; -use clap::Parser; -use rand::{Rng, SeedableRng}; -use rand_pcg::Pcg64Mcg; -use selene_core::error_model::interface::ErrorModelInterfaceFactory; -use selene_core::error_model::{BatchResult, ErrorModelInterface}; -use selene_core::export_error_model_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; -use selene_core::simulator::SimulatorInterface; -use selene_core::utils::MetricValue; - -#[derive(Parser, Debug)] -struct Params { - #[arg(long, default_value = "0.01")] - flip_probability: f64, - #[arg(long, default_value = "0.01")] - angle_mutation: f64, - #[arg(long, default_value = "0.01")] - leak_probability: f64, -} - -#[derive(Default, Debug)] -struct Stats { - flips_induced: u64, - leaks_induced: u64, - total_angle_error: f64, -} - -pub struct ExampleErrorModel { - rng: Pcg64Mcg, - error_params: Params, - stats: Stats, - leakage_map: Vec, -} - -impl ExampleErrorModel { - fn mutate_angle(&mut self, angle: f64) -> f64 { - let offset = self - .rng - .random_range(-self.error_params.angle_mutation..self.error_params.angle_mutation); - self.stats.total_angle_error += offset.abs(); - angle + offset - } - - fn should_flip(&mut self) -> bool { - self.rng.random_bool(self.error_params.flip_probability) - } - - fn should_leak(&mut self) -> bool { - self.rng.random_bool(self.error_params.leak_probability) - } - - fn flip_qubit(&mut self, simulator: &mut dyn SimulatorInterface, qubit_id: u64) -> Result<()> { - self.apply_simulator_void( - simulator, - Operation::phased_x(qubit_id, std::f64::consts::PI, 0.0)?, - )?; - self.stats.flips_induced += 1; - Ok(()) - } - - fn apply_simulator_void( - &mut self, - simulator: &mut dyn SimulatorInterface, - operation: Operation, - ) -> Result<()> { - let results = simulator.handle_operations(BatchOperation::error_model(vec![operation]))?; - if results.bool_results.is_empty() && results.u64_results.is_empty() { - Ok(()) - } else { - Err(anyhow!( - "ExampleErrorModel: simulator unexpectedly produced results for a non-measurement operation" - )) - } - } - - fn measure_simulator( - &mut self, - simulator: &mut dyn SimulatorInterface, - qubit_id: u64, - ) -> Result { - let results = simulator.handle_operations(BatchOperation::error_model(vec![ - Operation::Measure { - qubit_id, - result_id: 0, - }, - ]))?; - if results.u64_results.is_empty() && results.bool_results.len() == 1 { - Ok(results.bool_results[0].value) - } else { - Err(anyhow!( - "ExampleErrorModel: simulator returned an unexpected measurement result shape" - )) - } - } - - fn handle_gate( - &mut self, - operation: Operation, - simulator: &mut dyn SimulatorInterface, - ) -> Result<()> { - match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => { - let theta = self.mutate_angle(theta); - let phi = self.mutate_angle(phi); - self.apply_simulator_void(simulator, Operation::phased_x(qubit_id, theta, phi)?)?; - if self.should_flip() { - self.flip_qubit(simulator, qubit_id)?; - } - if self.should_leak() { - self.stats.leaks_induced += 1; - self.leakage_map[qubit_id as usize] = true; - } - } - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => { - let theta = self.mutate_angle(theta); - let mut leaked_1 = self.leakage_map[qubit_id_1 as usize]; - let mut leaked_2 = self.leakage_map[qubit_id_2 as usize]; - match (leaked_1, leaked_2) { - (false, true) => { - self.leakage_map[qubit_id_1 as usize] = true; - leaked_1 = true; - } - (true, false) => { - self.leakage_map[qubit_id_2 as usize] = true; - leaked_2 = true; - } - (false, false) => { - if self.should_leak() { - self.stats.leaks_induced += 2; - self.leakage_map[qubit_id_1 as usize] = true; - self.leakage_map[qubit_id_2 as usize] = true; - leaked_1 = true; - leaked_2 = true; - } - } - _ => {} - } - if !leaked_1 && !leaked_2 { - self.apply_simulator_void( - simulator, - Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?, - )?; - if self.should_flip() { - self.flip_qubit(simulator, qubit_id_1)?; - self.flip_qubit(simulator, qubit_id_2)?; - } - } - } - Some(BuiltinGate::RZ { qubit_id, theta }) => { - let theta = self.mutate_angle(theta); - self.apply_simulator_void(simulator, Operation::rz(qubit_id, theta)?)?; - if self.should_flip() { - self.flip_qubit(simulator, qubit_id)?; - } - if self.should_leak() { - self.stats.leaks_induced += 1; - self.leakage_map[qubit_id as usize] = true; - } - } - Some(BuiltinGate::PhasedXX { .. }) | None => { - self.apply_simulator_void(simulator, operation)?; - } - } - Ok(()) - } -} - -impl ErrorModelInterface for ExampleErrorModel { - fn exit(&mut self) -> Result<()> { - Ok(()) - } - - fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { - self.rng = Pcg64Mcg::seed_from_u64(seed); - Ok(()) - } - - fn shot_end(&mut self) -> Result<()> { - Ok(()) - } - - fn handle_operations( - &mut self, - operations: BatchOperation, - simulator: &mut dyn SimulatorInterface, - ) -> Result { - let mut results = BatchResult::default(); - for operation in operations { - match operation { - Operation::Gate { .. } => self.handle_gate(operation, simulator)?, - Operation::Measure { - qubit_id, - result_id, - } => { - let measurement = if self.leakage_map[qubit_id as usize] { - self.rng.random_bool(0.9) - } else { - let true_measurement = self.measure_simulator(simulator, qubit_id)?; - if self.should_flip() { - self.stats.flips_induced += 1; - !true_measurement - } else { - true_measurement - } - }; - results.set_bool_result(result_id, measurement); - } - Operation::MeasureLeaked { - qubit_id, - result_id, - } => { - let measurement = if self.leakage_map[qubit_id as usize] { - 2 - } else { - let true_measurement = self.measure_simulator(simulator, qubit_id)?; - if self.should_flip() { - self.stats.flips_induced += 1; - (!true_measurement) as u64 - } else { - true_measurement as u64 - } - }; - results.set_u64_result(result_id, measurement); - } - Operation::Reset { qubit_id } => { - self.leakage_map[qubit_id as usize] = false; - self.apply_simulator_void(simulator, Operation::Reset { qubit_id })?; - if self.should_flip() { - self.flip_qubit(simulator, qubit_id)?; - } - } - Operation::Postselect { - qubit_id, - target_value, - } => self.apply_simulator_void( - simulator, - Operation::Postselect { - qubit_id, - target_value, - }, - )?, - Operation::Custom { .. } => {} - _ => {} - } - } - Ok(results) - } - - fn get_metric(&mut self, nth_metric: u8) -> Result> { - match nth_metric { - 0 => Ok(Some(( - "flips_induced".to_string(), - MetricValue::U64(self.stats.flips_induced), - ))), - 1 => Ok(Some(( - "total_angle_error".to_string(), - MetricValue::F64(self.stats.total_angle_error), - ))), - 2 => Ok(Some(( - "leaks_induced".to_string(), - MetricValue::U64(self.stats.leaks_induced), - ))), - 3 => Ok(None), - _ => Err(anyhow!( - "Selene requested an out of bounds metric: {}", - nth_metric - )), - } - } -} - -#[derive(Default)] -pub struct ExampleErrorModelFactory; - -impl ErrorModelInterfaceFactory for ExampleErrorModelFactory { - type Interface = ExampleErrorModel; - - fn name(&self) -> &str { - "ExampleErrorModel" - } - - fn init( - self: std::sync::Arc, - n_qubits: u64, - error_model_args: &[impl AsRef], - ) -> Result> { - match Params::try_parse_from(error_model_args.iter().map(|s| s.as_ref())) { - Err(e) => Err(anyhow!( - "Error parsing arguments to the example error model plugin: {}", - e - )), - Ok(params) => Ok(Box::new(ExampleErrorModel { - rng: Pcg64Mcg::seed_from_u64(0), - error_params: params, - stats: Stats::default(), - leakage_map: vec![false; n_qubits as usize], - })), - } - } -} - -export_error_model_plugin!(crate::ExampleErrorModelFactory); diff --git a/selene-core/examples/runtime/.gitignore b/selene-core/examples/runtime/.gitignore deleted file mode 100644 index c8f04429..00000000 --- a/selene-core/examples/runtime/.gitignore +++ /dev/null @@ -1,72 +0,0 @@ -/target - -# Byte-compiled / optimized / DLL files -__pycache__/ -.pytest_cache/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -.venv/ -env/ -bin/ -build/ -develop-eggs/ -dist/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -include/ -man/ -venv/ -*.egg-info/ -.installed.cfg -*.egg - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt -pip-selfcheck.json - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.cache -nosetests.xml -coverage.xml - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Rope -.ropeproject - -# Django stuff: -*.log -*.pot - -.DS_Store - -# Sphinx documentation -docs/_build/ - -# PyCharm -.idea/ - -# VSCode -.vscode/ - -# Pyenv -.python-version diff --git a/selene-core/examples/runtime/Cargo.lock b/selene-core/examples/runtime/Cargo.lock deleted file mode 100644 index cc0f4645..00000000 --- a/selene-core/examples/runtime/Cargo.lock +++ /dev/null @@ -1,347 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" - -[[package]] -name = "bitflags" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures", -] - -[[package]] -name = "cc" -version = "1.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "delegate" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "selene-core" -version = "0.3.0-alpha.1" -dependencies = [ - "anyhow", - "bitflags", - "blake3", - "delegate", - "derive_more", - "indexmap", - "libloading", - "smallvec", - "static_assertions", - "thiserror", -] - -[[package]] -name = "selene-example-runtime" -version = "0.2.0" -dependencies = [ - "anyhow", - "selene-core", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "2.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/selene-core/examples/runtime/Cargo.toml b/selene-core/examples/runtime/Cargo.toml deleted file mode 100644 index ea66745d..00000000 --- a/selene-core/examples/runtime/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[workspace] - -[package] -name = "selene-example-runtime" -edition = "2024" -version = "0.2.0" - -[lib] -name = "selene_example_runtime" -path = "rust/lib.rs" -crate-type = ["cdylib"] - -[dependencies] -anyhow = "1.0" -selene-core = { path = "../../" } diff --git a/selene-core/examples/runtime/python/selene_example_runtime/__init__.py b/selene-core/examples/runtime/python/selene_example_runtime/__init__.py deleted file mode 100644 index 31b859cd..00000000 --- a/selene-core/examples/runtime/python/selene_example_runtime/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import ExampleRuntime - -__all__ = ["ExampleRuntime"] diff --git a/selene-core/examples/runtime/python/selene_example_runtime/plugin.py b/selene-core/examples/runtime/python/selene_example_runtime/plugin.py deleted file mode 100644 index 4c799729..00000000 --- a/selene-core/examples/runtime/python/selene_example_runtime/plugin.py +++ /dev/null @@ -1,36 +0,0 @@ -import platform -from dataclasses import dataclass -from pathlib import Path - -from selene_core import Runtime - - -@dataclass -class ExampleRuntime(Runtime): - """ - A plugin for running a example runtime in selene. - - It is a lazy runtime, so when the user program requests a gate to be performed, it is - not performed immediately, but is stored in a queue. Upon the request for a measurement - result, operations before and including the measurement are performed in order to - retrieve the result. - """ - - @property - def library_file(self): - libdir = Path(__file__).parent / "_dist/lib/" - match platform.system(): - case "Linux": - return libdir / "libselene_example_runtime.so" - case "Darwin": - return libdir / "libselene_example_runtime.dylib" - case "Windows": - return libdir / "selene_example_runtime.dll" - case _: - raise RuntimeError(f"Unsupported platform: {platform.system()}") - - def get_init_args(self): - """ - There are no init args for the example runtime. - """ - return [] diff --git a/selene-core/examples/runtime/rust/lib.rs b/selene-core/examples/runtime/rust/lib.rs deleted file mode 100644 index f3621720..00000000 --- a/selene-core/examples/runtime/rust/lib.rs +++ /dev/null @@ -1,308 +0,0 @@ -use std::collections::VecDeque; - -use anyhow::{bail, Result}; -use selene_core::{ - export_runtime_plugin, - gatewire::OwnedGateInstance, - runtime::{ - interface::RuntimeInterfaceFactory, BatchOperation, BuiltinGate, Operation, - RuntimeInterface, - }, - utils::MetricValue, -}; - -#[derive(Debug, Clone, PartialEq)] -enum QubitStatus { - Free, - Active { phase: f64 }, -} - -// We choose to encode a future bool or future u64 -// result as a u64 value with a boolean 'is_set' flag. -// User code should be careful to read the correct type. -#[derive(Debug, Clone)] -struct FutureResult { - is_set: bool, - value: u64, -} - - -struct ExampleRuntime { - qubits: Vec, - operation_queue: VecDeque, - flush_size: usize, - future_results: Vec, - start: selene_core::time::Instant, -} - -impl ExampleRuntime { - pub fn new(n_qubits: u64, start: selene_core::time::Instant) -> Self { - Self { - qubits: vec![QubitStatus::Free; n_qubits as usize], - operation_queue: VecDeque::with_capacity(10000), - flush_size: 0, - future_results: Vec::with_capacity(1000), - start, - } - } - - pub fn push(&mut self, op: Operation) { - self.operation_queue.push_back(BatchOperation::runtime( - vec![op], - self.start, - Default::default(), - )); - } -} - -impl RuntimeInterface for ExampleRuntime { - fn exit(&mut self) -> Result<()> { - self.operation_queue.clear(); - self.qubits.clear(); - self.flush_size = 0; - self.future_results.clear(); - Ok(()) - } - fn get_next_operations(&mut self) -> Result> { - debug_assert!( - self.flush_size <= self.operation_queue.len(), - "flush size is greater than operation queue length" - ); - if self.flush_size == 0 { - return Ok(None); - } - self.flush_size -= 1; - Ok(self.operation_queue.pop_front()) - } - - fn shot_start(&mut self, _shot_id: u64, _seed: u64) -> Result<()> { - Ok(()) - } - fn shot_end(&mut self) -> Result<()> { - self.qubits = vec![QubitStatus::Free; self.qubits.len()]; - self.operation_queue.clear(); - self.flush_size = 0; - self.future_results.clear(); - Ok(()) - } - fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { - self.flush_size = self.operation_queue.len(); - Ok(()) - } - fn local_barrier(&mut self, qubits: &[u64], _sleep_ns: u64) -> Result<()> { - let mut last_op_using_qubits = 0; - for (i, operation) in self - .operation_queue - .iter() - .skip(self.flush_size) - .enumerate() - { - for op in operation.iter_ops() { - if op - .get_qubit_ids() - .iter() - .any(|qubit_id| qubits.contains(qubit_id)) - { - last_op_using_qubits = i; - } - } - } - self.flush_size = std::cmp::max(self.flush_size, last_op_using_qubits + 1); - Ok(()) - } - // Allocation - fn qalloc(&mut self) -> Result { - for (i, qubit) in self.qubits.iter_mut().enumerate() { - if *qubit == QubitStatus::Free { - *qubit = QubitStatus::Active { phase: 0.0 }; - return Ok(i as u64); - } - } - Ok(u64::MAX) - } - fn qfree(&mut self, qubit_id: u64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("freeing out-of-bounds qubit {qubit_id}") - } else { - self.qubits[qubit_id as usize] = QubitStatus::Free; - Ok(()) - } - } - fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { - match Operation::from_gate_instance(gate.clone())?.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying PhasedX gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.push(Operation::phased_x(qubit_id, theta, phi - phase)?); - } - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => { - if qubit_id_1 >= self.qubits.len() as u64 { - bail!("applying ZZPhase gate to out-of-bounds qubit1 {qubit_id_1}"); - } - if qubit_id_2 >= self.qubits.len() as u64 { - bail!("applying ZZPhase gate to out-of-bounds qubit2 {qubit_id_2}"); - } - self.push(Operation::zz_phase(qubit_id_1, qubit_id_2, theta)?); - } - Some(BuiltinGate::RZ { qubit_id, theta }) => { - if qubit_id >= self.qubits.len() as u64 { - bail!("applying RZ gate to out-of-bounds qubit {qubit_id}"); - } - let QubitStatus::Active { phase } = self.qubits[qubit_id as usize] else { - bail!("Qubit {qubit_id} is not active"); - }; - self.qubits[qubit_id as usize] = QubitStatus::Active { - phase: phase + theta, - }; - } - Some(BuiltinGate::PhasedXX { .. }) => { - bail!("ExampleRuntime does not support PhasedXX gates"); - } - None => bail!("ExampleRuntime does not support custom gates"), - } - Ok(()) - } - // Lifetime ops - fn measure(&mut self, qubit_id: u64) -> Result { - if qubit_id >= self.qubits.len() as u64 { - bail!("measuring out-of-bounds qubit {qubit_id}") - } - let result_id = self.future_results.len() as u64; - self.future_results.push(FutureResult { - is_set: false, - value: 0, - }); - self.push(Operation::Measure { - qubit_id, - result_id, - }); - Ok(result_id) - } - fn measure_leaked(&mut self, qubit_id: u64) -> Result { - if qubit_id >= self.qubits.len() as u64 { - bail!("measuring out-of-bounds qubit {qubit_id}") - } - let result_id = self.future_results.len() as u64; - self.future_results.push(FutureResult { - is_set: false, - value: 0, - }); - self.push(Operation::MeasureLeaked { - qubit_id, - result_id, - }); - Ok(result_id) - } - fn reset(&mut self, qubit_id: u64) -> Result<()> { - if qubit_id >= self.qubits.len() as u64 { - bail!("resetting out-of-bounds qubit {qubit_id}") - } - self.push(Operation::Reset { qubit_id }); - Ok(()) - } - fn force_result(&mut self, result_id: u64) -> Result<()> { - if result_id >= self.future_results.len() as u64 { - bail!("forcing out-of-bounds measurement {result_id}") - } - for (i, operation) in self.operation_queue.iter().enumerate().rev() { - for op in operation.iter_ops() { - match op { - Operation::Measure { result_id: id, .. } - | Operation::MeasureLeaked { result_id: id, .. } => { - if *id == result_id { - self.flush_size = std::cmp::max(self.flush_size, i + 1); - return Ok(()); - } - } - _ => {} - } - } - } - bail!("No measurement operation with result {result_id} found") - } - fn get_bool_result(&mut self, result_id: u64) -> Result> { - if result_id >= self.future_results.len() as u64 { - bail!("getting out-of-bounds measurement {result_id}"); - } - let result = &self.future_results[result_id as usize]; - Ok(if result.is_set { - Some(result.value > 0) - } else { - None - }) - } - fn get_u64_result(&mut self, result_id: u64) -> Result> { - if result_id >= self.future_results.len() as u64 { - bail!("getting out-of-bounds measurement {result_id}"); - } - let result = &self.future_results[result_id as usize]; - Ok(if result.is_set { - Some(result.value) - } else { - None - }) - } - fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { - if result_id >= self.future_results.len() as u64 { - bail!("setting out-of-bounds measurement {result_id}"); - } - self.future_results[result_id as usize] = FutureResult { - is_set: true, - value: result.into(), - }; - Ok(()) - } - fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { - if result_id >= self.future_results.len() as u64 { - bail!("setting out-of-bounds measurement {result_id}"); - } - self.future_results[result_id as usize] = FutureResult { - is_set: true, - value: result, - }; - Ok(()) - } - fn increment_future_refcount(&mut self, _future_ref: u64) -> Result<()> { - Ok(()) - } - fn decrement_future_refcount(&mut self, _future_ref: u64) -> Result<()> { - Ok(()) - } - fn get_metric(&mut self, _nth_metric: u8) -> Result> { - Ok(None) - } -} - -#[derive(Default)] -struct ExampleRuntimeFactory; - -impl RuntimeInterfaceFactory for ExampleRuntimeFactory { - type Interface = ExampleRuntime; - - fn name(&self) -> &str { - "ExampleRuntime" - } - - fn init( - self: std::sync::Arc, - n_qubits: u64, - start: selene_core::time::Instant, - _args: &[impl AsRef], - ) -> Result> { - Ok(Box::new(ExampleRuntime::new(n_qubits, start))) - } -} - -export_runtime_plugin!(crate::ExampleRuntimeFactory); diff --git a/selene-core/examples/simulator/Cargo.lock b/selene-core/examples/simulator/Cargo.lock deleted file mode 100644 index 6103e220..00000000 --- a/selene-core/examples/simulator/Cargo.lock +++ /dev/null @@ -1,613 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "blake3" -version = "1.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "cpufeatures", -] - -[[package]] -name = "cc" -version = "1.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "clap" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "delegate" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "rand" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_pcg" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "selene-core" -version = "0.3.0-alpha.1" -dependencies = [ - "anyhow", - "bitflags", - "blake3", - "delegate", - "derive_more", - "indexmap", - "libloading", - "smallvec", - "static_assertions", - "thiserror", -] - -[[package]] -name = "selene-example-simulator" -version = "0.2.0" -dependencies = [ - "anyhow", - "approx", - "clap", - "rand", - "rand_pcg", - "selene-core", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "zerocopy" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/selene-core/examples/simulator/Cargo.toml b/selene-core/examples/simulator/Cargo.toml deleted file mode 100644 index ab879c47..00000000 --- a/selene-core/examples/simulator/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[workspace] - -[package] -name = "selene-example-simulator" -version = "0.2.0" -edition = "2024" - -[lib] -name = "selene_example_simulator" -path = "rust/lib.rs" -crate-type = ["cdylib"] - -[dependencies] -clap = { version = "4.5", features = ["derive"] } -rand = "0.9" -anyhow = "1.0" -approx = "0.5" -rand_pcg = "0.9" -selene-core = { path = "../../" } diff --git a/selene-core/examples/simulator/python/selene_example_simulator_plugin/__init__.py b/selene-core/examples/simulator/python/selene_example_simulator_plugin/__init__.py deleted file mode 100644 index 863de6c4..00000000 --- a/selene-core/examples/simulator/python/selene_example_simulator_plugin/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .plugin import ExampleSimulator - -__all__ = ["ExampleSimulator"] diff --git a/selene-core/examples/simulator/python/selene_example_simulator_plugin/plugin.py b/selene-core/examples/simulator/python/selene_example_simulator_plugin/plugin.py deleted file mode 100644 index 78be1ed5..00000000 --- a/selene-core/examples/simulator/python/selene_example_simulator_plugin/plugin.py +++ /dev/null @@ -1,38 +0,0 @@ -import platform -from dataclasses import dataclass -from pathlib import Path - -from selene_core import Simulator - - -@dataclass -class ExampleSimulator(Simulator): - """ - A plugin for using an example simulator in selene. - Attributes: - bias (float): The bias of the coinflip simulator. Must be between 0 and 1 (both inclusive). - The greater this value, the more likely a measurement will return True. - """ - - bias: float = 0.5 - - def __post_init__(self): - assert 0 <= self.bias <= 1, "bias must be between 0 and 1 (both inclusive)" - - def get_init_args(self): - return [ - f"--bias={self.bias}", - ] - - @property - def library_file(self): - libdir = Path(__file__).parent / "_dist/lib/" - match platform.system(): - case "Linux": - return libdir / "libselene_example_simulator_plugin.so" - case "Darwin": - return libdir / "libselene_example_simulator_plugin.dylib" - case "Windows": - return libdir / "selene_example_simulator_plugin.dll" - case _: - raise RuntimeError(f"Unsupported platform: {platform.system()}") diff --git a/selene-core/examples/simulator/rust/lib.rs b/selene-core/examples/simulator/rust/lib.rs deleted file mode 100644 index 81c065e4..00000000 --- a/selene-core/examples/simulator/rust/lib.rs +++ /dev/null @@ -1,224 +0,0 @@ -use anyhow::{anyhow, Result}; -use clap::Parser; -use rand::{Rng, SeedableRng}; -use rand_pcg::Pcg64Mcg; -use selene_core::error_model::BatchResult; -use selene_core::export_simulator_plugin; -use selene_core::runtime::{BatchOperation, BuiltinGate, Operation}; -use selene_core::simulator::interface::SimulatorInterfaceFactory; -use selene_core::simulator::SimulatorInterface; -use selene_core::utils::MetricValue; - -#[derive(Parser, Debug)] -struct Params { - #[arg(long)] - bias: f64, -} - -pub struct ExampleSimulator { - n_qubits: u64, - rng: Pcg64Mcg, - bias: f64, - total_flips: u64, - true_flips: u64, -} -impl ExampleSimulator { - pub fn flip(&mut self) -> bool { - self.rng.random::() < self.bias - } - pub fn get_observed_bias(&self) -> f64 { - self.true_flips as f64 / self.total_flips as f64 - } - - fn phased_x(&mut self, q0: u64, _theta: f64, _phi: f64) -> Result<()> { - if q0 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "PhasedX(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn zz_phase(&mut self, q0: u64, q1: u64, _theta: f64) -> Result<()> { - if q0 < self.n_qubits && q1 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "ZZPhase(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn rz(&mut self, q0: u64, _theta: f64) -> Result<()> { - if q0 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "RZ(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn phased_xx(&mut self, q0: u64, q1: u64, _theta: f64, _phi: f64) -> Result<()> { - if q0 < self.n_qubits && q1 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "PhasedXX(q0={q0}, q1={q1}) is out of bounds. q0 and q1 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn measure(&mut self, q0: u64) -> Result { - if q0 < self.n_qubits { - self.total_flips += 1; - if self.flip() { - self.true_flips += 1; - Ok(true) - } else { - Ok(false) - } - } else { - Err(anyhow!( - "Measure(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn reset(&mut self, q0: u64) -> Result<()> { - if q0 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "Reset(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } -} - -impl SimulatorInterface for ExampleSimulator { - fn exit(&mut self) -> Result<()> { - Ok(()) - } - - fn shot_start(&mut self, _shot_id: u64, seed: u64) -> Result<()> { - self.rng = Pcg64Mcg::seed_from_u64(seed); - Ok(()) - } - - fn shot_end(&mut self) -> Result<()> { - self.true_flips = 0; - self.total_flips = 0; - Ok(()) - } - - fn handle_operations(&mut self, operations: BatchOperation) -> Result { - let mut results = BatchResult::default(); - for operation in operations { - match operation { - Operation::Gate { .. } => match operation.as_builtin_gate()? { - Some(BuiltinGate::PhasedX { - qubit_id, - theta, - phi, - }) => self.phased_x(qubit_id, theta, phi)?, - Some(BuiltinGate::ZZPhase { - qubit_id_1, - qubit_id_2, - theta, - }) => self.zz_phase(qubit_id_1, qubit_id_2, theta)?, - Some(BuiltinGate::RZ { qubit_id, theta }) => self.rz(qubit_id, theta)?, - Some(BuiltinGate::PhasedXX { - qubit_id_1, - qubit_id_2, - theta, - phi, - }) => self.phased_xx(qubit_id_1, qubit_id_2, theta, phi)?, - None => {} - }, - Operation::Measure { - qubit_id, - result_id, - } => results.set_bool_result(result_id, self.measure(qubit_id)?), - Operation::MeasureLeaked { - qubit_id, - result_id, - } => results.set_u64_result(result_id, self.measure(qubit_id)? as u64), - Operation::Reset { qubit_id } => self.reset(qubit_id)?, - Operation::Postselect { - qubit_id, - target_value, - } => self.postselect(qubit_id, target_value)?, - Operation::Custom { .. } => {} - _ => {} - } - } - Ok(results) - } - - fn postselect(&mut self, q0: u64, _target_value: bool) -> Result<()> { - if q0 < self.n_qubits { - Ok(()) - } else { - Err(anyhow!( - "Postselect(q0={q0}) is out of bounds. q0 must be less than the number of qubits ({}).", - self.n_qubits - )) - } - } - - fn get_metric(&mut self, nth_metric: u8) -> Result> { - match nth_metric { - 0 => { - if self.total_flips == 0 { - Ok(None) - } else { - Ok(Some(( - "observed_bias".to_string(), - MetricValue::F64(self.get_observed_bias()), - ))) - } - } - _ => Ok(None), - } - } -} - -#[derive(Default)] -pub struct ExampleSimulatorFactory; - -impl SimulatorInterfaceFactory for ExampleSimulatorFactory { - type Interface = ExampleSimulator; - - fn name(&self) -> &str { - "ExampleSimulator" - } - - fn init( - self: std::sync::Arc, - n_qubits: u64, - args: &[impl AsRef], - ) -> Result> { - let args: Vec = args.iter().map(|s| s.as_ref().to_string()).collect(); - - match Params::try_parse_from(args) { - Err(e) => Err(anyhow!("Error parsing arguments to example plugin: {}", e)), - Ok(params) => Ok(Box::new(ExampleSimulator { - n_qubits, - rng: Pcg64Mcg::seed_from_u64(0), - bias: params.bias, - total_flips: 0, - true_flips: 0, - })), - } - } -} - -export_simulator_plugin!(crate::ExampleSimulatorFactory); diff --git a/selene-core/pyproject.toml b/selene-core/pyproject.toml index 995c41bc..bf0b5055 100644 --- a/selene-core/pyproject.toml +++ b/selene-core/pyproject.toml @@ -44,9 +44,6 @@ path = "hatch_build.py" [tool.uv] cache-keys = [ - { file = "examples/**/*.rs" }, - { file = "examples/**/Cargo.lock" }, - { file = "examples/**/Cargo.toml" }, { file = "python/selene_core/trace.py" }, { file = "c/include/selene/*.h" }, { file = "Cargo.toml" }, diff --git a/selene-core/python/selene_core/c_abi/_cffi.py b/selene-core/python/selene_core/c_abi/_cffi.py index 60ffe80a..4fee3315 100644 --- a/selene-core/python/selene_core/c_abi/_cffi.py +++ b/selene-core/python/selene_core/c_abi/_cffi.py @@ -36,17 +36,28 @@ def _cdef_header(path: Path) -> str: return "\n".join(lines) +_EXTRA_DECLARATIONS = { + "runtime.h": """ + extern const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1; + const SeleneRuntimePluginDescriptorV1 *selene_runtime_get_plugin_descriptor_v1(void); + """, + "simulator.h": """ + extern const SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1; + const SeleneSimulatorPluginDescriptorV1 *selene_simulator_get_plugin_descriptor_v1(void); + """, +} + + @cache -def ffi(extra_headers: tuple[Path, ...] = ()) -> FFI: +def ffi( + extra_headers: tuple[Path, ...] = (), + builtin_headers: tuple[str, ...] = ("gatewire.h",), +) -> FFI: result = FFI() - for builtin_header in ( - "gatewire.h", - "operation.h", - "plugin.h", - "simulator.h", - "runtime.h", - ): + for builtin_header in builtin_headers: result.cdef(_cdef_header(_header_path(builtin_header)), override=True) + if extra_declarations := _EXTRA_DECLARATIONS.get(builtin_header): + result.cdef(extra_declarations, override=True) for extra_header in extra_headers: result.cdef(_cdef_header(extra_header), override=True) return result diff --git a/selene-core/python/selene_core/c_abi/simulator.py b/selene-core/python/selene_core/c_abi/simulator.py index aec1b287..507f193b 100644 --- a/selene-core/python/selene_core/c_abi/simulator.py +++ b/selene-core/python/selene_core/c_abi/simulator.py @@ -12,7 +12,6 @@ def __init__(self, cffi: Any) -> None: self.runtime_extract_operation_handle = cffi.typeof( "RuntimeExtractOperationHandle *" ) - self.runtime_get_operation_handle = cffi.typeof("RuntimeGetOperationHandle *") self.operation_result_handle = cffi.typeof("OperationResultHandle *") self.uint8_array = cffi.typeof("uint8_t[]") self.uint8_ptr = cffi.typeof("uint8_t *") diff --git a/selene-core/rust/error_model.rs b/selene-core/rust/error_model.rs index 327729f5..9b66407b 100644 --- a/selene-core/rust/error_model.rs +++ b/selene-core/rust/error_model.rs @@ -108,7 +108,7 @@ impl ErrorModel { fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { plugin_utils::check_plugin_errno_with_context( errno, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, || anyhow!("{}", self.context(message)), ) } @@ -176,7 +176,7 @@ impl ErrorModelInterface for ErrorModel { &self.label(), self.handle.instance, self.handle.interface.negotiate_gateset_fn, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, gateset, ) } diff --git a/selene-core/rust/error_model/helper.rs b/selene-core/rust/error_model/helper.rs index 0ba7063d..a44909ff 100644 --- a/selene-core/rust/error_model/helper.rs +++ b/selene-core/rust/error_model/helper.rs @@ -399,7 +399,7 @@ macro_rules! export_error_model_plugin { ErrorModelPluginDescriptorV1, current_api_version().as_u64(), { - last_error_fn: Some(selene_error_model_last_error), + last_error_fn: selene_error_model_last_error, get_name_fn: selene_error_model_get_name, init_fn: Some(selene_error_model_init), exit_fn: Some(selene_error_model_exit), diff --git a/selene-core/rust/error_model/plugin.rs b/selene-core/rust/error_model/plugin.rs index 56fb14e3..b315ae6e 100644 --- a/selene-core/rust/error_model/plugin.rs +++ b/selene-core/rust/error_model/plugin.rs @@ -132,16 +132,10 @@ impl ErrorModelPluginInterface { validate_descriptor_v1(&descriptor, |api_version| { ErrorModelAPIVersion::from(api_version).validate() })?; - let get_name_fn = - require_callback("Error model", "get_name_fn", descriptor.header.get_name_fn)?; - let name = read_plugin_name("Error model", get_name_fn)?; + let name = read_plugin_name("Error model", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, - last_error_fn: require_callback( - "Error model", - "last_error_fn", - descriptor.header.last_error_fn, - )?, + last_error_fn: descriptor.header.last_error_fn, name, init_fn: require_callback("Error model", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, @@ -184,7 +178,7 @@ impl ErrorModelInterfaceFactory for ErrorModelPluginInterface { unsafe { (self.init_fn)(&mut instance, n_qubits, error_model_argc, error_model_argv) }, - Some(self.last_error_fn), + self.last_error_fn, || anyhow!("ErrorModelPluginInterface: init failed"), ) })?; @@ -207,21 +201,21 @@ impl ErrorModelInterface for ErrorModelPlugin { }; check_plugin_errno( unsafe { exit_fn(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: exit failed"), ) } fn shot_start(&mut self, shot_id: u64, error_model_seed: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, error_model_seed) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: shot_start failed"), ) } fn shot_end(&mut self) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: shot_end failed"), ) } @@ -231,7 +225,7 @@ impl ErrorModelInterface for ErrorModelPlugin { "ErrorModelPlugin", self.instance, self.interface.negotiate_gateset_fn, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, gateset, ) } @@ -252,7 +246,7 @@ impl ErrorModelInterface for ErrorModelPlugin { unsafe { (self.interface.handle_operations_fn)(self.instance, batch, simulator, result) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: handle_operations failed"), )?; Ok(result_builder.finish()) diff --git a/selene-core/rust/macros.rs b/selene-core/rust/macros.rs index 71edb390..026fbd3c 100644 --- a/selene-core/rust/macros.rs +++ b/selene-core/rust/macros.rs @@ -16,7 +16,7 @@ macro_rules! export_plugin_descriptor_v1 { struct_size: core::mem::size_of::<$descriptor_ty>() as u64, api_version: $api_version, last_error_fn: $last_error_fn, - get_name_fn: Some($get_name_fn), + get_name_fn: $get_name_fn, }, $($field: $value,)* }; diff --git a/selene-core/rust/plugin.rs b/selene-core/rust/plugin.rs index f6dffc0c..201eff3f 100644 --- a/selene-core/rust/plugin.rs +++ b/selene-core/rust/plugin.rs @@ -16,8 +16,8 @@ pub type PluginNameFn = unsafe extern "C" fn() -> *const c_char; pub struct PluginDescriptorHeaderV1 { pub struct_size: u64, pub api_version: u64, - pub last_error_fn: Option, - pub get_name_fn: Option, + pub last_error_fn: LastErrorFn, + pub get_name_fn: PluginNameFn, } pub(crate) trait PluginDescriptorV1: Copy { @@ -100,8 +100,7 @@ pub(crate) fn require_callback( }) } -pub(crate) fn read_last_error(last_error_fn: Option) -> Option { - let last_error_fn = last_error_fn?; +pub(crate) fn read_last_error(last_error_fn: LastErrorFn) -> Option { let mut written = 0usize; check_errno( unsafe { last_error_fn(std::ptr::null_mut(), 0, &mut written) }, @@ -144,7 +143,7 @@ pub(crate) fn read_plugin_name(kind: &str, get_name_fn: PluginNameFn) -> Result< pub(crate) fn check_plugin_errno( errno: Errno, - last_error_fn: Option, + last_error_fn: LastErrorFn, mk_err: impl FnOnce() -> E, ) -> Result<(), anyhow::Error> { if errno == 0 { @@ -164,7 +163,7 @@ pub(crate) fn check_plugin_errno( pub(crate) fn check_plugin_errno_with_context( errno: Errno, - last_error_fn: Option, + last_error_fn: LastErrorFn, mk_err: impl FnOnce() -> E, ) -> Result<(), anyhow::Error> { if errno == 0 { @@ -195,7 +194,7 @@ pub(crate) fn negotiate_gateset( kind: &str, instance: I, negotiate_gateset_fn: NegotiateGatesetFn, - last_error_fn: Option, + last_error_fn: LastErrorFn, gateset: &DynamicGateSet, ) -> Result { let input = gateset.serialize(); @@ -290,14 +289,29 @@ mod tests { } } + unsafe extern "C" fn no_last_error( + _output: *mut c_char, + _output_len: usize, + written: *mut usize, + ) -> Errno { + unsafe { + *written = 0; + } + 0 + } + + unsafe extern "C" fn test_plugin_name() -> *const c_char { + c"Test".as_ptr() + } + #[test] fn descriptor_validation_checks_version_and_size() { let descriptor = TestDescriptor { header: PluginDescriptorHeaderV1 { struct_size: core::mem::size_of::() as u64, api_version: 7, - last_error_fn: None, - get_name_fn: None, + last_error_fn: no_last_error, + get_name_fn: test_plugin_name, }, }; validate_descriptor_v1(&descriptor, |version| { @@ -310,8 +324,8 @@ mod tests { header: PluginDescriptorHeaderV1 { struct_size: 0, api_version: 7, - last_error_fn: None, - get_name_fn: None, + last_error_fn: no_last_error, + get_name_fn: test_plugin_name, }, }; let error = validate_descriptor_v1(&descriptor, |_| Ok(())).unwrap_err(); @@ -325,8 +339,8 @@ mod tests { header: PluginDescriptorHeaderV1 { struct_size: core::mem::size_of::() as u64, api_version: 7, - last_error_fn: None, - get_name_fn: None, + last_error_fn: no_last_error, + get_name_fn: test_plugin_name, }, }; let error = @@ -371,7 +385,7 @@ mod tests { fn negotiate_gateset_uses_two_call_output_protocol() { let gateset = builtin::QuantinuumGateSet::dynamic(); let negotiated = - negotiate_gateset("TestPlugin", 0usize, echo_gateset, None, &gateset).unwrap(); + negotiate_gateset("TestPlugin", 0usize, echo_gateset, no_last_error, &gateset).unwrap(); assert_eq!(negotiated.serialize(), gateset.serialize()); } @@ -408,14 +422,14 @@ mod tests { #[test] fn check_plugin_errno_prefers_last_error_detail() { let error = - check_plugin_errno(-1, Some(fixed_last_error), || anyhow!("Host failure")).unwrap_err(); + check_plugin_errno(-1, fixed_last_error, || anyhow!("Host failure")).unwrap_err(); assert_eq!(error.to_string(), "plugin-specific failure"); } #[test] fn check_plugin_errno_with_context_includes_public_context_and_last_error_detail() { let error = - check_plugin_errno_with_context(-1, Some(fixed_last_error), || anyhow!("Host failure")) + check_plugin_errno_with_context(-1, fixed_last_error, || anyhow!("Host failure")) .unwrap_err(); assert_eq!(error.to_string(), "Host failure: plugin-specific failure"); } diff --git a/selene-core/rust/runtime.rs b/selene-core/rust/runtime.rs index bf15521c..cace904c 100644 --- a/selene-core/rust/runtime.rs +++ b/selene-core/rust/runtime.rs @@ -88,7 +88,7 @@ impl Runtime { fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { plugin_utils::check_plugin_errno_with_context( errno, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, || anyhow!("{}", self.context(message)), ) } @@ -149,7 +149,7 @@ impl RuntimeInterface for Runtime { &self.label(), self.handle.instance, self.handle.interface.negotiate_gateset_fn, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, gateset, ) } diff --git a/selene-core/rust/runtime/helper.rs b/selene-core/rust/runtime/helper.rs index 98e6b6b3..31ee1788 100644 --- a/selene-core/rust/runtime/helper.rs +++ b/selene-core/rust/runtime/helper.rs @@ -763,7 +763,7 @@ macro_rules! export_runtime_plugin { RuntimePluginDescriptorV1, current_api_version().as_u64(), { - last_error_fn: Some(selene_runtime_last_error), + last_error_fn: selene_runtime_last_error, get_name_fn: selene_runtime_get_name, init_fn: Some(selene_runtime_init), exit_fn: Some(selene_runtime_exit), diff --git a/selene-core/rust/runtime/plugin.rs b/selene-core/rust/runtime/plugin.rs index 2a366e1c..4ce749da 100644 --- a/selene-core/rust/runtime/plugin.rs +++ b/selene-core/rust/runtime/plugin.rs @@ -208,16 +208,10 @@ impl RuntimePluginInterface { validate_descriptor_v1(&descriptor, |api_version| { RuntimeAPIVersion::from(api_version).validate() })?; - let get_name_fn = - require_callback("Runtime", "get_name_fn", descriptor.header.get_name_fn)?; - let name = read_plugin_name("Runtime", get_name_fn)?; + let name = read_plugin_name("Runtime", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, - last_error_fn: require_callback( - "Runtime", - "last_error_fn", - descriptor.header.last_error_fn, - )?, + last_error_fn: descriptor.header.last_error_fn, name, init_fn: require_callback("Runtime", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, @@ -312,7 +306,7 @@ impl RuntimeInterfaceFactory for RuntimePluginInterface { with_strings_to_cargs(args, |argc, argv| { check_plugin_errno( unsafe { (self.init_fn)(&mut instance, n_qubits, start.into(), argc, argv) }, - Some(self.last_error_fn), + self.last_error_fn, || anyhow!("RuntimePluginInterface: init failed"), ) })?; @@ -335,7 +329,7 @@ impl RuntimeInterface for RuntimePlugin { }; check_plugin_errno( unsafe { exit_fn(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: exit failed"), ) } @@ -345,7 +339,7 @@ impl RuntimeInterface for RuntimePlugin { let ops = batch_builder.runtime_get_operation(); check_plugin_errno( unsafe { (self.interface.get_next_operations_fn)(self.instance, ops) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: get_next_operations failed"), )?; Ok(Some(batch_builder.finish()).filter(|b| !b.is_empty())) @@ -354,7 +348,7 @@ impl RuntimeInterface for RuntimePlugin { fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, seed) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: shot_start failed"), ) } @@ -362,7 +356,7 @@ impl RuntimeInterface for RuntimePlugin { fn shot_end(&mut self) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: shot_end failed"), ) } @@ -372,7 +366,7 @@ impl RuntimeInterface for RuntimePlugin { "RuntimePlugin", self.instance, self.interface.negotiate_gateset_fn, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, gateset, ) } @@ -391,7 +385,7 @@ impl RuntimeInterface for RuntimePlugin { let result_ref = &mut result; check_plugin_errno( unsafe { (self.interface.qalloc_fn)(self.instance, result_ref as *mut _) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: qalloc failed"), )?; Ok(result) @@ -400,7 +394,7 @@ impl RuntimeInterface for RuntimePlugin { fn qfree(&mut self, qubit_id: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.qfree_fn)(self.instance, qubit_id) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: qfree failed"), ) } @@ -408,7 +402,7 @@ impl RuntimeInterface for RuntimePlugin { fn global_barrier(&mut self, sleep_ns: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.global_barrier_fn)(self.instance, sleep_ns) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: global barrier failed"), ) } @@ -425,7 +419,7 @@ impl RuntimeInterface for RuntimePlugin { sleep_ns, ) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: local barrier failed"), ) } @@ -434,7 +428,7 @@ impl RuntimeInterface for RuntimePlugin { let data = gate.serialize(); check_plugin_errno( unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: gate failed"), ) } @@ -444,7 +438,7 @@ impl RuntimeInterface for RuntimePlugin { let result_ref = &mut result; check_plugin_errno( unsafe { (self.interface.measure_fn)(self.instance, qubit_id, result_ref as *mut _) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: measure failed"), )?; Ok(result) @@ -457,7 +451,7 @@ impl RuntimeInterface for RuntimePlugin { unsafe { (self.interface.measure_leaked_fn)(self.instance, qubit_id, result_ref as *mut _) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: measure failed"), )?; Ok(result) @@ -466,7 +460,7 @@ impl RuntimeInterface for RuntimePlugin { fn reset(&mut self, qubit_id: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.reset_fn)(self.instance, qubit_id) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: reset failed"), ) } @@ -474,7 +468,7 @@ impl RuntimeInterface for RuntimePlugin { fn force_result(&mut self, result_id: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.force_result_fn)(self.instance, result_id) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: force_result failed"), ) } @@ -486,7 +480,7 @@ impl RuntimeInterface for RuntimePlugin { unsafe { (self.interface.get_bool_result_fn)(self.instance, result_id, result_ref as *mut _) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: get_bool_result failed"), )?; // TODO document this @@ -504,7 +498,7 @@ impl RuntimeInterface for RuntimePlugin { unsafe { (self.interface.get_u64_result_fn)(self.instance, result_id, result_ref as *mut u64) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: get_u64_result failed"), )?; // TODO document this @@ -517,7 +511,7 @@ impl RuntimeInterface for RuntimePlugin { fn set_bool_result(&mut self, result_id: u64, result: bool) -> Result<()> { check_plugin_errno( unsafe { (self.interface.set_bool_result_fn)(self.instance, result_id, result) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: set_bool_result failed"), ) } @@ -525,7 +519,7 @@ impl RuntimeInterface for RuntimePlugin { fn set_u64_result(&mut self, result_id: u64, result: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.set_u64_result_fn)(self.instance, result_id, result) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: set_u64_result failed"), ) } @@ -533,7 +527,7 @@ impl RuntimeInterface for RuntimePlugin { fn increment_future_refcount(&mut self, future_ref: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.increment_future_refcount_fn)(self.instance, future_ref) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: increment_future_refcount failed"), ) } @@ -541,7 +535,7 @@ impl RuntimeInterface for RuntimePlugin { fn decrement_future_refcount(&mut self, future_ref: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.decrement_future_refcount_fn)(self.instance, future_ref) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: decrement_future_refcount failed"), ) } @@ -560,7 +554,7 @@ impl RuntimeInterface for RuntimePlugin { result_ref as *mut _, ) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: custom_call failed"), )?; Ok(result) @@ -575,7 +569,7 @@ impl RuntimeInterface for RuntimePlugin { if let Some(simulate_delay_fn) = self.interface.simulate_delay_fn { check_plugin_errno( unsafe { simulate_delay_fn(self.instance, delay_ns) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("RuntimePlugin: simulate_delay failed"), ) } else { diff --git a/selene-core/rust/simulator.rs b/selene-core/rust/simulator.rs index a3f466d2..d6987afe 100644 --- a/selene-core/rust/simulator.rs +++ b/selene-core/rust/simulator.rs @@ -101,7 +101,7 @@ impl Simulator { fn check_errno(&self, errno: plugin_utils::Errno, message: &'static str) -> Result<()> { plugin_utils::check_plugin_errno_with_context( errno, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, || anyhow!("{}", self.context(message)), ) } @@ -147,7 +147,7 @@ impl SimulatorInterface for Simulator { &self.label(), self.handle.instance, self.handle.interface.negotiate_gateset_fn, - Some(self.handle.interface.last_error_fn), + self.handle.interface.last_error_fn, gateset, ) } diff --git a/selene-core/rust/simulator/helper.rs b/selene-core/rust/simulator/helper.rs index 5c293f09..7b5b03e0 100644 --- a/selene-core/rust/simulator/helper.rs +++ b/selene-core/rust/simulator/helper.rs @@ -364,7 +364,7 @@ macro_rules! export_simulator_plugin { SimulatorPluginDescriptorV1, current_api_version().as_u64(), { - last_error_fn: Some(selene_simulator_last_error), + last_error_fn: selene_simulator_last_error, get_name_fn: selene_simulator_get_name, init_fn: Some(selene_simulator_init), exit_fn: Some(selene_simulator_exit), diff --git a/selene-core/rust/simulator/plugin.rs b/selene-core/rust/simulator/plugin.rs index 16cb5e50..82b5e0b1 100644 --- a/selene-core/rust/simulator/plugin.rs +++ b/selene-core/rust/simulator/plugin.rs @@ -143,16 +143,10 @@ impl SimulatorPluginInterface { validate_descriptor_v1(&descriptor, |api_version| { SimulatorAPIVersion::from(api_version).validate() })?; - let get_name_fn = - require_callback("Simulator", "get_name_fn", descriptor.header.get_name_fn)?; - let name = read_plugin_name("Simulator", get_name_fn)?; + let name = read_plugin_name("Simulator", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, - last_error_fn: require_callback( - "Simulator", - "last_error_fn", - descriptor.header.last_error_fn, - )?, + last_error_fn: descriptor.header.last_error_fn, name, init_fn: require_callback("Simulator", "init_fn", descriptor.init_fn)?, exit_fn: descriptor.exit_fn, @@ -203,7 +197,7 @@ impl SimulatorInterfaceFactory for SimulatorPluginInterface { with_strings_to_cargs(args, |argc, argv| { check_plugin_errno( unsafe { (self.init_fn)(&mut instance, n_qubits, argc, argv) }, - Some(self.last_error_fn), + self.last_error_fn, || anyhow!("SimulatorPlugin: init failed"), ) })?; @@ -221,14 +215,14 @@ impl SimulatorInterface for SimulatorPlugin { }; check_plugin_errno( unsafe { exit_fn(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("SimulatorPlugin: exit failed"), ) } fn shot_start(&mut self, shot_id: u64, seed: u64) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_start_fn)(self.instance, shot_id, seed) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || { anyhow!( "SimulatorPlugin({}): shot_start failed", @@ -240,7 +234,7 @@ impl SimulatorInterface for SimulatorPlugin { fn shot_end(&mut self) -> Result<()> { check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || anyhow!("SimulatorPlugin({}): shot_end failed", &self.interface.name), ) } @@ -251,7 +245,7 @@ impl SimulatorInterface for SimulatorPlugin { &plugin, self.instance, self.interface.negotiate_gateset_fn, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, gateset, ) } @@ -262,7 +256,7 @@ impl SimulatorInterface for SimulatorPlugin { let result = result_builder.operation_result(); check_plugin_errno( unsafe { (self.interface.handle_operations_fn)(self.instance, batch, result) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || { anyhow!( "SimulatorPlugin({}): handle_operations failed", @@ -299,7 +293,7 @@ impl SimulatorInterface for SimulatorPlugin { qubit_count, ) }, - Some(self.interface.last_error_fn), + self.interface.last_error_fn, || { anyhow!( "SimulatorPlugin({}): dump_state failed", diff --git a/selene-ext/interfaces/helios_qis/c/CMakeLists.txt b/selene-ext/interfaces/helios_qis/c/CMakeLists.txt index f1022bc3..69e6264b 100644 --- a/selene-ext/interfaces/helios_qis/c/CMakeLists.txt +++ b/selene-ext/interfaces/helios_qis/c/CMakeLists.txt @@ -9,6 +9,10 @@ find_package(Selene REQUIRED) find_package(BaseQISSeleneInterface REQUIRED) get_target_property(selene_include_dirs Selene::Selene INTERFACE_INCLUDE_DIRECTORIES) +set(SELENE_CORE_INCLUDE_DIR "" CACHE PATH "Directory containing selene-core C headers") +if(NOT SELENE_CORE_INCLUDE_DIR) + message(FATAL_ERROR "SELENE_CORE_INCLUDE_DIR is required to build the Helios QIS interface") +endif() add_library(helios_qis_selene_interface INTERFACE) target_include_directories(helios_qis_selene_interface INTERFACE @@ -62,6 +66,7 @@ function(build_helios_interface log_level suffix) "$" "$" ) + target_include_directories(${interface_target} PRIVATE "${SELENE_CORE_INCLUDE_DIR}") target_link_libraries(${interface_target} PRIVATE Selene::Selene) target_link_libraries(${interface_target} PUBLIC BaseQISSeleneInterface::base_qis_selene_interface_logging_${suffix}) if(MSVC) diff --git a/selene-ext/interfaces/helios_qis/c/src/helios_ops.c b/selene-ext/interfaces/helios_qis/c/src/helios_ops.c index a83028bd..3650a0d3 100644 --- a/selene-ext/interfaces/helios_qis/c/src/helios_ops.c +++ b/selene-ext/interfaces/helios_qis/c/src/helios_ops.c @@ -1,5 +1,6 @@ #include +#include #include // selene_ functions #include // selene_instance #include // unwrap diff --git a/selene-ext/interfaces/helios_qis/c/src/interface.c b/selene-ext/interfaces/helios_qis/c/src/interface.c index 3e776128..77f93743 100644 --- a/selene-ext/interfaces/helios_qis/c/src/interface.c +++ b/selene-ext/interfaces/helios_qis/c/src/interface.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/selene-ext/interfaces/sol_qis/c/CMakeLists.txt b/selene-ext/interfaces/sol_qis/c/CMakeLists.txt index ac9545ff..ef91c72b 100644 --- a/selene-ext/interfaces/sol_qis/c/CMakeLists.txt +++ b/selene-ext/interfaces/sol_qis/c/CMakeLists.txt @@ -9,6 +9,10 @@ find_package(Selene REQUIRED) find_package(BaseQISSeleneInterface REQUIRED) get_target_property(selene_include_dirs Selene::Selene INTERFACE_INCLUDE_DIRECTORIES) +set(SELENE_CORE_INCLUDE_DIR "" CACHE PATH "Directory containing selene-core C headers") +if(NOT SELENE_CORE_INCLUDE_DIR) + message(FATAL_ERROR "SELENE_CORE_INCLUDE_DIR is required to build the Sol QIS interface") +endif() add_library(sol_qis_selene_interface INTERFACE) target_include_directories(sol_qis_selene_interface INTERFACE @@ -62,6 +66,7 @@ function(build_sol_interface log_level suffix) "$" "$" ) + target_include_directories(${interface_target} PRIVATE "${SELENE_CORE_INCLUDE_DIR}") target_link_libraries(${interface_target} PRIVATE Selene::Selene) target_link_libraries(${interface_target} PUBLIC BaseQISSeleneInterface::base_qis_selene_interface_logging_${suffix}) if(MSVC) diff --git a/selene-ext/interfaces/sol_qis/c/src/interface.c b/selene-ext/interfaces/sol_qis/c/src/interface.c index 6a80f4de..ea139ad6 100644 --- a/selene-ext/interfaces/sol_qis/c/src/interface.c +++ b/selene-ext/interfaces/sol_qis/c/src/interface.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/selene-ext/interfaces/sol_qis/c/src/sol_ops.c b/selene-ext/interfaces/sol_qis/c/src/sol_ops.c index 2df43c57..01a6b122 100644 --- a/selene-ext/interfaces/sol_qis/c/src/sol_ops.c +++ b/selene-ext/interfaces/sol_qis/c/src/sol_ops.c @@ -1,5 +1,6 @@ #include +#include #include // selene_ functions #include // selene_instance #include // unwrap diff --git a/selene-sim/c/CMakeLists.txt b/selene-sim/c/CMakeLists.txt index a8449b20..171d78b2 100644 --- a/selene-sim/c/CMakeLists.txt +++ b/selene-sim/c/CMakeLists.txt @@ -27,9 +27,6 @@ target_link_libraries(Selene INTERFACE install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../../selene-core/c/include/selene/gatewire.h" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/selene -) # Install the interface target (this now works!) install( diff --git a/selene-sim/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index 90984e01..2802637c 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -6,7 +6,6 @@ #include #include #include -#include "selene/gatewire.h" typedef struct SeleneInstance SeleneInstance; diff --git a/selene-sim/cbindgen.toml b/selene-sim/cbindgen.toml index 50edf3f5..8481a86e 100644 --- a/selene-sim/cbindgen.toml +++ b/selene-sim/cbindgen.toml @@ -22,7 +22,7 @@ include_version = false namespaces = [] using_namespaces = [] sys_includes = [] -includes = ["selene/gatewire.h"] +includes = [] no_includes = false # cpp_compat = true after_includes = "" diff --git a/selene-sim/python/selene_sim/interactive/full_stack.py b/selene-sim/python/selene_sim/interactive/full_stack.py index 74888985..f4c74c3e 100644 --- a/selene-sim/python/selene_sim/interactive/full_stack.py +++ b/selene-sim/python/selene_sim/interactive/full_stack.py @@ -30,13 +30,10 @@ DEFAULT_SHOT_SPEC = ShotSpec(count=1000, offset=0, increment=1) -_SELENE_HEADER = Path(__file__).parents[2] / "_dist/include/selene/selene.h" -_SOURCE_SELENE_HEADER = ( - Path(__file__).parents[4] / "selene-sim/c/include/selene/selene.h" +_FFI = ffi( + (selene_dist / "include/selene/selene.h",), + builtin_headers=("gatewire.h",), ) -if _SOURCE_SELENE_HEADER.exists(): - _SELENE_HEADER = _SOURCE_SELENE_HEADER -_FFI = ffi((_SELENE_HEADER,)) def _component_config(component: SeleneComponent, default_seed: int | None) -> dict: diff --git a/selene-sim/python/selene_sim/interactive/runtime.py b/selene-sim/python/selene_sim/interactive/runtime.py index 52b768aa..d4ffb4ae 100644 --- a/selene-sim/python/selene_sim/interactive/runtime.py +++ b/selene-sim/python/selene_sim/interactive/runtime.py @@ -10,7 +10,7 @@ from ._library import load_selene_global -_FFI = ffi() +_FFI = ffi(builtin_headers=("gatewire.h", "runtime.h")) @dataclass @@ -151,15 +151,10 @@ def callback_set_batch_time(instance, start_time: int, duration: int): class SeleneSimRuntimeLib: def __init__(self, runtime: Runtime) -> None: load_selene_global() - self.ffi = ffi() + self.ffi = ffi(builtin_headers=("gatewire.h", "runtime.h")) self.types = RuntimeCTypes(self.ffi) self.lib = self.ffi.dlopen(str(runtime.library_file)) - try: - self.descriptor = self.lib.selene_runtime_plugin_descriptor_v1 - except AttributeError as exc: - raise RuntimeError( - "Runtime plugin did not expose descriptor symbol" - ) from exc + self.descriptor = self._descriptor() self.last_error_fn = self._required( self.descriptor.header.last_error_fn, "last_error_fn" @@ -227,6 +222,20 @@ def _required(self, function, function_name: str): raise RuntimeError(f"Runtime plugin does not expose {function_name}") return function + def _descriptor(self): + try: + return self.lib.selene_runtime_get_plugin_descriptor_v1()[0] + except AttributeError: + pass + try: + return self.ffi.addressof(self.lib, "selene_runtime_plugin_descriptor_v1")[ + 0 + ] + except (AttributeError, KeyError, NotImplementedError) as exc: + raise RuntimeError( + "Runtime plugin did not expose descriptor symbol" + ) from exc + def _operation_callbacks(self): callbacks = self.ffi.new(self.types.runtime_get_operation_interface_ptr) callbacks.measure_fn = callback_measure diff --git a/selene-sim/python/selene_sim/interactive/simulator.py b/selene-sim/python/selene_sim/interactive/simulator.py index 507773e6..ed8668a7 100644 --- a/selene-sim/python/selene_sim/interactive/simulator.py +++ b/selene-sim/python/selene_sim/interactive/simulator.py @@ -14,15 +14,10 @@ class SeleneSimSimulatorLib: def __init__(self, simulator: Simulator) -> None: load_selene_global() - self.ffi = ffi() + self.ffi = ffi(builtin_headers=("gatewire.h", "simulator.h")) self.types = SimulatorCTypes(self.ffi) self.lib = self.ffi.dlopen(str(simulator.library_file)) - try: - self.descriptor = self.lib.selene_simulator_plugin_descriptor_v1 - except AttributeError as exc: - raise RuntimeError( - "Simulator plugin did not expose descriptor symbol" - ) from exc + self.descriptor = self._descriptor() self.last_error_fn = self._required( self.descriptor.header.last_error_fn, "last_error_fn" @@ -53,6 +48,20 @@ def _required(self, function, function_name: str): raise RuntimeError(f"Simulator plugin does not expose {function_name}") return function + def _descriptor(self): + try: + return self.lib.selene_simulator_get_plugin_descriptor_v1()[0] + except AttributeError: + pass + try: + return self.ffi.addressof( + self.lib, "selene_simulator_plugin_descriptor_v1" + )[0] + except (AttributeError, KeyError, NotImplementedError) as exc: + raise RuntimeError( + "Simulator plugin did not expose descriptor symbol" + ) from exc + def init(self, n_qubits: int, args: list[str]): handle = self.ffi.new(self.types.simulator_instance_ptr) encoded_args = [ @@ -113,12 +122,12 @@ def _result_handle(self): result = {"bool": {}, "u64": {}} refs: list[Any] = [] - @self.ffi.callback("void(SeleneOperationResultInstance, uint64_t, bool)") + @self.ffi.callback("void(OperationResultInstance, uint64_t, bool)") def set_bool(instance, result_id, value): target = self.ffi.from_handle(instance) target["bool"][int(result_id)] = bool(value) - @self.ffi.callback("void(SeleneOperationResultInstance, uint64_t, uint64_t)") + @self.ffi.callback("void(OperationResultInstance, uint64_t, uint64_t)") def set_u64(instance, result_id, value): target = self.ffi.from_handle(instance) target["u64"][int(result_id)] = int(value) From 0dea5e3e4c714b51dd6de11c2081d7a28dab0c10 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:36:46 +0100 Subject: [PATCH 17/19] Expose utility callback registration through base QIS --- selene-ext/interfaces/base_qis/c/include/base_qis/hooks.h | 5 +++++ selene-ext/interfaces/base_qis/c/src/hooks.c | 8 ++++++++ selene-ext/utilities/argreader/rust/lib.rs | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/selene-ext/interfaces/base_qis/c/include/base_qis/hooks.h b/selene-ext/interfaces/base_qis/c/include/base_qis/hooks.h index 112ceda0..861450b2 100644 --- a/selene-ext/interfaces/base_qis/c/include/base_qis/hooks.h +++ b/selene-ext/interfaces/base_qis/c/include/base_qis/hooks.h @@ -4,9 +4,14 @@ #include #include +#include EXPORT void simulate_delay(uint64_t delay_ns); EXPORT uint64_t custom_runtime_call(uint64_t tag, void* data, uint64_t data_len); EXPORT void log_utility_call(uint64_t tag, void* data, uint64_t data_len); +EXPORT struct selene_void_result_t register_utility_event_callbacks( + SeleneInstance* instance, + SeleneUtilityEventCallbacksV1 callbacks +); #endif diff --git a/selene-ext/interfaces/base_qis/c/src/hooks.c b/selene-ext/interfaces/base_qis/c/src/hooks.c index 4dd60a9f..a01828ec 100644 --- a/selene-ext/interfaces/base_qis/c/src/hooks.c +++ b/selene-ext/interfaces/base_qis/c/src/hooks.c @@ -29,3 +29,11 @@ void log_utility_call(uint64_t tag, void* data, uint64_t data_len) { } unwrap(selene_log_utility_call(selene_instance, tag, data, data_len)); } + +struct selene_void_result_t register_utility_event_callbacks( + SeleneInstance* instance, + SeleneUtilityEventCallbacksV1 callbacks +) { + DIAGNOSTIC("register_utility_event_callbacks(%p, ...)\n", instance); + return selene_register_utility_event_callbacks(instance, callbacks); +} diff --git a/selene-ext/utilities/argreader/rust/lib.rs b/selene-ext/utilities/argreader/rust/lib.rs index 60d7bcc1..ee47fa82 100644 --- a/selene-ext/utilities/argreader/rust/lib.rs +++ b/selene-ext/utilities/argreader/rust/lib.rs @@ -84,7 +84,7 @@ unsafe extern "C" { // the selene result stream based on a C string rather than a CL string. fn panic_str(error_code: u32, message: *const std::ffi::c_char) -> !; fn log_utility_call(tag: u64, data_ptr: *const u8, data_len: u64); - fn selene_register_utility_event_callbacks( + fn register_utility_event_callbacks( instance: *mut c_void, callbacks: SeleneUtilityEventCallbacksV1, ) -> SeleneVoidResult; @@ -207,7 +207,7 @@ pub unsafe extern "C" fn selene_argreader_register_utility( on_shot_start: Some(argreader_on_shot_start), on_shot_end: Some(argreader_on_shot_end), }; - unsafe { selene_register_utility_event_callbacks(instance, callbacks) } + unsafe { register_utility_event_callbacks(instance, callbacks) } } fn get_key(key_ptr: *const u8) -> String { From 1282a5c3e977bd6c1e59ead388325112cd983e26 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:01:43 +0100 Subject: [PATCH 18/19] Update docs, remove (now-fixed) windows divergence test in quest runs --- README.md | 15 +- docs/README.md | 4 +- docs/extensibility/README.md | 12 +- docs/extensibility/error-model/c.md | 169 +++++++++++------- docs/extensibility/fundamentals.md | 43 ++++- docs/extensibility/gates/README.md | 8 +- docs/extensibility/gates/rust.md | 39 +++- docs/extensibility/runtime/rust.md | 14 +- docs/extensibility/simulator/c.md | 14 +- docs/extensibility/simulator/rust.md | 41 +++-- docs/extensibility/utility/README.md | 78 ++++++++ docs/migration/0.3.md | 58 +++--- selene-core/README.md | 39 ++-- selene-core/python/selene_core/README.md | 27 +-- .../python/selene_core/build_utils/README.md | 11 +- selene-core/rust/simulator/README.md | 143 ++++----------- selene-sim/python/selene_sim/README.md | 10 +- selene-sim/python/tests/test_guppy.py | 10 +- 18 files changed, 453 insertions(+), 282 deletions(-) create mode 100644 docs/extensibility/utility/README.md diff --git a/README.md b/README.md index 364741b9..abb500b9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Selene is a quantum computer emulation platform written primarily in Rust with a Selene is built with flexibility in mind. This includes: -- A plugin system for the addition of additional components including simulators, error models, quantum runtimes to be provided within Selene or as third party plugins +- A descriptor-based plugin system for simulators, error models, quantum runtimes, and link-time utilities provided either within Selene or as third-party packages. +- Gatewire gatesets, allowing interfaces and plugins to negotiate custom gate vocabularies before a run starts. - Support for custom input formats and device APIs through [the selene-core build system](selene-core/python/selene_core/build_utils). ## What's included @@ -32,7 +33,17 @@ Error models that are currently provided include: And we offer two example quantum runtimes, including: - Simple, which executes the program as-is, without any modifications -- SoftRZ, which elides Z rotations through RXY gates, providing the same observable behaviour with fewer quantum operations +- SoftRZ, which accepts `RZ` operations and elides physical Z rotations through subsequent gates, providing the same observable behaviour with fewer quantum operations + +## Documentation + +- [Extensibility docs](docs/extensibility/README.md) cover plugins, utilities, + custom gates, and the gatewire handshake. +- [0.3 migration guide](docs/migration/0.3.md) covers the breaking plugin and + gate API changes from the 0.2 series. +- [Clifford+T stack example](examples/clifford_t_stack/README.md) is a complete + custom-gateset project with Python bindings, Rust runtime/error-model + plugins, and a C++ simulator plugin. ## Installation diff --git a/docs/README.md b/docs/README.md index 90b31d65..24150bd7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,5 +6,5 @@ If you are moving from Selene 0.2 to the 0.3 series, start with the [0.3 migration guide](migration/0.3.md). Start with [Extensibility](extensibility/README.md) if you want to write a -plugin, define gates, or understand how Selene connects a Python-facing -interface to runtime, error model, and simulator backends. +plugin, define gates, write a utility, or understand how Selene connects a +Python-facing interface to runtime, error model, and simulator backends. diff --git a/docs/extensibility/README.md b/docs/extensibility/README.md index 74d8bc16..909b8c3f 100644 --- a/docs/extensibility/README.md +++ b/docs/extensibility/README.md @@ -32,14 +32,17 @@ Read these in order if you are new to Selene plugins: gatesets, metrics, and memory rules shared by all plugin types. 2. [Gates and gatesets](gates/README.md) explains builtin gates, custom gate definitions, and gatewire transport. -3. [Worked examples](examples/README.md) shows a complete custom-gateset stack - from interface registration to simulator validation. +3. [The Clifford+T example](../../examples/clifford_t_stack/README.md) shows a + complete custom-gateset stack from interface registration to simulator + validation. 4. [Runtime plugins](runtime/README.md) explains how a runtime receives user operations and emits batches. 5. [Error model plugins](error-model/README.md) explains how stochastic errors are injected. 6. [Simulator plugins](simulator/README.md) explains how a backend executes the final operation stream. +7. [Utility plugins](utility/README.md) explains link-time extensions that + expose classical helper symbols to user programs. Each plugin section has a Rust tutorial and a C tutorial. Rust plugins normally use Selene's traits and export macros. C plugins implement the descriptor ABI @@ -61,3 +64,8 @@ negotiation rather than silently approximating them. Define gates when the builtin gates are not enough. Gates are not plugins by themselves; they are the shared vocabulary that interfaces and plugins negotiate. + +Write a utility when a compiled user program needs extra classical symbols at +link time. Utilities are linked into the final executable rather than loaded +from the runtime configuration, and they may optionally register shot lifecycle +callbacks after Selene has been configured. diff --git a/docs/extensibility/error-model/c.md b/docs/extensibility/error-model/c.md index 3db9d3f2..09dd2534 100644 --- a/docs/extensibility/error-model/c.md +++ b/docs/extensibility/error-model/c.md @@ -95,75 +95,103 @@ static SeleneErrno my_error_model_negotiate_gateset(SeleneErrorModelInstance han } ``` -## 3. Understand Batch Extraction +## 3. Forward a Batch -Selene does not expose the runtime batch as a raw array. Instead, the error -model passes a collector to `batch.interface.extract_fn`. Selene replays the -batch into that collector through callbacks such as `gate_fn`, `measure_fn`, and +The simulator's first-class entry point is `handle_operations_fn`. An error +model that only forwards operations can pass Selene's batch extractor straight +through to the simulator and let the simulator write measurement results into +the result handle: + +```c +static SeleneErrno my_error_model_handle_operations( + SeleneErrorModelInstance handle, + struct RuntimeExtractOperationHandle batch, + struct SimulatorHandle simulator, + struct OperationResultHandle results +) { + (void)handle; + return simulator.interface.handle_operations_fn( + simulator.instance, + batch, + results + ); +} +``` + +This is the preferred path for pass-through models. It preserves whatever +operation kinds Selene knows about without requiring your C code to decode and +re-emit them. + +## 4. Inspect a Batch + +If the model needs to inspect or mutate operations, Selene still does not expose +the runtime batch as a raw array. Instead, pass a collector to +`batch.interface.extract_fn`. Selene replays the batch into that collector +through callbacks such as `gate_fn`, `measure_fn`, `postselect_fn`, and `reset_fn`. -A minimal collector looks like this: +A collector usually stores decoded operations in model-owned memory, records an +error flag if any callback fails, then builds one or more simulator batches. +This stripped-down collector only records that a one-qubit gate was observed: ```c typedef struct { - struct SimulatorHandle simulator; - struct OperationResultHandle results; MyErrorModel *model; + int failed; } Collector; static void collect_gate(SeleneRuntimeGetOperationInstance instance, const uint8_t *data, size_t len) { Collector *collector = (Collector *)instance; + GwDecodedGate *gate = NULL; + if (gw_gate_deserialize(data, len, &gate) != GW_STATUS_OK) { + collector->failed = 1; + return; + } - collector->simulator.interface.gate_fn( - collector->simulator.instance, - data, - len - ); + size_t qubits = 0; + if (gw_decoded_gate_qubit_operand_count(gate, &qubits) != GW_STATUS_OK) { + collector->failed = 1; + } else if (qubits == 1) { + collector->model->injected_errors += 1; + } - /* You can decode data with gw_gate_deserialize and inject extra gates here. */ + gw_decoded_gate_free(gate); } static void collect_measure(SeleneRuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { - Collector *collector = (Collector *)instance; - SeleneErrno measured = collector->simulator.interface.measure_fn( - collector->simulator.instance, - qubit - ); - - if (measured == 0 || measured == 1) { - collector->results.interface.set_bool_result_fn( - collector->results.instance, - result_id, - measured == 1 - ); - } + (void)instance; + (void)qubit; + (void)result_id; + /* Store or forward the measurement operation in production code. */ } static void collect_reset(SeleneRuntimeGetOperationInstance instance, uint64_t qubit) { - Collector *collector = (Collector *)instance; - collector->simulator.interface.reset_fn(collector->simulator.instance, qubit); + (void)instance; + (void)qubit; + /* Store or forward the reset operation in production code. */ +} + +static void collect_postselect(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + bool target_value) { + (void)instance; + (void)qubit; + (void)target_value; + /* Store or forward the postselection operation in production code. */ } static void collect_measure_leaked(SeleneRuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { - Collector *collector = (Collector *)instance; - SeleneErrno measured = collector->simulator.interface.measure_fn( - collector->simulator.instance, - qubit - ); - if (measured == 0 || measured == 1) { - collector->results.interface.set_u64_result_fn( - collector->results.instance, - result_id, - (uint64_t)measured - ); - } + (void)instance; + (void)qubit; + (void)result_id; + /* Store or forward the leakage measurement operation in production code. */ } static void collect_custom(SeleneRuntimeGetOperationInstance instance, @@ -187,13 +215,11 @@ static void collect_batch_time(SeleneRuntimeGetOperationInstance instance, } ``` -The simulator measurement convention is the same as the simulator C API: -`0` means false, `1` means true, and any other value is an error. - -## 4. Handle a Runtime Batch +## 5. Extract and Then Call the Simulator Build a `RuntimeGetOperationHandle` from your collector and ask Selene to -extract the batch: +extract the batch. After inspection, call `simulator.interface.handle_operations_fn` +with the original batch, a newly built batch, or both, depending on your model. ```c static SeleneErrno my_error_model_handle_operations( @@ -203,14 +229,14 @@ static SeleneErrno my_error_model_handle_operations( struct OperationResultHandle results ) { Collector collector = { - .simulator = simulator, - .results = results, .model = (MyErrorModel *)handle, + .failed = 0, }; struct RuntimeGetOperationInterface interface = { .measure_fn = collect_measure, .measure_leaked_fn = collect_measure_leaked, + .postselect_fn = collect_postselect, .reset_fn = collect_reset, .custom_fn = collect_custom, .set_batch_time_fn = collect_batch_time, @@ -223,23 +249,33 @@ static SeleneErrno my_error_model_handle_operations( }; batch.interface.extract_fn(&batch, sink); - return 0; + if (collector.failed) { + return 1; + } + + return simulator.interface.handle_operations_fn( + simulator.instance, + batch, + results + ); } ``` -Production code should collect errors from callbacks and return nonzero if any -simulator call fails. A small collector struct is usually the cleanest way to -store that error state. +Production code that mutates the operation stream should construct a fresh +`RuntimeExtractOperationHandle` for the operations it wants to send downstream. +The simulator writes measurement results into `results`; the error model should +not treat errno return values as measurement values. -## 5. Inject Gates +## 6. Inject Gates -To inject a gate, serialize a `GwGateInstanceView` and call the simulator's -`gate_fn`: +To inject a gate, serialize a `GwGateInstanceView` and include it in a simulator +batch. The serialization step looks like this: ```c -static SeleneErrno inject_rz(struct SimulatorHandle simulator, - uint32_t qubit, - double theta) { +static SeleneErrno serialize_rz(uint32_t qubit, + double theta, + uint8_t **out, + size_t *out_len) { GwGateValue values[2] = { { .abi_size = sizeof(GwGateValue), @@ -272,18 +308,21 @@ static SeleneErrno inject_rz(struct SimulatorHandle simulator, size_t written = 0; GwStatus status = gw_gate_serialize(&gate, buffer, len, &written); - if (status == GW_STATUS_OK) { - status = simulator.interface.gate_fn(simulator.instance, buffer, written) == 0 - ? GW_STATUS_OK - : GW_STATUS_PANIC; + if (status != GW_STATUS_OK) { + free(buffer); + return 1; } - free(buffer); - return status == GW_STATUS_OK ? 0 : 1; + *out = buffer; + *out_len = written; + return 0; } ``` -## 6. Reseed and Report Metrics +The injected bytes can then be emitted from the extractor callback for the +simulator batch you construct. + +## 7. Reseed and Report Metrics ```c static SeleneErrno my_error_model_shot_start(SeleneErrorModelInstance handle, @@ -312,7 +351,7 @@ static SeleneErrno my_error_model_get_metrics(SeleneErrorModelInstance handle, } ``` -## 7. Export the Descriptor +## 8. Export the Descriptor ```c const SeleneErrorModelPluginDescriptorV1 selene_error_model_plugin_descriptor_v1 = { diff --git a/docs/extensibility/fundamentals.md b/docs/extensibility/fundamentals.md index d0313d05..c493db5e 100644 --- a/docs/extensibility/fundamentals.md +++ b/docs/extensibility/fundamentals.md @@ -73,11 +73,17 @@ Selene finds that descriptor either as a public symbol named `selene__plugin_descriptor_v1` or through a getter function named `selene__get_plugin_descriptor_v1`. -The descriptor contains: +Every descriptor begins with the same `PluginDescriptorHeaderV1`: - `struct_size`, so Selene can detect a descriptor compiled against a different struct layout. - `api_version`, so Selene can reject an incompatible ABI version. +- `last_error_fn`, so callback failures can carry a useful plugin-provided + message. +- `get_name_fn`, so errors can say which plugin failed. + +The rest of the descriptor contains: + - Function pointers for the plugin lifecycle and operation callbacks. Rust plugins usually do not write the descriptor by hand. They implement a @@ -90,9 +96,12 @@ Descriptor validation is intentionally boring: - `struct_size` must match the descriptor type Selene expects. - The API version reserved byte must be zero. - The major and minor API versions must match Selene's current API. -- Required callbacks must be present. -- Optional callbacks may be null, in which case Selene uses the documented - default behavior for that feature. +- Required callbacks must be present. `last_error_fn`, `get_name_fn`, and + `negotiate_gateset_fn` are mandatory for every runtime, error-model, and + simulator plugin. +- Optional callbacks may be null only where the header for that plugin type + documents them as optional. Missing optional functionality should fail when + the feature is used, not while the plugin is loaded. For C plugins, use the version macro from the header for the plugin type you are implementing: @@ -148,6 +157,23 @@ Rust plugins exported with Selene's helper macros get this behavior automatically: any error returned from a callback is captured and reported through `last_error_fn`. +## Header Ownership + +The C headers are split by layer: + +- `selene-core` owns plugin ABI headers such as `selene/runtime.h`, + `selene/error_model.h`, `selene/simulator.h`, `selene/plugin.h`, and + `selene/gatewire.h`. +- `selene-sim` owns the frontend C API in `selene/selene.h`, including + `selene_load_config`, `selene_register_gateset`, `selene_gate`, result stream + operations, and utility callback registration. + +Third-party plugin packages should build against the headers provided by +`selene-core`. Frontends and QIS shims that call into a running Selene instance +also include `selene/selene.h` from `selene-sim`. If a C file uses gatewire +types or `gw_*` helpers, include `selene/gatewire.h` explicitly; do not rely on +`selene/selene.h` to pull it in. + ## Gatewire and Gatesets Gatewire is the gate transport layer. It solves two problems: @@ -195,6 +221,11 @@ emit only `PhasedX` and `ZZPhase` after lowering. The simulator should validate the gateset it actually receives from the error model, not the gateset the user originally requested. +In Rust, prefer `DynamicGateSet::is_subset_of`, +`DynamicGateSet::is_superset_of`, or `DynamicGateSet::first_unsupported_by` +instead of open-coding declaration loops in every plugin. In C, use the +`GwGateSet` helpers from `selene/gatewire.h`. + The C ABI uses a two-call buffer protocol for gateset negotiation: 1. Selene calls the callback with `output == NULL` and `output_len == 0`. @@ -235,6 +266,10 @@ Error models report measurement results back through an `OperationResultHandle`. Simulators return measurement outcomes to the error model immediately. +Postselection is part of the operation stream. A simulator should handle it +through its batch operation entry point and return a clear simulator error when +the requested postselection is impossible. + ## Randomness and Reproducibility `shot_start` receives a seed. Any plugin that uses randomness should derive all diff --git a/docs/extensibility/gates/README.md b/docs/extensibility/gates/README.md index bde59db1..67264362 100644 --- a/docs/extensibility/gates/README.md +++ b/docs/extensibility/gates/README.md @@ -24,6 +24,13 @@ The builtin names are the names used by the plugin API. Older names such as `rz`, `rxy`, `rzz`, and `rpp` belong at compatibility boundaries such as QIS parsers, not inside plugin implementations. +Convenience gatesets are available where the common platform vocabulary is +useful: + +- `HeliosGateSet`: `RZ`, `PhasedX`, `ZZPhase`. +- `SolGateSet`: `RZ`, `PhasedX`, `PhasedXX`. +- `QuantinuumGateSet`: the union of the Helios and Sol gatesets. + ## Custom Gates Use a custom gate when a runtime, error model, and simulator need to agree on an @@ -59,4 +66,3 @@ and then the Rust guide for your plugin type. If you are writing a plugin in C, start with [Writing gates in C](c.md). The C plugin tutorials assume you are comfortable with serialized gatewire bytes and the `gw_*` functions. - diff --git a/docs/extensibility/gates/rust.md b/docs/extensibility/gates/rust.md index 30a7f756..4377c25a 100644 --- a/docs/extensibility/gates/rust.md +++ b/docs/extensibility/gates/rust.md @@ -28,6 +28,17 @@ let gateset = GateSet::::new()?; let bytes = gateset.serialize_gate(&RzOnly::RZ(gate))?; ``` +For the bundled platform vocabularies you usually do not need to define the enum +yourself. Use the builtin gatesets: + +```rust +use selene_core::gatewire::builtin::{HeliosGateSet, QuantinuumGateSet, SolGateSet}; + +let helios = HeliosGateSet::dynamic(); +let sol = SolGateSet::dynamic(); +let quantinuum = QuantinuumGateSet::dynamic(); +``` + For a set containing several gate types, define an enum: ```rust @@ -92,15 +103,13 @@ fn output_gateset() -> DynamicGateSet { } fn negotiate_gateset(input: &DynamicGateSet) -> Result { - for decl in input.declarations() { - // Reject gates you cannot accept from the previous layer. - if !output_gateset().contains(decl.semantic_id) { - anyhow::bail!("unsupported input gate {}", decl.name); - } + let output = output_gateset(); + if let Some(unsupported) = input.first_unsupported_by(&output) { + anyhow::bail!("unsupported input gate {}", unsupported.name); } // Return the gates this plugin may emit downstream. - Ok(output_gateset()) + Ok(output) } ``` @@ -129,6 +138,24 @@ match HeliosGates::try_from_instance(gate)? { } ``` +Builtin view enums give a compact shape when you want plain values: + +```rust +use selene_core::gatewire::builtin::{QuantinuumGate, QuantinuumGateSet}; +use selene_core::gatewire::{GateSetSpec, GateView}; + +match QuantinuumGateSet::try_from_instance(gate)?.map(QuantinuumGate::from_gate) { + Some(QuantinuumGate::PhasedX { qubit_id, theta, phi }) => { + // use qubit_id, theta, phi + } + Some(QuantinuumGate::ZZPhase { qubit_id_1, qubit_id_2, theta }) => { + // use qubit_id_1, qubit_id_2, theta + } + Some(_) => {} + None => anyhow::bail!("gate was not part of QuantinuumGateSet"), +} +``` + This keeps the plugin independent of frontend naming and independent of the serialized wire format. diff --git a/docs/extensibility/runtime/rust.md b/docs/extensibility/runtime/rust.md index f0ca5da2..36b31365 100644 --- a/docs/extensibility/runtime/rust.md +++ b/docs/extensibility/runtime/rust.md @@ -38,9 +38,11 @@ define_gateset! { } ``` -If the runtime lowers gates, define a second gateset for output. The input -gateset is what the interface may send. The output gateset is what the runtime -may emit downstream. +If the runtime accepts one of Selene's builtin platform vocabularies unchanged, +prefer `HeliosGateSet::dynamic()`, `SolGateSet::dynamic()`, or +`QuantinuumGateSet::dynamic()` over redefining the same enum. If the runtime +lowers gates, define a second gateset for output. The input gateset is what the +interface may send. The output gateset is what the runtime may emit downstream. ## 3. Store Runtime State @@ -81,10 +83,8 @@ impl MyRuntime { impl RuntimeInterface for MyRuntime { fn negotiate_gateset(&mut self, input: &DynamicGateSet) -> Result { let accepted = Self::output_gateset(); - for decl in input.declarations() { - if !accepted.contains(decl.semantic_id) { - bail!("runtime does not accept gate {}", decl.name); - } + if let Some(unsupported) = input.first_unsupported_by(&accepted) { + bail!("runtime does not accept gate {}", unsupported.name); } Ok(accepted) } diff --git a/docs/extensibility/simulator/c.md b/docs/extensibility/simulator/c.md index 521d9fb9..bba69af1 100644 --- a/docs/extensibility/simulator/c.md +++ b/docs/extensibility/simulator/c.md @@ -146,14 +146,14 @@ static bool apply_measure(MySimulator *sim, uint64_t qubit) { return false; } -static void collect_gate(SeleneRuntimeGetOperationInstance instance, +static void collect_gate(RuntimeGetOperationInstance instance, const uint8_t *data, size_t len) { SimulatorCollector *collector = (SimulatorCollector *)instance; (void)apply_gate(collector->sim, data, len); } -static void collect_measure(SeleneRuntimeGetOperationInstance instance, +static void collect_measure(RuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { SimulatorCollector *collector = (SimulatorCollector *)instance; @@ -165,7 +165,7 @@ static void collect_measure(SeleneRuntimeGetOperationInstance instance, ); } -static void collect_measure_leaked(SeleneRuntimeGetOperationInstance instance, +static void collect_measure_leaked(RuntimeGetOperationInstance instance, uint64_t qubit, uint64_t result_id) { SimulatorCollector *collector = (SimulatorCollector *)instance; @@ -177,7 +177,7 @@ static void collect_measure_leaked(SeleneRuntimeGetOperationInstance instance, ); } -static void collect_postselect(SeleneRuntimeGetOperationInstance instance, +static void collect_postselect(RuntimeGetOperationInstance instance, uint64_t qubit, bool target_value) { SimulatorCollector *collector = (SimulatorCollector *)instance; @@ -187,7 +187,7 @@ static void collect_postselect(SeleneRuntimeGetOperationInstance instance, /* backend_postselect(collector->sim, qubit, target_value); */ } -static void collect_reset(SeleneRuntimeGetOperationInstance instance, +static void collect_reset(RuntimeGetOperationInstance instance, uint64_t qubit) { SimulatorCollector *collector = (SimulatorCollector *)instance; (void)collector; @@ -195,7 +195,7 @@ static void collect_reset(SeleneRuntimeGetOperationInstance instance, /* backend_reset(collector->sim, qubit); */ } -static void collect_custom(SeleneRuntimeGetOperationInstance instance, +static void collect_custom(RuntimeGetOperationInstance instance, size_t tag, const void *data, size_t len) { @@ -205,7 +205,7 @@ static void collect_custom(SeleneRuntimeGetOperationInstance instance, (void)len; } -static void collect_batch_time(SeleneRuntimeGetOperationInstance instance, +static void collect_batch_time(RuntimeGetOperationInstance instance, uint64_t start, uint64_t duration) { (void)instance; diff --git a/docs/extensibility/simulator/rust.md b/docs/extensibility/simulator/rust.md index 536122a1..011d9daa 100644 --- a/docs/extensibility/simulator/rust.md +++ b/docs/extensibility/simulator/rust.md @@ -20,7 +20,14 @@ selene-core = { path = "../../path/to/selene-core/rust" } ## 2. Define Supported Gates -If your simulator supports the standard native gates, define a gateset enum: +If your simulator supports the standard Quantinuum builtin vocabulary, use the +builtin union gateset and view enum: + +```rust +use selene_core::gatewire::builtin::{QuantinuumGate, QuantinuumGateSet}; +``` + +If you need a different set, define your own gateset enum: ```rust use selene_core::define_gateset; @@ -44,7 +51,8 @@ example, should reject non-Clifford declarations in `negotiate_gateset`. ```rust use anyhow::{Result, bail}; use selene_core::error_model::BatchResult; -use selene_core::gatewire::{DynamicGateSet, GateSetSpec}; +use selene_core::gatewire::{DynamicGateSet, GateSetSpec, GateView}; +use selene_core::gatewire::builtin::{QuantinuumGate, QuantinuumGateSet}; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -65,18 +73,15 @@ The simulator validates the final gateset: ```rust impl MySimulator { fn supported_gateset() -> DynamicGateSet { - DynamicGateSet::from_declarations(SimulatorGates::declarations()) - .expect("simulator gate declarations are unique") + QuantinuumGateSet::dynamic() } } impl SimulatorInterface for MySimulator { fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { let supported = Self::supported_gateset(); - for decl in gateset.declarations() { - if !supported.contains(decl.semantic_id) { - bail!("simulator does not support gate {}", decl.name); - } + if let Some(unsupported) = gateset.first_unsupported_by(&supported) { + bail!("simulator does not support gate {}", unsupported.name); } Ok(gateset.clone()) } @@ -98,7 +103,9 @@ fn handle_operations(&mut self, operations: BatchOperation) -> Result { - let Some(gate) = SimulatorGates::try_from_instance(&gate)? else { + let Some(gate) = QuantinuumGateSet::try_from_instance(&gate)? + .map(QuantinuumGate::from_gate) + else { bail!("simulator received an unnegotiated gate"); }; self.apply_gate(gate)?; @@ -130,13 +137,17 @@ Decode gates by semantic ID, not by display string: ```rust impl MySimulator { - fn apply_gate(&mut self, gate: SimulatorGates) -> Result<()> { + fn apply_gate(&mut self, gate: QuantinuumGate) -> Result<()> { match gate { - SimulatorGates::RZ(g) => self.apply_rz(g.q0.0.into(), g.theta.0), - SimulatorGates::PhasedX(g) => self.apply_phased_x(g.q0.0.into(), g.theta.0, g.phi.0), - SimulatorGates::ZZPhase(g) => self.apply_zz_phase(g.q0.0.into(), g.q1.0.into(), g.theta.0), - SimulatorGates::PhasedXX(g) => { - self.apply_phased_xx(g.q0.0.into(), g.q1.0.into(), g.theta.0, g.phi.0) + QuantinuumGate::RZ { qubit_id, theta } => self.apply_rz(qubit_id, theta), + QuantinuumGate::PhasedX { qubit_id, theta, phi } => { + self.apply_phased_x(qubit_id, theta, phi) + } + QuantinuumGate::ZZPhase { qubit_id_1, qubit_id_2, theta } => { + self.apply_zz_phase(qubit_id_1, qubit_id_2, theta) + } + QuantinuumGate::PhasedXX { qubit_id_1, qubit_id_2, theta, phi } => { + self.apply_phased_xx(qubit_id_1, qubit_id_2, theta, phi) } } } diff --git a/docs/extensibility/utility/README.md b/docs/extensibility/utility/README.md new file mode 100644 index 00000000..e69472be --- /dev/null +++ b/docs/extensibility/utility/README.md @@ -0,0 +1,78 @@ +# Utility Plugins + +Utilities are link-time extensions for compiled Selene programs. They are useful +when the user program needs ordinary classical helper symbols, such as an +argument reader, a calibration data accessor, or a logging helper. + +Utilities are different from runtime, error-model, and simulator plugins: + +- They are linked into the final executable by `selene_sim.build(...)`. +- They are not selected from the runtime plugin configuration. +- They do not have a descriptor. +- They may optionally register shot lifecycle callbacks after Selene has loaded + its configuration. + +## Python Shape + +A utility package exposes a Python class derived from `selene_core.Utility`: + +```python +from pathlib import Path +from selene_core import Utility + +class MyUtility(Utility): + @property + def library_file(self) -> Path: + return Path(__file__).parent / "_dist/lib/libmy_utility.so" + + @property + def library_search_dirs(self) -> list[Path]: + return [self.library_file.parent] + + @property + def registration_symbol(self) -> str | None: + return "my_utility_register" +``` + +Pass utilities to the build step: + +```python +from selene_sim.build import build + +instance = build(program, utilities=[MyUtility()]) +``` + +Selene generates a small C object in an artifact subdirectory that calls each +registration symbol once after `selene_load_config(...)` succeeds. + +## Registration Function + +A registration symbol has this C signature: + +```c +struct selene_void_result_t my_utility_register(SeleneInstance *instance); +``` + +It can call `selene_register_utility_event_callbacks(...)` directly, or, when +the utility links against the Base QIS helper library, call the wrapper +`register_utility_event_callbacks(...)` from `base_qis/hooks.h`. + +The callback struct currently supports: + +- `on_shot_start(context, shot_id)`; +- `on_shot_end(context, shot_id)`. + +Both callbacks return `struct selene_void_result_t`. A nonzero error code aborts +the current run with a utility callback error. + +## Link-Time Boundaries + +On macOS and Windows, unresolved symbols in a utility shared library generally +fail at link time. If a utility calls Base QIS helper symbols such as +`panic_str`, `log_utility_call`, or `register_utility_event_callbacks`, link it +against the Base QIS library and provide the correct library search directory in +the Python `Utility` class. + +If a utility emits quantum gates, it should not invent private gate entrypoints. +Use the same gateset registration and `selene_gate(...)` path as a QIS +interface. diff --git a/docs/migration/0.3.md b/docs/migration/0.3.md index 26f0efb1..d98c27bb 100644 --- a/docs/migration/0.3.md +++ b/docs/migration/0.3.md @@ -118,11 +118,11 @@ then instantiate gates from that gateset: ```python from math import pi -from selene_core import Gateset, PhasedX, RZ, ZZPhase +from selene_core import HeliosGateSet from selene_sim import Quest from selene_sim.interactive import InteractiveSimulator -gates = Gateset(RZ, PhasedX, ZZPhase) +gates = HeliosGateSet sim = InteractiveSimulator( simulator=Quest(), n_qubits=2, @@ -143,11 +143,11 @@ sim.gate(gates.PhasedX(0, pi, 0.0)) Use the same model with `InteractiveFullStack`: ```python -from selene_core import Gateset, PhasedX, RZ, ZZPhase +from selene_core import HeliosGateSet from selene_sim import Quest, SoftRZRuntime from selene_sim.interactive import InteractiveFullStack -gates = Gateset(RZ, PhasedX, ZZPhase) +gates = HeliosGateSet stack = InteractiveFullStack( simulator=Quest(), runtime=SoftRZRuntime(), @@ -187,6 +187,10 @@ gates = QuantinuumGateSet gate = gates.PhasedX(q0=0, theta=1.57079632679, phi=0.0) ``` +`HeliosGateSet`, `SolGateSet`, and `QuantinuumGateSet` are ready-made +`Gateset` values. Use `QuantinuumGateSet` when a component accepts the union of +the Helios and Sol builtin vocabularies. + Define custom gates with semantic IDs: ```python @@ -413,36 +417,40 @@ exported as a top-level symbol. Simulator descriptors now include: - `last_error_fn(output, output_len, written)`; -- `gate_fn(handle, const uint8_t *data, size_t len)`; +- `get_name_fn()`; +- `handle_operations_fn(handle, RuntimeExtractOperationHandle, + OperationResultHandle)`; - `negotiate_gateset_fn(handle, input, input_len, output, output_len, written)`; -- measurement, postselect, reset, metrics, state dump, shot lifecycle, and - init/exit callbacks. +- metrics, state dump, shot lifecycle, and init/exit callbacks. Runtime descriptors now include: - `last_error_fn(output, output_len, written)`; +- `get_name_fn()`; - `gate_fn(handle, const uint8_t *data, size_t len)`; - `negotiate_gateset_fn(...)`; - generic output callback `gate_fn` in `SeleneRuntimeGetOperationInterface`. -Simulator descriptors now include: - -- `last_error_fn(output, output_len, written)`; -- `handle_operations_fn(handle, RuntimeExtractOperationHandle, - OperationResultHandle)`; -- `negotiate_gateset_fn(...)`. - Error-model descriptors now include: - `last_error_fn(output, output_len, written)`; +- `get_name_fn()`; - `handle_operations_fn(handle, RuntimeExtractOperationHandle, SimulatorHandle, OperationResultHandle)`; - `negotiate_gateset_fn(...)`; - init without simulator plugin path/arguments. -Use `selene/gatewire.h` for gatesets and serialized gates. In C, text fields -are `const char *` plus a length. Serialized gates and gatesets are byte -buffers: +All runtime, error-model, and simulator descriptors begin with +`PluginDescriptorHeaderV1`. The name and last-error callbacks are no longer +optional. + +Use the headers from `selene-core` for plugin ABIs and gatewire: +`selene/runtime.h`, `selene/error_model.h`, `selene/simulator.h`, +`selene/plugin.h`, and `selene/gatewire.h`. Use `selene/selene.h` from +`selene-sim` for frontend/QIS calls into a running Selene instance. + +In C, text fields are `const char *` plus a length. Serialized gates and +gatesets are byte buffers: ```c const uint8_t *data; @@ -506,6 +514,10 @@ Then serialize each gate instance and emit it through `selene_gate`: selene_gate(selene_instance, gate_bytes, gate_bytes_len); ``` +Include `selene/gatewire.h` explicitly in C files that use `GwGateSet`, +`GwGateValue`, or other `gw_*` APIs. `selene/selene.h` no longer re-exports the +gatewire ABI. + For bundled interfaces: - Helios emits `RZ`, `PhasedX`, and `ZZPhase`; @@ -516,9 +528,15 @@ not pretend to be the builtin gateset unless you are emitting those semantic IDs and operand layouts. Utility plugins that only perform classical work or use -`selene_custom_runtime_call` may not need changes. Utilities or frontend shims -that emit quantum gates must use the same gateset registration and `selene_gate` -path as QIS interfaces. +`selene_custom_runtime_call` may not need gate changes. Utilities can now expose +a Python `registration_symbol`; Selene links a generated registration object +under the build artifacts support directory and calls that symbol after +configuration. A utility registration function may register shot lifecycle +callbacks with `selene_register_utility_event_callbacks`, or with the Base QIS +wrapper `register_utility_event_callbacks` when linking against Base QIS. + +Utilities or frontend shims that emit quantum gates must use the same gateset +registration and `selene_gate` path as QIS interfaces. ## Custom Gateset Example diff --git a/selene-core/README.md b/selene-core/README.md index 1b4fb0da..8ff9b6f9 100644 --- a/selene-core/README.md +++ b/selene-core/README.md @@ -1,20 +1,20 @@ # Selene-Core -Selene is designed to be extensible through the use of plugins, in the form -of compiled libraries and lightweight python interfaces that provide configuration -for the selene-sim frontend. We achieve this through this selene-core crate and -python module. +Selene is designed to be extensible through compiled plugins, link-time +utilities, custom gate definitions, and lightweight Python interfaces that +provide configuration for the `selene-sim` frontend. We achieve this through the +`selene-core` Rust crate and Python package. Each plugin should comprise a python component and a compiled library component. -The compiled library implements the Selene plugin API, and the python component -provides configuration, link information and the path to the compiled library to -the selene frontend. +The compiled library implements the Selene descriptor ABI, and the Python +component provides configuration, link information, and the path to the compiled +library to the Selene frontend. ## The python module -The selene-core python module provides interfaces for plugins to adhere to. It also -provides a bundled include directory, containing C headers for the Selene plugin API -for each type of component. +The `selene-core` Python module provides base classes for plugin packages to +adhere to. It also provides a bundled include directory containing the C headers +for the plugin ABI and gatewire. To access the C headers in the build stage of a python package, depend on selene-core as a build dependency and call `selene_core.get_include_directory()`. The resulting @@ -24,18 +24,21 @@ through: #include # for the simulator API #include # for the error model API #include # for the runtime API +#include # for gatesets and serialized gates ``` -By implementing the required functions, the plugin can be dynamically loaded by Selene -at runtime. +By exporting the relevant descriptor, the plugin can be dynamically loaded by +Selene at runtime. ## The rust crate The selene-core rust crate defines the compiled plugin interfaces for the Selene backend to use. It additionally provides helper functionality for rust-based plugins -to expose the Selene plugin APIs while providing a more idiomatic trait interface. - -See the following for examples: -- [example simulator](examples/simulator/rust/lib.rs) -- [example error model](examples/error_model/rust/lib.rs) -- [example runtime](examples/runtime/rust/lib.rs) +to expose the Selene plugin APIs while providing a more idiomatic trait +interface. The same crate owns the gatewire Rust API, including builtin gatesets +such as `HeliosGateSet`, `SolGateSet`, and `QuantinuumGateSet`. + +For current plugin documentation, start with +[the extensibility docs](../docs/extensibility/README.md). For a complete +custom-gateset project, see +[the Clifford+T stack example](../examples/clifford_t_stack/README.md). diff --git a/selene-core/python/selene_core/README.md b/selene-core/python/selene_core/README.md index c704f740..943a6580 100644 --- a/selene-core/python/selene_core/README.md +++ b/selene-core/python/selene_core/README.md @@ -1,18 +1,17 @@ # Selene-Core -Selene is designed to be extensible through the use of plugins, in the form -of compiled libraries and lightweight python interfaces that provide configuration -for the selene-sim frontend. We achieve this through this selene-core crate and -python module. +Selene is designed to be extensible through compiled plugins, link-time +utilities, custom gate definitions, and lightweight Python interfaces that +provide configuration for the `selene-sim` frontend. Each plugin should comprise a python component and a compiled library component. -The compiled library implements the Selene plugin API, and the python component -provides configuration, link information and the path to the compiled library to -the selene frontend. +The compiled library implements the Selene descriptor ABI, and the Python +component provides configuration, link information, and the path to the compiled +library to the Selene frontend. -The selene-core python module provides interfaces for plugins to adhere to. It also -provides a bundled include directory, containing C headers for the Selene plugin API -for each type of component. +The `selene-core` Python module provides interfaces for plugin packages to +adhere to. It also provides a bundled include directory containing the C headers +for the plugin ABI and gatewire. To access the C headers in the build stage of a python package, depend on selene-core as a build dependency and call `selene_core.get_include_directory()`. The resulting @@ -22,7 +21,11 @@ through: #include # for the simulator API #include # for the error model API #include # for the runtime API +#include # for gatesets and serialized gates ``` -By implementing the required functions, the plugin can be dynamically loaded by Selene -at runtime. +By exporting the relevant descriptor, the plugin can be dynamically loaded by +Selene at runtime. + +For current plugin, utility, and gateset documentation, see the repository's +[extensibility docs](../../../docs/extensibility/README.md). diff --git a/selene-core/python/selene_core/build_utils/README.md b/selene-core/python/selene_core/build_utils/README.md index beed5c99..6865e5b1 100644 --- a/selene-core/python/selene_core/build_utils/README.md +++ b/selene-core/python/selene_core/build_utils/README.md @@ -32,9 +32,12 @@ with looks like: - This is then compiled in a subsequent step into an object file with the above calling convention - The object file is linked against a "shim" library which maps the appropriate - platform calls to selene functions, e.g. `selene_rz(qubit: u64, theta: f64)`. -- The object file is linked against `libselene.so`, which contains the selene hooks - (such as `selene_rz`) for invoking quantum operations. + platform calls to Selene functions. In the 0.3 API, that shim registers the + gateset it emits after `selene_load_config(...)`, serializes gate instances + with gatewire, and calls `selene_gate(...)`. +- The object file is linked against `libselene.so`, which contains the Selene + hooks for loading configuration, registering gatesets, emitting generic gates, + and reporting results. - If any non-quantum external functions are used (such as `gemm`), then the program is further linked against the appropriate libraries. @@ -78,7 +81,7 @@ Examples of ArtifactKind provided in ./builtin.py include: HUGR Packagehugr.package.Package HUGR Package Pointerhugr.package.PackagePointer HUGR Filepathlib.Path (.hugr) - LLVM IR Filepathlib.Path (.llvm) + LLVM IR Filepathlib.Path (.ll) LLVM Bitcode Filepathlib.Path (.bc) Helios Object Filepathlib.Path (.o) Selene Object Filepathlib.Path (.o) diff --git a/selene-core/rust/simulator/README.md b/selene-core/rust/simulator/README.md index 51860142..6aa2d62c 100644 --- a/selene-core/rust/simulator/README.md +++ b/selene-core/rust/simulator/README.md @@ -1,120 +1,53 @@ # Simulator Design -Selene is designed to accept simulators as plugins. These plugins are provided -to selene from outside of selene itself, and thus the interface to them is primarily -designed around extern "C" function calls in order to provide a stable ABI. +Selene accepts simulators as plugins. A simulator owns the quantum state, while +Selene owns the pipeline that feeds operations into it. -This leads to some design decisions that aim to strike a balance between ownership -of resources (by the plugin), while providing selene with the ability to own the -simulator's lifecycle. +The simulator API has two layers: -## The extern "C" mindset for simulators +- `SimulatorInterface` in [interface.rs](interface.rs) is the idiomatic Rust + trait implemented by Rust simulators. +- The descriptor and function-table ABI in [plugin.rs](plugin.rs) and + [inline.rs](inline.rs) is the C-compatible shape used by dynamically loaded + plugins and in-process adapters. -Fundamentally, simulators are objects. They have state, and they have methods -that mutate this state. +## Rust Trait Shape -Of course, C has a somewhat tenuous relationship with object oriented programming. -Rather than the interface one might be used to from C++: +A Rust simulator implements: -```cpp -class MySimulator{ - QuantumRegister register; -public: - MySimulator(std::uint64_t n_qubits); - ~MySimulator(); - void next_shot(std::uint64_t seed); - void gate(const GateInstance& gate); - bool measure(std::uint64_t q); - void reset(std::uint64_t q); -} -``` +- lifecycle callbacks: `shot_start`, `shot_end`, and `exit`; +- `negotiate_gateset`, which validates the final gateset the simulator may + receive; +- `handle_operations`, which applies a batch and returns measurement results; +- optional state dumping and dynamic metrics. -or Rust: +`handle_operations` is the first-class operation entry point. Gates, +measurements, resets, postselection, timing, and custom operations all travel in +the same batch representation. This keeps the simulator ABI aligned with the +runtime and error-model APIs and avoids a split between direct per-operation +callbacks and batched execution. -``` -struct MySimulator { - register: QuantumRegister, -} -impl MySimulator { - fn new(n_qubits: u64) -> Self; - fn next_shot(seed: u64); - fn gate(&mut self, gate: &OwnedGateInstance); - fn measure(&mut self, q: u64) -> bool; - fn reset(&mut self, q: u64); -} -``` +## Gate Compatibility -A C interface in which the type of the internal state is exposed -to the user might look like: +The default `negotiate_gateset` implementation accepts the builtin +`QuantinuumGateSet`. Simulators with a smaller domain should override this +method and reject unsupported gates before any shot starts. For example, a +Clifford-only simulator should fail negotiation for a gateset containing a +non-Clifford gate rather than attempting to approximate it later. -```c -struct MySimulator { - QuantumRegister register; -}; +Simulators decode generic gate instances by semantic ID through gatewire. They +should not branch on frontend names such as historical `rxy` or `rzz`. -void MySimulator_create(struct MySimulator** sim, uint64_t n_qubits); -void MySimulator_destroy(struct MySimulator* sim); -void MySimulator_next_shot(struct MySimulator* sim, uint64_t seed); -void MySimulator_gate(struct MySimulator* sim, const GateInstance* gate); -bool MySimulator_measure(struct MySimulator* sim, uint64_t q); -void MySimulator_reset(struct MySimulator* sim, uint64_t q); -``` +## C ABI Shape -and one that does not expose the internal state might look like: +The C-compatible descriptor begins with `PluginDescriptorHeaderV1`, including +the mandatory plugin name and last-error callbacks. The simulator descriptor +then provides lifecycle callbacks, `handle_operations_fn`, +`negotiate_gateset_fn`, metrics, and optional state dumping. -```c -void MySimulator_create(void** sim, uint64_t n_qubits, uint64_t random_seed); -void MySimulator_destroy(void* sim); -void MySimulator_next_shot(void* sim, uint64_t seed); -void MySimulator_gate(void* sim, const GateInstance* gate); -bool MySimulator_measure(void* sim, uint64_t q); -void MySimulator_reset(void* sim, uint64_t q); -``` +The helper macro `export_simulator_plugin!(FactoryType)` in [helper.rs](helper.rs) +turns a Rust `SimulatorInterfaceFactory` implementation into that descriptor and +captures Rust callback errors for `last_error_fn`. -Note that in the second example, the internal state of the simulator is generic for -the purposes of a user. They cannot know of the type of `MySimulator`, they cannot -easily access internal fields of `MySimulator`, or keep its state on the stack (as -its size is unknown). What they can do, though is keep track of a memory address -through a void pointer: - -```c -void* instance_1; -void* instance_2; -MySimulator_create(&instance_1, 10); -MySimulator_create(&instance_2, 10); -MySimulator_next_shot(instance_1, 1234); -MySimulator_next_shot(instance_2, 1235); -MySimulator_gate(instance_1, &gate_1); -MySimulator_gate(instance_2, &gate_2); -MySimulator_destroy(instance_1); -MySimulator_destroy(instance_2); -``` - -In this case, you are entrusting ownership of the state of the simulator entirely -to the simulator itself, but you still have sufficient information to pick and choose -between different simulator instances. The C implementation could later completely change -the memory contents of its internal state and the implementation of its functions, yet -it would still function with the above code. - -## The actual simulator interface - -With the above in mind, let's discuss the interface that selene provides for -simulators. - -Simulators can be plugged into various interfaces to selene functionality as a -shared object (from a built dynamic library), and they can be provided as a -struct that acts as a function table. In the case of a shared object, libloading -is used to load the shared object and retrieve the function table, and this is -done in [plugin.rs](./plugin.rs). In the case of a function table, this interface -is defined in [inline.rs](./inline.rs). - -In both cases, the interface is wrapped in a struct that adheres to the SimulationInterface trait -defined in [./interface.rs](./interface.rs). For the purposes of simulation, a -`GenericSimulationInterface` is provided, which wraps an Arc, -allowing the simulation interface to be shared generically around selene as it sees fit, -and keeping lifetime management simple. - -One can define both of these with help from [helper.rs](./helper.rs), which allows -them to use a normal Rust struct and implement the SimulatorHelper trait. A -`export_simulator_plugin!(YourStructName)` macro invocation will provide -`pub extern "C"` functions automatically. +For user-facing simulator plugin tutorials, see +[the extensibility simulator docs](../../../docs/extensibility/simulator/README.md). diff --git a/selene-sim/python/selene_sim/README.md b/selene-sim/python/selene_sim/README.md index 51eb112c..a7ee628d 100644 --- a/selene-sim/python/selene_sim/README.md +++ b/selene-sim/python/selene_sim/README.md @@ -4,7 +4,8 @@ Selene is a quantum computer emulation platform written primarily in Rust with a python frontend. Selene is built with flexibility in mind. This includes: -- A plugin system for the addition of additional components including simulators, error models, quantum runtimes to be provided within Selene or as third party plugins +- A descriptor-based plugin system for simulators, error models, quantum runtimes, and link-time utilities provided either within Selene or as third-party packages. +- Gatewire gatesets, allowing interfaces and plugins to negotiate custom gate vocabularies before a run starts. - Support for custom input formats and device APIs through [the selene-core build system](https://github.com/quantinuum/selene/tree/main/selene-core/python/selene_core/build_utils). ## What's included @@ -12,7 +13,7 @@ Selene is built with flexibility in mind. This includes: Out of the box, Selene provides first-class support for the [HUGR](https://github.com/quantinuum/hugr) ecosystem, including execution of [Guppy](https://github.com/quantinuum/guppy) programs in an emulation environment, making use of our [open-source compiler](https://github.com/quantinuum/tket2/tree/main/qis-compiler/). You can find many examples of guppy usage in our [unit tests](https://github.com/quantinuum/selene/tree/main/selene-sim/python/tests/test_guppy.py). Selene provides a range of simulators, including: -- Statevector simulation using [QuEST](https://github.com/QuEST-Kit/QuEST) and the [quest-sys crate](https://crates.io/crates/quest-sys). +- Statevector simulation using [QuEST](https://github.com/QuEST-Kit/QuEST). - Stabilizer simulation using [Stim](https://github.com/quantumlib/Stim) - Coinflip simulation with customisable bias - Classical Replay, for running pre-recorded measurements without direct simulation @@ -26,6 +27,9 @@ And we offer two example quantum runtimes, including: - Simple, which executes the program as-is, without any modifications - SoftRZ, which elides Z rotations through PhasedX gates, providing the same observable behaviour with fewer quantum operations +For plugin, utility, and custom-gateset documentation, see the +[Selene extensibility docs](https://github.com/quantinuum/selene/tree/main/docs/extensibility). + ## Usage example Although examples are provided in our [tests](https://github.com/quantinuum/selene/tree/main/selene-sim/python/tests) folder, here is a quick walkthrough to get you started with Selene, HUGR and Guppy. @@ -78,7 +82,7 @@ print(shot) # run_shots runs efficient multi-shot simulations # n_processes provides multi-processing across shots # deterministic results can be achieved by providing a random seed -shots = QsysShot(runner.run( +shots = QsysResult(runner.run_shots( simulator=Stim(random_seed=5), n_qubits=10, n_shots=100, diff --git a/selene-sim/python/tests/test_guppy.py b/selene-sim/python/tests/test_guppy.py index 8fafbc92..a251866e 100644 --- a/selene-sim/python/tests/test_guppy.py +++ b/selene-sim/python/tests/test_guppy.py @@ -1,6 +1,5 @@ import datetime import importlib -import sys from pathlib import Path from textwrap import dedent @@ -391,14 +390,7 @@ def main() -> None: ) ) measured = "".join(shots.register_bitstrings()["result"]) - # TODO: try to get emulators to match RNG behaviour - # across OSes. This is primarily in the hands - # of upstream deps, but if there's a way we can - # force it to behave the same then we should. - if sys.platform in ["win32", "cygwin"]: - assert measured == "0001000010" - else: - assert measured == "1000100100" + assert measured == "1000100100" def test_get_current_shot(compiled_guppy): From 2f2cc7c4e9c6a138669577ef077ae22334d26e13 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:46:06 +0100 Subject: [PATCH 19/19] Docs cleanup, bump to 1.0.0a0, add github/release-please content for upcoming 1.0-series --- .github/workflows/build_wheels.yml | 4 ++ .github/workflows/ci_rust.yml | 2 + .github/workflows/nitpick.yml | 2 + .github/workflows/release-please.yml | 2 + .release-please-manifest.json | 4 +- Cargo.lock | 24 +++---- Cargo.toml | 2 +- docs/extensibility/error-model/README.md | 7 +- docs/extensibility/error-model/c.md | 9 +-- docs/extensibility/fundamentals.md | 7 +- docs/extensibility/gates/README.md | 7 +- docs/extensibility/utility/README.md | 2 +- docs/migration/0.3.md | 86 +++++++++++++++-------- examples/clifford_t_stack/Cargo.lock | 2 +- examples/clifford_t_stack/pyproject.toml | 4 +- pyproject.toml | 6 +- selene-core/Cargo.toml | 32 +++++++-- selene-core/README.md | 14 ++-- selene-core/pyproject.toml | 2 +- selene-ext/utilities/argreader/Cargo.lock | 2 +- uv.lock | 4 +- 21 files changed, 139 insertions(+), 85 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 2849586d..f40ce892 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -4,11 +4,15 @@ on: pull_request: branches: - main + - 0.2-series - 0.3-series + - 1.0-series push: branches: - main + - 0.2-series - 0.3-series + - 1.0-series tags: - "*" workflow_dispatch: diff --git a/.github/workflows/ci_rust.yml b/.github/workflows/ci_rust.yml index feb3b355..fd407ca9 100644 --- a/.github/workflows/ci_rust.yml +++ b/.github/workflows/ci_rust.yml @@ -4,7 +4,9 @@ on: pull_request: branches: - main + - 0.2-series - 0.3-series + - 1.0-series concurrency: group: ${{ github.head_ref || github.run_id }} diff --git a/.github/workflows/nitpick.yml b/.github/workflows/nitpick.yml index 061f2aad..6f8cf2f8 100644 --- a/.github/workflows/nitpick.yml +++ b/.github/workflows/nitpick.yml @@ -10,7 +10,9 @@ on: push: branches: - main + - 0.2-series - 0.3-series + - 1.0-series env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 6b4e8abd..2baa4abe 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -5,7 +5,9 @@ on: push: branches: - main + - 0.2-series - 0.3-series + - 1.0-series permissions: contents: write diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c99cd7b2..5ef04324 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,4 @@ { - "selene-core": "0.3.0-alpha.1", - ".": "0.3.0-alpha.1" + "selene-core": "1.0.0-alpha.0", + ".": "1.0.0-alpha.0" } diff --git a/Cargo.lock b/Cargo.lock index e833aa08..b51a740c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,7 +648,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "bitflags", @@ -664,7 +664,7 @@ dependencies = [ [[package]] name = "selene-error-model-depolarizing" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -675,7 +675,7 @@ dependencies = [ [[package]] name = "selene-error-model-ideal" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "selene-core", @@ -683,7 +683,7 @@ dependencies = [ [[package]] name = "selene-error-model-simple-leakage" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -694,7 +694,7 @@ dependencies = [ [[package]] name = "selene-sim" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "rand", @@ -707,7 +707,7 @@ dependencies = [ [[package]] name = "selene-simple-runtime" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -716,7 +716,7 @@ dependencies = [ [[package]] name = "selene-simulator-classical-replay" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "base64", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "selene-simulator-coinflip" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "approx", @@ -738,7 +738,7 @@ dependencies = [ [[package]] name = "selene-simulator-quantum-replay" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "base64", @@ -748,7 +748,7 @@ dependencies = [ [[package]] name = "selene-simulator-quest" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "bytesize", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "selene-simulator-stim" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -770,7 +770,7 @@ dependencies = [ [[package]] name = "selene-soft-rz-runtime" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index b92f5834..1271245d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ rust-version = "1.94" authors = ["Jake Arkinstall "] edition = "2024" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" repository = "https://github.com/quantinuum/selene" license = "Apache-2.0" diff --git a/docs/extensibility/error-model/README.md b/docs/extensibility/error-model/README.md index ed694dbe..bcfafece 100644 --- a/docs/extensibility/error-model/README.md +++ b/docs/extensibility/error-model/README.md @@ -4,10 +4,9 @@ An error model plugin sits between the runtime and simulator. It receives batches of operations from the runtime, applies a stochastic model, calls the simulator, and reports measurement results back to Selene. -Selene's current error-model API is operation-level. Noise is represented by -injecting gates, resets, measurements, delays, or other operation mutations. It -does not currently expose a density-matrix channel API to error models, although -that may be added later. +Selene's error-model API is operation-level. Noise is represented by injecting +gates, resets, measurements, delays, or other operation mutations. It does not +expose a density-matrix channel API to error models. Use an error model plugin when you want to model behavior such as: diff --git a/docs/extensibility/error-model/c.md b/docs/extensibility/error-model/c.md index 09dd2534..4a87fdc5 100644 --- a/docs/extensibility/error-model/c.md +++ b/docs/extensibility/error-model/c.md @@ -124,11 +124,12 @@ re-emit them. ## 4. Inspect a Batch -If the model needs to inspect or mutate operations, Selene still does not expose -the runtime batch as a raw array. Instead, pass a collector to -`batch.interface.extract_fn`. Selene replays the batch into that collector +If the model needs to inspect or mutate operations, use the batch extractor +rather than assuming an in-memory array layout. Pass a collector to +`batch.interface.extract_fn`; Selene replays the batch into that collector through callbacks such as `gate_fn`, `measure_fn`, `postselect_fn`, and -`reset_fn`. +`reset_fn`. This keeps the C ABI independent of Selene's internal batch +representation. A collector usually stores decoded operations in model-owned memory, records an error flag if any callback fails, then builds one or more simulator batches. diff --git a/docs/extensibility/fundamentals.md b/docs/extensibility/fundamentals.md index c493db5e..4a421d89 100644 --- a/docs/extensibility/fundamentals.md +++ b/docs/extensibility/fundamentals.md @@ -302,7 +302,6 @@ gateset was invalid, the buffer was too small, or a pointer argument was wrong. ## Threading and Reentrancy Plugin callbacks should not assume they are globally unique. Selene may create -multiple instances, and future execution modes may run independent instances at -the same time. Keep instance state behind the instance pointer or Rust struct. -If you use process-global state, protect it explicitly and document why it is -shared. +multiple instances, and independent instances should not share mutable state by +accident. Keep instance state behind the instance pointer or Rust struct. If you +use process-global state, protect it explicitly and document why it is shared. diff --git a/docs/extensibility/gates/README.md b/docs/extensibility/gates/README.md index 67264362..ea885960 100644 --- a/docs/extensibility/gates/README.md +++ b/docs/extensibility/gates/README.md @@ -20,9 +20,10 @@ Selene ships four builtin gate declarations: - `ZZPhase(q0, q1, theta)` - `PhasedXX(q0, q1, theta, phi)` -The builtin names are the names used by the plugin API. Older names such as -`rz`, `rxy`, `rzz`, and `rpp` belong at compatibility boundaries such as QIS -parsers, not inside plugin implementations. +The builtin names are the names used by the plugin API. Older 0.2 names such as +`rz`, `rxy`, and `rzz` belong at compatibility boundaries such as QIS parsers, +not inside plugin implementations. In 0.3 we have also added `PhasedXX` as a +new builtin gate for the Sol gateset. Convenience gatesets are available where the common platform vocabulary is useful: diff --git a/docs/extensibility/utility/README.md b/docs/extensibility/utility/README.md index e69472be..f899d503 100644 --- a/docs/extensibility/utility/README.md +++ b/docs/extensibility/utility/README.md @@ -57,7 +57,7 @@ It can call `selene_register_utility_event_callbacks(...)` directly, or, when the utility links against the Base QIS helper library, call the wrapper `register_utility_event_callbacks(...)` from `base_qis/hooks.h`. -The callback struct currently supports: +The callback struct supports: - `on_shot_start(context, shot_id)`; - `on_shot_end(context, shot_id)`. diff --git a/docs/migration/0.3.md b/docs/migration/0.3.md index d98c27bb..af9975ad 100644 --- a/docs/migration/0.3.md +++ b/docs/migration/0.3.md @@ -1,12 +1,28 @@ -# Migrating To Selene 0.3 +# Migrating To Selene 1.0 This guide is for projects moving from the `main`/0.2-series API to the -0.3-series API. +1.0-series API. + +Selene 1.0 takes the opportunity of a breaking change to introduce some long- +awaited improvements to the plugin model. The python interface remains broadly +unchanged, though there are some slight changes in the context of gates. + +The main new concept is gatewire. Selene 0.2 exposed a fixed builtin gate +vocabulary through the core/plugin APIs. Selene 1.0 introduces gatewire, an +API for describing gates and gatesets: frontends, runtimes, error models, and +simulators can negotiate the gates they use, instead of forcing every plugin +API to be bound to one hardcoded gateset. + +Another concept is the architectural separation of the simulator and error +model. In Selene 0.2, error models owned and loaded the simulator, and all +of their interaction with the simulator was internal and kept separate from +libselene itself. While this gave error models the ability to arbitrarily +act upon the simulator, it also reduced observability, required repetition, +and lead to confusing error messages (e.g. simulator errors appeared as +error model errors). We now provide error models with callback structs to +interact with the simulator, while keeping it under the control and +observability of libselene. -Selene 0.3 is intentionally a breaking release. The Python build-and-run -workflow is mostly stable, but the extension model has changed substantially: -gates are now explicit data carried through a negotiated gateset, and plugin -ABIs are descriptor-based. ## Who Needs To Change Code? @@ -27,20 +43,22 @@ You need changes if you: ## Upgrade Checklist -1. Upgrade `selene-sim` and `selene-core` together to the 0.3 series. +1. Upgrade `selene-sim` and `selene-core` together to the 1.0 series. 2. Rebuild all native plugins. 0.2 plugin binaries are not ABI-compatible with - 0.3. + 1.0. 3. Replace old gate names: - | 0.2 name | 0.3 name | + | 0.2 name | 1.0 name | | --- | --- | | `rz` / `Rz` | `RZ` | | `rxy` / `Rxy` | `PhasedX` | | `rzz` / `Rzz` | `ZZPhase` | - | `rpp` / `Rpp` | `PhasedXX` | -4. Replace direct interactive gate calls with gateset-bound gates and - `sim.gate(...)`. + In 1.0 we have also added a new builtin gate, `PhasedXX`, as part of the + Sol gateset. + +4. Replace direct interactive gate shortcuts with gate instances constructed + from a registered gateset. 5. Update snapshots and metric assertions from typed gate opcodes to generic gate records. 6. For plugin code, implement gateset negotiation and generic gate handling. @@ -74,7 +92,7 @@ new behavior, register a build kind and build step with Selene's build planner. ### Selecting Helios Or Sol -Helios remains the default interface. 0.3 also includes a Sol interface. +Helios remains the default interface. 1.0 also includes a Sol interface. Use either an explicit interface: @@ -95,7 +113,8 @@ sol_instance = build(program, platform="sol") ``` Helios registers and emits `RZ`, `PhasedX`, and `ZZPhase`. Sol registers and -emits `RZ`, `PhasedX`, and `PhasedXX`. +emits `RZ`, `PhasedX`, and `PhasedXX`. `PhasedXX` is new in 1.0 and is part of +the Sol gateset. ## Interactive Python @@ -113,8 +132,9 @@ sim.rz(0, pi / 2) sim.rzz(0, 1, pi / 2) ``` -In 0.3, construct or import a gateset, pass it to the interactive simulator, -then instantiate gates from that gateset: +In 1.0, construct or import a gateset, pass it to the interactive simulator, +then instantiate gates from that gateset. The old per-gate methods are gone; +the interactive API now has one generic gate-submission method: ```python from math import pi @@ -191,7 +211,7 @@ gate = gates.PhasedX(q0=0, theta=1.57079632679, phi=0.0) `Gateset` values. Use `QuantinuumGateSet` when a component accepts the union of the Helios and Sol builtin vocabularies. -Define custom gates with semantic IDs: +Define custom gates with semantic IDs, and compose them into a gateset: ```python from selene_core import GateDefinition, Gateset, OperandDefinition, QUBIT, F64 @@ -217,7 +237,9 @@ gate = clifford_t.CNOT(control=0, target=1) ``` The semantic ID is the compatibility key. The display name is for humans, -metrics, traces, and snapshots. +metrics, traces, and snapshots. In order to use this gateset with selene, +the components you use must accept the semantic IDs you define, and their +signatures must match. ## Events, Traces, And Metrics @@ -274,7 +296,7 @@ error placement should be treated as behavior changes and checked carefully. ## Plugin Authors -All native plugin authors need to update for 0.3. The old exported function +All native plugin authors need to update for 1.0. The old exported function sets have been replaced by plugin descriptors, and gate operations now travel through gatewire. @@ -304,7 +326,7 @@ fn rzz_gate(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()>; fn rz_gate(&mut self, qubit_id: u64, theta: f64) -> Result<()>; ``` -0.3 runtimes implement generic gates: +1.0 runtimes implement generic gates: ```rust use selene_core::gatewire::{DynamicGateSet, OwnedGateInstance}; @@ -328,7 +350,7 @@ variant rather than `RXYGate`, `RZGate`, or `RZZGate`. the simulator plugin path and simulator arguments, and `shot_start` received a simulator seed. -In 0.3, Selene owns the simulator and passes a simulator interface to the error +In 1.0, Selene owns the simulator and passes a simulator interface to the error model when operations are handled: ```rust @@ -380,7 +402,7 @@ fn rz(&mut self, qubit: u64, theta: f64) -> Result<()>; fn rzz(&mut self, q0: u64, q1: u64, theta: f64) -> Result<()>; ``` -0.3 simulators negotiate a gateset and handle batches: +1.0 simulators negotiate a gateset and handle batches: ```rust use selene_core::gatewire::DynamicGateSet; @@ -411,7 +433,7 @@ example, a Clifford-only simulator should reject non-Clifford gates there. ## C ABI Plugins -0.3 uses descriptor structs rather than requiring every plugin function to be +1.0 uses descriptor structs rather than requiring every plugin function to be exported as a top-level symbol. Simulator descriptors now include: @@ -480,7 +502,7 @@ selene_rz(selene_instance, q0, theta); selene_rzz(selene_instance, q0, q1, theta); ``` -In 0.3, register the interface gateset once after configuration: +In 1.0, register the interface gateset once after configuration: ```c struct selene_void_result_t result = @@ -515,8 +537,9 @@ selene_gate(selene_instance, gate_bytes, gate_bytes_len); ``` Include `selene/gatewire.h` explicitly in C files that use `GwGateSet`, -`GwGateValue`, or other `gw_*` APIs. `selene/selene.h` no longer re-exports the -gatewire ABI. +`GwGateValue`, or other `gw_*` APIs. Treat `selene/selene.h` as the frontend +runtime API and `selene/gatewire.h` as the gate declaration and serialization +API. For bundled interfaces: @@ -540,7 +563,7 @@ registration and `selene_gate` path as QIS interfaces. ## Custom Gateset Example -The repository includes a complete 0.3-style example under +The repository includes a complete 1.0-style example under `examples/clifford_t_stack`. It demonstrates: - a custom Clifford+T gateset; @@ -558,7 +581,7 @@ through normal Selene workflows. ### "Plugin did not expose descriptor symbol or accessor" -The plugin is still exporting the 0.2 ABI. Rebuild it against 0.3 and export +The plugin is still exporting the 0.2 ABI. Rebuild it against 1.0 and export the descriptor expected by the relevant plugin kind. ### "The chosen simulator does not support gate ..." @@ -569,7 +592,8 @@ gate, or teach the simulator to negotiate and implement that gate. ### Interactive `.rxy(...)` or `.rz(...)` is missing -Use a gateset and `sim.gate(...)` instead. +Use a registered gateset to construct a `Gate`, then submit that `Gate` through +the interactive generic gate method. ### Snapshot diffs show `Rxy` changing to `PhasedX` @@ -583,5 +607,5 @@ Use the generic gate metric keys, for example `gate:PhasedX:count`. ## Release-Branch Note At the time this guide was written, `main` represented the 0.2-series release -line and `0.3-series` contained the breaking gatewire/plugin work. This guide -is written for users moving from 0.2.x to the 0.3-series release. +line and `1.0-series` contained the breaking gatewire/plugin work. This guide +is written for users moving from 0.2.x to the 1.0-series release. diff --git a/examples/clifford_t_stack/Cargo.lock b/examples/clifford_t_stack/Cargo.lock index cd3eb97e..ed0250ae 100644 --- a/examples/clifford_t_stack/Cargo.lock +++ b/examples/clifford_t_stack/Cargo.lock @@ -252,7 +252,7 @@ dependencies = [ [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "bitflags", diff --git a/examples/clifford_t_stack/pyproject.toml b/examples/clifford_t_stack/pyproject.toml index 403b77e1..61f72e79 100644 --- a/examples/clifford_t_stack/pyproject.toml +++ b/examples/clifford_t_stack/pyproject.toml @@ -9,8 +9,8 @@ requires-python = ">=3.10" description = "A worked Selene custom-gateset example using Clifford+T plugins" readme = "README.md" dependencies = [ - "selene-core~=0.3.0a0", - "selene-sim~=0.3.0a0", + "selene-core~=1.0.0a0", + "selene-sim~=1.0.0a0", ] [tool.uv.sources] diff --git a/pyproject.toml b/pyproject.toml index 6503d0ba..bdc88169 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,15 +5,13 @@ build-backend = "hatchling.build" [project] name = "selene-sim" description = "Quantinuum's open source emulator for quantum computation" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" requires-python = ">=3.10" dependencies = [ "numpy>=2.2.6", "pyyaml>=6.0.2", - "selene-core~=0.3.0a0", + "selene-core==1.0.0a0", # x-release-please-version "tqdm>=4.67.1", - # TODO: decide if we want this as a formal dependency or not - #"selene-hugr-qis-compiler~=0.3.0", ] authors = [ { name = "Jake Arkinstall", email = "jake.arkinstall@quantinuum.com" }, diff --git a/selene-core/Cargo.toml b/selene-core/Cargo.toml index f153ee47..a39c1734 100644 --- a/selene-core/Cargo.toml +++ b/selene-core/Cargo.toml @@ -1,13 +1,35 @@ [package] name = "selene-core" -version = "0.3.0-alpha.1" -edition = "2024" +version = "1.0.0-alpha.0" +edition.workspace = true rust-version.workspace = true authors.workspace = true repository.workspace = true license.workspace = true -description = "Core Rust APIs for Selene plugins, gatewire gatesets, and simulation infrastructure." +description = "Core Rust APIs for Quantinuum's Selene plugins, gate apis, and simulation infrastructure." readme = "README.md" +homepage = "https://github.com/quantinuum/selene" +documentation = "https://docs.rs/selene-core" +keywords = [ + "Quantum", + "Quantinuum", + "Simulation", + "Selene", +] +categories = [ + "science:quantum-computing", + "emulators", + "simulation", + "api-bindings", + "development-tools:ffi", +] +include = [ + "Cargo.toml", + "README.md", + "CHANGELOG.md", + "rust/**/*.rs", + "rust/**/README.md", +] [lib] name = "selene_core" @@ -27,5 +49,5 @@ smallvec = "1" static_assertions = "1" [lints.clippy] -undocumented_unsafe_blocks = "allow" # TODO: add safety docs -missing_safety_doc = "allow" # TODO: add safety docs +undocumented_unsafe_blocks = "allow" +missing_safety_doc = "allow" diff --git a/selene-core/README.md b/selene-core/README.md index 8ff9b6f9..47ff5a9d 100644 --- a/selene-core/README.md +++ b/selene-core/README.md @@ -10,7 +10,7 @@ The compiled library implements the Selene descriptor ABI, and the Python component provides configuration, link information, and the path to the compiled library to the Selene frontend. -## The python module +## The Python Module The `selene-core` Python module provides base classes for plugin packages to adhere to. It also provides a bundled include directory containing the C headers @@ -30,15 +30,15 @@ through: By exporting the relevant descriptor, the plugin can be dynamically loaded by Selene at runtime. -## The rust crate +## The Rust Crate -The selene-core rust crate defines the compiled plugin interfaces for the Selene -backend to use. It additionally provides helper functionality for rust-based plugins +The `selene-core` Rust crate defines the compiled plugin interfaces for the Selene +backend to use. It additionally provides helper functionality for Rust-based plugins to expose the Selene plugin APIs while providing a more idiomatic trait interface. The same crate owns the gatewire Rust API, including builtin gatesets such as `HeliosGateSet`, `SolGateSet`, and `QuantinuumGateSet`. For current plugin documentation, start with -[the extensibility docs](../docs/extensibility/README.md). For a complete -custom-gateset project, see -[the Clifford+T stack example](../examples/clifford_t_stack/README.md). +[the extensibility docs](https://github.com/quantinuum/selene/tree/main/docs/extensibility). +For a complete custom-gateset project, see +[the Clifford+T stack example](https://github.com/quantinuum/selene/tree/main/examples/clifford_t_stack). diff --git a/selene-core/pyproject.toml b/selene-core/pyproject.toml index bf0b5055..b83e2f6e 100644 --- a/selene-core/pyproject.toml +++ b/selene-core/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" requires-python = ">=3.10" description = "The core interop library for Selene python interfaces" readme = "python/selene_core/README.md" diff --git a/selene-ext/utilities/argreader/Cargo.lock b/selene-ext/utilities/argreader/Cargo.lock index 4e3b83e7..c0247c9a 100644 --- a/selene-ext/utilities/argreader/Cargo.lock +++ b/selene-ext/utilities/argreader/Cargo.lock @@ -215,7 +215,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "bitflags", diff --git a/uv.lock b/uv.lock index 2a76171d..0f1c88e7 100644 --- a/uv.lock +++ b/uv.lock @@ -1409,7 +1409,7 @@ wheels = [ [[package]] name = "selene-core" -version = "0.3.0a1" +version = "1.0.0a0" source = { editable = "selene-core" } dependencies = [ { name = "blake3" }, @@ -1453,7 +1453,7 @@ dev = [{ name = "qir-qis", specifier = "~=0.1.6" }] [[package]] name = "selene-sim" -version = "0.3.0a1" +version = "1.0.0a0" source = { editable = "." } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },