Skip to content

Commit c005066

Browse files
committed
Merge remote-tracking branch 'ibm/main' into oxidize-ctrl-flow
2 parents 5e732ec + 044b649 commit c005066

7 files changed

Lines changed: 390 additions & 32 deletions

File tree

crates/cext/src/transpiler/target.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use qiskit_circuit::operations::{Operation, Param, StandardGate};
2424
use qiskit_circuit::packed_instruction::PackedOperation;
2525
use qiskit_circuit::parameter::parameter_expression::ParameterExpression;
2626
use qiskit_circuit::parameter::symbol_expr::Symbol;
27-
use qiskit_transpiler::target::{InstructionProperties, Qargs, Target};
27+
use qiskit_transpiler::target::{InstructionProperties, Qargs, Target, TargetOperation};
2828
use smallvec::{SmallVec, smallvec};
2929

3030
/// @ingroup QkTarget
@@ -943,6 +943,93 @@ pub unsafe extern "C" fn qk_target_num_instructions(target: *const Target) -> us
943943
target.len()
944944
}
945945

946+
/// @ingroup QkTarget
947+
/// Checks if the provided instruction and its qargs are supported by this
948+
/// ``Target``.
949+
///
950+
/// @param target A pointer to the ``Target``.
951+
/// @param operation_name The instruction name to check for.
952+
/// @param qargs The pointer to the array of ``uint32_t`` values to use as
953+
/// qargs. Can be ``NULL`` if global.
954+
/// @param params A pointer to an array of pointers of ``QkParam`` objects as parameters
955+
/// to check. Can be ``NULL`` if no parameters are present.
956+
///
957+
/// @return Whether the instruction is supported or not.
958+
///
959+
/// # Example
960+
/// ```c
961+
/// // Create a mock target with only a global crx entry
962+
/// // and 3.14 as its rotation parameter.
963+
/// QkTarget *target = qk_target_new(5);
964+
/// QkTargetEntry *crx_entry = qk_target_entry_new_fixed(QkGate_CRX, (double[]){3.14});
965+
/// qk_target_entry_add_property(crx_entry, NULL, 0, 0.0, 0.1);
966+
/// qk_target_add_instruction(target, crx_entry);
967+
///
968+
/// // Check if target is compatible with a "crx" gate
969+
/// // at [0, 1] with 3.14 rotation.
970+
/// QkParam *params[1] = {qk_param_from_double(3.14)};
971+
/// qk_target_instruction_supported(target, "crx", (uint32_t []){0, 1}, params);
972+
///
973+
/// // Free the pointers
974+
/// qk_param_free(params[0]);
975+
/// qk_target_free(target);
976+
/// ```
977+
///
978+
/// # Safety
979+
///
980+
/// Behavior is undefined if ``target`` is not a valid, non-null pointer to a ``QkTarget``.
981+
///
982+
/// The ``qargs`` argument is expected to be a pointer to an array of ``u32int_t`` where the length
983+
/// matches the expectation of the gate. If the array is insufficiently long the behavior of this
984+
/// function is undefined as this will read outside the bounds of the array. It can be a null
985+
/// pointer if there are no qubits for a given gate. You can check `qk_gate_num_qubits` to
986+
/// determine how many qubits are required for a given gate.
987+
///
988+
/// The ``params`` argument is expected to be an array of ``QkParam`` where the length matches the
989+
/// expectation of the operation in question. If the array is insufficiently long, the behavior will
990+
/// be undefined just as mentioned above for the ``qargs`` argument. You can always check ``qk_gate_num_params``
991+
/// in the case of a ``QkGate``.
992+
#[unsafe(no_mangle)]
993+
#[cfg(feature = "cbinding")]
994+
pub unsafe extern "C" fn qk_target_instruction_supported(
995+
target: *const Target,
996+
operation_name: *const c_char,
997+
qargs: *const u32,
998+
params: *mut *mut Param,
999+
) -> bool {
1000+
// SAFETY: Per documentation, the pointer is non-null and aligned.
1001+
let target = unsafe { const_ptr_as_ref(target) };
1002+
// SAFETY: Per documentation, the pointer for operation name points to a valid null terminated string.
1003+
let name = unsafe { CStr::from_ptr(operation_name) }
1004+
.to_str()
1005+
.expect("Error extracting gate name from target.");
1006+
let Some(operation) = target.operation_from_name(name) else {
1007+
return false;
1008+
};
1009+
let num_params = match operation {
1010+
TargetOperation::Variadic(_) => 0,
1011+
TargetOperation::Normal(op) => op.params.as_ref().map(|p| p.len()).unwrap_or(0),
1012+
};
1013+
1014+
// SAFETY: Per documentation, the params argument points to an appropriately allocated
1015+
// array of valid pointers to `QkParam` objects.
1016+
let params = if params.is_null() {
1017+
Vec::with_capacity(0)
1018+
} else {
1019+
unsafe {
1020+
std::slice::from_raw_parts(params, num_params)
1021+
.iter()
1022+
.map(|param| const_ptr_as_ref(*param).clone())
1023+
.collect()
1024+
}
1025+
};
1026+
1027+
// SAFETY: Per the documentation the qubits pointer is an array of num_qubits elements
1028+
let qargs: Qargs = unsafe { parse_qargs(qargs, operation.num_qubits()) };
1029+
1030+
target.instruction_supported(name, &qargs, &params, false)
1031+
}
1032+
9461033
/// Parses qargs based on a pointer and its size.
9471034
///
9481035
/// # Arguments

