microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7421e7dd1015dcbd940bf843d33583470de580ea

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interpreter/data_interop.rs

456lines · 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::FromPyObjectBound,
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, PyObject>;
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, PyObject)>,
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: &PyObject) -> 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 PyObject, ty: &Ty) -> PyResult<T>
193where
194 T: FromPyObjectBound<'obj, 'py>,
195 'py: 'obj,
196{
197 match obj.extract::<T>(py) {
198 Ok(val) => Ok(val),
199 Err(err) => {
200 if err.is_instance_of::<PyTypeError>(py) {
201 // If we have a type error, we return a friendly user error.
202 Err(PyTypeError::new_err(format!(
203 "expected {}, found {}",
204 ty.display(),
205 obj_type(py, obj)?
206 )))
207 } else {
208 // If we have other kind of errors (e.g.: an overflow error when
209 // converting from a python int to a rust i64) we leave it as is.
210 Err(err)
211 }
212 }
213 }
214}
215
216/// Given a type, convert a Python object into a Q# value of that type. This will recur through tuples and arrays,
217/// and will return an error if the type is not supported or the object cannot be converted.
218pub(super) fn pyobj_to_value(
219 ctx: &interpret::Interpreter,
220 py: Python,
221 obj: &PyObject,
222 ty: &Ty,
223) -> PyResult<Value> {
224 match ty {
225 Ty::Prim(prim_ty) => match prim_ty {
226 Prim::Bool => Ok(Value::Bool(extract_obj::<bool>(py, obj, ty)?)),
227 Prim::Int => Ok(Value::Int(extract_obj::<i64>(py, obj, ty)?)),
228 Prim::BigInt => Ok(Value::BigInt(extract_obj::<BigInt>(py, obj, ty)?)),
229 Prim::Double => Ok(Value::Double(extract_obj::<f64>(py, obj, ty)?)),
230 Prim::String => Ok(Value::String(extract_obj::<String>(py, obj, ty)?.into())),
231 Prim::Result => Ok(Value::Result(extract_obj::<Result>(py, obj, ty)?.into())),
232 Prim::Pauli => Ok(Value::Pauli(extract_obj::<Pauli>(py, obj, ty)?.into())),
233 Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
234 unimplemented!("primitive input type: {prim_ty:?}")
235 }
236 },
237 Ty::Tuple(tup) => {
238 let objs = extract_obj::<Vec<PyObject>>(py, obj, ty)?;
239
240 if tup.len() != objs.len() {
241 return Err(PyTypeError::new_err(format!(
242 "mismatched tuple arity: expected {}, found {}",
243 tup.len(),
244 objs.len()
245 )));
246 }
247 if objs.len() == 1 {
248 pyobj_to_value(ctx, py, &objs[0], &tup[0])
249 } else {
250 let mut tuple = Vec::new();
251 for (obj, ty) in objs.iter().zip(tup) {
252 tuple.push(pyobj_to_value(ctx, py, obj, ty)?);
253 }
254 Ok(Value::Tuple(tuple.into(), None))
255 }
256 }
257 Ty::Array(ty) => {
258 let objs = extract_obj::<Vec<PyObject>>(py, obj, ty)?;
259 let ty = &**ty;
260 let mut array = Vec::new();
261 for obj in &objs {
262 array.push(pyobj_to_value(ctx, py, obj, ty)?);
263 }
264 Ok(Value::Array(array.into()))
265 }
266 Ty::Udt(_, res) => {
267 let qsc::hir::Res::Item(item_id) = res else {
268 panic!("Udt should be an item");
269 };
270 let (udt, kind) = ctx.udt_ty_from_item_id(item_id);
271
272 match kind {
273 interpret::UdtKind::Angle => {
274 let angle = extract_obj::<f64>(py, obj, ty)?;
275 let angle = qsc::qasm::stdlib::angle::Angle::from_f64_maybe_sized(angle, None);
276 let value = i64::try_from(angle.value)
277 .expect("angles built with `None` size have at most 53 bits");
278 let size = i64::from(angle.size);
279 Ok(Value::Tuple(
280 Rc::new([Value::Int(value), Value::Int(size)]),
281 Some(Rc::new(ctx.get_angle_id())),
282 ))
283 }
284 interpret::UdtKind::Complex => {
285 let val = extract_obj::<num_complex::Complex64>(py, obj, ty)?;
286 Ok(Value::Tuple(
287 Rc::new([Value::Double(val.re), Value::Double(val.im)]),
288 Some(Rc::new(ctx.get_complex_id())),
289 ))
290 }
291 interpret::UdtKind::Udt => {
292 let udt_fields = extract_obj::<UdtFields>(py, obj, ty)?;
293
294 let mut tuple = Vec::new();
295 for (name, ty) in collect_udt_fields(udt)? {
296 let Some(value) = udt_fields.get(&*name) else {
297 return Err(PyTypeError::new_err(format!(
298 "missing field {} in {}",
299 name, udt.name,
300 )));
301 };
302 tuple.push(pyobj_to_value(ctx, py, value, ty)?);
303 }
304 Ok(Value::Tuple(tuple.into(), None))
305 }
306 }
307 }
308 _ => unimplemented!("input type: {ty}"),
309 }
310}
311
312pub(super) fn type_ir_from_qsharp_ty(ctx: &interpret::Interpreter, ty: &Ty) -> Option<TypeIR> {
313 match ty {
314 Ty::Prim(prim) => {
315 let prim = match prim {
316 Prim::Bool => PrimitiveKind::Bool,
317 Prim::Int | Prim::BigInt => PrimitiveKind::Int,
318 Prim::Double => PrimitiveKind::Double,
319 Prim::String => PrimitiveKind::String,
320 Prim::Pauli => PrimitiveKind::Pauli,
321 Prim::Result => PrimitiveKind::Result,
322 Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
323 return None;
324 }
325 };
326 Some(TypeIR::Primitive(prim))
327 }
328 Ty::Array(ty) => Some(TypeIR::Array(vec![type_ir_from_qsharp_ty(ctx, ty)?])),
329 Ty::Tuple(items) => {
330 let mut tuple = Vec::new();
331 for item in items {
332 tuple.push(type_ir_from_qsharp_ty(ctx, item)?);
333 }
334 Some(TypeIR::Tuple(tuple))
335 }
336 Ty::Udt(name, res) => {
337 let qsc::hir::Res::Item(item_id) = res else {
338 panic!("Udt should be an item");
339 };
340 let (udt, kind) = ctx.udt_ty_from_item_id(item_id);
341
342 match kind {
343 interpret::UdtKind::Angle => Some(TypeIR::Primitive(PrimitiveKind::Double)),
344 interpret::UdtKind::Complex => Some(TypeIR::Primitive(PrimitiveKind::Complex)),
345 interpret::UdtKind::Udt => {
346 let udt_fields = collect_udt_fields(udt).ok()?;
347 let mut fields = Vec::new();
348
349 for (name, ty) in udt_fields {
350 fields.push((name.to_string(), type_ir_from_qsharp_ty(ctx, ty)?));
351 }
352
353 Some(TypeIR::Udt(UdtIR {
354 name: name.to_string(),
355 fields,
356 }))
357 }
358 }
359 }
360 Ty::Param { .. } | Ty::Infer(..) | Ty::Arrow(..) | Ty::Err => None,
361 }
362}
363
364pub(crate) fn value_to_pyobj(
365 ctx: &interpret::Interpreter,
366 py: Python,
367 value: &Value,
368) -> PyResult<PyObject> {
369 match value {
370 Value::Int(val) => val.into_py_any(py),
371 Value::BigInt(val) => val.into_py_any(py),
372 Value::Double(val) => val.into_py_any(py),
373 Value::Bool(val) => val.into_py_any(py),
374 Value::String(val) => val.into_py_any(py),
375 Value::Result(val) => {
376 let val = match val {
377 qsc::interpret::Result::Id(_) => {
378 panic!("unexpected Result::Id in typed_value_to_value_ir")
379 }
380 qsc::interpret::Result::Val(true) => Result::One,
381 qsc::interpret::Result::Val(false) => Result::Zero,
382 qsc::interpret::Result::Loss => Result::Loss,
383 };
384 val.into_py_any(py)
385 }
386 Value::Pauli(val) => {
387 let val = match val {
388 fir::Pauli::I => Pauli::I,
389 fir::Pauli::X => Pauli::X,
390 fir::Pauli::Y => Pauli::Y,
391 fir::Pauli::Z => Pauli::Z,
392 };
393 val.into_py_any(py)
394 }
395 Value::Tuple(values, None) => {
396 let mut tuple = Vec::new();
397 for val in values.iter() {
398 tuple.push(value_to_pyobj(ctx, py, val)?);
399 }
400
401 // Special case Value::UNIT maps to None.
402 if tuple.is_empty() {
403 Ok(py.None())
404 } else {
405 PyTuple::new(py, tuple)?.into_py_any(py)
406 }
407 }
408 Value::Tuple(values, Some(store_item_id)) => {
409 let (udt, kind) = ctx.udt_ty_from_store_item_id(**store_item_id);
410
411 match kind {
412 interpret::UdtKind::Angle => {
413 let value = values[0].clone().unwrap_int();
414 let size = values[1].clone().unwrap_int();
415 let value = u64::try_from(value).expect("value should fit in u64");
416 let size = u32::try_from(size).expect("size should fit in u32");
417 let angle = qsc::qasm::stdlib::angle::Angle::new(value, size);
418 let angle: f64 = angle
419 .try_into()
420 .map_err(|_| QasmError::new_err("failed to cast angle to 64-bit float"))?;
421 angle.into_py_any(py)
422 }
423 interpret::UdtKind::Complex => {
424 let re = values[0].clone().unwrap_double();
425 let im = values[1].clone().unwrap_double();
426 let val = num_complex::Complex { re, im };
427 val.into_py_any(py)
428 }
429 interpret::UdtKind::Udt => {
430 let ty_fields = collect_udt_fields(udt)?;
431 let mut fields = Vec::new();
432 for (value, (name, _)) in values.iter().zip(ty_fields) {
433 fields.push((name.to_string(), value_to_pyobj(ctx, py, value)?));
434 }
435 UdtValue {
436 name: udt.name.to_string(),
437 fields,
438 }
439 .into_py_any(py)
440 }
441 }
442 }
443 Value::Array(values) => {
444 let mut array = Vec::with_capacity(values.len());
445 for val in values.iter() {
446 array.push(value_to_pyobj(ctx, py, val)?);
447 }
448 PyList::new(py, array)?.into_py_any(py)
449 }
450 Value::Closure(..)
451 | Value::Global(..)
452 | Value::Qubit(..)
453 | Value::Range(..)
454 | Value::Var(..) => format!("<{}> {}", value.type_name(), value).into_py_any(py),
455 }
456}
457