• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 23304525254

19 Mar 2026 04:09PM UTC coverage: 87.346% (+0.04%) from 87.309%
23304525254

Pull #15361

github

web-flow
Merge 52d8c7053 into 13b23a355
Pull Request #15361: Add `qk_circuit_draw` function to the C API

15 of 17 new or added lines in 2 files covered. (88.24%)

102 existing lines in 7 files now uncovered.

103313 of 118280 relevant lines covered (87.35%)

1137052.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.92
/crates/qpy/src/py_methods.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2025
4
//
5
// This code is licensed under the Apache License, Version 2.0. You may
6
// obtain a copy of this license in the LICENSE.txt file in the root directory
7
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
// Methods for QPY serialization working directly with Python-based data
14
use binrw::Endian;
15
use numpy::Complex64;
16
use pyo3::IntoPyObjectExt;
17
use pyo3::intern;
18
use pyo3::prelude::*;
19
use pyo3::types::{PyAny, PyComplex, PyDict, PyFloat, PyInt, PyList, PyString, PyTuple};
20
use qiskit_circuit::classical::expr::Expr;
21
use std::num::NonZero;
22
use std::sync::Arc;
23

24
use qiskit_circuit::bit::{ClassicalRegister, ShareableClbit};
25
use qiskit_circuit::classical;
26
use qiskit_circuit::imports;
27
use qiskit_circuit::operations::{Operation, OperationRef, PyRange};
28
use qiskit_circuit::packed_instruction::PackedOperation;
29
use qiskit_circuit::parameter::parameter_expression::{
30
    PyParameter, PyParameterExpression, PyParameterVectorElement,
31
};
32
use qiskit_quantum_info::sparse_observable::PySparseObservable;
33
use uuid::Uuid;
34

35
use crate::bytes::Bytes;
36
use crate::circuit_writer::standard_instruction_class_name;
37
use crate::error::QpyError;
38
use crate::formats;
39
use crate::value::{
40
    GenericValue, ModifierType, ParamRegisterValue, QPYWriteData, ValueType,
41
    serialize_generic_value,
42
};
43

44
pub const UNITARY_GATE_CLASS_NAME: &str = "UnitaryGate";
45
pub const PAULI_PRODUCT_MEASUREMENT_GATE_CLASS_NAME: &str = "PauliProductMeasurement";
46
pub const PAULI_PRODUCT_ROTATION_GATE_CLASS_NAME: &str = "PauliProductRotationGate";
47

48
fn is_python_gate(
102,678✔
49
    py: Python,
102,678✔
50
    op: &PackedOperation,
102,678✔
51
    python_gate: &Bound<PyAny>,
102,678✔
52
) -> Result<bool, QpyError> {
102,678✔
53
    match op.view() {
102,678✔
54
        OperationRef::Gate(pygate) => {
220✔
55
            if pygate.instruction.bind(py).is_instance(python_gate)? {
220✔
56
                Ok(true)
30✔
57
            } else {
58
                Ok(false)
190✔
59
            }
60
        }
61
        _ => Ok(false),
102,458✔
62
    }
63
}
102,678✔
64

