microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/src/interpreter/data_interop.rs

458lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This module contains the types and functions used to build the
5//! data-interop layer between Python and Q#.
6
7use crate::interpreter::QasmError;
8
9use super::{Pauli, Result};
10use num_bigint::BigInt;
11use pyo3::{
12 IntoPyObjectExt,
13 conversion::FromPyObject,
14 exceptions::PyTypeError,
15 prelude::*,
16 types::{PyList, PyTuple},
17};
18use qsc::{
19 fir::{self},
20 hir::ty::{Prim, Ty},
21 interpret::{self, Value},
22};
23use rustc_hash::FxHashMap;
24use std::rc::Rc;
25
26/// Instances of this enum represent a Q# type. This is used
27/// to send the definitions of Q# UDTs defined by the user to Python
28/// and creating equivalent Python dataclasses in `qsharp.code.*`.
29#[pyclass]
30#[derive(Clone)]
31pub(super) enum TypeIR {
32 Primitive(PrimitiveKind),
33 Tuple(Vec<TypeIR>),
34 Array(Vec<TypeIR>),
35 Udt(UdtIR),
36}
37
38#[pymethods]
39impl TypeIR {
40 fn kind(&self) -> TypeKind {
41 match self {
42 Self::Primitive(_) => TypeKind::Primitive,
43 Self::Tuple(_) => TypeKind::Tuple,
44 Self::Array(_) => TypeKind::Array,
45 Self::Udt(_) => TypeKind::Udt,
46 }
47 }
48
49 fn unwrap_primitive(&self) -> PyResult<PrimitiveKind> {
50 if let Self::Primitive(ty) = self {
51 Ok(*ty)
52 } else {
53 Err(PyTypeError::new_err("type is not a primitive".to_string()))
54 }
55 }
56
57 fn unwrap_tuple(&self) -> PyResult<Vec<TypeIR>> {
58 if let Self::Tuple(ty) = self {
59 Ok(ty.clone())
60 } else {
61 Err(PyTypeError::new_err("type is not a tuple".to_string()))
62 }
63 }
64
65 fn unwrap_array(&self) -> PyResult<Vec<TypeIR>> {
66 if let Self::Tuple(ty) = self {
67 Ok(ty.clone())
68 } else {
69 Err(PyTypeError::new_err("type is not an array".to_string()))
70 }
71 }
72
73 fn unwrap_udt(&self) -> PyResult<UdtIR> {
74 if let Self::Udt(ty) = self {
75 Ok(ty.clone())
76 } else {
77 Err(PyTypeError::new_err("type is not a UDT".to_string()))
78 }
79 }
80}
81
82#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
83#[pyclass(eq, eq_int, ord)]
84pub(super) enum TypeKind {
85 Primitive,
86 Tuple,
87 Array,
88 Udt,
89}
90
91#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
92#[pyclass(eq, eq_int, ord)]
93pub(super) enum PrimitiveKind {
94 Bool,
95 Int,
96 Double,
97 Complex,
98 String,
99 Pauli,
100 Result,
101}
102
103#[pyclass]
104#[derive(Clone)]
105pub(super) struct UdtIR {
106 #[pyo3(get)]
107 name: String,
108 #[pyo3(get)]
109 fields: Vec<(String, TypeIR)>,
110}
111
112/// This type is used to send objects from Python to Q#.
113/// It is a `HashMap` to make it simple checking that the
114/// objects have all the required fields to match the UDTs
115/// they represent, without considering the order of the fields.
116pub(super) type UdtFields = FxHashMap<String, Py<PyAny>>;
117
118/// This type is used to send instances of UDTs from Q# to Python.
119/// It is a `Vec` and not a `HashMap` to preserve the order of the fields,
120/// since that results in a better user experience when printing the
121/// objects in Python.
122#[pyclass]
123pub(super) struct UdtValue {
124 #[pyo3(get)]
125 name: String,
126 #[pyo3(get)]
127 fields: Vec<(String, Py<PyAny>)>,
128}
129
130#[pyclass]
131#[derive(Clone)]
132pub(super) enum PrimitiveValue {
133 Bool(bool),
134 Int(i64),
135 BigInt(BigInt),
136 Double(f64),
137 Complex(num_complex::Complex64),
138 String(String),
139 Result(Result),
140 Pauli(Pauli),
141}
142
143/// UDT fields are stored recursively, this function flattens that structure
144/// and returns a vector with all the fields. Errors if any of the fields
145/// is anonymous.
146pub(super) fn collect_udt_fields<'ctx, 'udt_def>(
147 udt: &'udt_def qsc::hir::ty::Udt,
148) -> PyResult<Vec<(Rc<str>, &'ctx Ty)>>
149where
150 'udt_def: 'ctx,
151{
152 let mut fields = Vec::new();
153 collect_udt_fields_rec(&udt.name, &udt.definition, &mut fields)?;
154 Ok(fields)
155}
156
157fn collect_udt_fields_rec<'ctx, 'udt_def>(
158 udt_name: &str,
159 udt_def: &'udt_def qsc::hir::ty::UdtDef,
160 buffer: &mut Vec<(Rc<str>, &'ctx Ty)>,
161) -> PyResult<()>
162where
163 'udt_def: 'ctx,
164{
165 match &udt_def.kind {
166 qsc::hir::ty::UdtDefKind::Field(udt_field) => {
167 if let Some(name) = udt_field.name.as_ref() {
168 buffer.push((name.clone(), &udt_field.ty));
169 Ok(())
170 } else {
171 Err(PyTypeError::new_err(format!(
172 "structs with anonymous fields are not supported: {udt_name}"
173 )))
174 }
175 }
176 qsc::hir::ty::UdtDefKind::Tuple(udt_defs) => {
177 for udt_def in udt_defs {
178 collect_udt_fields_rec(udt_name, udt_def, buffer)?;
179 }
180 Ok(())
181 }
182 }
183}
184
185/// Gets the type name of a Python object.
186fn obj_type(py: Python, obj: &Py<PyAny>) -> PyResult<String> {
187 Ok(obj.bind(py).get_type().name()?.to_string())
188}
189
190/// A wrapper around the `obj.extract::<T>` functionality that allows to return
191/// user friendly errors when casting fails, similar to the Q# ones.
192fn extract_obj<'py, 'obj, T>(py: Python<'py>, obj: &'obj Py<PyAny>, ty: &Ty) -> PyResult<T>
193where
194 T: FromPyObject<'obj, 'py>,
195 'py: 'obj,
196{
197 match obj.extract::<T>(py) {
198 Ok(val) => Ok(val),
199 Err(err) => {
200 let err = err.into();
201 if err.is_instance_of::<PyTypeError>(py) {
202 // If we have a type error, we return a friendly user error.
203 Err(PyTypeError::new_err(format!(
204 "expected {}, found {}",
205 ty.display(),
206 obj_type(py, obj)?
207 )))
208 } else {
209 // If we have other kind of errors (e.g.: an overflow error when
210 // converting from a python int to a rust i64) we leave it as is.
211 Err(err)
212 }
213 }
214 }
215}
216
217/// Given a type, convert a Python object into a Q# value of that type. This will recur through tuples and arrays,
218/// and will return an error if the type is not supported or the object cannot be converted.
219pub(super) fn pyobj_to_value(
220 ctx: &interpret::Interpreter,
221 py: Python,
222 obj: &Py<PyAny>,
223 ty: &Ty,
224) -> PyResult<Value> {
225 match ty {
226 Ty::Prim(prim_ty) => match prim_ty {
227 Prim::Bool => Ok(Value::Bool(extract_obj::<bool>(py, obj, ty)?)),
228 Prim::Int => Ok(Value::Int(extract_obj::<i64>(py, obj, ty)?)),
229 Prim::BigInt => Ok(Value::BigInt(extract_obj::<BigInt>(py, obj, ty)?)),
230 Prim::Double => Ok(Value::Double(extract_obj::<f64>(py, obj, ty)?)),
231 Prim::String => Ok(Value::String(extract_obj::<String>(py, obj, ty)?.into())),
232 Prim::Result => Ok(Value::Result(extract_obj::<Result>(py, obj, ty)?.into())),
233 Prim::Pauli => Ok(Value::Pauli(extract_obj::<Pauli>(py, obj, ty)?.into())),
234 Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
235 unimplemented!("primitive input type: {prim_ty:?}")
236 }
237 },
238 Ty::Tuple(tup) => {
239 let objs = extract_obj::<Vec<Py<PyAny>>>(py, obj, ty)?;
240
241 if tup.len() != objs.len() {
242 return Err(PyTypeError::new_err(format!(
243 "mismatched tuple arity: expected {}, found {}",
244 tup.len(),
245 objs.len()
246 )));
247 }
248 if objs.len() == 1 {
249 pyobj_to_value(ctx, py, &objs[0], &tup[0])
250 } else {
251 let mut tuple = Vec::new();
252 for (obj, ty) in objs.iter().zip(tup) {
253 tuple.push(pyobj_to_value(ctx, py, obj, ty)?);
254 }
255 Ok(Value::Tuple(tuple.into(), None))
256 }
257 }
258 Ty::Array(ty) => {
259 let objs = extract_obj::<Vec<Py<PyAny>>>(py, obj, ty)?;
260 let ty = &**ty;
261 let mut array = Vec::new();
262 for obj in &objs {
263 array.push(pyobj_to_value(ctx, py, obj, ty)?);
264 }
265 Ok(Value::Array(array.into()))
266 }
267 Ty::Udt(_, res) => {
268 let qsc::hir::Res::Item(item_id) = res else {
269 panic!("Udt should be an item");
270 };
271 let (udt, kind) = ctx.udt_ty_from_item_id(item_id);
272
273 match kind {
274 interpret::UdtKind::Angle => {
275 let angle = extract_obj::<f64>(py, obj, ty)?;
276 let angle =
277 qsc::openqasm::stdlib::angle::Angle::from_f64_maybe_sized(angle, None);
278 let value = i64::try_from(angle.value)
279 .expect("angles built with `None` size have at most 53 bits");
280 let size = i64::from(angle.size);
281 Ok(Value::Tuple(
282 Rc::new([Value::Int(value), Value::Int(size)]),
283 Some(Rc::new(ctx.get_angle_id())),
284 ))
285 }
286 interpret::UdtKind::Complex => {
287 let val = extract_obj::<num_complex::Complex64>(py, obj, ty)?;
288 Ok(Value::Tuple(
289 Rc::new([Value::Double(val.re), Value::Double(val.im)]),
290 Some(Rc::new(ctx.get_complex_id())),
291 ))
292 }
293 interpret::UdtKind::Udt => {
294 let udt_fields = extract_obj::<UdtFields>(py, obj, ty)?;
295
296 let mut tuple = Vec::new();
297 for (name, ty) in collect_udt_fields(udt)? {
298 let Some(value) = udt_fields.get(&*name) else {
299 return Err(PyTypeError::new_err(format!(
300 "missing field {} in {}",
301 name, udt.name,
302 )));
303 };
304 tuple.push(pyobj_to_value(ctx, py, value, ty)?);
305 }
306 Ok(Value::Tuple(tuple.into(), None))
307 }
308 }
309 }
310 _ => unimplemented!("input type: {ty}"),
311 }
312}
313
314pub(super) fn type_ir_from_qsharp_ty(ctx: &interpret::Interpreter, ty: &Ty) -> Option<TypeIR> {
315 match ty {
316 Ty::Prim(prim) => {
317 let prim = match prim {
318 Prim::Bool => PrimitiveKind::Bool,
319 Prim::Int | Prim::BigInt => PrimitiveKind::Int,
320 Prim::Double => PrimitiveKind::Double,
321 Prim::String => PrimitiveKind::String,
322 Prim::Pauli => PrimitiveKind::Pauli,
323 Prim::Result => PrimitiveKind::Result,
324 Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
325 return None;
326 }
327 };
328 Some(TypeIR::Primitive(prim))
329 }
330 Ty::Array(ty) => Some(TypeIR::Array(vec![type_ir_from_qsharp_ty(ctx, ty)?])),
331 Ty::Tuple(items) => {
332 let mut tuple = Vec::new();
333 for item in items {
334 tuple.push(type_ir_from_qsharp_ty(ctx, item)?);
335 }
336 Some(TypeIR::Tuple(tuple))
337 }
338 Ty::Udt(name, res) => {
339 let qsc::hir::Res::Item(item_id) = res else {
340 panic!("Udt should be an item");
341 };
342 let (udt, kind) = ctx.udt_ty_from_item_id(item_id);
343
344 match kind {
345 interpret::UdtKind::Angle => Some(TypeIR::Primitive(PrimitiveKind::Double)),
346 interpret::UdtKind::Complex => Some(TypeIR::Primitive(PrimitiveKind::Complex)),
347 interpret::UdtKind::Udt => {
348 let udt_fields = collect_udt_fields(udt).ok()?;
349 let mut fields = Vec::new();
350
351 for (name, ty) in udt_fields {
352 fields.push((name.to_string(), type_ir_from_qsharp_ty(ctx, ty)?));
353 }
354
355 Some(TypeIR::Udt(UdtIR {
356 name: name.to_string(),
357 fields,
358 }))
359 }
360 }
361 }
362 Ty::Param { .. } | Ty::Infer(..) | Ty::Arrow(..) | Ty::Err => None,
363 }
364}
365
366pub(crate) fn value_to_pyobj(
367 ctx: &interpret::Interpreter,
368 py: Python,
369 value: &Value,
370) -> PyResult<Py<PyAny>> {
371 match value {
372 Value::Int(val) => val.into_py_any(py),
373 Value::BigInt(val) => val.into_py_any(py),
374 Value::Double(val) => val.into_py_any(py),
375 Value::Bool(val) => val.into_py_any(py),
376 Value::String(val) => val.into_py_any(py),
377 Value::Result(val) => {
378 let val = match val {
379 qsc::interpret::Result::Id(_) => {
380 panic!("unexpected Result::Id in typed_value_to_value_ir")
381 }
382 qsc::interpret::Result::Val(true) => Result::One,
383 qsc::interpret::Result::Val(false) => Result::Zero,
384 qsc::interpret::Result::Loss => Result::Loss,
385 };
386 val.into_py_any(py)
387 }
388 Value::Pauli(val) => {
389 let val = match val {
390 fir::Pauli::I => Pauli::I,
391 fir::Pauli::X => Pauli::X,
392 fir::Pauli::Y => Pauli::Y,
393 fir::Pauli::Z => Pauli::Z,
394 };
395 val.into_py_any(py)
396 }
397 Value::Tuple(values, None) => {
398 let mut tuple = Vec::new();
399 for val in values.iter() {
400 tuple.push(value_to_pyobj(ctx, py, val)?);
401 }
402
403 // Special case Value::UNIT maps to None.
404 if tuple.is_empty() {
405 Ok(py.None())
406 } else {
407 PyTuple::new(py, tuple)?.into_py_any(py)
408 }
409 }
410 Value::Tuple(values, Some(store_item_id)) => {
411 let (udt, kind) = ctx.udt_ty_from_store_item_id(**store_item_id);
412
413 match kind {
414 interpret::UdtKind::Angle => {
415 let value = values[0].clone().unwrap_int();
416 let size = values[1].clone().unwrap_int();
417 let value = u64::try_from(value).expect("value should fit in u64");
418 let size = u32::try_from(size).expect("size should fit in u32");
419 let angle = qsc::openqasm::stdlib::angle::Angle::new(value, size);
420 let angle: f64 = angle
421 .try_into()
422 .map_err(|_| QasmError::new_err("failed to cast angle to 64-bit float"))?;
423 angle.into_py_any(py)
424 }
425 interpret::UdtKind::Complex => {
426 let re = values[0].clone().unwrap_double();
427 let im = values[1].clone().unwrap_double();
428 let val = num_complex::Complex { re, im };
429 val.into_py_any(py)
430 }
431 interpret::UdtKind::Udt => {
432 let ty_fields = collect_udt_fields(udt)?;
433 let mut fields = Vec::new();
434 for (value, (name, _)) in values.iter().zip(ty_fields) {
435 fields.push((name.to_string(), value_to_pyobj(ctx, py, value)?));
436 }
437 UdtValue {
438 name: udt.name.to_string(),
439 fields,
440 }
441 .into_py_any(py)
442 }
443 }
444 }
445 Value::Array(values) => {
446 let mut array = Vec::with_capacity(values.len());
447 for val in values.iter() {
448 array.push(value_to_pyobj(ctx, py, val)?);
449 }
450 PyList::new(py, array)?.into_py_any(py)
451 }
452 Value::Closure(..)
453 | Value::Global(..)
454 | Value::Qubit(..)
455 | Value::Range(..)
456 | Value::Var(..) => format!("<{}> {}", value.type_name(), value).into_py_any(py),
457 }
458}
459