Skip to content

Commit 75002e3

Browse files
Cryorisjakelishman
andauthored
Enable parameterized gates in C (#15106)
* Add parameterized gates * fix C tests * review comments Jake * add reno * fix cbindgen * Fix namings * Trying to retrigger GitHub * Store pointers to parameter copies QkCircuitInstruction stores copies of the PackedInstruction data, not references to it. This also holds for the parameters. * Apply suggestions from code review Co-authored-by: Jake Lishman <jake@binhbar.com> * Jake's review comments * Fix circuit comparison * Apply suggestions from code review Co-authored-by: Jake Lishman <jake@binhbar.com> * Satisfy clippy --------- Co-authored-by: Jake Lishman <jake@binhbar.com> Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
1 parent ff2ee35 commit 75002e3

6 files changed

Lines changed: 396 additions & 18 deletions

File tree

crates/cext/src/circuit.rs

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use num_complex::{Complex64, ComplexFloat};
2323

2424
use qiskit_circuit::bit::{ClassicalRegister, QuantumRegister};
2525
use qiskit_circuit::bit::{ShareableClbit, ShareableQubit};
26-
use qiskit_circuit::circuit_data::CircuitData;
26+
use qiskit_circuit::circuit_data::{CircuitData, CircuitDataError};
2727
use qiskit_circuit::dag_circuit::DAGCircuit;
2828
use qiskit_circuit::instruction::Parameters;
2929
use qiskit_circuit::interner::Interner;
@@ -32,6 +32,7 @@ use qiskit_circuit::operations::{
3232
PauliProductRotation, StandardGate, StandardInstruction, UnitaryGate,
3333
};
3434
use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation};
35+
use qiskit_circuit::parameter_table::ParameterTableError;
3536
use qiskit_circuit::{BlocksMode, Clbit, Qubit, VarsMode};
3637
use smallvec::smallvec;
3738

@@ -339,6 +340,46 @@ pub unsafe extern "C" fn qk_circuit_num_clbits(circuit: *const CircuitData) -> u
339340
circuit.num_clbits() as u32
340341
}
341342

343+
/// @ingroup QkCircuit
344+
/// Get the number of unbound symbols in ``QkParam`` objects in the circuit.
345+
///
346+
/// @param circuit A pointer to the circuit.
347+
///
348+
/// @return The number of unbound ``QkParam`` symbols in the circuit.
349+
///
350+
/// # Example
351+
///
352+
/// ```c
353+
/// QkCircuit *qc = qk_circuit_new(2, 0);
354+
/// QkParam *x = qk_param_new_symbol("x");
355+
/// QkParam *y = qk_param_new_symbol("y");
356+
///
357+
/// uint32_t q0[1] = {0};
358+
/// const QkParam *rx_param[1] = {x};
359+
/// const QkParam *ry_param[1] = {y};
360+
///
361+
/// qk_circuit_parameterized_gate(qc, QkGate_RX, q0, rx_param);
362+
/// qk_circuit_parameterized_gate(qc, QkGate_RY, q0, ry_param);
363+
///
364+
/// // check the number of QkParam symbols
365+
/// size_t num_symbols = qk_circuit_num_param_symbols(qc); // == 2
366+
///
367+
/// qk_param_free(x);
368+
/// qk_param_free(y);
369+
/// qk_circuit_free(qc);
370+
/// ```
371+
///
372+
/// # Safety
373+
///
374+
/// Behavior is undefined if ``circuit`` is not a valid, non-null pointer to a ``QkCircuit``.
375+
#[unsafe(no_mangle)]
376+
pub unsafe extern "C" fn qk_circuit_num_param_symbols(circuit: *const CircuitData) -> usize {
377+
// SAFETY: Per documentation, the pointer is non-null and aligned.
378+
let circuit = unsafe { const_ptr_as_ref(circuit) };
379+
380+
circuit.num_parameters()
381+
}
382+
342383
/// @ingroup QkCircuit
343384
/// Free the circuit.
344385
///
@@ -457,6 +498,95 @@ pub unsafe extern "C" fn qk_circuit_gate(
457498
ExitCode::Success
458499
}
459500