65
/// custom gates have unique UUID attached to their name
66
/// this method recognizes whether we have such a gate and returns a unique name for it
67
/// since custom gates are implemented in python, this is a heavy python-space function
68
pub(crate) fn recognize_custom_operation(
34,326✔
69
    op: &PackedOperation,
34,326✔
70
    name: &String,
34,326✔
71
) -> Result<Option<String>, QpyError> {
34,326✔
72
    Python::attach(|py| {
34,326✔
73
        let library = py.import("qiskit.circuit.library")?;
34,326✔
74
        let circuit_mod = py.import("qiskit.circuit")?;
34,326✔
75
        let controlflow = py.import("qiskit.circuit.controlflow")?;
34,326✔
76

77
        if (!library.hasattr(name)?
34,326✔
78
            && !circuit_mod.hasattr(name)?
1,414✔
79
            && !controlflow.hasattr(name)?
1,230✔
80
            && (name != "Clifford" && name != PAULI_PRODUCT_MEASUREMENT_GATE_CLASS_NAME))
24✔
81
            || name == "Gate"
34,306✔
82
            || name == "Instruction"
34,268✔
83
            || is_python_gate(py, op, imports::BLUEPRINT_CIRCUIT.get_bound(py))?
34,244✔
84
        {
85
            // Assign a uuid to each instance of a custom operation
86
            let new_name = if !["ucrx_dg", "ucry_dg", "ucrz_dg"].contains(&op.name()) {
82✔
87
                format!("{}_{}", &op.name(), Uuid::new_v4().as_simple())
82✔
88
            } else {
89
                // ucr*_dg gates can have different numbers of parameters,
90
                // the uuid is appended to avoid storing a single definition
91
                // in circuits with multiple ucr*_dg gates. For legacy reasons
92
                // the uuid is stored in a different format as this was done
93
                // prior to QPY 11.
UNCOV
94
                format!("{}_{}", &op.name(), Uuid::new_v4())
×
95
            };
96
            return Ok(Some(new_name));
82✔
97
        }
34,244✔
98

99
        if ["ControlledGate", "AnnotatedOperation"].contains(&name.as_str())
34,244✔
100
            || is_python_gate(py, op, imports::MCMT_GATE.get_bound(py))?
34,218✔
101
        {
102
            return Ok(Some(format!("{}_{}", op.name(), Uuid::new_v4())));
28✔
103
        }
34,216✔
104

105
        if is_python_gate(py, op, imports::PAULI_EVOLUTION_GATE.get_bound(py))? {
34,216✔
106
            return Ok(Some(format!("###PauliEvolutionGate_{}", Uuid::new_v4())));
28✔
107
        }
34,188✔
108

109
        Ok(None)
34,188✔
110
    })
34,326✔
111
}
34,326✔
112

113
/// when trying to instantiate nonstandard gates, we turn to the relevant python clas
114
/// this function obtains the class based on the gate class name
115
pub(crate) fn get_python_gate_class<'a>(
162✔
116
    py: Python<'a>,
162✔
117
    gate_class_name: &String,
162✔
118
) -> Result<Bound<'a, PyAny>, QpyError> {
162✔
119
    let library = py.import("qiskit.circuit.library")?;
162✔
120
    let circuit_mod = py.import("qiskit.circuit")?;
162✔
121
    let control_flow = py.import("qiskit.circuit.controlflow")?;
162✔
122
    if library.hasattr(gate_class_name)? {
162✔
123
        library.getattr(gate_class_name).map_err(QpyError::from)
48✔
124
    } else if circuit_mod.hasattr(gate_class_name)? {
114✔
125
        circuit_mod.getattr(gate_class_name).map_err(QpyError::from)
110✔
126
    } else if control_flow.hasattr(gate_class_name)? {
4✔
UNCOV
127
        control_flow
×
UNCOV
128
            .getattr(gate_class_name)
×
129
            .map_err(QpyError::from)
×
130
    } else if gate_class_name == "Clifford" {
4✔
131
        Ok(imports::CLIFFORD.get_bound(py).clone())
4✔
UNCOV
132
    } else if gate_class_name == "pauli_product_measurement" {
×
UNCOV
133
        Ok(imports::PAULI_PRODUCT_MEASUREMENT.get_bound(py).clone())
×
134
    } else {
135
        Err(QpyError::ConversionError(format!(
×
UNCOV
136
            "Gate class not found: {:?}",
×
137
            gate_class_name
×
138
        )))
×
139
    }
140
}
162✔
141

