Skip to content

Commit 1b10653

Browse files
committed
Add substitute_node_with_dag to c api
This commit adds a new function qk_dag_substitute_node_with_dag to the C api. This facilitates in place substitute from the new dag api. This is fairly mechanical addition to the api as `DAGCircuit::substitute_node_with_dag()` is already rust native. So we only need to expose that inteface to C. The only difference between the C and existing internal rust interface is that the qubit, clbit, and var mapping arguments are not exposed to C. These options aren't commonly used, and if the user needs to map the ordering they can do that during replacement dag construction rather that at substitution time. Fixes #15190
1 parent 7138ea9 commit 1b10653

File tree

7 files changed

+147
-1
lines changed

7 files changed

+147
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ rand_pcg = "0.9"
3838
rand_distr = "0.5"
3939
num-traits = "0.2"
4040
uuid = { version = "1.18", features = ["v4", "fast-rng"], default-features = false }
41+
anyhow = "1.0"
4142

4243
# Most of the crates don't need the feature `extension-module`, since only `qiskit-pyext` builds an
4344
# actual C extension (the feature disables linking in `libpython`, which is forbidden in Python

crates/cext/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ nalgebra.workspace = true
3030
hashbrown.workspace = true
3131
rand.workspace = true
3232
rand_pcg.workspace = true
33+
anyhow.workspace = true
3334

3435
[build-dependencies]
3536
cbindgen = "0.29"

crates/cext/src/dag.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
// copyright notice, and modified files need to carry a notice indicating
1111
// that they have been altered from the originals.
1212

13+
use anyhow::Error;
1314
use num_complex::Complex64;
1415
use smallvec::smallvec;
16+
use std::ffi::{CString, c_char};
1517

1618
use qiskit_circuit::Qubit;
1719
use qiskit_circuit::bit::{ClassicalRegister, QuantumRegister};
@@ -23,6 +25,7 @@ use qiskit_circuit::operations::{
2325
};
2426

2527
use crate::circuit::unitary_from_pointer;
28+
use crate::exit_codes::ExitCode;
2629
use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref};
2730

2831
/// @ingroup QkDag
@@ -968,3 +971,96 @@ pub unsafe extern "C" fn qk_dag_topological_op_nodes(dag: *const DAGCircuit, out
968971
unsafe { out_order.add(i).write(node.index() as u32) }
969972
}
970973
}
974+
975+
/// @ingroup QkDag
976+
/// Replace a node in a `QkDag` with a subcircuit specfied by another `QkDag`
977+
///
978+
/// @param dag A pointer to the DAG.
979+
/// @param node The node index of the operation to replace with the other `QkDag`. This
980+
/// must be the node index for an operation node in ``dag`` and the qargs and cargs
981+
/// count must match the number of qubits and clbits in `replacement`.
982+
/// @param replacement The other `QkDag` to replace `node` with. This dag must have
983+
/// the same number of qubits as the operation for ``node``. The node
984+
/// bit ordering will be ordering will be handled in order, so `qargs[0]` for
985+
/// `node` will be mapped to `qubits[0]` in `replacement`, `qargs[1]` to
986+
/// `qubits[0]`, etc. The same pattern applies to classical bits too.
987+
/// @param error A pointer to a pointer with an nul terminated string with an
988+
/// error description. If the function fails a pointer to the string with
989+
/// the error description will be written to this pointer. That pointer
990+
/// needs to be freed with `qk_str_free`. This can be a null pointer in
991+
/// which case the error will not be written out.
992+
///
993+
/// @returns The return code for the operation, ``QkExitCode_Success`` means success and all
994+
/// other values indicate an error.
995+
///
996+
/// # Example
997+
///
998+
/// ```c
999+
/// QkDag *dag = qk_dag_new();
1000+
/// QkQuantumRegister *qr = qk_quantum_register_new(1, "my_register");
1001+
/// qk_dag_add_quantum_register(dag, qr);
1002+
///
1003+
/// uint32_t qubit[1] = {0};
1004+
/// uint32_t node_to_replace = qk_dag_apply_gate(dag, QkGate_H, qubit, NULL, false);
1005+
/// qk_dag_apply_gate(dag, QkGate_S, qubit, NULL, false);
1006+
///
1007+
/// // Build replacement dag for H
1008+
/// QkDag *replacement = qk_dag_new();
1009+
/// QkQuantumRegister *replacement_qr = qk_quantum_register_new(1, "other");
1010+
/// qk_dag_add_quantum_register(replacement, replacement_qr);
1011+
/// double pi_param [1] = {3.14159,};
1012+
/// qk_dag_apply_gate(replacement, QkGate_RZ, qubit, pi_param, false);
1013+
/// qk_dag_apply_gate(replacement, QkGate_SX, qubit, NULL, false);
1014+
/// qk_dag_apply_gate(replacement, QkGate_RZ, qubit, pi_param, false);
1015+
///
1016+
/// qk_dag_substitute_node_with_dag(dag, node_to_replace, replacement, NULL);
1017+
///
1018+
/// // Free the replacement dag, register, dag, and register
1019+
/// qk_quantum_register_free(replacement_qr);
1020+
/// qk_dag_free(replacement);
1021+
/// qk_quantum_register_free(qr);
1022+
/// qk_dag_free(dag);
1023+
/// ```
1024+
///
1025+
/// # Safety
1026+
///
1027+
/// Behavior is undefined if ``dag`` and ``replacement`` are not a valid, non-null pointer to a
1028+
/// ``QkDag``. ``error`` must be a valid pointer to a ``char`` pointer or ``NULL``.
1029+
#[unsafe(no_mangle)]
1030+
#[cfg(feature = "cbinding")]
1031+
pub unsafe extern "C" fn qk_dag_substitute_node_with_dag(
1032+
dag: *mut DAGCircuit,
1033+
node: u32,
1034+
replacement: *const DAGCircuit,
1035+
error: *mut *mut c_char,
1036+
) -> ExitCode {
1037+
// SAFETY: Per documentation, ``dag`` is non-null and valid.
1038+
let dag = unsafe { mut_ptr_as_ref(dag) };
1039+
let replacement = unsafe { const_ptr_as_ref(replacement) };
1040+
1041+
match dag.substitute_node_with_dag(NodeIndex::new(node as usize), replacement, None, None, None)
1042+
{
1043+
Ok(_) => ExitCode::Success,
1044+
Err(e) => {
1045+
if !error.is_null() {
1046+
let err: Error = e.into();
1047+
// SAFETY: Per the documentation error is either null or a valid and aligned
1048+
// pointer to a pointer of a C string which is safe to write a pointer to the
1049+
// Rust heap into.
1050+
unsafe {
1051+
// Right now we return a backtrace of the error. This at least gives a hint as to
1052+
// which pass failed when we have rust errors normalized we can actually have error
1053+
// messages which are user facing. But most likely this will be a PyErr and panic
1054+
// when trying to extract the string.
1055+
*error = CString::new(format!(
1056+
"Transpilation failed with this backtrace: {}",
1057+
err.backtrace()
1058+
))
1059+
.unwrap()
1060+
.into_raw();
1061+
}
1062+
}
1063+
ExitCode::DagError
1064+
}
1065+
}
1066+
}

