Skip to content

Remove phase from CircuitData::with_capacity#15571

Open
jakelishman wants to merge 5 commits into
Qiskit:mainfrom
jakelishman:infallible-capacity
Open

Remove phase from CircuitData::with_capacity#15571
jakelishman wants to merge 5 commits into
Qiskit:mainfrom
jakelishman:infallible-capacity

Conversation

@jakelishman

Copy link
Copy Markdown
Member

Summary

This makes the function infallible (as it should be!). The only real failure mechanism of the function is by passing a global-phase parameter of an incorrect Param variant. When the global-phase is known to be either a symbol expression or a float, setting the global phase on an empty circuit is guaranteed to be infallible; setting the global phase to a float is always infallible, while setting the global phase to a symbolic expression can only fail if there are duplicated symbol names. This cannot happen for empty circuits, since ParameterExpression enforces name uniqueness in the same way that CircuitData does.

Details and comments

Built on #15570 and #15562. This is where I've been going with the infallibility - it was seriously annoying me that we were forcing awkward unwrap or fallibility checks on basic circuit construction.

Setting the global phase of either `CircuitData` or `DAGCirucit` to a
floating-point value is infallible, but we didn't previously expose a
way to do that.  This is a small step on the way to making more of our
Rust-native operations statically provably infallible.
This modifies the error handling in `ObjectRegistry` to make the error
returns from the functions more explicit, and to add methods that allow
short-hand assertions about the allowed failure modes.  This is a
stepping stone to making more circuit functions infallible when called
from Rust space.
When we can statically state that the qubits we're adding are new
anonymous qubits, the only way the qubit-adding functions can fail is by
going over capacity.  This commit exposes a way to do this safely,
reducing one of the failure modes of `CircuitData::with_capacity`, which
can provably not error during bit expansion because the maximums of the
`num_qubits` and `num_clbits` arguments are within the capacity.
This makes the function infallible (as it _should_ be!).  The only real
failure mechanism of the function is by passing a global-phase parameter
of an incorrect `Param` variant.  When the global-phase is known to be
either a symbol expression or a float, setting the global phase on an
empty circuit is guaranteed to be infallible; setting the global phase
to a float is _always_ infallible, while setting the global phase to a
symbolic expression can only fail if there are duplicated symbol names.
This cannot happen for empty circuits, since `ParameterExpression`
enforces name uniqueness in the same way that `CircuitData` does.
@jakelishman jakelishman added this to the 2.4.0 milestone Jan 14, 2026
@jakelishman jakelishman requested a review from a team as a code owner January 14, 2026 13:28
@jakelishman jakelishman added type: qa Issues and PRs that relate to testing and code quality Changelog: None Do not include in the GitHub Release changelog. Rust This PR or issue is related to Rust code in the repository mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library labels Jan 14, 2026
@qiskit-bot

Copy link
Copy Markdown
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core
  • @levbishop

@jakelishman jakelishman added the on hold Can not fix yet label Jan 14, 2026
@coveralls

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 20995839947

Details

  • 177 of 196 (90.31%) changed or added relevant lines in 16 files are covered.
  • 12 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.002%) to 88.313%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/synthesis/src/discrete_basis/basic_approximations.rs 3 4 75.0%
crates/circuit/src/lib.rs 0 3 0.0%
crates/circuit/src/object_registry.rs 31 38 81.58%
crates/circuit/src/circuit_data.rs 87 95 91.58%
Files with Coverage Reduction New Missed Lines %
crates/circuit/src/parameter/parameter_expression.rs 1 82.3%
crates/qasm2/src/lex.rs 5 92.03%
crates/qasm2/src/parse.rs 6 96.62%
Totals Coverage Status
Change from base Build 20969635435: -0.002%
Covered Lines: 96944
Relevant Lines: 109773

💛 - Coveralls

@gadial gadial left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some minor comments, overall seems good to me.

#[pyo3(name="add_qubit", signature = (bit, *, strict=true))]
pub fn py_add_qubit(&mut self, bit: ShareableQubit, strict: bool) -> PyResult<()> {
Ok(self.add_qubit(bit, strict)?)
#[pyo3(signature = (bit, *, strict=true))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't object to this change, I'm interested as to why we are doing it - i.e. consolidating what before was a rust method + thin py wrapper into a single function (here and in other instances) - was there a reason for the wrapper approach?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is actually in #15570, which in turn is built on #15564 - we might want to look at those separately first.

In short, though - I suspect Matt added the separation (it's very recent) not realising that you're allowed to return anything that implements Into<PyErr> in the error type, or he was thinking about a possible conditional compilation of the Python components of CircuitData. For exposing more things to C safely, though, and better control-flow sharing, we'll need more than just a separation of the pymethods and regular methods, so I'm not too worried.

I can separate them out again if you prefer.


pub fn set_global_phase_f64(&mut self, phase: f64) {
self.clear_global_phase();
self.global_phase = Param::Float(phase.rem_euclid(::std::f64::consts::TAU));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this modulo? Is it possible that a user will be interested in setting a large global phase, losing this data as a result?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because we've just always done this - this isn't a behaviour change.

// Add an object to the registry, panicking if it is a duplicate or out of capacity.
pub fn add_unique_within_capacity(&mut self, object: B) -> T {
self.add(object)
.unwrap_or_else(|e| panic!("{}", e.to_string()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use expect here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do - I can modify #15564 to do that.

@jakelishman jakelishman Feb 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed this in #15564, which is the base PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: None Do not include in the GitHub Release changelog. mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library on hold Can not fix yet Rust This PR or issue is related to Rust code in the repository type: qa Issues and PRs that relate to testing and code quality

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

5 participants