142
// serializes python metadata to JSON using a python JSON serializer
143
pub(crate) fn serialize_metadata(
1,960✔
144
    metadata_opt: &Option<Bound<PyAny>>,
1,960✔
145
    metadata_serializer: Option<&Bound<PyAny>>,
1,960✔
146
) -> Result<Bytes, QpyError> {
1,960✔
147
    match metadata_opt {
1,960✔
UNCOV
148
        None => Ok(Bytes::new()),
×
149
        Some(metadata) => {
1,960✔
150
            let py = metadata.py();
1,960✔
151
            let none = py.None();
1,960✔
152
            let py_serializer = metadata_serializer.unwrap_or(none.bind(py));
1,960✔
153
            let json = py.import("json")?;
1,960✔
154
            let kwargs = PyDict::new(py);
1,960✔
155
            kwargs.set_item("separators", PyTuple::new(py, [",", ":"])?)?;
1,960✔
156
            kwargs.set_item("cls", py_serializer)?;
1,960✔
157
            Ok(json
1,960✔
158
                .call_method("dumps", (metadata,), Some(&kwargs))?
1,960✔
159
                .extract::<String>()?
1,960✔
160
                .into())
1,960✔
161
        }
162
    }
163
}
1,960✔
164

165
// helper method to extract attribute from a py_object
166
pub(crate) fn getattr_or_none<'py>(
80✔
167
    py_object: &'py Bound<'py, PyAny>,
80✔
168
    name: &str,
80✔
169
) -> Option<Bound<'py, PyAny>> {
80✔
170
    match py_object.getattr(name) {
80✔
171
        Ok(val) => {
80✔
172
            if val.is_none() {
80✔
173
                None
16✔
174
            } else {
175
                Some(val)
64✔
176
            }
177
        }
UNCOV
178
        Err(_) => None,
×
179
    }
180
}
80✔
181

182
pub(crate) fn py_serialize_numpy_object(py_object: &Py<PyAny>) -> Result<Bytes, QpyError> {
56✔
183
    Python::attach(|py| -> Result<Bytes, QpyError> {
56✔
184
        let np = py.import("numpy")?;
56✔
185
        let io = py.import("io")?;
56✔
186
        let buffer = io.call_method0("BytesIO")?;
56✔
187
        np.call_method1("save", (&buffer, py_object))?;
56✔
188
        Ok(buffer.call_method0("getvalue")?.extract::<Bytes>()?)
56✔
189
    })
56✔
190
}
56✔
191

192
pub(crate) fn py_deserialize_numpy_object(data: &Bytes) -> Result<Py<PyAny>, QpyError> {
68✔
193
    Python::attach(|py| {
68✔
194
        let np = py.import("numpy")?;
68✔
195
        let io = py.import("io")?;
68✔
196
        let buffer = io.call_method0("BytesIO")?;
68✔
197
        buffer.call_method1("write", (data.clone(),))?;
68✔
198
        buffer.call_method1("seek", (0,))?;
68✔
199
        Ok(np.call_method1("load", (buffer,))?.unbind())
68✔
200
    })
68✔
201
}
68✔
202

203
fn pack_sparse_pauli_op(
38✔
204
    operator: &Bound<PyAny>,
38✔
205
    qpy_data: &QPYWriteData,
38✔
206
) -> Result<formats::PauliDataPack, QpyError> {
38✔
207
    if operator.is_instance_of::<PySparseObservable>() {
38✔
208
        let py_sparse_observable: PyRef<PySparseObservable> = operator
4✔
209
            .extract()
4✔
210
            .map_err(|e| QpyError::from(PyErr::from(e)))?;
4✔
211
        let sparse_observable = py_sparse_observable.inner.read().map_err(|_| {
4✔
UNCOV
212
            QpyError::ConversionError("Can't extract sparse observable data".to_string())
×
UNCOV
213
        })?;
×
214
        let num_qubits = sparse_observable.num_qubits();
4✔
215
        let coeff_data = sparse_observable
4✔
216
            .coeffs()
4✔
217
            .iter()
4✔
218
            .flat_map(|coeff| [coeff.re, coeff.im])
8✔
219
            .collect();
4✔
220
        let bitterm_data = sparse_observable
4✔
221
            .bit_terms()
4✔
222
            .iter()
4✔
223
            .map(|&bitterm| bitterm as u16)
16✔
224
            .collect();
4✔
225
        let inds_data = sparse_observable.indices().to_vec();
4✔
226
        let bounds_data = sparse_observable
4✔
227
            .boundaries()
4✔
228
            .iter()
4✔
229
            .map(|&boundary| boundary as u64)
12✔
230
            .collect();
4✔
231
        let sparse_observable_pack = formats::SparsePauliObservableElemPack {
4✔
232
            num_qubits,
4✔
233
            coeff_data,
4✔
234
            bitterm_data,
4✔
235
            inds_data,
4✔
236
            bounds_data,
4✔
237
        };
4✔
238
        Ok(formats::PauliDataPack::V17(
4✔
239
            formats::PauliDataPackV17::SparseObservable(sparse_observable_pack),
4✔
240
        ))
4✔
241
    } else {
242
        // this is the case of SparsePauliOp, which we convert to a numpy list
243
        let op_as_np_list = operator.call_method1("to_list", (true,))?;
34✔
244
        let value = py_convert_to_generic_value(&op_as_np_list)?;
34✔
245
        let (_, data) = serialize_generic_value(&value, qpy_data)?;
34✔
246
        let pack = formats::SparsePauliOpListElemPack { data };
34✔
247
        Ok(formats::PauliDataPack::V17(
34✔
248
            formats::PauliDataPackV17::SparsePauliOp(pack),
34✔
249
        ))
34✔
250
    }
251
}
38✔
252

