microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.19.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interop.rs

787lines · modecode

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