mirror of https://github.com/Qiskit/qiskit.git
Add infrastructure for gates, instruction, and operations in Rust (#12459)
* Add infrastructure for gates, instruction, and operations in Rust This commit adds a native representation of Gates, Instruction, and Operations to rust's circuit module. At a high level this works by either wrapping the Python object in a rust wrapper struct that tracks metadata about the operations (name, num_qubits, etc) and then for other details it calls back to Python to get dynamic details like the definition, matrix, etc. For standard library gates like Swap, CX, H, etc this replaces the on-circuit representation with a new rust enum StandardGate. The enum representation is much more efficient and has a minimal memory footprint (just the enum variant and then any parameters or other mutable state stored in the circuit instruction). All the gate properties such as the matrix, definiton, name, etc are statically defined in rust code based on the enum variant (which represents the gate). The use of an enum to represent standard gates does mean a change in what we store on a CircuitInstruction. To represent a standard gate fully we need to store the mutable properties of the existing Gate class on the circuit instruction as the gate by itself doesn't contain this detail. That means, the parameters, label, unit, duration, and condition are added to the rust side of circuit instrucion. However no Python side access methods are added for these as they're internal only to the Rust code. In Qiskit 2.0 to simplify this storage we'll be able to drop, unit, duration, and condition from the api leaving only label and parameters. But for right now we're tracking all of the fields. To facilitate working with circuits and gates full from rust the setting the `operation` attribute of a `CircuitInstruction` object now transltates the python object to an internal rust representation. For standard gates this translates it to the enum form described earlier, and for other circuit operations 3 new Rust structs: PyGate, PyInstruction, and PyOperation are used to wrap the underlying Python object in a Rust api. These structs cache some commonly accessed static properties of the operation, such as the name, number of qubits, etc. However for dynamic pieces, such as the definition or matrix, callback to python to get a rust representation for those. Similarly whenever the `operation` attribute is accessed from Python it converts it back to the normal Python object representation. For standard gates this involves creating a new instance of a Python object based on it's internal rust representation. For the wrapper structs a reference to the wrapped PyObject is returned. To manage the 4 variants of operation (`StandardGate`, `PyGate`, `PyInstruction`, and `PyOperation`) a new Rust trait `Operation` is created that defines a standard interface for getting the properties of a given circuit operation. This common interface is implemented for the 4 variants as well as the `OperationType` enum which wraps all 4 (and is used as the type for `CircuitInstruction.operation` in the rust code. As everything in the `QuantumCircuit` data model is quite coupled moving the source of truth for the operations to exist in Rust means that more of the underlying `QuantumCircuit`'s responsibility has to move to Rust as well. Primarily this involves the `ParameterTable` which was an internal class for tracking which instructions in the circuit have a `ParameterExpression` parameter so that when we go to bind parameters we can lookup which operations need to be updated with the bind value. Since the representation of those instructions now lives in Rust and Python only recieves a ephemeral copy of the instructions the ParameterTable had to be reimplemented in Rust to track the instructions. This new parameter table maps the Parameter's uuid (as a u128) as a unique identifier for each parameter and maps this to a positional index in the circuit data to the underlying instruction using that parameter. This is a bit different from the Python parameter table which was mapping a parameter object to the id of the operation object using that parmaeter. This also leads to a difference in the binding mechanics as the parameter assignment was done by reference in the old model, but now we need to update the entire instruction more explicitly in rust. Additionally, because the global phase of a circuit can be parameterized the ownership of global phase is moved from Python into Rust in this commit as well. After this commit the only properties of a circuit that are not defined in Rust for the source of truth are the bits (and vars) of the circuit, and when creating circuits from rust this is what causes a Python interaction to still be required. This commit does not translate the full standard library of gates as that would make the pull request huge, instead this adds the basic infrastructure for having a more efficient standard gate representation on circuits. There will be follow up pull requests to add the missing gates and round out support in rust. The goal of this pull request is primarily to add the infrastructure for representing the full circuit model (and dag model in the future) in rust. By itself this is not expected to improve runtime performance (if anything it will probably hurt performance because of extra type conversions) but it is intended to enable writing native circuit manipulations in Rust, including transpiler passes without needing involvement from Python. Longer term this should greatly improve the runtime performance and reduce the memory overhead of Qiskit. But, this is just an early step towards that goal, and is more about unlocking the future capability. The next steps after this commit are to finish migrating the standard gate library and also update the `QuantumCircuit` methods to better leverage the more complete rust representation (which should help offset the performance penalty introduced by this). Fixes: #12205 * Fix Python->Rust Param conversion This commit adds a custom implementation of the FromPyObject trait for the Param enum. Previously, the Param trait derived it's impl of the trait, but this logic wasn't perfect. In cases whern a ParameterExpression was effectively a constant (such as `0 * x`) the trait's attempt to coerce to a float first would result in those ParameterExpressions being dropped from the circuit at insertion time. This was a change in behavior from before having gates in Rust as the parameters would disappear from the circuit at insertion time instead of at bind time. This commit fixes this by having a custom impl for FromPyObject that first tries to figure out if the parameter is a ParameterExpression (or a QuantumCircuit) by using a Python isinstance() check, then tries to extract it as a float, and finally stores a non-parameter object; which is a new variant in the Param enum. This new variant also lets us simplify the logic around adding gates to the parameter table as we're able to know ahead of time which gate parameters are `ParameterExpression`s and which are other objects (and don't need to be tracked in the parameter table. Additionally this commit tweaks two tests, the first is test.python.circuit.library.test_nlocal.TestNLocal.test_parameters_setter which was adjusted in the previous commit to workaround the bug fixed by this commit. The second is test.python.circuit.test_parameters which was testing that a bound ParameterExpression with a value of 0 defaults to an int which was a side effect of passing an int input to symengine for the bind value and not part of the api and didn't need to be checked. This assertion was removed from the test because the rust representation is only storing f64 values for the numeric parameters and it is never an int after binding from the Python perspective it isn't any different to have float(0) and int(0) unless you explicit isinstance check like the test previously was. * Fix qasm3 exporter for std gates without stdgates.inc This commit fixes the handling of standard gates in Qiskit when the user specifies excluding the use of the stdgates.inc file from the exported qasm. Previously the object id of the standard gates were used to maintain a lookup table of the global definitions for all the standard gates explicitly in the file. However, the rust refactor means that every time the exporter accesses `circuit.data[x].operation` a new instance is returned. This means that on subsequent lookups for the definition the gate definitions are never found. To correct this issue this commit adds to the lookup table a fallback of the gate name + parameters to do the lookup for. This should be unique for any standard gate and not interfere with the previous logic that's still in place and functional for other custom gate definitions. While this fixes the logic in the exporter the test is still failing because the test is asserting the object ids are the same in the qasm3 file, which isn't the case anymore. The test will be updated in a subsequent commit to validate the qasm3 file is correct without using a hardcoded object id. * Fix base scheduler analysis pass duration setting When ALAPScheduleAnalysis and ASAPScheduleAnalysis were setting the duration of a gate they were doing `node.op.duration = duration` this wasn't always working because if `node.op` was a standard gate it returned a new Python object created from the underlying rust representation. This commit fixes the passes so that they modify the duration and then explicit set the operation to update it's rust representation. * Fix python lint * Fix last failing qasm3 test for std gates without stdgates.inc While the logic for the qasm3 exporter was fixed in commita6e69ba4c9
to handle the edge case of a user specifying that the qasm exporter does not use the stdgates.inc include file in the output, but also has qiskit's standard gates in their circuit being exported. The one unit test to provide coverage for that scenario was not passing because when an id was used for the gate definitions in the qasm3 file it was being referenced against a temporary created by accessing a standard gate from the circuit and the ids weren't the same so the reference string didn't match what the exporter generated. This commit fixes this by changing the test to not do an exact string comparison, but instead a line by line comparison that either does exact equality check or a regex search for the expected line and the ids are checked as being any 15 character integer. * Remove superfluous comment * Cache imported classes with GILOnceCell * Remove unused python variables * Add missing file * Update QuantumCircuit gate methods to bypass Python object This commit updates the QuantumCircuit gate methods which add a given gate to the circuit to bypass the python gate object creation and directly insert a rust representation of the gate. This avoids a conversion in the rust side of the code. While in practice this is just the Python side object creation and a getattr for the rust code to determine it's a standard gate that we're skipping. This may add up over time if there are a lot of gates being created by the method. To accomplish this the rust code handling the mapping of rust StandardGate variants to the Python classes that represent those gates needed to be updated as well. By bypassing the python object creation we need a fallback to populate the gate class for when a user access the operation object from Python. Previously this mapping was only being populated at insertion time and if we never insert the python object (for a circuit created only via the methods) then we need a way to find what the gate class is. A static lookup table of import paths and class names are added to `qiskit_circuit::imports` module to faciliate this and helper functions are added to facilitate interacting with the class objects that represent each gate. * Deduplicate gate matrix definitions * Fix lint * Attempt to fix qasm3 test failure * Add compile time option to cache py gate returns for rust std gates This commit adds a new rust crate feature flag for the qiskit-circuits and qiskit-pyext that enables caching the output from CircuitInstruction.operation to python space. Previously, for memory efficiency we were reconstructing the python object on demand for every access. This was to avoid carrying around an extra pointer and keeping the ephemeral python object around longer term if it's only needed once. But right now nothing is directly using the rust representation yet and everything is accessing via the python interface, so recreating gate objects on the fly has a huge performance penalty. To avoid that this adds caching by default as a temporary solution to avoid this until we have more usage of the rust representation of gates. There is an inherent tension between an optimal rust representation and something that is performant for Python access and there isn't a clear cut answer on which one is better to optimize for. A build time feature lets the user pick, if what we settle on for the default doesn't agree with their priorities or use case. Personally I'd like to see us disable the caching longer term (hopefully before releasing this functionality), but that's dependent on a sufficent level of usage from rust superseding the current Python space usage in the core of Qiskit. * Add num_nonlocal_gates implementation in rust This commit adds a native rust implementation to rust for the num_nonlocal_gates method on QuantumCircuit. Now that we have a rust representation of gates it is potentially faster to do the count because the iteration and filtering is done rust side. * Performance tuning circuit construction This commit fixes some performance issues with the addition of standard gates to a circuit. To workaround potential reference cycles in Python when calling rust we need to check the parameters of the operation. This was causing our fast path for standard gates to access the `operation` attribute to get the parameters. This causes the gate to be eagerly constructed on the getter. However, the reference cycle case can only happen in situations without a standard gate, and the fast path for adding standard gates directly won't need to run this so a skip is added if we're adding a standard gate. * Add back validation of parameters on gate methods In the previous commit a side effect of the accidental eager operation creation was that the parameter input for gates were being validated by that. By fixing that in the previous commit the validation of input parameters on the circuit methods was broken. This commit fixes that oversight and adds back the validation. * Skip validation on gate creation from rust * Offload operation copying to rust This commit fixes a performance regression in the `QuantumCircuit.copy()` method which was previously using Python to copy the operations which had extra overhead to go from rust to python and vice versa. This moves that logic to exist in rust and improve the copy performance. * Fix lint * Perform deepcopy in rust This commit moves the deepcopy handling to occur solely in Rust. Previously each instruction would be directly deepcopied by iterating over the circuit data. However, we can do this rust side now and doing this is more efficient because while we need to rely on Python to run a deepcopy we can skip it for the Rust standard gates and rely on Rust to copy those gates. * Fix QuantumCircuit.compose() performance regression This commit fixes a performance regression in the compose() method. This was caused by the checking for classical conditions in the method requiring eagerly converting all standard gates to a Python object. This changes the logic to do this only if we know we have a condition (which we can determine Python side now). * Fix map_ops test case with no caching case * Fix typos in docs This commit fixes several docs typos that were caught during code review. Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com> * Shrink memory usage for extra mutable instruction state This commit changes how we store the extra mutable instruction state (condition, duration, unit, and label) for each `CircuitInstruction` and `PackedInstruction` in the circuit. Previously it was all stored as separate `Option<T>` fields on the struct, which required at least a pointer's width for each field which was wasted space the majority of the time as using these fields are not common. To optimize the memory layout of the struct this moves these attributes to a new struct which is put in an `Option<Box<_>>` which reduces it from 4 pointer widths down to 1 per object. This comes from extra runtime cost from the extra layer of pointer indirection but as this is the uncommon path this tradeoff is fine. * Remove Option<> from params field in CircuitInstruction This commit removes the Option<> from the params field in CircuitInstruction. There is no real distinction between an empty vec and None in this case, so the option just added another layer in the API that we didn't need to deal with. Also depending on the memory alignment using an Option<T> might have ended up in a little extra memory usage too, so removing it removes that potential source of overhead. * Eagerly construct rust python wrappers in .append() This commit updates the Python code in QuantumCircuit.append() method to eagerly construct the rust wrapper objects for python defined circuit operations. * Simplify code around handling python errors in rust * Revert "Skip validation on gate creation from rust" This reverts commit2f81bde8bf
. The validation skipping was unsound in some cases and could lead to invalid circuit being generated. If we end up needing this as an optimization we can remove this in the future in a follow-up PR that explores this in isolation. * Temporarily use git for qasm3 import In Qiskit/qiskit-qasm3-import#34 the issue we're hitting caused by qiskit-qasm3-import using the private circuit attributes removed in this PR was fixed. This commit temporarily moves to installing it from git so we can fully run CI. When qiskit-qasm3-import is released we should revert this commit. * Fix lint * Fix lint for real (we really need to use a py312 compatible version of pylint) * Fix test failure caused by incorrect lint fix * Relax trait-method typing requirements * Encapsulate `GILOnceCell` initialisers to local logic * Simplify Interface for building circuit of standard gates in rust * Simplify complex64 creation in gate_matrix.rs This just switches Complex64::new(re, im) to be c64(re, im) to reduce the amount of typing. c64 needs to be defined inplace so it can be a const fn. * Simplify initialization of array of elements that are not Copy (#28) * Simplify initialization of array of elements that are not Copy * Only generate array when necessary * Fix doc typos Co-authored-by: Kevin Hartman <kevin@hart.mn> * Add conversion trait for OperationType -> OperationInput and simplify CircuitInstruction::replace() * Use destructuring for operation_type_to_py extra attr handling * Simplify trait bounds for map_indices() The map_indices() method previously specified both Iterator and ExactSizeIterator for it's trait bounds, but Iterator is a supertrait of ExactSizeIterator and we don't need to explicitly list both. This commit removes the duplicate trait bound. * Make Qubit and Clbit newtype member public As we start to use Qubit and Clbit for creating circuits from accelerate and other crates in the Qiskit workspace we need to be able to create instances of them. However, the newtype member BitType was not public which prevented creating new Qubits. This commit fixes this by making it public. * Use snakecase for gate matrix names * Remove pointless underscore prefix * Use downcast instead of bound * Rwork _append reference cycle handling This commit reworks the multiple borrow handling in the _append() method to leveraging `Bound.try_borrow()` to return a consistent error message if we're unable to borrow a CircuitInstruction in the rust code meaning there is a cyclical reference in the code. Previously we tried to detect this cycle up-front which added significant overhead for a corner case. * Make CircuitData.global_phase_param_index a class attr * Use &[Param] instead of &SmallVec<..> for operation_type_and_data_to_py * Have get_params_unsorted return a set * Use lookup table for static property methods of StandardGate * Use PyTuple::empty_bound() * Fix lint * Add missing test method docstring * Reuse allocations in parameter table update * Remove unnecessary global phase zeroing * Move manually set params to a separate function * Fix release note typo * Use constant for global-phase index * Switch requirement to release version --------- Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Co-authored-by: Jake Lishman <jake.lishman@ibm.com> Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com> Co-authored-by: Kevin Hartman <kevin@hart.mn>
This commit is contained in:
parent
439de04e88
commit
f304a4b4f8
|
@ -36,6 +36,15 @@ jobs:
|
|||
python -m pip install -U -r requirements.txt -c constraints.txt
|
||||
python -m pip install -U -r requirements-dev.txt -c constraints.txt
|
||||
python -m pip install -c constraints.txt -e .
|
||||
if: matrix.python-version == '3.10'
|
||||
env:
|
||||
QISKIT_NO_CACHE_GATES: 1
|
||||
- name: 'Install dependencies'
|
||||
run: |
|
||||
python -m pip install -U -r requirements.txt -c constraints.txt
|
||||
python -m pip install -U -r requirements-dev.txt -c constraints.txt
|
||||
python -m pip install -c constraints.txt -e .
|
||||
if: matrix.python-version == '3.12'
|
||||
- name: 'Install optionals'
|
||||
run: |
|
||||
python -m pip install -r requirements-optional.txt -c constraints.txt
|
||||
|
|
|
@ -135,6 +135,18 @@ Note that in order to run `python setup.py ...` commands you need have build
|
|||
dependency packages installed in your environment, which are listed in the
|
||||
`pyproject.toml` file under the `[build-system]` section.
|
||||
|
||||
### Compile time options
|
||||
|
||||
When building qiskit from source there are options available to control how
|
||||
Qiskit is built. Right now the only option is if you set the environment
|
||||
variable `QISKIT_NO_CACHE_GATES=1` this will disable runtime caching of
|
||||
Python gate objects when accessing them from a `QuantumCircuit` or `DAGCircuit`.
|
||||
This makes a tradeoff between runtime performance for Python access and memory
|
||||
overhead. Caching gates will result in better runtime for users of Python at
|
||||
the cost of increased memory consumption. If you're working with any custom
|
||||
transpiler passes written in python or are otherwise using a workflow that
|
||||
repeatedly accesses the `operation` attribute of a `CircuitInstruction` or `op`
|
||||
attribute of `DAGOpNode` enabling caching is recommended.
|
||||
|
||||
## Issues and pull requests
|
||||
|
||||
|
|
|
@ -1196,7 +1196,11 @@ name = "qiskit-circuit"
|
|||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"ndarray",
|
||||
"num-complex",
|
||||
"numpy",
|
||||
"pyo3",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -16,6 +16,11 @@ license = "Apache-2.0"
|
|||
[workspace.dependencies]
|
||||
indexmap.version = "2.2.6"
|
||||
hashbrown.version = "0.14.0"
|
||||
num-complex = "0.4"
|
||||
ndarray = "^0.15.6"
|
||||
numpy = "0.21.0"
|
||||
smallvec = "1.13"
|
||||
|
||||
# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
|
||||
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python
|
||||
# distributions). We only activate that feature when building the C extension module; we still need
|
||||
|
|
|
@ -11,13 +11,13 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
rayon = "1.10"
|
||||
numpy = "0.21.0"
|
||||
numpy.workspace = true
|
||||
rand = "0.8"
|
||||
rand_pcg = "0.3"
|
||||
rand_distr = "0.4.3"
|
||||
ahash = "0.8.11"
|
||||
num-traits = "0.2"
|
||||
num-complex = "0.4"
|
||||
num-complex.workspace = true
|
||||
num-bigint = "0.4"
|
||||
rustworkx-core = "0.14"
|
||||
faer = "0.19.0"
|
||||
|
@ -25,7 +25,7 @@ itertools = "0.13.0"
|
|||
qiskit-circuit.workspace = true
|
||||
|
||||
[dependencies.smallvec]
|
||||
version = "1.13"
|
||||
workspace = true
|
||||
features = ["union"]
|
||||
|
||||
[dependencies.pyo3]
|
||||
|
@ -33,7 +33,7 @@ workspace = true
|
|||
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]
|
||||
|
||||
[dependencies.ndarray]
|
||||
version = "^0.15.6"
|
||||
workspace = true
|
||||
features = ["rayon", "approx-0_5"]
|
||||
|
||||
[dependencies.approx]
|
||||
|
|
|
@ -23,7 +23,7 @@ use itertools::Itertools;
|
|||
use ndarray::prelude::*;
|
||||
use numpy::{IntoPyArray, PyReadonlyArray1, PyReadonlyArray2};
|
||||
|
||||
use crate::two_qubit_decompose::ONE_QUBIT_IDENTITY;
|
||||
use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY;
|
||||
|
||||
/// Find special unitary matrix that maps [c0,c1] to [r,0] or [0,r] if basis_state=0 or
|
||||
/// basis_state=1 respectively
|
||||
|
|
|
@ -51,6 +51,7 @@ use rand::prelude::*;
|
|||
use rand_distr::StandardNormal;
|
||||
use rand_pcg::Pcg64Mcg;
|
||||
|
||||
use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE};
|
||||
use qiskit_circuit::SliceOrInt;
|
||||
|
||||
const PI2: f64 = PI / 2.0;
|
||||
|
@ -60,11 +61,6 @@ const TWO_PI: f64 = 2.0 * PI;
|
|||
|
||||
const C1: c64 = c64 { re: 1.0, im: 0.0 };
|
||||
|
||||
pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] = [
|
||||
[Complex64::new(1., 0.), Complex64::new(0., 0.)],
|
||||
[Complex64::new(0., 0.), Complex64::new(1., 0.)],
|
||||
];
|
||||
|
||||
static B_NON_NORMALIZED: [[Complex64; 4]; 4] = [
|
||||
[
|
||||
Complex64::new(1.0, 0.),
|
||||
|
@ -342,54 +338,6 @@ fn rz_matrix(theta: f64) -> Array2<Complex64> {
|
|||
]
|
||||
}
|
||||
|
||||
static HGATE: [[Complex64; 2]; 2] = [
|
||||
[
|
||||
Complex64::new(FRAC_1_SQRT_2, 0.),
|
||||
Complex64::new(FRAC_1_SQRT_2, 0.),
|
||||
],
|
||||
[
|
||||
Complex64::new(FRAC_1_SQRT_2, 0.),
|
||||
Complex64::new(-FRAC_1_SQRT_2, 0.),
|
||||
],
|
||||
];
|
||||
|
||||
static CXGATE: [[Complex64; 4]; 4] = [
|
||||
[
|
||||
Complex64::new(1., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
],
|
||||
[
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(1., 0.),
|
||||
],
|
||||
[
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(1., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
],
|
||||
[
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(1., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
Complex64::new(0., 0.),
|
||||
],
|
||||
];
|
||||
|
||||
static SXGATE: [[Complex64; 2]; 2] = [
|
||||
[Complex64::new(0.5, 0.5), Complex64::new(0.5, -0.5)],
|
||||
[Complex64::new(0.5, -0.5), Complex64::new(0.5, 0.5)],
|
||||
];
|
||||
|
||||
static XGATE: [[Complex64; 2]; 2] = [
|
||||
[Complex64::new(0., 0.), Complex64::new(1., 0.)],
|
||||
[Complex64::new(1., 0.), Complex64::new(0., 0.)],
|
||||
];
|
||||
|
||||
fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2<Complex64> {
|
||||
let identity = aview2(&ONE_QUBIT_IDENTITY);
|
||||
let phase = Complex64::new(0., global_phase).exp();
|
||||
|
@ -402,10 +350,10 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2<
|
|||
// sequence. If we get a different gate this is getting called
|
||||
// by something else and is invalid.
|
||||
let gate_matrix = match inst.0.as_ref() {
|
||||
"sx" => aview2(&SXGATE).to_owned(),
|
||||
"sx" => aview2(&SX_GATE).to_owned(),
|
||||
"rz" => rz_matrix(inst.1[0]),
|
||||
"cx" => aview2(&CXGATE).to_owned(),
|
||||
"x" => aview2(&XGATE).to_owned(),
|
||||
"cx" => aview2(&CX_GATE).to_owned(),
|
||||
"x" => aview2(&X_GATE).to_owned(),
|
||||
_ => unreachable!("Undefined gate"),
|
||||
};
|
||||
(gate_matrix, &inst.2)
|
||||
|
@ -1481,7 +1429,7 @@ impl TwoQubitBasisDecomposer {
|
|||
} else {
|
||||
euler_matrix_q0 = rz_matrix(euler_q0[0][2] + euler_q0[1][0]).dot(&euler_matrix_q0);
|
||||
}
|
||||
euler_matrix_q0 = aview2(&HGATE).dot(&euler_matrix_q0);
|
||||
euler_matrix_q0 = aview2(&H_GATE).dot(&euler_matrix_q0);
|
||||
self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q0.view(), 0);
|
||||
|
||||
let rx_0 = rx_matrix(euler_q1[0][0]);
|
||||
|
@ -1489,7 +1437,7 @@ impl TwoQubitBasisDecomposer {
|
|||
let rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]);
|
||||
let mut euler_matrix_q1 = rz.dot(&rx_0);
|
||||
euler_matrix_q1 = rx_1.dot(&euler_matrix_q1);
|
||||
euler_matrix_q1 = aview2(&HGATE).dot(&euler_matrix_q1);
|
||||
euler_matrix_q1 = aview2(&H_GATE).dot(&euler_matrix_q1);
|
||||
self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q1.view(), 1);
|
||||
|
||||
gates.push(("cx".to_string(), smallvec![], smallvec![1, 0]));
|
||||
|
@ -1550,12 +1498,12 @@ impl TwoQubitBasisDecomposer {
|
|||
return None;
|
||||
}
|
||||
gates.push(("cx".to_string(), smallvec![], smallvec![1, 0]));
|
||||
let mut euler_matrix = rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&HGATE));
|
||||
let mut euler_matrix = rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&H_GATE));
|
||||
euler_matrix = rx_matrix(euler_q0[3][1]).dot(&euler_matrix);
|
||||
euler_matrix = rz_matrix(euler_q0[3][2]).dot(&euler_matrix);
|
||||
self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix.view(), 0);
|
||||
|
||||
let mut euler_matrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&HGATE));
|
||||
let mut euler_matrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&H_GATE));
|
||||
euler_matrix = rz_matrix(euler_q1[3][1]).dot(&euler_matrix);
|
||||
euler_matrix = rx_matrix(euler_q1[3][2]).dot(&euler_matrix);
|
||||
self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix.view(), 1);
|
||||
|
|
|
@ -11,4 +11,17 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
hashbrown.workspace = true
|
||||
pyo3.workspace = true
|
||||
num-complex.workspace = true
|
||||
ndarray.workspace = true
|
||||
numpy.workspace = true
|
||||
|
||||
[dependencies.pyo3]
|
||||
workspace = true
|
||||
features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"]
|
||||
|
||||
[dependencies.smallvec]
|
||||
workspace = true
|
||||
features = ["union"]
|
||||
|
||||
[features]
|
||||
cache_pygates = []
|
||||
|
|
|
@ -4,3 +4,66 @@ The Rust-based data structures for circuits.
|
|||
This currently defines the core data collections for `QuantumCircuit`, but may expand in the future to back `DAGCircuit` as well.
|
||||
|
||||
This crate is a very low part of the Rust stack, if not the very lowest.
|
||||
|
||||
The data model exposed by this crate is as follows.
|
||||
|
||||
## CircuitData
|
||||
|
||||
The core representation of a quantum circuit in Rust is the `CircuitData` struct. This containts the list
|
||||
of instructions that are comprising the circuit. Each element in this list is modeled by a
|
||||
`CircuitInstruction` struct. The `CircuitInstruction` contains the operation object and it's operands.
|
||||
This includes the parameters and bits. It also contains the potential mutable state of the Operation representation from the legacy Python data model; namely `duration`, `unit`, `condition`, and `label`.
|
||||
In the future we'll be able to remove all of that except for label.
|
||||
|
||||
At rest a `CircuitInstruction` is compacted into a `PackedInstruction` which caches reused qargs
|
||||
in the instructions to reduce the memory overhead of `CircuitData`. The `PackedInstruction` objects
|
||||
get unpacked back to `CircuitInstruction` when accessed for a more convienent working form.
|
||||
|
||||
Additionally the `CircuitData` contains a `param_table` field which is used to track parameterized
|
||||
instructions that are using python defined `ParameterExpression` objects for any parameters and also
|
||||
a global phase field which is used to track the global phase of the circuit.
|
||||
|
||||
## Operation Model
|
||||
|
||||
In the circuit crate all the operations used in a `CircuitInstruction` are part of the `OperationType`
|
||||
enum. The `OperationType` enum has four variants which are used to define the different types of
|
||||
operation objects that can be on a circuit:
|
||||
|
||||
- `StandardGate`: a rust native representation of a member of the Qiskit standard gate library. This is
|
||||
an `enum` that enumerates all the gates in the library and statically defines all the gate properties
|
||||
except for gates that take parameters,
|
||||
- `PyGate`: A struct that wraps a gate outside the standard library defined in Python. This struct wraps
|
||||
a `Gate` instance (or subclass) as a `PyObject`. The static properties of this object (such as name,
|
||||
number of qubits, etc) are stored in Rust for performance but the dynamic properties such as
|
||||
the matrix or definition are accessed by calling back into Python to get them from the stored
|
||||
`PyObject`
|
||||
- `PyInstruction`: A struct that wraps an instruction defined in Python. This struct wraps an
|
||||
`Instruction` instance (or subclass) as a `PyObject`. The static properties of this object (such as
|
||||
name, number of qubits, etc) are stored in Rust for performance but the dynamic properties such as
|
||||
the definition are accessed by calling back into Python to get them from the stored `PyObject`. As
|
||||
the primary difference between `Gate` and `Instruction` in the python data model are that `Gate` is a
|
||||
specialized `Instruction` subclass that represents unitary operations the primary difference between
|
||||
this and `PyGate` are that `PyInstruction` will always return `None` when it's matrix is accessed.
|
||||
- `PyOperation`: A struct that wraps an operation defined in Python. This struct wraps an `Operation`
|
||||
instance (or subclass) as a `PyObject`. The static properties of this object (such as name, number
|
||||
of qubits, etc) are stored in Rust for performance. As `Operation` is the base abstract interface
|
||||
definition of what can be put on a circuit this is mostly just a container for custom Python objects.
|
||||
Anything that's operating on a bare operation will likely need to access it via the `PyObject`
|
||||
manually because the interface doesn't define many standard properties outside of what's cached in
|
||||
the struct.
|
||||
|
||||
There is also an `Operation` trait defined which defines the common access pattern interface to these
|
||||
4 types along with the `OperationType` parent. This trait defines methods to access the standard data
|
||||
model attributes of operations in Qiskit. This includes things like the name, number of qubits, the matrix, the definition, etc.
|
||||
|
||||
## ParamTable
|
||||
|
||||
The `ParamTable` struct is used to track which circuit instructions are using `ParameterExpression`
|
||||
objects for any of their parameters. The Python space `ParameterExpression` is comprised of a symengine
|
||||
symbolic expression that defines operations using `Parameter` objects. Each `Parameter` is modeled by
|
||||
a uuid and a name to uniquely identify it. The parameter table maps the `Parameter` objects to the
|
||||
`CircuitInstruction` in the `CircuitData` that are using them. The `Parameter` comprised of 3 `HashMaps` internally that map the uuid (as `u128`, which is accesible in Python by using `uuid.int`) to the `ParamEntry`, the `name` to the uuid, and the uuid to the PyObject for the actual `Parameter`.
|
||||
|
||||
The `ParamEntry` is just a `HashSet` of 2-tuples with usize elements. The two usizes represent the instruction index in the `CircuitData` and the index of the `CircuitInstruction.params` field of
|
||||
a give instruction where the given `Parameter` is used in the circuit. If the instruction index is
|
||||
`GLOBAL_PHASE_MAX`, that points to the global phase property of the circuit instead of a `CircuitInstruction`.
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
use crate::BitType;
|
||||
use hashbrown::HashMap;
|
||||
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
||||
use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyList;
|
||||
use std::fmt::Debug;
|
||||
|
@ -83,6 +83,15 @@ pub(crate) struct BitData<T> {
|
|||
|
||||
pub(crate) struct BitNotFoundError<'py>(pub(crate) Bound<'py, PyAny>);
|
||||
|
||||
impl<'py> From<BitNotFoundError<'py>> for PyErr {
|
||||
fn from(error: BitNotFoundError) -> Self {
|
||||
PyKeyError::new_err(format!(
|
||||
"Bit {:?} has not been added to this circuit.",
|
||||
error.0
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BitData<T>
|
||||
where
|
||||
T: From<BitType> + Copy,
|
||||
|
@ -142,7 +151,7 @@ where
|
|||
/// Map the provided native indices to the corresponding Python
|
||||
/// bit instances.
|
||||
/// Panics if any of the indices are out of range.
|
||||
pub fn map_indices(&self, bits: &[T]) -> impl Iterator<Item = &Py<PyAny>> + ExactSizeIterator {
|
||||
pub fn map_indices(&self, bits: &[T]) -> impl ExactSizeIterator<Item = &Py<PyAny>> {
|
||||
let v: Vec<_> = bits.iter().map(|i| self.get(*i).unwrap()).collect();
|
||||
v.into_iter()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,9 +11,45 @@
|
|||
// that they have been altered from the originals.
|
||||
|
||||
use pyo3::basic::CompareOp;
|
||||
use pyo3::exceptions::PyValueError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyList, PyTuple};
|
||||
use pyo3::{PyObject, PyResult};
|
||||
use pyo3::types::{IntoPyDict, PyList, PyTuple, PyType};
|
||||
use pyo3::{intern, IntoPy, PyObject, PyResult};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::imports::{
|
||||
get_std_gate_class, populate_std_gate_map, GATE, INSTRUCTION, OPERATION,
|
||||
SINGLETON_CONTROLLED_GATE, SINGLETON_GATE,
|
||||
};
|
||||
use crate::interner::Index;
|
||||
use crate::operations::{OperationType, Param, PyGate, PyInstruction, PyOperation, StandardGate};
|
||||
|
||||
/// These are extra mutable attributes for a circuit instruction's state. In general we don't
|
||||
/// typically deal with this in rust space and the majority of the time they're not used in Python
|
||||
/// space either. To save memory these are put in a separate struct and are stored inside a
|
||||
/// `Box` on `CircuitInstruction` and `PackedInstruction`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtraInstructionAttributes {
|
||||
pub label: Option<String>,
|
||||
pub duration: Option<PyObject>,
|
||||
pub unit: Option<String>,
|
||||
pub condition: Option<PyObject>,
|
||||
}
|
||||
|
||||
/// Private type used to store instructions with interned arg lists.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PackedInstruction {
|
||||
/// The Python-side operation instance.
|
||||
pub op: OperationType,
|
||||
/// The index under which the interner has stored `qubits`.
|
||||
pub qubits_id: Index,
|
||||
/// The index under which the interner has stored `clbits`.
|
||||
pub clbits_id: Index,
|
||||
pub params: SmallVec<[Param; 3]>,
|
||||
pub extra_attrs: Option<Box<ExtraInstructionAttributes>>,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
pub py_op: Option<PyObject>,
|
||||
}
|
||||
|
||||
/// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and
|
||||
/// various operands.
|
||||
|
@ -47,28 +83,45 @@ use pyo3::{PyObject, PyResult};
|
|||
/// mutations of the object do not invalidate the types, nor the restrictions placed on it by
|
||||
/// its context. Typically this will mean, for example, that :attr:`qubits` must be a sequence
|
||||
/// of distinct items, with no duplicates.
|
||||
#[pyclass(
|
||||
freelist = 20,
|
||||
sequence,
|
||||
get_all,
|
||||
module = "qiskit._accelerate.circuit"
|
||||
)]
|
||||
#[pyclass(freelist = 20, sequence, module = "qiskit._accelerate.circuit")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CircuitInstruction {
|
||||
/// The logical operation that this instruction represents an execution of.
|
||||
pub operation: PyObject,
|
||||
pub operation: OperationType,
|
||||
/// A sequence of the qubits that the operation is applied to.
|
||||
#[pyo3(get)]
|
||||
pub qubits: Py<PyTuple>,
|
||||
/// A sequence of the classical bits that this operation reads from or writes to.
|
||||
#[pyo3(get)]
|
||||
pub clbits: Py<PyTuple>,
|
||||
pub params: SmallVec<[Param; 3]>,
|
||||
pub extra_attrs: Option<Box<ExtraInstructionAttributes>>,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
pub py_op: Option<PyObject>,
|
||||
}
|
||||
|
||||
/// This enum is for backwards compatibility if a user was doing something from
|
||||
/// Python like CircuitInstruction(SXGate(), [qr[0]], []) by passing a python
|
||||
/// gate object directly to a CircuitInstruction. In this case we need to
|
||||
/// create a rust side object from the pyobject in CircuitInstruction.new()
|
||||
/// With the `Object` variant which will convert the python object to a rust
|
||||
/// `OperationType`
|
||||
#[derive(FromPyObject, Debug)]
|
||||
pub enum OperationInput {
|
||||
Standard(StandardGate),
|
||||
Gate(PyGate),
|
||||
Instruction(PyInstruction),
|
||||
Operation(PyOperation),
|
||||
Object(PyObject),
|
||||
}
|
||||
|
||||
impl CircuitInstruction {
|
||||
pub fn new<T1, T2, U1, U2>(
|
||||
py: Python,
|
||||
operation: PyObject,
|
||||
operation: OperationType,
|
||||
qubits: impl IntoIterator<Item = T1, IntoIter = U1>,
|
||||
clbits: impl IntoIterator<Item = T2, IntoIter = U2>,
|
||||
params: SmallVec<[Param; 3]>,
|
||||
extra_attrs: Option<Box<ExtraInstructionAttributes>>,
|
||||
) -> Self
|
||||
where
|
||||
T1: ToPyObject,
|
||||
|
@ -80,19 +133,41 @@ impl CircuitInstruction {
|
|||
operation,
|
||||
qubits: PyTuple::new_bound(py, qubits).unbind(),
|
||||
clbits: PyTuple::new_bound(py, clbits).unbind(),
|
||||
params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OperationType> for OperationInput {
|
||||
fn from(value: OperationType) -> Self {
|
||||
match value {
|
||||
OperationType::Standard(op) => Self::Standard(op),
|
||||
OperationType::Gate(gate) => Self::Gate(gate),
|
||||
OperationType::Instruction(inst) => Self::Instruction(inst),
|
||||
OperationType::Operation(op) => Self::Operation(op),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl CircuitInstruction {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[new]
|
||||
#[pyo3(signature = (operation, qubits=None, clbits=None, params=smallvec![], label=None, duration=None, unit=None, condition=None))]
|
||||
pub fn py_new(
|
||||
py: Python<'_>,
|
||||
operation: PyObject,
|
||||
operation: OperationInput,
|
||||
qubits: Option<&Bound<PyAny>>,
|
||||
clbits: Option<&Bound<PyAny>>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
params: SmallVec<[Param; 3]>,
|
||||
label: Option<String>,
|
||||
duration: Option<PyObject>,
|
||||
unit: Option<String>,
|
||||
condition: Option<PyObject>,
|
||||
) -> PyResult<Self> {
|
||||
fn as_tuple(py: Python<'_>, seq: Option<&Bound<PyAny>>) -> PyResult<Py<PyTuple>> {
|
||||
match seq {
|
||||
None => Ok(PyTuple::empty_bound(py).unbind()),
|
||||
|
@ -116,14 +191,136 @@ impl CircuitInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
Py::new(
|
||||
py,
|
||||
CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
},
|
||||
)
|
||||
let extra_attrs =
|
||||
if label.is_some() || duration.is_some() || unit.is_some() || condition.is_some() {
|
||||
Some(Box::new(ExtraInstructionAttributes {
|
||||
label,
|
||||
duration,
|
||||
unit,
|
||||
condition,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match operation {
|
||||
OperationInput::Standard(operation) => {
|
||||
let operation = OperationType::Standard(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: None,
|
||||
})
|
||||
}
|
||||
OperationInput::Gate(operation) => {
|
||||
let operation = OperationType::Gate(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: None,
|
||||
})
|
||||
}
|
||||
OperationInput::Instruction(operation) => {
|
||||
let operation = OperationType::Instruction(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: None,
|
||||
})
|
||||
}
|
||||
OperationInput::Operation(operation) => {
|
||||
let operation = OperationType::Operation(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: None,
|
||||
})
|
||||
}
|
||||
OperationInput::Object(old_op) => {
|
||||
let op = convert_py_to_operation_type(py, old_op.clone_ref(py))?;
|
||||
let extra_attrs = if op.label.is_some()
|
||||
|| op.duration.is_some()
|
||||
|| op.unit.is_some()
|
||||
|| op.condition.is_some()
|
||||
{
|
||||
Some(Box::new(ExtraInstructionAttributes {
|
||||
label: op.label,
|
||||
duration: op.duration,
|
||||
unit: op.unit,
|
||||
condition: op.condition,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match op.operation {
|
||||
OperationType::Standard(operation) => {
|
||||
let operation = OperationType::Standard(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params: op.params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: Some(old_op.clone_ref(py)),
|
||||
})
|
||||
}
|
||||
OperationType::Gate(operation) => {
|
||||
let operation = OperationType::Gate(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params: op.params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: Some(old_op.clone_ref(py)),
|
||||
})
|
||||
}
|
||||
OperationType::Instruction(operation) => {
|
||||
let operation = OperationType::Instruction(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params: op.params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: Some(old_op.clone_ref(py)),
|
||||
})
|
||||
}
|
||||
OperationType::Operation(operation) => {
|
||||
let operation = OperationType::Operation(operation);
|
||||
Ok(CircuitInstruction {
|
||||
operation,
|
||||
qubits: as_tuple(py, qubits)?,
|
||||
clbits: as_tuple(py, clbits)?,
|
||||
params: op.params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: Some(old_op.clone_ref(py)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a shallow copy.
|
||||
|
@ -134,28 +331,127 @@ impl CircuitInstruction {
|
|||
self.clone()
|
||||
}
|
||||
|
||||
/// The logical operation that this instruction represents an execution of.
|
||||
#[cfg(not(feature = "cache_pygates"))]
|
||||
#[getter]
|
||||
pub fn operation(&self, py: Python) -> PyResult<PyObject> {
|
||||
operation_type_to_py(py, self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
#[getter]
|
||||
pub fn operation(&mut self, py: Python) -> PyResult<PyObject> {
|
||||
Ok(match &self.py_op {
|
||||
Some(op) => op.clone_ref(py),
|
||||
None => {
|
||||
let op = operation_type_to_py(py, self)?;
|
||||
self.py_op = Some(op.clone_ref(py));
|
||||
op
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a shallow copy with the given fields replaced.
|
||||
///
|
||||
/// Returns:
|
||||
/// CircuitInstruction: A new instance with the given fields replaced.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn replace(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
operation: Option<PyObject>,
|
||||
operation: Option<OperationInput>,
|
||||
qubits: Option<&Bound<PyAny>>,
|
||||
clbits: Option<&Bound<PyAny>>,
|
||||
) -> PyResult<Py<Self>> {
|
||||
params: Option<SmallVec<[Param; 3]>>,
|
||||
label: Option<String>,
|
||||
duration: Option<PyObject>,
|
||||
unit: Option<String>,
|
||||
condition: Option<PyObject>,
|
||||
) -> PyResult<Self> {
|
||||
let operation = operation.unwrap_or_else(|| self.operation.clone().into());
|
||||
|
||||
let params = match params {
|
||||
Some(params) => params,
|
||||
None => self.params.clone(),
|
||||
};
|
||||
|
||||
let label = match label {
|
||||
Some(label) => Some(label),
|
||||
None => match &self.extra_attrs {
|
||||
Some(extra_attrs) => extra_attrs.label.clone(),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
let duration = match duration {
|
||||
Some(duration) => Some(duration),
|
||||
None => match &self.extra_attrs {
|
||||
Some(extra_attrs) => extra_attrs.duration.clone(),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
let unit: Option<String> = match unit {
|
||||
Some(unit) => Some(unit),
|
||||
None => match &self.extra_attrs {
|
||||
Some(extra_attrs) => extra_attrs.unit.clone(),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
let condition: Option<PyObject> = match condition {
|
||||
Some(condition) => Some(condition),
|
||||
None => match &self.extra_attrs {
|
||||
Some(extra_attrs) => extra_attrs.condition.clone(),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
|
||||
CircuitInstruction::py_new(
|
||||
py,
|
||||
operation.unwrap_or_else(|| self.operation.clone_ref(py)),
|
||||
operation,
|
||||
Some(qubits.unwrap_or_else(|| self.qubits.bind(py))),
|
||||
Some(clbits.unwrap_or_else(|| self.clbits.bind(py))),
|
||||
params,
|
||||
label,
|
||||
duration,
|
||||
unit,
|
||||
condition,
|
||||
)
|
||||
}
|
||||
|
||||
fn __getnewargs__(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||
fn __getstate__(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||
Ok((
|
||||
self.operation.bind(py),
|
||||
operation_type_to_py(py, self)?,
|
||||
self.qubits.bind(py),
|
||||
self.clbits.bind(py),
|
||||
)
|
||||
.into_py(py))
|
||||
}
|
||||
|
||||
fn __setstate__(&mut self, py: Python<'_>, state: &Bound<PyTuple>) -> PyResult<()> {
|
||||
let op = convert_py_to_operation_type(py, state.get_item(0)?.into())?;
|
||||
self.operation = op.operation;
|
||||
self.params = op.params;
|
||||
self.qubits = state.get_item(1)?.extract()?;
|
||||
self.clbits = state.get_item(2)?.extract()?;
|
||||
if op.label.is_some()
|
||||
|| op.duration.is_some()
|
||||
|| op.unit.is_some()
|
||||
|| op.condition.is_some()
|
||||
{
|
||||
self.extra_attrs = Some(Box::new(ExtraInstructionAttributes {
|
||||
label: op.label,
|
||||
duration: op.duration,
|
||||
unit: op.unit,
|
||||
condition: op.condition,
|
||||
}));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||
Ok((
|
||||
operation_type_to_py(py, self)?,
|
||||
self.qubits.bind(py),
|
||||
self.clbits.bind(py),
|
||||
)
|
||||
|
@ -172,7 +468,7 @@ impl CircuitInstruction {
|
|||
, clbits={}\
|
||||
)",
|
||||
type_name,
|
||||
r.operation.bind(py).repr()?,
|
||||
operation_type_to_py(py, &r)?,
|
||||
r.qubits.bind(py).repr()?,
|
||||
r.clbits.bind(py).repr()?
|
||||
))
|
||||
|
@ -184,23 +480,50 @@ impl CircuitInstruction {
|
|||
// the interface to behave exactly like the old 3-tuple `(inst, qargs, cargs)` if it's treated
|
||||
// like that via unpacking or similar. That means that the `parameters` field is completely
|
||||
// absent, and the qubits and clbits must be converted to lists.
|
||||
pub fn _legacy_format<'py>(&self, py: Python<'py>) -> Bound<'py, PyTuple> {
|
||||
PyTuple::new_bound(
|
||||
#[cfg(not(feature = "cache_pygates"))]
|
||||
pub fn _legacy_format<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
|
||||
let op = operation_type_to_py(py, self)?;
|
||||
|
||||
Ok(PyTuple::new_bound(
|
||||
py,
|
||||
[
|
||||
self.operation.bind(py),
|
||||
&self.qubits.bind(py).to_list(),
|
||||
&self.clbits.bind(py).to_list(),
|
||||
],
|
||||
)
|
||||
[op, self.qubits.to_object(py), self.clbits.to_object(py)],
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
pub fn _legacy_format<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
|
||||
let op = match &self.py_op {
|
||||
Some(op) => op.clone_ref(py),
|
||||
None => {
|
||||
let op = operation_type_to_py(py, self)?;
|
||||
self.py_op = Some(op.clone_ref(py));
|
||||
op
|
||||
}
|
||||
};
|
||||
Ok(PyTuple::new_bound(
|
||||
py,
|
||||
[op, self.qubits.to_object(py), self.clbits.to_object(py)],
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cache_pygates"))]
|
||||
pub fn __getitem__(&self, py: Python<'_>, key: &Bound<PyAny>) -> PyResult<PyObject> {
|
||||
Ok(self._legacy_format(py).as_any().get_item(key)?.into_py(py))
|
||||
Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
pub fn __getitem__(&mut self, py: Python<'_>, key: &Bound<PyAny>) -> PyResult<PyObject> {
|
||||
Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cache_pygates"))]
|
||||
pub fn __iter__(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||
Ok(self._legacy_format(py).as_any().iter()?.into_py(py))
|
||||
Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
|
||||
}
|
||||
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
pub fn __iter__(&mut self, py: Python<'_>) -> PyResult<PyObject> {
|
||||
Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py))
|
||||
}
|
||||
|
||||
pub fn __len__(&self) -> usize {
|
||||
|
@ -227,16 +550,94 @@ impl CircuitInstruction {
|
|||
let other: PyResult<Bound<CircuitInstruction>> = other.extract();
|
||||
return other.map_or(Ok(Some(false)), |v| {
|
||||
let v = v.try_borrow()?;
|
||||
let op_eq = match &self_.operation {
|
||||
OperationType::Standard(op) => {
|
||||
if let OperationType::Standard(other) = &v.operation {
|
||||
if op != other {
|
||||
false
|
||||
} else {
|
||||
let other_params = &v.params;
|
||||
let mut out = true;
|
||||
for (param_a, param_b) in self_.params.iter().zip(other_params)
|
||||
{
|
||||
match param_a {
|
||||
Param::Float(val_a) => {
|
||||
if let Param::Float(val_b) = param_b {
|
||||
if val_a != val_b {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Param::ParameterExpression(val_a) => {
|
||||
if let Param::ParameterExpression(val_b) = param_b {
|
||||
if !val_a.bind(py).eq(val_b.bind(py))? {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Param::Obj(val_a) => {
|
||||
if let Param::Obj(val_b) = param_b {
|
||||
if !val_a.bind(py).eq(val_b.bind(py))? {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
out = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
OperationType::Gate(op) => {
|
||||
if let OperationType::Gate(other) = &v.operation {
|
||||
op.gate.bind(py).eq(other.gate.bind(py))?
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
OperationType::Instruction(op) => {
|
||||
if let OperationType::Instruction(other) = &v.operation {
|
||||
op.instruction.bind(py).eq(other.instruction.bind(py))?
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
OperationType::Operation(op) => {
|
||||
if let OperationType::Operation(other) = &v.operation {
|
||||
op.operation.bind(py).eq(other.operation.bind(py))?
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(
|
||||
self_.clbits.bind(py).eq(v.clbits.bind(py))?
|
||||
&& self_.qubits.bind(py).eq(v.qubits.bind(py))?
|
||||
&& self_.operation.bind(py).eq(v.operation.bind(py))?,
|
||||
&& op_eq,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
if other.is_instance_of::<PyTuple>() {
|
||||
return Ok(Some(self_._legacy_format(py).eq(other)?));
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
let mut self_ = self_.clone();
|
||||
let legacy_format = self_._legacy_format(py)?;
|
||||
return Ok(Some(legacy_format.eq(other)?));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
|
@ -255,3 +656,222 @@ impl CircuitInstruction {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a reference to a `CircuitInstruction` and convert the operation
|
||||
/// inside that to a python side object.
|
||||
pub(crate) fn operation_type_to_py(
|
||||
py: Python,
|
||||
circuit_inst: &CircuitInstruction,
|
||||
) -> PyResult<PyObject> {
|
||||
let (label, duration, unit, condition) = match &circuit_inst.extra_attrs {
|
||||
None => (None, None, None, None),
|
||||
Some(extra_attrs) => (
|
||||
extra_attrs.label.clone(),
|
||||
extra_attrs.duration.clone(),
|
||||
extra_attrs.unit.clone(),
|
||||
extra_attrs.condition.clone(),
|
||||
),
|
||||
};
|
||||
operation_type_and_data_to_py(
|
||||
py,
|
||||
&circuit_inst.operation,
|
||||
&circuit_inst.params,
|
||||
&label,
|
||||
&duration,
|
||||
&unit,
|
||||
&condition,
|
||||
)
|
||||
}
|
||||
|
||||
/// Take an OperationType and the other mutable state fields from a
|
||||
/// rust instruction representation and return a PyObject representing
|
||||
/// a Python side full-fat Qiskit operation as a PyObject. This is typically
|
||||
/// used by accessor functions that need to return an operation to Qiskit, such
|
||||
/// as accesing `CircuitInstruction.operation`.
|
||||
pub(crate) fn operation_type_and_data_to_py(
|
||||
py: Python,
|
||||
operation: &OperationType,
|
||||
params: &[Param],
|
||||
label: &Option<String>,
|
||||
duration: &Option<PyObject>,
|
||||
unit: &Option<String>,
|
||||
condition: &Option<PyObject>,
|
||||
) -> PyResult<PyObject> {
|
||||
match &operation {
|
||||
OperationType::Standard(op) => {
|
||||
let gate_class: &PyObject = &get_std_gate_class(py, *op)?;
|
||||
|
||||
let args = if params.is_empty() {
|
||||
PyTuple::empty_bound(py)
|
||||
} else {
|
||||
PyTuple::new_bound(py, params)
|
||||
};
|
||||
let kwargs = [
|
||||
("label", label.to_object(py)),
|
||||
("unit", unit.to_object(py)),
|
||||
("duration", duration.to_object(py)),
|
||||
]
|
||||
.into_py_dict_bound(py);
|
||||
let mut out = gate_class.call_bound(py, args, Some(&kwargs))?;
|
||||
if condition.is_some() {
|
||||
out = out.call_method0(py, "to_mutable")?;
|
||||
out.setattr(py, "condition", condition.to_object(py))?;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
OperationType::Gate(gate) => Ok(gate.gate.clone_ref(py)),
|
||||
OperationType::Instruction(inst) => Ok(inst.instruction.clone_ref(py)),
|
||||
OperationType::Operation(op) => Ok(op.operation.clone_ref(py)),
|
||||
}
|
||||
}
|
||||
|
||||
/// A container struct that contains the output from the Python object to
|
||||
/// conversion to construct a CircuitInstruction object
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OperationTypeConstruct {
|
||||
pub operation: OperationType,
|
||||
pub params: SmallVec<[Param; 3]>,
|
||||
pub label: Option<String>,
|
||||
pub duration: Option<PyObject>,
|
||||
pub unit: Option<String>,
|
||||
pub condition: Option<PyObject>,
|
||||
}
|
||||
|
||||
/// Convert an inbound Python object for a Qiskit operation and build a rust
|
||||
/// representation of that operation. This will map it to appropriate variant
|
||||
/// of operation type based on class
|
||||
pub(crate) fn convert_py_to_operation_type(
|
||||
py: Python,
|
||||
py_op: PyObject,
|
||||
) -> PyResult<OperationTypeConstruct> {
|
||||
let attr = intern!(py, "_standard_gate");
|
||||
let py_op_bound = py_op.clone_ref(py).into_bound(py);
|
||||
// Get PyType from either base_class if it exists, or if not use the
|
||||
// class/type info from the pyobject
|
||||
let binding = py_op_bound.getattr(intern!(py, "base_class")).ok();
|
||||
let op_obj = py_op_bound.get_type();
|
||||
let raw_op_type: Py<PyType> = match binding {
|
||||
Some(base_class) => base_class.downcast()?.clone().unbind(),
|
||||
None => op_obj.unbind(),
|
||||
};
|
||||
let op_type: Bound<PyType> = raw_op_type.into_bound(py);
|
||||
let mut standard: Option<StandardGate> = match op_type.getattr(attr) {
|
||||
Ok(stdgate) => match stdgate.extract().ok() {
|
||||
Some(gate) => gate,
|
||||
None => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
};
|
||||
// If the input instruction is a standard gate and a singleton instance
|
||||
// we should check for mutable state. A mutable instance should be treated
|
||||
// as a custom gate not a standard gate because it has custom properties.
|
||||
//
|
||||
// In the futuer we can revisit this when we've dropped `duration`, `unit`,
|
||||
// and `condition` from the api as we should own the label in the
|
||||
// `CircuitInstruction`. The other piece here is for controlled gates there
|
||||
// is the control state, so for `SingletonControlledGates` we'll still need
|
||||
// this check.
|
||||
if standard.is_some() {
|
||||
let mutable: bool = py_op.getattr(py, intern!(py, "mutable"))?.extract(py)?;
|
||||
if mutable
|
||||
&& (py_op_bound.is_instance(SINGLETON_GATE.get_bound(py))?
|
||||
|| py_op_bound.is_instance(SINGLETON_CONTROLLED_GATE.get_bound(py))?)
|
||||
{
|
||||
standard = None;
|
||||
}
|
||||
}
|
||||
if let Some(op) = standard {
|
||||
let base_class = op_type.to_object(py);
|
||||
populate_std_gate_map(py, op, base_class);
|
||||
return Ok(OperationTypeConstruct {
|
||||
operation: OperationType::Standard(op),
|
||||
params: py_op.getattr(py, intern!(py, "params"))?.extract(py)?,
|
||||
label: py_op.getattr(py, intern!(py, "label"))?.extract(py)?,
|
||||
duration: py_op.getattr(py, intern!(py, "duration"))?.extract(py)?,
|
||||
unit: py_op.getattr(py, intern!(py, "unit"))?.extract(py)?,
|
||||
condition: py_op.getattr(py, intern!(py, "condition"))?.extract(py)?,
|
||||
});
|
||||
}
|
||||
if op_type.is_subclass(GATE.get_bound(py))? {
|
||||
let params = py_op.getattr(py, intern!(py, "params"))?.extract(py)?;
|
||||
let label = py_op.getattr(py, intern!(py, "label"))?.extract(py)?;
|
||||
let duration = py_op.getattr(py, intern!(py, "duration"))?.extract(py)?;
|
||||
let unit = py_op.getattr(py, intern!(py, "unit"))?.extract(py)?;
|
||||
let condition = py_op.getattr(py, intern!(py, "condition"))?.extract(py)?;
|
||||
|
||||
let out_op = PyGate {
|
||||
qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
|
||||
clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
|
||||
params: py_op
|
||||
.getattr(py, intern!(py, "params"))?
|
||||
.downcast_bound::<PyList>(py)?
|
||||
.len() as u32,
|
||||
op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
|
||||
gate: py_op,
|
||||
};
|
||||
return Ok(OperationTypeConstruct {
|
||||
operation: OperationType::Gate(out_op),
|
||||
params,
|
||||
label,
|
||||
duration,
|
||||
unit,
|
||||
condition,
|
||||
});
|
||||
}
|
||||
if op_type.is_subclass(INSTRUCTION.get_bound(py))? {
|
||||
let params = py_op.getattr(py, intern!(py, "params"))?.extract(py)?;
|
||||
let label = py_op.getattr(py, intern!(py, "label"))?.extract(py)?;
|
||||
let duration = py_op.getattr(py, intern!(py, "duration"))?.extract(py)?;
|
||||
let unit = py_op.getattr(py, intern!(py, "unit"))?.extract(py)?;
|
||||
let condition = py_op.getattr(py, intern!(py, "condition"))?.extract(py)?;
|
||||
|
||||
let out_op = PyInstruction {
|
||||
qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
|
||||
clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
|
||||
params: py_op
|
||||
.getattr(py, intern!(py, "params"))?
|
||||
.downcast_bound::<PyList>(py)?
|
||||
.len() as u32,
|
||||
op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
|
||||
instruction: py_op,
|
||||
};
|
||||
return Ok(OperationTypeConstruct {
|
||||
operation: OperationType::Instruction(out_op),
|
||||
params,
|
||||
label,
|
||||
duration,
|
||||
unit,
|
||||
condition,
|
||||
});
|
||||
}
|
||||
|
||||
if op_type.is_subclass(OPERATION.get_bound(py))? {
|
||||
let params = match py_op.getattr(py, intern!(py, "params")) {
|
||||
Ok(value) => value.extract(py)?,
|
||||
Err(_) => smallvec![],
|
||||
};
|
||||
let label = None;
|
||||
let duration = None;
|
||||
let unit = None;
|
||||
let condition = None;
|
||||
let out_op = PyOperation {
|
||||
qubits: py_op.getattr(py, intern!(py, "num_qubits"))?.extract(py)?,
|
||||
clbits: py_op.getattr(py, intern!(py, "num_clbits"))?.extract(py)?,
|
||||
params: match py_op.getattr(py, intern!(py, "params")) {
|
||||
Ok(value) => value.downcast_bound::<PyList>(py)?.len() as u32,
|
||||
Err(_) => 0,
|
||||
},
|
||||
op_name: py_op.getattr(py, intern!(py, "name"))?.extract(py)?,
|
||||
operation: py_op,
|
||||
};
|
||||
return Ok(OperationTypeConstruct {
|
||||
operation: OperationType::Operation(out_op),
|
||||
params,
|
||||
label,
|
||||
duration,
|
||||
unit,
|
||||
condition,
|
||||
});
|
||||
}
|
||||
Err(PyValueError::new_err(format!("Invalid input: {}", py_op)))
|
||||
}
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use crate::circuit_instruction::CircuitInstruction;
|
||||
use crate::circuit_instruction::{
|
||||
convert_py_to_operation_type, operation_type_to_py, CircuitInstruction,
|
||||
ExtraInstructionAttributes,
|
||||
};
|
||||
use crate::operations::Operation;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple};
|
||||
use pyo3::{intern, PyObject, PyResult};
|
||||
|
@ -106,13 +110,33 @@ impl DAGOpNode {
|
|||
}
|
||||
None => qargs.str()?.into_any(),
|
||||
};
|
||||
let res = convert_py_to_operation_type(py, op.clone_ref(py))?;
|
||||
|
||||
let extra_attrs = if res.label.is_some()
|
||||
|| res.duration.is_some()
|
||||
|| res.unit.is_some()
|
||||
|| res.condition.is_some()
|
||||
{
|
||||
Some(Box::new(ExtraInstructionAttributes {
|
||||
label: res.label,
|
||||
duration: res.duration,
|
||||
unit: res.unit,
|
||||
condition: res.condition,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((
|
||||
DAGOpNode {
|
||||
instruction: CircuitInstruction {
|
||||
operation: op,
|
||||
operation: res.operation,
|
||||
qubits: qargs.unbind(),
|
||||
clbits: cargs.unbind(),
|
||||
params: res.params,
|
||||
extra_attrs,
|
||||
#[cfg(feature = "cache_pygates")]
|
||||
py_op: Some(op),
|
||||
},
|
||||
sort_key: sort_key.unbind(),
|
||||
},
|
||||
|
@ -120,18 +144,18 @@ impl DAGOpNode {
|
|||
))
|
||||
}
|
||||
|
||||
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
|
||||
fn __reduce__(slf: PyRef<Self>, py: Python) -> PyResult<PyObject> {
|
||||
let state = (slf.as_ref()._node_id, &slf.sort_key);
|
||||
(
|
||||
Ok((
|
||||
py.get_type_bound::<Self>(),
|
||||
(
|
||||
&slf.instruction.operation,
|
||||
operation_type_to_py(py, &slf.instruction)?,
|
||||
&slf.instruction.qubits,
|
||||
&slf.instruction.clbits,
|
||||
),
|
||||
state,
|
||||
)
|
||||
.into_py(py)
|
||||
.into_py(py))
|
||||
}
|
||||
|
||||
fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
|
||||
|
@ -142,13 +166,31 @@ impl DAGOpNode {
|
|||
}
|
||||
|
||||
#[getter]
|
||||
fn get_op(&self, py: Python) -> PyObject {
|
||||
self.instruction.operation.clone_ref(py)
|
||||
fn get_op(&self, py: Python) -> PyResult<PyObject> {
|
||||
operation_type_to_py(py, &self.instruction)
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_op(&mut self, op: PyObject) {
|
||||
self.instruction.operation = op;
|
||||
fn set_op(&mut self, py: Python, op: PyObject) -> PyResult<()> {
|
||||
let res = convert_py_to_operation_type(py, op)?;
|
||||
self.instruction.operation = res.operation;
|
||||
self.instruction.params = res.params;
|
||||
let extra_attrs = if res.label.is_some()
|
||||
|| res.duration.is_some()
|
||||
|| res.unit.is_some()
|
||||
|| res.condition.is_some()
|
||||
{
|
||||
Some(Box::new(ExtraInstructionAttributes {
|
||||
label: res.label,
|
||||
duration: res.duration,
|
||||
unit: res.unit,
|
||||
condition: res.condition,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.instruction.extra_attrs = extra_attrs;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
|
@ -173,29 +215,27 @@ impl DAGOpNode {
|
|||
|
||||
/// Returns the Instruction name corresponding to the op for this node
|
||||
#[getter]
|
||||
fn get_name(&self, py: Python) -> PyResult<PyObject> {
|
||||
Ok(self
|
||||
.instruction
|
||||
.operation
|
||||
.bind(py)
|
||||
.getattr(intern!(py, "name"))?
|
||||
.unbind())
|
||||
fn get_name(&self, py: Python) -> PyObject {
|
||||
self.instruction.operation.name().to_object(py)
|
||||
}
|
||||
|
||||
/// Sets the Instruction name corresponding to the op for this node
|
||||
#[setter]
|
||||
fn set_name(&self, py: Python, new_name: PyObject) -> PyResult<()> {
|
||||
self.instruction
|
||||
.operation
|
||||
.bind(py)
|
||||
.setattr(intern!(py, "name"), new_name)
|
||||
fn set_name(&mut self, py: Python, new_name: PyObject) -> PyResult<()> {
|
||||
let op = operation_type_to_py(py, &self.instruction)?;
|
||||
op.bind(py).setattr(intern!(py, "name"), new_name)?;
|
||||
let res = convert_py_to_operation_type(py, op)?;
|
||||
self.instruction.operation = res.operation;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a representation of the DAGOpNode
|
||||
fn __repr__(&self, py: Python) -> PyResult<String> {
|
||||
Ok(format!(
|
||||
"DAGOpNode(op={}, qargs={}, cargs={})",
|
||||
self.instruction.operation.bind(py).repr()?,
|
||||
operation_type_to_py(py, &self.instruction)?
|
||||
.bind(py)
|
||||
.repr()?,
|
||||
self.instruction.qubits.bind(py).repr()?,
|
||||
self.instruction.clbits.bind(py).repr()?
|
||||
))
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
// This code is part of Qiskit.
|
||||
//
|
||||
// (C) Copyright IBM 2023
|
||||
//
|
||||
// This code is licensed under the Apache License, Version 2.0. You may
|
||||
// obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// Any modifications or derivative works of this code must retain this
|
||||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use num_complex::Complex64;
|
||||
use std::f64::consts::FRAC_1_SQRT_2;
|
||||
|
||||
// num-complex exposes an equivalent function but it's not a const function
|
||||
// so it's not compatible with static definitions. This is a const func and
|
||||
// just reduces the amount of typing we need.
|
||||
#[inline(always)]
|
||||
const fn c64(re: f64, im: f64) -> Complex64 {
|
||||
Complex64::new(re, im)
|
||||
}
|
||||
|
||||
pub static ONE_QUBIT_IDENTITY: [[Complex64; 2]; 2] =
|
||||
[[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(1., 0.)]];
|
||||
|
||||
#[inline]
|
||||
pub fn rx_gate(theta: f64) -> [[Complex64; 2]; 2] {
|
||||
let half_theta = theta / 2.;
|
||||
let cos = c64(half_theta.cos(), 0.);
|
||||
let isin = c64(0., -half_theta.sin());
|
||||
[[cos, isin], [isin, cos]]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ry_gate(theta: f64) -> [[Complex64; 2]; 2] {
|
||||
let half_theta = theta / 2.;
|
||||
let cos = c64(half_theta.cos(), 0.);
|
||||
let sin = c64(half_theta.sin(), 0.);
|
||||
[[cos, -sin], [sin, cos]]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rz_gate(theta: f64) -> [[Complex64; 2]; 2] {
|
||||
let ilam2 = c64(0., 0.5 * theta);
|
||||
[[(-ilam2).exp(), c64(0., 0.)], [c64(0., 0.), ilam2.exp()]]
|
||||
}
|
||||
|
||||
pub static H_GATE: [[Complex64; 2]; 2] = [
|
||||
[c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)],
|
||||
[c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)],
|
||||
];
|
||||
|
||||
pub static CX_GATE: [[Complex64; 4]; 4] = [
|
||||
[c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
];
|
||||
|
||||
pub static SX_GATE: [[Complex64; 2]; 2] = [
|
||||
[c64(0.5, 0.5), c64(0.5, -0.5)],
|
||||
[c64(0.5, -0.5), c64(0.5, 0.5)],
|
||||
];
|
||||
|
||||
pub static X_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(1., 0.)], [c64(1., 0.), c64(0., 0.)]];
|
||||
|
||||
pub static Z_GATE: [[Complex64; 2]; 2] = [[c64(1., 0.), c64(0., 0.)], [c64(0., 0.), c64(-1., 0.)]];
|
||||
|
||||
pub static Y_GATE: [[Complex64; 2]; 2] = [[c64(0., 0.), c64(0., -1.)], [c64(0., 1.), c64(0., 0.)]];
|
||||
|
||||
pub static CZ_GATE: [[Complex64; 4]; 4] = [
|
||||
[c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(-1., 0.)],
|
||||
];
|
||||
|
||||
pub static CY_GATE: [[Complex64; 4]; 4] = [
|
||||
[c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(0., -1.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 1.), c64(0., 0.), c64(0., 0.)],
|
||||
];
|
||||
|
||||
pub static CCX_GATE: [[Complex64; 8]; 8] = [
|
||||
[
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(1., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
];
|
||||
|
||||
pub static ECR_GATE: [[Complex64; 4]; 4] = [
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(FRAC_1_SQRT_2, 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., FRAC_1_SQRT_2),
|
||||
],
|
||||
[
|
||||
c64(FRAC_1_SQRT_2, 0.),
|
||||
c64(0., 0.),
|
||||
c64(0., -FRAC_1_SQRT_2),
|
||||
c64(0., 0.),
|
||||
],
|
||||
[
|
||||
c64(0., 0.),
|
||||
c64(0., FRAC_1_SQRT_2),
|
||||
c64(0., 0.),
|
||||
c64(FRAC_1_SQRT_2, 0.),
|
||||
],
|
||||
[
|
||||
c64(0., -FRAC_1_SQRT_2),
|
||||
c64(0., 0.),
|
||||
c64(FRAC_1_SQRT_2, 0.),
|
||||
c64(0., 0.),
|
||||
],
|
||||
];
|
||||
|
||||
pub static SWAP_GATE: [[Complex64; 4]; 4] = [
|
||||
[c64(1., 0.), c64(0., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(1., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(1., 0.), c64(0., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., 0.), c64(0., 0.), c64(1., 0.)],
|
||||
];
|
||||
|
||||
#[inline]
|
||||
pub fn global_phase_gate(theta: f64) -> [[Complex64; 1]; 1] {
|
||||
[[c64(0., theta).exp()]]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn phase_gate(lam: f64) -> [[Complex64; 2]; 2] {
|
||||
[
|
||||
[c64(1., 0.), c64(0., 0.)],
|
||||
[c64(0., 0.), c64(0., lam).exp()],
|
||||
]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn u_gate(theta: f64, phi: f64, lam: f64) -> [[Complex64; 2]; 2] {
|
||||
let cos = (theta / 2.).cos();
|
||||
let sin = (theta / 2.).sin();
|
||||
[
|
||||
[c64(cos, 0.), (-c64(0., lam).exp()) * sin],
|
||||
[c64(0., phi).exp() * sin, c64(0., phi + lam).exp() * cos],
|
||||
]
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
// This code is part of Qiskit.
|
||||
//
|
||||
// (C) Copyright IBM 2024
|
||||
//
|
||||
// This code is licensed under the Apache License, Version 2.0. You may
|
||||
// obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// Any modifications or derivative works of this code must retain this
|
||||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
// This module contains objects imported from Python that are reused. These are
|
||||
// typically data model classes that are used to identify an object, or for
|
||||
// python side casting
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::sync::GILOnceCell;
|
||||
|
||||
use crate::operations::{StandardGate, STANDARD_GATE_SIZE};
|
||||
|
||||
/// Helper wrapper around `GILOnceCell` instances that are just intended to store a Python object
|
||||
/// that is lazily imported.
|
||||
pub struct ImportOnceCell {
|
||||
module: &'static str,
|
||||
object: &'static str,
|
||||
cell: GILOnceCell<Py<PyAny>>,
|
||||
}
|
||||
|
||||
impl ImportOnceCell {
|
||||
const fn new(module: &'static str, object: &'static str) -> Self {
|
||||
Self {
|
||||
module,
|
||||
object,
|
||||
cell: GILOnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the underlying GIL-independent reference to the contained object, importing if
|
||||
/// required.
|
||||
#[inline]
|
||||
pub fn get(&self, py: Python) -> &Py<PyAny> {
|
||||
self.cell.get_or_init(py, || {
|
||||
py.import_bound(self.module)
|
||||
.unwrap()
|
||||
.getattr(self.object)
|
||||
.unwrap()
|
||||
.unbind()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a GIL-bound reference to the contained object, importing if required.
|
||||
#[inline]
|
||||
pub fn get_bound<'py>(&self, py: Python<'py>) -> &Bound<'py, PyAny> {
|
||||
self.get(py).bind(py)
|
||||
}
|
||||
}
|
||||
|
||||
pub static BUILTIN_LIST: ImportOnceCell = ImportOnceCell::new("builtins", "list");
|
||||
pub static OPERATION: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.operation", "Operation");
|
||||
pub static INSTRUCTION: ImportOnceCell =
|
||||
ImportOnceCell::new("qiskit.circuit.instruction", "Instruction");
|
||||
pub static GATE: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.gate", "Gate");
|
||||
pub static QUBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.quantumregister", "Qubit");
|
||||
pub static CLBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.classicalregister", "Clbit");
|
||||
pub static PARAMETER_EXPRESSION: ImportOnceCell =
|
||||
ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression");
|
||||
pub static QUANTUM_CIRCUIT: ImportOnceCell =
|
||||
ImportOnceCell::new("qiskit.circuit.quantumcircuit", "QuantumCircuit");
|
||||
pub static SINGLETON_GATE: ImportOnceCell =
|
||||
ImportOnceCell::new("qiskit.circuit.singleton", "SingletonGate");
|
||||
pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell =
|
||||
ImportOnceCell::new("qiskit.circuit.singleton", "SingletonControlledGate");
|
||||
|
||||
/// A mapping from the enum variant in crate::operations::StandardGate to the python
|
||||
/// module path and class name to import it. This is used to populate the conversion table
|
||||
/// when a gate is added directly via the StandardGate path and there isn't a Python object
|
||||
/// to poll the _standard_gate attribute for.
|
||||
///
|
||||
/// NOTE: the order here is significant it must match the StandardGate variant's number must match
|
||||
/// index of it's entry in this table. This is all done statically for performance
|
||||
static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [
|
||||
// ZGate = 0
|
||||
["qiskit.circuit.library.standard_gates.z", "ZGate"],
|
||||
// YGate = 1
|
||||
["qiskit.circuit.library.standard_gates.y", "YGate"],
|
||||
// XGate = 2
|
||||
["qiskit.circuit.library.standard_gates.x", "XGate"],
|
||||
// CZGate = 3
|
||||
["qiskit.circuit.library.standard_gates.z", "CZGate"],
|
||||
// CYGate = 4
|
||||
["qiskit.circuit.library.standard_gates.y", "CYGate"],
|
||||
// CXGate = 5
|
||||
["qiskit.circuit.library.standard_gates.x", "CXGate"],
|
||||
// CCXGate = 6
|
||||
["qiskit.circuit.library.standard_gates.x", "CCXGate"],
|
||||
// RXGate = 7
|
||||
["qiskit.circuit.library.standard_gates.rx", "RXGate"],
|
||||
// RYGate = 8
|
||||
["qiskit.circuit.library.standard_gates.ry", "RYGate"],
|
||||
// RZGate = 9
|
||||
["qiskit.circuit.library.standard_gates.rz", "RZGate"],
|
||||
// ECRGate = 10
|
||||
["qiskit.circuit.library.standard_gates.ecr", "ECRGate"],
|
||||
// SwapGate = 11
|
||||
["qiskit.circuit.library.standard_gates.swap", "SwapGate"],
|
||||
// SXGate = 12
|
||||
["qiskit.circuit.library.standard_gates.sx", "SXGate"],
|
||||
// GlobalPhaseGate = 13
|
||||
[
|
||||
"qiskit.circuit.library.standard_gates.global_phase",
|
||||
"GlobalPhaseGate",
|
||||
],
|
||||
// IGate = 14
|
||||
["qiskit.circuit.library.standard_gates.i", "IGate"],
|
||||
// HGate = 15
|
||||
["qiskit.circuit.library.standard_gates.h", "HGate"],
|
||||
// PhaseGate = 16
|
||||
["qiskit.circuit.library.standard_gates.p", "PhaseGate"],
|
||||
// UGate = 17
|
||||
["qiskit.circuit.library.standard_gates.u", "UGate"],
|
||||
];
|
||||
|
||||
/// A mapping from the enum variant in crate::operations::StandardGate to the python object for the
|
||||
/// class that matches it. This is typically used when we need to convert from the internal rust
|
||||
/// representation to a Python object for a python user to interact with.
|
||||
///
|
||||
/// NOTE: the order here is significant it must match the StandardGate variant's number must match
|
||||
/// index of it's entry in this table. This is all done statically for performance
|
||||
static mut STDGATE_PYTHON_GATES: GILOnceCell<[Option<PyObject>; STANDARD_GATE_SIZE]> =
|
||||
GILOnceCell::new();
|
||||
|
||||
#[inline]
|
||||
pub fn populate_std_gate_map(py: Python, rs_gate: StandardGate, py_gate: PyObject) {
|
||||
let gate_map = unsafe {
|
||||
match STDGATE_PYTHON_GATES.get_mut() {
|
||||
Some(gate_map) => gate_map,
|
||||
None => {
|
||||
let array: [Option<PyObject>; STANDARD_GATE_SIZE] = std::array::from_fn(|_| None);
|
||||
STDGATE_PYTHON_GATES.set(py, array).unwrap();
|
||||
STDGATE_PYTHON_GATES.get_mut().unwrap()
|
||||
}
|
||||
}
|
||||
};
|
||||
let gate_cls = &gate_map[rs_gate as usize];
|
||||
if gate_cls.is_none() {
|
||||
gate_map[rs_gate as usize] = Some(py_gate.clone_ref(py));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_std_gate_class(py: Python, rs_gate: StandardGate) -> PyResult<PyObject> {
|
||||
let gate_map =
|
||||
unsafe { STDGATE_PYTHON_GATES.get_or_init(py, || std::array::from_fn(|_| None)) };
|
||||
let gate = &gate_map[rs_gate as usize];
|
||||
let populate = gate.is_none();
|
||||
let out_gate = match gate {
|
||||
Some(gate) => gate.clone_ref(py),
|
||||
None => {
|
||||
let [py_mod, py_class] = STDGATE_IMPORT_PATHS[rs_gate as usize];
|
||||
py.import_bound(py_mod)?.getattr(py_class)?.unbind()
|
||||
}
|
||||
};
|
||||
if populate {
|
||||
populate_std_gate_map(py, rs_gate, out_gate.clone_ref(py));
|
||||
}
|
||||
Ok(out_gate)
|
||||
}
|
|
@ -10,11 +10,13 @@
|
|||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use pyo3::{IntoPy, PyObject, Python};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use pyo3::exceptions::PyRuntimeError;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Index(u32);
|
||||
|
||||
|
@ -42,6 +44,12 @@ impl IntoPy<PyObject> for Index {
|
|||
|
||||
pub struct CacheFullError;
|
||||
|
||||
impl From<CacheFullError> for PyErr {
|
||||
fn from(_: CacheFullError) -> Self {
|
||||
PyRuntimeError::new_err("The bit operands cache is full!")
|
||||
}
|
||||
}
|
||||
|
||||
/// An append-only data structure for interning generic
|
||||
/// Rust types.
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -13,10 +13,13 @@
|
|||
pub mod circuit_data;
|
||||
pub mod circuit_instruction;
|
||||
pub mod dag_node;
|
||||
pub mod gate_matrix;
|
||||
pub mod imports;
|
||||
pub mod operations;
|
||||
pub mod parameter_table;
|
||||
|
||||
mod bit_data;
|
||||
mod interner;
|
||||
mod packed_instruction;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PySlice;
|
||||
|
@ -33,9 +36,9 @@ pub enum SliceOrInt<'a> {
|
|||
|
||||
pub type BitType = u32;
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct Qubit(BitType);
|
||||
pub struct Qubit(pub BitType);
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct Clbit(BitType);
|
||||
pub struct Clbit(pub BitType);
|
||||
|
||||
impl From<BitType> for Qubit {
|
||||
fn from(value: BitType) -> Self {
|
||||
|
@ -69,5 +72,9 @@ pub fn circuit(m: Bound<PyModule>) -> PyResult<()> {
|
|||
m.add_class::<dag_node::DAGOutNode>()?;
|
||||
m.add_class::<dag_node::DAGOpNode>()?;
|
||||
m.add_class::<circuit_instruction::CircuitInstruction>()?;
|
||||
m.add_class::<operations::StandardGate>()?;
|
||||
m.add_class::<operations::PyInstruction>()?;
|
||||
m.add_class::<operations::PyGate>()?;
|
||||
m.add_class::<operations::PyOperation>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,786 @@
|
|||
// This code is part of Qiskit.
|
||||
//
|
||||
// (C) Copyright IBM 2024
|
||||
//
|
||||
// This code is licensed under the Apache License, Version 2.0. You may
|
||||
// obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// Any modifications or derivative works of this code must retain this
|
||||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use crate::circuit_data::CircuitData;
|
||||
use crate::imports::{PARAMETER_EXPRESSION, QUANTUM_CIRCUIT};
|
||||
use crate::{gate_matrix, Qubit};
|
||||
|
||||
use ndarray::{aview2, Array2};
|
||||
use num_complex::Complex64;
|
||||
use numpy::IntoPyArray;
|
||||
use numpy::PyReadonlyArray2;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{intern, IntoPy, Python};
|
||||
use smallvec::smallvec;
|
||||
|
||||
/// Valid types for an operation field in a CircuitInstruction
|
||||
///
|
||||
/// These are basically the types allowed in a QuantumCircuit
|
||||
#[derive(FromPyObject, Clone, Debug)]
|
||||
pub enum OperationType {
|
||||
Standard(StandardGate),
|
||||
Instruction(PyInstruction),
|
||||
Gate(PyGate),
|
||||
Operation(PyOperation),
|
||||
}
|
||||
|
||||
impl Operation for OperationType {
|
||||
fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Standard(op) => op.name(),
|
||||
Self::Gate(op) => op.name(),
|
||||
Self::Instruction(op) => op.name(),
|
||||
Self::Operation(op) => op.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_qubits(&self) -> u32 {
|
||||
match self {
|
||||
Self::Standard(op) => op.num_qubits(),
|
||||
Self::Gate(op) => op.num_qubits(),
|
||||
Self::Instruction(op) => op.num_qubits(),
|
||||
Self::Operation(op) => op.num_qubits(),
|
||||
}
|
||||
}
|
||||
fn num_clbits(&self) -> u32 {
|
||||
match self {
|
||||
Self::Standard(op) => op.num_clbits(),
|
||||
Self::Gate(op) => op.num_clbits(),
|
||||
Self::Instruction(op) => op.num_clbits(),
|
||||
Self::Operation(op) => op.num_clbits(),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_params(&self) -> u32 {
|
||||
match self {
|
||||
Self::Standard(op) => op.num_params(),
|
||||
Self::Gate(op) => op.num_params(),
|
||||
Self::Instruction(op) => op.num_params(),
|
||||
Self::Operation(op) => op.num_params(),
|
||||
}
|
||||
}
|
||||
fn matrix(&self, params: &[Param]) -> Option<Array2<Complex64>> {
|
||||
match self {
|
||||
Self::Standard(op) => op.matrix(params),
|
||||
Self::Gate(op) => op.matrix(params),
|
||||
Self::Instruction(op) => op.matrix(params),
|
||||
Self::Operation(op) => op.matrix(params),
|
||||
}
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> bool {
|
||||
match self {
|
||||
Self::Standard(op) => op.control_flow(),
|
||||
Self::Gate(op) => op.control_flow(),
|
||||
Self::Instruction(op) => op.control_flow(),
|
||||
Self::Operation(op) => op.control_flow(),
|
||||
}
|
||||
}
|
||||
|
||||
fn definition(&self, params: &[Param]) -> Option<CircuitData> {
|
||||
match self {
|
||||
Self::Standard(op) => op.definition(params),
|
||||
Self::Gate(op) => op.definition(params),
|
||||
Self::Instruction(op) => op.definition(params),
|
||||
Self::Operation(op) => op.definition(params),
|
||||
}
|
||||
}
|
||||
|
||||
fn standard_gate(&self) -> Option<StandardGate> {
|
||||
match self {
|
||||
Self::Standard(op) => op.standard_gate(),
|
||||
Self::Gate(op) => op.standard_gate(),
|
||||
Self::Instruction(op) => op.standard_gate(),
|
||||
Self::Operation(op) => op.standard_gate(),
|
||||
}
|
||||
}
|
||||
|
||||
fn directive(&self) -> bool {
|
||||
match self {
|
||||
Self::Standard(op) => op.directive(),
|
||||
Self::Gate(op) => op.directive(),
|
||||
Self::Instruction(op) => op.directive(),
|
||||
Self::Operation(op) => op.directive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for generic circuit operations these define the common attributes
|
||||
/// needed for something to be addable to the circuit struct
|
||||
pub trait Operation {
|
||||
fn name(&self) -> &str;
|
||||
fn num_qubits(&self) -> u32;
|
||||
fn num_clbits(&self) -> u32;
|
||||
fn num_params(&self) -> u32;
|
||||
fn control_flow(&self) -> bool;
|
||||
fn matrix(&self, params: &[Param]) -> Option<Array2<Complex64>>;
|
||||
fn definition(&self, params: &[Param]) -> Option<CircuitData>;
|
||||
fn standard_gate(&self) -> Option<StandardGate>;
|
||||
fn directive(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Param {
|
||||
ParameterExpression(PyObject),
|
||||
Float(f64),
|
||||
Obj(PyObject),
|
||||
}
|
||||
|
||||
impl<'py> FromPyObject<'py> for Param {
|
||||
fn extract_bound(b: &Bound<'py, PyAny>) -> Result<Self, PyErr> {
|
||||
Ok(
|
||||
if b.is_instance(PARAMETER_EXPRESSION.get_bound(b.py()))?
|
||||
|| b.is_instance(QUANTUM_CIRCUIT.get_bound(b.py()))?
|
||||
{
|
||||
Param::ParameterExpression(b.clone().unbind())
|
||||
} else if let Ok(val) = b.extract::<f64>() {
|
||||
Param::Float(val)
|
||||
} else {
|
||||
Param::Obj(b.clone().unbind())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPy<PyObject> for Param {
|
||||
fn into_py(self, py: Python) -> PyObject {
|
||||
match &self {
|
||||
Self::Float(val) => val.to_object(py),
|
||||
Self::ParameterExpression(val) => val.clone_ref(py),
|
||||
Self::Obj(val) => val.clone_ref(py),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for Param {
|
||||
fn to_object(&self, py: Python) -> PyObject {
|
||||
match self {
|
||||
Self::Float(val) => val.to_object(py),
|
||||
Self::ParameterExpression(val) => val.clone_ref(py),
|
||||
Self::Obj(val) => val.clone_ref(py),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)]
|
||||
#[pyclass(module = "qiskit._accelerate.circuit")]
|
||||
pub enum StandardGate {
|
||||
ZGate = 0,
|
||||
YGate = 1,
|
||||
XGate = 2,
|
||||
CZGate = 3,
|
||||
CYGate = 4,
|
||||
CXGate = 5,
|
||||
CCXGate = 6,
|
||||
RXGate = 7,
|
||||
RYGate = 8,
|
||||
RZGate = 9,
|
||||
ECRGate = 10,
|
||||
SwapGate = 11,
|
||||
SXGate = 12,
|
||||
GlobalPhaseGate = 13,
|
||||
IGate = 14,
|
||||
HGate = 15,
|
||||
PhaseGate = 16,
|
||||
UGate = 17,
|
||||
}
|
||||
|
||||
static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] =
|
||||
[1, 1, 1, 2, 2, 2, 3, 1, 1, 1, 2, 2, 1, 0, 1, 1, 1, 1];
|
||||
|
||||
static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] =
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 3];
|
||||
|
||||
static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [
|
||||
"z",
|
||||
"y",
|
||||
"x",
|
||||
"cz",
|
||||
"cy",
|
||||
"cx",
|
||||
"ccx",
|
||||
"rx",
|
||||
"ry",
|
||||
"rz",
|
||||
"ecr",
|
||||
"swap",
|
||||
"sx",
|
||||
"global_phase",
|
||||
"id",
|
||||
"h",
|
||||
"p",
|
||||
"u",
|
||||
];
|
||||
|
||||
#[pymethods]
|
||||
impl StandardGate {
|
||||
pub fn copy(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
// These pymethods are for testing:
|
||||
pub fn _to_matrix(&self, py: Python, params: Vec<Param>) -> Option<PyObject> {
|
||||
self.matrix(¶ms)
|
||||
.map(|x| x.into_pyarray_bound(py).into())
|
||||
}
|
||||
|
||||
pub fn _num_params(&self) -> u32 {
|
||||
self.num_params()
|
||||
}
|
||||
|
||||
pub fn _get_definition(&self, params: Vec<Param>) -> Option<CircuitData> {
|
||||
self.definition(¶ms)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
pub fn get_num_qubits(&self) -> u32 {
|
||||
self.num_qubits()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
pub fn get_num_clbits(&self) -> u32 {
|
||||
self.num_clbits()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
pub fn get_num_params(&self) -> u32 {
|
||||
self.num_params()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
pub fn get_name(&self) -> &str {
|
||||
self.name()
|
||||
}
|
||||
}
|
||||
|
||||
// This must be kept up-to-date with `StandardGate` when adding or removing
|
||||
// gates from the enum
|
||||
//
|
||||
// Remove this when std::mem::variant_count() is stabilized (see
|
||||
// https://github.com/rust-lang/rust/issues/73662 )
|
||||
pub const STANDARD_GATE_SIZE: usize = 18;
|
||||
|
||||
impl Operation for StandardGate {
|
||||
fn name(&self) -> &str {
|
||||
STANDARD_GATE_NAME[*self as usize]
|
||||
}
|
||||
|
||||
fn num_qubits(&self) -> u32 {
|
||||
STANDARD_GATE_NUM_QUBITS[*self as usize]
|
||||
}
|
||||
|
||||
fn num_params(&self) -> u32 {
|
||||
STANDARD_GATE_NUM_PARAMS[*self as usize]
|
||||
}
|
||||
|
||||
fn num_clbits(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn control_flow(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn directive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn matrix(&self, params: &[Param]) -> Option<Array2<Complex64>> {
|
||||
match self {
|
||||
Self::ZGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::Z_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::YGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::Y_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::XGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::X_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::CZGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::CYGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::CY_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::CXGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::CX_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::CCXGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::RXGate => match params {
|
||||
[Param::Float(theta)] => Some(aview2(&gate_matrix::rx_gate(*theta)).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::RYGate => match params {
|
||||
[Param::Float(theta)] => Some(aview2(&gate_matrix::ry_gate(*theta)).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::RZGate => match params {
|
||||
[Param::Float(theta)] => Some(aview2(&gate_matrix::rz_gate(*theta)).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::ECRGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::SwapGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::SXGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::SX_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::GlobalPhaseGate => match params {
|
||||
[Param::Float(theta)] => {
|
||||
Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Self::IGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::HGate => match params {
|
||||
[] => Some(aview2(&gate_matrix::H_GATE).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::PhaseGate => match params {
|
||||
[Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
Self::UGate => match params {
|
||||
[Param::Float(theta), Param::Float(phi), Param::Float(lam)] => {
|
||||
Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned())
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn definition(&self, params: &[Param]) -> Option<CircuitData> {
|
||||
match self {
|
||||
Self::ZGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::PhaseGate,
|
||||
smallvec![Param::Float(PI)],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::YGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::UGate,
|
||||
smallvec![
|
||||
Param::Float(PI),
|
||||
Param::Float(PI / 2.),
|
||||
Param::Float(PI / 2.),
|
||||
],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::XGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::UGate,
|
||||
smallvec![Param::Float(PI), Param::Float(0.), Param::Float(PI)],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::CZGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
let q1 = smallvec![Qubit(1)];
|
||||
let q0_1 = smallvec![Qubit(0), Qubit(1)];
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
2,
|
||||
[
|
||||
(Self::HGate, smallvec![], q1.clone()),
|
||||
(Self::CXGate, smallvec![], q0_1),
|
||||
(Self::HGate, smallvec![], q1),
|
||||
],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::CYGate => todo!("Add when we have S and S dagger"),
|
||||
Self::CXGate => None,
|
||||
Self::CCXGate => todo!("Add when we have T and TDagger"),
|
||||
Self::RXGate => todo!("Add when we have R"),
|
||||
Self::RYGate => todo!("Add when we have R"),
|
||||
Self::RZGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
match ¶ms[0] {
|
||||
Param::Float(theta) => Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::PhaseGate,
|
||||
smallvec![Param::Float(*theta)],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
Param::Float(-0.5 * theta),
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
),
|
||||
Param::ParameterExpression(theta) => Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::PhaseGate,
|
||||
smallvec![Param::ParameterExpression(theta.clone_ref(py))],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
Param::ParameterExpression(
|
||||
theta
|
||||
.call_method1(py, intern!(py, "__rmul__"), (-0.5,))
|
||||
.expect("Parameter expression for global phase failed"),
|
||||
),
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
),
|
||||
Param::Obj(_) => unreachable!(),
|
||||
}
|
||||
}),
|
||||
Self::ECRGate => todo!("Add when we have RZX"),
|
||||
Self::SwapGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
2,
|
||||
[
|
||||
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
|
||||
(Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]),
|
||||
(Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]),
|
||||
],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::SXGate => todo!("Add when we have S dagger"),
|
||||
Self::GlobalPhaseGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(py, 0, [], params[0].clone())
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::IGate => None,
|
||||
Self::HGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::UGate,
|
||||
smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::PhaseGate => Python::with_gil(|py| -> Option<CircuitData> {
|
||||
Some(
|
||||
CircuitData::from_standard_gates(
|
||||
py,
|
||||
1,
|
||||
[(
|
||||
Self::UGate,
|
||||
smallvec![Param::Float(0.), Param::Float(0.), params[0].clone()],
|
||||
smallvec![Qubit(0)],
|
||||
)],
|
||||
FLOAT_ZERO,
|
||||
)
|
||||
.expect("Unexpected Qiskit python bug"),
|
||||
)
|
||||
}),
|
||||
Self::UGate => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn standard_gate(&self) -> Option<StandardGate> {
|
||||
Some(*self)
|
||||
}
|
||||
}
|
||||
|
||||
const FLOAT_ZERO: Param = Param::Float(0.0);
|
||||
|
||||
/// This class is used to wrap a Python side Instruction that is not in the standard library
|
||||
#[derive(Clone, Debug)]
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub struct PyInstruction {
|
||||
pub qubits: u32,
|
||||
pub clbits: u32,
|
||||
pub params: u32,
|
||||
pub op_name: String,
|
||||
pub instruction: PyObject,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyInstruction {
|
||||
#[new]
|
||||
fn new(op_name: String, qubits: u32, clbits: u32, params: u32, instruction: PyObject) -> Self {
|
||||
PyInstruction {
|
||||
qubits,
|
||||
clbits,
|
||||
params,
|
||||
op_name,
|
||||
instruction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation for PyInstruction {
|
||||
fn name(&self) -> &str {
|
||||
self.op_name.as_str()
|
||||
}
|
||||
fn num_qubits(&self) -> u32 {
|
||||
self.qubits
|
||||
}
|
||||
fn num_clbits(&self) -> u32 {
|
||||
self.clbits
|
||||
}
|
||||
fn num_params(&self) -> u32 {
|
||||
self.params
|
||||
}
|
||||
fn control_flow(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn matrix(&self, _params: &[Param]) -> Option<Array2<Complex64>> {
|
||||
None
|
||||
}
|
||||
fn definition(&self, _params: &[Param]) -> Option<CircuitData> {
|
||||
Python::with_gil(|py| -> Option<CircuitData> {
|
||||
match self.instruction.getattr(py, intern!(py, "definition")) {
|
||||
Ok(definition) => {
|
||||
let res: Option<PyObject> = definition.call0(py).ok()?.extract(py).ok();
|
||||
match res {
|
||||
Some(x) => {
|
||||
let out: CircuitData =
|
||||
x.getattr(py, intern!(py, "data")).ok()?.extract(py).ok()?;
|
||||
Some(out)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn standard_gate(&self) -> Option<StandardGate> {
|
||||
None
|
||||
}
|
||||
|
||||
fn directive(&self) -> bool {
|
||||
Python::with_gil(|py| -> bool {
|
||||
match self.instruction.getattr(py, intern!(py, "_directive")) {
|
||||
Ok(directive) => {
|
||||
let res: bool = directive.extract(py).unwrap();
|
||||
res
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This class is used to wrap a Python side Gate that is not in the standard library
|
||||
#[derive(Clone, Debug)]
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub struct PyGate {
|
||||
pub qubits: u32,
|
||||
pub clbits: u32,
|
||||
pub params: u32,
|
||||
pub op_name: String,
|
||||
pub gate: PyObject,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyGate {
|
||||
#[new]
|
||||
fn new(op_name: String, qubits: u32, clbits: u32, params: u32, gate: PyObject) -> Self {
|
||||
PyGate {
|
||||
qubits,
|
||||
clbits,
|
||||
params,
|
||||
op_name,
|
||||
gate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation for PyGate {
|
||||
fn name(&self) -> &str {
|
||||
self.op_name.as_str()
|
||||
}
|
||||
fn num_qubits(&self) -> u32 {
|
||||
self.qubits
|
||||
}
|
||||
fn num_clbits(&self) -> u32 {
|
||||
self.clbits
|
||||
}
|
||||
fn num_params(&self) -> u32 {
|
||||
self.params
|
||||
}
|
||||
fn control_flow(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn matrix(&self, _params: &[Param]) -> Option<Array2<Complex64>> {
|
||||
Python::with_gil(|py| -> Option<Array2<Complex64>> {
|
||||
match self.gate.getattr(py, intern!(py, "to_matrix")) {
|
||||
Ok(to_matrix) => {
|
||||
let res: Option<PyObject> = to_matrix.call0(py).ok()?.extract(py).ok();
|
||||
match res {
|
||||
Some(x) => {
|
||||
let array: PyReadonlyArray2<Complex64> = x.extract(py).ok()?;
|
||||
Some(array.as_array().to_owned())
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn definition(&self, _params: &[Param]) -> Option<CircuitData> {
|
||||
Python::with_gil(|py| -> Option<CircuitData> {
|
||||
match self.gate.getattr(py, intern!(py, "definition")) {
|
||||
Ok(definition) => {
|
||||
let res: Option<PyObject> = definition.call0(py).ok()?.extract(py).ok();
|
||||
match res {
|
||||
Some(x) => {
|
||||
let out: CircuitData =
|
||||
x.getattr(py, intern!(py, "data")).ok()?.extract(py).ok()?;
|
||||
Some(out)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn standard_gate(&self) -> Option<StandardGate> {
|
||||
Python::with_gil(|py| -> Option<StandardGate> {
|
||||
match self.gate.getattr(py, intern!(py, "_standard_gate")) {
|
||||
Ok(stdgate) => match stdgate.extract(py) {
|
||||
Ok(out_gate) => out_gate,
|
||||
Err(_) => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
fn directive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This class is used to wrap a Python side Operation that is not in the standard library
|
||||
#[derive(Clone, Debug)]
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub struct PyOperation {
|
||||
pub qubits: u32,
|
||||
pub clbits: u32,
|
||||
pub params: u32,
|
||||
pub op_name: String,
|
||||
pub operation: PyObject,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyOperation {
|
||||
#[new]
|
||||
fn new(op_name: String, qubits: u32, clbits: u32, params: u32, operation: PyObject) -> Self {
|
||||
PyOperation {
|
||||
qubits,
|
||||
clbits,
|
||||
params,
|
||||
op_name,
|
||||
operation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation for PyOperation {
|
||||
fn name(&self) -> &str {
|
||||
self.op_name.as_str()
|
||||
}
|
||||
fn num_qubits(&self) -> u32 {
|
||||
self.qubits
|
||||
}
|
||||
fn num_clbits(&self) -> u32 {
|
||||
self.clbits
|
||||
}
|
||||
fn num_params(&self) -> u32 {
|
||||
self.params
|
||||
}
|
||||
fn control_flow(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn matrix(&self, _params: &[Param]) -> Option<Array2<Complex64>> {
|
||||
None
|
||||
}
|
||||
fn definition(&self, _params: &[Param]) -> Option<CircuitData> {
|
||||
None
|
||||
}
|
||||
fn standard_gate(&self) -> Option<StandardGate> {
|
||||
None
|
||||
}
|
||||
|
||||
fn directive(&self) -> bool {
|
||||
Python::with_gil(|py| -> bool {
|
||||
match self.operation.getattr(py, intern!(py, "_directive")) {
|
||||
Ok(directive) => {
|
||||
let res: bool = directive.extract(py).unwrap();
|
||||
res
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// This code is part of Qiskit.
|
||||
//
|
||||
// (C) Copyright IBM 2024
|
||||
//
|
||||
// This code is licensed under the Apache License, Version 2.0. You may
|
||||
// obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// Any modifications or derivative works of this code must retain this
|
||||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use crate::interner::Index;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// Private type used to store instructions with interned arg lists.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PackedInstruction {
|
||||
/// The Python-side operation instance.
|
||||
pub op: PyObject,
|
||||
/// The index under which the interner has stored `qubits`.
|
||||
pub qubits_id: Index,
|
||||
/// The index under which the interner has stored `clbits`.
|
||||
pub clbits_id: Index,
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
// This code is part of Qiskit.
|
||||
//
|
||||
// (C) Copyright IBM 2024
|
||||
//
|
||||
// This code is licensed under the Apache License, Version 2.0. You may
|
||||
// obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// Any modifications or derivative works of this code must retain this
|
||||
// copyright notice, and modified files need to carry a notice indicating
|
||||
// that they have been altered from the originals.
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{import_exception, intern, PyObject};
|
||||
|
||||
import_exception!(qiskit.circuit.exceptions, CircuitError);
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
|
||||
/// The index value in a `ParamEntry` that indicates the global phase.
|
||||
pub const GLOBAL_PHASE_INDEX: usize = usize::MAX;
|
||||
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub(crate) struct ParamEntryKeys {
|
||||
keys: Vec<(usize, usize)>,
|
||||
iter_pos: usize,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ParamEntryKeys {
|
||||
fn __iter__(slf: PyRef<Self>) -> Py<ParamEntryKeys> {
|
||||
slf.into()
|
||||
}
|
||||
|
||||
fn __next__(mut slf: PyRefMut<Self>) -> Option<(usize, usize)> {
|
||||
if slf.iter_pos < slf.keys.len() {
|
||||
let res = Some(slf.keys[slf.iter_pos]);
|
||||
slf.iter_pos += 1;
|
||||
res
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub(crate) struct ParamEntry {
|
||||
/// Mapping of tuple of instruction index (in CircuitData) and parameter index to the actual
|
||||
/// parameter object
|
||||
pub index_ids: HashSet<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl ParamEntry {
|
||||
pub fn add(&mut self, inst_index: usize, param_index: usize) {
|
||||
self.index_ids.insert((inst_index, param_index));
|
||||
}
|
||||
|
||||
pub fn discard(&mut self, inst_index: usize, param_index: usize) {
|
||||
self.index_ids.remove(&(inst_index, param_index));
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ParamEntry {
|
||||
#[new]
|
||||
pub fn new(inst_index: usize, param_index: usize) -> Self {
|
||||
ParamEntry {
|
||||
index_ids: HashSet::from([(inst_index, param_index)]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn __len__(&self) -> usize {
|
||||
self.index_ids.len()
|
||||
}
|
||||
|
||||
pub fn __contains__(&self, key: (usize, usize)) -> bool {
|
||||
self.index_ids.contains(&key)
|
||||
}
|
||||
|
||||
pub fn __iter__(&self) -> ParamEntryKeys {
|
||||
ParamEntryKeys {
|
||||
keys: self.index_ids.iter().copied().collect(),
|
||||
iter_pos: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[pyclass(freelist = 20, module = "qiskit._accelerate.circuit")]
|
||||
pub(crate) struct ParamTable {
|
||||
/// Mapping of parameter uuid (as an int) to the Parameter Entry
|
||||
pub table: HashMap<u128, ParamEntry>,
|
||||
/// Mapping of parameter name to uuid as an int
|
||||
pub names: HashMap<String, u128>,
|
||||
/// Mapping of uuid to a parameter object
|
||||
pub uuid_map: HashMap<u128, PyObject>,
|
||||
}
|
||||
|
||||
impl ParamTable {
|
||||
pub fn insert(&mut self, py: Python, parameter: PyObject, entry: ParamEntry) -> PyResult<()> {
|
||||
let uuid: u128 = parameter
|
||||
.getattr(py, intern!(py, "_uuid"))?
|
||||
.getattr(py, intern!(py, "int"))?
|
||||
.extract(py)?;
|
||||
let name: String = parameter.getattr(py, intern!(py, "name"))?.extract(py)?;
|
||||
|
||||
if self.names.contains_key(&name) && !self.table.contains_key(&uuid) {
|
||||
return Err(CircuitError::new_err(format!(
|
||||
"Name conflict on adding parameter: {}",
|
||||
name
|
||||
)));
|
||||
}
|
||||
self.table.insert(uuid, entry);
|
||||
self.names.insert(name, uuid);
|
||||
self.uuid_map.insert(uuid, parameter);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn discard_references(
|
||||
&mut self,
|
||||
uuid: u128,
|
||||
inst_index: usize,
|
||||
param_index: usize,
|
||||
name: String,
|
||||
) {
|
||||
if let Some(refs) = self.table.get_mut(&uuid) {
|
||||
if refs.__len__() == 1 {
|
||||
self.table.remove(&uuid);
|
||||
self.names.remove(&name);
|
||||
self.uuid_map.remove(&uuid);
|
||||
} else {
|
||||
refs.discard(inst_index, param_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ParamTable {
|
||||
#[new]
|
||||
pub fn new() -> Self {
|
||||
ParamTable {
|
||||
table: HashMap::new(),
|
||||
names: HashMap::new(),
|
||||
uuid_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.table.clear();
|
||||
self.names.clear();
|
||||
self.uuid_map.clear();
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, key: u128, name: String) -> Option<ParamEntry> {
|
||||
self.names.remove(&name);
|
||||
self.uuid_map.remove(&key);
|
||||
self.table.remove(&key)
|
||||
}
|
||||
|
||||
fn set(&mut self, uuid: u128, name: String, param: PyObject, refs: ParamEntry) {
|
||||
self.names.insert(name, uuid);
|
||||
self.table.insert(uuid, refs);
|
||||
self.uuid_map.insert(uuid, param);
|
||||
}
|
||||
|
||||
pub fn get_param_from_name(&self, py: Python, name: String) -> Option<PyObject> {
|
||||
self.names
|
||||
.get(&name)
|
||||
.map(|x| self.uuid_map.get(x).map(|y| y.clone_ref(py)))?
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ crate-type = ["cdylib"]
|
|||
# crates as standalone binaries, executables, we need `libpython` to be linked in, so we make the
|
||||
# feature a default, and run `cargo test --no-default-features` to turn it off.
|
||||
default = ["pyo3/extension-module"]
|
||||
cache_pygates = ["pyo3/extension-module", "qiskit-circuit/cache_pygates"]
|
||||
|
||||
[dependencies]
|
||||
pyo3.workspace = true
|
||||
|
|
|
@ -57,7 +57,9 @@ class CircuitScopeInterface(abc.ABC):
|
|||
"""Indexable view onto the :class:`.CircuitInstruction`s backing this scope."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def append(self, instruction: CircuitInstruction) -> CircuitInstruction:
|
||||
def append(
|
||||
self, instruction: CircuitInstruction, *, _standard_gate=False
|
||||
) -> CircuitInstruction:
|
||||
"""Low-level 'append' primitive; this may assume that the qubits, clbits and operation are
|
||||
all valid for the circuit.
|
||||
|
||||
|
@ -420,7 +422,9 @@ class ControlFlowBuilderBlock(CircuitScopeInterface):
|
|||
" because it is not in a loop."
|
||||
)
|
||||
|
||||
def append(self, instruction: CircuitInstruction) -> CircuitInstruction:
|
||||
def append(
|
||||
self, instruction: CircuitInstruction, *, _standard_gate: bool = False
|
||||
) -> CircuitInstruction:
|
||||
if self._forbidden_message is not None:
|
||||
raise CircuitError(self._forbidden_message)
|
||||
if not self._allow_jumps:
|
||||
|
|
|
@ -58,6 +58,7 @@ class Instruction(Operation):
|
|||
# Class attribute to treat like barrier for transpiler, unroller, drawer
|
||||
# NOTE: Using this attribute may change in the future (See issue # 5811)
|
||||
_directive = False
|
||||
_standard_gate = None
|
||||
|
||||
def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt", label=None):
|
||||
"""Create a new instruction.
|
||||
|
|
|
@ -140,13 +140,12 @@ class InstructionSet:
|
|||
)
|
||||
if self._requester is not None:
|
||||
classical = self._requester(classical)
|
||||
for instruction in self._instructions:
|
||||
for idx, instruction in enumerate(self._instructions):
|
||||
if isinstance(instruction, CircuitInstruction):
|
||||
updated = instruction.operation.c_if(classical, val)
|
||||
if updated is not instruction.operation:
|
||||
raise CircuitError(
|
||||
"SingletonGate instances can only be added to InstructionSet via _add_ref"
|
||||
)
|
||||
self._instructions[idx] = instruction.replace(
|
||||
operation=updated, condition=updated.condition
|
||||
)
|
||||
else:
|
||||
data, idx = instruction
|
||||
instruction = data[idx]
|
||||
|
|
|
@ -17,7 +17,7 @@ from abc import ABC, abstractmethod
|
|||
|
||||
from qiskit._accelerate.circuit import CircuitData
|
||||
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
|
||||
from qiskit.circuit.parametertable import ParameterTable, ParameterView
|
||||
from qiskit.circuit.parametertable import ParameterView
|
||||
|
||||
|
||||
class BlueprintCircuit(QuantumCircuit, ABC):
|
||||
|
@ -68,7 +68,6 @@ class BlueprintCircuit(QuantumCircuit, ABC):
|
|||
def _invalidate(self) -> None:
|
||||
"""Invalidate the current circuit build."""
|
||||
self._data = CircuitData(self._data.qubits, self._data.clbits)
|
||||
self._parameter_table = ParameterTable()
|
||||
self.global_phase = 0
|
||||
self._is_built = False
|
||||
|
||||
|
@ -88,7 +87,6 @@ class BlueprintCircuit(QuantumCircuit, ABC):
|
|||
self._ancillas = []
|
||||
self._qubit_indices = {}
|
||||
self._data = CircuitData(clbits=self._data.clbits)
|
||||
self._parameter_table = ParameterTable()
|
||||
self.global_phase = 0
|
||||
self._is_built = False
|
||||
|
||||
|
@ -122,10 +120,10 @@ class BlueprintCircuit(QuantumCircuit, ABC):
|
|||
self._build()
|
||||
return super().parameters
|
||||
|
||||
def _append(self, instruction, _qargs=None, _cargs=None):
|
||||
def _append(self, instruction, _qargs=None, _cargs=None, *, _standard_gate=False):
|
||||
if not self._is_built:
|
||||
self._build()
|
||||
return super()._append(instruction, _qargs, _cargs)
|
||||
return super()._append(instruction, _qargs, _cargs, _standard_gate=_standard_gate)
|
||||
|
||||
def compose(
|
||||
self,
|
||||
|
|
|
@ -17,6 +17,7 @@ import numpy as np
|
|||
from qiskit.circuit._utils import with_gate_array
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
from .rzx import RZXGate
|
||||
from .x import XGate
|
||||
|
||||
|
@ -84,6 +85,8 @@ class ECRGate(SingletonGate):
|
|||
\end{pmatrix}
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.ECRGate
|
||||
|
||||
def __init__(self, label=None, *, duration=None, unit="dt"):
|
||||
"""Create new ECR gate."""
|
||||
super().__init__("ecr", 2, [], label=label, duration=duration, unit=unit)
|
||||
|
|
|
@ -20,6 +20,7 @@ from qiskit.circuit.gate import Gate
|
|||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.quantumcircuit import QuantumCircuit
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class GlobalPhaseGate(Gate):
|
||||
|
@ -36,6 +37,8 @@ class GlobalPhaseGate(Gate):
|
|||
\end{pmatrix}
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.GlobalPhaseGate
|
||||
|
||||
def __init__(
|
||||
self, phase: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
|
||||
):
|
||||
|
|
|
@ -17,6 +17,7 @@ import numpy
|
|||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
_H_ARRAY = 1 / sqrt(2) * numpy.array([[1, 1], [1, -1]], dtype=numpy.complex128)
|
||||
|
||||
|
@ -51,6 +52,8 @@ class HGate(SingletonGate):
|
|||
\end{pmatrix}
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.HGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new H gate."""
|
||||
super().__init__("h", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
from typing import Optional
|
||||
from qiskit.circuit.singleton import SingletonGate, stdlib_singleton_key
|
||||
from qiskit.circuit._utils import with_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
@with_gate_array([[1, 0], [0, 1]])
|
||||
|
@ -45,6 +46,8 @@ class IGate(SingletonGate):
|
|||
└───┘
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.IGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new Identity gate."""
|
||||
super().__init__("id", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
|
|
@ -19,6 +19,7 @@ from qiskit.circuit.controlledgate import ControlledGate
|
|||
from qiskit.circuit.gate import Gate
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class PhaseGate(Gate):
|
||||
|
@ -75,6 +76,8 @@ class PhaseGate(Gate):
|
|||
`1612.00858 <https://arxiv.org/abs/1612.00858>`_
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.PhaseGate
|
||||
|
||||
def __init__(
|
||||
self, theta: ParameterValueType, label: str | None = None, *, duration=None, unit="dt"
|
||||
):
|
||||
|
|
|
@ -21,6 +21,7 @@ from qiskit.circuit.controlledgate import ControlledGate
|
|||
from qiskit.circuit.gate import Gate
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class RXGate(Gate):
|
||||
|
@ -50,6 +51,8 @@ class RXGate(Gate):
|
|||
\end{pmatrix}
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.RXGate
|
||||
|
||||
def __init__(
|
||||
self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
|
||||
):
|
||||
|
|
|
@ -20,6 +20,7 @@ from qiskit.circuit.controlledgate import ControlledGate
|
|||
from qiskit.circuit.gate import Gate
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class RYGate(Gate):
|
||||
|
@ -49,6 +50,8 @@ class RYGate(Gate):
|
|||
\end{pmatrix}
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.RYGate
|
||||
|
||||
def __init__(
|
||||
self, theta: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
|
||||
):
|
||||
|
|
|
@ -17,6 +17,7 @@ from qiskit.circuit.gate import Gate
|
|||
from qiskit.circuit.controlledgate import ControlledGate
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class RZGate(Gate):
|
||||
|
@ -59,6 +60,8 @@ class RZGate(Gate):
|
|||
`1612.00858 <https://arxiv.org/abs/1612.00858>`_
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.RZGate
|
||||
|
||||
def __init__(
|
||||
self, phi: ParameterValueType, label: Optional[str] = None, *, duration=None, unit="dt"
|
||||
):
|
||||
|
|
|
@ -17,6 +17,7 @@ import numpy
|
|||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
_SWAP_ARRAY = numpy.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])
|
||||
|
@ -58,6 +59,8 @@ class SwapGate(SingletonGate):
|
|||
|a, b\rangle \rightarrow |b, a\rangle
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.SwapGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new SWAP gate."""
|
||||
super().__init__("swap", 2, [], label=label, duration=duration, unit=unit)
|
||||
|
|
|
@ -17,6 +17,7 @@ from typing import Optional, Union
|
|||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
_SX_ARRAY = [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]]
|
||||
|
@ -62,6 +63,8 @@ class SXGate(SingletonGate):
|
|||
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.SXGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new SX gate."""
|
||||
super().__init__("sx", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
|
|
@ -21,6 +21,7 @@ from qiskit.circuit.controlledgate import ControlledGate
|
|||
from qiskit.circuit.gate import Gate
|
||||
from qiskit.circuit.parameterexpression import ParameterValueType
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
|
||||
class UGate(Gate):
|
||||
|
@ -68,6 +69,8 @@ class UGate(Gate):
|
|||
U(\theta, 0, 0) = RY(\theta)
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.UGate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
theta: ParameterValueType,
|
||||
|
|
|
@ -19,6 +19,7 @@ from qiskit.circuit.controlledgate import ControlledGate
|
|||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit._utils import _ctrl_state_to_int, with_gate_array, with_controlled_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
_X_ARRAY = [[0, 1], [1, 0]]
|
||||
|
||||
|
@ -70,6 +71,8 @@ class XGate(SingletonGate):
|
|||
|1\rangle \rightarrow |0\rangle
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.XGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new X gate."""
|
||||
super().__init__("x", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
@ -212,6 +215,8 @@ class CXGate(SingletonControlledGate):
|
|||
`|a, b\rangle \rightarrow |a, a \oplus b\rangle`
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.CXGate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: Optional[str] = None,
|
||||
|
@ -362,6 +367,8 @@ class CCXGate(SingletonControlledGate):
|
|||
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.CCXGate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: Optional[str] = None,
|
||||
|
|
|
@ -19,6 +19,7 @@ from typing import Optional, Union
|
|||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
_Y_ARRAY = [[0, -1j], [1j, 0]]
|
||||
|
||||
|
@ -70,6 +71,8 @@ class YGate(SingletonGate):
|
|||
|1\rangle \rightarrow -i|0\rangle
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.YGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new Y gate."""
|
||||
super().__init__("y", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
@ -197,6 +200,8 @@ class CYGate(SingletonControlledGate):
|
|||
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.CYGate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: Optional[str] = None,
|
||||
|
|
|
@ -20,6 +20,7 @@ import numpy
|
|||
from qiskit.circuit._utils import with_gate_array, with_controlled_gate_array
|
||||
from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate, stdlib_singleton_key
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
from qiskit._accelerate.circuit import StandardGate
|
||||
|
||||
from .p import PhaseGate
|
||||
|
||||
|
@ -73,6 +74,8 @@ class ZGate(SingletonGate):
|
|||
|1\rangle \rightarrow -|1\rangle
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.ZGate
|
||||
|
||||
def __init__(self, label: Optional[str] = None, *, duration=None, unit="dt"):
|
||||
"""Create new Z gate."""
|
||||
super().__init__("z", 1, [], label=label, duration=duration, unit=unit)
|
||||
|
@ -181,6 +184,8 @@ class CZGate(SingletonControlledGate):
|
|||
the target qubit if the control qubit is in the :math:`|1\rangle` state.
|
||||
"""
|
||||
|
||||
_standard_gate = StandardGate.CZGate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: Optional[str] = None,
|
||||
|
|
|
@ -12,197 +12,8 @@
|
|||
"""
|
||||
Look-up table for variable parameters in QuantumCircuit.
|
||||
"""
|
||||
import operator
|
||||
import typing
|
||||
from collections.abc import MappingView, MutableMapping, MutableSet
|
||||
|
||||
|
||||
class ParameterReferences(MutableSet):
|
||||
"""A set of instruction parameter slot references.
|
||||
Items are expected in the form ``(instruction, param_index)``. Membership
|
||||
testing is overridden such that items that are otherwise value-wise equal
|
||||
are still considered distinct if their ``instruction``\\ s are referentially
|
||||
distinct.
|
||||
|
||||
In the case of the special value :attr:`.ParameterTable.GLOBAL_PHASE` for ``instruction``, the
|
||||
``param_index`` should be ``None``.
|
||||
"""
|
||||
|
||||
def _instance_key(self, ref):
|
||||
return (id(ref[0]), ref[1])
|
||||
|
||||
def __init__(self, refs):
|
||||
self._instance_ids = {}
|
||||
|
||||
for ref in refs:
|
||||
if not isinstance(ref, tuple) or len(ref) != 2:
|
||||
raise ValueError("refs must be in form (instruction, param_index)")
|
||||
k = self._instance_key(ref)
|
||||
self._instance_ids[k] = ref[0]
|
||||
|
||||
def __getstate__(self):
|
||||
# Leave behind the reference IDs (keys of _instance_ids) since they'll
|
||||
# be incorrect after unpickling on the other side.
|
||||
return list(self)
|
||||
|
||||
def __setstate__(self, refs):
|
||||
# Recompute reference IDs for the newly unpickled instructions.
|
||||
self._instance_ids = {self._instance_key(ref): ref[0] for ref in refs}
|
||||
|
||||
def __len__(self):
|
||||
return len(self._instance_ids)
|
||||
|
||||
def __iter__(self):
|
||||
for (_, idx), instruction in self._instance_ids.items():
|
||||
yield (instruction, idx)
|
||||
|
||||
def __contains__(self, x) -> bool:
|
||||
return self._instance_key(x) in self._instance_ids
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ParameterReferences({repr(list(self))})"
|
||||
|
||||
def add(self, value):
|
||||
"""Adds a reference to the listing if it's not already present."""
|
||||
k = self._instance_key(value)
|
||||
self._instance_ids[k] = value[0]
|
||||
|
||||
def discard(self, value):
|
||||
k = self._instance_key(value)
|
||||
self._instance_ids.pop(k, None)
|
||||
|
||||
def copy(self):
|
||||
"""Create a shallow copy."""
|
||||
return ParameterReferences(self)
|
||||
|
||||
|
||||
class ParameterTable(MutableMapping):
|
||||
"""Class for tracking references to circuit parameters by specific
|
||||
instruction instances.
|
||||
|
||||
Keys are parameters. Values are of type :class:`~ParameterReferences`,
|
||||
which overrides membership testing to be referential for instructions,
|
||||
and is set-like. Elements of :class:`~ParameterReferences`
|
||||
are tuples of ``(instruction, param_index)``.
|
||||
"""
|
||||
|
||||
__slots__ = ["_table", "_keys", "_names"]
|
||||
|
||||
class _GlobalPhaseSentinel:
|
||||
__slots__ = ()
|
||||
|
||||
def __copy__(self):
|
||||
return self
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
return self
|
||||
|
||||
def __reduce__(self):
|
||||
return (operator.attrgetter("GLOBAL_PHASE"), (ParameterTable,))
|
||||
|
||||
def __repr__(self):
|
||||
return "<global-phase sentinel>"
|
||||
|
||||
GLOBAL_PHASE = _GlobalPhaseSentinel()
|
||||
"""Tracking object to indicate that a reference refers to the global phase of a circuit."""
|
||||
|
||||
def __init__(self, mapping=None):
|
||||
"""Create a new instance, initialized with ``mapping`` if provided.
|
||||
|
||||
Args:
|
||||
mapping (Mapping[Parameter, ParameterReferences]):
|
||||
Mapping of parameter to the set of parameter slots that reference
|
||||
it.
|
||||
|
||||
Raises:
|
||||
ValueError: A value in ``mapping`` is not a :class:`~ParameterReferences`.
|
||||
"""
|
||||
if mapping is not None:
|
||||
if any(not isinstance(refs, ParameterReferences) for refs in mapping.values()):
|
||||
raise ValueError("Values must be of type ParameterReferences")
|
||||
self._table = mapping.copy()
|
||||
else:
|
||||
self._table = {}
|
||||
|
||||
self._keys = set(self._table)
|
||||
self._names = {x.name: x for x in self._table}
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._table[key]
|
||||
|
||||
def __setitem__(self, parameter, refs):
|
||||
"""Associate a parameter with the set of parameter slots ``(instruction, param_index)``
|
||||
that reference it.
|
||||
|
||||
.. note::
|
||||
|
||||
Items in ``refs`` are considered unique if their ``instruction`` is referentially
|
||||
unique. See :class:`~ParameterReferences` for details.
|
||||
|
||||
Args:
|
||||
parameter (Parameter): the parameter
|
||||
refs (Union[ParameterReferences, Iterable[(Instruction, int)]]): the parameter slots.
|
||||
If this is an iterable, a new :class:`~ParameterReferences` is created from its
|
||||
contents.
|
||||
"""
|
||||
if not isinstance(refs, ParameterReferences):
|
||||
refs = ParameterReferences(refs)
|
||||
|
||||
self._table[parameter] = refs
|
||||
self._keys.add(parameter)
|
||||
self._names[parameter.name] = parameter
|
||||
|
||||
def get_keys(self):
|
||||
"""Return a set of all keys in the parameter table
|
||||
|
||||
Returns:
|
||||
set: A set of all the keys in the parameter table
|
||||
"""
|
||||
return self._keys
|
||||
|
||||
def get_names(self):
|
||||
"""Return a set of all parameter names in the parameter table
|
||||
|
||||
Returns:
|
||||
set: A set of all the names in the parameter table
|
||||
"""
|
||||
return self._names.keys()
|
||||
|
||||
def parameter_from_name(self, name: str, default: typing.Any = None):
|
||||
"""Get a :class:`.Parameter` with references in this table by its string name.
|
||||
|
||||
If the parameter is not present, return the ``default`` value.
|
||||
|
||||
Args:
|
||||
name: The name of the :class:`.Parameter`
|
||||
default: The object that should be returned if the parameter is missing.
|
||||
"""
|
||||
return self._names.get(name, default)
|
||||
|
||||
def discard_references(self, expression, key):
|
||||
"""Remove all references to parameters contained within ``expression`` at the given table
|
||||
``key``. This also discards parameter entries from the table if they have no further
|
||||
references. No action is taken if the object is not tracked."""
|
||||
for parameter in expression.parameters:
|
||||
if (refs := self._table.get(parameter)) is not None:
|
||||
if len(refs) == 1:
|
||||
del self[parameter]
|
||||
else:
|
||||
refs.discard(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._table[key]
|
||||
self._keys.discard(key)
|
||||
del self._names[key.name]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._table)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._table)
|
||||
|
||||
def __repr__(self):
|
||||
return f"ParameterTable({repr(self._table)})"
|
||||
from collections.abc import MappingView
|
||||
|
||||
|
||||
class ParameterView(MappingView):
|
||||
|
|
|
@ -37,6 +37,7 @@ from typing import (
|
|||
)
|
||||
import numpy as np
|
||||
from qiskit._accelerate.circuit import CircuitData
|
||||
from qiskit._accelerate.circuit import StandardGate, PyGate, PyInstruction, PyOperation
|
||||
from qiskit.exceptions import QiskitError
|
||||
from qiskit.utils.multiprocessing import is_main_process
|
||||
from qiskit.circuit.instruction import Instruction
|
||||
|
@ -57,7 +58,7 @@ from .classical import expr, types
|
|||
from .parameterexpression import ParameterExpression, ParameterValueType
|
||||
from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
|
||||
from .classicalregister import ClassicalRegister, Clbit
|
||||
from .parametertable import ParameterReferences, ParameterTable, ParameterView
|
||||
from .parametertable import ParameterView
|
||||
from .parametervector import ParameterVector
|
||||
from .instructionset import InstructionSet
|
||||
from .operation import Operation
|
||||
|
@ -1124,14 +1125,10 @@ class QuantumCircuit:
|
|||
self._calibrations: DefaultDict[str, dict[tuple, Any]] = defaultdict(dict)
|
||||
self.add_register(*regs)
|
||||
|
||||
# Parameter table tracks instructions with variable parameters.
|
||||
self._parameter_table = ParameterTable()
|
||||
|
||||
# Cache to avoid re-sorting parameters
|
||||
self._parameters = None
|
||||
|
||||
self._layout = None
|
||||
self._global_phase: ParameterValueType = 0
|
||||
self.global_phase = global_phase
|
||||
|
||||
# Add classical variables. Resolve inputs and captures first because they can't depend on
|
||||
|
@ -1159,6 +1156,15 @@ class QuantumCircuit:
|
|||
Qiskit will not examine the content of this mapping, but it will pass it through the
|
||||
transpiler and reattach it to the output, so you can track your own metadata."""
|
||||
|
||||
@classmethod
|
||||
def _from_circuit_data(cls, data: CircuitData) -> typing.Self:
|
||||
"""A private constructor from rust space circuit data."""
|
||||
out = QuantumCircuit()
|
||||
out.add_bits(data.qubits)
|
||||
out.add_bits(data.clbits)
|
||||
out._data = data
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def from_instructions(
|
||||
instructions: Iterable[
|
||||
|
@ -1259,7 +1265,6 @@ class QuantumCircuit:
|
|||
data_input = list(data_input)
|
||||
self._data.clear()
|
||||
self._parameters = None
|
||||
self._parameter_table = ParameterTable()
|
||||
# Repopulate the parameter table with any global-phase entries.
|
||||
self.global_phase = self.global_phase
|
||||
if not data_input:
|
||||
|
@ -1382,12 +1387,11 @@ class QuantumCircuit:
|
|||
|
||||
# Avoids pulling self._data into a Python list
|
||||
# like we would when pickling.
|
||||
result._data = self._data.copy()
|
||||
result._data = self._data.copy(deepcopy=True)
|
||||
result._data.replace_bits(
|
||||
qubits=_copy.deepcopy(self._data.qubits, memo),
|
||||
clbits=_copy.deepcopy(self._data.clbits, memo),
|
||||
)
|
||||
result._data.map_ops(lambda op: _copy.deepcopy(op, memo))
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
|
@ -1896,7 +1900,7 @@ class QuantumCircuit:
|
|||
clbits = self.clbits[: other.num_clbits]
|
||||
if front:
|
||||
# Need to keep a reference to the data for use after we've emptied it.
|
||||
old_data = dest._data.copy()
|
||||
old_data = dest._data.copy(copy_instructions=copy)
|
||||
dest.clear()
|
||||
dest.append(other, qubits, clbits, copy=copy)
|
||||
for instruction in old_data:
|
||||
|
@ -2024,14 +2028,14 @@ class QuantumCircuit:
|
|||
)
|
||||
return n_op.copy() if n_op is op and copy else n_op
|
||||
|
||||
instructions = source._data.copy()
|
||||
instructions = source._data.copy(copy_instructions=copy)
|
||||
instructions.replace_bits(qubits=new_qubits, clbits=new_clbits)
|
||||
instructions.map_ops(map_vars)
|
||||
dest._current_scope().extend(instructions)
|
||||
|
||||
append_existing = None
|
||||
if front:
|
||||
append_existing = dest._data.copy()
|
||||
append_existing = dest._data.copy(copy_instructions=copy)
|
||||
dest.clear()
|
||||
copy_with_remapping(
|
||||
other,
|
||||
|
@ -2296,6 +2300,35 @@ class QuantumCircuit:
|
|||
clbit_representation, self.clbits, self._clbit_indices, Clbit
|
||||
)
|
||||
|
||||
def _append_standard_gate(
|
||||
self,
|
||||
op: StandardGate,
|
||||
params: Sequence[ParameterValueType] | None = None,
|
||||
qargs: Sequence[QubitSpecifier] | None = None,
|
||||
cargs: Sequence[ClbitSpecifier] | None = None,
|
||||
label: str | None = None,
|
||||
) -> InstructionSet:
|
||||
"""An internal method to bypass some checking when directly appending a standard gate."""
|
||||
circuit_scope = self._current_scope()
|
||||
|
||||
if params is None:
|
||||
params = []
|
||||
|
||||
expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []]
|
||||
expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []]
|
||||
if params is not None:
|
||||
for param in params:
|
||||
Gate.validate_parameter(op, param)
|
||||
|
||||
instructions = InstructionSet(resource_requester=circuit_scope.resolve_classical_resource)
|
||||
broadcast_iter = Gate.broadcast_arguments(op, expanded_qargs, expanded_cargs)
|
||||
for qarg, carg in broadcast_iter:
|
||||
self._check_dups(qarg)
|
||||
instruction = CircuitInstruction(op, qarg, carg, params=params, label=label)
|
||||
circuit_scope.append(instruction, _standard_gate=True)
|
||||
instructions._add_ref(circuit_scope.instructions, len(circuit_scope.instructions) - 1)
|
||||
return instructions
|
||||
|
||||
def append(
|
||||
self,
|
||||
instruction: Operation | CircuitInstruction,
|
||||
|
@ -2393,16 +2426,47 @@ class QuantumCircuit:
|
|||
if isinstance(operation, Instruction)
|
||||
else Instruction.broadcast_arguments(operation, expanded_qargs, expanded_cargs)
|
||||
)
|
||||
params = None
|
||||
if isinstance(operation, Gate):
|
||||
params = operation.params
|
||||
operation = PyGate(
|
||||
operation.name,
|
||||
operation.num_qubits,
|
||||
operation.num_clbits,
|
||||
len(params),
|
||||
operation,
|
||||
)
|
||||
elif isinstance(operation, Instruction):
|
||||
params = operation.params
|
||||
operation = PyInstruction(
|
||||
operation.name,
|
||||
operation.num_qubits,
|
||||
operation.num_clbits,
|
||||
len(params),
|
||||
operation,
|
||||
)
|
||||
elif isinstance(operation, Operation):
|
||||
params = getattr(operation, "params", ())
|
||||
operation = PyOperation(
|
||||
operation.name,
|
||||
operation.num_qubits,
|
||||
operation.num_clbits,
|
||||
len(params),
|
||||
operation,
|
||||
)
|
||||
|
||||
for qarg, carg in broadcast_iter:
|
||||
self._check_dups(qarg)
|
||||
instruction = CircuitInstruction(operation, qarg, carg)
|
||||
instruction = CircuitInstruction(operation, qarg, carg, params=params)
|
||||
circuit_scope.append(instruction)
|
||||
instructions._add_ref(circuit_scope.instructions, len(circuit_scope.instructions) - 1)
|
||||
return instructions
|
||||
|
||||
# Preferred new style.
|
||||
@typing.overload
|
||||
def _append(self, instruction: CircuitInstruction) -> CircuitInstruction: ...
|
||||
def _append(
|
||||
self, instruction: CircuitInstruction, *, _standard_gate: bool
|
||||
) -> CircuitInstruction: ...
|
||||
|
||||
# To-be-deprecated old style.
|
||||
@typing.overload
|
||||
|
@ -2413,7 +2477,7 @@ class QuantumCircuit:
|
|||
cargs: Sequence[Clbit],
|
||||
) -> Operation: ...
|
||||
|
||||
def _append(self, instruction, qargs=(), cargs=()):
|
||||
def _append(self, instruction, qargs=(), cargs=(), *, _standard_gate: bool = False):
|
||||
"""Append an instruction to the end of the circuit, modifying the circuit in place.
|
||||
|
||||
.. warning::
|
||||
|
@ -2454,40 +2518,39 @@ class QuantumCircuit:
|
|||
|
||||
:meta public:
|
||||
"""
|
||||
if _standard_gate:
|
||||
new_param = self._data.append(instruction)
|
||||
if new_param:
|
||||
self._parameters = None
|
||||
self.duration = None
|
||||
self.unit = "dt"
|
||||
return instruction
|
||||
|
||||
old_style = not isinstance(instruction, CircuitInstruction)
|
||||
if old_style:
|
||||
instruction = CircuitInstruction(instruction, qargs, cargs)
|
||||
self._data.append(instruction)
|
||||
self._track_operation(instruction.operation)
|
||||
return instruction.operation if old_style else instruction
|
||||
# If there is a reference to the outer circuit in an
|
||||
# instruction param the inner rust append method will raise a runtime error.
|
||||
# When this happens we need to handle the parameters separately.
|
||||
# This shouldn't happen in practice but 2 tests were doing this and it's not
|
||||
# explicitly prohibted by the API so this and the `params` optional argument
|
||||
# path guard against it.
|
||||
try:
|
||||
new_param = self._data.append(instruction)
|
||||
except RuntimeError:
|
||||
params = []
|
||||
for idx, param in enumerate(instruction.operation.params):
|
||||
if isinstance(param, (ParameterExpression, QuantumCircuit)):
|
||||
params.append((idx, list(set(param.parameters))))
|
||||
new_param = self._data.append(instruction, params)
|
||||
if new_param:
|
||||
# clear cache if new parameter is added
|
||||
self._parameters = None
|
||||
|
||||
def _track_operation(self, operation: Operation):
|
||||
"""Sync all non-data-list internal data structures for a newly tracked operation."""
|
||||
if isinstance(operation, Instruction):
|
||||
self._update_parameter_table(operation)
|
||||
# Invalidate whole circuit duration if an instruction is added
|
||||
self.duration = None
|
||||
self.unit = "dt"
|
||||
|
||||
def _update_parameter_table(self, instruction: Instruction):
|
||||
for param_index, param in enumerate(instruction.params):
|
||||
if isinstance(param, (ParameterExpression, QuantumCircuit)):
|
||||
# Scoped constructs like the control-flow ops use QuantumCircuit as a parameter.
|
||||
atomic_parameters = set(param.parameters)
|
||||
else:
|
||||
atomic_parameters = set()
|
||||
|
||||
for parameter in atomic_parameters:
|
||||
if parameter in self._parameter_table:
|
||||
self._parameter_table[parameter].add((instruction, param_index))
|
||||
else:
|
||||
if parameter.name in self._parameter_table.get_names():
|
||||
raise CircuitError(f"Name conflict on adding parameter: {parameter.name}")
|
||||
self._parameter_table[parameter] = ParameterReferences(
|
||||
((instruction, param_index),)
|
||||
)
|
||||
|
||||
# clear cache if new parameter is added
|
||||
self._parameters = None
|
||||
return instruction.operation if old_style else instruction
|
||||
|
||||
@typing.overload
|
||||
def get_parameter(self, name: str, default: T) -> Union[Parameter, T]: ...
|
||||
|
@ -2538,7 +2601,7 @@ class QuantumCircuit:
|
|||
A similar method, but for :class:`.expr.Var` run-time variables instead of
|
||||
:class:`.Parameter` compile-time parameters.
|
||||
"""
|
||||
if (parameter := self._parameter_table.parameter_from_name(name, None)) is None:
|
||||
if (parameter := self._data.get_param_from_name(name)) is None:
|
||||
if default is Ellipsis:
|
||||
raise KeyError(f"no parameter named '{name}' is present")
|
||||
return default
|
||||
|
@ -3415,13 +3478,7 @@ class QuantumCircuit:
|
|||
|
||||
Conditional nonlocal gates are also included.
|
||||
"""
|
||||
multi_qubit_gates = 0
|
||||
for instruction in self._data:
|
||||
if instruction.operation.num_qubits > 1 and not getattr(
|
||||
instruction.operation, "_directive", False
|
||||
):
|
||||
multi_qubit_gates += 1
|
||||
return multi_qubit_gates
|
||||
return self._data.num_nonlocal_gates()
|
||||
|
||||
def get_instructions(self, name: str) -> list[CircuitInstruction]:
|
||||
"""Get instructions matching name.
|
||||
|
@ -3535,29 +3592,6 @@ class QuantumCircuit:
|
|||
"""
|
||||
cpy = self.copy_empty_like(name)
|
||||
cpy._data = self._data.copy()
|
||||
|
||||
# The special global-phase sentinel doesn't need copying, but it's
|
||||
# added here to ensure it's recognised. The global phase itself was
|
||||
# already copied over in `copy_empty_like`.
|
||||
operation_copies = {id(ParameterTable.GLOBAL_PHASE): ParameterTable.GLOBAL_PHASE}
|
||||
|
||||
def memo_copy(op):
|
||||
if (out := operation_copies.get(id(op))) is not None:
|
||||
return out
|
||||
copied = op.copy()
|
||||
operation_copies[id(op)] = copied
|
||||
return copied
|
||||
|
||||
cpy._data.map_ops(memo_copy)
|
||||
cpy._parameter_table = ParameterTable(
|
||||
{
|
||||
param: ParameterReferences(
|
||||
(operation_copies[id(operation)], param_index)
|
||||
for operation, param_index in self._parameter_table[param]
|
||||
)
|
||||
for param in self._parameter_table
|
||||
}
|
||||
)
|
||||
return cpy
|
||||
|
||||
def copy_empty_like(
|
||||
|
@ -3636,12 +3670,9 @@ class QuantumCircuit:
|
|||
else: # pragma: no cover
|
||||
raise ValueError(f"unknown vars_mode: '{vars_mode}'")
|
||||
|
||||
cpy._parameter_table = ParameterTable()
|
||||
for parameter in getattr(cpy.global_phase, "parameters", ()):
|
||||
cpy._parameter_table[parameter] = ParameterReferences(
|
||||
[(ParameterTable.GLOBAL_PHASE, None)]
|
||||
)
|
||||
cpy._data = CircuitData(self._data.qubits, self._data.clbits)
|
||||
cpy._data = CircuitData(
|
||||
self._data.qubits, self._data.clbits, global_phase=self._data.global_phase
|
||||
)
|
||||
|
||||
cpy._calibrations = _copy.deepcopy(self._calibrations)
|
||||
cpy._metadata = _copy.deepcopy(self._metadata)
|
||||
|
@ -3661,7 +3692,6 @@ class QuantumCircuit:
|
|||
quantum and classical typed data, but without mutating the original circuit.
|
||||
"""
|
||||
self._data.clear()
|
||||
self._parameter_table.clear()
|
||||
# Repopulate the parameter table with any phase symbols.
|
||||
self.global_phase = self.global_phase
|
||||
|
||||
|
@ -3945,10 +3975,9 @@ class QuantumCircuit:
|
|||
circ._clbit_indices = {}
|
||||
|
||||
# Clear instruction info
|
||||
circ._data = CircuitData(qubits=circ._data.qubits, reserve=len(circ._data))
|
||||
circ._parameter_table.clear()
|
||||
# Repopulate the parameter table with any global-phase entries.
|
||||
circ.global_phase = circ.global_phase
|
||||
circ._data = CircuitData(
|
||||
qubits=circ._data.qubits, reserve=len(circ._data), global_phase=circ.global_phase
|
||||
)
|
||||
|
||||
# We must add the clbits first to preserve the original circuit
|
||||
# order. This way, add_register never adds clbits and just
|
||||
|
@ -4021,7 +4050,7 @@ class QuantumCircuit:
|
|||
"""The global phase of the current circuit scope in radians."""
|
||||
if self._control_flow_scopes:
|
||||
return self._control_flow_scopes[-1].global_phase
|
||||
return self._global_phase
|
||||
return self._data.global_phase
|
||||
|
||||
@global_phase.setter
|
||||
def global_phase(self, angle: ParameterValueType):
|
||||
|
@ -4032,23 +4061,18 @@ class QuantumCircuit:
|
|||
"""
|
||||
# If we're currently parametric, we need to throw away the references. This setter is
|
||||
# called by some subclasses before the inner `_global_phase` is initialised.
|
||||
global_phase_reference = (ParameterTable.GLOBAL_PHASE, None)
|
||||
if isinstance(previous := getattr(self, "_global_phase", None), ParameterExpression):
|
||||
if isinstance(getattr(self._data, "global_phase", None), ParameterExpression):
|
||||
self._parameters = None
|
||||
self._parameter_table.discard_references(previous, global_phase_reference)
|
||||
|
||||
if isinstance(angle, ParameterExpression) and angle.parameters:
|
||||
for parameter in angle.parameters:
|
||||
if parameter not in self._parameter_table:
|
||||
self._parameters = None
|
||||
self._parameter_table[parameter] = ParameterReferences(())
|
||||
self._parameter_table[parameter].add(global_phase_reference)
|
||||
if isinstance(angle, ParameterExpression):
|
||||
if angle.parameters:
|
||||
self._parameters = None
|
||||
else:
|
||||
angle = _normalize_global_phase(angle)
|
||||
|
||||
if self._control_flow_scopes:
|
||||
self._control_flow_scopes[-1].global_phase = angle
|
||||
else:
|
||||
self._global_phase = angle
|
||||
self._data.global_phase = angle
|
||||
|
||||
@property
|
||||
def parameters(self) -> ParameterView:
|
||||
|
@ -4118,7 +4142,7 @@ class QuantumCircuit:
|
|||
@property
|
||||
def num_parameters(self) -> int:
|
||||
"""The number of parameter objects in the circuit."""
|
||||
return len(self._parameter_table)
|
||||
return self._data.num_params()
|
||||
|
||||
def _unsorted_parameters(self) -> set[Parameter]:
|
||||
"""Efficiently get all parameters in the circuit, without any sorting overhead.
|
||||
|
@ -4131,7 +4155,7 @@ class QuantumCircuit:
|
|||
"""
|
||||
# This should be free, by accessing the actual backing data structure of the table, but that
|
||||
# means that we need to copy it if adding keys from the global phase.
|
||||
return self._parameter_table.get_keys()
|
||||
return self._data.get_params_unsorted()
|
||||
|
||||
@overload
|
||||
def assign_parameters(
|
||||
|
@ -4280,7 +4304,7 @@ class QuantumCircuit:
|
|||
target._parameters = None
|
||||
# This is deliberately eager, because we want the side effect of clearing the table.
|
||||
all_references = [
|
||||
(parameter, value, target._parameter_table.pop(parameter, ()))
|
||||
(parameter, value, target._data.pop_param(parameter.uuid.int, parameter.name, ()))
|
||||
for parameter, value in parameter_binds.items()
|
||||
]
|
||||
seen_operations = {}
|
||||
|
@ -4291,20 +4315,28 @@ class QuantumCircuit:
|
|||
if isinstance(bound_value, ParameterExpression)
|
||||
else ()
|
||||
)
|
||||
for operation, index in references:
|
||||
seen_operations[id(operation)] = operation
|
||||
if operation is ParameterTable.GLOBAL_PHASE:
|
||||
for inst_index, index in references:
|
||||
if inst_index == self._data.global_phase_param_index:
|
||||
operation = None
|
||||
seen_operations[inst_index] = None
|
||||
assignee = target.global_phase
|
||||
validate = _normalize_global_phase
|
||||
else:
|
||||
operation = target._data[inst_index].operation
|
||||
seen_operations[inst_index] = operation
|
||||
assignee = operation.params[index]
|
||||
validate = operation.validate_parameter
|
||||
if isinstance(assignee, ParameterExpression):
|
||||
new_parameter = assignee.assign(to_bind, bound_value)
|
||||
for parameter in update_parameters:
|
||||
if parameter not in target._parameter_table:
|
||||
target._parameter_table[parameter] = ParameterReferences(())
|
||||
target._parameter_table[parameter].add((operation, index))
|
||||
if not target._data.contains_param(parameter.uuid.int):
|
||||
target._data.add_new_parameter(parameter, inst_index, index)
|
||||
else:
|
||||
target._data.update_parameter_entry(
|
||||
parameter.uuid.int,
|
||||
inst_index,
|
||||
index,
|
||||
)
|
||||
if not new_parameter.parameters:
|
||||
new_parameter = validate(new_parameter.numeric())
|
||||
elif isinstance(assignee, QuantumCircuit):
|
||||
|
@ -4316,12 +4348,18 @@ class QuantumCircuit:
|
|||
f"Saw an unknown type during symbolic binding: {assignee}."
|
||||
" This may indicate an internal logic error in symbol tracking."
|
||||
)
|
||||
if operation is ParameterTable.GLOBAL_PHASE:
|
||||
if inst_index == self._data.global_phase_param_index:
|
||||
# We've already handled parameter table updates in bulk, so we need to skip the
|
||||
# public setter trying to do it again.
|
||||
target._global_phase = new_parameter
|
||||
target._data.global_phase = new_parameter
|
||||
else:
|
||||
operation.params[index] = new_parameter
|
||||
temp_params = operation.params
|
||||
temp_params[index] = new_parameter
|
||||
operation.params = temp_params
|
||||
target._data.setitem_no_param_table_update(
|
||||
inst_index,
|
||||
target._data[inst_index].replace(operation=operation, params=temp_params),
|
||||
)
|
||||
|
||||
# After we've been through everything at the top level, make a single visit to each
|
||||
# operation we've seen, rebinding its definition if necessary.
|
||||
|
@ -4368,6 +4406,7 @@ class QuantumCircuit:
|
|||
for gate, calibrations in target._calibrations.items()
|
||||
),
|
||||
)
|
||||
target._parameters = None
|
||||
return None if inplace else target
|
||||
|
||||
def _unroll_param_dict(
|
||||
|
@ -4450,9 +4489,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.h import HGate
|
||||
|
||||
return self.append(HGate(), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.HGate, [], qargs=[qubit])
|
||||
|
||||
def ch(
|
||||
self,
|
||||
|
@ -4496,9 +4533,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.i import IGate
|
||||
|
||||
return self.append(IGate(), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.IGate, None, qargs=[qubit])
|
||||
|
||||
def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> InstructionSet:
|
||||
"""Apply :class:`~qiskit.circuit.library.MSGate`.
|
||||
|
@ -4529,9 +4564,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.p import PhaseGate
|
||||
|
||||
return self.append(PhaseGate(theta), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.PhaseGate, [theta], qargs=[qubit])
|
||||
|
||||
def cp(
|
||||
self,
|
||||
|
@ -4712,9 +4745,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.rx import RXGate
|
||||
|
||||
return self.append(RXGate(theta, label=label), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.RXGate, [theta], [qubit], None, label=label)
|
||||
|
||||
def crx(
|
||||
self,
|
||||
|
@ -4783,9 +4814,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.ry import RYGate
|
||||
|
||||
return self.append(RYGate(theta, label=label), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.RYGate, [theta], [qubit], None, label=label)
|
||||
|
||||
def cry(
|
||||
self,
|
||||
|
@ -4851,9 +4880,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.rz import RZGate
|
||||
|
||||
return self.append(RZGate(phi), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.RZGate, [phi], [qubit], None)
|
||||
|
||||
def crz(
|
||||
self,
|
||||
|
@ -4937,9 +4964,9 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.ecr import ECRGate
|
||||
|
||||
return self.append(ECRGate(), [qubit1, qubit2], [], copy=False)
|
||||
return self._append_standard_gate(
|
||||
StandardGate.ECRGate, [], qargs=[qubit1, qubit2], cargs=None
|
||||
)
|
||||
|
||||
def s(self, qubit: QubitSpecifier) -> InstructionSet:
|
||||
"""Apply :class:`~qiskit.circuit.library.SGate`.
|
||||
|
@ -5044,9 +5071,12 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.swap import SwapGate
|
||||
|
||||
return self.append(SwapGate(), [qubit1, qubit2], [], copy=False)
|
||||
return self._append_standard_gate(
|
||||
StandardGate.SwapGate,
|
||||
[],
|
||||
qargs=[qubit1, qubit2],
|
||||
cargs=None,
|
||||
)
|
||||
|
||||
def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet:
|
||||
"""Apply :class:`~qiskit.circuit.library.iSwapGate`.
|
||||
|
@ -5107,9 +5137,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.sx import SXGate
|
||||
|
||||
return self.append(SXGate(), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.SXGate, None, qargs=[qubit])
|
||||
|
||||
def sxdg(self, qubit: QubitSpecifier) -> InstructionSet:
|
||||
"""Apply :class:`~qiskit.circuit.library.SXdgGate`.
|
||||
|
@ -5207,9 +5235,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.u import UGate
|
||||
|
||||
return self.append(UGate(theta, phi, lam), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.UGate, [theta, phi, lam], qargs=[qubit])
|
||||
|
||||
def cu(
|
||||
self,
|
||||
|
@ -5262,9 +5288,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.x import XGate
|
||||
|
||||
return self.append(XGate(label=label), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.XGate, None, qargs=[qubit], label=label)
|
||||
|
||||
def cx(
|
||||
self,
|
||||
|
@ -5288,14 +5312,17 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
if ctrl_state is not None:
|
||||
from .library.standard_gates.x import CXGate
|
||||
|
||||
from .library.standard_gates.x import CXGate
|
||||
|
||||
return self.append(
|
||||
CXGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
return self.append(
|
||||
CXGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
)
|
||||
return self._append_standard_gate(
|
||||
StandardGate.CXGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
|
||||
)
|
||||
|
||||
def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet:
|
||||
|
@ -5336,13 +5363,20 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.x import CCXGate
|
||||
if ctrl_state is not None:
|
||||
from .library.standard_gates.x import CCXGate
|
||||
|
||||
return self.append(
|
||||
CCXGate(ctrl_state=ctrl_state),
|
||||
[control_qubit1, control_qubit2, target_qubit],
|
||||
return self.append(
|
||||
CCXGate(ctrl_state=ctrl_state),
|
||||
[control_qubit1, control_qubit2, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
)
|
||||
return self._append_standard_gate(
|
||||
StandardGate.CCXGate,
|
||||
[],
|
||||
copy=False,
|
||||
qargs=[control_qubit1, control_qubit2, target_qubit],
|
||||
cargs=None,
|
||||
)
|
||||
|
||||
def mcx(
|
||||
|
@ -5440,9 +5474,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.y import YGate
|
||||
|
||||
return self.append(YGate(), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.YGate, None, qargs=[qubit])
|
||||
|
||||
def cy(
|
||||
self,
|
||||
|
@ -5466,13 +5498,18 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.y import CYGate
|
||||
if ctrl_state is not None:
|
||||
from .library.standard_gates.y import CYGate
|
||||
|
||||
return self.append(
|
||||
CYGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
return self.append(
|
||||
CYGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
)
|
||||
|
||||
return self._append_standard_gate(
|
||||
StandardGate.CYGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
|
||||
)
|
||||
|
||||
def z(self, qubit: QubitSpecifier) -> InstructionSet:
|
||||
|
@ -5486,9 +5523,7 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.z import ZGate
|
||||
|
||||
return self.append(ZGate(), [qubit], [], copy=False)
|
||||
return self._append_standard_gate(StandardGate.ZGate, None, qargs=[qubit])
|
||||
|
||||
def cz(
|
||||
self,
|
||||
|
@ -5512,13 +5547,18 @@ class QuantumCircuit:
|
|||
Returns:
|
||||
A handle to the instructions created.
|
||||
"""
|
||||
from .library.standard_gates.z import CZGate
|
||||
if ctrl_state is not None:
|
||||
from .library.standard_gates.z import CZGate
|
||||
|
||||
return self.append(
|
||||
CZGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
return self.append(
|
||||
CZGate(label=label, ctrl_state=ctrl_state),
|
||||
[control_qubit, target_qubit],
|
||||
[],
|
||||
copy=False,
|
||||
)
|
||||
|
||||
return self._append_standard_gate(
|
||||
StandardGate.CZGate, [], qargs=[control_qubit, target_qubit], cargs=None, label=label
|
||||
)
|
||||
|
||||
def ccz(
|
||||
|
@ -5907,36 +5947,9 @@ class QuantumCircuit:
|
|||
if not self._data:
|
||||
raise CircuitError("This circuit contains no instructions.")
|
||||
instruction = self._data.pop()
|
||||
if isinstance(instruction.operation, Instruction):
|
||||
self._update_parameter_table_on_instruction_removal(instruction)
|
||||
self._parameters = None
|
||||
return instruction
|
||||
|
||||
def _update_parameter_table_on_instruction_removal(self, instruction: CircuitInstruction):
|
||||
"""Update the :obj:`.ParameterTable` of this circuit given that an instance of the given
|
||||
``instruction`` has just been removed from the circuit.
|
||||
|
||||
.. note::
|
||||
|
||||
This does not account for the possibility for the same instruction instance being added
|
||||
more than once to the circuit. At the time of writing (2021-11-17, main commit 271a82f)
|
||||
there is a defensive ``deepcopy`` of parameterised instructions inside
|
||||
:meth:`.QuantumCircuit.append`, so this should be safe. Trying to account for it would
|
||||
involve adding a potentially quadratic-scaling loop to check each entry in ``data``.
|
||||
"""
|
||||
atomic_parameters: list[tuple[Parameter, int]] = []
|
||||
for index, parameter in enumerate(instruction.operation.params):
|
||||
if isinstance(parameter, (ParameterExpression, QuantumCircuit)):
|
||||
atomic_parameters.extend((p, index) for p in parameter.parameters)
|
||||
for atomic_parameter, index in atomic_parameters:
|
||||
new_entries = self._parameter_table[atomic_parameter].copy()
|
||||
new_entries.discard((instruction.operation, index))
|
||||
if not new_entries:
|
||||
del self._parameter_table[atomic_parameter]
|
||||
# Invalidate cache.
|
||||
self._parameters = None
|
||||
else:
|
||||
self._parameter_table[atomic_parameter] = new_entries
|
||||
|
||||
@typing.overload
|
||||
def while_loop(
|
||||
self,
|
||||
|
@ -6582,13 +6595,15 @@ class _OuterCircuitScopeInterface(CircuitScopeInterface):
|
|||
def instructions(self):
|
||||
return self.circuit._data
|
||||
|
||||
def append(self, instruction):
|
||||
def append(self, instruction, *, _standard_gate: bool = False):
|
||||
# QuantumCircuit._append is semi-public, so we just call back to it.
|
||||
return self.circuit._append(instruction)
|
||||
return self.circuit._append(instruction, _standard_gate=_standard_gate)
|
||||
|
||||
def extend(self, data: CircuitData):
|
||||
self.circuit._data.extend(data)
|
||||
data.foreach_op(self.circuit._track_operation)
|
||||
self.circuit._parameters = None
|
||||
self.circuit.duration = None
|
||||
self.circuit.unit = "dt"
|
||||
|
||||
def resolve_classical_resource(self, specifier):
|
||||
# This is slightly different to cbit_argument_conversion, because it should not
|
||||
|
|
|
@ -45,8 +45,6 @@ class QuantumCircuitData(MutableSequence):
|
|||
operation, qargs, cargs = value
|
||||
value = self._resolve_legacy_value(operation, qargs, cargs)
|
||||
self._circuit._data[key] = value
|
||||
if isinstance(value.operation, Instruction):
|
||||
self._circuit._update_parameter_table(value.operation)
|
||||
|
||||
def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction:
|
||||
"""Resolve the old-style 3-tuple into the new :class:`CircuitInstruction` type."""
|
||||
|
@ -76,7 +74,7 @@ class QuantumCircuitData(MutableSequence):
|
|||
return CircuitInstruction(operation, tuple(qargs), tuple(cargs))
|
||||
|
||||
def insert(self, index, value):
|
||||
self._circuit._data.insert(index, CircuitInstruction(None, (), ()))
|
||||
self._circuit._data.insert(index, value.replace(qubits=(), clbits=()))
|
||||
try:
|
||||
self[index] = value
|
||||
except CircuitError:
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
# that they have been altered from the originals.
|
||||
|
||||
"""Helper function for converting a circuit to an instruction."""
|
||||
from qiskit.circuit.parametertable import ParameterTable, ParameterReferences
|
||||
from qiskit.exceptions import QiskitError
|
||||
from qiskit.circuit.instruction import Instruction
|
||||
from qiskit.circuit.quantumregister import QuantumRegister
|
||||
|
@ -121,7 +120,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None
|
|||
regs.append(creg)
|
||||
|
||||
clbit_map = {bit: creg[idx] for idx, bit in enumerate(circuit.clbits)}
|
||||
operation_map = {id(ParameterTable.GLOBAL_PHASE): ParameterTable.GLOBAL_PHASE}
|
||||
operation_map = {}
|
||||
|
||||
def fix_condition(op):
|
||||
original_id = id(op)
|
||||
|
@ -149,15 +148,6 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None
|
|||
|
||||
qc = QuantumCircuit(*regs, name=out_instruction.name)
|
||||
qc._data = data
|
||||
qc._parameter_table = ParameterTable(
|
||||
{
|
||||
param: ParameterReferences(
|
||||
(operation_map[id(operation)], param_index)
|
||||
for operation, param_index in target._parameter_table[param]
|
||||
)
|
||||
for param in target._parameter_table
|
||||
}
|
||||
)
|
||||
|
||||
if circuit.global_phase:
|
||||
qc.global_phase = circuit.global_phase
|
||||
|
|
|
@ -252,6 +252,9 @@ class GlobalNamespace:
|
|||
def __setitem__(self, name_str, instruction):
|
||||
self._data[name_str] = instruction.base_class
|
||||
self._data[id(instruction)] = name_str
|
||||
ctrl_state = str(getattr(instruction, "ctrl_state", ""))
|
||||
|
||||
self._data[f"{instruction.name}_{ctrl_state}_{instruction.params}"] = name_str
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, Instruction):
|
||||
|
@ -262,7 +265,9 @@ class GlobalNamespace:
|
|||
pass
|
||||
# Built-in gates.
|
||||
if key.name not in self._data:
|
||||
raise KeyError(key)
|
||||
# Registerd qiskit standard gate without stgates.inc
|
||||
ctrl_state = str(getattr(key, "ctrl_state", ""))
|
||||
return self._data[f"{key.name}_{ctrl_state}_{key.params}"]
|
||||
return key.name
|
||||
return self._data[key]
|
||||
|
||||
|
@ -1102,7 +1107,8 @@ def _infer_variable_declaration(
|
|||
# _should_ be an intrinsic part of the parameter, or somewhere publicly accessible, but
|
||||
# Terra doesn't have those concepts yet. We can only try and guess at the type by looking
|
||||
# at all the places it's used in the circuit.
|
||||
for instruction, index in circuit._parameter_table[parameter]:
|
||||
for instr_index, index in circuit._data._get_param(parameter.uuid.int):
|
||||
instruction = circuit.data[instr_index].operation
|
||||
if isinstance(instruction, ForLoopOp):
|
||||
# The parameters of ForLoopOp are (indexset, loop_parameter, body).
|
||||
if index == 1:
|
||||
|
|
|
@ -452,8 +452,7 @@ class CNOTDihedral(BaseOperator, AdjointMixin):
|
|||
new_qubits = [bit_indices[tup] for tup in instruction.qubits]
|
||||
if instruction.operation.name == "p":
|
||||
params = 2 * np.pi - instruction.operation.params[0]
|
||||
instruction.operation.params[0] = params
|
||||
new_circ.append(instruction.operation, new_qubits)
|
||||
new_circ.p(params, new_qubits)
|
||||
elif instruction.operation.name == "t":
|
||||
instruction.operation.name = "tdg"
|
||||
new_circ.append(instruction.operation, new_qubits)
|
||||
|
|
|
@ -361,17 +361,17 @@ class PadDynamicalDecoupling(BasePadding):
|
|||
theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv)
|
||||
if isinstance(next_node, DAGOpNode) and isinstance(next_node.op, (UGate, U3Gate)):
|
||||
# Absorb the inverse into the successor (from left in circuit)
|
||||
theta_r, phi_r, lam_r = next_node.op.params
|
||||
next_node.op.params = Optimize1qGates.compose_u3(
|
||||
theta_r, phi_r, lam_r, theta, phi, lam
|
||||
)
|
||||
op = next_node.op
|
||||
theta_r, phi_r, lam_r = op.params
|
||||
op.params = Optimize1qGates.compose_u3(theta_r, phi_r, lam_r, theta, phi, lam)
|
||||
next_node.op = op
|
||||
sequence_gphase += phase
|
||||
elif isinstance(prev_node, DAGOpNode) and isinstance(prev_node.op, (UGate, U3Gate)):
|
||||
# Absorb the inverse into the predecessor (from right in circuit)
|
||||
theta_l, phi_l, lam_l = prev_node.op.params
|
||||
prev_node.op.params = Optimize1qGates.compose_u3(
|
||||
theta, phi, lam, theta_l, phi_l, lam_l
|
||||
)
|
||||
op = prev_node.op
|
||||
theta_l, phi_l, lam_l = op.params
|
||||
op.params = Optimize1qGates.compose_u3(theta, phi, lam, theta_l, phi_l, lam_l)
|
||||
prev_node.op = op
|
||||
sequence_gphase += phase
|
||||
else:
|
||||
# Don't do anything if there's no single-qubit gate to absorb the inverse
|
||||
|
|
|
@ -70,8 +70,9 @@ class BaseScheduler(AnalysisPass):
|
|||
duration = dag.calibrations[node.op.name][cal_key].duration
|
||||
|
||||
# Note that node duration is updated (but this is analysis pass)
|
||||
node.op = node.op.to_mutable()
|
||||
node.op.duration = duration
|
||||
op = node.op.to_mutable()
|
||||
op.duration = duration
|
||||
node.op = op
|
||||
else:
|
||||
duration = node.op.duration
|
||||
|
||||
|
|
|
@ -105,9 +105,10 @@ class TimeUnitConversion(TransformationPass):
|
|||
)
|
||||
except TranspilerError:
|
||||
continue
|
||||
node.op = node.op.to_mutable()
|
||||
node.op.duration = duration
|
||||
node.op.unit = time_unit
|
||||
op = node.op.to_mutable()
|
||||
op.duration = duration
|
||||
op.unit = time_unit
|
||||
node.op = op
|
||||
|
||||
self.property_set["time_unit"] = time_unit
|
||||
return dag
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
features_circuits:
|
||||
- |
|
||||
A native rust representation of Qiskit's standard gate library has been added. When a standard gate
|
||||
is added to a :class:`~.QuantumCircuit` or :class:`~.DAGCircuit` it is now represented in a more
|
||||
efficient manner directly in Rust seamlessly. Accessing that gate object from a circuit or dag will
|
||||
return a new Python object representing the standard gate. This leads to faster and more efficient
|
||||
transpilation and manipulation of circuits for functionality written in Rust.
|
||||
features_misc:
|
||||
- |
|
||||
Added a new build-time environment variable ``QISKIT_NO_CACHE_GATES`` which
|
||||
when set to a value of ``1`` (i.e. ``QISKIT_NO_CACHE_GATES=1``) which
|
||||
decreases the memory overhead of a :class:`.CircuitInstruction` and
|
||||
:class:`.DAGOpNode` object at the cost of decreased runtime on multiple
|
||||
accesses to :attr:`.CircuitInstruction.operation` and :attr:`.DAGOpNode.op`.
|
||||
If this environment variable is set when building the Qiskit python package
|
||||
from source the caching of the return of these attributes will be disabled.
|
||||
upgrade_circuits:
|
||||
- |
|
||||
The :class:`.Operation` instances of :attr:`.DAGOpNode.op`
|
||||
being returned will not necessarily share a common reference to the
|
||||
underlying object anymore. This was never guaranteed to be the case and
|
||||
mutating the :attr:`~.DAGOpNode.op` directly by reference
|
||||
was unsound and always likely to corrupt the dag's internal state tracking
|
||||
Due to the internal refactor of the :class:`.QuantumCircuit` and
|
||||
:class:`.DAGCircuit` to store standard gates in rust the output object from
|
||||
:attr:`.DAGOpNode.op` will now likely be a copy instead of a shared instance. If you
|
||||
need to mutate an element should ensure that you either do::
|
||||
|
||||
op = dag_node.op
|
||||
op.params[0] = 3.14159
|
||||
dag_node.op = op
|
||||
|
||||
or::
|
||||
|
||||
op = dag_node.op
|
||||
op.params[0] = 3.14159
|
||||
dag.substitute_node(dag_node, op)
|
||||
|
||||
instead of doing something like::
|
||||
|
||||
dag_node.op.params[0] = 3.14159
|
||||
|
||||
which will not work for any standard gates in this release. It would have
|
||||
likely worked by chance in a previous release but was never an API guarantee.
|
||||
- |
|
||||
The :class:`.Operation` instances of :attr:`.CircuitInstruction.operation`
|
||||
being returned will not necessarily share a common reference to the
|
||||
underlying object anymore. This was never guaranteed to be the case and
|
||||
mutating the :attr:`~.CircuitInstruction.operation` directly by reference
|
||||
was unsound and always likely to corrupt the circuit, especially when
|
||||
parameters were in use. Due to the internal refactor of the QuantumCircuit
|
||||
to store standard gates in rust the output object from
|
||||
:attr:`.CircuitInstruction.operation` will now likely be a copy instead
|
||||
of a shared instance. If you need to mutate an element in the circuit (which
|
||||
is strongly **not** recommended as it's inefficient and error prone) you
|
||||
should ensure that you do::
|
||||
|
||||
from qiskit.circuit import QuantumCircuit
|
||||
|
||||
qc = QuantumCircuit(1)
|
||||
qc.p(0)
|
||||
|
||||
op = qc.data[0].operation
|
||||
op.params[0] = 3.14
|
||||
|
||||
qc.data[0] = qc.data[0].replace(operation=op)
|
||||
|
||||
instead of doing something like::
|
||||
|
||||
from qiskit.circuit import QuantumCircuit
|
||||
|
||||
qc = QuantumCircuit(1)
|
||||
qc.p(0)
|
||||
|
||||
qc.data[0].operation.params[0] = 3.14
|
||||
|
||||
which will not work for any standard gates in this release. It would have
|
||||
likely worked by chance in a previous release but was never an API guarantee.
|
|
@ -19,7 +19,7 @@ seaborn>=0.9.0
|
|||
|
||||
# Functionality and accelerators.
|
||||
qiskit-aer
|
||||
qiskit-qasm3-import
|
||||
qiskit-qasm3-import>=0.5.0
|
||||
python-constraint>=1.4
|
||||
cvxpy
|
||||
scikit-learn>=0.20.0
|
||||
|
|
12
setup.py
12
setup.py
|
@ -30,6 +30,17 @@ from setuptools_rust import Binding, RustExtension
|
|||
# it's an editable installation.
|
||||
rust_debug = True if os.getenv("RUST_DEBUG") == "1" else None
|
||||
|
||||
# If QISKIT_NO_CACHE_GATES is set then don't enable any features while building
|
||||
#
|
||||
# TODO: before final release we should reverse this by default once the default transpiler pass
|
||||
# is all in rust (default to no caching and make caching an opt-in feature). This is opt-out
|
||||
# right now to avoid the runtime overhead until we are leveraging the rust gates infrastructure.
|
||||
if os.getenv("QISKIT_NO_CACHE_GATES") == "1":
|
||||
features = []
|
||||
else:
|
||||
features = ["cache_pygates"]
|
||||
|
||||
|
||||
setup(
|
||||
rust_extensions=[
|
||||
RustExtension(
|
||||
|
@ -37,6 +48,7 @@ setup(
|
|||
"crates/pyext/Cargo.toml",
|
||||
binding=Binding.PyO3,
|
||||
debug=rust_debug,
|
||||
features=features,
|
||||
)
|
||||
],
|
||||
options={"bdist_wheel": {"py_limited_api": "cp38"}},
|
||||
|
|
|
@ -77,17 +77,17 @@ class TestBlueprintCircuit(QiskitTestCase):
|
|||
|
||||
with self.subTest(msg="after building"):
|
||||
self.assertGreater(len(mock._data), 0)
|
||||
self.assertEqual(len(mock._parameter_table), 1)
|
||||
self.assertEqual(mock._data.num_params(), 1)
|
||||
|
||||
mock._invalidate()
|
||||
with self.subTest(msg="after invalidating"):
|
||||
self.assertFalse(mock._is_built)
|
||||
self.assertEqual(len(mock._parameter_table), 0)
|
||||
self.assertEqual(mock._data.num_params(), 0)
|
||||
|
||||
mock._build()
|
||||
with self.subTest(msg="after re-building"):
|
||||
self.assertGreater(len(mock._data), 0)
|
||||
self.assertEqual(len(mock._parameter_table), 1)
|
||||
self.assertEqual(mock._data.num_params(), 1)
|
||||
|
||||
def test_calling_attributes_works(self):
|
||||
"""Test that the circuit is constructed when attributes are called."""
|
||||
|
|
|
@ -187,12 +187,20 @@ class TestQuantumCircuitData(QiskitTestCase):
|
|||
def test_map_ops(self):
|
||||
"""Test all operations are replaced."""
|
||||
qr = QuantumRegister(5)
|
||||
|
||||
# Use a custom gate to ensure we get a gate class returned and not
|
||||
# a standard gate.
|
||||
class CustomXGate(XGate):
|
||||
"""A custom X gate that doesn't have rust native representation."""
|
||||
|
||||
_standard_gate = None
|
||||
|
||||
data_list = [
|
||||
CircuitInstruction(XGate(), [qr[0]], []),
|
||||
CircuitInstruction(XGate(), [qr[1]], []),
|
||||
CircuitInstruction(XGate(), [qr[2]], []),
|
||||
CircuitInstruction(XGate(), [qr[3]], []),
|
||||
CircuitInstruction(XGate(), [qr[4]], []),
|
||||
CircuitInstruction(CustomXGate(), [qr[0]], []),
|
||||
CircuitInstruction(CustomXGate(), [qr[1]], []),
|
||||
CircuitInstruction(CustomXGate(), [qr[2]], []),
|
||||
CircuitInstruction(CustomXGate(), [qr[3]], []),
|
||||
CircuitInstruction(CustomXGate(), [qr[4]], []),
|
||||
]
|
||||
data = CircuitData(qubits=list(qr), data=data_list)
|
||||
data.map_ops(lambda op: op.to_mutable())
|
||||
|
@ -828,6 +836,9 @@ class TestQuantumCircuitInstructionData(QiskitTestCase):
|
|||
qc0.append(rx, [0])
|
||||
qc1.append(rx, [0])
|
||||
qc0.assign_parameters({a: b}, inplace=True)
|
||||
qc0_instance = next(iter(qc0._parameter_table[b]))[0]
|
||||
qc1_instance = next(iter(qc1._parameter_table[a]))[0]
|
||||
# A fancy way of doing qc0_instance = qc0.data[0] and qc1_instance = qc1.data[0]
|
||||
# but this at least verifies the parameter table is point from the parameter to
|
||||
# the correct instruction (which is the only one)
|
||||
qc0_instance = qc0._data[next(iter(qc0._data._get_param(b.uuid.int)))[0]]
|
||||
qc1_instance = qc1._data[next(iter(qc1._data._get_param(a.uuid.int)))[0]]
|
||||
self.assertNotEqual(qc0_instance, qc1_instance)
|
||||
|
|
|
@ -593,7 +593,7 @@ class TestCircuitOperations(QiskitTestCase):
|
|||
qc.clear()
|
||||
|
||||
self.assertEqual(len(qc.data), 0)
|
||||
self.assertEqual(len(qc._parameter_table), 0)
|
||||
self.assertEqual(qc._data.num_params(), 0)
|
||||
|
||||
def test_barrier(self):
|
||||
"""Test multiple argument forms of barrier."""
|
||||
|
|
|
@ -357,7 +357,8 @@ class TestCircuitCompose(QiskitTestCase):
|
|||
self.assertIsNot(should_copy.data[-1].operation, parametric.data[-1].operation)
|
||||
self.assertEqual(should_copy.data[-1].operation, parametric.data[-1].operation)
|
||||
forbid_copy = base.compose(parametric, qubits=[0], copy=False)
|
||||
self.assertIs(forbid_copy.data[-1].operation, parametric.data[-1].operation)
|
||||
# For standard gates a fresh copy is returned from the data list each time
|
||||
self.assertEqual(forbid_copy.data[-1].operation, parametric.data[-1].operation)
|
||||
|
||||
conditional = QuantumCircuit(1, 1)
|
||||
conditional.x(0).c_if(conditional.clbits[0], True)
|
||||
|
|
|
@ -577,14 +577,14 @@ class TestInstructions(QiskitTestCase):
|
|||
instructions.add(instruction, [Qubit()], [])
|
||||
register = ClassicalRegister(2)
|
||||
instructions.c_if(register, 0)
|
||||
self.assertIs(instruction.condition[0], register)
|
||||
self.assertIs(instructions[0].operation.condition[0], register)
|
||||
with self.subTest("accepts arbitrary bit"):
|
||||
instruction = RZGate(0)
|
||||
instructions = InstructionSet()
|
||||
instructions.add(instruction, [Qubit()], [])
|
||||
bit = Clbit()
|
||||
instructions.c_if(bit, 0)
|
||||
self.assertIs(instruction.condition[0], bit)
|
||||
self.assertIs(instructions[0].operation.condition[0], bit)
|
||||
with self.subTest("rejects index"):
|
||||
instruction = RZGate(0)
|
||||
instructions = InstructionSet()
|
||||
|
@ -617,7 +617,7 @@ class TestInstructions(QiskitTestCase):
|
|||
bit = Clbit()
|
||||
instructions.c_if(bit, 0)
|
||||
dummy_requester.assert_called_once_with(bit)
|
||||
self.assertIs(instruction.condition[0], sentinel_bit)
|
||||
self.assertIs(instructions[0].operation.condition[0], sentinel_bit)
|
||||
with self.subTest("calls requester with index"):
|
||||
dummy_requester.reset_mock()
|
||||
instruction = RZGate(0)
|
||||
|
@ -626,7 +626,7 @@ class TestInstructions(QiskitTestCase):
|
|||
index = 0
|
||||
instructions.c_if(index, 0)
|
||||
dummy_requester.assert_called_once_with(index)
|
||||
self.assertIs(instruction.condition[0], sentinel_bit)
|
||||
self.assertIs(instructions[0].operation.condition[0], sentinel_bit)
|
||||
with self.subTest("calls requester with register"):
|
||||
dummy_requester.reset_mock()
|
||||
instruction = RZGate(0)
|
||||
|
@ -635,7 +635,7 @@ class TestInstructions(QiskitTestCase):
|
|||
register = ClassicalRegister(2)
|
||||
instructions.c_if(register, 0)
|
||||
dummy_requester.assert_called_once_with(register)
|
||||
self.assertIs(instruction.condition[0], sentinel_register)
|
||||
self.assertIs(instructions[0].operation.condition[0], sentinel_register)
|
||||
with self.subTest("calls requester only once when broadcast"):
|
||||
dummy_requester.reset_mock()
|
||||
instruction_list = [RZGate(0), RZGate(0), RZGate(0)]
|
||||
|
@ -646,7 +646,7 @@ class TestInstructions(QiskitTestCase):
|
|||
instructions.c_if(register, 0)
|
||||
dummy_requester.assert_called_once_with(register)
|
||||
for instruction in instruction_list:
|
||||
self.assertIs(instruction.condition[0], sentinel_register)
|
||||
self.assertIs(instructions[0].operation.condition[0], sentinel_register)
|
||||
|
||||
def test_label_type_enforcement(self):
|
||||
"""Test instruction label type enforcement."""
|
||||
|
|
|
@ -102,7 +102,6 @@ class TestIsometry(QiskitTestCase):
|
|||
# Simulate the decomposed gate
|
||||
unitary = Operator(qc).data
|
||||
iso_from_circuit = unitary[::, 0 : 2**num_q_input]
|
||||
|
||||
self.assertTrue(np.allclose(iso_from_circuit, iso))
|
||||
|
||||
@data(
|
||||
|
|
|
@ -26,7 +26,7 @@ import qiskit.circuit.library as circlib
|
|||
from qiskit.circuit.library.standard_gates.rz import RZGate
|
||||
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
|
||||
from qiskit.circuit import Gate, Instruction, Parameter, ParameterExpression, ParameterVector
|
||||
from qiskit.circuit.parametertable import ParameterReferences, ParameterTable, ParameterView
|
||||
from qiskit.circuit.parametertable import ParameterView
|
||||
from qiskit.circuit.exceptions import CircuitError
|
||||
from qiskit.compiler import assemble, transpile
|
||||
from qiskit import pulse
|
||||
|
@ -45,8 +45,6 @@ def raise_if_parameter_table_invalid(circuit):
|
|||
CircuitError: if QuantumCircuit and ParameterTable are inconsistent.
|
||||
"""
|
||||
|
||||
table = circuit._parameter_table
|
||||
|
||||
# Assert parameters present in circuit match those in table.
|
||||
circuit_parameters = {
|
||||
parameter
|
||||
|
@ -55,7 +53,7 @@ def raise_if_parameter_table_invalid(circuit):
|
|||
for parameter in param.parameters
|
||||
if isinstance(param, ParameterExpression)
|
||||
}
|
||||
table_parameters = set(table._table.keys())
|
||||
table_parameters = set(circuit._data.get_params_unsorted())
|
||||
|
||||
if circuit_parameters != table_parameters:
|
||||
raise CircuitError(
|
||||
|
@ -67,8 +65,10 @@ def raise_if_parameter_table_invalid(circuit):
|
|||
# Assert parameter locations in table are present in circuit.
|
||||
circuit_instructions = [instr.operation for instr in circuit._data]
|
||||
|
||||
for parameter, instr_list in table.items():
|
||||
for instr, param_index in instr_list:
|
||||
for parameter in table_parameters:
|
||||
instr_list = circuit._data._get_param(parameter.uuid.int)
|
||||
for instr_index, param_index in instr_list:
|
||||
instr = circuit.data[instr_index].operation
|
||||
if instr not in circuit_instructions:
|
||||
raise CircuitError(f"ParameterTable instruction not present in circuit: {instr}.")
|
||||
|
||||
|
@ -88,13 +88,15 @@ def raise_if_parameter_table_invalid(circuit):
|
|||
)
|
||||
|
||||
# Assert circuit has no other parameter locations other than those in table.
|
||||
for instruction in circuit._data:
|
||||
for instr_index, instruction in enumerate(circuit._data):
|
||||
for param_index, param in enumerate(instruction.operation.params):
|
||||
if isinstance(param, ParameterExpression):
|
||||
parameters = param.parameters
|
||||
|
||||
for parameter in parameters:
|
||||
if (instruction.operation, param_index) not in table[parameter]:
|
||||
if (instr_index, param_index) not in circuit._data._get_param(
|
||||
parameter.uuid.int
|
||||
):
|
||||
raise CircuitError(
|
||||
"Found parameterized instruction not "
|
||||
"present in table. Instruction: {} "
|
||||
|
@ -158,15 +160,19 @@ class TestParameters(QiskitTestCase):
|
|||
self.assertIsNot(qc.data[-1].operation, gate_param)
|
||||
self.assertEqual(qc.data[-1].operation, gate_param)
|
||||
|
||||
# Standard gates are not stored as Python objects so a fresh object
|
||||
# is always instantiated on accessing `CircuitInstruction.operation`
|
||||
qc.append(gate_param, [0], copy=False)
|
||||
self.assertIs(qc.data[-1].operation, gate_param)
|
||||
self.assertEqual(qc.data[-1].operation, gate_param)
|
||||
|
||||
qc.append(gate_expr, [0], copy=True)
|
||||
self.assertIsNot(qc.data[-1].operation, gate_expr)
|
||||
self.assertEqual(qc.data[-1].operation, gate_expr)
|
||||
|
||||
# Standard gates are not stored as Python objects so a fresh object
|
||||
# is always instantiated on accessing `CircuitInstruction.operation`
|
||||
qc.append(gate_expr, [0], copy=False)
|
||||
self.assertIs(qc.data[-1].operation, gate_expr)
|
||||
self.assertEqual(qc.data[-1].operation, gate_expr)
|
||||
|
||||
def test_parameters_property(self):
|
||||
"""Test instantiating gate with variable parameters"""
|
||||
|
@ -177,10 +183,9 @@ class TestParameters(QiskitTestCase):
|
|||
qc = QuantumCircuit(qr)
|
||||
rxg = RXGate(theta)
|
||||
qc.append(rxg, [qr[0]], [])
|
||||
vparams = qc._parameter_table
|
||||
self.assertEqual(len(vparams), 1)
|
||||
self.assertIs(theta, next(iter(vparams)))
|
||||
self.assertEqual(rxg, next(iter(vparams[theta]))[0])
|
||||
self.assertEqual(qc._data.num_params(), 1)
|
||||
self.assertIs(theta, next(iter(qc._data.get_params_unsorted())))
|
||||
self.assertEqual(rxg, qc.data[next(iter(qc._data._get_param(theta.uuid.int)))[0]].operation)
|
||||
|
||||
def test_parameters_property_by_index(self):
|
||||
"""Test getting parameters by index"""
|
||||
|
@ -553,12 +558,12 @@ class TestParameters(QiskitTestCase):
|
|||
qc.rx(theta, 0)
|
||||
qc.ry(phi, 0)
|
||||
|
||||
self.assertEqual(len(qc._parameter_table[theta]), 1)
|
||||
self.assertEqual(len(qc._parameter_table[phi]), 1)
|
||||
self.assertEqual(qc._data._get_entry_count(theta), 1)
|
||||
self.assertEqual(qc._data._get_entry_count(phi), 1)
|
||||
|
||||
qc.assign_parameters({theta: -phi}, inplace=True)
|
||||
|
||||
self.assertEqual(len(qc._parameter_table[phi]), 2)
|
||||
self.assertEqual(qc._data._get_entry_count(phi), 2)
|
||||
|
||||
def test_expression_partial_binding_zero(self):
|
||||
"""Verify that binding remains possible even if a previous partial bind
|
||||
|
@ -580,7 +585,6 @@ class TestParameters(QiskitTestCase):
|
|||
fbqc = pqc.assign_parameters({phi: 1})
|
||||
|
||||
self.assertEqual(fbqc.parameters, set())
|
||||
self.assertIsInstance(fbqc.data[0].operation.params[0], int)
|
||||
self.assertEqual(float(fbqc.data[0].operation.params[0]), 0)
|
||||
|
||||
def test_raise_if_assigning_params_not_in_circuit(self):
|
||||
|
@ -614,7 +618,7 @@ class TestParameters(QiskitTestCase):
|
|||
qc.append(gate, [0], [])
|
||||
qc.append(gate, [0], [])
|
||||
qc2 = qc.assign_parameters({theta: 1.0})
|
||||
self.assertEqual(len(qc2._parameter_table), 0)
|
||||
self.assertEqual(qc2._data.num_params(), 0)
|
||||
for instruction in qc2.data:
|
||||
self.assertEqual(float(instruction.operation.params[0]), 1.0)
|
||||
|
||||
|
@ -2170,155 +2174,6 @@ class TestParameterEquality(QiskitTestCase):
|
|||
self.assertEqual(phi._parameter_symbols, cos_phi._parameter_symbols)
|
||||
|
||||
|
||||
class TestParameterReferences(QiskitTestCase):
|
||||
"""Test the ParameterReferences class."""
|
||||
|
||||
def test_equal_inst_diff_instance(self):
|
||||
"""Different value equal instructions are treated as distinct."""
|
||||
|
||||
theta = Parameter("theta")
|
||||
gate1 = RZGate(theta)
|
||||
gate2 = RZGate(theta)
|
||||
|
||||
self.assertIsNot(gate1, gate2)
|
||||
self.assertEqual(gate1, gate2)
|
||||
|
||||
refs = ParameterReferences(((gate1, 0), (gate2, 0)))
|
||||
|
||||
# test __contains__
|
||||
self.assertIn((gate1, 0), refs)
|
||||
self.assertIn((gate2, 0), refs)
|
||||
|
||||
gate_ids = {id(gate1), id(gate2)}
|
||||
self.assertEqual(gate_ids, {id(gate) for gate, _ in refs})
|
||||
self.assertTrue(all(idx == 0 for _, idx in refs))
|
||||
|
||||
def test_pickle_unpickle(self):
|
||||
"""Membership testing after pickle/unpickle."""
|
||||
|
||||
theta = Parameter("theta")
|
||||
gate1 = RZGate(theta)
|
||||
gate2 = RZGate(theta)
|
||||
|
||||
self.assertIsNot(gate1, gate2)
|
||||
self.assertEqual(gate1, gate2)
|
||||
|
||||
refs = ParameterReferences(((gate1, 0), (gate2, 0)))
|
||||
|
||||
to_pickle = (gate1, refs)
|
||||
pickled = pickle.dumps(to_pickle)
|
||||
(gate1_new, refs_new) = pickle.loads(pickled)
|
||||
|
||||
self.assertEqual(len(refs_new), len(refs))
|
||||
self.assertNotIn((gate1, 0), refs_new)
|
||||
self.assertIn((gate1_new, 0), refs_new)
|
||||
|
||||
def test_equal_inst_same_instance(self):
|
||||
"""Referentially equal instructions are treated as same."""
|
||||
|
||||
theta = Parameter("theta")
|
||||
gate = RZGate(theta)
|
||||
|
||||
refs = ParameterReferences(((gate, 0), (gate, 0)))
|
||||
|
||||
self.assertIn((gate, 0), refs)
|
||||
self.assertEqual(len(refs), 1)
|
||||
self.assertIs(next(iter(refs))[0], gate)
|
||||
self.assertEqual(next(iter(refs))[1], 0)
|
||||
|
||||
def test_extend_refs(self):
|
||||
"""Extending references handles duplicates."""
|
||||
|
||||
theta = Parameter("theta")
|
||||
ref0 = (RZGate(theta), 0)
|
||||
ref1 = (RZGate(theta), 0)
|
||||
ref2 = (RZGate(theta), 0)
|
||||
|
||||
refs = ParameterReferences((ref0,))
|
||||
refs |= ParameterReferences((ref0, ref1, ref2, ref1, ref0))
|
||||
|
||||
self.assertEqual(refs, ParameterReferences((ref0, ref1, ref2)))
|
||||
|
||||
def test_copy_param_refs(self):
|
||||
"""Copy of parameter references is a shallow copy."""
|
||||
|
||||
theta = Parameter("theta")
|
||||
ref0 = (RZGate(theta), 0)
|
||||
ref1 = (RZGate(theta), 0)
|
||||
ref2 = (RZGate(theta), 0)
|
||||
ref3 = (RZGate(theta), 0)
|
||||
|
||||
refs = ParameterReferences((ref0, ref1))
|
||||
refs_copy = refs.copy()
|
||||
|
||||
# Check same gate instances in copy
|
||||
gate_ids = {id(ref0[0]), id(ref1[0])}
|
||||
self.assertEqual({id(gate) for gate, _ in refs_copy}, gate_ids)
|
||||
|
||||
# add new ref to original and check copy not modified
|
||||
refs.add(ref2)
|
||||
self.assertNotIn(ref2, refs_copy)
|
||||
self.assertEqual(refs_copy, ParameterReferences((ref0, ref1)))
|
||||
|
||||
# add new ref to copy and check original not modified
|
||||
refs_copy.add(ref3)
|
||||
self.assertNotIn(ref3, refs)
|
||||
self.assertEqual(refs, ParameterReferences((ref0, ref1, ref2)))
|
||||
|
||||
|
||||
class TestParameterTable(QiskitTestCase):
|
||||
"""Test the ParameterTable class."""
|
||||
|
||||
def test_init_param_table(self):
|
||||
"""Parameter table init from mapping."""
|
||||
|
||||
p1 = Parameter("theta")
|
||||
p2 = Parameter("theta")
|
||||
|
||||
ref0 = (RZGate(p1), 0)
|
||||
ref1 = (RZGate(p1), 0)
|
||||
ref2 = (RZGate(p2), 0)
|
||||
|
||||
mapping = {p1: ParameterReferences((ref0, ref1)), p2: ParameterReferences((ref2,))}
|
||||
|
||||
table = ParameterTable(mapping)
|
||||
|
||||
# make sure editing mapping doesn't change `table`
|
||||
del mapping[p1]
|
||||
|
||||
self.assertEqual(table[p1], ParameterReferences((ref0, ref1)))
|
||||
self.assertEqual(table[p2], ParameterReferences((ref2,)))
|
||||
|
||||
def test_set_references(self):
|
||||
"""References replacement by parameter key."""
|
||||
|
||||
p1 = Parameter("theta")
|
||||
|
||||
ref0 = (RZGate(p1), 0)
|
||||
ref1 = (RZGate(p1), 0)
|
||||
|
||||
table = ParameterTable()
|
||||
table[p1] = ParameterReferences((ref0, ref1))
|
||||
self.assertEqual(table[p1], ParameterReferences((ref0, ref1)))
|
||||
|
||||
table[p1] = ParameterReferences((ref1,))
|
||||
self.assertEqual(table[p1], ParameterReferences((ref1,)))
|
||||
|
||||
def test_set_references_from_iterable(self):
|
||||
"""Parameter table init from iterable."""
|
||||
|
||||
p1 = Parameter("theta")
|
||||
|
||||
ref0 = (RZGate(p1), 0)
|
||||
ref1 = (RZGate(p1), 0)
|
||||
ref2 = (RZGate(p1), 0)
|
||||
|
||||
table = ParameterTable({p1: ParameterReferences((ref0, ref1))})
|
||||
table[p1] = (ref2, ref1, ref0)
|
||||
|
||||
self.assertEqual(table[p1], ParameterReferences((ref2, ref1, ref0)))
|
||||
|
||||
|
||||
class TestParameterView(QiskitTestCase):
|
||||
"""Test the ParameterView object."""
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2024
|
||||
#
|
||||
# This code is licensed under the Apache License, Version 2.0. You may
|
||||
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
||||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
#
|
||||
# Any modifications or derivative works of this code must retain this
|
||||
# copyright notice, and modified files need to carry a notice indicating
|
||||
# that they have been altered from the originals.
|
||||
|
||||
"""Rust gate definition tests"""
|
||||
|
||||
from math import pi
|
||||
|
||||
from test import QiskitTestCase
|
||||
|
||||
import numpy as np
|
||||
|
||||
from qiskit.circuit import QuantumCircuit
|
||||
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
|
||||
|
||||
SKIP_LIST = {"cy", "ccx", "rx", "ry", "ecr", "sx"}
|
||||
CUSTOM_MAPPING = {"x", "rz"}
|
||||
|
||||
|
||||
class TestRustGateEquivalence(QiskitTestCase):
|
||||
"""Tests that compile time rust gate definitions is correct."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.standard_gates = get_standard_gate_name_mapping()
|
||||
# Pre-warm gate mapping cache, this is needed so rust -> py conversion is done
|
||||
qc = QuantumCircuit(3)
|
||||
for gate in self.standard_gates.values():
|
||||
if getattr(gate, "_standard_gate", None):
|
||||
if gate.params:
|
||||
gate = gate.base_class(*[pi] * len(gate.params))
|
||||
qc.append(gate, list(range(gate.num_qubits)))
|
||||
|
||||
def test_definitions(self):
|
||||
"""Test definitions are the same in rust space."""
|
||||
for name, gate_class in self.standard_gates.items():
|
||||
standard_gate = getattr(gate_class, "_standard_gate", None)
|
||||
if name in SKIP_LIST:
|
||||
# gate does not have a rust definition yet
|
||||
continue
|
||||
if standard_gate is None:
|
||||
# gate is not in rust yet
|
||||
continue
|
||||
|
||||
with self.subTest(name=name):
|
||||
params = [pi] * standard_gate._num_params()
|
||||
py_def = gate_class.base_class(*params).definition
|
||||
rs_def = standard_gate._get_definition(params)
|
||||
if py_def is None:
|
||||
self.assertIsNone(rs_def)
|
||||
else:
|
||||
rs_def = QuantumCircuit._from_circuit_data(rs_def)
|
||||
for rs_inst, py_inst in zip(rs_def._data, py_def._data):
|
||||
# Rust uses U but python still uses U3 and u2
|
||||
if rs_inst.operation.name == "u":
|
||||
if py_inst.operation.name == "u3":
|
||||
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
|
||||
elif py_inst.operation.name == "u2":
|
||||
self.assertEqual(
|
||||
rs_inst.operation.params,
|
||||
[
|
||||
pi / 2,
|
||||
py_inst.operation.params[0],
|
||||
py_inst.operation.params[1],
|
||||
],
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[py_def.find_bit(x).index for x in py_inst.qubits],
|
||||
[rs_def.find_bit(x).index for x in rs_inst.qubits],
|
||||
)
|
||||
# Rust uses P but python still uses u1
|
||||
elif rs_inst.operation.name == "p":
|
||||
self.assertEqual(py_inst.operation.name, "u1")
|
||||
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
|
||||
self.assertEqual(
|
||||
[py_def.find_bit(x).index for x in py_inst.qubits],
|
||||
[rs_def.find_bit(x).index for x in rs_inst.qubits],
|
||||
)
|
||||
else:
|
||||
self.assertEqual(py_inst.operation.name, rs_inst.operation.name)
|
||||
self.assertEqual(rs_inst.operation.params, py_inst.operation.params)
|
||||
self.assertEqual(
|
||||
[py_def.find_bit(x).index for x in py_inst.qubits],
|
||||
[rs_def.find_bit(x).index for x in rs_inst.qubits],
|
||||
)
|
||||
|
||||
def test_matrix(self):
|
||||
"""Test matrices are the same in rust space."""
|
||||
for name, gate_class in self.standard_gates.items():
|
||||
standard_gate = getattr(gate_class, "_standard_gate", None)
|
||||
if standard_gate is None:
|
||||
# gate is not in rust yet
|
||||
continue
|
||||
|
||||
with self.subTest(name=name):
|
||||
params = [pi] * standard_gate._num_params()
|
||||
py_def = gate_class.base_class(*params).to_matrix()
|
||||
rs_def = standard_gate._to_matrix(params)
|
||||
np.testing.assert_allclose(rs_def, py_def)
|
||||
|
||||
def test_name(self):
|
||||
"""Test that the gate name properties match in rust space."""
|
||||
for name, gate_class in self.standard_gates.items():
|
||||
standard_gate = getattr(gate_class, "_standard_gate", None)
|
||||
if standard_gate is None:
|
||||
# gate is not in rust yet
|
||||
continue
|
||||
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(gate_class.name, standard_gate.name)
|
||||
|
||||
def test_num_qubits(self):
|
||||
"""Test the number of qubits are the same in rust space."""
|
||||
for name, gate_class in self.standard_gates.items():
|
||||
standard_gate = getattr(gate_class, "_standard_gate", None)
|
||||
if standard_gate is None:
|
||||
# gate is not in rust yet
|
||||
continue
|
||||
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(gate_class.num_qubits, standard_gate.num_qubits)
|
||||
|
||||
def test_num_params(self):
|
||||
"""Test the number of parameters are the same in rust space."""
|
||||
for name, gate_class in self.standard_gates.items():
|
||||
standard_gate = getattr(gate_class, "_standard_gate", None)
|
||||
if standard_gate is None:
|
||||
# gate is not in rust yet
|
||||
continue
|
||||
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(
|
||||
len(gate_class.params), standard_gate.num_params, msg=f"{name} not equal"
|
||||
)
|
|
@ -1946,7 +1946,7 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
|
|||
def test_basis_gates(self):
|
||||
"""Teleportation with physical qubits"""
|
||||
qc = QuantumCircuit(3, 2)
|
||||
first_h = qc.h(1)[0].operation
|
||||
qc.h(1)
|
||||
qc.cx(1, 2)
|
||||
qc.barrier()
|
||||
qc.cx(0, 1)
|
||||
|
@ -1957,52 +1957,51 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
|
|||
first_x = qc.x(2).c_if(qc.clbits[1], 1)[0].operation
|
||||
qc.z(2).c_if(qc.clbits[0], 1)
|
||||
|
||||
u2 = first_h.definition.data[0].operation
|
||||
u3_1 = u2.definition.data[0].operation
|
||||
u3_2 = first_x.definition.data[0].operation
|
||||
|
||||
expected_qasm = "\n".join(
|
||||
[
|
||||
"OPENQASM 3.0;",
|
||||
f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
|
||||
" U(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u2_{id(u2)}(_gate_p_0, _gate_p_1) _gate_q_0 {{",
|
||||
f" u3_{id(u3_1)}(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"gate h _gate_q_0 {",
|
||||
f" u2_{id(u2)}(0, pi) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
|
||||
" U(pi, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"gate x _gate_q_0 {",
|
||||
f" u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"bit[2] c;",
|
||||
"qubit[3] q;",
|
||||
"h q[1];",
|
||||
"cx q[1], q[2];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"cx q[0], q[1];",
|
||||
"h q[0];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"c[0] = measure q[0];",
|
||||
"c[1] = measure q[1];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"if (c[1]) {",
|
||||
" x q[2];",
|
||||
"}",
|
||||
"if (c[0]) {",
|
||||
" z q[2];",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
self.assertEqual(
|
||||
Exporter(includes=[], basis_gates=["cx", "z", "U"]).dumps(qc),
|
||||
expected_qasm,
|
||||
)
|
||||
id_len = len(str(id(first_x)))
|
||||
expected_qasm = [
|
||||
"OPENQASM 3.0;",
|
||||
re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
|
||||
" U(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len),
|
||||
re.compile(r" u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
"gate h _gate_q_0 {",
|
||||
re.compile(r" u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
|
||||
" U(pi, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"gate x _gate_q_0 {",
|
||||
re.compile(r" u3_\d{%s}\(pi, 0, pi\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
"bit[2] c;",
|
||||
"qubit[3] q;",
|
||||
"h q[1];",
|
||||
"cx q[1], q[2];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"cx q[0], q[1];",
|
||||
"h q[0];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"c[0] = measure q[0];",
|
||||
"c[1] = measure q[1];",
|
||||
"barrier q[0], q[1], q[2];",
|
||||
"if (c[1]) {",
|
||||
" x q[2];",
|
||||
"}",
|
||||
"if (c[0]) {",
|
||||
" z q[2];",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
res = Exporter(includes=[], basis_gates=["cx", "z", "U"]).dumps(qc).splitlines()
|
||||
for result, expected in zip(res, expected_qasm):
|
||||
if isinstance(expected, str):
|
||||
self.assertEqual(result, expected)
|
||||
else:
|
||||
self.assertTrue(
|
||||
expected.search(result), f"Line {result} doesn't match regex: {expected}"
|
||||
)
|
||||
|
||||
def test_teleportation(self):
|
||||
"""Teleportation with physical qubits"""
|
||||
|
@ -2120,62 +2119,58 @@ class TestCircuitQASM3ExporterTemporaryCasesWithBadParameterisation(QiskitTestCa
|
|||
circuit.sx(0)
|
||||
circuit.cx(0, 1)
|
||||
|
||||
rz = circuit.data[0].operation
|
||||
u1_1 = rz.definition.data[0].operation
|
||||
u3_1 = u1_1.definition.data[0].operation
|
||||
sx = circuit.data[1].operation
|
||||
sdg = sx.definition.data[0].operation
|
||||
u1_2 = sdg.definition.data[0].operation
|
||||
u3_2 = u1_2.definition.data[0].operation
|
||||
h_ = sx.definition.data[1].operation
|
||||
u2_1 = h_.definition.data[0].operation
|
||||
u3_3 = u2_1.definition.data[0].operation
|
||||
expected_qasm = "\n".join(
|
||||
[
|
||||
"OPENQASM 3.0;",
|
||||
f"gate u3_{id(u3_1)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
|
||||
" U(0, 0, pi/2) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u1_{id(u1_1)}(_gate_p_0) _gate_q_0 {{",
|
||||
f" u3_{id(u3_1)}(0, 0, pi/2) _gate_q_0;",
|
||||
"}",
|
||||
f"gate rz_{id(rz)}(_gate_p_0) _gate_q_0 {{",
|
||||
f" u1_{id(u1_1)}(pi/2) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u3_{id(u3_2)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
|
||||
" U(0, 0, -pi/2) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u1_{id(u1_2)}(_gate_p_0) _gate_q_0 {{",
|
||||
f" u3_{id(u3_2)}(0, 0, -pi/2) _gate_q_0;",
|
||||
"}",
|
||||
"gate sdg _gate_q_0 {",
|
||||
f" u1_{id(u1_2)}(-pi/2) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u3_{id(u3_3)}(_gate_p_0, _gate_p_1, _gate_p_2) _gate_q_0 {{",
|
||||
" U(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
f"gate u2_{id(u2_1)}(_gate_p_0, _gate_p_1) _gate_q_0 {{",
|
||||
f" u3_{id(u3_3)}(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"gate h _gate_q_0 {",
|
||||
f" u2_{id(u2_1)}(0, pi) _gate_q_0;",
|
||||
"}",
|
||||
"gate sx _gate_q_0 {",
|
||||
" sdg _gate_q_0;",
|
||||
" h _gate_q_0;",
|
||||
" sdg _gate_q_0;",
|
||||
"}",
|
||||
"gate cx c, t {",
|
||||
" ctrl @ U(pi, 0, pi) c, t;",
|
||||
"}",
|
||||
"qubit[2] q;",
|
||||
f"rz_{id(rz)}(pi/2) q[0];",
|
||||
"sx q[0];",
|
||||
"cx q[0], q[1];",
|
||||
"",
|
||||
]
|
||||
)
|
||||
self.assertEqual(Exporter(includes=[]).dumps(circuit), expected_qasm)
|
||||
id_len = len(str(id(circuit.data[0].operation)))
|
||||
expected_qasm = [
|
||||
"OPENQASM 3.0;",
|
||||
re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
|
||||
" U(0, 0, pi/2) _gate_q_0;",
|
||||
"}",
|
||||
re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
|
||||
re.compile(r" u3_\d{%s}\(0, 0, pi/2\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
re.compile(r"gate rz_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
|
||||
re.compile(r" u1_\d{%s}\(pi/2\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
|
||||
" U(0, 0, -pi/2) _gate_q_0;",
|
||||
"}",
|
||||
re.compile(r"gate u1_\d{%s}\(_gate_p_0\) _gate_q_0 \{" % id_len),
|
||||
re.compile(r" u3_\d{%s}\(0, 0, -pi/2\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
"gate sdg _gate_q_0 {",
|
||||
re.compile(r" u1_\d{%s}\(-pi/2\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
re.compile(r"gate u3_\d{%s}\(_gate_p_0, _gate_p_1, _gate_p_2\) _gate_q_0 \{" % id_len),
|
||||
" U(pi/2, 0, pi) _gate_q_0;",
|
||||
"}",
|
||||
re.compile(r"gate u2_\d{%s}\(_gate_p_0, _gate_p_1\) _gate_q_0 \{" % id_len),
|
||||
re.compile(r" u3_\d{%s}\(pi/2, 0, pi\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
"gate h _gate_q_0 {",
|
||||
re.compile(r" u2_\d{%s}\(0, pi\) _gate_q_0;" % id_len),
|
||||
"}",
|
||||
"gate sx _gate_q_0 {",
|
||||
" sdg _gate_q_0;",
|
||||
" h _gate_q_0;",
|
||||
" sdg _gate_q_0;",
|
||||
"}",
|
||||
"gate cx c, t {",
|
||||
" ctrl @ U(pi, 0, pi) c, t;",
|
||||
"}",
|
||||
"qubit[2] q;",
|
||||
re.compile(r"rz_\d{%s}\(pi/2\) q\[0\];" % id_len),
|
||||
"sx q[0];",
|
||||
"cx q[0], q[1];",
|
||||
"",
|
||||
]
|
||||
res = Exporter(includes=[]).dumps(circuit).splitlines()
|
||||
for result, expected in zip(res, expected_qasm):
|
||||
if isinstance(expected, str):
|
||||
self.assertEqual(result, expected)
|
||||
else:
|
||||
self.assertTrue(
|
||||
expected.search(result), f"Line {result} doesn't match regex: {expected}"
|
||||
)
|
||||
|
||||
def test_unusual_conditions(self):
|
||||
"""Test that special QASM constructs such as ``measure`` are correctly handled when the
|
||||
|
|
Loading…
Reference in New Issue