253
pub(crate) fn py_pack_pauli_evolution_gate(
28✔
254
    evolution_gate: &Bound<PyAny>,
28✔
255
    qpy_data: &QPYWriteData,
28✔
256
) -> Result<formats::PauliEvolutionDefPack, QpyError> {
28✔
257
    let py = evolution_gate.py();
28✔
258
    let operators = evolution_gate.getattr("operator")?;
28✔
259
    let mut standalone = false;
28✔
260
    let operator_list: Bound<PyList> = if !operators.is_instance_of::<PyList>() {
28✔
261
        standalone = true;
24✔
262
        PyList::new(py, [operators])?
24✔
263
    } else {
264
        operators
4✔
265
            .cast()
4✔
266
            .map_err(|e| QpyError::from(PyErr::from(e)))?
4✔
267
            .clone()
4✔
268
    };
269
    let pauli_data = operator_list
28✔
270
        .iter()
28✔
271
        .map(|operator| pack_sparse_pauli_op(&operator, qpy_data))
38✔
272
        .collect::<Result<_, QpyError>>()?;
28✔
273

274
    let time_value = py_convert_to_generic_value(&evolution_gate.getattr("time")?)?;
28✔
275
    let (time_type, time_data) = serialize_generic_value(&time_value, qpy_data)?;
28✔
276
    let synth_class = evolution_gate
28✔
277
        .getattr("synthesis")?
28✔
278
        .get_type()
28✔
279
        .getattr("__name__")?;
28✔
280
    let settings_dict = evolution_gate.getattr("synthesis")?.getattr("settings")?;
28✔
281
    let json = py.import("json")?;
28✔
282
    let args = PyDict::new(py);
28✔
283
    args.set_item("class", synth_class)?;
28✔
284
    args.set_item("settings", settings_dict)?;
28✔
285
    let synth_data: Bytes = json
28✔
286
        .call_method1("dumps", (args,))?
28✔
287
        .extract::<String>()?
28✔
288
        .into();
28✔
289

290
    let standalone_op = standalone as u8;
28✔
291
    Ok(formats::PauliEvolutionDefPack {
28✔
292
        standalone_op,
28✔
293
        time_type,
28✔
294
        pauli_data,
28✔
295
        time_data,
28✔
296
        synth_data,
28✔
297
    })
28✔
298
}
28✔
299

