microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.22.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interpreter.rs

1374lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4pub(crate) mod data_interop;
5
6use crate::{
7 displayable_output::{DisplayableMatrix, DisplayableOutput, DisplayableState},
8 fs::file_system,
9 generic_estimator::register_generic_estimator_submodule,
10 interop::{
11 circuit_qasm_program, compile_qasm_program_to_qir, compile_qasm_to_qsharp,
12 create_filesystem_from_py, get_operation_name, get_output_semantics, get_program_type,
13 get_search_path, resource_estimate_qasm_program, run_qasm_program,
14 },
15 interpreter::data_interop::{
16 PrimitiveKind, TypeIR, TypeKind, UdtFields, UdtIR, UdtValue, collect_udt_fields,
17 pyobj_to_value, type_ir_from_qsharp_ty, value_to_pyobj,
18 },
19 noisy_simulator::register_noisy_simulator_submodule,
20};
21use miette::{Diagnostic, Report};
22use num_bigint::BigUint;
23use num_complex::Complex64;
24use pyo3::{
25 IntoPyObjectExt, create_exception,
26 exceptions::{PyException, PyValueError},
27 prelude::*,
28 types::{PyDict, PyList, PyString, PyTuple, PyType},
29};
30use qsc::{
31 LanguageFeatures, PackageType, SourceMap,
32 error::WithSource,
33 fir::{self},
34 hir::ty::{Prim, Ty},
35 interpret::{
36 self, CircuitEntryPoint, PauliNoise, TaggedItem, Value,
37 output::{Error, Receiver},
38 },
39 packages::BuildableProgram,
40 project::{FileSystem, PackageCache, PackageGraphSources, ProjectType},
41 qasm::{CompilerConfig, QubitSemantics, compiler::compile_to_qsharp_ast_with_config},
42 target::Profile,
43};
44
45use resource_estimator::{
46 self as re, estimate_call, estimate_expr, logical_counts_call, logical_counts_expr,
47};
48use std::{cell::RefCell, fmt::Write, path::PathBuf, rc::Rc, str::FromStr, sync::Arc};
49
50/// If the classes are not Send, the Python interpreter
51/// will not be able to use them in a separate thread.
52///
53/// This function is used to verify that the classes are Send.
54/// The code will fail to compile if the classes are not Send.
55///
56/// ### Note
57/// `QSharpError`, and `QasmError` are not `Send`, *BUT*
58/// we return `QasmError::new_err` or `QSharpError::new_err` which
59/// actually returns a `PyErr` that is `Send` and the args passed
60/// into the `new_err` call must also impl `Send`.
61/// Because of this, we don't need to check the `Send`-ness of
62/// them. On the Python side, the `PyErr` is converted into the
63/// corresponding exception.
64fn verify_classes_are_sendable() {
65 fn is_send<T: Send>() {}
66 is_send::<OutputSemantics>();
67 is_send::<ProgramType>();
68 is_send::<TargetProfile>();
69 is_send::<Result>();
70 is_send::<Pauli>();
71 is_send::<Output>();
72 is_send::<StateDumpData>();
73 is_send::<Circuit>();
74 is_send::<UdtValue>();
75 is_send::<UdtFields>();
76 is_send::<TypeIR>();
77 is_send::<TypeKind>();
78 is_send::<PrimitiveKind>();
79 is_send::<UdtIR>();
80}
81
82#[pymodule]
83fn _native<'a>(py: Python<'a>, m: &Bound<'a, PyModule>) -> PyResult<()> {
84 verify_classes_are_sendable();
85 m.add_class::<OutputSemantics>()?;
86 m.add_class::<ProgramType>()?;
87 m.add_class::<TargetProfile>()?;
88 m.add_class::<Interpreter>()?;
89 m.add_class::<Result>()?;
90 m.add_class::<Pauli>()?;
91 m.add_class::<Output>()?;
92 m.add_class::<StateDumpData>()?;
93 m.add_class::<Circuit>()?;
94 m.add_class::<GlobalCallable>()?;
95 m.add_class::<UdtValue>()?;
96 m.add_class::<TypeIR>()?;
97 m.add_class::<TypeKind>()?;
98 m.add_class::<PrimitiveKind>()?;
99 m.add_class::<UdtIR>()?;
100 m.add_function(wrap_pyfunction!(physical_estimates, m)?)?;
101 m.add("QSharpError", py.get_type::<QSharpError>())?;
102 register_noisy_simulator_submodule(py, m)?;
103 register_generic_estimator_submodule(m)?;
104 // QASM interop
105 m.add("QasmError", py.get_type::<QasmError>())?;
106 m.add_function(wrap_pyfunction!(resource_estimate_qasm_program, m)?)?;
107 m.add_function(wrap_pyfunction!(run_qasm_program, m)?)?;
108 m.add_function(wrap_pyfunction!(circuit_qasm_program, m)?)?;
109 m.add_function(wrap_pyfunction!(compile_qasm_program_to_qir, m)?)?;
110 m.add_function(wrap_pyfunction!(compile_qasm_to_qsharp, m)?)?;
111 Ok(())
112}
113
114// This ordering must match the _native.pyi file.
115#[derive(Clone, Copy, Default, PartialEq)]
116#[pyclass(eq, eq_int, module = "qsharp._native")]
117#[allow(non_camel_case_types)]
118/// A Q# target profile.
119///
120/// A target profile describes the capabilities of the hardware or simulator
121/// which will be used to run the Q# program.
122pub(crate) enum TargetProfile {
123 /// Target supports the minimal set of capabilities required to run a quantum program.
124 ///
125 /// This option maps to the Base Profile as defined by the QIR specification.
126 #[default]
127 Base,
128 /// Target supports the Adaptive profile with the integer computation extension.
129 ///
130 /// This profile includes all of the required Adaptive Profile
131 /// capabilities, as well as the optional integer computation
132 /// extension defined by the QIR specification.
133 Adaptive_RI,
134 /// Target supports the Adaptive profile with integer & floating-point
135 /// computation extensions.
136 ///
137 /// This profile includes all required Adaptive Profile and `Adaptive_RI`
138 /// capabilities, as well as the optional floating-point computation
139 /// extension defined by the QIR specification.
140 Adaptive_RIF,
141 /// Target supports the full set of capabilities required to run any Q# program.
142 ///
143 /// This option maps to the Full Profile as defined by the QIR specification.
144 Unrestricted,
145}
146
147#[pymethods]
148impl TargetProfile {
149 #[new]
150 // We need to define `new` so that instances of `TargetProfile` can be created by Python
151 pub(crate) fn new() -> Self {
152 Self::default()
153 }
154
155 // called and the returned object is pickled as the contents for the instance
156 #[allow(clippy::trivially_copy_pass_by_ref)]
157 fn __getstate__(&self) -> PyResult<isize> {
158 Ok(self.__pyo3__int__())
159 }
160
161 // called with the unpickled state and the instance is updated in place
162 // This is what requires `new` to be implemented as we can't hydrate an
163 // unininitialized instance in Python.
164 fn __setstate__(&mut self, state: i32) -> PyResult<()> {
165 (*self) = match state {
166 0 => Self::Base,
167 1 => Self::Adaptive_RI,
168 2 => Self::Adaptive_RIF,
169 3 => Self::Unrestricted,
170 _ => return Err(PyValueError::new_err("invalid state")),
171 };
172 Ok(())
173 }
174
175 #[allow(clippy::trivially_copy_pass_by_ref)]
176 fn __str__(&self) -> String {
177 Into::<Profile>::into(*self).to_str().to_owned()
178 }
179
180 /// Creates a target profile from a string.
181 /// :param value: The string to parse.
182 /// :raises ValueError: If the string does not match any target profile.
183 #[classmethod]
184 #[allow(clippy::needless_pass_by_value)]
185 fn from_str(_cls: &Bound<'_, PyType>, key: String) -> pyo3::PyResult<Self> {
186 let profile = Profile::from_str(key.as_str())
187 .map_err(|()| PyValueError::new_err(format!("{key} is not a valid target profile")))?;
188 Ok(TargetProfile::from(profile))
189 }
190}
191
192impl From<Profile> for TargetProfile {
193 fn from(profile: Profile) -> Self {
194 match profile {
195 Profile::Base => TargetProfile::Base,
196 Profile::AdaptiveRI => TargetProfile::Adaptive_RI,
197 Profile::AdaptiveRIF => TargetProfile::Adaptive_RIF,
198 Profile::Unrestricted => TargetProfile::Unrestricted,
199 }
200 }
201}
202
203impl From<TargetProfile> for Profile {
204 fn from(profile: TargetProfile) -> Self {
205 match profile {
206 TargetProfile::Base => Profile::Base,
207 TargetProfile::Adaptive_RI => Profile::AdaptiveRI,
208 TargetProfile::Adaptive_RIF => Profile::AdaptiveRIF,
209 TargetProfile::Unrestricted => Profile::Unrestricted,
210 }
211 }
212}
213
214// This ordering must match the _native.pyi file.
215#[derive(Clone, Copy, Default, PartialEq)]
216#[pyclass(eq, eq_int, module = "qsharp._native")]
217#[allow(non_camel_case_types)]
218/// Represents the output semantics for OpenQASM 3 compilation.
219/// Each has implications on the output of the compilation
220/// and the semantic checks that are performed.
221pub(crate) enum OutputSemantics {
222 /// The output is in Qiskit format meaning that the output
223 /// is all of the classical registers, in reverse order
224 /// in which they were added to the circuit with each
225 /// bit within each register in reverse order.
226 #[default]
227 Qiskit,
228 /// [OpenQASM 3 has two output modes](https://openqasm.com/language/directives.html#input-output)
229 /// - If the programmer provides one or more `output` declarations, then
230 /// variables described as outputs will be returned as output.
231 /// The spec make no mention of endianness or order of the output.
232 /// - Otherwise, assume all of the declared variables are returned as output.
233 OpenQasm,
234 /// No output semantics are applied. The entry point returns `Unit`.
235 ResourceEstimation,
236}
237
238#[pymethods]
239impl OutputSemantics {
240 #[new]
241 // We need to define `new` so that instances of `TargetProfile` can be created by Python
242 pub(crate) fn new() -> Self {
243 Self::default()
244 }
245
246 // called and the returned object is pickled as the contents for the instance
247 #[allow(clippy::trivially_copy_pass_by_ref)]
248 fn __getstate__(&self) -> PyResult<isize> {
249 Ok(self.__pyo3__int__())
250 }
251
252 // called with the unpickled state and the instance is updated in place
253 // This is what requires `new` to be implemented as we can't hydrate an
254 // unininitialized instance in Python.
255 fn __setstate__(&mut self, state: i32) -> PyResult<()> {
256 (*self) = match state {
257 0 => Self::Qiskit,
258 1 => Self::OpenQasm,
259 2 => Self::ResourceEstimation,
260 _ => return Err(PyValueError::new_err("invalid state")),
261 };
262 Ok(())
263 }
264}
265
266impl From<OutputSemantics> for qsc::qasm::OutputSemantics {
267 fn from(output_semantics: OutputSemantics) -> Self {
268 match output_semantics {
269 OutputSemantics::Qiskit => qsc::qasm::OutputSemantics::Qiskit,
270 OutputSemantics::OpenQasm => qsc::qasm::OutputSemantics::OpenQasm,
271 OutputSemantics::ResourceEstimation => qsc::qasm::OutputSemantics::ResourceEstimation,
272 }
273 }
274}
275
276// This ordering must match the _native.pyi file.
277#[derive(Clone, Copy, Default, PartialEq)]
278#[pyclass(eq, eq_int, module = "qsharp._native")]
279#[allow(non_camel_case_types)]
280/// Represents the type of compilation output to create
281pub enum ProgramType {
282 /// Creates an operation in a namespace as if the program is a standalone
283 /// file. Inputs are lifted to the operation params. Output are lifted to
284 /// the operation return type. The operation is marked as `@EntryPoint`
285 /// as long as there are no input parameters.
286 #[default]
287 File,
288 /// Programs are compiled to a standalone function. Inputs are lifted to
289 /// the operation params. Output are lifted to the operation return type.
290 Operation,
291 /// Creates a list of statements from the program. This is useful for
292 /// interactive environments where the program is a list of statements
293 /// imported into the current scope.
294 /// This is also useful for testing individual statements compilation.
295 Fragments,
296}
297
298#[pymethods]
299impl ProgramType {
300 #[new]
301 // We need to define `new` so that instances of `TargetProfile` can be created by Python
302 pub(crate) fn new() -> Self {
303 Self::default()
304 }
305
306 // called and the returned object is pickled as the contents for the instance
307 #[allow(clippy::trivially_copy_pass_by_ref)]
308 fn __getstate__(&self) -> PyResult<isize> {
309 Ok(self.__pyo3__int__())
310 }
311
312 // called with the unpickled state and the instance is updated in place
313 // This is what requires `new` to be implemented as we can't hydrate an
314 // unininitialized instance in Python.
315 fn __setstate__(&mut self, state: i32) -> PyResult<()> {
316 (*self) = match state {
317 0 => Self::File,
318 1 => Self::Operation,
319 2 => Self::Fragments,
320 _ => return Err(PyValueError::new_err("invalid state")),
321 };
322 Ok(())
323 }
324}
325
326impl From<ProgramType> for qsc::qasm::ProgramType {
327 fn from(output_semantics: ProgramType) -> Self {
328 match output_semantics {
329 ProgramType::File => qsc::qasm::ProgramType::File,
330 ProgramType::Operation => qsc::qasm::ProgramType::Operation,
331 ProgramType::Fragments => qsc::qasm::ProgramType::Fragments,
332 }
333 }
334}
335
336#[allow(clippy::struct_field_names)]
337#[pyclass(unsendable)]
338pub(crate) struct Interpreter {
339 pub(crate) interpreter: interpret::Interpreter,
340 /// The Python function to call to create a new function wrapping a callable invocation.
341 pub(crate) make_callable: Option<PyObject>,
342 /// The Python function to call to create a class representing a qsharp struct.
343 pub(crate) make_class: Option<PyObject>,
344 /// Whether circuit tracing was enabled.
345 trace_circuit: bool,
346}
347
348thread_local! { static PACKAGE_CACHE: Rc<RefCell<PackageCache>> = Rc::default(); }
349
350#[pymethods]
351/// A Q# interpreter.
352impl Interpreter {
353 #[allow(clippy::too_many_arguments)]
354 #[allow(clippy::needless_pass_by_value)]
355 #[pyo3(signature = (target_profile, language_features=None, project_root=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None, make_callable=None, make_class=None, trace_circuit=None))]
356 #[new]
357 /// Initializes a new Q# interpreter.
358 pub(crate) fn new(
359 py: Python,
360 target_profile: TargetProfile,
361 language_features: Option<Vec<String>>,
362 project_root: Option<String>,
363 read_file: Option<PyObject>,
364 list_directory: Option<PyObject>,
365 resolve_path: Option<PyObject>,
366 fetch_github: Option<PyObject>,
367 make_callable: Option<PyObject>,
368 make_class: Option<PyObject>,
369 trace_circuit: Option<bool>,
370 ) -> PyResult<Self> {
371 let target = Into::<Profile>::into(target_profile).into();
372
373 let language_features = LanguageFeatures::from_iter(language_features.unwrap_or_default());
374
375 let package_cache = PACKAGE_CACHE.with(Clone::clone);
376
377 let buildable_program = if let Some(project_root) = project_root {
378 if let (Some(read_file), Some(list_directory), Some(resolve_path), Some(fetch_github)) =
379 (read_file, list_directory, resolve_path, fetch_github)
380 {
381 let project =
382 file_system(py, read_file, list_directory, resolve_path, fetch_github)
383 .load_project(&PathBuf::from(project_root), Some(&package_cache))
384 .map_err(IntoPyErr::into_py_err)?;
385
386 if !project.errors.is_empty() {
387 return Err(project.errors.into_py_err());
388 }
389 let ProjectType::QSharp(package_graph_sources) = project.project_type else {
390 unreachable!("Project type should be Q#")
391 };
392 BuildableProgram::new(target, package_graph_sources)
393 } else {
394 panic!("file system hooks should have been passed in with a manifest descriptor")
395 }
396 } else {
397 let graph = PackageGraphSources::with_no_dependencies(
398 Vec::default(),
399 LanguageFeatures::from_iter(language_features),
400 None,
401 );
402 BuildableProgram::new(target, graph)
403 };
404
405 let trace_circuit = trace_circuit.unwrap_or(false);
406 let interpreter = if trace_circuit {
407 interpret::Interpreter::with_circuit_trace(
408 SourceMap::new(buildable_program.user_code.sources, None),
409 PackageType::Lib,
410 target,
411 buildable_program.user_code.language_features,
412 buildable_program.store,
413 &buildable_program.user_code_dependencies,
414 )
415 } else {
416 interpret::Interpreter::new(
417 SourceMap::new(buildable_program.user_code.sources, None),
418 PackageType::Lib,
419 target,
420 buildable_program.user_code.language_features,
421 buildable_program.store,
422 &buildable_program.user_code_dependencies,
423 )
424 }
425 .map_err(|errors| QSharpError::new_err(format_errors(errors)))?;
426
427 if let Some(make_callable) = &make_callable {
428 // Add any global callables from the user source as Python functions to the environment.
429 let exported_items = interpreter.source_globals();
430 for (namespace, name, val) in exported_items {
431 create_py_callable(py, make_callable, &namespace, &name, val)?;
432 }
433 }
434 if let Some(make_class) = &make_class {
435 // Add any global structs from the user source as Python classes to the environment.
436 let exported_items = interpreter.source_types();
437 for TaggedItem {
438 item_id,
439 name,
440 namespace,
441 } in exported_items
442 {
443 let ty = Ty::Udt(name.clone(), qsc::hir::Res::Item(item_id));
444 create_py_class(&interpreter, py, make_class, &namespace, &name, &ty)?;
445 }
446 }
447 Ok(Self {
448 interpreter,
449 make_callable,
450 make_class,
451 trace_circuit,
452 })
453 }
454
455 /// Interprets Q# source code.
456 ///
457 /// :param input: The Q# source code to interpret.
458 /// :param output_fn: A callback function that will be called with each output.
459 ///
460 /// :returns value: The value returned by the last statement in the input.
461 ///
462 /// :raises QSharpError: If there is an error interpreting the input.
463 #[pyo3(signature=(input, callback=None))]
464 fn interpret(
465 &mut self,
466 py: Python,
467 input: &str,
468 callback: Option<PyObject>,
469 ) -> PyResult<PyObject> {
470 let mut receiver = OptionalCallbackReceiver { callback, py };
471 match self.interpreter.eval_fragments(&mut receiver, input) {
472 Ok(value) => {
473 if let Some(make_callable) = &self.make_callable {
474 // Get any global callables from the evaluated input and add them to the environment. This will grab
475 // every callable that was defined in the input and by previous calls that added to the open package.
476 // This is safe because either the callable will be replaced with itself or a new callable with the
477 // same name will shadow the previous one, which is the expected behavior.
478 let new_items = self.interpreter.user_globals();
479 for (namespace, name, val) in new_items {
480 create_py_callable(py, make_callable, &namespace, &name, val)?;
481 }
482 }
483 if let Some(make_class) = &self.make_class {
484 // Get any global UDTs from the evaluated input and add them to the environment. This will grab
485 // every UDT that was defined in the input and by previous calls that added to the open package.
486 // This is safe because either the UDT will be replaced with itself or a new UDT with the
487 // same name will shadow the previous one, which is the expected behavior.
488 let new_items = self.interpreter.user_types();
489 for TaggedItem {
490 item_id,
491 name,
492 namespace,
493 } in new_items
494 {
495 let ty = Ty::Udt(name.clone(), qsc::hir::Res::Item(item_id));
496 create_py_class(&self.interpreter, py, make_class, &namespace, &name, &ty)?;
497 }
498 }
499 value_to_pyobj(&self.interpreter, py, &value)
500 }
501 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
502 }
503 }
504
505 /// Imports OpenQASM source code into the active Q# interpreter.
506 ///
507 /// Args:
508 /// source (str): An OpenQASM program or fragment.
509 /// output_fn: The function to handle the output of the execution.
510 /// read_file: A callable that reads a file and returns its content and path.
511 /// list_directory: A callable that lists the contents of a directory.
512 /// resolve_path: A callable that resolves a file path given a base path and a relative path.
513 /// fetch_github: A callable that fetches a file from GitHub.
514 /// **kwargs: Additional keyword arguments to pass to the execution.
515 /// - name (str): The name of the program. This is used as the entry point for the program.
516 /// - search_path (Optional[str]): The optional search path for resolving file references.
517 /// - output_semantics (OutputSemantics, optional): The output semantics for the compilation.
518 /// - program_type (ProgramType, optional): The type of program compilation to perform.
519 ///
520 /// Returns:
521 /// value: The value returned by the last statement in the source code.
522 ///
523 /// Raises:
524 /// QasmError: If there is an error generating, parsing, or analyzing the OpenQASM source.
525 /// QSharpError: If there is an error compiling the program.
526 /// QSharpError: If there is an error evaluating the source code.
527 #[pyo3(signature=(input, output_fn, read_file, list_directory, resolve_path, fetch_github, **kwargs))]
528 #[allow(clippy::needless_pass_by_value)]
529 #[allow(clippy::too_many_arguments)]
530 fn import_qasm(
531 &mut self,
532 py: Python,
533 input: &str,
534 output_fn: Option<PyObject>,
535 read_file: Option<PyObject>,
536 list_directory: Option<PyObject>,
537 resolve_path: Option<PyObject>,
538 fetch_github: Option<PyObject>,
539 kwargs: Option<Bound<'_, PyDict>>,
540 ) -> PyResult<PyObject> {
541 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
542
543 let operation_name = get_operation_name(&kwargs)?;
544 let search_path = get_search_path(&kwargs)?;
545 let program_ty = get_program_type(&kwargs, || ProgramType::Operation)?;
546 let output_semantics = get_output_semantics(&kwargs, || OutputSemantics::OpenQasm)?;
547
548 let fs =
549 create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
550 let file_path = PathBuf::from_str(&search_path)
551 .expect("from_str is infallible")
552 .join("program.qasm");
553 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(input)));
554 let ProjectType::OpenQASM(sources) = project.project_type else {
555 return Err(QasmError::new_err(
556 "Expected OpenQASM project, but got a different type".to_string(),
557 ));
558 };
559
560 let config = CompilerConfig::new(
561 QubitSemantics::Qiskit,
562 output_semantics.into(),
563 program_ty.into(),
564 Some(operation_name.into()),
565 None,
566 );
567 let res = qsc::qasm::semantic::parse_sources(&sources);
568 let unit = compile_to_qsharp_ast_with_config(res, config);
569 let (sources, errors, package, _, _) = unit.into_tuple();
570
571 if !errors.is_empty() {
572 let errors = errors
573 .iter()
574 .map(|e| {
575 use qsc::compile::ErrorKind;
576 use qsc::interpret::Error;
577 let error = e.error().clone();
578 let kind = ErrorKind::OpenQasm(error);
579 let v = WithSource::from_map(&sources, kind);
580 Error::Compile(v)
581 })
582 .collect();
583 return Err(QSharpError::new_err(format_errors(errors)));
584 }
585 let mut receiver = OptionalCallbackReceiver {
586 callback: output_fn,
587 py,
588 };
589
590 match self
591 .interpreter
592 .eval_ast_fragments(&mut receiver, input, package)
593 {
594 Ok(value) => {
595 if let Some(make_callable) = &self.make_callable {
596 // Get any global callables from the evaluated input and add them to the environment. This will grab
597 // every callable that was defined in the input and by previous calls that added to the open package.
598 // This is safe because either the callable will be replaced with itself or a new callable with the
599 // same name will shadow the previous one, which is the expected behavior.
600 let new_items = self.interpreter.user_globals();
601 for (namespace, name, val) in new_items {
602 create_py_callable(py, make_callable, &namespace, &name, val)?;
603 }
604 }
605 value_to_pyobj(&self.interpreter, py, &value)
606 }
607 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
608 }
609 }
610
611 /// Sets the quantum seed for the interpreter.
612 #[pyo3(signature=(seed=None))]
613 fn set_quantum_seed(&mut self, seed: Option<u64>) {
614 self.interpreter.set_quantum_seed(seed);
615 }
616
617 /// Sets the classical seed for the interpreter.
618 #[pyo3(signature=(seed=None))]
619 fn set_classical_seed(&mut self, seed: Option<u64>) {
620 self.interpreter.set_classical_seed(seed);
621 }
622
623 /// Dumps the quantum state of the interpreter.
624 /// Returns a tuple of (amplitudes, num_qubits), where amplitudes is a dictionary from integer indices to
625 /// pairs of real and imaginary amplitudes.
626 fn dump_machine(&mut self) -> StateDumpData {
627 let (state, qubit_count) = self.interpreter.get_quantum_state();
628 StateDumpData(DisplayableState(state, qubit_count))
629 }
630
631 /// Dumps a circuit showing the current state of the simulator.
632 ///
633 /// This circuit will contain the gates that have been applied
634 /// in the simulator up to the current point.
635 ///
636 /// Requires the interpreter to be initialized with `trace_circuit=True`.
637 fn dump_circuit(&mut self, py: Python) -> PyResult<PyObject> {
638 if !self.trace_circuit {
639 return Err(QSharpError::new_err(
640 "to enable circuit dumping, the interpreter must be created with trace_circuit=True",
641 ));
642 }
643 Circuit(self.interpreter.get_circuit()).into_py_any(py)
644 }
645
646 #[allow(clippy::too_many_arguments)]
647 #[pyo3(signature=(entry_expr=None, callback=None, noise=None, qubit_loss=None, callable=None, args=None))]
648 fn run(
649 &mut self,
650 py: Python,
651 entry_expr: Option<&str>,
652 callback: Option<PyObject>,
653 noise: Option<(f64, f64, f64)>,
654 qubit_loss: Option<f64>,
655 callable: Option<GlobalCallable>,
656 args: Option<PyObject>,
657 ) -> PyResult<PyObject> {
658 let mut receiver = OptionalCallbackReceiver { callback, py };
659
660 let noise = match noise {
661 None => None,
662 Some((px, py, pz)) => match PauliNoise::from_probabilities(px, py, pz) {
663 Ok(noise_struct) => Some(noise_struct),
664 Err(error_message) => return Err(PyException::new_err(error_message)),
665 },
666 };
667
668 let result = match callable {
669 Some(callable) => {
670 let (input_ty, output_ty) = self
671 .interpreter
672 .global_callable_ty(&callable.0)
673 .ok_or(QSharpError::new_err("callable not found"))?;
674 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
675
676 self.interpreter.invoke_with_noise(
677 &mut receiver,
678 callable.0,
679 args,
680 noise,
681 qubit_loss,
682 )
683 }
684 _ => self
685 .interpreter
686 .run(&mut receiver, entry_expr, noise, qubit_loss),
687 };
688
689 match result {
690 Ok(value) => value_to_pyobj(&self.interpreter, py, &value),
691 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
692 }
693 }
694
695 #[pyo3(signature=(callable, args=None, callback=None))]
696 fn invoke(
697 &mut self,
698 py: Python,
699 callable: GlobalCallable,
700 args: Option<PyObject>,
701 callback: Option<PyObject>,
702 ) -> PyResult<PyObject> {
703 let mut receiver = OptionalCallbackReceiver { callback, py };
704 let (input_ty, output_ty) = self
705 .interpreter
706 .global_callable_ty(&callable.0)
707 .ok_or(QSharpError::new_err("callable not found"))?;
708
709 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
710
711 match self.interpreter.invoke(&mut receiver, callable.0, args) {
712 Ok(value) => value_to_pyobj(&self.interpreter, py, &value),
713 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
714 }
715 }
716
717 #[pyo3(signature=(entry_expr=None, callable=None, args=None))]
718 fn qir(
719 &mut self,
720 py: Python,
721 entry_expr: Option<&str>,
722 callable: Option<GlobalCallable>,
723 args: Option<PyObject>,
724 ) -> PyResult<String> {
725 if let Some(entry_expr) = entry_expr {
726 match self.interpreter.qirgen(entry_expr) {
727 Ok(qir) => Ok(qir),
728 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
729 }
730 } else {
731 let callable = callable.ok_or_else(|| {
732 QSharpError::new_err("either entry_expr or callable must be specified")
733 })?;
734 let (input_ty, output_ty) = self
735 .interpreter
736 .global_callable_ty(&callable.0)
737 .ok_or(QSharpError::new_err("callable not found"))?;
738
739 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
740 match self.interpreter.qirgen_from_callable(&callable.0, args) {
741 Ok(qir) => Ok(qir),
742 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
743 }
744 }
745 }
746
747 /// Synthesizes a circuit for a Q# program. Either an entry
748 /// expression or an operation must be provided.
749 ///
750 /// :param entry_expr: An entry expression.
751 ///
752 /// :param operation: The operation to synthesize. This can be a name of
753 /// an operation of a lambda expression. The operation must take only
754 /// qubits or arrays of qubits as parameters.
755 ///
756 /// :param callable: A callable to synthesize.
757 ///
758 /// :param args: The arguments to pass to the callable.
759 ///
760 /// :raises QSharpError: If there is an error synthesizing the circuit.
761 #[pyo3(signature=(entry_expr=None, operation=None, callable=None, args=None))]
762 fn circuit(
763 &mut self,
764 py: Python,
765 entry_expr: Option<String>,
766 operation: Option<String>,
767 callable: Option<GlobalCallable>,
768 args: Option<PyObject>,
769 ) -> PyResult<PyObject> {
770 let entrypoint = match (entry_expr, operation, callable) {
771 (Some(entry_expr), None, None) => CircuitEntryPoint::EntryExpr(entry_expr),
772 (None, Some(operation), None) => CircuitEntryPoint::Operation(operation),
773 (None, None, Some(callable)) => {
774 let (input_ty, output_ty) = self
775 .interpreter
776 .global_callable_ty(&callable.0)
777 .ok_or(QSharpError::new_err("callable not found"))?;
778 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
779 CircuitEntryPoint::Callable(callable.0, args)
780 }
781 _ => {
782 return Err(PyException::new_err(
783 "either entry_expr or operation must be specified",
784 ));
785 }
786 };
787
788 match self.interpreter.circuit(entrypoint, false) {
789 Ok(circuit) => Circuit(circuit).into_py_any(py),
790 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
791 }
792 }
793
794 #[pyo3(signature=(job_params, entry_expr=None, callable=None, args=None))]
795 fn estimate(
796 &mut self,
797 py: Python,
798 job_params: &str,
799 entry_expr: Option<&str>,
800 callable: Option<GlobalCallable>,
801 args: Option<PyObject>,
802 ) -> PyResult<String> {
803 let results = if let Some(entry_expr) = entry_expr {
804 estimate_expr(&mut self.interpreter, entry_expr, job_params)
805 } else {
806 let callable = callable.ok_or_else(|| {
807 QSharpError::new_err("either entry_expr or callable must be specified")
808 })?;
809 let (input_ty, output_ty) = self
810 .interpreter
811 .global_callable_ty(&callable.0)
812 .ok_or(QSharpError::new_err("callable not found"))?;
813 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
814 estimate_call(&mut self.interpreter, callable.0, args, job_params)
815 };
816 match results {
817 Ok(estimate) => Ok(estimate),
818 Err(errors) if matches!(errors[0], re::Error::Interpreter(_)) => {
819 Err(QSharpError::new_err(format_errors(
820 errors
821 .into_iter()
822 .map(|e| match e {
823 re::Error::Interpreter(e) => e,
824 re::Error::Estimation(_) => unreachable!(),
825 })
826 .collect::<Vec<_>>(),
827 )))
828 }
829 Err(errors) => Err(QSharpError::new_err(
830 errors
831 .into_iter()
832 .map(|e| match e {
833 re::Error::Estimation(e) => e.to_string(),
834 re::Error::Interpreter(_) => unreachable!(),
835 })
836 .collect::<Vec<_>>()
837 .join("\n"),
838 )),
839 }
840 }
841
842 #[pyo3(signature=(entry_expr=None, callable=None, args=None))]
843 fn logical_counts<'a>(
844 &mut self,
845 py: Python<'a>,
846 entry_expr: Option<&str>,
847 callable: Option<GlobalCallable>,
848 args: Option<PyObject>,
849 ) -> PyResult<Bound<'a, PyDict>> {
850 let results = if let Some(entry_expr) = entry_expr {
851 logical_counts_expr(&mut self.interpreter, entry_expr)
852 } else {
853 let callable = callable.ok_or_else(|| {
854 QSharpError::new_err("either entry_expr or callable must be specified")
855 })?;
856 let (input_ty, output_ty) = self
857 .interpreter
858 .global_callable_ty(&callable.0)
859 .ok_or(QSharpError::new_err("callable not found"))?;
860 let args = args_to_values(&self.interpreter, py, args, &input_ty, &output_ty)?;
861 logical_counts_call(&mut self.interpreter, callable.0, args)
862 };
863 match results {
864 Ok(counts) => {
865 let dict = PyDict::new(py);
866 dict.set_item("numQubits", counts.num_qubits)?;
867 dict.set_item("tCount", counts.t_count)?;
868 dict.set_item("rotationCount", counts.rotation_count)?;
869 dict.set_item("rotationDepth", counts.rotation_depth)?;
870 dict.set_item("cczCount", counts.ccz_count)?;
871 dict.set_item("ccixCount", counts.ccix_count)?;
872 dict.set_item("measurementCount", counts.measurement_count)?;
873 Ok(dict)
874 }
875 Err(errors) if matches!(errors[0], re::Error::Interpreter(_)) => {
876 Err(QSharpError::new_err(format_errors(
877 errors
878 .into_iter()
879 .map(|e| match e {
880 re::Error::Interpreter(e) => e,
881 re::Error::Estimation(_) => unreachable!(),
882 })
883 .collect::<Vec<_>>(),
884 )))
885 }
886 Err(errors) => Err(QSharpError::new_err(
887 errors
888 .into_iter()
889 .map(|e| match e {
890 re::Error::Estimation(e) => e.to_string(),
891 re::Error::Interpreter(_) => unreachable!(),
892 })
893 .collect::<Vec<_>>()
894 .join("\n"),
895 )),
896 }
897 }
898}
899
900fn args_to_values(
901 ctx: &interpret::Interpreter,
902 py: Python,
903 args: Option<PyObject>,
904 input_ty: &Ty,
905 output_ty: &Ty,
906) -> PyResult<Value> {
907 // If the types are not supported, we can't convert the arguments or return value.
908 // Check this before trying to convert the arguments, and return an error if the types are not supported.
909 if let Some(ty) = first_unsupported_interop_ty(ctx, input_ty) {
910 return Err(QSharpError::new_err(format!(
911 "unsupported input type: `{ty}`"
912 )));
913 }
914 if let Some(ty) = first_unsupported_interop_ty(ctx, output_ty) {
915 return Err(QSharpError::new_err(format!(
916 "unsupported output type: `{ty}`"
917 )));
918 }
919
920 // Convert the Python arguments to Q# values, treating None as an empty tuple aka `Unit`.
921 if matches!(&input_ty, Ty::Tuple(tup) if tup.is_empty()) {
922 // Special case for unit, where args should be None
923 if args.is_some() {
924 return Err(QSharpError::new_err("expected no arguments"));
925 }
926 Ok(Value::unit())
927 } else {
928 let Some(args) = args else {
929 return Err(QSharpError::new_err(format!(
930 "expected arguments of type `{input_ty}`"
931 )));
932 };
933 // This conversion will produce errors if the types don't match or can't be converted.
934 Ok(pyobj_to_value(ctx, py, &args, input_ty)?)
935 }
936}
937
938/// Finds any Q# type recursively that does not support interop with Python, meaning our code cannot convert it back and forth
939/// across the interop boundary.
940fn first_unsupported_interop_ty<'ctx, 'ty>(
941 ctx: &'ctx interpret::Interpreter,
942 ty: &'ty Ty,
943) -> Option<&'ctx Ty>
944where
945 'ty: 'ctx,
946{
947 match ty {
948 Ty::Prim(prim_ty) => match prim_ty {
949 Prim::Pauli
950 | Prim::BigInt
951 | Prim::Bool
952 | Prim::Double
953 | Prim::Int
954 | Prim::String
955 | Prim::Result => None,
956 Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
957 Some(ty)
958 }
959 },
960 Ty::Tuple(tup) => tup
961 .iter()
962 .find(|t| first_unsupported_interop_ty(ctx, t).is_some()),
963 Ty::Array(ty) => first_unsupported_interop_ty(ctx, ty),
964 Ty::Udt(_, res) => {
965 let qsc::hir::Res::Item(item_id) = res else {
966 panic!("Udt should be an item");
967 };
968 let (udt, _) = ctx.udt_ty_from_item_id(item_id);
969
970 let Ok(fields) = collect_udt_fields(udt) else {
971 return Some(ty);
972 };
973
974 for field in fields {
975 if let Some(ty) = first_unsupported_interop_ty(ctx, field.1) {
976 return Some(ty);
977 }
978 }
979
980 None
981 }
982 Ty::Arrow(..) | Ty::Infer(..) | Ty::Param { .. } | Ty::Err => Some(ty),
983 }
984}
985
986#[pyfunction]
987pub fn physical_estimates(logical_resources: &str, job_params: &str) -> PyResult<String> {
988 match re::estimate_physical_resources_from_json(logical_resources, job_params) {
989 Ok(estimates) => Ok(estimates),
990 Err(error) => Err(QSharpError::new_err(error.to_string())),
991 }
992}
993
994create_exception!(
995 module,
996 QSharpError,
997 pyo3::exceptions::PyException,
998 "An error returned from the Q# interpreter."
999);
1000
1001create_exception!(
1002 module,
1003 QasmError,
1004 pyo3::exceptions::PyException,
1005 "An error returned from the OpenQASM parser."
1006);
1007
1008pub(crate) fn format_errors(errors: Vec<interpret::Error>) -> String {
1009 errors
1010 .into_iter()
1011 .map(|e| format_error(&e))
1012 .collect::<Vec<_>>()
1013 .join("\n")
1014}
1015
1016pub(crate) fn format_error(e: &interpret::Error) -> String {
1017 let mut message = String::new();
1018 if let Some(stack_trace) = e.stack_trace() {
1019 write!(message, "{stack_trace}").unwrap();
1020 }
1021 let additional_help = python_help(e);
1022 let report = Report::new(e.clone());
1023 write!(message, "{report:?}")
1024 .unwrap_or_else(|err| panic!("writing error failed: {err} error was: {e:?}"));
1025 if let Some(additional_help) = additional_help {
1026 writeln!(message, "{additional_help}").unwrap();
1027 }
1028 message
1029}
1030
1031/// Additional help text for an error specific to the Python module
1032fn python_help(error: &interpret::Error) -> Option<String> {
1033 if matches!(error, interpret::Error::UnsupportedRuntimeCapabilities) {
1034 Some("Unsupported target profile. Initialize Q# by running `qsharp.init(target_profile=qsharp.TargetProfile.Base)` before performing code generation.".into())
1035 } else {
1036 None
1037 }
1038}
1039
1040#[pyclass]
1041pub(crate) struct Output(DisplayableOutput);
1042
1043#[pymethods]
1044/// An output returned from the Q# interpreter.
1045/// Outputs can be a state dumps or messages. These are normally printed to the console.
1046impl Output {
1047 fn __repr__(&self) -> String {
1048 match &self.0 {
1049 DisplayableOutput::State(state) => state.to_plain(),
1050 DisplayableOutput::Matrix(matrix) => matrix.to_plain(),
1051 DisplayableOutput::Message(msg) => msg.clone(),
1052 }
1053 }
1054
1055 fn __str__(&self) -> String {
1056 self.__repr__()
1057 }
1058
1059 fn _repr_markdown_(&self) -> Option<String> {
1060 match &self.0 {
1061 DisplayableOutput::State(state) => {
1062 let latex = if let Some(latex) = state.to_latex() {
1063 format!("\n\n{latex}")
1064 } else {
1065 String::default()
1066 };
1067 Some(format!("{}{latex}", state.to_html()))
1068 }
1069 DisplayableOutput::Message(_) => None,
1070 DisplayableOutput::Matrix(matrix) => Some(matrix.to_latex()),
1071 }
1072 }
1073
1074 fn state_dump(&self) -> Option<StateDumpData> {
1075 match &self.0 {
1076 DisplayableOutput::State(state) => Some(StateDumpData(state.clone())),
1077 DisplayableOutput::Matrix(_) | DisplayableOutput::Message(_) => None,
1078 }
1079 }
1080
1081 fn is_state_dump(&self) -> bool {
1082 matches!(&self.0, DisplayableOutput::State(_))
1083 }
1084
1085 fn is_matrix(&self) -> bool {
1086 matches!(&self.0, DisplayableOutput::Matrix(_))
1087 }
1088
1089 fn is_message(&self) -> bool {
1090 matches!(&self.0, DisplayableOutput::Message(_))
1091 }
1092}
1093
1094#[pyclass]
1095/// Captured simulation state dump.
1096pub(crate) struct StateDumpData(pub(crate) DisplayableState);
1097
1098#[pymethods]
1099impl StateDumpData {
1100 fn get_dict<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyDict>> {
1101 let dict = rustc_hash::FxHashMap::from_iter(self.0.0.clone());
1102 dict.into_pyobject(py)
1103 }
1104
1105 #[getter]
1106 fn get_qubit_count(&self) -> usize {
1107 self.0.1
1108 }
1109
1110 fn __len__(&self) -> usize {
1111 self.0.0.len()
1112 }
1113
1114 fn __repr__(&self) -> String {
1115 self.0.to_plain()
1116 }
1117
1118 fn __str__(&self) -> String {
1119 self.__repr__()
1120 }
1121
1122 fn _repr_markdown_(&self) -> String {
1123 let latex = if let Some(latex) = self.0.to_latex() {
1124 format!("\n\n{latex}")
1125 } else {
1126 String::default()
1127 };
1128 format!("{}{latex}", self.0.to_html())
1129 }
1130
1131 fn _repr_latex_(&self) -> Option<String> {
1132 self.0.to_latex()
1133 }
1134}
1135
1136#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1137#[pyclass(eq, eq_int, ord)]
1138/// A Q# measurement result.
1139pub(crate) enum Result {
1140 Zero,
1141 One,
1142 Loss,
1143}
1144
1145impl From<Result> for qsc::interpret::Result {
1146 fn from(value: Result) -> Self {
1147 match value {
1148 Result::Loss => qsc::interpret::Result::Loss,
1149 Result::One | Result::Zero => qsc::interpret::Result::Val(value == Result::One),
1150 }
1151 }
1152}
1153
1154#[pymethods]
1155impl Result {
1156 #[allow(clippy::trivially_copy_pass_by_ref)]
1157 fn __repr__(&self) -> String {
1158 match self {
1159 Result::Zero => "Zero".to_owned(),
1160 Result::One => "One".to_owned(),
1161 Result::Loss => "Loss".to_owned(),
1162 }
1163 }
1164
1165 #[allow(clippy::trivially_copy_pass_by_ref)]
1166 fn __str__(&self) -> String {
1167 self.__repr__()
1168 }
1169
1170 #[allow(clippy::trivially_copy_pass_by_ref)]
1171 fn __hash__(&self) -> u32 {
1172 match self {
1173 Result::Zero => 0,
1174 Result::One => 1,
1175 Result::Loss => 2,
1176 }
1177 }
1178}
1179
1180#[derive(Clone, Copy, PartialEq)]
1181#[pyclass(eq, eq_int)]
1182/// A Q# Pauli operator.
1183pub(crate) enum Pauli {
1184 I,
1185 X,
1186 Y,
1187 Z,
1188}
1189
1190impl From<Pauli> for fir::Pauli {
1191 fn from(value: Pauli) -> Self {
1192 match value {
1193 Pauli::I => fir::Pauli::I,
1194 Pauli::X => fir::Pauli::X,
1195 Pauli::Y => fir::Pauli::Y,
1196 Pauli::Z => fir::Pauli::Z,
1197 }
1198 }
1199}
1200
1201pub(crate) struct OptionalCallbackReceiver<'a> {
1202 pub(crate) callback: Option<PyObject>,
1203 pub(crate) py: Python<'a>,
1204}
1205
1206impl Receiver for OptionalCallbackReceiver<'_> {
1207 fn state(
1208 &mut self,
1209 state: Vec<(BigUint, Complex64)>,
1210 qubit_count: usize,
1211 ) -> core::result::Result<(), Error> {
1212 if let Some(callback) = &self.callback {
1213 let out = DisplayableOutput::State(DisplayableState(state, qubit_count));
1214 callback
1215 .call1(
1216 self.py,
1217 PyTuple::new(
1218 self.py,
1219 &[Py::new(self.py, Output(out)).expect("should be able to create output")],
1220 )
1221 .map_err(|_| Error)?,
1222 )
1223 .map_err(|_| Error)?;
1224 }
1225 Ok(())
1226 }
1227
1228 fn matrix(&mut self, matrix: Vec<Vec<Complex64>>) -> std::result::Result<(), Error> {
1229 if let Some(callback) = &self.callback {
1230 let out = DisplayableOutput::Matrix(DisplayableMatrix(matrix));
1231 callback
1232 .call1(
1233 self.py,
1234 PyTuple::new(
1235 self.py,
1236 &[Py::new(self.py, Output(out)).expect("should be able to create output")],
1237 )
1238 .map_err(|_| Error)?,
1239 )
1240 .map_err(|_| Error)?;
1241 }
1242 Ok(())
1243 }
1244
1245 fn message(&mut self, msg: &str) -> core::result::Result<(), Error> {
1246 if let Some(callback) = &self.callback {
1247 let out = DisplayableOutput::Message(msg.to_owned());
1248 callback
1249 .call1(
1250 self.py,
1251 PyTuple::new(
1252 self.py,
1253 &[Py::new(self.py, Output(out)).expect("should be able to create output")],
1254 )
1255 .map_err(|_| Error)?,
1256 )
1257 .map_err(|_| Error)?;
1258 }
1259 Ok(())
1260 }
1261}
1262
1263#[pyclass]
1264pub(crate) struct Circuit(pub qsc::circuit::Circuit);
1265
1266#[pymethods]
1267impl Circuit {
1268 fn __repr__(&self) -> String {
1269 self.0.to_string()
1270 }
1271
1272 fn __str__(&self) -> String {
1273 self.__repr__()
1274 }
1275
1276 fn json(&self, _py: Python) -> PyResult<String> {
1277 serde_json::to_string(&self.0).map_err(|e| PyException::new_err(e.to_string()))
1278 }
1279}
1280
1281trait IntoPyErr {
1282 fn into_py_err(self) -> PyErr;
1283}
1284
1285impl IntoPyErr for Report {
1286 fn into_py_err(self) -> PyErr {
1287 PyException::new_err(format!("{self:?}"))
1288 }
1289}
1290
1291impl<E> IntoPyErr for Vec<E>
1292where
1293 E: Diagnostic + Send + Sync + 'static,
1294{
1295 fn into_py_err(self) -> PyErr {
1296 let mut message = String::new();
1297 for diag in self {
1298 let report = Report::new(diag);
1299 writeln!(message, "{report:?}").expect("string should be writable");
1300 }
1301 PyException::new_err(message)
1302 }
1303}
1304
1305#[pyclass(unsendable)]
1306#[derive(Clone)]
1307struct GlobalCallable(Value);
1308
1309impl From<Value> for GlobalCallable {
1310 fn from(val: Value) -> Self {
1311 match val {
1312 val @ Value::Global(..) => GlobalCallable(val),
1313 _ => panic!("expected global callable"),
1314 }
1315 }
1316}
1317
1318impl From<GlobalCallable> for Value {
1319 fn from(val: GlobalCallable) -> Self {
1320 val.0
1321 }
1322}
1323
1324/// Create a Python callable from a Q# callable and adds it to the given environment.
1325fn create_py_callable(
1326 py: Python,
1327 make_callable: &PyObject,
1328 namespace: &[Rc<str>],
1329 name: &str,
1330 val: Value,
1331) -> PyResult<()> {
1332 if namespace.is_empty() && name == "<lambda>" {
1333 // We don't want to bind auto-generated lambda callables.
1334 return Ok(());
1335 }
1336
1337 let args = (
1338 Py::new(py, GlobalCallable::from(val)).expect("should be able to create callable"), // callable id
1339 PyList::new(py, namespace.iter().map(ToString::to_string))?, // namespace as string array
1340 PyString::new(py, name), // name of callable
1341 );
1342
1343 // Call into the Python layer to create the function wrapping the callable invocation.
1344 make_callable.call1(py, args)?;
1345
1346 Ok(())
1347}
1348
1349/// Create a Python class from a Q# type and adds it to the given environment.
1350fn create_py_class(
1351 ctx: &interpret::Interpreter,
1352 py: Python,
1353 make_class: &PyObject,
1354 namespace: &[Rc<str>],
1355 name: &str,
1356 ty: &Ty,
1357) -> PyResult<()> {
1358 let Some(type_ir) = type_ir_from_qsharp_ty(ctx, ty) else {
1359 // If the UDT can't be expressed in Python, we don't want to raise
1360 // an error, instead we just don't define that type in `qsharp.code.*`.
1361 return Ok(());
1362 };
1363
1364 let args = (
1365 Py::new(py, type_ir).expect("should be able to create callable"), // callable id
1366 PyList::new(py, namespace.iter().map(ToString::to_string))?, // namespace as string array
1367 PyString::new(py, name), // name of callable
1368 );
1369
1370 // Call into the Python layer to create the function wrapping the callable invocation.
1371 make_class.call1(py, args)?;
1372
1373 Ok(())
1374}
1375