microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.18.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/src/interop.rs

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