300
pub(crate) fn gate_class_name(op: &PackedOperation) -> Result<String, QpyError> {
34,326✔
301
    Python::attach(|py| {
34,326✔
302
        let name = match op.view() {
34,326✔
303
            // getting __name__ for standard gates and instructions should
304
            // eventually be replaced with a Rust-side mapping
305
            OperationRef::StandardGate(gate) => Ok(imports::get_std_gate_class_name(&gate)),
19,988✔
306
            OperationRef::StandardInstruction(inst) => {
12,832✔
307
                Ok(standard_instruction_class_name(&inst).to_string())
12,832✔
308
            }
309
            OperationRef::Gate(pygate) => pygate
144✔
310
                .instruction
144✔
311
                .bind(py)
144✔
312
                .getattr(intern!(py, "__class__"))?
144✔
313
                .getattr(intern!(py, "__name__"))?
144✔
314
                .extract::<String>(),
144✔
315
            OperationRef::Instruction(pyinst) => pyinst
114✔
316
                .instruction
114✔
317
                .bind(py)
114✔
318
                .getattr(intern!(py, "__class__"))?
114✔
319
                .getattr(intern!(py, "__name__"))?
114✔
320
                .extract::<String>(),
114✔
321
            OperationRef::Unitary(_) => Ok(UNITARY_GATE_CLASS_NAME.to_string()),
14✔
322
            OperationRef::Operation(py_op) => py_op
12✔
323
                .instruction
12✔
324
                .bind(py)
12✔
325
                .getattr(intern!(py, "__class__"))?
12✔
326
                .getattr(intern!(py, "__name__"))?
12✔
327
                .extract::<String>(),
12✔
328
            OperationRef::PauliProductMeasurement(_) => {
329
                Ok(String::from(PAULI_PRODUCT_MEASUREMENT_GATE_CLASS_NAME))
10✔
330
            }
331
            OperationRef::PauliProductRotation(_) => {
332
                Ok(String::from(PAULI_PRODUCT_ROTATION_GATE_CLASS_NAME))
6✔
333
            }
334
            OperationRef::ControlFlow(inst) => Ok(inst.name().to_string()),
1,206✔
UNCOV
335
        }?;
×
336
        Ok(name)
34,326✔
337
    })
34,326✔
338
}
34,326✔
339

340
pub(crate) fn py_get_type_key(py_object: &Bound<PyAny>) -> Result<ValueType, QpyError> {
1,660✔
341
    let py: Python<'_> = py_object.py();
1,660✔
342
    if py_object.is_instance(imports::PARAMETER_VECTOR_ELEMENT.get_bound(py))? {
1,660✔
343
        return Ok(ValueType::ParameterVector);
14✔
344
    } else if py_object.is_instance(imports::PARAMETER.get_bound(py))? {
1,646✔
345
        return Ok(ValueType::Parameter);
2✔
346
    } else if py_object.is_instance(imports::PARAMETER_EXPRESSION.get_bound(py))? {
1,644✔
347
        return Ok(ValueType::ParameterExpression);
2✔
348
    } else if py_object.is_instance(imports::QUANTUM_CIRCUIT.get_bound(py))? {
1,642✔
349
        return Ok(ValueType::Circuit);
1,268✔
350
    } else if py_object.is_instance(imports::CLBIT.get_bound(py))?
374✔
351
        || py_object.is_instance(imports::CLASSICAL_REGISTER.get_bound(py))?
374✔
352
    {
UNCOV
353
        return Ok(ValueType::Register);
×
354
    } else if py_object.extract::<classical::expr::Expr>().is_ok() {
374✔
355
        return Ok(ValueType::Expression);
168✔
356
    } else if py_object.is_instance(imports::BUILTIN_RANGE.get_bound(py))? {
206✔
UNCOV
357
        return Ok(ValueType::Range);
×
358
    } else if py_object.is_instance(imports::NUMPY_ARRAY.get_bound(py))? {
206✔
359
        return Ok(ValueType::NumpyObject);
56✔
360
    } else if py_object.is_instance(imports::MODIFIER.get_bound(py))? {
150✔
361
        return Ok(ValueType::Modifier);
14✔
362
    } else if py_object.is_instance_of::<PyInt>() {
136✔
363
        return Ok(ValueType::Integer);
20✔
364
    } else if py_object.is_instance_of::<PyFloat>() {
116✔
365
        return Ok(ValueType::Float);
10✔
366
    } else if py_object.is_instance_of::<PyComplex>() {
106✔
367
        return Ok(ValueType::Complex);
82✔
368
    } else if py_object.is_instance_of::<PyString>() {
24✔
369
        return Ok(ValueType::String);
10✔
370
    } else if py_object.is_instance_of::<PyTuple>() || py_object.is_instance_of::<PyList>() {
14✔
371
        return Ok(ValueType::Tuple);
14✔
UNCOV
372
    } else if py_object.is(imports::CASE_DEFAULT.get_bound(py)) {
×
UNCOV
373
        return Ok(ValueType::CaseDefault);
×
374
    } else if py_object.is_none() {
×
375
        return Ok(ValueType::Null);
×
376
    }
×
377

378
    Err(QpyError::ConversionError(format!(
×
UNCOV
379
        "Unidentified type_key for: {}",
×
380
        py_object
×
381
    )))
×
382
}
1,660✔
383

