qiskit/crates/circuit
Kevin Hartman 8ec4c9cf58
Unify identifer handling of `Var` and `Stretch` in `DAGCircuit` (#14000)
* s

* Rename BitData to ObjectRegistry.

* s

* Update tests for new error string.

This is only really a user-facing error message when
working with DAGCircuit, since QuantumCircuit first
checks if the bits being added to it are duplicates.
And, in the case of DAGCircuit, the previous error
message was already unfriendly:

ValueError: Existing bit ShareableQubit(Owned { register: OwningRegisterInfo { name: "q16", size: 2, subclass: QUBIT }, index: 0 }) cannot be re-added in strict mode.

* Fix comment in commutation checker.

* Fix lint.

* Address review comments.

* Unify identifier handling in DAG.

Tracks stretches the same way we track vars.
Also happens to fix a bug in DAG equality where
order mattered between stretch captures (it should
never have). And, fixes a serialization bug with
stretches.

* Fix tests.

* Improve testing and fix bug.

* Add pickle and deepcopy tests for stretches.

Also fix lint.

* Undo breaking name change.

* Add release note.

* Fix lint.

* Fix Python lint.

* Address review comments.
2025-04-10 18:38:46 +00:00
..
src Unify identifer handling of `Var` and `Stretch` in `DAGCircuit` (#14000) 2025-04-10 18:38:46 +00:00
Cargo.toml Move `Bit` and `Register` to live in Rust space (#13860) 2025-03-07 21:31:52 +00:00
README.md Add infrastructure for gates, instruction, and operations in Rust (#12459) 2024-06-13 10:48:40 +00:00

README.md

qiskit-circuit

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.