501+
/// @ingroup QkCircuit
502+
/// Append a ``QkGate`` with ``QkParam*`` parameters to the circuit.
503+
///
504+
/// @param circuit A pointer to the circuit to add the gate to.
505+
/// @param gate The ``QkGate`` to add to the circuit.
506+
/// @param qubits The pointer to the array of ``uint32_t`` qubit indices to add the gate on. This
507+
/// can be a null pointer if there are no qubits for ``gate`` (e.g. ``QkGate_GlobalPhase``).
508+
/// @param params The pointer to the array of ``QkParam*`` parameters to use for the gate parameters.
509+
/// This can be a null pointer if there are no parameters for ``gate`` (e.g. ``QkGate_H``).
510+
///
511+
/// @return ``QkExitCode_Success`` upon successful append. Upon failure,
512+
/// ``QkExitCode_ParameterNameConflict`` indicates that a new parameter symbol has a name
513+
/// conflict with an existing one. ``QkExitCode_ParameterError`` describes other generic
514+
/// failures when attempting to track the parameter symbols.
515+
///
516+
/// # Example
517+
///
518+
/// ```c
519+
/// QkCircuit *qc = qk_circuit_new(100, 0);
520+
/// QkParam *theta = qk_param_new_symbol("theta");
521+
/// uint32_t qubit[1] = {0};
522+
/// const QkParam* params[1] = {theta};
523+
/// qk_circuit_parameterized_gate(qc, QkGate_RX, qubit, params); // add RX(theta) to the circuit
524+
/// ```
525+
///
526+
/// # Safety
527+
///
528+
/// The ``qubits`` and ``params`` types are expected to be a pointer to an array of ``uint32_t``
529+
/// and ``QkParam*`` respectively where the length is matching the expectations for the standard
530+
/// gate. If the array is insufficiently long the behavior of this function is undefined as this
531+
/// will read outside the bounds of the array. It can be a null pointer if there are no qubits
532+
/// or params for a given gate. You can check ``qk_gate_num_qubits`` and ``qk_gate_num_params`` to
533+
/// determine how many qubits and params are required for a given gate.
534+
///
535+
/// Behavior is undefined if ``circuit`` is not a valid, non-null pointer to a ``QkCircuit``,
536+
/// or if any of the elements in the ``params`` array is not a valid, non-null pointer to a
537+
/// ``QkParam``.
538+
#[unsafe(no_mangle)]
539+
pub unsafe extern "C" fn qk_circuit_parameterized_gate(
540+
circuit: *mut CircuitData,
541+
gate: StandardGate,
542+
qubits: *const u32,
543+
params: *const *const Param,
544+
) -> ExitCode {
545+
// SAFETY: Per documentation, the pointer is non-null and aligned.
546+
let circuit = unsafe { mut_ptr_as_ref(circuit) };
547+
548+
// SAFETY: Per the documentation the qubits pointer is an array of num_qubits() elements.
549+
let qargs: &[Qubit] = unsafe {
550+
match gate.num_qubits() {
551+
0 => &[],
552+
1 => &[Qubit(*qubits.wrapping_add(0))],
553+
2 => &[
554+
Qubit(*qubits.wrapping_add(0)),
555+
Qubit(*qubits.wrapping_add(1)),
556+
],
557+
3 => &[
558+
Qubit(*qubits.wrapping_add(0)),
559+
Qubit(*qubits.wrapping_add(1)),
560+
Qubit(*qubits.wrapping_add(2)),
561+
],
562+
4 => &[
563+
Qubit(*qubits.wrapping_add(0)),
564+
Qubit(*qubits.wrapping_add(1)),
565+
Qubit(*qubits.wrapping_add(2)),
566+
Qubit(*qubits.wrapping_add(3)),
567+
],
568+
// There are no ``QkGate``s > 4 qubits
569+
_ => unreachable!(),
570+
}
571+
};
572+
573+
// SAFETY: Per documentation, the params pointer is an array of num_params() elements, and
574+
// each element is a valid, non-null pointer to a Param.
575+
let params: Vec<Param> =
576+
unsafe { ::std::slice::from_raw_parts(params, gate.num_params() as usize) }
577+
.iter()
578+
.map(|&ptr| unsafe { const_ptr_as_ref(ptr) }.clone())
579+
.collect();
580+
581+
match circuit.push_standard_gate(gate, &params, qargs) {
582+
Ok(()) => ExitCode::Success,
583+
Err(CircuitDataError::ParameterTableError(ParameterTableError::NameConflict(_))) => {
584+
ExitCode::ParameterNameConflict
585+
}
586+
Err(_) => ExitCode::ParameterError,
587+
}
588+
}
589+
460590
/// @ingroup QkCircuit
461591
/// Get the number of qubits for a ``QkGate``.
462592
///
@@ -941,7 +1071,7 @@ pub struct CInstruction {
9411071
/// A pointer to an array of clbit indices this instruction operates on.
9421072
clbits: *mut u32,
9431073
/// A pointer to an array of parameter values for this instruction.
944-
params: *mut f64,
1074+
params: *mut *mut Param,
9451075
/// The number of qubits for this instruction.
9461076
num_qubits: u32,
9471077
/// The number of clbits for this instruction.
@@ -956,8 +1086,9 @@ impl CInstruction {
9561086
/// This must be cleared by a call to `qk_circuit_instruction_clear` to avoid leaking its
9571087
/// allocations.
9581088
///
959-
/// Panics if the operation name contains a nul, or if the instruction has non-float parameters.
960-
pub(crate) fn from_packed_instruction_with_floats(
1089+
/// Panics if the operation name contains a nul, or if the instruction has non-numeric
1090+
/// parameters (not [Param::Float] or [Param::ParameterExpression]).
1091+
pub(crate) fn from_packed_instruction_with_numeric(
9611092
packed: &PackedInstruction,
9621093
qargs_interner: &Interner<[Qubit]>,
9631094
cargs_interner: &Interner<[Clbit]>,
@@ -971,11 +1102,13 @@ impl CInstruction {
9711102
.params_view()
9721103
.iter()
9731104
.map(|p| match p {
974-
Param::Float(p) => Some(*p),
1105+
Param::Float(_) | Param::ParameterExpression(_) => {
1106+
Some(Box::into_raw(Box::new(p.clone())))
1107+
}
9751108
_ => None,
9761109
})
977-
.collect::<Option<Box<[f64]>>>()
978-
.expect("caller is responsible for ensuring all parameters are floats");
1110+
.collect::<Option<Box<[*mut Param]>>>()
1111+
.expect("caller is responsible for ensuring all parameters are numeric");
9791112
Self {
9801113
name,
9811114
num_qubits: qargs.len() as u32,
@@ -1027,7 +1160,7 @@ pub unsafe extern "C" fn qk_circuit_get_instruction(
10271160
) {
10281161
// SAFETY: Per documentation, `circuit` is a pointer to valid data.
10291162
let circuit = unsafe { const_ptr_as_ref(circuit) };
1030-
let inst = CInstruction::from_packed_instruction_with_floats(
1163+
let inst = CInstruction::from_packed_instruction_with_numeric(
10311164
&circuit.data()[index],
10321165
circuit.qargs_interner(),
10331166
circuit.cargs_interner(),
@@ -1325,7 +1458,10 @@ pub unsafe extern "C" fn qk_circuit_instruction_clear(inst: *mut CInstruction) {
13251458
inst.num_clbits = 0;
13261459
if inst.num_params > 0 && !inst.params.is_null() {
13271460
let params = std::slice::from_raw_parts_mut(inst.params, inst.num_params as usize);
1328-
let _ = Box::from_raw(params as *mut [f64]);
1461+
for param in params.iter() {
1462+
let _ = Box::from_raw(*param);
1463+
}
1464+
let _ = Box::from_raw(params);
13291465
inst.params = std::ptr::null_mut();
13301466
}
13311467
inst.num_params = 0;

crates/cext/src/dag.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,7 @@ pub unsafe extern "C" fn qk_dag_get_instruction(
12111211
) {
12121212
// SAFETY: per documentation, `dag` is a pointer to valid data.
12131213
let dag = unsafe { const_ptr_as_ref(dag) };
1214-
let inst = CInstruction::from_packed_instruction_with_floats(
1214+
let inst = CInstruction::from_packed_instruction_with_numeric(
12151215
dag.dag()[NodeIndex::new(index as usize)].unwrap_operation(),
12161216
dag.qargs_interner(),
12171217
dag.cargs_interner(),

crates/cext/src/exit_codes.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ pub enum ExitCode {
6868
DagComposeMismatch = 501,
6969
/// One or more bit indices were not found during compose.
7070
DagComposeMissingBit = 502,
71+
/// Errors concerning parameter handling.
72+
ParameterError = 600,
73+
/// Parameter name conflict.
74+
ParameterNameConflict = 601,
7175
}
7276

7377
impl From<ArithmeticError> for ExitCode {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
features_c:
3+
- |
4+
Added support to add parameterized gates to circuits using the new
5+
:c:func:`qk_circuit_gate_param` function, which specifies the gate parameters as
6+
``QkParam *`` objects. For example:
7+
8+
.. code-block:: c
9+
10+
QkCircuit *qc = qk_circuit_new(100, 0);
11+
QkParam *theta = qk_param_new_symbol("theta");
12+
uint32_t qubit[1] = {0};
13+
const QkParam* params[1] = {theta};
14+
qk_circuit_parameterized_gate(qc, QkGate_RX, qubit, params); // add RX(theta) to the circuit
15+
16+
- |
17+
Added :c:func:`qk_circuit_num_param_symbols` to query the number of unbound symbols present
18+
in the circuit.
19+
upgrade_c:
20+
- |
21+
:c:member:`QkInstruction.params` was changed from a pointer to ``double`` to a pointer
22+
to ``const QkParam *`` objects. This change happened because gate parameters in the
23+
C API are no longer restricted to fixed numeric values, but may also be parameterized
24+
expressions with unbound symbols, which are represented by a ``QkParam *``.

test/c/common.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,14 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) {
126126
return false;
127127
}
128128
for (uint32_t j = 0; j < res_inst.num_params; j++) {
129-
if (res_inst.params[j] != expected_inst.params[j]) {
130-
printf("Parameter %d for gate %zu are different %f was found and expected %f\n", j,
131-
i, res_inst.params[j], expected_inst.params[j]);
129+
if (!qk_param_equal(res_inst.params[j], expected_inst.params[j])) {
130+
char *res_str = qk_param_str(res_inst.params[j]);
131+
char *expected_str = qk_param_str(expected_inst.params[j]);
132+
133+
printf("Parameter %d for gate %zu are different %s was found and expected %s\n", j,
134+
i, res_str, expected_str);
135+
qk_str_free(res_str);
136+
qk_str_free(expected_str);
132137
qk_circuit_instruction_clear(&res_inst);
133138
qk_circuit_instruction_clear(&expected_inst);
134139
return false;

0 commit comments

Comments
 (0)