384
pub(crate) fn py_convert_to_generic_value(
1,660✔
385
    py_object: &Bound<PyAny>,
1,660✔
386
) -> Result<GenericValue, QpyError> {
1,660✔
387
    let type_key = py_get_type_key(py_object)?;
1,660✔
388
    match type_key {
1,660✔
UNCOV
389
        ValueType::Bool => Ok(GenericValue::Bool(py_object.extract::<bool>()?)),
×
390
        ValueType::Integer => Ok(GenericValue::Int64(py_object.extract::<i64>()?)),
20✔
391
        ValueType::Float => Ok(GenericValue::Float64(py_object.extract::<f64>()?)),
10✔
392
        ValueType::Complex => Ok(GenericValue::Complex64(py_object.extract::<Complex64>()?)),
82✔
393
        ValueType::String => Ok(GenericValue::String(py_object.extract::<String>()?)),
10✔
394
        ValueType::Expression => Ok(GenericValue::Expression(py_object.extract::<Expr>()?)),
168✔
UNCOV
395
        ValueType::CaseDefault => Ok(GenericValue::CaseDefault),
×
UNCOV
396
        ValueType::Null => Ok(GenericValue::Null),
×
397
        ValueType::Parameter => Ok(GenericValue::ParameterExpressionSymbol(
398
            py_object
2✔
399
                .extract::<PyParameter>()
2✔
400
                .map_err(|e| QpyError::from(PyErr::from(e)))?
2✔
401
                .symbol()
2✔
402
                .clone(),
2✔
403
        )),
404
        ValueType::ParameterVector => Ok(GenericValue::ParameterExpressionVectorSymbol(
405
            py_object
14✔
406
                .extract::<PyParameterVectorElement>()
14✔
407
                .map_err(|e| QpyError::from(PyErr::from(e)))?
14✔
408
                .symbol()
14✔
409
                .clone(),
14✔
410
        )),
411
        ValueType::ParameterExpression => Ok(GenericValue::ParameterExpression(Arc::new(
2✔
412
            py_object
2✔
413
                .extract::<PyParameterExpression>()
2✔
414
                .map_err(|e| QpyError::from(PyErr::from(e)))?
2✔
415
                .inner,
416
        ))),
417
        ValueType::Circuit => Ok(GenericValue::Circuit(py_object.clone().unbind())),
1,268✔
418
        ValueType::Tuple => {
419
            let elements: Vec<GenericValue> = py_object
14✔
420
                .try_iter()?
14✔
421
                .map(|data_item| {
28✔
422
                    // let data_item = possible_data_item?;
423
                    py_convert_to_generic_value(&data_item?)
28✔
424
                })
28✔
425
                .collect::<Result<_, QpyError>>()?;
14✔
426
            Ok(GenericValue::Tuple(elements))
14✔
427
        }
428
        ValueType::Range => {
UNCOV
429
            let start = py_object.getattr("start")?.extract::<isize>()?;
×
UNCOV
430
            let stop = py_object.getattr("stop")?.extract::<isize>()?;
×
431
            let step = py_object.getattr("step")?.extract::<isize>()?;
×
432
            let step = NonZero::new(step).ok_or_else(|| {
×
433
                QpyError::InvalidParameter("range step cannot be zero".to_string())
×
434
            })?;
×
435
            let range = PyRange { start, stop, step };
×
436
            Ok(GenericValue::Range(range))
×
437
        }
438
        // the python-managed data types
439
        ValueType::NumpyObject => Ok(GenericValue::NumpyObject(py_object.clone().unbind())),
56✔
440
        ValueType::Modifier => Ok(GenericValue::Modifier(py_object.clone().unbind())),
14✔
441
        ValueType::Register => {
UNCOV
442
            if let Ok(clbit) = py_object.extract::<ShareableClbit>() {
×
UNCOV
443
                Ok(GenericValue::Register(ParamRegisterValue::ShareableClbit(
×
444
                    clbit,
×
445
                )))
×
446
            } else if let Ok(reg) = py_object.extract::<ClassicalRegister>() {
×
447
                Ok(GenericValue::Register(ParamRegisterValue::Register(reg)))
×
448
            } else {
449
                Err(QpyError::InvalidRegister(
×
UNCOV
450
                    "Could not read python register".to_string(),
×
451
                ))
×
452
            }
453
        }
454
    }
455
}
1,660✔
456

