microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7421e7dd1015dcbd940bf843d33583470de580ea

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interop.rs

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