Skip to content

Add Split2qUnitaries to C API#14720

Merged
raynelfss merged 13 commits into
Qiskit:mainfrom
mtreinish:split-c-2q-unitary
Aug 22, 2025
Merged

Add Split2qUnitaries to C API#14720
raynelfss merged 13 commits into
Qiskit:mainfrom
mtreinish:split-c-2q-unitary

Conversation

@mtreinish

@mtreinish mtreinish commented Jul 9, 2025

Copy link
Copy Markdown
Member

Summary

This commit adds a standalone transpiler pass function to the C API for
running the Split2qUnitaries transpiler pass. This pass mutates the dag
in place if there is no swap splitting that occurs (either by user
option or because there are no applicable gates) or returns a new dag
with a permutation. This result is encapsulated in a
Split2qUnitariesResult struct that contains the circuit and the
permutation.

One small change was needed to the dag circuit and the rust code of the
pass to enable running it without python. A py token was used for
updating the instruction cache when splitting instructions. This was
mostly a leftover from when unitary gate was a PyGate where this
mattered, but now there isn't anything to prepopulate. But also it
wasn't ever necessary in the case of a python defined operation it would
do the ref count update implicitly and populate the cache on the first
access. This commit removes the python usage for this which simplifies
the code and also enables running this pass from standalone C.

Details and comments

Fixes #14448

TODO:

  • Expand test coverage

This PR is based on top of #14668 will need to be rebased once #14668 merges. In the meantime you can review just the contents of this PR by looking at the HEAD commit on the PR: 0e582b7 This has been rebased now

@mtreinish mtreinish added this to the 2.2.0 milestone Jul 9, 2025
@mtreinish mtreinish requested a review from a team as a code owner July 9, 2025 21:43
@mtreinish mtreinish added on hold Can not fix yet Changelog: Added Add an "Added" entry in the GitHub Release changelog. mod: transpiler Issues and PRs related to Transpiler C API Related to the C API labels Jul 9, 2025
@qiskit-bot

Copy link
Copy Markdown
Collaborator

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

@coveralls

coveralls commented Jul 9, 2025

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 17153328607

Details

  • 32 of 35 (91.43%) changed or added relevant lines in 3 files are covered.
  • 23 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.006%) to 88.429%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/cext/src/transpiler/passes/split_2q_unitaries.rs 27 30 90.0%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 5 92.01%
crates/qasm2/src/parse.rs 18 96.62%
Totals Coverage Status
Change from base Build 17143354507: 0.006%
Covered Lines: 89843
Relevant Lines: 101599

💛 - Coveralls

@raynelfss raynelfss self-assigned this Jul 22, 2025
@mtreinish mtreinish force-pushed the split-c-2q-unitary branch from 0e582b7 to d1f83e7 Compare July 30, 2025 19:58
@mtreinish mtreinish removed the on hold Can not fix yet label Jul 30, 2025
@mtreinish mtreinish changed the title [WIP] Add Split2qUnitaries to C API Add Split2qUnitaries to C API Jul 30, 2025
This commit adds a standalone transpiler pass function to the C API for
running the Split2qUnitaries transpiler pass. This pass mutates the dag
in place if there is no swap splitting that occurs (either by user
option or because there are no applicable gates) or returns a new dag
with a permutation. This result is encapsulated in a
Split2qUnitariesResult struct that contains the circuit and the
permutation.

One small change was needed to the dag circuit and the rust code of the
pass to enable running it without python. A py token was used for
updating the instruction cache when splitting instructions. This was
mostly a leftover from when unitary gate was a PyGate where this
mattered, but now there isn't anything to prepopulate. But also it
wasn't ever necessary in the case of a python defined operation it would
do the ref count update implicitly and populate the cache on the first
access. This commit removes the python usage for this which simplifies
the code and also enables running this pass from standalone C.

Fixes Qiskit#14448
@mtreinish

Copy link
Copy Markdown
Member Author

This is ready for review now, I've rebased it on main now that the pre-reqs have merged and added the missing test coverage.