457
pub(crate) fn py_convert_from_generic_value(value: &GenericValue) -> Result<Py<PyAny>, QpyError> {
758✔
458
    Python::attach(|py| -> Result<Py<PyAny>, QpyError> {
758✔
459
        match value {
758✔
UNCOV
460
            GenericValue::Bool(value) => Ok(value.into_py_any(py)?),
×
461
            GenericValue::Int64(value) => Ok(value.into_py_any(py)?),
54✔
462
            GenericValue::Float64(value) => Ok(value.into_py_any(py)?),
×
463
            GenericValue::Complex64(value) => Ok(value.into_py_any(py)?),
164✔
464
            GenericValue::String(value) => Ok(value.into_py_any(py)?),
30✔
465
            GenericValue::Expression(exp) => Ok(exp.clone().into_py_any(py)?),
348✔
UNCOV
466
            GenericValue::CaseDefault => Ok(imports::CASE_DEFAULT.get(py).clone()),
×
UNCOV
467
            GenericValue::Null => Ok(py.None()),
×
468
            GenericValue::ParameterExpressionSymbol(symbol) => {
×
469
                Ok(symbol.clone().into_py_any(py)?)
×
470
            }
471
            GenericValue::ParameterExpressionVectorSymbol(symbol) => {
×
UNCOV
472
                Ok(symbol.clone().into_py_any(py)?)
×
473
            }
474
            GenericValue::ParameterExpression(exp) => Ok(exp.as_ref().clone().into_py_any(py)?),
×
475
            GenericValue::Circuit(py_object) => Ok(py_object.clone()),
30✔
476
            GenericValue::CircuitData(circuit_data) => {
×
UNCOV
477
                Ok(circuit_data.clone().into_py_quantum_circuit(py)?.unbind())
×
478
            }
479
            GenericValue::Modifier(py_object) => Ok(py_object.clone()),
28✔
UNCOV
480
            GenericValue::Range(py_range) => Ok(py_range.into_py_any(py)?),
×
481
            GenericValue::NumpyObject(py_object) => Ok(py_object.clone()),
16✔
482
            GenericValue::Tuple(values) => {
88✔
483
                let elements: Vec<Py<PyAny>> = values
88✔
484
                    .iter()
88✔
485
                    .map(py_convert_from_generic_value)
88✔
486
                    .collect::<Result<_, QpyError>>()?;
88✔
487
                Ok(PyTuple::new(py, &elements)?.into_py_any(py)?)
88✔
488
            }
UNCOV
489
            GenericValue::Register(reg_value) => match reg_value {
×
UNCOV
490
                ParamRegisterValue::Register(reg) => Ok(reg.clone().into_py_any(py)?),
×
491
                ParamRegisterValue::ShareableClbit(clbit) => Ok(clbit.clone().into_py_any(py)?),
×
492
            },
493
            GenericValue::BigInt(bigint) => Ok(bigint.clone().into_py_any(py)?),
×
UNCOV
494
            GenericValue::Duration(duration) => Ok((*duration).into_py_any(py)?),
×
495
        }
496
    })
758✔
497
}
758✔
498

