Skip to content

Migrate synth_mcx_{1,2}_kg24 to Rust - #16783

Open
ellabarkan wants to merge 14 commits into
Qiskit:mainfrom
ellabarkan:rust_implementation_synth_mcx_kg24
Open

Migrate synth_mcx_{1,2}_kg24 to Rust #16783
ellabarkan wants to merge 14 commits into
Qiskit:mainfrom
ellabarkan:rust_implementation_synth_mcx_kg24

Conversation

@ellabarkan

@ellabarkan ellabarkan commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates synth_mcx_1_kg24 (1 ancilla, linear depth) and synth_mcx_2_kg24 (2 ancillae,
logarithmic depth) from pure-Python QuantumCircuit construction to Rust. Both functions implement the
Khattar-Gidney 2024 conditionally-clean-ancilla constructions. No API changes were required.

Changes Made

Implementation:

  • Rust implementation of synth_mcx_1_kg24 and synth_mcx_2_kg24 (including helpers
    linear_depth_ladder_ops, log_depth_ladder_ops, and synth_mcx_2_finish) in
    crates/synthesis/src/multi_controlled/mcx.rs
  • Bindings via qiskit._accelerate.synthesis.multi_controlled (py_synth_mcx_1_kg24,
    py_synth_mcx_2_kg24)
  • Python implementation replaced — full Rust migration
  • All six public Python wrappers (synth_mcx_1_kg24, synth_mcx_1_clean_kg24,
    synth_mcx_1_dirty_kg24, synth_mcx_2_kg24, synth_mcx_2_clean_kg24,
    synth_mcx_2_dirty_kg24) now delegate to Rust via QuantumCircuit._from_circuit_data(...)

Algorithm Details:

  • 1-ancilla, linear-depth (Sec. 5.1/5.3 of [1]): prime a single ancilla as conditionally
    clean with one RCCX on the first two controls, fold the remaining controls through a
    linear-depth ladder of RCCX+X gates, apply one CCX to the target, then unfold/unprime. For
    a dirty ancilla, the fold → CCX → unfold portion repeats a second time (toggle detection);
    priming/unpriming happens once.
  • 2-ancilla, log-depth (Sec. 5.2/5.4 of [1]): the same prime/unprime bookend, but controls
    are folded through a log-depth binary AND-tree (successive rounds of pairwise RCCX gates
    that double the pool of reusable conditionally-clean qubits), giving O(log k) depth. The
    finish step is either a single CCX or a recursive call into the 1-ancilla construction over
    the leftover controls plus a second ancilla.

Reference:
Khattar and Gidney, Rise of conditionally clean ancillae for optimizing quantum circuits,
arXiv:2407.17966

Performance

Benchmarking compares the Rust implementation (this branch) against the original Python
version
(main branch), measured with timeit.repeat (see methodology below).

synth_mcx_1_kg24 — clean ancilla:

k Rust (µs) Python (µs) Speedup
50 215.00 527.97 2.46x
100 448.60 974.32 2.17x
500 2103.63 5216.21 2.48x
1000 4309.85 10320.14 2.39x

synth_mcx_1_kg24 — dirty ancilla:

k Rust (µs) Python (µs) Speedup
50 265.58 791.03 2.98x
100 579.65 1580.68 2.73x
500 2787.14 7451.01 2.67x
1000 5472.10 14971.03 2.74x

synth_mcx_2_kg24 — clean ancilla:

k Rust (µs) Python (µs) Speedup
50 227.12 957.09 4.21x
100 472.84 1609.29 3.40x
500 2189.05 5790.66 2.65x
1000 4363.73 10832.93 2.48x

synth_mcx_2_kg24 — dirty ancilla:

k Rust (µs) Python (µs) Speedup
50 287.07 1760.60 6.13x
100 558.02 2981.86 5.34x
500 2700.36 11462.13 4.24x
1000 5366.03 20985.92 3.91x

Result: The 1-ancilla construction delivers a consistent 2.2–3.0x speedup across all
tested control counts (k = 50–1000) in both clean and dirty modes. The 2-ancilla construction
shows a larger, scale-dependent gain: 2.5–4.2x for clean ancilla and 3.9–6.1x for dirty
ancilla
, with the largest improvements at small k — consistent with the Python version's
relatively higher per-round compose overhead in the log-depth construction shrinking faster
than the absolute operation count grows.

Benchmark Methodology

The code for performance estimation:

import timeit
from qiskit.synthesis.multi_controlled import synth_mcx_1_kg24, synth_mcx_2_kg24

K_VALUES = [50, 100, 500, 1000]

def number_for(k):
    if k <= 100:
        return 100
    if k <= 500:
        return 50
    return 20

def _time_per_call_us(fn, k, clean, number) -> float:
    """Mean time per single call in microseconds across timeit.repeat trials."""
    samples = timeit.repeat(lambda: fn(k, clean), number=number, repeat=7)
    return (sum(samples) / len(samples) / number) * 1e6

for k in K_VALUES:
    n = number_for(k)
    print(f"k={k:>4} clean | {_time_per_call_us(synth_mcx_1_kg24, k, True,  n):.2f}µs")
    print(f"k={k:>4} dirty | {_time_per_call_us(synth_mcx_1_kg24, k, False, n):.2f}µs")

