Skip to content

Conversation

@Ameya28Bhave
Copy link
Contributor

Title: Expose IQP circuit generator via C bindings

Summary

This PR exposes the IQP circuit generator from qiskit-circuit-library through the C API and adds C tests for the new entry points.

Changes

  • crates/circuit_library/src/iqp.rs

    • Make the internal helper
      pub fn iqp(interactions: ArrayView2<'_, i64>) -> impl Iterator<...>
      public so it can be reused by other crates without duplicating the instruction construction logic.
  • crates/cext/src/circuit_library/iqp.rs

    • Add
      QkCircuit *qk_circuit_library_iqp_(uint32_t num_qubits, const int64_t *interactions);
      • Interprets interactions as an n × n row-major int64_t matrix.
      • Checks for NULL pointer, num_qubits > 0, and enforces symmetry on the upper triangle.
      • Wraps the buffer in an ArrayView2 and builds CircuitData using the shared iqp helper.
      • Returns NULL for invalid input (null pointer, zero qubits, or non-symmetric matrix).
    • Add
      QkCircuit *qk_circuit_library_random_iqp_(uint32_t num_qubits, int64_t seed);
      • Wraps py_random_iqp(num_qubits, Option<u64>) to generate a random IQP circuit.
      • Uses a negative seed to indicate “draw from OS entropy”, mirroring existing C bindings.
  • test/c/test_iqp.c

    • Add C-level tests for the new bindings:
      • Build an IQP circuit from a small symmetric interaction matrix and check:
        • total instruction count,
        • that all gates are 1- or 2-qubit,
        • that exactly one 2-qubit gate appears for the chosen matrix.
      • Verify that a non-symmetric interaction matrix causes qk_circuit_library_iqp_ to return NULL.
      • Generate a random IQP circuit and assert it is non-empty and contains only 1- and 2-qubit gates.

@Ameya28Bhave Ameya28Bhave requested a review from a team as a code owner November 29, 2025 18:18
@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Nov 29, 2025
@qiskit-bot
Copy link
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

@coveralls
Copy link

coveralls commented Nov 29, 2025

Pull Request Test Coverage Report for Build 19807786529

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 36 of 43 (83.72%) changed or added relevant lines in 2 files are covered.
  • 740 unchanged lines in 7 files lost coverage.
  • Overall coverage increased (+0.01%) to 88.374%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/cext/src/circuit_library/iqp.rs 35 42 83.33%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 2 91.52%
crates/circuit/src/parameter_table.rs 6 91.71%
crates/qasm2/src/parse.rs 6 96.15%
crates/circuit/src/circuit_instruction.rs 40 84.18%
crates/circuit/src/circuit_data.rs 59 89.55%
crates/circuit/src/operations.rs 157 86.76%
crates/circuit/src/dag_circuit.rs 470 84.64%
Totals Coverage Status
Change from base Build 19740043964: 0.01%
Covered Lines: 95689
Relevant Lines: 108277

💛 - Coveralls

@Cryoris Cryoris self-assigned this Nov 30, 2025
@Cryoris
Copy link
Contributor

Cryoris commented Nov 30, 2025

Thanks for opening the PR, Ameya! Just FYI, you don't need to add a detailed changelog like that usually. It's nice to summarize the PR if it is a complicated change, but here it would've been fine without (or much shorter) 🙂

/// - `view` is square and symmetric.
/// - Any error from `from_standard_gates` is considered unreachable for valid
/// inputs, so we use `unwrap()` like other cext code.
fn iqp_from_view(view : ArrayView2<'_, i64>) -> CircuitData {
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like a legacy function from an earlier design -- why not just move this into the main function, since it's just 2 lines (or effectively one)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point — I’ve removed iqp_from_view and now call CircuitData::from_standard_gates(..., iqp(view), …) directly in qk_circuit_library_iqp.

/// matrix is not symmetric.
#[unsafe(no_mangle)]
#[cfg(feature = "cbinding")]
pub extern "C" fn qk_circuit_library_iqp_(
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we remove the final underscore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the C API names to qk_circuit_library_iqp and qk_circuit_library_random_iqp (no trailing underscore) and adjusted the header + tests accordingly.


let num_qubits = num_qubits as usize;
if num_qubits == 0 {
return std::ptr::null_mut();
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it would be valid to return a QkCircuit* object here, but just with 0 qubits.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handled num_qubits == 0 by returning an empty but valid circuit via from_standard_gates(0, std::iter::empty(), Param::Float(0.0)) instead of `NULL.

/// - A newly allocated `QkCircuit*` (caller must free with `qk_circuit_free`).
#[unsafe(no_mangle)]
#[cfg(feature = "cbinding")]
pub extern "C" fn qk_circuit_library_random_iqp_(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here on the final _ 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

nrows as u32,
iqp(view),
Param::Float(0.0)
).unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you replace the unwraps with expects that state what broke? E.g. here we could have .expect("Failed building the circuit from IQP data.") or something along those lines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done — replaced the unwrap()s with expect(...) messages in both the IQP and random IQP paths.

// Wrap the flat buffer as an `ndarray` view with shape (n, n).
let view = ndarray::ArrayView2::from_shape((num_qubits, num_qubits), buf).unwrap();

// Symmetry on upper triangle only.
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have a slight preference to avoid data verification here, since we're targeting a performant C interface. If you think it would be helpful we could add a flag to optionally include checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a check_input bool parameter: when true we run the symmetry check and return NULL on mismatch; when false we skip validation for the fast path.

return std::ptr::null_mut();
}

// SAFETY: caller guarantees at least n*n elements are readable.
Copy link
Contributor

Choose a reason for hiding this comment

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

What's n? 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Clarified the docstring so that n is explicitly defined as num_qubits in the description of the interactions matrix.

@Cryoris Cryoris added this to the 2.4.0 milestone Nov 30, 2025
@Cryoris Cryoris added the C API Related to the C API label Nov 30, 2025
// Optional symmetry check on the upper triangle only.
if check_input {
let mut i = 0;
while i < num_qubits {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use for-loops? 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually -- there's already a symmetry check in the Python call for IQP, see py_iqp. It would be cleaner to just move this (optional) check into the iqp function and have it in a single place. There's an existing check_symmetric function there, too.

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 Community PR PRs from contributors that are not 'members' of the Qiskit repo

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

4 participants