499
// This functions packs an instruction parameter, which can be an arbitrary piece of data
500
// Not to be confused with Parameter, which is an atom of ParameterExpression
501
pub(crate) fn py_pack_param(
1,570✔
502
    py_object: &Bound<PyAny>,
1,570✔
503
    qpy_data: &QPYWriteData,
1,570✔
504
    endian: Endian,
1,570✔
505
) -> Result<formats::GenericDataPack, QpyError> {
1,570✔
506
    let value = py_convert_to_generic_value(py_object)?;
1,570✔
507
    let (type_key, data) = match endian {
1,570✔
UNCOV
508
        Endian::Big => serialize_generic_value(&value, qpy_data)?,
×
509
        Endian::Little => serialize_generic_value(&value.as_le(), qpy_data)?,
1,570✔
510
    };
511
    Ok(formats::GenericDataPack { type_key, data })
1,570✔
512
}
1,570✔
513

514
pub(crate) fn py_pack_modifier(modifier: &Py<PyAny>) -> Result<formats::ModifierPack, QpyError> {
14✔
515
    Python::attach(|py| {
14✔
516
        let modifier = modifier.bind(py);
14✔
517
        let module = py.import("qiskit.circuit.annotated_operation")?;
14✔
518
        if modifier.is_instance(&module.getattr("InverseModifier")?)? {
14✔
519
            Ok(formats::ModifierPack {
8✔
520
                modifier_type: ModifierType::Inverse,
8✔
521
                num_ctrl_qubits: 0,
8✔
522
                ctrl_state: 0,
8✔
523
                power: 0.0,
8✔
524
            })
8✔
525
        } else if modifier.is_instance(&module.getattr("ControlModifier")?)? {
6✔
526
            Ok(formats::ModifierPack {
527
                modifier_type: ModifierType::Control,
4✔
528
                num_ctrl_qubits: modifier.getattr("num_ctrl_qubits")?.extract::<u32>()?,
4✔
529
                ctrl_state: modifier.getattr("ctrl_state")?.extract::<u32>()?,
4✔
530
                power: 0.0,
531
            })
532
        } else if modifier.is_instance(&module.getattr("PowerModifier")?)? {
2✔
533
            Ok(formats::ModifierPack {
534
                modifier_type: ModifierType::Power,
2✔
535
                num_ctrl_qubits: 0,
536
                ctrl_state: 0,
537
                power: modifier.getattr("power")?.extract::<f64>()?,
2✔
538
            })
539
        } else {
UNCOV
540
            Err(QpyError::ConversionError(
×
UNCOV
541
                "Unsupported modifier".to_string(),
×
UNCOV
542
            ))
×
543
        }
544
    })
14✔
545
}
14✔
546

547
pub(crate) fn py_unpack_modifier(
14✔
548
    packed_modifier: &formats::ModifierPack,
14✔
549
) -> Result<Py<PyAny>, QpyError> {
14✔
550
    Python::attach(|py| match packed_modifier.modifier_type {
14✔
551
        ModifierType::Inverse => Ok(imports::INVERSE_MODIFIER.get_bound(py).call0()?.unbind()),
8✔
552
        ModifierType::Control => {
553
            let kwargs = PyDict::new(py);
4✔
554
            kwargs.set_item(
4✔
555
                intern!(py, "num_ctrl_qubits"),
4✔
556
                packed_modifier.num_ctrl_qubits,
4✔
UNCOV
557
            )?;
×
558
            kwargs.set_item(intern!(py, "ctrl_state"), packed_modifier.ctrl_state)?;
4✔
559
            Ok(imports::CONTROL_MODIFIER
4✔
560
                .get_bound(py)
4✔
561
                .call((), Some(&kwargs))?
4✔
562
                .unbind())
4✔
563
        }
564
        ModifierType::Power => {
565
            let kwargs = PyDict::new(py);
2✔
566
            kwargs.set_item(intern!(py, "power"), packed_modifier.power)?;
2✔
567
            Ok(imports::POWER_MODIFIER
2✔
568
                .get_bound(py)
2✔
569
                .call((), Some(&kwargs))?
2✔
570
                .unbind())
2✔
571
        }
572
    })
14✔
573
}
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc