microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
jan

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interop.rs

821lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![allow(
5 clippy::doc_markdown,
6 reason = "docstrings in this module conform to the python docstring format."
7)]
8
9use std::fmt::Write;
10use std::path::PathBuf;
11use std::str::FromStr;
12use std::sync::Arc;
13
14use pyo3::IntoPyObjectExt;
15use pyo3::exceptions::PyException;
16use pyo3::prelude::*;
17use pyo3::types::{PyDict, PyList};
18use qsc::hir::PackageId;
19use qsc::interpret::output::Receiver;
20use qsc::interpret::{CircuitEntryPoint, Interpreter, into_errors};
21use qsc::project::ProjectType;
22use qsc::qasm::compiler::compile_to_qsharp_ast_with_config;
23use qsc::qasm::semantic::QasmSemanticParseResult;
24use qsc::qasm::{OperationSignature, QubitSemantics};
25use qsc::target::Profile;
26use qsc::{Backend, PackageType, PauliNoise, SparseSim};
27use qsc::{
28 LanguageFeatures, SourceMap, ast::Package, error::WithSource, interpret, project::FileSystem,
29};
30
31use crate::fs::file_system;
32use crate::interpreter::data_interop::value_to_pyobj;
33use crate::interpreter::{
34 CircuitConfig, OptionalCallbackReceiver, OutputSemantics, ProgramType, QSharpError, QasmError,
35 TargetProfile, format_error, format_errors,
36};
37
38use resource_estimator as re;
39
40/// Runs the given OpenQASM program for the given number of shots.
41/// Each shot uses an independent instance of the simulator.
42///
43/// Note:
44/// This call while exported is not intended to be used directly by the user.
45/// It is intended to be used by the Python wrapper which will handle the
46/// callbacks and other Python specific details.
47///
48/// Args:
49/// source (str): The OpenQASM source code to execute.
50/// output_fn (Callable[[Output], None]): The function to handle the output of the execution.
51/// noise: The noise to use in simulation.
52/// read_file (Callable[[str], Tuple[str, str]]): The function to read a file and return its contents.
53/// list_directory (Callable[[str], List[Dict[str, str]]]): The function to list the contents of a directory.
54/// resolve_path (Callable[[str, str], str]): The function to resolve a path given a base path and a relative path.
55/// fetch_github (Callable[[str, str, str, str], str]): The function to fetch a file from GitHub.
56/// **kwargs: Additional keyword arguments to pass to the execution.
57/// - target_profile (TargetProfile): The target profile to use for execution.
58/// - name (str): The name of the circuit. This is used as the entry point for the program. Defaults to 'program'.
59/// - search_path (str): The optional search path for resolving imports.
60/// - output_semantics (OutputSemantics, optional): The output semantics for the compilation.
61/// - shots (int): The number of shots to run the program for. Defaults to 1.
62/// - seed (int): The seed to use for the random number generator.
63///
64/// Returns:
65/// Any: The result of the execution.
66///
67/// Raises:
68/// QasmError: If there is an error generating, parsing, or analyzing the OpenQASM source.
69/// QSharpError: If there is an error interpreting the input.
70#[pyfunction]
71#[allow(clippy::too_many_arguments)]
72#[pyo3(
73 signature = (source, callback=None, noise=None, qubit_loss=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None, **kwargs)
74)]
75pub(crate) fn run_qasm_program(
76 py: Python,
77 source: &str,
78 callback: Option<PyObject>,
79 noise: Option<(f64, f64, f64)>,
80 qubit_loss: Option<f64>,
81 read_file: Option<PyObject>,
82 list_directory: Option<PyObject>,
83 resolve_path: Option<PyObject>,
84 fetch_github: Option<PyObject>,
85 kwargs: Option<Bound<'_, PyDict>>,
86) -> PyResult<PyObject> {
87 let mut receiver = OptionalCallbackReceiver { callback, py };
88
89 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
90
91 let target = get_target_profile(&kwargs)?;
92 let operation_name = get_operation_name(&kwargs)?;
93 let output_semantics = get_output_semantics(&kwargs, || OutputSemantics::OpenQasm)?;
94 let seed = get_seed(&kwargs);
95 let shots = get_shots(&kwargs)?;
96 let search_path = get_search_path(&kwargs)?;
97
98 let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
99 let file_path = PathBuf::from_str(&search_path)
100 .expect("from_str is infallible")
101 .join("program.qasm");
102 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(source)));
103 let ProjectType::OpenQASM(sources) = project.project_type else {
104 return Err(QasmError::new_err(
105 "Expected OpenQASM project, but got a different type".to_string(),
106 ));
107 };
108 let res = qsc::qasm::semantic::parse_sources(&sources);
109 let (package, source_map, signature) = compile_qasm_enriching_errors(
110 res,
111 &operation_name,
112 ProgramType::File,
113 output_semantics,
114 false,
115 )?;
116
117 let package_type = PackageType::Exe;
118 let language_features = LanguageFeatures::default();
119 let mut interpreter =
120 create_interpreter_from_ast(package, source_map, target, language_features, package_type)
121 .map_err(|errors| QSharpError::new_err(format_errors(errors)))?;
122
123 let entry_expr = signature.create_entry_expr_from_params(String::new());
124 interpreter
125 .set_entry_expr(&entry_expr)
126 .map_err(|errors| map_entry_compilation_errors(errors, &signature))?;
127
128 let noise = match noise {
129 None => None,
130 Some((px, py, pz)) => match PauliNoise::from_probabilities(px, py, pz) {
131 Ok(noise_struct) => Some(noise_struct),
132 Err(error_message) => return Err(PyException::new_err(error_message)),
133 },
134 };
135 let loss = qubit_loss.unwrap_or(0.0);
136 let result = run_ast(&mut interpreter, &mut receiver, shots, seed, noise, loss);
137 match result {
138 Ok(result) => {
139 let list: Result<Vec<_>, _> = result
140 .iter()
141 .map(|v| value_to_pyobj(&interpreter, py, v))
142 .collect();
143 Ok(PyList::new(py, list?)?.into())
144 }
145 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
146 }
147}
148
149pub(crate) fn run_ast(
150 interpreter: &mut Interpreter,
151 receiver: &mut impl Receiver,
152 shots: usize,
153 seed: Option<u64>,
154 noise: Option<PauliNoise>,
155 loss: f64,
156) -> Result<Vec<qsc::interpret::Value>, Vec<interpret::Error>> {
157 let mut results = Vec::with_capacity(shots);
158 for i in 0..shots {
159 let mut sim = if let Some(noise) = noise {
160 SparseSim::new_with_noise(&noise)
161 } else {
162 SparseSim::new()
163 };
164 sim.set_loss(loss);
165 // If seed is provided, we want to use a different seed for each shot
166 // so that the results are different for each shot, but still deterministic
167 sim.set_seed(seed.map(|s| s + i as u64));
168 let result = interpreter.run_with_sim(&mut sim, receiver, None)?;
169 results.push(result);
170 }
171
172 Ok(results)
173}
174
175/// Estimates the resource requirements for executing OpenQASM source code.
176///
177/// Note:
178/// This call while exported is not intended to be used directly by the user.
179/// It is intended to be used by the Python wrapper which will handle the
180/// callbacks and other Python specific details.
181///
182/// Args:
183/// source (str): The OpenQASM source code to estimate the resource requirements for.
184/// job_params (str): The parameters for the job.
185/// read_file (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path.
186/// list_directory (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory.
187/// resolve_path (Callable[[str, str], str]): A callable that resolves a file path given a base path and a relative path.
188/// fetch_github (Callable[[str, str, str, str], str]): A callable that fetches a file from GitHub.
189/// **kwargs: Additional keyword arguments to pass to the execution.
190/// - name (str): The name of the circuit. This is used as the entry point for the program. Defaults to 'program'.
191/// - search_path (str): The optional search path for resolving imports.
192/// Returns:
193/// str: The estimated resource requirements for executing the OpenQASM source code.
194#[pyfunction]
195#[allow(clippy::too_many_arguments)]
196#[pyo3(
197 signature = (source, job_params, read_file, list_directory, resolve_path, fetch_github, **kwargs)
198)]
199pub(crate) fn resource_estimate_qasm_program(
200 py: Python,
201 source: &str,
202 job_params: &str,
203 read_file: Option<PyObject>,
204 list_directory: Option<PyObject>,
205 resolve_path: Option<PyObject>,
206 fetch_github: Option<PyObject>,
207 kwargs: Option<Bound<'_, PyDict>>,
208) -> PyResult<String> {
209 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
210
211 let operation_name = get_operation_name(&kwargs)?;
212 let search_path = get_search_path(&kwargs)?;
213
214 let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
215 let file_path = PathBuf::from_str(&search_path)
216 .expect("from_str is infallible")
217 .join("program.qasm");
218 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(source)));
219 let ProjectType::OpenQASM(sources) = project.project_type else {
220 return Err(QasmError::new_err(
221 "Expected OpenQASM project, but got a different type".to_string(),
222 ));
223 };
224 let res = qsc::qasm::semantic::parse_sources(&sources);
225
226 let program_type = ProgramType::File;
227 let output_semantics = OutputSemantics::ResourceEstimation;
228 let (package, source_map, _) =
229 compile_qasm_enriching_errors(res, &operation_name, program_type, output_semantics, false)?;
230
231 match crate::interop::estimate_qasm(package, source_map, job_params) {
232 Ok(estimate) => Ok(estimate),
233 Err(errors) if matches!(errors[0], re::Error::Interpreter(_)) => {
234 Err(QSharpError::new_err(format_errors(
235 errors
236 .into_iter()
237 .map(|e| match e {
238 re::Error::Interpreter(e) => e,
239 re::Error::Estimation(_) => unreachable!(),
240 })
241 .collect::<Vec<_>>(),
242 )))
243 }
244 Err(errors) => Err(QSharpError::new_err(
245 errors
246 .into_iter()
247 .map(|e| match e {
248 re::Error::Estimation(e) => e.to_string(),
249 re::Error::Interpreter(_) => unreachable!(),
250 })
251 .collect::<Vec<_>>()
252 .join("\n"),
253 )),
254 }
255}
256
257/// Compiles the OpenQASM source code into a program that can be submitted to a
258/// target as QIR (Quantum Intermediate Representation).
259///
260/// Note:
261/// This call while exported is not intended to be used directly by the user.
262/// It is intended to be used by the Python wrapper which will handle the
263/// callbacks and other Python specific details.
264///
265/// Args:
266/// source (str): The OpenQASM source code to estimate the resource requirements for.
267/// read_file (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path.
268/// list_directory (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory.
269/// resolve_path (Callable[[str, str], str]): A callable that resolves a file path given a base path and a relative path.
270/// fetch_github (Callable[[str, str, str, str], str]): A callable that fetches a file from GitHub.
271/// **kwargs: Additional keyword arguments to pass to the compilation when source program is provided.
272/// - name (str): The name of the circuit. This is used as the entry point for the program.
273/// - target_profile (TargetProfile): The target profile to use for code generation.
274/// - search_path (Optional[str]): The optional search path for resolving file references.
275/// - output_semantics (OutputSemantics, optional): The output semantics for the compilation.
276///
277/// Returns:
278/// str: The converted QIR code as a string.
279///
280/// Raises:
281/// QasmError: If there is an error generating, parsing, or analyzing the OpenQASM source.
282/// QSharpError: If there is an error compiling the program.
283#[pyfunction]
284#[allow(clippy::too_many_arguments)]
285#[pyo3(
286 signature = (source, read_file, list_directory, resolve_path, fetch_github, **kwargs)
287)]
288pub(crate) fn compile_qasm_program_to_qir(
289 py: Python,
290 source: &str,
291 read_file: Option<PyObject>,
292 list_directory: Option<PyObject>,
293 resolve_path: Option<PyObject>,
294 fetch_github: Option<PyObject>,
295 kwargs: Option<Bound<'_, PyDict>>,
296) -> PyResult<String> {
297 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
298
299 let target = get_target_profile(&kwargs)?;
300 let operation_name = get_operation_name(&kwargs)?;
301 let search_path = get_search_path(&kwargs)?;
302
303 let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
304 let file_path = PathBuf::from_str(&search_path)
305 .expect("from_str is infallible")
306 .join("program.qasm");
307 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(source)));
308 let ProjectType::OpenQASM(sources) = project.project_type else {
309 return Err(QasmError::new_err(
310 "Expected OpenQASM project, but got a different type".to_string(),
311 ));
312 };
313 let res = qsc::qasm::semantic::parse_sources(&sources);
314
315 let program_ty = ProgramType::File;
316 let output_semantics = get_output_semantics(&kwargs, || OutputSemantics::Qiskit)?;
317 let (package, source_map, signature) =
318 compile_qasm_enriching_errors(res, &operation_name, program_ty, output_semantics, false)?;
319
320 let package_type = PackageType::Lib;
321 let language_features = LanguageFeatures::default();
322 let mut interpreter =
323 create_interpreter_from_ast(package, source_map, target, language_features, package_type)
324 .map_err(|errors| QSharpError::new_err(format_errors(errors)))?;
325 let entry_expr = signature.create_entry_expr_from_params(String::new());
326
327 generate_qir_from_ast(entry_expr, &mut interpreter)
328}
329
330pub(crate) fn compile_qasm_enriching_errors<S: AsRef<str>>(
331 semantic_parse_result: QasmSemanticParseResult,
332 operation_name: S,
333 program_ty: ProgramType,
334 output_semantics: OutputSemantics,
335 allow_input_params: bool,
336) -> PyResult<(Package, SourceMap, OperationSignature)> {
337 let config = qsc::qasm::CompilerConfig::new(
338 QubitSemantics::Qiskit,
339 output_semantics.into(),
340 program_ty.into(),
341 Some(operation_name.as_ref().into()),
342 None,
343 );
344
345 let unit = compile_to_qsharp_ast_with_config(semantic_parse_result, config);
346
347 let (source_map, errors, package, sig, _) = unit.into_tuple();
348 if !errors.is_empty() {
349 return Err(QasmError::new_err(format_qasm_errors(errors)));
350 }
351
352 let Some(signature) = sig else {
353 return Err(QasmError::new_err(
354 "signature should have had value. This is a bug",
355 ));
356 };
357
358 if !signature.input.is_empty() && !allow_input_params {
359 // no entry expression is provided, but the signature has input parameters.
360 let mut message = String::new();
361 message += "Circuit has unbound input parameters\n";
362 write!(message, " help: Parameters: {}", signature.input_params())
363 .expect("writing to string should succeed");
364
365 return Err(QSharpError::new_err(message));
366 }
367
368 Ok((package, source_map, signature))
369}
370
371fn generate_qir_from_ast<S: AsRef<str>>(
372 entry_expr: S,
373 interpreter: &mut Interpreter,
374) -> PyResult<String> {
375 interpreter
376 .qirgen(entry_expr.as_ref())
377 .map_err(map_qirgen_errors)
378}
379
380/// This call while exported is not intended to be used directly by the user.
381/// It is intended to be used by the Python wrapper which will handle the
382/// callbacks and other Python specific details.
383#[pyfunction]
384#[allow(clippy::too_many_arguments)]
385#[pyo3(
386 signature = (source, read_file, list_directory, resolve_path, fetch_github, **kwargs)
387)]
388pub(crate) fn compile_qasm_to_qsharp(
389 py: Python,
390 source: &str,
391 read_file: Option<PyObject>,
392 list_directory: Option<PyObject>,
393 resolve_path: Option<PyObject>,
394 fetch_github: Option<PyObject>,
395 kwargs: Option<Bound<'_, PyDict>>,
396) -> PyResult<String> {
397 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
398
399 let operation_name = get_operation_name(&kwargs)?;
400 let search_path = get_search_path(&kwargs)?;
401
402 let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
403 let file_path = PathBuf::from_str(&search_path)
404 .expect("from_str is infallible")
405 .join("program.qasm");
406 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(source)));
407 let ProjectType::OpenQASM(sources) = project.project_type else {
408 return Err(QasmError::new_err(
409 "Expected OpenQASM project, but got a different type".to_string(),
410 ));
411 };
412 let res = qsc::qasm::semantic::parse_sources(&sources);
413
414 let program_ty = get_program_type(&kwargs, || ProgramType::File)?;
415 let output_semantics = get_output_semantics(&kwargs, || OutputSemantics::Qiskit)?;
416 let (package, _, _) =
417 compile_qasm_enriching_errors(res, &operation_name, program_ty, output_semantics, true)?;
418
419 let qsharp = qsc::codegen::qsharp::write_package_string(&package);
420 Ok(qsharp)
421}
422
423/// Enriches the compilation errors to provide more helpful messages
424/// as we know that we are compiling the entry expression.
425pub(crate) fn map_entry_compilation_errors(
426 errors: Vec<interpret::Error>,
427 sig: &OperationSignature,
428) -> PyErr {
429 let mut semantic = vec![];
430 for error in errors {
431 match &error {
432 interpret::Error::Compile(_) => {
433 // The entry expression is invalid. This is likely due to a type mismatch
434 // or missing parameter(s). We should provide a more helpful error message.
435 let mut message = format_error(&error);
436 writeln!(message).expect("write should succeed");
437 writeln!(message, "failed to compile entry point.").expect("write should succeed");
438 writeln!(
439 message,
440 " help: check that the parameter types match the supplied parameters"
441 )
442 .expect("write should succeed");
443
444 write!(message, " help: Parameters: {}", sig.input_params())
445 .expect("writing to string should succeed");
446
447 semantic.push(message);
448 }
449 _ => {
450 semantic.push(format_error(&error));
451 }
452 }
453 }
454 let message = semantic.into_iter().collect::<String>();
455 QSharpError::new_err(message)
456}
457
458/// Adds additional information to interpreter errors to make them more user-friendly.
459/// when QIR generation fails.
460fn map_qirgen_errors(errors: Vec<interpret::Error>) -> PyErr {
461 let mut semantic = vec![];
462 for error in errors {
463 match &error {
464 interpret::Error::Compile(_) => {
465 // We've gotten this far with no compilation errors, so if we get one here
466 // then the entry expression is invalid.
467 let mut message = format_error(&error);
468 writeln!(message).expect("write should succeed");
469 writeln!(message, "failed to compile entry point.").expect("write should succeed");
470 writeln!(
471 message,
472 " help: check that the parameter types match the entry point signature"
473 )
474 .expect("write should succeed");
475
476 semantic.push(message);
477 }
478 interpret::Error::PartialEvaluation(pe) => match pe.error() {
479 qsc::partial_eval::Error::OutputResultLiteral(..) => {
480 let mut message = format_error(&error);
481 writeln!(message).expect("write should succeed");
482 writeln!(
483 message,
484 " help: ensure all output registers have been measured into."
485 )
486 .expect("write should succeed");
487
488 semantic.push(message);
489 }
490 _ => {
491 semantic.push(format_error(&error));
492 }
493 },
494 _ => {
495 semantic.push(format_error(&error));
496 }
497 }
498 }
499 let message = semantic.into_iter().collect::<String>();
500 QSharpError::new_err(message)
501}
502
503/// Estimates the resources required to run a QASM program
504/// represented by the provided AST. The source map is used for
505/// error reporting during compilation or runtime.
506fn estimate_qasm(
507 ast_package: Package,
508 source_map: SourceMap,
509 params: &str,
510) -> Result<String, Vec<resource_estimator::Error>> {
511 let mut interpreter = create_interpreter_from_ast(
512 ast_package,
513 source_map,
514 Profile::Unrestricted,
515 LanguageFeatures::default(),
516 PackageType::Exe,
517 )
518 .map_err(into_estimation_errors)?;
519
520 resource_estimator::estimate_entry(&mut interpreter, params)
521}
522
523/// Synthesizes a circuit for an `OpenQASM` program.
524///
525/// Note:
526/// This call while exported is not intended to be used directly by the user.
527/// It is intended to be used by the Python wrapper which will handle the
528/// callbacks and other Python specific details.
529///
530/// Args:
531/// source (str): An `OpenQASM` program. Alternatively, a callable can be provided,
532/// which must be an already imported global callable.
533/// `read_file` (Callable[[str], Tuple[str, str]]): A callable that reads a file and returns its content and path.
534/// `list_directory` (Callable[[str], List[Dict[str, str]]]): A callable that lists the contents of a directory.
535/// `resolve_path` (Callable[[str, str], str]): A callable that resolves a file path given a base path and a relative path.
536/// `fetch_github` (Callable[[str, str, str, str], str]): A callable that fetches a file from GitHub.
537/// **kwargs: Additional keyword arguments to pass to the execution.
538/// - name (str): The name of the program. This is used as the entry point for the program.
539/// - `search_path` (Optional[str]): The optional search path for resolving file references.
540/// Returns:
541/// Circuit: The synthesized circuit.
542///
543/// Raises:
544/// `QasmError`: If there is an error generating, parsing, or analyzing the `OpenQASM` source.
545/// `QSharpError`: If there is an error evaluating the program.
546/// `QSharpError`: If there is an error synthesizing the circuit.
547#[pyfunction]
548#[allow(clippy::too_many_arguments)]
549#[pyo3(
550 signature = (source, config, read_file, list_directory, resolve_path, fetch_github, **kwargs)
551)]
552pub(crate) fn circuit_qasm_program(
553 py: Python,
554 source: &str,
555 config: &CircuitConfig,
556 read_file: Option<PyObject>,
557 list_directory: Option<PyObject>,
558 resolve_path: Option<PyObject>,
559 fetch_github: Option<PyObject>,
560 kwargs: Option<Bound<'_, PyDict>>,
561) -> PyResult<PyObject> {
562 let kwargs = kwargs.unwrap_or_else(|| PyDict::new(py));
563
564 let operation_name = get_operation_name(&kwargs)?;
565 let search_path = get_search_path(&kwargs)?;
566
567 let fs = create_filesystem_from_py(py, read_file, list_directory, resolve_path, fetch_github);
568 let file_path = PathBuf::from_str(&search_path)
569 .expect("from_str is infallible")
570 .join("program.qasm");
571 let project = fs.load_openqasm_project(&file_path, Some(Arc::<str>::from(source)));
572 let ProjectType::OpenQASM(sources) = project.project_type else {
573 return Err(QasmError::new_err(
574 "Expected OpenQASM project, but got a different type".to_string(),
575 ));
576 };
577 let res = qsc::qasm::semantic::parse_sources(&sources);
578
579 let (package, source_map, signature) = compile_qasm_enriching_errors(
580 res,
581 &operation_name,
582 ProgramType::File,
583 OutputSemantics::ResourceEstimation,
584 false,
585 )?;
586
587 let package_type = PackageType::Exe;
588 let language_features = LanguageFeatures::default();
589 let mut interpreter = create_interpreter_from_ast(
590 package,
591 source_map,
592 TargetProfile::Unrestricted.into(),
593 language_features,
594 package_type,
595 )
596 .map_err(|errors| QSharpError::new_err(format_errors(errors)))?;
597
598 let entry_expr = signature.create_entry_expr_from_params(String::new());
599 interpreter
600 .set_entry_expr(&entry_expr)
601 .map_err(|errors| map_entry_compilation_errors(errors, &signature))?;
602
603 let mut tracer_config = qsc::circuit::TracerConfig::default();
604 if let Some(max_ops) = config.max_operations {
605 tracer_config.max_operations = max_ops;
606 }
607 if let Some(locations) = config.source_locations {
608 tracer_config.source_locations = locations;
609 }
610 if let Some(group_by_scope) = config.group_by_scope {
611 tracer_config.group_by_scope = group_by_scope;
612 }
613
614 let generation_method = if let Some(generation_method) = config.generation_method {
615 generation_method.into()
616 } else {
617 qsc::interpret::CircuitGenerationMethod::ClassicalEval
618 };
619
620 match interpreter.circuit(
621 CircuitEntryPoint::EntryExpr(entry_expr),
622 generation_method,
623 tracer_config,
624 ) {
625 Ok(circuit) => crate::interpreter::Circuit(circuit).into_py_any(py),
626 Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
627 }
628}
629
630/// Converts a list of Q# errors into a list of resource estimator errors.
631fn into_estimation_errors(errors: Vec<interpret::Error>) -> Vec<resource_estimator::Error> {
632 errors
633 .into_iter()
634 .map(|error| resource_estimator::Error::Interpreter(error.clone()))
635 .collect::<Vec<_>>()
636}
637
638/// Formats a list of QASM errors into a single string.
639pub(crate) fn format_qasm_errors(errors: Vec<WithSource<qsc::qasm::error::Error>>) -> String {
640 errors
641 .into_iter()
642 .map(|e| {
643 let mut message = String::new();
644 let report = miette::Report::new(e);
645 write!(message, "{report:?}").expect("write should succeed");
646 message
647 })
648 .collect::<String>()
649}
650
651/// Creates a `FileSystem` from the provided Python callbacks.
652/// If any of the callbacks are missing, this will panic.
653pub(crate) fn create_filesystem_from_py(
654 py: Python,
655 read_file: Option<PyObject>,
656 list_directory: Option<PyObject>,
657 resolve_path: Option<PyObject>,
658 fetch_github: Option<PyObject>,
659) -> impl FileSystem {
660 file_system(
661 py,
662 read_file.expect("file system hooks should have been passed in with a read file callback"),
663 list_directory
664 .expect("file system hooks should have been passed in with a list directory callback"),
665 resolve_path
666 .expect("file system hooks should have been passed in with a resolve path callback"),
667 fetch_github
668 .expect("file system hooks should have been passed in with a fetch github callback"),
669 )
670}
671
672/// Creates an `Interpreter` from the provided AST package and configuration.
673fn create_interpreter_from_ast(
674 ast_package: Package,
675 source_map: SourceMap,
676 profile: Profile,
677 language_features: LanguageFeatures,
678 package_type: PackageType,
679) -> Result<Interpreter, Vec<interpret::Error>> {
680 let capabilities = profile.into();
681 let (stdid, mut store) = qsc::compile::package_store_with_stdlib(capabilities);
682 let dependencies = vec![(PackageId::CORE, None), (stdid, None)];
683
684 let (mut unit, errors) = qsc::compile::compile_ast(
685 &store,
686 &dependencies,
687 ast_package,
688 source_map,
689 package_type,
690 capabilities,
691 );
692
693 if !errors.is_empty() {
694 return Err(into_errors(errors));
695 }
696
697 unit.expose();
698 let source_package_id = store.insert(unit);
699
700 interpret::Interpreter::with_package_store(
701 false,
702 store,
703 source_package_id,
704 capabilities,
705 language_features,
706 &dependencies,
707 )
708}
709
710/// Sanitizes the name to ensure it is a valid identifier according
711/// to the Q# specification. If the name is empty, returns "circuit".
712pub(crate) fn sanitize_name<S: AsRef<str>>(name: S) -> String {
713 let name = name.as_ref();
714 if name.is_empty() {
715 return "circuit".to_string();
716 }
717
718 let mut output = String::with_capacity(name.len());
719 let c = name.chars().next().expect("name should not be empty");
720 if c == '_' || c.is_alphabetic() {
721 output.push(c);
722 } else {
723 // invalid first character, replace with '_'
724 output.push('_');
725 }
726 output.extend(name.chars().skip(1).filter_map(|c| {
727 if c == '-' {
728 Some('_')
729 } else if c == '_' || c.is_alphanumeric() {
730 Some(c)
731 } else {
732 None
733 }
734 }));
735 output
736}
737
738/// Extracts the search path from the kwargs dictionary.
739/// If the search path is not present, returns an error.
740/// Otherwise, returns the search path as a string.
741pub(crate) fn get_search_path(kwargs: &Bound<'_, PyDict>) -> PyResult<String> {
742 kwargs.get_item("search_path")?.map_or_else(
743 || {
744 Err(PyException::new_err(
745 "Could not parse search path".to_string(),
746 ))
747 },
748 |x| x.extract::<String>(),
749 )
750}
751
752/// Extracts the program type from the kwargs dictionary.
753pub(crate) fn get_program_type<D>(kwargs: &Bound<'_, PyDict>, default: D) -> PyResult<ProgramType>
754where
755 D: FnOnce() -> ProgramType,
756{
757 let target = kwargs
758 .get_item("program_type")?
759 .map_or_else(|| Ok(default()), |x| x.extract::<ProgramType>())?;
760 Ok(target)
761}
762
763/// Extracts the output semantics from the kwargs dictionary.
764pub(crate) fn get_output_semantics<D>(
765 kwargs: &Bound<'_, PyDict>,
766 default: D,
767) -> PyResult<OutputSemantics>
768where
769 D: FnOnce() -> OutputSemantics,
770{
771 let target = kwargs
772 .get_item("output_semantics")?
773 .map_or_else(|| Ok(default()), |x| x.extract::<OutputSemantics>())?;
774 Ok(target)
775}
776
777/// Extracts the name from the kwargs dictionary.
778/// If the name is not present, returns "program".
779/// Otherwise, returns the name after sanitizing it.
780pub(crate) fn get_operation_name(kwargs: &Bound<'_, PyDict>) -> PyResult<String> {
781 let name = kwargs
782 .get_item("name")?
783 .map_or_else(|| Ok("program".to_string()), |x| x.extract::<String>())?;
784
785 // sanitize the name to ensure it is a valid identifier
786 // When creating operation, we'll throw an error if the name is not a valid identifier
787 // so that the user gets the exact name they expect, but here it's better to sanitize.
788 Ok(sanitize_name(name))
789}
790
791/// Extracts the target profile from the kwargs dictionary.
792/// If the target profile is not present, returns `TargetProfile::Unrestricted`.
793/// Otherwise if not a valid `TargetProfile`, returns an error.
794///
795/// This also maps the `TargetProfile` exposed to Python to a `Profile`
796/// used by the interpreter.
797pub(crate) fn get_target_profile(kwargs: &Bound<'_, PyDict>) -> PyResult<Profile> {
798 let target = kwargs.get_item("target_profile")?.map_or_else(
799 || Ok(TargetProfile::Unrestricted),
800 |x| x.extract::<TargetProfile>(),
801 )?;
802 Ok(target.into())
803}
804
805/// Extracts the shots from the kwargs dictionary.
806/// If the shots are not present, or are not a valid usize, returns an error.
807pub(crate) fn get_shots(kwargs: &Bound<'_, PyDict>) -> PyResult<usize> {
808 kwargs.get_item("shots")?.map_or_else(
809 || Err(PyException::new_err("Could not parse shots".to_string())),
810 |x| x.extract::<usize>(),
811 )
812}
813
814/// Extracts the seed from the kwargs dictionary.
815/// If the seed is not present, or is not a valid u64, returns None.
816pub(crate) fn get_seed(kwargs: &Bound<'_, PyDict>) -> Option<u64> {
817 kwargs
818 .get_item("seed")
819 .ok()?
820 .map_or_else(|| None::<u64>, |x| x.extract::<u64>().ok())
821}
822