crates/cext/src/exit_codes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ pub enum ExitCode {
6060
TargetInvalidInstKey = 304,
6161
/// Transpilation failed
6262
TranspilerError = 400,
63+
/// QkDag operation error
64+
DagError = 500,
6365
}
6466

6567
impl From<ArithmeticError> for ExitCode {

crates/transpiler/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ qiskit-quantum-info.workspace = true
2929
thiserror.workspace = true
3030
bytemuck.workspace = true
3131
fixedbitset = "0.5.7"
32-
anyhow = "1.0"
32+
anyhow.workspace = true
3333

3434
[dependencies.uuid]
3535
workspace = true

test/c/test_dag.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,50 @@ static int test_unitary_gates(void) {
508508
return res;
509509
}
510510

511+
static int test_substitute_node_with_dag(void) {
512+
int res = Ok;
513+
QkDag *dag = qk_dag_new();
514+
QkQuantumRegister *qr = qk_quantum_register_new(1, "my_register");
515+
qk_dag_add_quantum_register(dag, qr);
516+
517+
uint32_t qubit[1] = {0};
518+
uint32_t node_to_replace = qk_dag_apply_gate(dag, QkGate_H, qubit, NULL, false);
519+
qk_dag_apply_gate(dag, QkGate_S, qubit, NULL, false);
520+
521+
// Build replacement dag for H
522+
QkDag *replacement = qk_dag_new();
523+
QkQuantumRegister *replacement_qr = qk_quantum_register_new(1, "other");
524+
qk_dag_add_quantum_register(replacement, replacement_qr);
525+
double pi_param[1] = {
526+
3.14159,
527+
};
528+
qk_dag_apply_gate(replacement, QkGate_RZ, qubit, pi_param, false);
529+
qk_dag_apply_gate(replacement, QkGate_SX, qubit, NULL, false);
530+
qk_dag_apply_gate(replacement, QkGate_RZ, qubit, pi_param, false);
531+
char *error = NULL;
532+
int ret_code = qk_dag_substitute_node_with_dag(dag, node_to_replace, replacement, &error);
533+
if (ret_code != QkExitCode_Success) {
534+
res = EqualityError;
535+
printf("Substitute call failed: %s\n", error);
536+
goto cleanup;
537+
}
538+
539+
if (qk_dag_num_op_nodes(dag) != 4) {
540+
res = EqualityError;
541+
printf("Number of instructions is %zd but expected 4\n", qk_dag_num_op_nodes(dag));
542+
goto cleanup;
543+
}
544+
545+
cleanup:
546+
// Free the replacement dag, register, dag, and register
547+
qk_quantum_register_free(replacement_qr);
548+
qk_dag_free(replacement);
549+
qk_quantum_register_free(qr);
550+
qk_dag_free(dag);
551+
552+
return res;
553+
}
554+
511555
int test_dag(void) {
512556
int num_failed = 0;
513557
num_failed += RUN_TEST(test_empty);
@@ -520,6 +564,7 @@ int test_dag(void) {
520564
num_failed += RUN_TEST(test_op_node_bits_explicit);
521565
num_failed += RUN_TEST(test_dag_topological_op_nodes);
522566
num_failed += RUN_TEST(test_unitary_gates);
567+
num_failed += RUN_TEST(test_substitute_node_with_dag);
523568

524569
fflush(stderr);
525570
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);

0 commit comments

Comments
 (0)