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 6a25b4ff..b51a740c 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]] @@ -520,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" @@ -605,18 +648,23 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", + "bitflags", + "blake3", "delegate", "derive_more", + "indexmap", "libloading", + "smallvec", + "static_assertions", "thiserror", ] [[package]] name = "selene-error-model-depolarizing" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -627,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", @@ -635,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", @@ -646,7 +694,7 @@ dependencies = [ [[package]] name = "selene-sim" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "rand", @@ -659,7 +707,7 @@ dependencies = [ [[package]] name = "selene-simple-runtime" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -668,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", @@ -678,7 +726,7 @@ dependencies = [ [[package]] name = "selene-simulator-coinflip" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "approx", @@ -690,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", @@ -700,19 +748,18 @@ dependencies = [ [[package]] name = "selene-simulator-quest" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "bytesize", - "cc", - "quest-sys", + "cmake", "selene-core", "sysinfo", ] [[package]] name = "selene-simulator-stim" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" dependencies = [ "anyhow", "clap", @@ -723,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", @@ -799,6 +846,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 +960,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/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/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/devenv.nix b/devenv.nix index 5655e262..df4ea185 100644 --- a/devenv.nix +++ b/devenv.nix @@ -4,6 +4,7 @@ packages = with pkgs; [ cmake just + stdenv.cc zlib libxml2 ncurses @@ -69,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/docs/README.md b/docs/README.md new file mode 100644 index 00000000..24150bd7 --- /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, 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 new file mode 100644 index 00000000..909b8c3f --- /dev/null +++ b/docs/extensibility/README.md @@ -0,0 +1,71 @@ +# 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. [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 +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. + +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/README.md b/docs/extensibility/error-model/README.md new file mode 100644 index 00000000..bcfafece --- /dev/null +++ b/docs/extensibility/error-model/README.md @@ -0,0 +1,55 @@ +# 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 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: + +- 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..4a87fdc5 --- /dev/null +++ b/docs/extensibility/error-model/c.md @@ -0,0 +1,376 @@ +# 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. Forward a Batch + +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, 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`. 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. +This stripped-down collector only records that a one-qubit gate was observed: + +```c +typedef struct { + 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; + } + + 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; + } + + gw_decoded_gate_free(gate); +} + +static void collect_measure(SeleneRuntimeGetOperationInstance instance, + uint64_t qubit, + uint64_t result_id) { + (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) { + (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) { + (void)instance; + (void)qubit; + (void)result_id; + /* Store or forward the leakage measurement operation in production code. */ +} + +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. */ +} +``` + +## 5. Extract and Then Call the Simulator + +Build a `RuntimeGetOperationHandle` from your collector and ask Selene to +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( + SeleneErrorModelInstance handle, + struct RuntimeExtractOperationHandle batch, + struct SimulatorHandle simulator, + struct OperationResultHandle results +) { + Collector collector = { + .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, + .gate_fn = collect_gate, + }; + + struct RuntimeGetOperationHandle sink = { + .instance = &collector, + .interface = interface, + }; + + batch.interface.extract_fn(&batch, sink); + if (collector.failed) { + return 1; + } + + return simulator.interface.handle_operations_fn( + simulator.instance, + batch, + results + ); +} +``` + +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. + +## 6. Inject Gates + +To inject a gate, serialize a `GwGateInstanceView` and include it in a simulator +batch. The serialization step looks like this: + +```c +static SeleneErrno serialize_rz(uint32_t qubit, + double theta, + uint8_t **out, + size_t *out_len) { + 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) { + free(buffer); + return 1; + } + + *out = buffer; + *out_len = written; + return 0; +} +``` + +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, + 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; +} +``` + +## 8. Export the Descriptor + +```c +const SeleneErrorModelPluginDescriptorV1 selene_error_model_plugin_descriptor_v1 = { + .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, + .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..6504c61a --- /dev/null +++ b/docs/extensibility/error-model/rust.md @@ -0,0 +1,240 @@ +# 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 name(&self) -> &str { + "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..4a421d89 --- /dev/null +++ b/docs/extensibility/fundamentals.md @@ -0,0 +1,307 @@ +# 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`. + +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 +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. `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: + +```c +#include + +const SeleneRuntimePluginDescriptorV1 selene_runtime_plugin_descriptor_v1 = { + .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 the shared descriptor header +for you from the factory's `name()` method and the plugin type's current API +version. + +## 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. + +## 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`. + +## 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: + +- 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 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: + +- 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. + +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`. + 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`. + +`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 + +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 +`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 +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 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 new file mode 100644 index 00000000..ea885960 --- /dev/null +++ b/docs/extensibility/gates/README.md @@ -0,0 +1,69 @@ +# 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 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: + +- `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 +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..734ee431 --- /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 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) { + 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..4377c25a --- /dev/null +++ b/docs/extensibility/gates/rust.md @@ -0,0 +1,194 @@ +# 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 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 +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 { + 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) +} +``` + +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"), +} +``` + +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. + +## 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..31ad7453 --- /dev/null +++ b/docs/extensibility/runtime/c.md @@ -0,0 +1,251 @@ +# 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 = { + .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, + .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..36b31365 --- /dev/null +++ b/docs/extensibility/runtime/rust.md @@ -0,0 +1,257 @@ +# 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 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 + +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(); + if let Some(unsupported) = input.first_unsupported_by(&accepted) { + bail!("runtime does not accept gate {}", unsupported.name); + } + Ok(accepted) + } + + /* other methods shown below */ +} +``` + +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 + +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 name(&self) -> &str { + "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..bba69af1 --- /dev/null +++ b/docs/extensibility/simulator/c.md @@ -0,0 +1,287 @@ +# 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. Handle Operation Batches + +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 +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; + } + + 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; +} + +static bool apply_measure(MySimulator *sim, uint64_t qubit) { + (void)qubit; + sim->measurements++; + return false; +} + +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(RuntimeGetOperationInstance 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 + ); +} + +static void collect_measure_leaked(RuntimeGetOperationInstance 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(RuntimeGetOperationInstance 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(RuntimeGetOperationInstance instance, + uint64_t qubit) { + SimulatorCollector *collector = (SimulatorCollector *)instance; + (void)collector; + (void)qubit; + /* backend_reset(collector->sim, qubit); */ +} + +static void collect_custom(RuntimeGetOperationInstance instance, + size_t tag, + const void *data, + size_t len) { + (void)instance; + (void)tag; + (void)data; + (void)len; +} + +static void collect_batch_time(RuntimeGetOperationInstance 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; +} +``` + +Validate operand kinds before using them in production code. Postselection is +part of the same operation batch as gates, measurements, and resets. + +## 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 +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; +} +``` + +## 5. Export the Descriptor + +```c +const SeleneSimulatorPluginDescriptorV1 selene_simulator_plugin_descriptor_v1 = { + .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, + .handle_operations_fn = my_simulator_handle_operations, + .get_metrics_fn = my_simulator_get_metrics, + .dump_state_fn = my_simulator_dump_state, + .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..011d9daa --- /dev/null +++ b/docs/extensibility/simulator/rust.md @@ -0,0 +1,206 @@ +# 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 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; +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, GateView}; +use selene_core::gatewire::builtin::{QuantinuumGate, QuantinuumGateSet}; +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 { + QuantinuumGateSet::dynamic() + } +} + +impl SimulatorInterface for MySimulator { + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = Self::supported_gateset(); + if let Some(unsupported) = gateset.first_unsupported_by(&supported) { + bail!("simulator does not support gate {}", unsupported.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) = QuantinuumGateSet::try_from_instance(&gate)? + .map(QuantinuumGate::from_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: QuantinuumGate) -> Result<()> { + match gate { + 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) + } + } + } +} +``` + +## 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 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> { + 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 name(&self) -> &str { + "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/extensibility/utility/README.md b/docs/extensibility/utility/README.md new file mode 100644 index 00000000..f899d503 --- /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 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 new file mode 100644 index 00000000..af9975ad --- /dev/null +++ b/docs/migration/0.3.md @@ -0,0 +1,611 @@ +# Migrating To Selene 1.0 + +This guide is for projects moving from the `main`/0.2-series API to the +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. + + +## 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 1.0 series. +2. Rebuild all native plugins. 0.2 plugin binaries are not ABI-compatible with + 1.0. +3. Replace old gate names: + + | 0.2 name | 1.0 name | + | --- | --- | + | `rz` / `Rz` | `RZ` | + | `rxy` / `Rxy` | `PhasedX` | + | `rzz` / `Rzz` | `ZZPhase` | + + 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. + +## 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. 1.0 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`. `PhasedXX` is new in 1.0 and is part of +the Sol gateset. + +## 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 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 +from selene_core import HeliosGateSet +from selene_sim import Quest +from selene_sim.interactive import InteractiveSimulator + +gates = HeliosGateSet +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 HeliosGateSet +from selene_sim import Quest, SoftRZRuntime +from selene_sim.interactive import InteractiveFullStack + +gates = HeliosGateSet +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, + QuantinuumGateSet, + RZ, + ZZPhase, +) +``` + +Use builtin definitions for Selene's builtin gates: + +```python +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, and compose them into a gateset: + +```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. 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 + +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 1.0. 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<()>; +``` + +1.0 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 1.0, 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<()>; +``` + +1.0 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 + +1.0 uses descriptor structs rather than requiring every plugin function to be +exported as a top-level symbol. + +Simulator descriptors now include: + +- `last_error_fn(output, output_len, written)`; +- `get_name_fn()`; +- `handle_operations_fn(handle, RuntimeExtractOperationHandle, + OperationResultHandle)`; +- `negotiate_gateset_fn(handle, input, input_len, output, output_len, written)`; +- 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`. + +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. + +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; +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 1.0, 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); +``` + +Include `selene/gatewire.h` explicitly in C files that use `GwGateSet`, +`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: + +- 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 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 + +The repository includes a complete 1.0-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 1.0 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 registered gateset to construct a `Gate`, then submit that `Gate` through +the interactive generic gate method. + +### 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 `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/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..ed0250ae --- /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 = "1.0.0-alpha.0" +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..15f1e4ce --- /dev/null +++ b/examples/clifford_t_stack/cpp/src/qrack_simulator.cpp @@ -0,0 +1,491 @@ +#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; +} + +thread_local std::string last_error_message; + +int fail(const char* context, const std::exception& error) { + 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); + } +} + +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_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", [&]() { + 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)); }); +} + +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); +} + +bool apply_measure(QrackSimulator& simulator, uint64_t qubit) { + ensure_qubit(simulator, qubit); + + 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( + 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", [&]() { apply_reset(instance(handle), qubit); }); +} + +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); + 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; + } + } catch (const std::exception& error) { + return fail("get_metrics", error); + } +} + +extern "C" SeleneErrno qrack_dump_state(SeleneSimulatorInstance, const char*, const uint64_t*, uint64_t) { + 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, + 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_last_error, + qrack_get_name, + qrack_init, + qrack_exit, + qrack_shot_start, + qrack_shot_end, + qrack_handle_operations, + qrack_get_metrics, + qrack_dump_state, + 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..61f72e79 --- /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~=1.0.0a0", + "selene-sim~=1.0.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..3da322ea --- /dev/null +++ b/examples/clifford_t_stack/src/error_model.rs @@ -0,0 +1,186 @@ +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::Postselect { + qubit_id, + target_value, + } => { + Self::send(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(( + "injected_x".to_string(), + MetricValue::U64(self.injected_x), + ))), + _ => Ok(None), + } + } +} + +#[derive(Default)] +pub struct CliffordTErrorModelFactory; + +impl ErrorModelInterfaceFactory for CliffordTErrorModelFactory { + type Interface = CliffordTErrorModel; + + fn name(&self) -> &str { + "CliffordT" + } + + 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..f51987ad --- /dev/null +++ b/examples/clifford_t_stack/src/runtime.rs @@ -0,0 +1,234 @@ +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 name(&self) -> &str { + "CliffordT" + } + + 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..96809879 --- /dev/null +++ b/examples/clifford_t_stack/src/simulator.rs @@ -0,0 +1,186 @@ +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::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, + 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/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 2ec5c34a..1a5331ea 100644 --- a/justfile +++ b/justfile @@ -51,6 +51,18 @@ 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 + + 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 @@ -83,4 +95,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..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" }, @@ -120,10 +118,10 @@ 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'] +norecursedirs = ['*.egg', '.*', 'build', 'target', 'dist', 'venv', 'examples'] [tool.deptry] diff --git a/selene-core/Cargo.toml b/selene-core/Cargo.toml index f5909422..a39c1734 100644 --- a/selene-core/Cargo.toml +++ b/selene-core/Cargo.toml @@ -1,7 +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 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" @@ -13,8 +41,13 @@ 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 -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 1b4fb0da..47ff5a9d 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 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 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. +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`. -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) +For current plugin documentation, start with +[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/c/include/selene/core_types.h b/selene-core/c/include/selene/core_types.h index ee862caf..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 @@ -7,23 +10,21 @@ 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; + +#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 + +#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 3a547523..e7a9e80a 100644 --- a/selene-core/c/include/selene/error_model.h +++ b/selene-core/c/include/selene/error_model.h @@ -1,10 +1,13 @@ +#ifndef SELENE_ERROR_MODEL_H +#define SELENE_ERROR_MODEL_H + #include #include #include #include #include #include "selene/core_types.h" - +#define SELENE_ERROR_MODEL_CURRENT_API_VERSION 0x00000200ULL typedef struct SeleneErrorModelAPIVersion { /** @@ -27,31 +30,32 @@ 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 it's provided - * [ErrorModelSetResultInterface]. - */ -typedef void *SeleneErrorModelSetResultInstance; +typedef void *SeleneOperationResultInstance; -/** - * 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, +typedef struct SeleneOperationResultInterface { + void (*set_bool_result_fn)(SeleneOperationResultInstance, uint64_t, bool); - void (*set_u64_result_fn)(SeleneErrorModelSetResultInstance, + void (*set_u64_result_fn)(SeleneOperationResultInstance, uint64_t, uint64_t); -} SeleneErrorModelSetResultInterface; +} 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; + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; +} PluginDescriptorHeaderV1; + typedef void *SeleneRuntimeExtractOperationInstance; /** @@ -68,6 +72,9 @@ typedef struct RuntimeGetOperationInterface { 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, @@ -77,22 +84,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 { @@ -101,7 +95,7 @@ typedef struct RuntimeGetOperationHandle { } RuntimeGetOperationHandle; typedef struct SeleneRuntimeExtractOperationInterface { - void (*extract_fn)(struct RuntimeExtractOperationHandle, + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, struct RuntimeGetOperationHandle); } SeleneRuntimeExtractOperationInterface; @@ -112,33 +106,23 @@ typedef struct RuntimeExtractOperationHandle { typedef void *SeleneSimulatorInstance; +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 (*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 (*handle_operations_fn)(SeleneSimulatorInstance instance, + struct RuntimeExtractOperationHandle batch, + struct OperationResultHandle result); 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, @@ -150,6 +134,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 { @@ -157,14 +150,8 @@ typedef struct SimulatorHandle { struct SimulatorOperationInterface interface; } SimulatorHandle; -typedef struct ErrorModelSetResultHandle { - SeleneErrorModelSetResultInstance instance; - struct SeleneErrorModelSetResultInterface interface; -} ErrorModelSetResultHandle; - typedef struct SeleneErrorModelPluginDescriptorV1 { - uint64_t struct_size; - uint64_t api_version; + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(SeleneErrorModelInstance *handle, uint64_t n_qubits, uint32_t error_model_argc, @@ -177,10 +164,33 @@ 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, 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; + +#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 + +#endif /* SELENE_ERROR_MODEL_H */ diff --git a/selene-core/c/include/selene/gatewire.h b/selene-core/c/include/selene/gatewire.h new file mode 100644 index 00000000..2b1ffc0c --- /dev/null +++ b/selene-core/c/include/selene/gatewire.h @@ -0,0 +1,198 @@ +#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/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 ee78ddfc..dbdcd1bd 100644 --- a/selene-core/c/include/selene/runtime.h +++ b/selene-core/c/include/selene/runtime.h @@ -1,29 +1,13 @@ +#ifndef SELENE_RUNTIME_H +#define SELENE_RUNTIME_H + #include #include #include #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 { /** @@ -58,6 +42,9 @@ typedef struct SeleneRuntimeGetOperationInterface { 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, @@ -67,22 +54,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; @@ -98,17 +72,29 @@ typedef struct RuntimeGetOperationHandle { } RuntimeGetOperationHandle; typedef struct SeleneRuntimeExtractOperationInterface { - void (*extract_fn)(struct RuntimeExtractOperationHandle, + void (*extract_fn)(const struct RuntimeExtractOperationHandle*, struct RuntimeGetOperationHandle); } SeleneRuntimeExtractOperationInterface; typedef int32_t SeleneErrno; -typedef void *RuntimeInstance; +typedef SeleneErrno (*LastErrorFn)(char *output, + size_t output_len, + size_t *written); -typedef struct SeleneRuntimePluginDescriptorV1 { +typedef const char *(*PluginNameFn)(void); + +typedef struct PluginDescriptorHeaderV1 { uint64_t struct_size; uint64_t api_version; + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; +} PluginDescriptorHeaderV1; + +typedef void *RuntimeInstance; + +typedef struct SeleneRuntimePluginDescriptorV1 { + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(RuntimeInstance *handle, uint64_t n_qubits, uint64_t start, @@ -136,22 +122,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 +155,30 @@ 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; + +#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 + +#endif /* SELENE_RUNTIME_H */ diff --git a/selene-core/c/include/selene/simulator.h b/selene-core/c/include/selene/simulator.h index 78a70ff2..5673e078 100644 --- a/selene-core/c/include/selene/simulator.h +++ b/selene-core/c/include/selene/simulator.h @@ -1,29 +1,13 @@ +#ifndef SELENE_SIMULATOR_H +#define SELENE_SIMULATOR_H + #include #include #include #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 { /** @@ -48,10 +32,85 @@ typedef void *SeleneSimulatorInstance; typedef int32_t SeleneErrno; -typedef struct SeleneSimulatorPluginDescriptorV1 { +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; - const char *(*get_name_fn)(void); + LastErrorFn last_error_fn; + PluginNameFn get_name_fn; +} PluginDescriptorHeaderV1; + +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 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 { + struct PluginDescriptorHeaderV1 header; SeleneErrno (*init_fn)(SeleneSimulatorInstance *handle, uint64_t n_qubits, uint32_t argc, @@ -61,29 +120,9 @@ 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, - 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, @@ -93,4 +132,27 @@ typedef struct SeleneSimulatorPluginDescriptorV1 { const char *file, const uint64_t *qubits, uint64_t n_qubits); + 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; + +#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 + +#endif /* SELENE_SIMULATOR_H */ diff --git a/selene-core/cbindgen/core_types.toml b/selene-core/cbindgen/core_types.toml index f0214426..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 = [] @@ -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..50259a70 100644 --- a/selene-core/cbindgen/error_model.toml +++ b/selene-core/cbindgen/error_model.toml @@ -2,9 +2,10 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_ERROR_MODEL_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 @@ -20,8 +21,8 @@ usize_is_size_t = true include = [ "ErrorModelAPIVersion", "ErrorModelInstance", - "ErrorModelSetResultInterface", - "ErrorModelSetResultInstance", + "OperationResultInterface", + "OperationResultInstance", "ErrorModelPluginDescriptorV1", "RuntimeGetOperationInstance", "RuntimeExtractOperationInterface", @@ -30,21 +31,79 @@ 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 [export.rename] "ErrorModelAPIVersion" = "SeleneErrorModelAPIVersion" "ErrorModelInstance" = "SeleneErrorModelInstance" -"ErrorModelSetResultInterface" = "SeleneErrorModelSetResultInterface" -"ErrorModelSetResultInstance" = "SeleneErrorModelSetResultInstance" +"OperationResultInterface" = "SeleneOperationResultInterface" +"OperationResultInstance" = "SeleneOperationResultInstance" "ErrorModelPluginDescriptorV1" = "SeleneErrorModelPluginDescriptorV1" "RuntimeGetOperationInstance" = "SeleneRuntimeGetOperationInstance" "RuntimeExtractOperationInterface" = "SeleneRuntimeExtractOperationInterface" "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..098dc478 100644 --- a/selene-core/cbindgen/runtime.toml +++ b/selene-core/cbindgen/runtime.toml @@ -2,9 +2,10 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_RUNTIME_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 +27,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 +97,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..74b68e4d 100644 --- a/selene-core/cbindgen/simulator.toml +++ b/selene-core/cbindgen/simulator.toml @@ -2,9 +2,10 @@ language = "C" include_version = false includes = ["selene/core_types.h"] +include_guard = "SELENE_SIMULATOR_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 +24,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 +91,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/cbindgen.toml b/selene-core/examples/cbindgen.toml deleted file mode 100644 index bb293369..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", - "ErrorModelSetResultInterface", - "ErrorModelSetResultInstance", - "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" -"ErrorModelSetResultInterface" = "SeleneErrorModelSetResultInterface" -"ErrorModelSetResultInstance" = "SeleneErrorModelSetResultInstance" -"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.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 3a4e56b8..00000000 --- a/selene-core/examples/error_model/rust/lib.rs +++ /dev/null @@ -1,331 +0,0 @@ -use anyhow::{anyhow, Result}; -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::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")] - 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 { - 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); - 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, - })?; - 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]))?; - 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, qubit_id: u64) -> Result { - let results = self - .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" - )) - } - } -} - -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); - Ok(()) - } - fn shot_end(&mut self) -> Result<()> { - self.simulator.shot_end()?; - Ok(()) - } - - fn handle_operations(&mut self, operations: BatchOperation) -> 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; - } - } - 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. - 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, - } => { - // 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 - } 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. - 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 } => { - // 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. - if self.should_flip() { - self.flip_qubit(qubit_id)?; - } - } - Operation::Custom { .. } => { - // Passively ignore custom operations - } - } - } - Ok(results) - } - - 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)) - } - } - } -} - -#[derive(Default)] -pub struct ExampleErrorModelFactory; - -impl ErrorModelInterfaceFactory for ExampleErrorModelFactory { - type Interface = ExampleErrorModel; - - fn init( - 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, - })) - } - } - } -} - -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 40c88fc0..00000000 --- a/selene-core/examples/runtime/Cargo.lock +++ /dev/null @@ -1,210 +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 = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[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 = [ - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[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", - "delegate", - "derive_more", - "libloading", - "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 = "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 = "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 8e127dd5..00000000 --- a/selene-core/examples/runtime/rust/lib.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::collections::VecDeque; - -use anyhow::{bail, Result}; -use selene_core::{ - export_runtime_plugin, - runtime::{interface::RuntimeInterfaceFactory, BatchOperation, 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(()) - } - // Engine ops - 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() { - 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 { .. } => {} - } - } - } - 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 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}"); - } - 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 - 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_result.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 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.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 dadf8151..00000000 --- a/selene-core/examples/simulator/rust/lib.rs +++ /dev/null @@ -1,212 +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, 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 rxy(&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 ({}).", - self.n_qubits - )) - } - } - - fn rzz(&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 ({}).", - 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 rpp(&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 ({}).", - 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::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::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, 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 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/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..b83e2f6e 100644 --- a/selene-core/pyproject.toml +++ b/selene-core/pyproject.toml @@ -1,10 +1,12 @@ [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" 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", @@ -42,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/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/__init__.py b/selene-core/python/selene_core/__init__.py index 5d420d22..c92c2a9e 100644 --- a/selene-core/python/selene_core/__init__.py +++ b/selene-core/python/selene_core/__init__.py @@ -12,6 +12,34 @@ DEFAULT_BUILD_PLANNER, ) from .headers import get_include_directory +from .gatewire import ( + BOOL, + F64, + I64, + QUBIT, + U8, + U64, + BoundGate, + Gate, + GateDefinition, + GateValue, + Gateset, + HeliosGateSet, + OperandDefinition, + OperandKind, + PhasedX, + PhasedXX, + QuantinuumGateSet, + RZ, + SolGateSet, + ZZPhase, + builtin_gateset, + phased_x, + phased_xx, + rz, + semantic_id_from_text, + zz_phase, +) __all__ = [ "SeleneComponent", @@ -26,6 +54,32 @@ "BuildCtx", "DEFAULT_BUILD_PLANNER", "get_include_directory", + "BOOL", + "F64", + "I64", + "QUBIT", + "U8", + "U64", + "BoundGate", + "Gate", + "GateDefinition", + "GateValue", + "Gateset", + "HeliosGateSet", + "OperandDefinition", + "OperandKind", + "PhasedX", + "PhasedXX", + "QuantinuumGateSet", + "RZ", + "SolGateSet", + "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/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/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/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..4fee3315 --- /dev/null +++ b/selene-core/python/selene_core/c_abi/_cffi.py @@ -0,0 +1,63 @@ +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) + + +_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, ...] = (), + builtin_headers: tuple[str, ...] = ("gatewire.h",), +) -> FFI: + result = FFI() + 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/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..507f193b --- /dev/null +++ b/selene-core/python/selene_core/c_abi/simulator.py @@ -0,0 +1,19 @@ +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.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-core/python/selene_core/gatewire.py b/selene-core/python/selene_core/gatewire.py new file mode 100644 index 00000000..35257b38 --- /dev/null +++ b/selene-core/python/selene_core/gatewire.py @@ -0,0 +1,456 @@ +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 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)) + + 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)] +) + +HeliosGateSet: Gateset = Gateset(RZ, PhasedX, ZZPhase) +SolGateSet: Gateset = Gateset(RZ, PhasedX, PhasedXX) +QuantinuumGateSet: Gateset = HeliosGateSet.union(SolGateSet) + + +def builtin_gateset() -> Gateset: + return QuantinuumGateSet + + +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/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-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..9b66407b 100644 --- a/selene-core/rust/error_model.rs +++ b/selene-core/rust/error_model.rs @@ -1,20 +1,21 @@ -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; +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; -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)] @@ -53,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(), } } @@ -73,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( @@ -86,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, + self.handle.interface.last_error_fn, + || anyhow!("{}", self.context(message)), + ) + } + pub fn handle_operations_with_simulator( &mut self, operations: BatchOperation, @@ -93,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, @@ -105,7 +132,7 @@ impl ErrorModel { result, ) }, - || anyhow!("ErrorModel: handle_operations failed"), + "handle_operations failed", )?; Ok(result_builder.finish()) } @@ -125,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, @@ -133,14 +160,24 @@ 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( + &self.label(), + self.handle.instance, + self.handle.interface.negotiate_gateset_fn, + self.handle.interface.last_error_fn, + gateset, ) } @@ -153,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, @@ -164,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 03b76fd7..a44909ff 100644 --- a/selene-core/rust/error_model/helper.rs +++ b/selene-core/rust/error_model/helper.rs @@ -5,14 +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)] @@ -53,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; } @@ -96,11 +100,29 @@ 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, simulator: SimulatorHandle<'static>, - result: ErrorModelSetResultHandle, + result: OperationResultHandle, ) -> Errno { result_to_errno( "Failed to handle operations", @@ -108,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, .. @@ -169,14 +191,13 @@ macro_rules! export_error_model_plugin { ErrorModelInterfaceFactory, plugin::{ Errno, ErrorModelInstance, ErrorModelPluginDescriptorV1, - ErrorModelSetResultHandle, ErrorModelSetResultInstance, - ErrorModelSetResultInterface, }, - version::CURRENT_API_VERSION, + version::current_api_version, }, operation::plugin::{ - BatchBuilder, RuntimeExtractOperationHandle, RuntimeExtractOperationInstance, - RuntimeExtractOperationInterface, + BatchBuilder, OperationResultHandle, OperationResultInstance, + OperationResultInterface, RuntimeExtractOperationHandle, + RuntimeExtractOperationInstance, RuntimeExtractOperationInterface, }, }; @@ -191,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 @@ -265,6 +303,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. @@ -276,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) } @@ -346,13 +397,16 @@ 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, + 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), - 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..976d6db7 100644 --- a/selene-core/rust/error_model/inline.rs +++ b/selene-core/rust/error_model/inline.rs @@ -1,11 +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::{LastErrorFn, write_negotiated_gateset}, simulator::{Simulator, inline::SimulatorHandle}, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -34,8 +36,10 @@ 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, handle_operations_fn: Self::handle_operations, get_metrics_fn: Self::get_metrics, _marker: PhantomData, @@ -58,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, @@ -79,18 +91,35 @@ 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, 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 = @@ -101,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, .. @@ -141,15 +170,24 @@ 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, + 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..54e06f8f 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. @@ -60,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 e4375b83..b315ae6e 100644 --- a/selene-core/rust/error_model/plugin.rs +++ b/selene-core/rust/error_model/plugin.rs @@ -1,11 +1,14 @@ -use super::{ - BatchResult, BoolResult, ErrorModelAPIVersion, ErrorModelInterface, ErrorModelInterfaceFactory, - U64Result, -}; +use super::{BatchResult, ErrorModelAPIVersion, ErrorModelInterface, ErrorModelInterfaceFactory}; +use crate::gatewire::DynamicGateSet; use crate::operation::BatchOperation; -use crate::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; +use crate::operation::plugin::{OperationResultBuilder, OperationResultHandle}; +use crate::plugin::{ + 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, read_raw_metric, with_strings_to_cargs}; use anyhow::{Result, anyhow}; -use libloading; use std::ffi::OsStr; use std::{ffi, sync::Arc}; @@ -15,27 +18,32 @@ pub type Errno = i32; #[repr(C)] #[derive(Clone, Copy)] 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 header: PluginDescriptorHeaderV1, + 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: OperationResultHandle, + ) -> Errno, + >, pub get_metrics_fn: Option< unsafe extern "C" fn( handle: ErrorModelInstance, @@ -45,6 +53,24 @@ 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 header(&self) -> &PluginDescriptorHeaderV1 { + &self.header + } } /// Provides an error model backend that controls a plugin, in the form of a shared object. @@ -56,9 +82,11 @@ 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, + last_error_fn: LastErrorFn, + name: String, init_fn: unsafe extern "C" fn( handle: *mut ErrorModelInstance, n_qubits: u64, @@ -72,11 +100,12 @@ pub struct ErrorModelPluginInterface { error_model_seed: u64, ) -> Errno, shot_end_fn: unsafe extern "C" fn(handle: ErrorModelInstance) -> Errno, + 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( @@ -91,48 +120,41 @@ 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" - )); - } + let name = read_plugin_name("Error model", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, - init_fn: descriptor.init_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, - 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: require_callback( + "Error model", + "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, })) } @@ -141,6 +163,10 @@ impl ErrorModelPluginInterface { impl ErrorModelInterfaceFactory for ErrorModelPluginInterface { type Interface = ErrorModelPlugin; + fn name(&self) -> &str { + &self.name + } + fn init( self: Arc, n_qubits: u64, @@ -148,10 +174,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) }, + self.last_error_fn, || anyhow!("ErrorModelPluginInterface: init failed"), ) })?; @@ -172,22 +199,36 @@ 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) }, + 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) }, + 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) }, + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: shot_end failed"), ) } + + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_gateset( + "ErrorModelPlugin", + self.instance, + self.interface.negotiate_gateset_fn, + self.interface.last_error_fn, + gateset, + ) + } fn handle_operations( &mut self, operations: BatchOperation, @@ -196,15 +237,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) }, + self.interface.last_error_fn, || anyhow!("ErrorModelPlugin: handle_operations failed"), )?; Ok(result_builder.finish()) @@ -218,74 +260,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 it's 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 89ce2dc8..1e1961cd 100644 --- a/selene-core/rust/error_model/version.rs +++ b/selene-core/rust/error_model/version.rs @@ -32,17 +32,21 @@ 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 -// ErrorModelSetResultInterface +// OperationResultInterface impl ErrorModelAPIVersion { pub const fn as_u64(self) -> u64 { 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..61c9f4ee --- /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, GateView, TryDecode}; + +pub mod prelude { + pub use crate::gatewire::builtin; + pub use crate::gatewire::{ + Angle, DynamicGateSet, GateDecl, GateError, GateOperand, GateSemanticId, GateSet, + GateSetSpec, GateSpec, GateValue, GateView, 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..55f7d25c --- /dev/null +++ b/selene-core/rust/gatewire/builtin.rs @@ -0,0 +1,199 @@ +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} +} + +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/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..9129c47b --- /dev/null +++ b/selene-core/rust/gatewire/dynamic.rs @@ -0,0 +1,137 @@ +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, PartialEq, Eq)] +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 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() + } + 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..fbaf2eca --- /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::QuantinuumGateSet::dynamic())) 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..1fcab057 --- /dev/null +++ b/selene-core/rust/gatewire/tests.rs @@ -0,0 +1,101 @@ +use super::builtin::{PhasedX, PhasedXX, QuantinuumGate, RZ, ZZPhase}; +use crate::gatewire::{Angle, DynamicGateSet, GateSet, Qubit}; +use crate::runtime::Operation; + +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") + ); +} + +#[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 new file mode 100644 index 00000000..36f06127 --- /dev/null +++ b/selene-core/rust/gatewire/typed.rs @@ -0,0 +1,86 @@ +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>; +} + +pub trait GateView: Sized { + type GateSet: GateSetSpec; + + fn from_gate(gate: Self::GateSet) -> Self; +} + +#[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..b3c064e4 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 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..026fbd3c --- /dev/null +++ b/selene-core/rust/macros.rs @@ -0,0 +1,208 @@ +#[macro_export] +macro_rules! export_plugin_descriptor_v1 { + ( + $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 { + 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: $get_name_fn, + }, + $($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..509aecf5 100644 --- a/selene-core/rust/operation.rs +++ b/selene-core/rust/operation.rs @@ -3,8 +3,12 @@ pub mod plugin; use std::collections::HashSet; use std::iter; +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, }; @@ -53,39 +57,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 +80,150 @@ pub enum Operation { }, } +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +#[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 }, + 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_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); + }; + 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::Postselect { 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..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; @@ -38,84 +39,44 @@ 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( + unsafe extern "C" fn measure( interface: RuntimeGetOperationInstance, qubit_id: u64, - theta: f64, - phi: f64, + result_id: u64, ) { Self::push( interface, - Operation::RXYGate { + Operation::Measure { 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, + result_id, }, ) } - unsafe extern "C" fn measure( + unsafe extern "C" fn measure_leaked( interface: RuntimeGetOperationInstance, qubit_id: u64, result_id: u64, ) { Self::push( interface, - Operation::Measure { + Operation::MeasureLeaked { qubit_id, result_id, }, ) } - unsafe extern "C" fn measure_leaked( + unsafe extern "C" fn postselect( interface: RuntimeGetOperationInstance, qubit_id: u64, - result_id: u64, + target_value: bool, ) { Self::push( interface, - Operation::MeasureLeaked { + Operation::Postselect { qubit_id, - result_id, + target_value, }, ) } @@ -136,6 +97,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, @@ -152,13 +124,11 @@ 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, - rzz_fn: Self::rzz, - rxy_fn: Self::rxy, - rz_fn: Self::rz, - rpp_fn: Self::rpp, + gate_fn: Self::gate, }, } } @@ -179,14 +149,12 @@ 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), 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)] @@ -198,21 +166,20 @@ 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, - rzz_fn, - rxy_fn, - rz_fn, - rpp_fn, + gate_fn, .. } = output.interface; if let Some(timing) = batch.runtime_source() { @@ -234,26 +201,15 @@ impl BatchExtractor { qubit_id, 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 { + Operation::Postselect { 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) }, + 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(); + 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) } @@ -272,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 new file mode 100644 index 00000000..201eff3f --- /dev/null +++ b/selene-core/rust/plugin.rs @@ -0,0 +1,469 @@ +use anyhow::{Result, anyhow, bail}; +use libloading::Library; +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: LastErrorFn, + pub get_name_fn: PluginNameFn, +} + +pub(crate) trait PluginDescriptorV1: Copy { + const KIND: &'static str; + + 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 { + 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) 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) }, + || (), + ) + .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: LastErrorFn, + 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: LastErrorFn, + 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, + 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: NegotiateGatesetFn, + last_error_fn: LastErrorFn, + gateset: &DynamicGateSet, +) -> Result { + let input = gateset.serialize(); + let mut written = 0usize; + check_plugin_errno( + unsafe { + negotiate_gateset_fn( + instance, + input.as_ptr(), + input.len(), + std::ptr::null_mut(), + 0, + &mut written, + ) + }, + last_error_fn, + || anyhow!("{kind}: negotiate_gateset failed"), + )?; + + let mut output = vec![0u8; written]; + check_plugin_errno( + unsafe { + negotiate_gateset_fn( + instance, + input.as_ptr(), + input.len(), + output.as_mut_ptr(), + output.len(), + &mut written, + ) + }, + last_error_fn, + || 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 { + header: PluginDescriptorHeaderV1, + } + + impl PluginDescriptorV1 for TestDescriptor { + const KIND: &'static str = "Test"; + + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header + } + } + + 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: no_last_error, + get_name_fn: test_plugin_name, + }, + }; + validate_descriptor_v1(&descriptor, |version| { + assert_eq!(version, 7); + Ok(()) + }) + .unwrap(); + + let descriptor = TestDescriptor { + header: PluginDescriptorHeaderV1 { + struct_size: 0, + api_version: 7, + last_error_fn: no_last_error, + get_name_fn: test_plugin_name, + }, + }; + let error = validate_descriptor_v1(&descriptor, |_| Ok(())).unwrap_err(); + assert!( + error + .to_string() + .contains("Test plugin descriptor is too small") + ); + + let descriptor = TestDescriptor { + header: PluginDescriptorHeaderV1 { + struct_size: core::mem::size_of::() as u64, + api_version: 7, + last_error_fn: no_last_error, + get_name_fn: test_plugin_name, + }, + }; + 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::QuantinuumGateSet::dynamic(); + let negotiated = + negotiate_gateset("TestPlugin", 0usize, echo_gateset, no_last_error, &gateset).unwrap(); + assert_eq!(negotiated.serialize(), gateset.serialize()); + } + + #[test] + 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, 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, 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::QuantinuumGateSet::dynamic(); + 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..cace904c 100644 --- a/selene-core/rust/runtime.rs +++ b/selene-core/rust/runtime.rs @@ -8,17 +8,19 @@ 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}; 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}; use crate::operation::plugin::BatchBuilder; +use crate::plugin as plugin_utils; enum RuntimeBacking { Adapter { _adapter: Box }, @@ -32,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(), } } @@ -54,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( @@ -67,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, + self.handle.interface.last_error_fn, + || anyhow!("{}", self.context(message)), + ) + } } impl AsRef for Runtime { @@ -84,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() { @@ -106,22 +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( + &self.label(), + self.handle.instance, + self.handle.interface.negotiate_gateset_fn, + 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, @@ -131,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", ) } @@ -157,79 +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 rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - check_errno( + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + let data = gate.serialize(); + self.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<()> { - 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"), + "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, @@ -237,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, @@ -266,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, @@ -288,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) @@ -298,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, @@ -343,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 65a14de5..31ee1788 100644 --- a/selene-core/rust/runtime/helper.rs +++ b/selene-core/rust/runtime/helper.rs @@ -4,8 +4,12 @@ //! 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::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; +use crate::plugin::write_negotiated_gateset; +use crate::utils::{ + convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno, set_last_error, +}; use super::{ Operation, RuntimeInterface, @@ -52,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; } @@ -97,13 +101,11 @@ impl Helper { let RuntimeGetOperationInterface { measure_fn, measure_leaked_fn, + postselect_fn, 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() { @@ -125,28 +127,17 @@ 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) }, - 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 +163,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 +243,14 @@ impl Helper { ) } - pub unsafe fn rxy_gate( - instance: RuntimeInstance, - qubit_id: u64, - theta: f64, - phi: f64, - ) -> Errno { + pub unsafe fn gate(instance: RuntimeInstance, data: *const u8, data_len: usize) -> 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", + "Failed in 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 { - result_to_errno( - "Failed in rpp_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 +418,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, @@ -463,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 @@ -526,6 +516,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 +644,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( - 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( + /// Instruct the runtime to apply a gate encoded with gatewire. + unsafe extern "C" fn selene_runtime_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 +761,32 @@ 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, + 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), - 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..b160d778 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::{LastErrorFn, write_negotiated_gateset}, runtime::Operation, utils::{result_of_errno_to_errno, result_to_errno}, }; @@ -32,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, @@ -40,10 +43,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, @@ -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,13 +97,11 @@ impl RuntimeFFIAdapter { let RuntimeGetOperationInterface { measure_fn, measure_leaked_fn, + postselect_fn, 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() { @@ -114,28 +121,15 @@ impl RuntimeFFIAdapter { qubit_id, result_id, } => measure_leaked_fn(ops.instance, *qubit_id, *result_id), - Operation::Reset { qubit_id } => reset_fn(ops.instance, *qubit_id), - Operation::RXYGate { + Operation::Postselect { qubit_id, - theta, - phi, - } => { - rxy_fn(ops.instance, *qubit_id, *theta, *phi); - } - Operation::RZGate { qubit_id, theta } => { - rz_fn(ops.instance, *qubit_id, *theta) + 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(); + 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 +156,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 +224,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( @@ -399,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, @@ -409,10 +394,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 +407,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..f8fe0c9b 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 @@ -155,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 7520e8af..4ce749da 100644 --- a/selene-core/rust/runtime/plugin.rs +++ b/selene-core/rust/runtime/plugin.rs @@ -1,14 +1,19 @@ +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::utils::{MetricValue, check_errno, read_raw_metric, with_strings_to_cargs}; +use crate::plugin::{ + 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, 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}; @@ -19,21 +24,23 @@ pub type Errno = i32; #[repr(C)] #[derive(Clone, Copy)] 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 header: PluginDescriptorHeaderV1, + 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,26 @@ 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 header(&self) -> &PluginDescriptorHeaderV1 { + &self.header + } } /// Provides a runtime engine backend that controls a plugin, in the form of a shared object. @@ -106,9 +125,11 @@ 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, + last_error_fn: LastErrorFn, + name: String, init_fn: unsafe extern "C" fn( handle: *mut RuntimeInstance, n_qubits: u64, @@ -139,22 +160,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: NegotiateGatesetFn, measure_fn: unsafe extern "C" fn(handle: RuntimeInstance, qubit: u64, result_id: *mut u64) -> Errno, measure_leaked_fn: @@ -189,65 +196,93 @@ 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")); - } + let name = read_plugin_name("Runtime", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, - init_fn: descriptor.init_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, - 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: 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", + "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, })) @@ -257,6 +292,10 @@ impl RuntimePluginInterface { impl RuntimeInterfaceFactory for RuntimePluginInterface { type Interface = RuntimePlugin; + fn name(&self) -> &str { + &self.name + } + fn init( self: Arc, n_qubits: u64, @@ -265,8 +304,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) }, + self.last_error_fn, || anyhow!("RuntimePluginInterface: init failed"), ) })?; @@ -287,35 +327,50 @@ 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) }, + 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) }, + 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) }, + 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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: shot_end failed"), ) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + negotiate_gateset( + "RuntimePlugin", + self.instance, + self.interface.negotiate_gateset_fn, + self.interface.last_error_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); @@ -328,23 +383,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 _) }, + 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) }, + 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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: global barrier failed"), ) } @@ -352,7 +410,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, @@ -361,45 +419,26 @@ impl RuntimeInterface for RuntimePlugin { sleep_ns, ) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: local barrier failed"), ) } - fn rxy_gate(&mut self, qubit_id: u64, theta: f64, phi: f64) -> Result<()> { - 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"), + fn gate(&mut self, gate: &OwnedGateInstance) -> Result<()> { + let data = gate.serialize(); + check_plugin_errno( + unsafe { (self.interface.gate_fn)(self.instance, data.as_ptr(), data.len()) }, + self.interface.last_error_fn, + || anyhow!("RuntimePlugin: gate failed"), ) } 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 _) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: measure failed"), )?; Ok(result) @@ -408,25 +447,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 _) }, + 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) }, + 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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: force_result failed"), ) } @@ -434,10 +476,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 _) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: get_bool_result failed"), )?; // TODO document this @@ -451,10 +494,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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: get_u64_result failed"), )?; // TODO document this @@ -465,29 +509,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) }, + 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) }, + 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) }, + 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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: decrement_future_refcount failed"), ) } @@ -496,7 +544,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, @@ -506,6 +554,7 @@ impl RuntimeInterface for RuntimePlugin { result_ref as *mut _, ) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: custom_call failed"), )?; Ok(result) @@ -518,8 +567,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) }, + self.interface.last_error_fn, || anyhow!("RuntimePlugin: simulate_delay failed"), ) } else { 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..d6987afe 100644 --- a/selene-core/rust/simulator.rs +++ b/selene-core/rust/simulator.rs @@ -12,11 +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::runtime::{BatchOperation, Operation}; +use crate::gatewire::DynamicGateSet; +use crate::operation::plugin::{BatchExtractor, OperationResultBuilder}; +use crate::plugin as plugin_utils; +use crate::runtime::BatchOperation; enum SimulatorBacking { Adapter { _adapter: Box }, @@ -31,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(), } } @@ -52,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( @@ -69,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, + self.handle.interface.last_error_fn, + || anyhow!("{}", self.context(message)), + ) + } } impl AsRef for Simulator { @@ -92,132 +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( + &self.label(), + self.handle.instance, + self.handle.interface.negotiate_gateset_fn, + 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::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, - } => { - check_errno( - unsafe { - (self.handle.interface.rpp_fn)( - self.handle.instance, - qubit_id_1, - qubit_id_2, - theta, - phi, - ) - }, - || anyhow!("Simulator: rpp 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> { @@ -233,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, @@ -248,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/README.md b/selene-core/rust/simulator/README.md index b72fcb6e..6aa2d62c 100644 --- a/selene-core/rust/simulator/README.md +++ b/selene-core/rust/simulator/README.md @@ -1,127 +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 rzz(std::uint64_t q0, std::uint64_t q1, double theta); - void rxy(std::uint64_t q); - 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 rzz(&mut self, q0: u64, q1: u64, theta: f64); - fn rxy(&mut self, q: u64); - 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_rzz(struct MySimulator* sim, uint64_t q0, uint64_t q1, double theta); -void MySimulator_rxy(struct MySimulator* sim, uint64_t q); -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_rzz(void* sim, uint64_t q0, uint64_t q1, double theta); -void MySimulator_rxy(void* sim, uint64_t q); -bool MySimulator_measure(void* sim, uint64_t q); -void MySimulator_reset(void* sim, uint64_t q); -``` - -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_rzz(instance_1, 0, 1, 0.123); -MySimulator_rzz(instance_2, 0, 1, 0.456); -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 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. +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`. +For user-facing simulator plugin tutorials, see +[the extensibility simulator docs](../../../docs/extensibility/simulator/README.md). 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..7b5b03e0 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,8 +9,13 @@ use super::{ interface::SimulatorInterfaceFactory, plugin::{Errno, SimulatorInstance}, }; -use crate::runtime::{BatchOperation, Operation}; -use crate::utils::{convert_cargs_to_strings, result_of_errno_to_errno, result_to_errno}; +use crate::operation::plugin::{ + BatchBuilder, OperationResultHandle, RuntimeExtractOperationHandle, +}; +use crate::plugin::write_negotiated_gateset; +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 @@ -19,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 } @@ -54,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; } @@ -94,6 +95,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,154 +146,42 @@ impl Helper { }), ) } - pub unsafe fn rxy(instance: SimulatorInstance, qubit: u64, theta: f64, phi: f64) -> Errno { - result_to_errno( - "Failed to apply RXY gate", - Self::with_simulator_instance(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") - } - }), - ) - } - 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( + pub unsafe fn handle_operations( instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, ) -> Errno { result_to_errno( - "Failed to apply RPP gate", + "Failed to handle simulator operations", 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") + 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>(()) }), ) } } -#[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,8 +203,9 @@ macro_rules! export_simulator_plugin { plugin::{ Errno, SimulatorInstance, SimulatorPluginDescriptorV1, }, - version::CURRENT_API_VERSION, + version::current_api_version, }; + use selene_core::operation::plugin::{OperationResultHandle, RuntimeExtractOperationHandle}; use std::cell::LazyCell; use std::ffi::c_char; @@ -312,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 @@ -366,7 +290,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,86 +300,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( - 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, - ) -> i32 { - Helper::rz(instance, qubit, theta) - } - - /// Apply an RPP gate to the qubits at the requested indices, with the - /// provided angles. - unsafe extern "C" fn selene_simulator_operation_rpp( - instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, - phi: f64, - ) -> i32 { - Helper::rpp(instance, qubit1, qubit2, theta, phi) - } - - /// 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( + /// Negotiate the gates this simulator may receive, encoded as a gatewire gateset. + unsafe extern "C" fn selene_simulator_negotiate_gateset( instance: SimulatorInstance, - qubit: u64, - target_value: bool, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, ) -> i32 { - Helper::postselect(instance, qubit, target_value) + Helper::negotiate_gateset(instance, input, input_len, output, output_len, written) } - /// 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. @@ -499,22 +362,18 @@ 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, + 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), - 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, - postselect_fn: Some(selene_simulator_operation_postselect), - reset_fn: selene_simulator_operation_reset, + 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), 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..d2527e82 100644 --- a/selene-core/rust/simulator/inline.rs +++ b/selene-core/rust/simulator/inline.rs @@ -1,8 +1,11 @@ use super::{SimulatorInterface, plugin::SimulatorInstance}; use crate::{ + gatewire::OwnedGateInstance, + 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}; @@ -26,14 +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, - rxy_fn: BorrowedSimulatorBridge::rxy, - rz_fn: BorrowedSimulatorBridge::rz, - rzz_fn: BorrowedSimulatorBridge::rzz, - rpp_fn: BorrowedSimulatorBridge::rpp, + 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)) @@ -71,81 +80,78 @@ 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") - } - }) - }) + 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 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") - } - }) - }) + 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 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") } }) }) @@ -170,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| { @@ -240,14 +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, - rxy_fn: Self::rxy, - rz_fn: Self::rz, - rzz_fn: Self::rzz, - rpp_fn: Self::rpp, + 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, @@ -271,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}"), @@ -286,89 +290,69 @@ 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 { - 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 { + result_to_errno("SimulatorFFIAdapter: negotiate_gateset 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") - } + write_negotiated_gateset(input, input_len, output, output_len, written, |gateset| { + simulator.negotiate_gateset(gateset) + }) }) }) } - unsafe extern "C" fn rzz( + unsafe extern "C" fn handle_operations( instance: SimulatorInstance, - qubit1: u64, - qubit2: u64, - theta: f64, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, ) -> Errno { - result_to_errno("SimulatorFFIAdapter: rzz failed", unsafe { + result_to_errno("SimulatorFFIAdapter: handle_operations 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") + 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 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") } }) }) @@ -393,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| { @@ -469,32 +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 rxy_fn: unsafe extern "C" fn( + pub handle_operations_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, + 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, @@ -509,6 +465,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 ()>, } @@ -516,17 +482,16 @@ 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, - rxy_fn: self.rxy_fn, - rz_fn: self.rz_fn, - rzz_fn: self.rzz_fn, - rpp_fn: self.rpp_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, + 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..d4d061b9 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::QuantinuumGateSet::dynamic(); + 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; @@ -26,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. @@ -47,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 fe04e0e4..82b5e0b1 100644 --- a/selene-core/rust/simulator/plugin.rs +++ b/selene-core/rust/simulator/plugin.rs @@ -1,9 +1,17 @@ use super::{SimulatorAPIVersion, SimulatorInterface, SimulatorInterfaceFactory}; use crate::error_model::BatchResult; -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 crate::gatewire::DynamicGateSet; +use crate::operation::plugin::{ + BatchExtractor, OperationResultBuilder, OperationResultHandle, RuntimeExtractOperationHandle, +}; +use crate::plugin::{ + 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; +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; @@ -15,46 +23,26 @@ 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 init_fn: 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, - >, - pub rz_fn: - Option Errno>, - pub rzz_fn: Option< + pub header: PluginDescriptorHeaderV1, + pub init_fn: Option< unsafe extern "C" fn( - handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, + handle: *mut SimulatorInstance, + n_qubits: u64, + argc: u32, + argv: *const *const c_char, ) -> Errno, >, - pub rpp_fn: Option< + pub exit_fn: Option Errno>, + pub shot_start_fn: + Option Errno>, + pub shot_end_fn: Option Errno>, + pub handle_operations_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, - qubit0: u64, - qubit1: u64, - theta: f64, - phi: f64, + batch: RuntimeExtractOperationHandle, + result: OperationResultHandle, ) -> 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 reset_fn: unsafe extern "C" fn(handle: SimulatorInstance, qubit: u64) -> Errno, pub get_metrics_fn: Option< unsafe extern "C" fn( handle: SimulatorInstance, @@ -64,12 +52,32 @@ pub struct SimulatorPluginDescriptorV1 { value_out: *mut u64, ) -> Errno, >, - pub dump_state_fn: unsafe extern "C" fn( - handle: SimulatorInstance, - file: *const c_char, - qubits: *const u64, - n_qubits: u64, - ) -> Errno, + pub dump_state_fn: Option< + unsafe extern "C" fn( + handle: SimulatorInstance, + file: *const c_char, + qubits: *const u64, + n_qubits: u64, + ) -> Errno, + >, + pub negotiate_gateset_fn: Option< + unsafe extern "C" fn( + handle: SimulatorInstance, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: usize, + written: *mut usize, + ) -> Errno, + >, +} + +impl PluginDescriptorV1 for SimulatorPluginDescriptorV1 { + const KIND: &'static str = "Simulator"; + + fn header(&self) -> &PluginDescriptorHeaderV1 { + &self.header + } } /// Provides a simulation engine backend that controls a plugin, in the form of a shared object. @@ -79,13 +87,14 @@ 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, + last_error_fn: LastErrorFn, name: String, init_fn: unsafe extern "C" fn( handle: *mut SimulatorInstance, @@ -97,33 +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, - 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, - >, - 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, @@ -143,68 +131,47 @@ 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 - .and_then(|f| f().as_ref()) - .map_or_else( - || "Unknown".to_string(), - |name| { - std::ffi::CStr::from_ptr(name) - .to_string_lossy() - .into_owned() - }, - ) - }; - if descriptor.struct_size < core::mem::size_of::() as u64 { - bail!("Simulator plugin descriptor is too small for v1 ABI"); - } + let name = read_plugin_name("Simulator", descriptor.header.get_name_fn)?; Ok(Arc::new(Self { _lib: lib, + last_error_fn: descriptor.header.last_error_fn, 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, - postselect_fn: descriptor.postselect_fn, - reset_fn: descriptor.reset_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: 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: descriptor.dump_state_fn, + dump_state_fn: require_callback( + "Simulator", + "dump_state_fn", + descriptor.dump_state_fn, + )?, })) } } @@ -214,80 +181,13 @@ pub struct SimulatorPlugin { instance: SimulatorInstance, } -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 - ); - }; - 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), - ) - } - - 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, @@ -295,8 +195,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) }, + self.last_error_fn, || anyhow!("SimulatorPlugin: init failed"), ) })?; @@ -312,13 +213,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) }, + 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) }, + self.interface.last_error_fn, || { anyhow!( "SimulatorPlugin({}): shot_start failed", @@ -328,59 +232,39 @@ impl SimulatorInterface for SimulatorPlugin { ) } fn shot_end(&mut self) -> Result<()> { - check_errno( + check_plugin_errno( unsafe { (self.interface.shot_end_fn)(self.instance) }, + self.interface.last_error_fn, || anyhow!("SimulatorPlugin({}): shot_end failed", &self.interface.name), ) } - 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::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 negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let plugin = format!("SimulatorPlugin({})", &self.interface.name); + negotiate_gateset( + &plugin, + self.instance, + self.interface.negotiate_gateset_fn, + self.interface.last_error_fn, + gateset, + ) } - 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) }, + fn handle_operations(&mut self, operations: BatchOperation) -> Result { + 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) }, + 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 { @@ -400,7 +284,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, @@ -409,6 +293,7 @@ impl SimulatorInterface for SimulatorPlugin { qubit_count, ) }, + 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 1d0100a4..e73789d8 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, + minor: 2, + patch: 0, }; +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-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 } } 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..abc6394f --- /dev/null +++ b/selene-ext/compat/v02-common/src/lib.rs @@ -0,0 +1,210 @@ +use std::ffi::{OsStr, c_void}; + +use anyhow::{Result, anyhow, bail}; +use libloading::Library; +use selene_core::{ + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, + runtime::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 { + builtin::HeliosGateSet::dynamic() +} + +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 { + match Operation::gate_as_view::(gate)? { + Some(builtin::HeliosGate::RZ { qubit_id, theta }) => Ok(Self::Rz { + qubit: qubit_id, + theta, + }), + Some(builtin::HeliosGate::PhasedX { + qubit_id, + theta, + phi, + }) => Ok(Self::Rxy { + qubit: qubit_id, + theta, + phi, + }), + Some(builtin::HeliosGate::ZZPhase { + qubit_id_1, + qubit_id_2, + theta, + }) => Ok(Self::Rzz { + qubit0: qubit_id_1, + qubit1: qubit_id_2, + theta, + }), + 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 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), +} + +#[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-core/examples/error_model/Cargo.lock b/selene-ext/compat/v02-error-model/Cargo.lock similarity index 52% rename from selene-core/examples/error_model/Cargo.lock rename to selene-ext/compat/v02-error-model/Cargo.lock index 981d1151..2fd2e25b 100644 --- a/selene-core/examples/error_model/Cargo.lock +++ b/selene-ext/compat/v02-error-model/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -19,33 +19,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -54,27 +54,63 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +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.9.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +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" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.40" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -82,9 +118,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -94,9 +130,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -106,21 +142,45 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +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.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", @@ -142,23 +202,31 @@ 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 = "getrandom" -version = "0.3.3" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] +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" @@ -166,105 +234,62 @@ 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" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets", + "windows-link", ] [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" 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", -] +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" 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" @@ -279,28 +304,61 @@ 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" +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", - "rand", - "rand_pcg", + "libc", + "libloading", "selene-core", + "selene-v02-compat-common", ] [[package]] name = "semver" -version = "1.0.27" +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 = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +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" @@ -310,9 +368,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.102" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -321,18 +379,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -341,123 +399,39 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[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" +version = "1.0.24" 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" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "unicode-segmentation" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[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" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "zerocopy" -version = "0.8.25" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "zerocopy-derive" -version = "0.8.25" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "proc-macro2", - "quote", - "syn", + "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..2997d584 --- /dev/null +++ b/selene-ext/compat/v02-error-model/src/lib.rs @@ -0,0 +1,585 @@ +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, 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, +}; + +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 LegacyOperationResultInterface, +) -> 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, LegacyOperationResultInterface) { + ( + &mut self.0 as *mut BatchResult as Instance, + LegacyOperationResultInterface { + 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 name(&self) -> &str { + "v0.2 Error Model Compat" + } + + 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-core/examples/simulator/Cargo.lock b/selene-ext/compat/v02-runtime/Cargo.lock similarity index 50% rename from selene-core/examples/simulator/Cargo.lock rename to selene-ext/compat/v02-runtime/Cargo.lock index 2d171242..b77fa9d9 100644 --- a/selene-core/examples/simulator/Cargo.lock +++ b/selene-ext/compat/v02-runtime/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -19,33 +19,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -54,42 +54,63 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "approx" -version = "0.5.1" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] -name = "autocfg" -version = "1.4.0" +name = "arrayvec" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" [[package]] name = "bitflags" -version = "2.9.1" +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 = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +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" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.40" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -97,9 +118,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -109,9 +130,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -121,21 +142,45 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +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 = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] [[package]] name = "delegate" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6483c2bbed26f97861cf57651d4f2b731964a28cd2257f934a4b452480d21" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", @@ -157,138 +202,94 @@ 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 = "getrandom" -version = "0.3.3" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "heck" -version = "0.5.0" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] -name = "libc" -version = "0.2.172" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "libloading" -version = "0.8.8" +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ - "cfg-if", - "windows-targets", + "equivalent", + "hashbrown", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] -name = "once_cell_polyfill" -version = "1.70.1" +name = "libc" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "libloading" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "zerocopy", + "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.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" 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" @@ -303,29 +304,60 @@ 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" +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", - "approx", "clap", - "rand", - "rand_pcg", + "libloading", "selene-core", + "selene-v02-compat-common", ] [[package]] name = "semver" -version = "1.0.27" +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 = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +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" @@ -335,9 +367,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.102" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -346,18 +378,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -366,123 +398,39 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[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" +version = "1.0.24" 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" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "unicode-segmentation" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[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" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "zerocopy" -version = "0.8.25" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" -dependencies = [ - "zerocopy-derive", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "zerocopy-derive" -version = "0.8.25" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "proc-macro2", - "quote", - "syn", + "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..7269b1c3 --- /dev/null +++ b/selene-ext/compat/v02-runtime/src/lib.rs @@ -0,0 +1,513 @@ +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 name(&self) -> &str { + "v0.2 Runtime Compat" + } + + 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..f1439bb4 --- /dev/null +++ b/selene-ext/compat/v02-simulator/src/lib.rs @@ -0,0 +1,254 @@ +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::Postselect { + qubit_id, + target_value, + } => self.postselect(qubit_id, target_value)?, + Operation::Custom { .. } => {} + } + } + 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 name(&self) -> &str { + "v0.2 Simulator Compat" + } + + 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..6e1c4425 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, builtin}; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::utils::MetricValue; @@ -59,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, @@ -89,20 +106,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> { @@ -228,6 +237,12 @@ impl ErrorModelInterface for DepolarizingErrorModel { Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + gateset + .union(&DepolarizingCorrectionGateSet::dynamic()) + .map_err(Into::into) + } + fn exit(&mut self) -> Result<()> { Ok(()) } @@ -241,51 +256,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, @@ -333,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 } @@ -454,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, @@ -474,4 +472,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..be9da33d 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::QuantinuumGateSet::dynamic(); + 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,11 +40,9 @@ impl ErrorModelInterface for IdealErrorModel { let mut pending = Vec::new(); for op in operations { match op { - Operation::RXYGate { .. } - | Operation::RZZGate { .. } - | Operation::RZGate { .. } + Operation::Gate { .. } | Operation::Measure { .. } - | Operation::RPPGate { .. } + | Operation::Postselect { .. } | Operation::Reset { .. } => pending.push(op), Operation::MeasureLeaked { qubit_id, @@ -84,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 89301aa4..025f4c08 100644 --- a/selene-ext/error-models/simple-leakage/rust/lib.rs +++ b/selene-ext/error-models/simple-leakage/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, builtin}; use selene_core::runtime::{BatchOperation, 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::QuantinuumGateSet::dynamic(); + 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_gate_view::()? { + Some(builtin::QuantinuumGate::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(builtin::QuantinuumGate::RZ { qubit_id, theta }) => { + self.maybe_leak(qubit_id)?; + pending.push(Operation::rz(qubit_id, theta)?); + } + Some(builtin::QuantinuumGate::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(builtin::QuantinuumGate::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, @@ -212,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 } @@ -245,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/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/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/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/helios_ops.c b/selene-ext/interfaces/helios_qis/c/src/helios_ops.c index 5312ccb9..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,11 +1,77 @@ #include +#include #include // selene_ functions #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 +86,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..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 @@ -25,9 +26,63 @@ #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)); -int selene_helios_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { + 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_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]); @@ -42,6 +97,17 @@ 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; + } + 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); @@ -71,3 +137,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/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/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/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 3280d169..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 @@ -25,9 +26,63 @@ #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)); -int selene_sol_run(int argc, char** argv, uint64_t (*entrypoint)(uint64_t)) { + 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_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]); @@ -42,6 +97,17 @@ 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; + } + 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); @@ -71,3 +137,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/interfaces/sol_qis/c/src/sol_ops.c b/selene-ext/interfaces/sol_qis/c/src/sol_ops.c index c6cbe622..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,11 +1,77 @@ #include +#include #include // selene_ functions #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 +86,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..54708a6c 100644 --- a/selene-ext/runtimes/simple/rust/lib.rs +++ b/selene-ext/runtimes/simple/rust/lib.rs @@ -4,6 +4,7 @@ use anyhow::{Result, bail}; use clap::Parser; use selene_core::{ export_runtime_plugin, + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, runtime::{BatchOperation, Operation, RuntimeInterface, interface::RuntimeInterfaceFactory}, utils::MetricValue, }; @@ -11,13 +12,13 @@ use selene_core::{ #[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 +62,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_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, + Operation::MeasureLeaked { .. } => self.params.duration_ns_measure_leaked, + _ => 0, + }, }; self.operation_queue.push_back(BatchOperation::runtime( vec![op], @@ -87,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()) } @@ -101,6 +103,13 @@ impl RuntimeInterface for SimpleRuntime { self.future_results.clear(); Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let supported = builtin::QuantinuumGateSet::dynamic(); + 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 +138,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::gate_as_view::(gate)? { + Some(builtin::QuantinuumGate::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(builtin::QuantinuumGate::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(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}"); + } + 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(builtin::QuantinuumGate::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 { @@ -295,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/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..a669374c 100644 --- a/selene-ext/runtimes/soft_rz/rust/lib.rs +++ b/selene-ext/runtimes/soft_rz/rust/lib.rs @@ -4,16 +4,33 @@ use anyhow::{Result, bail}; use clap::Parser; use selene_core::{ export_runtime_plugin, + gatewire::{DynamicGateSet, OwnedGateInstance, builtin}, 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)] - 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 +102,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_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, + 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 +144,21 @@ 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_gate::(), + op.as_gate::() + ), + ( + Ok(Some(SoftRZEmittedGateSet::PhasedX(_))), + Ok(Some(SoftRZEmittedGateSet::PhasedX(_))) + ) | ( + Ok(Some(SoftRZEmittedGateSet::ZZPhase(_))), + Ok(Some(SoftRZEmittedGateSet::ZZPhase(_))) + ) + ) + } (Operation::Measure { .. }, Operation::Measure { .. }) => true, (Operation::MeasureLeaked { .. }, Operation::MeasureLeaked { .. }) => true, (Operation::Reset { .. }, Operation::Reset { .. }) => true, @@ -156,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(), @@ -179,6 +209,13 @@ impl RuntimeInterface for SoftRZRuntime { self.future_results.clear(); Ok(()) } + fn negotiate_gateset(&mut self, gateset: &DynamicGateSet) -> Result { + let accepted = builtin::HeliosGateSet::dynamic(); + if let Some(decl) = gateset.first_unsupported_by(&accepted) { + bail!("SoftRZRuntime does not support gate {}", decl.name); + } + Ok(SoftRZEmittedGateSet::dynamic()) + } fn global_barrier(&mut self, _sleep_ns: u64) -> Result<()> { self.flush_size = self.operation_queue.len(); Ok(()) @@ -219,59 +256,50 @@ 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::gate_as_view::(gate)? { + Some(builtin::HeliosGate::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(builtin::HeliosGate::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(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}"); + } + 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(()) + } + 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 { @@ -391,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 c6c564f1..ec5f420e 100644 --- a/selene-ext/simulators/classical-replay/rust/lib.rs +++ b/selene-ext/simulators/classical-replay/rust/lib.rs @@ -2,6 +2,7 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; +use selene_core::gatewire::builtin; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; @@ -35,23 +36,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 +69,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 +145,30 @@ 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_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, @@ -170,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 { .. } => {} _ => {} } @@ -194,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/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..218af125 100644 --- a/selene-ext/simulators/coinflip/rust/lib.rs +++ b/selene-ext/simulators/coinflip/rust/lib.rs @@ -4,6 +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::gatewire::builtin; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; @@ -33,23 +34,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 +67,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 +127,30 @@ 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_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, @@ -152,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 { .. } => {} _ => {} } @@ -193,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 f5a3093f..28ddf7f6 100644 --- a/selene-ext/simulators/quantum-replay/rust/lib.rs +++ b/selene-ext/simulators/quantum-replay/rust/lib.rs @@ -2,6 +2,7 @@ use anyhow::{Result, anyhow}; use clap::Parser; use selene_core::error_model::BatchResult; use selene_core::export_simulator_plugin; +use selene_core::gatewire::builtin; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::interface::SimulatorInterfaceFactory; use selene_core::simulator::{Simulator, SimulatorInterface}; @@ -82,31 +83,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 +107,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 +116,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 +197,30 @@ 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_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, @@ -238,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 { .. } => {} _ => {} } @@ -269,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/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/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/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 0cd20ae3..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::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,23 +50,30 @@ 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_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, @@ -126,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 { .. } => {} _ => {} } @@ -152,19 +113,18 @@ 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) } } - 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: // @@ -190,26 +150,25 @@ 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) } } - 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: @@ -235,28 +194,21 @@ 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 rpp(&mut self, q0: u64, q1: u64, theta: f64, phi: f64) -> Result<()> { - if q0 >= 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 { Err(anyhow!( - "RPP(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 { - // 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: // @@ -297,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) } } @@ -323,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) } } @@ -334,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(()) } } @@ -365,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(()) } @@ -390,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())?; } @@ -441,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, @@ -455,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)) +} 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/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/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 51617120..a10f5faa 100644 --- a/selene-ext/simulators/stim/rust/lib.rs +++ b/selene-ext/simulators/stim/rust/lib.rs @@ -9,6 +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::gatewire::builtin; use selene_core::runtime::{BatchOperation, Operation}; use selene_core::simulator::SimulatorInterface; use selene_core::simulator::interface::SimulatorInterfaceFactory; @@ -95,23 +96,30 @@ 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_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, @@ -121,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 { .. } => {} _ => {} } @@ -141,8 +153,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 +188,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 +196,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 +207,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 +239,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 +292,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 +335,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 +343,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 +352,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 +368,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 +401,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 +411,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 +445,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 +495,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." )); } } @@ -512,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}")) + } + } } } } @@ -560,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..d3a3187e 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): handle_operations 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-ext/utilities/argreader/Cargo.lock b/selene-ext/utilities/argreader/Cargo.lock index a024be52..c0247c9a 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" @@ -135,12 +215,17 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0-alpha.0" 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-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..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,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..72bd4025 100644 --- a/selene-ext/utilities/argreader/python/tests/test_argreader.py +++ b/selene-ext/utilities/argreader/python/tests/test_argreader.py @@ -236,3 +236,31 @@ 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..ee47fa82 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 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 { 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/c/include/selene/selene.h b/selene-sim/c/include/selene/selene.h index c6b8b3d4..2802637c 100644 --- a/selene-sim/c/include/selene/selene.h +++ b/selene-sim/c/include/selene/selene.h @@ -45,6 +45,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 @@ -88,6 +96,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 +265,15 @@ 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_register_utility_event_callbacks(struct SeleneInstance *instance, + struct SeleneUtilityEventCallbacksV1 callbacks); struct selene_void_result_t selene_set_tc(struct SeleneInstance *instance, uint64_t tc); diff --git a/selene-sim/python/selene_sim/README.md b/selene-sim/python/selene_sim/README.md index dcc8c1dd..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 @@ -24,7 +25,10 @@ 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 + +For plugin, utility, and custom-gateset documentation, see the +[Selene extensibility docs](https://github.com/quantinuum/selene/tree/main/docs/extensibility). ## Usage example @@ -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, @@ -111,7 +115,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/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/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/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/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/_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 e54c2ed6..f4c74c3e 100644 --- a/selene-sim/python/selene_sim/interactive/full_stack.py +++ b/selene-sim/python/selene_sim/interactive/full_stack.py @@ -1,15 +1,20 @@ from __future__ import annotations -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_core.c_abi import SeleneCTypes, ffi from selene_sim import dist_dir as selene_dist from selene_sim.backends import IdealErrorModel, SimpleRuntime @@ -18,11 +23,18 @@ 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 DEFAULT_SHOT_SPEC = ShotSpec(count=1000, offset=0, increment=1) +_FFI = ffi( + (selene_dist / "include/selene/selene.h",), + builtin_headers=("gatewire.h",), +) + def _component_config(component: SeleneComponent, default_seed: int | None) -> dict: full_name = ".".join( @@ -55,90 +67,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) @@ -149,127 +77,112 @@ 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) -class SeleneSimLib(ctypes.CDLL): +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 + + +def _unwrap_future(result) -> int: + return int(_unwrap(result).reference) + + +class SeleneSimLib: 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)) - 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_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] - 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.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): @@ -284,13 +197,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) @@ -321,9 +232,9 @@ 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() self._event_hook = event_hook or NoEventHook() self._shot_spec = DEFAULT_SHOT_SPEC self._shot_index = self._shot_spec.offset @@ -340,6 +251,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( @@ -364,13 +277,9 @@ def __init__( self._configuration = config_data 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 + 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: self._teardown_environment() raise @@ -455,33 +364,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) @@ -493,8 +402,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) @@ -536,7 +444,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) @@ -544,30 +452,29 @@ 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 register_gateset(self, gateset: Gateset) -> Gateset: + payload = gateset.serialize() + return Gateset.deserialize(self._lib.register_gateset(self._instance, payload)) - 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 gate(self, gate: Gate) -> None: + payload = gate.serialize() + 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.write_metadata(self._instance) + self._poll_results() def get_state(self, qubits: list[Qubit]): if not hasattr(self.simulator, "extract_states"): 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 cd401a2b..d4ffb4ae 100644 --- a/selene-sim/python/selene_sim/interactive/runtime.py +++ b/selene-sim/python/selene_sim/interactive/runtime.py @@ -1,82 +1,16 @@ from __future__ import annotations from dataclasses import dataclass -import ctypes +import struct -from selene_core import Runtime +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 - -RZZ_CB = ctypes.CFUNCTYPE( - None, - SeleneRuntimeGetOperationInstance, - ctypes.c_uint64, - ctypes.c_uint64, - ctypes.c_double, -) -RXY_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, -) - -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), - ("rzz_fn", RZZ_CB), - ("rxy_fn", RXY_CB), - ("rz_fn", RZ_CB), - ("rpp_fn", RPP_CB), - ] - - -class SeleneRuntimeGetOperationHandle(ctypes.Structure): - _fields_ = [ - ("instance", SeleneRuntimeGetOperationInstance), - ("interface", SeleneRuntimeGetOperationInterface), - ] +_FFI = ffi(builtin_headers=("gatewire.h", "runtime.h")) @dataclass @@ -91,31 +25,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 @@ -130,15 +41,19 @@ class MeasureLeakedOperation: result_id: int +@dataclass +class PostselectOperation: + qubit_id: int + target_value: bool + + RuntimeOperation = ( MeasureOperation | ResetOperation - | RXYGateOperation - | RZGateOperation - | RZZGateOperation - | RPPGateOperation + | GateOperation | CustomOperation | MeasureLeakedOperation + | PostselectOperation ) @@ -154,16 +69,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): @@ -174,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 @@ -186,234 +97,250 @@ 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) -def callback_rxy( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, theta: float, phi: float +@_FFI.callback("void(SeleneRuntimeGetOperationInstance, const uint8_t *, size_t)") +def callback_gate( + instance, + data_ptr, + data_len: int, ): - OperationBatch.from_ptr(instance).rxy(qubit_id, theta, phi) - + OperationBatch.from_handle(instance).gate( + Gate.deserialize(bytes(_FFI.buffer(data_ptr, data_len))) + ) -def callback_rz( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, theta: float -): - OperationBatch.from_ptr(instance).rz(qubit_id, theta) +@_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_rzz( - instance: SeleneRuntimeGetOperationInstance, - qubit_id_a: int, - qubit_id_b: int, - theta: float, -): - OperationBatch.from_ptr(instance).rzz(qubit_id_a, qubit_id_b, theta) +@_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_measure( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, result_id: int -): - OperationBatch.from_ptr(instance).measure(qubit_id, result_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) -def callback_measure_leaked( - instance: SeleneRuntimeGetOperationInstance, qubit_id: int, result_id: int -): - OperationBatch.from_ptr(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)") +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) + data = bytes(_FFI.buffer(data_ptr, data_len)) + OperationBatch.from_handle(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) - - -OPERATION_BATCH_CALLBACKS = SeleneRuntimeGetOperationInterface( - rzz_fn=RZZ_CB(callback_rzz), - rxy_fn=RXY_CB(callback_rxy), - rz_fn=RZ_CB(callback_rz), - 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) -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) -) -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), -) +@_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), - ("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), - ("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), - ] - - -GetRuntimeDescriptorFn = ctypes.CFUNCTYPE(ctypes.POINTER(RuntimePluginDescriptorV1)) - - -class SeleneSimRuntimeLib(ctypes.CDLL): +class SeleneSimRuntimeLib: def __init__(self, runtime: Runtime) -> None: - super().__init__(str(runtime.library_file)) - self._configure_signatures() + load_selene_global() + self.ffi = ffi(builtin_headers=("gatewire.h", "runtime.h")) + self.types = RuntimeCTypes(self.ffi) + self.lib = self.ffi.dlopen(str(runtime.library_file)) + self.descriptor = self._descriptor() + + 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.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.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 _configure_signatures(self): - descriptor: RuntimePluginDescriptorV1 | None = None + 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 _descriptor(self): 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: + 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 or accessor" - ) + "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 + 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] - 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_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 - 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 + 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 ) - self.selene_runtime_decrement_future_refcount = ( - descriptor.decrement_future_refcount_fn + 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 self.selene_runtime_custom_call is None: - raise RuntimeError("Runtime plugin does not expose custom_call_fn") + 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: @@ -423,50 +350,43 @@ 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() - # 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") - if 0 != self._lib.selene_runtime_shot_start( - self._instance, self.shot_id, self.runtime.random_seed - ): + 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) + 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: + 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") 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 @@ -474,85 +394,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 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): + 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]: @@ -564,31 +465,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: - super().__init__(str(simulator.library_file)) - self._configure_signatures() + load_selene_global() + self.ffi = ffi(builtin_headers=("gatewire.h", "simulator.h")) + self.types = SimulatorCTypes(self.ffi) + self.lib = self.ffi.dlopen(str(simulator.library_file)) + self.descriptor = self._descriptor() + + 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" + ) - def _configure_signatures(self): - descriptor: SimulatorPluginDescriptorV1 | None = None + 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 + + def _descriptor(self): 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: + 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 or accessor" - ) + "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 = [ + 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] - 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_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 - - 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") + 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(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(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) + + 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: @@ -152,83 +227,73 @@ 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() - # 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") - if 0 != self._lib.selene_simulator_shot_start( - self._instance, self.shot_id, self.simulator.random_seed - ): + self._instance = self._lib.init(n_qubits, arguments) + if self.gateset is not None: + self.emitted_gateset = self.register_gateset(self.gateset) + 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() + return Gateset.deserialize(self._lib.negotiate_gateset(self._instance, payload)) + + def gate(self, gate: Gate): + payload = gate.serialize() + self._apply_void_operation( + self._lib.gate(self._instance, payload), + "GATE", + ) + 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") - 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) def reset(self, qubit: int): - self._apply_void_operation( - self._lib.selene_simulator_operation_reset, "RESET", qubit - ) + self._apply_void_operation(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]: @@ -240,47 +305,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/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_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 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/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): 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_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/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..260c6a21 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 { @@ -65,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(()) }) } @@ -207,27 +217,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 +311,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..601c49ca 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,42 +22,33 @@ 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) } 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()) } + selene_core::runtime::Operation::Gate { gate } => Self::from_gate(gate), _ => todo!( "Unsupported runtime operation in instruction log: {:?}", operation @@ -71,34 +59,20 @@ 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) } 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()) } + selene_core::runtime::Operation::Gate { gate } => Self::from_gate(gate), _ => todo!( "Unsupported simulator operation in instruction log: {:?}", operation @@ -109,6 +83,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 +115,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 +185,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..b58317fd 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::QuantinuumGateSet::dynamic()) + } +} + +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..d6b631fa 100644 --- a/selene-sim/rust/ffi_interface.rs +++ b/selene-sim/rust/ffi_interface.rs @@ -1,6 +1,8 @@ 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 { @@ -14,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, @@ -201,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, @@ -512,46 +536,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.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(()) 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..0f1c88e7 100644 --- a/uv.lock +++ b/uv.lock @@ -65,6 +65,177 @@ 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 = "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" @@ -737,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" @@ -1229,9 +1409,11 @@ wheels = [ [[package]] name = "selene-core" -version = "0.3.0-alpha.1" +version = "1.0.0a0" 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'" }, @@ -1252,6 +1434,8 @@ 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" }, @@ -1269,7 +1453,7 @@ dev = [{ name = "qir-qis", specifier = "~=0.1.6" }] [[package]] name = "selene-sim" -version = "0.3.0-alpha.1" +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'" },