microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4c4e9d5f767314c193e5cc06ad889516c3c69b93

Branches

Tags

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

Clone

HTTPS

Download ZIP

pip/src/interpreter.rs

215lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::displayable_output::{DisplayableOutput, DisplayableState};
5use miette::Report;
6use num_bigint::BigUint;
7use num_complex::Complex64;
8use pyo3::{create_exception, exceptions::PyException, prelude::*, types::PyList, types::PyTuple};
9use qsc::{
10 hir,
11 interpret::{
12 output::{Error, Receiver},
13 stateful::{self, LineError},
14 Value,
15 },
16 SourceMap,
17};
18use std::{fmt::Write, sync::Arc};
19
20#[pymodule]
21fn _native(py: Python, m: &PyModule) -> PyResult<()> {
22 m.add_class::<Interpreter>()?;
23 m.add_class::<Result>()?;
24 m.add_class::<Pauli>()?;
25 m.add_class::<Output>()?;
26 m.add("QSharpError", py.get_type::<QSharpError>())?;
27
28 Ok(())
29}
30
31#[pyclass(unsendable)]
32pub(crate) struct Interpreter {
33 pub(crate) interpreter: stateful::Interpreter,
34}
35
36#[pymethods]
37/// A Q# interpreter.
38impl Interpreter {
39 #[new]
40 /// Initializes a new Q# interpreter.
41 pub(crate) fn new(_py: Python) -> PyResult<Self> {
42 match stateful::Interpreter::new(true, SourceMap::default()) {
43 Ok(interpreter) => Ok(Self { interpreter }),
44 Err(errors) => {
45 let mut message = String::new();
46 for error in errors {
47 writeln!(message, "{error}").expect("string should be writable");
48 }
49 Err(PyException::new_err(message))
50 }
51 }
52 }
53
54 /// Interprets Q# source code.
55 ///
56 /// :param input: The Q# source code to interpret.
57 /// :param output_fn: A callback function that will be called with each output.
58 ///
59 /// :returns value: The value returned by the last statement in the input.
60 ///
61 /// :raises QSharpError: If there is an error interpreting the input.
62 fn interpret(
63 &mut self,
64 py: Python,
65 input: &str,
66 callback: Option<PyObject>,
67 ) -> PyResult<PyObject> {
68 let mut receiver = OptionalCallbackReceiver { callback, py };
69 match self.interpreter.interpret_line(&mut receiver, input) {
70 Ok(value) => Ok(ValueWrapper(value).into_py(py)),
71 Err(errors) => Err(QSharpError::new_err(format_errors(input, errors))),
72 }
73 }
74}
75
76create_exception!(
77 module,
78 QSharpError,
79 pyo3::exceptions::PyException,
80 "An error returned from the Q# interpreter."
81);
82
83fn format_errors(expr: &str, errors: Vec<LineError>) -> String {
84 errors
85 .into_iter()
86 .map(|e| {
87 let mut message = String::new();
88 if let Some(stack_trace) = e.stack_trace() {
89 write!(message, "{stack_trace}").unwrap();
90 }
91 let report = Report::new(e).with_source_code(Arc::new(expr.to_owned()));
92 write!(message, "{report:?}").unwrap();
93 message
94 })
95 .collect::<String>()
96}
97
98#[pyclass(unsendable)]
99pub(crate) struct Output(DisplayableOutput);
100
101#[pymethods]
102/// An output returned from the Q# interpreter.
103/// Outputs can be a state dumps or messages. These are normally printed to the console.
104impl Output {
105 fn __repr__(&self) -> String {
106 match &self.0 {
107 DisplayableOutput::State(state) => state.to_plain(),
108 DisplayableOutput::Message(msg) => msg.clone(),
109 }
110 }
111
112 fn __str__(&self) -> String {
113 self.__repr__()
114 }
115
116 fn _repr_html_(&self) -> String {
117 match &self.0 {
118 DisplayableOutput::State(state) => state.to_html(),
119 DisplayableOutput::Message(msg) => format!("<p>{msg}</p>"),
120 }
121 }
122}
123
124#[pyclass(unsendable)]
125/// A Q# measurement result.
126pub(crate) enum Result {
127 Zero,
128 One,
129}
130
131#[pyclass(unsendable)]
132/// A Q# Pauli operator.
133pub(crate) enum Pauli {
134 I,
135 X,
136 Y,
137 Z,
138}
139
140// Mapping of Q# value types to Python value types.
141struct ValueWrapper(Value);
142
143impl IntoPy<PyObject> for ValueWrapper {
144 fn into_py(self, py: Python) -> PyObject {
145 match self.0 {
146 Value::Int(val) => val.into_py(py),
147 Value::Double(val) => val.into_py(py),
148 Value::Bool(val) => val.into_py(py),
149 Value::String(val) => val.into_py(py),
150 Value::Result(val) => if val { Result::One } else { Result::Zero }.into_py(py),
151 Value::Pauli(val) => match val {
152 hir::Pauli::I => Pauli::I.into_py(py),
153 hir::Pauli::X => Pauli::X.into_py(py),
154 hir::Pauli::Y => Pauli::Y.into_py(py),
155 hir::Pauli::Z => Pauli::Z.into_py(py),
156 },
157 Value::Tuple(val) => {
158 if val.is_empty() {
159 // Special case Value::unit as None
160 py.None()
161 } else {
162 PyTuple::new(py, val.iter().map(|v| ValueWrapper(v.clone()).into_py(py)))
163 .into_py(py)
164 }
165 }
166 Value::Array(val) => {
167 PyList::new(py, val.iter().map(|v| ValueWrapper(v.clone()).into_py(py))).into_py(py)
168 }
169 _ => format!("<{}> {}", Value::type_name(&self.0), &self.0).into_py(py),
170 }
171 }
172}
173
174struct OptionalCallbackReceiver<'a> {
175 callback: Option<PyObject>,
176 py: Python<'a>,
177}
178
179impl Receiver for OptionalCallbackReceiver<'_> {
180 fn state(
181 &mut self,
182 state: Vec<(BigUint, Complex64)>,
183 qubit_count: usize,
184 ) -> core::result::Result<(), Error> {
185 if let Some(callback) = &self.callback {
186 let out = DisplayableOutput::State(DisplayableState(state, qubit_count));
187 callback
188 .call1(
189 self.py,
190 PyTuple::new(
191 self.py,
192 &[Py::new(self.py, Output(out)).expect("should be able to create output")],
193 ),
194 )
195 .map_err(|_| Error)?;
196 }
197 Ok(())
198 }
199
200 fn message(&mut self, msg: &str) -> core::result::Result<(), Error> {
201 if let Some(callback) = &self.callback {
202 let out = DisplayableOutput::Message(msg.to_owned());
203 callback
204 .call1(
205 self.py,
206 PyTuple::new(
207 self.py,
208 &[Py::new(self.py, Output(out)).expect("should be able to create output")],
209 ),
210 )
211 .map_err(|_| Error)?;
212 }
213 Ok(())
214 }
215}
216