AI/LLM disclosure

  • I didn't use LLM tooling, or only used it privately.
  • I used the following tool to help write this PR description: Claude (Sonnet 4.5, via Bob/Claude Code)
  • I used the following tool to generate or modify code: Claude (Sonnet 4.5, via Bob/Claude Code) — used for the Rust implementation and subsequent review/cleanup passes.

@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Aug 16, 2026
@ellabarkan ellabarkan changed the title [WIP] Migrate synth_mcx_{1,2}_kg24 to Rust Migrate synth_mcx_{1,2}_kg24 to Rust Aug 16, 2026
@ellabarkan
ellabarkan marked this pull request as ready for review August 16, 2026 08:40
@ellabarkan
ellabarkan requested a review from a team as a code owner August 16, 2026 08:40
@qiskit-bot

Copy link
Copy Markdown
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

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

  • @Qiskit/terra-core

@ShellyGarion
ShellyGarion self-requested a review August 16, 2026 08:47
@ShellyGarion ShellyGarion removed the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Aug 16, 2026
@ShellyGarion ShellyGarion added this to the 2.6.0 milestone Aug 16, 2026
@ShellyGarion ShellyGarion added synthesis Rust This PR or issue is related to Rust code in the repository performance Changelog: Performance Performance improvements without API and semantic changes. labels Aug 16, 2026

@ShellyGarion ShellyGarion 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 @ellabarkan for the contribution! the PR looks good, I have some minor comments on the documentation.

- |
Improved the runtime performance of :py:func:`~qiskit.synthesis.synth_mcx_1_kg24` and
:py:func:`~qiskit.synthesis.synth_mcx_2_kg24` (and their ``_clean``/``_dirty`` variants)
by migrating the implementation to Rust. These functions synthesize multi-controlled X

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.

the functions ~qiskit.synthesis.synth_mcx_1_kg24 and ~qiskit.synthesis.synth_mcx_2_kg24 are only internal (and not part of the public API), so they should not be mentioned in the release notes.

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.

also you can write :func: and not :py:func:

@ellabarkan ellabarkan Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in commit 0117e8b

// Step 1: turn the ancilla into a "conditionally clean" qubit holding
// AND(control_0, control_1). RCCX is used (rather than CCX) because its stray
// relative phase is harmless: it will cancel against the same RCCX's inverse
// in step 5.

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 explanation into steps. could you put the ref to the relevant part of the paper? or image?

@ellabarkan ellabarkan Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in commit 0117e8b

Ok((qc, leftover_ctrls))
}

/// Flips `target` iff AND(ancilla0, leftover_ctrls...) holds (Step 3 of [1]).

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.

where is Step 3 of [1]? Step 3 of which part of the paper?

@ellabarkan ellabarkan Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in commit 0117e8b

// Step 1: prime -- turn ancilla0 into a conditionally clean qubit holding
// AND(control_0, control_1). RCCX is used (rather than CCX) because its stray
// relative phase is harmless: it will cancel against the same RCCX's inverse
// in step 5.

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.

again, could you refer to the specific part of the paper with the steps?

@ellabarkan ellabarkan Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in commit 0117e8b

@ShellyGarion ShellyGarion Aug 16, 2026

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.

perhaps it's worth to update this docstring (of synth_mcx_1_kg24) similarly to synth_mcx_2_kg24 ?

@ellabarkan ellabarkan Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in commit 0117e8b

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

Hello! I'd like to help make your code a little bit more Rusty.

NIT: I always lean toward intuitively named functions and variable names over many inline comments. Knowing nothing about the algorithm, this could be harder than I'm thinking.

Comment thread crates/synthesis/src/multi_controlled/mcx.rs Outdated
Comment thread crates/synthesis/src/multi_controlled/mcx.rs Outdated
Comment thread crates/synthesis/src/multi_controlled/mcx.rs Outdated
Comment thread crates/synthesis/src/multi_controlled/mcx.rs Outdated
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 32118024013

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.004%) to 87.793%

Details

  • Coverage increased (+0.004%) from the base build.
  • Patch coverage: 7 uncovered changes across 2 files (173 of 180 lines covered, 96.11%).
  • 20 coverage regressions across 5 files.

Uncovered Changes

File Changed Covered %
crates/synthesis/src/multi_controlled/mcx.rs 165 160 96.97%
qiskit/synthesis/multi_controlled/mcx_synthesis.py 7 5 71.43%
Total (3 files) 180 173 96.11%

Coverage Regressions

20 previously-covered lines in 5 files lost coverage.

File Lines Losing Coverage Coverage
crates/qasm2/src/parse.rs 6 97.63%
crates/qasm2/src/lex.rs 5 92.03%
crates/circuit_library/src/multi_local.rs 3 98.16%
qiskit/synthesis/multi_controlled/mcp_synthesis.py 3 95.65%
qiskit/synthesis/multi_controlled/mcx_synthesis.py 3 80.68%

Coverage Stats

Coverage Status
Relevant Lines: 129706
Covered Lines: 113873
Line Coverage: 87.79%
Coverage Strength: 998772.9 hits per line

💛 - Coveralls

@ellabarkan

Copy link
Copy Markdown
Contributor Author

@ShellyGarion I have resolved the issue with the differences in circuit depth in log_depth_ladder implementation (2 ancilla quibits version) and updated the docstings in python API as we discussed

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

Labels

Changelog: Performance Performance improvements without API and semantic changes. performance Rust This PR or issue is related to Rust code in the repository synthesis

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

5 participants