crates/transpiler/src/target/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,12 +1375,14 @@ impl Target {
13751375
return false;
13761376
}
13771377

1378-
for (index, params) in parameters.iter().enumerate() {
1379-
let obj_at_index = &obj_params[index];
1380-
let matching_params = match (obj_at_index, params) {
1378+
for (params, orig_params) in parameters.iter().zip(obj_params) {
1379+
let matching_params = match (orig_params, params) {
13811380
(Param::Float(obj_f), Param::Float(param_f)) => obj_f == param_f,
13821381
(Param::ParameterExpression(_), _) => true,
1383-
_ => Python::attach(|py| python_compare(py, params, &obj_params[index]))
1382+
(Param::Float(obj_f), Param::ParameterExpression(expr)) => {
1383+
expr.try_to_value(true).is_ok_and(|value| value.eq(obj_f))
1384+
}
1385+
_ => Python::attach(|py| python_compare(py, params, orig_params))
13841386
.expect("Error comparing Python parameters."),
13851387
};
13861388

qiskit/user_config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,18 @@ def read_config_file(self):
190190
self.settings["sabre_all_threads"] = sabre_all_threads
191191

192192
# Parse min_qpy_version
193-
min_qpy_version = self.config_parser.getint("default", "min_qpy_version", fallback=None)
193+
try:
194+
min_qpy_version = self.config_parser.getint(
195+
"default", "min_qpy_version", fallback=None
196+
)
197+
except ValueError as ve:
198+
raise exceptions.QiskitUserConfigError(
199+
"min_qpy_version is not a valid QPY version."
200+
) from ve
194201
if min_qpy_version:
195202
if min_qpy_version < 0:
196203
raise exceptions.QiskitUserConfigError(
197-
f"{min_qpy_version} is not a valid QPY version."
204+
f"min_qpy_version {min_qpy_version} is not a valid QPY version."
198205
)
199206
self.settings["min_qpy_version"] = min_qpy_version
200207

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features_c:
3+
- |
4+
Add function :c:func:`qk_target_instruction_supported` to check the compatibility of any gate
5+
or instruction with the instance of :c:type:`QkTarget`. This check is performed based
6+
on the provided instruction's name, qargs, and parameters. For more information on how it works
7+
read the examples listed in the documentation for :c:func:`qk_target_instruction_supported`.

test/c/test_target.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <stdio.h>
2121
#include <string.h>
2222

23+
QkTarget *create_sample_target(bool std_inst);
2324
/**
2425
* Test empty constructor for Target
2526
*/
@@ -552,6 +553,89 @@ static int test_target_update_instruction(void) {
552553
return result;
553554
}
554555

556+
/**
557+
* Test if our target is compatible with certain instructions.
558+
*/
559+
int test_target_instruction_supported(void) {
560+
QkTarget *sample_target = create_sample_target(true);
561+
int result = Ok;
562+
563+
char *gate_names[7] = {"x", "y", "id", "rz", "sx", "reset"};
564+
QkParam *rz_params[1] = {
565+
qk_param_from_double(3.14),
566+
};
567+
for (uint32_t qubit = 0; qubit < 5; qubit++) {
568+
uint32_t qargs[1] = {qubit};
569+
bool should_be_true = qubit < 4;
570+
571+
for (int gate = 0; gate < 6; gate++) {
572+
// If i == 4 condition should be false unless we try with the y gate
573+
// since y is added as a global gate.
574+
if (qk_target_instruction_supported(sample_target, gate_names[gate], qargs,
575+
gate != 3 ? NULL : rz_params) !=
576+
(should_be_true || gate == 1)) {
577+
printf("This target did not correctly demonstrate compatibility with %s and qargs "
578+
"[%d]",
579+
gate_names[gate], qubit);
580+
result = EqualityError;
581+
goto cleanup;
582+
}
583+
}
584+
585+
// Try checking with the wrong fixed parameter for RZ
586+
QkParam *param = qk_param_from_double(1.57);
587+
if (qk_target_instruction_supported(sample_target, "rz", qargs,
588+
(QkParam *[]){
589+
param,
590+
})) {
591+
printf("This target did not correctly demonstrate compatibility with 'rz' and qargs "
592+
"[%d]",
593+
qubit);
594+
result = EqualityError;
595+
qk_param_free(param);
596+
goto cleanup;
597+
}
598+
599+
// Test standard instructions reset and measure
600+
if (!(qk_target_instruction_supported(sample_target, "measure", qargs, NULL) ==
601+
(qubit < 2))) {
602+
printf(
603+
"This target did not correctly demonstrate compatibility with 'measure' and qargs "
604+
"[%d]",
605+
qubit);
606+
result = EqualityError;
607+
goto cleanup;
608+
}
609+
}
610+
611+
// Qarg samples for CX
612+
uint32_t qarg_samples[8][2] = {
613+
{3, 4}, {4, 3}, {3, 1}, {1, 3}, {1, 2}, {2, 1}, {0, 1}, {1, 0},
614+
};
615+
for (int i = 0; i < 8; i++) {
616+
if (!qk_target_instruction_supported(sample_target, "cx", qarg_samples[i], NULL)) {
617+
printf("This target did incorrectly demonstrate compatibility with 'cx' and qargs [%d, "
618+
"%d]",
619+
qarg_samples[i][0], qarg_samples[i][1]);
620+
result = EqualityError;
621+
goto cleanup;
622+
}
623+
}
624+
625+
uint32_t cx_qargs[2] = {3, 2};
626+
// Instruction should not show compatibility with (3, 2)
627+
if (qk_target_instruction_supported(sample_target, "cx", cx_qargs, NULL)) {
628+
printf("This target did incorrectly demonstrate compatibility with 'cx' and qargs [3, 2]");
629+
result = EqualityError;
630+
goto cleanup;
631+
}
632+
633+
cleanup:
634+
qk_param_free(rz_params[0]);
635+
qk_target_free(sample_target);
636+
return result;
637+
}
638+
555639
int test_target(void) {
556640
int num_failed = 0;
557641
num_failed += RUN_TEST(test_empty_target);
@@ -560,9 +644,77 @@ int test_target(void) {
560644
num_failed += RUN_TEST(test_target_add_instruction);
561645
num_failed += RUN_TEST(test_target_update_instruction);
562646
num_failed += RUN_TEST(test_target_construction_ibm_like_target);
647+
num_failed += RUN_TEST(test_target_instruction_supported);
563648

564649
fflush(stderr);
565650
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);
566651

567652
return num_failed;
568653
}
654+
655+
QkTarget *create_sample_target(bool std_inst) {
656+
// Build sample target
657+
QkTarget *target = qk_target_new(0);
658+
QkTargetEntry *i_entry = qk_target_entry_new(QkGate_I);
659+
for (int i = 0; i < 4; i++) {
660+
uint32_t qargs[1] = {i};
661+
qk_target_entry_add_property(i_entry, qargs, 1, 35.5e-9, 0.);
662+
}
663+
qk_target_add_instruction(target, i_entry);
664+
665+
double rz_params[1] = {3.14};
666+
QkTargetEntry *rz_entry = qk_target_entry_new_fixed(QkGate_RZ, rz_params, NULL);
667+
for (int i = 0; i < 4; i++) {
668+
uint32_t qargs[1] = {i};
669+
qk_target_entry_add_property(rz_entry, qargs, 1, 0., 0.);
670+
}
671+
qk_target_add_instruction(target, rz_entry);
672+
673+
QkTargetEntry *sx_entry = qk_target_entry_new(QkGate_SX);
674+
for (int i = 0; i < 4; i++) {
675+
uint32_t qargs[1] = {i};
676+
qk_target_entry_add_property(sx_entry, qargs, 1, 35.5e-9, 0.);
677+
}
678+
qk_target_add_instruction(target, sx_entry);
679+
680+
QkTargetEntry *x_entry = qk_target_entry_new(QkGate_X);
681+
for (int i = 0; i < 4; i++) {
682+
uint32_t qargs[1] = {i};
683+
qk_target_entry_add_property(x_entry, qargs, 1, 35.5e-9, 0.0005);
684+
}
685+
qk_target_add_instruction(target, x_entry);
686+
687+
QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX);
688+
uint32_t qarg_samples[8][2] = {
689+
{3, 4}, {4, 3}, {3, 1}, {1, 3}, {1, 2}, {2, 1}, {0, 1}, {1, 0},
690+
};
691+
double props[8][2] = {
692+
{2.7022e-11, 0.00713}, {3.0577e-11, 0.00713}, {4.6222e-11, 0.00929}, {4.9777e-11, 0.00929},
693+
{2.2755e-11, 0.00659}, {2.6311e-11, 0.00659}, {5.1911e-11, 0.01201}, {5.1911e-11, 0.01201},
694+
};
695+
for (int i = 0; i < 8; i++) {
696+
qk_target_entry_add_property(cx_entry, qarg_samples[i], 2, props[i][0], props[i][1]);
697+
}
698+
qk_target_add_instruction(target, cx_entry);
699+
700+
// Add global Y Gate
701+
qk_target_add_instruction(target, qk_target_entry_new(QkGate_Y));
702+
703+
if (std_inst) {
704+
QkTargetEntry *meas = qk_target_entry_new_measure();
705+
for (uint32_t i = 0; i < 2; i++) {
706+
uint32_t q[1] = {i};
707+
qk_target_entry_add_property(meas, q, 1, 1e-6, 1e-4);
708+
}
709+
qk_target_add_instruction(target, meas);
710+
711+
QkTargetEntry *reset = qk_target_entry_new_reset();
712+
for (uint32_t i = 0; i < 4; i++) {
713+
uint32_t q[1] = {i};
714+
qk_target_entry_add_property(reset, q, 1, 1e-6, 1e-4);
715+
}
716+
qk_target_add_instruction(target, reset);
717+
}
718+
719+
return target;
720+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Tests for min_qpy_version option"""
2+
3+
# This code is part of Qiskit.
4+
#
5+
# (C) Copyright IBM 2025.
6+
#
7+
# This code is licensed under the Apache License, Version 2.0. You may
8+
# obtain a copy of this license in the LICENSE.txt file in the root directory
9+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
10+
#
11+
# Any modifications or derivative works of this code must retain this
12+
# copyright notice, and modified files need to carry a notice indicating
13+
# that they have been altered from the originals.
14+
15+
import struct
16+
from unittest import mock
17+
import unittest
18+
from uuid import uuid4
19+
import io
20+
21+
from qiskit.qpy import load, formats, QpyError
22+
23+
24+
class TestMinQpyVersion(unittest.TestCase):
25+
"""Tests for min_qpy_version validation."""
26+
27+
def setUp(self):
28+
super().setUp()
29+
self.file_path = f"test_{uuid4()}.conf"
30+
31+
@mock.patch("qiskit.user_config.get_config")
32+
def test_enforce_min_qpy_version(self, mock_get_config):
33+
"""Test that QPY file below min_qpy_version raises QiskitError."""
34+
mock_get_config.return_value = {"min_qpy_version": 12}
35+
with io.BytesIO() as buf:
36+
buf.write(struct.pack(formats.FILE_HEADER_PACK, b"QISKIT", 9, 1, 0, 0, 0))
37+
buf.seek(0)
38+
with self.assertRaises(QpyError) as cm:
39+
load(buf)
40+
self.assertIn("is lower than the configured minimum version", str(cm.exception))
41+
42+
@mock.patch("qiskit.user_config.get_config")
43+
def test_matches_min_qpy_version(self, mock_get_config):
44+
"""Test that QPY file matches min_qpy_version."""
45+
mock_get_config.return_value = {"min_qpy_version": 4}
46+
with io.BytesIO() as buf:
47+
buf.write(struct.pack(formats.FILE_HEADER_PACK, b"QISKIT", 4, 1, 0, 0, 0))
48+
buf.seek(0)
49+
no_circs = load(buf)
50+
self.assertEqual(no_circs, [])
51+
52+
@mock.patch("qiskit.user_config.get_config")
53+
def test_skip_enforcement_if_unset(self, mock_get_config):
54+
"""Test that load proceeds if min_qpy_version is unset."""
55+
mock_get_config.return_value = {"min_qpy_version": None}
56+
with io.BytesIO() as buf:
57+
buf.write(struct.pack(formats.FILE_HEADER_PACK, b"QISKIT", 4, 1, 0, 0, 0))
58+
buf.seek(0)
59+
no_circs = load(buf)
60+
self.assertEqual(no_circs, [])

0 commit comments

Comments
 (0)