@mtreinish mtreinish mentioned this pull request Aug 17, 2025
13 tasks

@jakelishman jakelishman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I stopped looking through the C tests at the point of the last comment, but I suspect it'd just be the same comments in the last test too.

Comment on lines +20 to +24
/// The result from ``qk_transpiler_pass_standalone_split_2q_unitaries()``.
pub struct Split2qUnitariesResult {
circuit: CircuitData,
permutation: Vec<u32>,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will interact awkwardly with the new push to having TranspileLayout always store the output permutation. Perhaps we want to do a similar thing with this pass as we're doing for SabreLayout, but it's starting to get a bit awkward about whether we want to expose a stand-alone way of calling the pass if we can't either a) pass a TranspileLayout from pass to pass or b) provide methods on TranspileLayout in C to update the tracking data based on new changes.

I feel like our options are going to be:

  • don't bother with standalone functions like this one until we have a better answer for exactly what we'll do
  • claim "standalone passes can't be composed because you don't get the full pipeline tracking" so the current API is fine
  • extend the C API such that you could reasonably use the methods sequentially to emulate the Qiskit pipeline, just with loads of extra dag_to_circuit(do_pass(circuit_to_dag(qc))) everywhere

Personally I'd tend towards either the first or second options - I don't think the standalone passes are necessarily all super high priority. I think they're useful as a research direction, and for incrementally moving us towards full exposure to C, so that's an argument in favour of point 2 over point 1.

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 think we've discussed this in several other PRs, and settled on option 2. We'll want to figure out how to do this as part of exposing composable transpilation pipelines in C.

Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment on lines +91 to +92
let result = unsafe { const_ptr_as_ref(result) };
result.permutation.as_ptr()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we actually have to produce the &Split2qUnitariesResult here? I vaguely thought we could just unsafe { result.permutation }.as_ptr(), with all the undefined behaviour that goes with that.

I'm totally cool if we deliberately want explicit panics on bad accesses, though.

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 was changed in: ae02b37 it returns a null pointer if there is no permutation and a pointer to a transpile layout if there is one.

/// qk_circuit_gate(qc, QkGate_CX, qargs, NULL);
/// }
/// }
/// QkSplit2qUnitariesResult *result = qk_transpiler_pass_standalone_split_2q_unitaries(qc, 1.0 - 1e-16, true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's probably neither here nor there, but the closest double-precision floating-point value to 1.0 - 1e-16 is actually more like 1.11e-16 - saying 1e-16 is actually kind of wrong by over 10%. Are we actually able to hit a precision that's just 1ULP?

Comment on lines -6833 to +6835
#[cfg(feature = "cache_pygates")]
let py_op = match new_op.view() {
OperationRef::StandardGate(_)
| OperationRef::StandardInstruction(_)
| OperationRef::Unitary(_) => OnceLock::new(),
OperationRef::Gate(gate) => OnceLock::from(gate.gate.clone_ref(py)),
OperationRef::Instruction(instruction) => {
OnceLock::from(instruction.instruction.clone_ref(py))
}
OperationRef::Operation(op) => OnceLock::from(op.operation.clone_ref(py)),
};
let inst = PackedInstruction {
op: new_op,
qubits: self.qargs_interner.insert_owned(qubits),
clbits: self.cargs_interner.get_default(),
params: (!params.is_empty()).then(|| Box::new(params)),
label: None,
#[cfg(feature = "cache_pygates")]
py_op,
py_op: OnceLock::new(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This might have been better as a standalone patch, but let's not bother now.

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.

Heh, this I think is the reason this is needed for #14760

Comment thread test/c/test_split_2q_unitaries.c Outdated
Comment thread test/c/test_split_2q_unitaries.c Outdated
Comment thread test/c/test_split_2q_unitaries.c Outdated
Comment thread test/c/test_split_2q_unitaries.c

@raynelfss raynelfss 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.

This is looking good, I just have a couple of comments.

Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread docs/cdoc/index.h
Comment thread test/c/test_split_2q_unitaries.c
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs

@raynelfss raynelfss 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.

This looks about ready, just a couple small comments.

Comment thread test/c/test_split_2q_unitaries.c Outdated
Comment thread test/c/test_split_2q_unitaries.c Outdated
Comment thread test/c/test_split_2q_unitaries.c Outdated
Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
@mtreinish mtreinish requested a review from raynelfss August 21, 2025 20:41

@jakelishman jakelishman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Other than minor stuff, this is fine by me.

Comment thread crates/cext/src/transpiler/passes/mod.rs
Comment thread crates/cext/src/transpiler/passes/split_2q_unitaries.rs Outdated
Comment thread test/c/test_split_2q_unitaries.c
Comment thread test/c/test_split_2q_unitaries.c
@mtreinish mtreinish requested a review from jakelishman August 22, 2025 10:52

@jakelishman jakelishman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the changes - I think this is fine now (though tbh, I've looked at it enough times that my eyes are kind of glossing over). I'll let Ray press the merge button if he's happy too.

@raynelfss raynelfss added this pull request to the merge queue Aug 22, 2025
Merged via the queue into Qiskit:main with commit 72cb5a2 Aug 22, 2025
25 checks passed
littlebullGit pushed a commit to littlebullGit/qiskit that referenced this pull request Sep 5, 2025
* Add Split2qUnitaries to C API

This commit adds a standalone transpiler pass function to the C API for
running the Split2qUnitaries transpiler pass. This pass mutates the dag
in place if there is no swap splitting that occurs (either by user
option or because there are no applicable gates) or returns a new dag
with a permutation. This result is encapsulated in a
Split2qUnitariesResult struct that contains the circuit and the
permutation.

One small change was needed to the dag circuit and the rust code of the
pass to enable running it without python. A py token was used for
updating the instruction cache when splitting instructions. This was
mostly a leftover from when unitary gate was a PyGate where this
mattered, but now there isn't anything to prepopulate. But also it
wasn't ever necessary in the case of a python defined operation it would
do the ref count update implicitly and populate the cache on the first
access. This commit removes the python usage for this which simplifies
the code and also enables running this pass from standalone C.

Fixes Qiskit#14448

* Pivot to returning TranspileLayout instead of custom return type

* Fix memory leak in test

* Add missing newlines from error strings

* Apply suggestions from code review

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* Fix docstring example

* Fix error message on internal pass failure

* Add missing QkTranspileLayout free in failure mode where it shouldn't exist

* Fix failure message in test where permutation doesn't exist and is expected

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
aaryav-3 pushed a commit to aaryav-3/qiskit that referenced this pull request Oct 21, 2025
* Add Split2qUnitaries to C API

This commit adds a standalone transpiler pass function to the C API for
running the Split2qUnitaries transpiler pass. This pass mutates the dag
in place if there is no swap splitting that occurs (either by user
option or because there are no applicable gates) or returns a new dag
with a permutation. This result is encapsulated in a
Split2qUnitariesResult struct that contains the circuit and the
permutation.

One small change was needed to the dag circuit and the rust code of the
pass to enable running it without python. A py token was used for
updating the instruction cache when splitting instructions. This was
mostly a leftover from when unitary gate was a PyGate where this
mattered, but now there isn't anything to prepopulate. But also it
wasn't ever necessary in the case of a python defined operation it would
do the ref count update implicitly and populate the cache on the first
access. This commit removes the python usage for this which simplifies
the code and also enables running this pass from standalone C.

Fixes Qiskit#14448

* Pivot to returning TranspileLayout instead of custom return type

* Fix memory leak in test

* Add missing newlines from error strings

* Apply suggestions from code review

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* Fix docstring example

* Fix error message on internal pass failure

* Add missing QkTranspileLayout free in failure mode where it shouldn't exist

* Fix failure message in test where permutation doesn't exist and is expected

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C API Related to the C API Changelog: Added Add an "Added" entry in the GitHub Release changelog. mod: transpiler Issues and PRs related to Transpiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Split2qUnitaries to the C API

6 participants