diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b45876b8b2..b355249e28 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,7 +18,7 @@ /pip @billti @idavis @minestarks /playground @billti @cesarzc @minestarks /resource_estimator @billti @ivanbasov @swernli -/samples @cesarzc @DmitryVasilevsky @sezna @swernli +/samples @cesarzc @DmitryVasilevsky @sezna @swernli @tcNickolas /vscode @billti @idavis @minestarks @sezna /wasm @billti @cesarzc @sezna @swernli diff --git a/Cargo.lock b/Cargo.lock index 45bbaa08e7..32e5a33635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "dissimilar" version = "1.0.7" @@ -928,6 +934,7 @@ dependencies = [ "qsc_hir", "qsc_linter", "qsc_lowerer", + "qsc_partial_eval", "qsc_passes", "qsc_project", "qsc_rca", @@ -972,13 +979,16 @@ dependencies = [ name = "qsc_codegen" version = "0.0.0" dependencies = [ + "difference", "expect-test", "indoc", "num-bigint", "num-complex", + "qsc_ast", "qsc_data_structures", "qsc_eval", "qsc_fir", + "qsc_formatter", "qsc_frontend", "qsc_hir", "qsc_lowerer", @@ -1130,6 +1140,8 @@ dependencies = [ "expect-test", "indoc", "miette", + "num-bigint", + "num-complex", "qsc", "qsc_data_structures", "qsc_eval", diff --git a/Cargo.toml b/Cargo.toml index f0fd67efd9..6f5b1ace51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ version = "0.0.0" bitflags = "2.4.2" clap = "4.4" criterion = { version = "0.5", default-features = false } +difference = "2.0.0" enum-iterator = "1.5" env_logger = "0.10" expect-test = "1.4" diff --git a/README.md b/README.md index b4c1eda052..d11fc7e5b4 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ build of each project, including running tests and checks such as linting. Run w ### Playground -To run the "playground" locally, build the repository, then `cd` into the `playground` directory, and run `npm start`. -This will launch a local web server and output the URL to the console; copy that URL and open it in a browser to use the playground. -If you only want to build the functionality necessary to run the playground, you can use `python .\build.py --wasm --npm --play`. +The `playground` is a small website that loads the Q# editor, compiler, samples, katas, and documentation for the standard library. It's a way to manually validate any changes you make to these components. + +To see instructions for building the playground, refer to [Building the Playground Locally](./playground/README.md#building-the-playground-locally). ### Python diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml index 74aa6f1069..bb34ddb6ed 100644 --- a/compiler/qsc/Cargo.toml +++ b/compiler/qsc/Cargo.toml @@ -28,6 +28,7 @@ qsc_ast = { path = "../qsc_ast" } qsc_fir = { path = "../qsc_fir" } qsc_hir = { path = "../qsc_hir" } qsc_passes = { path = "../qsc_passes" } +qsc_partial_eval = { path = "../qsc_partial_eval" } qsc_project = { path = "../qsc_project", features = ["fs"] } qsc_rca = { path = "../qsc_rca" } qsc_circuit = { path = "../qsc_circuit" } diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs index 3dbee4548d..df4b54926d 100644 --- a/compiler/qsc/benches/eval.rs +++ b/compiler/qsc/benches/eval.rs @@ -8,7 +8,7 @@ use indoc::indoc; use qsc::{interpret::Interpreter, PackageType}; use qsc_data_structures::language_features::LanguageFeatures; use qsc_eval::output::GenericReceiver; -use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags}; const TELEPORT: &str = include_str!("../../../samples/algorithms/Teleportation.qs"); const DEUTSCHJOZSA: &str = include_str!("../../../samples/algorithms/DeutschJozsa.qs"); @@ -22,7 +22,7 @@ pub fn teleport(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -41,7 +41,7 @@ pub fn deutsch_jozsa(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -60,7 +60,7 @@ pub fn large_file(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -91,7 +91,7 @@ pub fn array_append(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -122,7 +122,7 @@ pub fn array_update(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -141,7 +141,7 @@ pub fn array_literal(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); @@ -177,7 +177,7 @@ pub fn large_nested_iteration(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); diff --git a/compiler/qsc/benches/large.rs b/compiler/qsc/benches/large.rs index 6955df5aec..5025108d5a 100644 --- a/compiler/qsc/benches/large.rs +++ b/compiler/qsc/benches/large.rs @@ -6,7 +6,7 @@ allocator::assign_global!(); use criterion::{criterion_group, criterion_main, Criterion}; use qsc::compile::{self, compile}; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_passes::PackageType; const INPUT: &str = include_str!("./large.qs"); @@ -14,7 +14,7 @@ const INPUT: &str = include_str!("./large.qs"); pub fn large_file(c: &mut Criterion) { c.bench_function("Large input file compilation", |b| { let mut store = PackageStore::new(compile::core()); - let std = store.insert(compile::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(compile::std(&store, TargetCapabilityFlags::all())); b.iter(|| { let sources = SourceMap::new([("large.qs".into(), INPUT.into())], None); let (_, reports) = compile( @@ -22,7 +22,7 @@ pub fn large_file(c: &mut Criterion) { &[std], sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(reports.is_empty()); @@ -38,7 +38,7 @@ pub fn large_file_interpreter(c: &mut Criterion) { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("code should compile"); diff --git a/compiler/qsc/benches/library.rs b/compiler/qsc/benches/library.rs index 07cf056947..3decd16bf8 100644 --- a/compiler/qsc/benches/library.rs +++ b/compiler/qsc/benches/library.rs @@ -5,13 +5,13 @@ allocator::assign_global!(); use criterion::{criterion_group, criterion_main, Criterion}; use qsc::compile; -use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags}; +use qsc_frontend::compile::{PackageStore, TargetCapabilityFlags}; pub fn library(c: &mut Criterion) { c.bench_function("Core + Standard library compilation", |b| { b.iter(|| { let store = PackageStore::new(compile::core()); - compile::std(&store, RuntimeCapabilityFlags::all()) + compile::std(&store, TargetCapabilityFlags::all()) }); }); } diff --git a/compiler/qsc/benches/rca.rs b/compiler/qsc/benches/rca.rs index 4a63359cce..c9005f9aa5 100644 --- a/compiler/qsc/benches/rca.rs +++ b/compiler/qsc/benches/rca.rs @@ -5,7 +5,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use qsc::incremental::Compiler; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir::PackageStore; -use qsc_frontend::compile::{PackageStore as HirPackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_passes::PackageType; use qsc_rca::{Analyzer, PackageStoreComputeProperties}; @@ -130,7 +130,7 @@ impl Default for CompilationContext { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("should be able to create a new compiler"); diff --git a/compiler/qsc/src/bin/memtest.rs b/compiler/qsc/src/bin/memtest.rs index cdacebf91e..05cb61bf04 100644 --- a/compiler/qsc/src/bin/memtest.rs +++ b/compiler/qsc/src/bin/memtest.rs @@ -4,7 +4,7 @@ //! Records the memory usage of the compiler. use qsc::{compile, CompileUnit}; -use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags}; +use qsc_frontend::compile::{PackageStore, TargetCapabilityFlags}; use std::{ alloc::{GlobalAlloc, Layout, System}, sync::atomic::{AtomicU64, Ordering}, @@ -48,7 +48,7 @@ static ALLOCATOR: AllocationCounter = AllocationCounter::new(System); #[must_use] pub fn compile_stdlib() -> CompileUnit { let store = PackageStore::new(compile::core()); - compile::std(&store, RuntimeCapabilityFlags::all()) + compile::std(&store, TargetCapabilityFlags::all()) } fn main() { diff --git a/compiler/qsc/src/bin/qsc.rs b/compiler/qsc/src/bin/qsc.rs index ee7a6c50d0..91f3f8b139 100644 --- a/compiler/qsc/src/bin/qsc.rs +++ b/compiler/qsc/src/bin/qsc.rs @@ -10,7 +10,7 @@ use qsc::compile::compile; use qsc_codegen::qir_base; use qsc_data_structures::language_features::LanguageFeatures; use qsc_frontend::{ - compile::{PackageStore, RuntimeCapabilityFlags, SourceContents, SourceMap, SourceName}, + compile::{PackageStore, SourceContents, SourceMap, SourceName, TargetCapabilityFlags}, error::WithSource, }; use qsc_hir::hir::{Package, PackageId}; @@ -74,9 +74,9 @@ fn main() -> miette::Result { let mut dependencies = Vec::new(); let (package_type, capabilities) = if cli.emit.contains(&Emit::Qir) { - (PackageType::Exe, RuntimeCapabilityFlags::empty()) + (PackageType::Exe, TargetCapabilityFlags::empty()) } else { - (PackageType::Lib, RuntimeCapabilityFlags::all()) + (PackageType::Lib, TargetCapabilityFlags::all()) }; if !cli.nostdlib { diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs index 6f3c8649b1..c987012e49 100644 --- a/compiler/qsc/src/bin/qsi.rs +++ b/compiler/qsc/src/bin/qsi.rs @@ -14,7 +14,7 @@ use qsc_eval::{ state::format_state_id, val::Value, }; -use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceContents, SourceMap, SourceName}; +use qsc_frontend::compile::{SourceContents, SourceMap, SourceName, TargetCapabilityFlags}; use qsc_passes::PackageType; use qsc_project::{FileSystem, Manifest, StdFs}; use std::{ @@ -106,7 +106,7 @@ fn main() -> miette::Result { !cli.nostdlib, SourceMap::new(sources, cli.entry.map(std::convert::Into::into)), PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), features, ) { Ok(interpreter) => interpreter, @@ -126,7 +126,7 @@ fn main() -> miette::Result { !cli.nostdlib, SourceMap::new(sources, None), PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), features, ) { Ok(interpreter) => interpreter, diff --git a/compiler/qsc/src/codegen.rs b/compiler/qsc/src/codegen.rs index 8dd19224b5..d0d8b692e6 100644 --- a/compiler/qsc/src/codegen.rs +++ b/compiler/qsc/src/codegen.rs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qsc_codegen::qir::hir_to_qir; +use qsc_codegen::qir::fir_to_qir; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags}; +use qsc_partial_eval::ProgramEntry; use qsc_passes::{PackageType, PassContext}; use crate::compile; @@ -11,7 +12,7 @@ use crate::compile; pub fn get_qir( sources: SourceMap, language_features: LanguageFeatures, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Result { let core = compile::core(); let mut package_store = PackageStore::new(core); @@ -35,17 +36,25 @@ pub fn get_qir( } let package_id = package_store.insert(unit); - - let caps_results = PassContext::run_fir_passes_on_hir(&package_store, package_id, capabilities); + let (fir_store, fir_package_id) = qsc_passes::lower_hir_to_fir(&package_store, package_id); + let caps_results = PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities); + let package = fir_store.get(fir_package_id); + let entry = ProgramEntry { + exec_graph: package.entry_exec_graph.clone(), + expr: ( + fir_package_id, + package + .entry + .expect("package must have an entry expression"), + ) + .into(), + }; // Ensure it compiles before trying to add it to the store. match caps_results { - Ok(compute_properties) => hir_to_qir( - &package_store, - package_id, - capabilities, - Some(compute_properties), - ) - .map_err(|e| e.to_string()), + Ok(compute_properties) => { + fir_to_qir(&fir_store, capabilities, Some(compute_properties), &entry) + .map_err(|e| e.to_string()) + } Err(_) => { // This should never happen, as the program should be checked for errors before trying to // generate code for it. But just in case, simply report the failure. diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs index 2ebbccd6d8..593ce37590 100644 --- a/compiler/qsc/src/compile.rs +++ b/compiler/qsc/src/compile.rs @@ -4,7 +4,7 @@ use miette::{Diagnostic, Report}; use qsc_data_structures::language_features::LanguageFeatures; use qsc_frontend::{ - compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, SourceMap}, + compile::{CompileUnit, PackageStore, SourceMap, TargetCapabilityFlags}, error::WithSource, }; use qsc_hir::hir::PackageId; @@ -33,22 +33,54 @@ pub enum ErrorKind { Lint(#[from] qsc_linter::Lint), } +#[must_use] +#[allow(clippy::module_name_repetitions)] +pub fn compile_ast( + store: &PackageStore, + dependencies: &[PackageId], + ast_package: qsc_ast::ast::Package, + sources: SourceMap, + package_type: PackageType, + capabilities: TargetCapabilityFlags, +) -> (CompileUnit, Vec) { + let unit = qsc_frontend::compile::compile_ast( + store, + dependencies, + ast_package, + sources, + capabilities, + vec![], + ); + process_compile_unit(store, package_type, capabilities, unit) +} + #[must_use] pub fn compile( store: &PackageStore, dependencies: &[PackageId], sources: SourceMap, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> (CompileUnit, Vec) { - let mut unit = qsc_frontend::compile::compile( + let unit = qsc_frontend::compile::compile( store, dependencies, sources, capabilities, language_features, ); + process_compile_unit(store, package_type, capabilities, unit) +} + +#[must_use] +#[allow(clippy::module_name_repetitions)] +fn process_compile_unit( + store: &PackageStore, + package_type: PackageType, + capabilities: TargetCapabilityFlags, + mut unit: CompileUnit, +) -> (CompileUnit, Vec) { let mut errors = Vec::new(); for error in unit.errors.drain(..) { errors.push(WithSource::from_map(&unit.sources, error.into())); @@ -90,7 +122,7 @@ pub fn core() -> CompileUnit { /// /// Panics if the standard library does not compile without errors. #[must_use] -pub fn std(store: &PackageStore, capabilities: RuntimeCapabilityFlags) -> CompileUnit { +pub fn std(store: &PackageStore, capabilities: TargetCapabilityFlags) -> CompileUnit { let mut unit = qsc_frontend::compile::std(store, capabilities); let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib, capabilities); if pass_errors.is_empty() { diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs index 53b1c8f451..a4473ce612 100644 --- a/compiler/qsc/src/incremental.rs +++ b/compiler/qsc/src/incremental.rs @@ -3,9 +3,10 @@ use crate::compile::{self, compile, core, std}; use miette::Diagnostic; +use qsc_ast::ast; use qsc_data_structures::language_features::LanguageFeatures; use qsc_frontend::{ - compile::{OpenPackageStore, PackageStore, RuntimeCapabilityFlags, SourceMap}, + compile::{OpenPackageStore, PackageStore, SourceMap, TargetCapabilityFlags}, error::WithSource, incremental::Increment, }; @@ -37,7 +38,7 @@ impl Compiler { include_std: bool, sources: SourceMap, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> Result { let core = core(); @@ -80,6 +81,24 @@ impl Compiler { }) } + pub fn from( + store: PackageStore, + source_package_id: PackageId, + capabilities: TargetCapabilityFlags, + language_features: LanguageFeatures, + ) -> Result { + let frontend = + qsc_frontend::incremental::Compiler::new(&store, [], capabilities, language_features); + let store = store.open(); + + Ok(Self { + store, + source_package_id, + frontend, + passes: PassContext::new(capabilities), + }) + } + /// Compiles Q# fragments. Fragments are Q# code that can contain /// top-level statements as well as namespaces. A notebook cell /// or an interpreter entry is an example of fragments. @@ -99,6 +118,26 @@ impl Compiler { self.compile_fragments(source_name, source_contents, fail_on_error) } + /// Compiles Q# ast fragments. Fragments are Q# code that can contain + /// top-level statements as well as namespaces. A notebook cell + /// or an interpreter entry is an example of fragments. + /// + /// This method returns the AST and HIR packages that were created as a result of + /// the compilation, however it does *not* update the current compilation. + /// + /// The caller can use the returned packages to perform passes, + /// get information about the newly added items, or do other modifications. + /// It is then the caller's responsibility to merge + /// these packages into the current `CompileUnit` using the `update()` method. + pub fn compile_ast_fragments_fail_fast( + &mut self, + source_name: &str, + source_contents: &str, + package: ast::Package, + ) -> Result { + self.compile_ast_fragments(source_name, source_contents, package, fail_on_error) + } + /// Compiles Q# fragments. See [`compile_fragments_fail_fast`] for more details. /// /// This method calls an accumulator function with any errors returned @@ -140,6 +179,52 @@ impl Compiler { Ok(increment) } + /// Compiles Q# ast fragments. See [`compile_ast_fragments_fail_fast`] for more details. + /// + /// This method calls an accumulator function with any errors returned + /// from each of the stages (parsing, lowering). + /// If the accumulator succeeds, compilation continues. + /// If the accumulator returns an error, compilation stops and the + /// error is returned to the caller. + pub fn compile_ast_fragments( + &mut self, + source_name: &str, + source_contents: &str, + package: ast::Package, + mut accumulate_errors: F, + ) -> Result + where + F: FnMut(Errors) -> Result<(), Errors>, + { + let (core, unit) = self.store.get_open_mut(); + + let mut errors = false; + let mut increment = self.frontend.compile_ast_fragments( + unit, + source_name, + source_contents, + package, + |e| { + errors = errors || !e.is_empty(); + accumulate_errors(into_errors(e)) + }, + )?; + + // Even if we don't fail fast, skip passes if there were compilation errors. + if !errors { + let pass_errors = self.passes.run_default_passes( + &mut increment.hir, + &mut unit.assigner, + core, + PackageType::Lib, + ); + + accumulate_errors(into_errors_with_source(pass_errors, &unit.sources))?; + } + + Ok(increment) + } + /// Compiles an entry expression. /// /// This method returns the AST and HIR packages that were created as a result of diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index 452ee0c946..b1f9dbceaf 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -24,6 +24,8 @@ pub use qsc_eval::{ StepAction, StepResult, }; use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir}; +use qsc_partial_eval::ProgramEntry; +use qsc_rca::PackageStoreComputeProperties; use crate::{ error::{self, WithStack}, @@ -38,7 +40,7 @@ use qsc_circuit::{ operations::entry_expr_for_qubit_operation, Builder as CircuitBuilder, Circuit, Config as CircuitConfig, }; -use qsc_codegen::qir_base::BaseProfSim; +use qsc_codegen::{qir::fir_to_qir, qir_base::BaseProfSim}; use qsc_data_structures::{ functors::FunctorApp, language_features::LanguageFeatures, @@ -56,8 +58,9 @@ use qsc_fir::{ visit::{self, Visitor}, }; use qsc_frontend::{ - compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap}, + compile::{CompileUnit, PackageStore, Source, SourceMap, TargetCapabilityFlags}, error::WithSource, + incremental::Increment, }; use qsc_passes::{PackageType, PassContext}; use rustc_hash::FxHashSet; @@ -97,14 +100,17 @@ pub enum Error { #[diagnostic(code("Qsc.Interpret.NotAnOperation"))] #[diagnostic(help("provide the name of a callable or a lambda expression"))] NotAnOperation, + #[error("partial evaluation error")] + #[diagnostic(transparent)] + PartialEvaluation(#[from] WithSource), } /// A Q# interpreter. pub struct Interpreter { /// The incremental Q# compiler. compiler: Compiler, - /// The runtime capabilities used for compilation. - capabilities: RuntimeCapabilityFlags, + /// The target capabilities used for compilation. + capabilities: TargetCapabilityFlags, /// The number of lines that have so far been compiled. /// This field is used to generate a unique label /// for each line evaluated with `eval_fragments`. @@ -142,7 +148,7 @@ impl Interpreter { std: bool, sources: SourceMap, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> std::result::Result> { Self::new_internal( @@ -162,7 +168,7 @@ impl Interpreter { std: bool, sources: SourceMap, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> std::result::Result> { Self::new_internal( @@ -180,7 +186,7 @@ impl Interpreter { std: bool, sources: SourceMap, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> std::result::Result> { let compiler = Compiler::new(std, sources, package_type, capabilities, language_features) @@ -205,19 +211,43 @@ impl Interpreter { fir_store, lowerer: qsc_lowerer::Lowerer::new().with_debug(dbg), env: Env::default(), - sim: BackendChain::new( - SparseSim::new(), - CircuitBuilder::new(CircuitConfig { - // When using in conjunction with the simulator, - // the circuit builder should *not* perform base profile - // decompositions, in order to match the simulator's behavior. - // - // Note that conditional compilation (e.g. @Config(Base) attributes) - // will still respect the selected profile. This also - // matches the behavior of the simulator. - base_profile: false, - }), - ), + sim: sim_circuit_backend(), + quantum_seed: None, + classical_seed: None, + package: map_hir_package_to_fir(package_id), + source_package: map_hir_package_to_fir(source_package_id), + }) + } + + pub fn from( + store: PackageStore, + source_package_id: qsc_hir::hir::PackageId, + capabilities: TargetCapabilityFlags, + language_features: LanguageFeatures, + ) -> std::result::Result> { + let compiler = Compiler::from(store, source_package_id, capabilities, language_features) + .map_err(into_errors)?; + + let mut fir_store = fir::PackageStore::new(); + for (id, unit) in compiler.package_store() { + let mut lowerer = qsc_lowerer::Lowerer::new(); + fir_store.insert( + map_hir_package_to_fir(id), + lowerer.lower_package(&unit.package), + ); + } + + let source_package_id = compiler.source_package_id(); + let package_id = compiler.package_id(); + + Ok(Self { + compiler, + lines: 0, + capabilities, + fir_store, + lowerer: qsc_lowerer::Lowerer::new(), + env: Env::default(), + sim: sim_circuit_backend(), quantum_seed: None, classical_seed: None, package: map_hir_package_to_fir(package_id), @@ -300,7 +330,37 @@ impl Interpreter { .compile_fragments_fail_fast(&label, fragments) .map_err(into_errors)?; - let (_, graph) = self.lower(&increment)?; + self.eval_increment(receiver, increment) + } + + /// It is assumed that if there were any parse errors on the fragments, the caller would have + /// already handled them. This function is intended to be used in cases where the caller wants + /// to handle the parse errors themselves. + /// # Errors + /// If the compilation of the fragments fails, an error is returned. + /// If there is a runtime error when interpreting the fragments, an error is returned. + pub fn eval_ast_fragments( + &mut self, + receiver: &mut impl Receiver, + fragments: &str, + package: qsc_ast::ast::Package, + ) -> InterpretResult { + let label = self.next_line_label(); + + let increment = self + .compiler + .compile_ast_fragments_fail_fast(&label, fragments, package) + .map_err(into_errors)?; + + self.eval_increment(receiver, increment) + } + + fn eval_increment( + &mut self, + receiver: &mut impl Receiver, + increment: Increment, + ) -> InterpretResult { + let (graph, _) = self.lower(&increment)?; // Updating the compiler state with the new AST/HIR nodes // is not necessary for the interpreter to function, as all @@ -345,17 +405,59 @@ impl Interpreter { /// Performs QIR codegen using the given entry expression on a new instance of the environment /// and simulator but using the current compilation. pub fn qirgen(&mut self, expr: &str) -> std::result::Result> { - if self.capabilities != RuntimeCapabilityFlags::empty() { + if self.capabilities == TargetCapabilityFlags::all() { return Err(vec![Error::UnsupportedRuntimeCapabilities]); } + if self.capabilities == TargetCapabilityFlags::empty() { + let mut sim = BaseProfSim::new(); + let mut stdout = std::io::sink(); + let mut out = GenericReceiver::new(&mut stdout); - let mut sim = BaseProfSim::new(); - let mut stdout = std::io::sink(); - let mut out = GenericReceiver::new(&mut stdout); - - let val = self.run_with_sim(&mut sim, &mut out, expr)??; + let val = self.run_with_sim(&mut sim, &mut out, expr)??; - Ok(sim.finish(&val)) + Ok(sim.finish(&val)) + } else { + // Compile the expression. This operation will set the expression as + // the entry-point in the FIR store. + let (graph, compute_properties) = self.compile_entry_expr(expr)?; + + let Some(compute_properties) = compute_properties else { + // This can only happen if capability analysis was not run. This would be a bug + // and we are in a bad state and can't proceed. + panic!( + "internal error: compute properties not set after lowering entry expression" + ); + }; + let package = self.fir_store.get(self.package); + let entry = ProgramEntry { + exec_graph: graph.into(), + expr: ( + self.package, + package + .entry + .expect("package must have an entry expression"), + ) + .into(), + }; + // Generate QIR + fir_to_qir( + &self.fir_store, + self.capabilities, + Some(compute_properties), + &entry, + ) + .map_err(|e| { + let source_package = self + .compiler + .package_store() + .get(map_fir_package_to_hir(self.package)) + .expect("package should exist in the package store"); + vec![Error::PartialEvaluation(WithSource::from_map( + &source_package.sources, + e, + ))] + }) + } } /// Generates a circuit representation for the program. @@ -365,16 +467,16 @@ impl Interpreter { /// /// An operation can be specified by its name or a lambda expression that only takes qubits. /// e.g. `Sample.Main` , `qs => H(qs[0])` + /// + /// If `simulate` is specified, the program is simulated and the resulting + /// circuit is returned (a.k.a. trace mode). Otherwise, the circuit is generated without + /// simulation. In this case circuit generation may fail if the program contains dynamic + /// behavior (quantum operations that are dependent on measurement results). pub fn circuit( &mut self, entry: CircuitEntryPoint, + simulate: bool, ) -> std::result::Result> { - let mut sink = std::io::sink(); - let mut out = GenericReceiver::new(&mut sink); - let mut sim = CircuitBuilder::new(CircuitConfig { - base_profile: self.capabilities.is_empty(), - }); - let entry_expr = match entry { CircuitEntryPoint::Operation(operation_expr) => { let (item, functor_app) = self.eval_to_operation(&operation_expr)?; @@ -386,13 +488,23 @@ impl Interpreter { CircuitEntryPoint::EntryPoint => None, }; - if let Some(entry_expr) = entry_expr { - self.run_with_sim(&mut sim, &mut out, &entry_expr)? + let circuit = if simulate { + let mut sim = sim_circuit_backend(); + + self.run_with_sim_no_output(entry_expr, &mut sim)?; + + sim.chained.finish() } else { - self.eval_entry_with_sim(&mut sim, &mut out) - }?; + let mut sim = CircuitBuilder::new(CircuitConfig { + base_profile: self.capabilities.is_empty(), + }); + + self.run_with_sim_no_output(entry_expr, &mut sim)?; - Ok(sim.finish()) + sim.finish() + }; + + Ok(circuit) } /// Runs the given entry expression on the given simulator with a new instance of the environment @@ -403,7 +515,7 @@ impl Interpreter { receiver: &mut impl Receiver, expr: &str, ) -> std::result::Result> { - let graph = self.compile_entry_expr(expr)?; + let (graph, _) = self.compile_entry_expr(expr)?; if self.quantum_seed.is_some() { sim.set_seed(self.quantum_seed); @@ -421,10 +533,43 @@ impl Interpreter { )) } + fn run_with_sim_no_output( + &mut self, + entry_expr: Option, + sim: &mut impl Backend>, + ) -> InterpretResult { + let mut sink = std::io::sink(); + let mut out = GenericReceiver::new(&mut sink); + + let (package_id, graph) = if let Some(entry_expr) = entry_expr { + // entry expression is provided + (self.package, self.compile_entry_expr(&entry_expr)?.0.into()) + } else { + // no entry expression, use the entrypoint in the package + (self.source_package, self.get_entry_exec_graph()?) + }; + + if self.quantum_seed.is_some() { + sim.set_seed(self.quantum_seed); + } + + eval( + package_id, + self.classical_seed, + graph, + self.compiler.package_store(), + &self.fir_store, + &mut Env::default(), + sim, + &mut out, + ) + } + fn compile_entry_expr( &mut self, expr: &str, - ) -> std::result::Result, Vec> { + ) -> std::result::Result<(Vec, Option), Vec> + { let increment = self .compiler .compile_entry_expr(expr) @@ -432,7 +577,7 @@ impl Interpreter { // `lower` will update the entry expression in the FIR store, // and it will always return an empty list of statements. - let (_, graph) = self.lower(&increment)?; + let (graph, compute_properties) = self.lower(&increment)?; // The AST and HIR packages in `increment` only contain an entry // expression and no statements. The HIR *can* contain items if the entry @@ -448,58 +593,52 @@ impl Interpreter { // here to keep the package stores consistent. self.compiler.update(increment); - Ok(graph) + Ok((graph, compute_properties)) } fn lower( &mut self, unit_addition: &qsc_frontend::incremental::Increment, - ) -> core::result::Result<(Vec, Vec), Vec> { - if self.capabilities != RuntimeCapabilityFlags::all() { + ) -> core::result::Result<(Vec, Option), Vec> + { + if self.capabilities != TargetCapabilityFlags::all() { return self.run_fir_passes(unit_addition); } let fir_package = self.fir_store.get_mut(self.package); - Ok(( - self.lowerer - .lower_and_update_package(fir_package, &unit_addition.hir), - self.lowerer.take_exec_graph(), - )) + self.lowerer + .lower_and_update_package(fir_package, &unit_addition.hir); + Ok((self.lowerer.take_exec_graph(), None)) } fn run_fir_passes( &mut self, unit: &qsc_frontend::incremental::Increment, - ) -> std::result::Result<(Vec, Vec), Vec> { + ) -> std::result::Result<(Vec, Option), Vec> + { let fir_package = self.fir_store.get_mut(self.package); - let stmts = self - .lowerer + self.lowerer .lower_and_update_package(fir_package, &unit.hir); let cap_results = PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities); - let Err(caps_errors) = cap_results else { - let graph = self.lowerer.take_exec_graph(); - return Ok((stmts, graph)); - }; + let compute_properties = cap_results.map_err(|caps_errors| { + // if there are errors, convert them to interpreter errors + // and don't update the lowerer or FIR store. + let source_package = self + .compiler + .package_store() + .get(map_fir_package_to_hir(self.package)) + .expect("package should exist in the package store"); - // if there are errors, convert them to interpreter errors - // and don't update the lowerer or FIR store. - let mut errors = Vec::with_capacity(caps_errors.len()); - let source_package = self - .compiler - .package_store() - .get(map_fir_package_to_hir(self.package)) - .expect("package should exist in the package store"); + caps_errors + .into_iter() + .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error))) + .collect::>() + })?; - for error in caps_errors { - errors.push(Error::Pass(WithSource::from_map( - &source_package.sources, - error, - ))); - } - - Err(errors) + let graph = self.lowerer.take_exec_graph(); + Ok((graph, Some(compute_properties))) } fn next_line_label(&mut self) -> String { @@ -539,6 +678,22 @@ impl Interpreter { } } +fn sim_circuit_backend() -> BackendChain { + BackendChain::new( + SparseSim::new(), + CircuitBuilder::new(CircuitConfig { + // When using in conjunction with the simulator, + // the circuit builder should *not* perform base profile + // decompositions, in order to match the simulator's behavior. + // + // Note that conditional compilation (e.g. @Config(Base) attributes) + // will still respect the selected profile. This also + // matches the behavior of the simulator. + base_profile: false, + }), + ) +} + /// Describes the entry point for circuit generation. pub enum CircuitEntryPoint { /// An operation. This must be a callable name or a lambda @@ -565,7 +720,7 @@ pub struct Debugger { impl Debugger { pub fn new( sources: SourceMap, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, position_encoding: Encoding, language_features: LanguageFeatures, ) -> std::result::Result> { diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs index 4b2255e8fa..6b9a4fa77a 100644 --- a/compiler/qsc/src/interpret/circuit_tests.rs +++ b/compiler/qsc/src/interpret/circuit_tests.rs @@ -39,7 +39,7 @@ fn empty() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![].assert_eq(&circ.to_string()); @@ -61,7 +61,7 @@ fn one_gate() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -86,7 +86,7 @@ fn rotation_gate() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // The wire isn't visible here since the gate label is longer @@ -115,7 +115,7 @@ fn classical_for_loop() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -142,7 +142,7 @@ fn m_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -171,7 +171,7 @@ fn m_unrestricted_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -199,7 +199,7 @@ fn mresetz_unrestricted_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -227,7 +227,7 @@ fn mresetz_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -265,7 +265,7 @@ fn unrestricted_profile_result_comparison() { interpreter.set_quantum_seed(Some(2)); let circuit_err = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect_err("circuit should return error") .pop() .expect("error should exist"); @@ -283,9 +283,23 @@ fn unrestricted_profile_result_comparison() { let mut out = std::io::sink(); let mut r = GenericReceiver::new(&mut out); - // Counterintuitive but expected: result comparisons - // are okay if calling get_circuit() after incremental - // evaluation, because we're using the current simulator + // Result comparisons are okay when tracing + // circuit with the simulator. + let circ = interpreter + .circuit(CircuitEntryPoint::EntryPoint, true) + .expect("circuit generation should succeed"); + + expect![[r" + q_0 ── H ──── M ──── X ─── |0〉 ─ + ╘═════════════════ + q_1 ── H ──── M ─── |0〉 ──────── + ╘═════════════════ + "]] + .assert_eq(&circ.to_string()); + + // Result comparisons are also okay if calling + // get_circuit() after incremental evaluation, + // because we're using the current simulator // state. interpreter .eval_fragments(&mut r, "Test.Main();") @@ -320,7 +334,7 @@ fn custom_intrinsic() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); expect![[r" @@ -349,7 +363,7 @@ fn custom_intrinsic_classical_arg() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // A custom intrinsic that doesn't take qubits just doesn't @@ -380,7 +394,7 @@ fn custom_intrinsic_one_classical_arg() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // A custom intrinsic that doesn't take qubits just doesn't @@ -418,7 +432,7 @@ fn custom_intrinsic_mixed_args() { ); let circ = interpreter - .circuit(CircuitEntryPoint::EntryPoint) + .circuit(CircuitEntryPoint::EntryPoint, false) .expect("circuit generation should succeed"); // This is one gate that spans ten target wires, even though the @@ -458,7 +472,7 @@ fn operation_with_qubits() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -488,7 +502,7 @@ fn operation_with_qubits_base_profile() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -535,7 +549,7 @@ fn operation_with_qubit_arrays() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -587,7 +601,10 @@ fn adjoint_operation() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("Adjoint Test.Foo".into())) + .circuit( + CircuitEntryPoint::Operation("Adjoint Test.Foo".into()), + false, + ) .expect("circuit generation should succeed"); expect![[r" @@ -608,7 +625,7 @@ fn lambda() { ); let circ = interpreter - .circuit(CircuitEntryPoint::Operation("q => H(q)".into())) + .circuit(CircuitEntryPoint::Operation("q => H(q)".into()), false) .expect("circuit generation should succeed"); expect![[r" @@ -649,7 +666,10 @@ fn controlled_operation() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Controlled Test.SWAP".into())) + .circuit( + CircuitEntryPoint::Operation("Controlled Test.SWAP".into()), + false, + ) .expect_err("circuit generation should fail"); // Controlled operations are not supported at the moment. @@ -682,7 +702,7 @@ fn internal_operation() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect_err("circuit generation should fail"); expect![[r#" @@ -731,7 +751,7 @@ fn operation_with_non_qubit_args() { ); let circ_err = interpreter - .circuit(CircuitEntryPoint::Operation("Test.Test".into())) + .circuit(CircuitEntryPoint::Operation("Test.Test".into()), false) .expect_err("circuit generation should fail"); expect![[r" diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs index 87a7b0ab17..3de86b7296 100644 --- a/compiler/qsc/src/interpret/debug/tests.rs +++ b/compiler/qsc/src/interpret/debug/tests.rs @@ -7,7 +7,7 @@ use indoc::indoc; use miette::Result; use qsc_data_structures::language_features::LanguageFeatures; use qsc_eval::{output::CursorReceiver, val::Value}; -use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags}; use qsc_passes::PackageType; use std::io::Cursor; @@ -70,7 +70,7 @@ fn stack_traces_can_cross_eval_session_and_file_boundaries() { true, source_map, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("Failed to compile base environment."); @@ -145,7 +145,7 @@ fn stack_traces_can_cross_file_and_entry_boundaries() { true, source_map, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("Failed to compile base environment."); diff --git a/compiler/qsc/src/interpret/debugger_tests.rs b/compiler/qsc/src/interpret/debugger_tests.rs index 33a9a2758c..d3e73953ae 100644 --- a/compiler/qsc/src/interpret/debugger_tests.rs +++ b/compiler/qsc/src/interpret/debugger_tests.rs @@ -8,7 +8,7 @@ use crate::line_column::Encoding; use qsc_data_structures::language_features::LanguageFeatures; use qsc_eval::{output::CursorReceiver, StepAction, StepResult}; use qsc_fir::fir::StmtId; -use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags}; use std::io::Cursor; fn get_breakpoint_ids(debugger: &Debugger, path: &str) -> Vec { @@ -131,7 +131,7 @@ mod given_debugger { let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None); let mut debugger = Debugger::new( sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), Encoding::Utf8, LanguageFeatures::default(), )?; @@ -154,7 +154,7 @@ mod given_debugger { let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None); let mut debugger = Debugger::new( sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), Encoding::Utf8, LanguageFeatures::default(), )?; @@ -173,7 +173,7 @@ mod given_debugger { let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None); let mut debugger = Debugger::new( sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), Encoding::Utf8, LanguageFeatures::default(), )?; @@ -199,7 +199,7 @@ mod given_debugger { let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None); let mut debugger = Debugger::new( sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), Encoding::Utf8, LanguageFeatures::default(), )?; diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs index dc1ab6dfc9..85cf65e778 100644 --- a/compiler/qsc/src/interpret/tests.rs +++ b/compiler/qsc/src/interpret/tests.rs @@ -9,7 +9,7 @@ mod given_interpreter { use miette::Diagnostic; use qsc_data_structures::language_features::LanguageFeatures; use qsc_eval::{output::CursorReceiver, val::Value}; - use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap}; + use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags}; use qsc_passes::PackageType; use std::{fmt::Write, io::Cursor, iter, str::from_utf8}; @@ -39,10 +39,21 @@ mod given_interpreter { (interpreter.eval_entry(&mut receiver), receiver.dump()) } + fn fragment( + interpreter: &mut Interpreter, + fragments: &str, + package: crate::ast::Package, + ) -> (Result>, String) { + let mut cursor = Cursor::new(Vec::::new()); + let mut receiver = CursorReceiver::new(&mut cursor); + let result = interpreter.eval_ast_fragments(&mut receiver, fragments, package); + (result, receiver.dump()) + } + mod without_sources { use expect_test::expect; use indoc::indoc; - use qsc_frontend::compile::RuntimeCapabilityFlags; + use qsc_frontend::compile::TargetCapabilityFlags; use super::*; @@ -58,7 +69,7 @@ mod given_interpreter { false, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -523,8 +534,7 @@ mod given_interpreter { "#]], ); } - let mut interpreter = - get_interpreter_with_capbilities(RuntimeCapabilityFlags::ForwardBranching); + let mut interpreter = get_interpreter_with_capbilities(TargetCapabilityFlags::Adaptive); let (result, output) = line( &mut interpreter, indoc! {r#" @@ -555,8 +565,7 @@ mod given_interpreter { "#]], ); } - let mut interpreter = - get_interpreter_with_capbilities(RuntimeCapabilityFlags::ForwardBranching); + let mut interpreter = get_interpreter_with_capbilities(TargetCapabilityFlags::Adaptive); let (result, output) = line( &mut interpreter, indoc! {r#" @@ -620,7 +629,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -651,7 +660,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -712,13 +721,181 @@ mod given_interpreter { "#]].assert_eq(&res); } + #[test] + fn adaptive_qirgen() { + let mut interpreter = Interpreter::new( + true, + SourceMap::default(), + PackageType::Lib, + TargetCapabilityFlags::Adaptive, + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let (result, output) = line( + &mut interpreter, + indoc! {r#" + namespace Test { + open Microsoft.Quantum.Math; + open QIR.Intrinsic; + @EntryPoint() + operation Main() : Result { + use q = Qubit(); + let pi_over_2 = 4.0 / 2.0; + __quantum__qis__rz__body(pi_over_2, q); + mutable some_angle = ArcSin(0.0); + __quantum__qis__rz__body(some_angle, q); + set some_angle = ArcCos(-1.0) / PI(); + __quantum__qis__rz__body(some_angle, q); + __quantum__qis__mresetz__body(q) + } + }"# + }, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter.qirgen("Test.Main()").expect("expected success"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__rz__body(double, %Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]] + .assert_eq(&res); + } + + #[test] + fn adaptive_qirgen_nested_output_types() { + let mut interpreter = Interpreter::new( + true, + SourceMap::default(), + PackageType::Lib, + TargetCapabilityFlags::Adaptive, + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let (result, output) = line( + &mut interpreter, + indoc! {r#" + namespace Test { + open QIR.Intrinsic; + @EntryPoint() + operation Main() : (Result, (Bool, Bool)) { + use q = Qubit(); + let r = __quantum__qis__mresetz__body(q); + (r, (r == One, r == Zero)) + } + }"# + }, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter.qirgen("Test.Main()").expect("expected success"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*)) + %var_1 = icmp eq i1 %var_0, true + %var_2 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*)) + %var_3 = icmp eq i1 %var_2, false + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__bool_record_output(i1 %var_1, i8* null) + call void @__quantum__rt__bool_record_output(i1 %var_3, i8* null) + ret void + } + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__qis__read_result__body(%Result*) + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + declare void @__quantum__rt__bool_record_output(i1, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]] + .assert_eq(&res); + } + + #[test] + fn adaptive_qirgen_fails_when_entry_expr_does_not_match_profile() { + let mut interpreter = Interpreter::new( + true, + SourceMap::default(), + PackageType::Lib, + TargetCapabilityFlags::Adaptive, + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let (result, output) = line( + &mut interpreter, + indoc! {r#" + use q = Qubit(); + mutable x = 1; + "# + }, + ); + is_only_value(&result, &output, &Value::unit()); + let res = interpreter + .qirgen("if M(q) == One { set x = 2; }") + .expect_err("expected error"); + is_error( + &res, + &expect![[r#" + cannot use a dynamic integer value + [] [set x = 2] + "#]], + ); + } + #[test] fn qirgen_entry_expr_in_block() { let mut interpreter = Interpreter::new( true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -785,7 +962,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -867,7 +1044,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -894,7 +1071,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -981,7 +1158,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1045,7 +1222,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1150,7 +1327,7 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1164,13 +1341,13 @@ mod given_interpreter { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created") } - fn get_interpreter_with_capbilities(capabilities: RuntimeCapabilityFlags) -> Interpreter { + fn get_interpreter_with_capbilities(capabilities: TargetCapabilityFlags) -> Interpreter { Interpreter::new( true, SourceMap::default(), @@ -1258,14 +1435,16 @@ mod given_interpreter { #[cfg(test)] mod with_sources { - use std::sync::Arc; + use std::{sync::Arc, vec}; use super::*; use crate::interpret::Debugger; use crate::line_column::Encoding; use expect_test::expect; use indoc::indoc; - use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap}; + use qsc_ast::ast::{Expr, ExprKind, NodeId, Package, Path, Stmt, StmtKind, TopLevelNode}; + use qsc_data_structures::span::Span; + use qsc_frontend::compile::{SourceMap, TargetCapabilityFlags}; use qsc_passes::PackageType; #[test] @@ -1283,7 +1462,7 @@ mod given_interpreter { true, sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1306,7 +1485,7 @@ mod given_interpreter { true, sources, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1333,7 +1512,7 @@ mod given_interpreter { true, sources, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1375,7 +1554,7 @@ mod given_interpreter { let sources = SourceMap::new(sources, None); let debugger = Debugger::new( sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), Encoding::Utf8, LanguageFeatures::default(), ) @@ -1406,7 +1585,7 @@ mod given_interpreter { true, sources, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1437,7 +1616,7 @@ mod given_interpreter { true, sources, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("interpreter should be created"); @@ -1451,5 +1630,177 @@ mod given_interpreter { "#]], ); } + + #[test] + fn interpreter_can_be_created_from_ast() { + let sources = SourceMap::new( + [( + "test".into(), + "namespace A { + operation B(): Result { + use qs = Qubit[2]; + X(qs[0]); + CNOT(qs[0], qs[1]); + let res = Measure([PauliZ, PauliZ], qs[...1]); + ResetAll(qs); + res + } + } + " + .into(), + )], + Some("A.B()".into()), + ); + + let (package_type, capabilities, language_features) = ( + PackageType::Lib, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ); + + let mut store = crate::PackageStore::new(crate::compile::core()); + let dependencies = vec![store.insert(crate::compile::std(&store, capabilities))]; + + let (unit, errors) = crate::compile::compile( + &store, + &dependencies, + sources, + package_type, + capabilities, + language_features, + ); + for e in &errors { + eprintln!("{e:?}"); + } + assert!(errors.is_empty(), "compilation failed: {}", errors[0]); + let package_id = store.insert(unit); + + let mut interpreter = + Interpreter::from(store, package_id, capabilities, language_features) + .expect("interpreter should be created"); + let (result, output) = entry(&mut interpreter); + is_only_value( + &result, + &output, + &Value::Result(qsc_eval::val::Result::Val(false)), + ); + } + + #[test] + fn ast_fragments_can_be_evaluated() { + let sources = SourceMap::new( + [( + "test".into(), + "namespace A { + operation B(): Result { + use qs = Qubit[2]; + X(qs[0]); + CNOT(qs[0], qs[1]); + let res = Measure([PauliZ, PauliZ], qs[...1]); + ResetAll(qs); + res + } + } + " + .into(), + )], + None, + ); + + let mut interpreter = Interpreter::new( + true, + sources, + PackageType::Lib, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let package = get_package_for_call("A", "B"); + let (result, output) = fragment(&mut interpreter, "A.B()", package); + is_only_value( + &result, + &output, + &Value::Result(qsc_eval::val::Result::Val(false)), + ); + } + + #[test] + fn ast_fragments_evaluation_returns_runtime_errors() { + let sources = SourceMap::new( + [( + "test".into(), + "namespace A { + operation B(): Int { + 42 / 0 + } + } + " + .into(), + )], + None, + ); + + let mut interpreter = Interpreter::new( + true, + sources, + PackageType::Lib, + TargetCapabilityFlags::all(), + LanguageFeatures::default(), + ) + .expect("interpreter should be created"); + let package = get_package_for_call("A", "B"); + let (result, output) = fragment(&mut interpreter, "A.B()", package); + is_only_error( + &result, + &output, + &expect![[r#" + runtime error: division by zero + cannot divide by zero [test] [0] + "#]], + ); + } + + fn get_package_for_call(ns: &str, name: &str) -> crate::ast::Package { + let args = Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Tuple(Box::new([]))), + }; + let path = Path { + id: NodeId::default(), + span: Span::default(), + namespace: Some(Box::new(qsc_ast::ast::Ident { + id: NodeId::default(), + span: Span::default(), + name: ns.into(), + })), + name: Box::new(qsc_ast::ast::Ident { + id: NodeId::default(), + span: Span::default(), + name: name.into(), + }), + }; + let path_expr = Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Path(Box::new(path))), + }; + let expr = Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Call(Box::new(path_expr), Box::new(args))), + }; + let stmt = Stmt { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(StmtKind::Expr(Box::new(expr))), + }; + let top_level = TopLevelNode::Stmt(Box::new(stmt)); + Package { + id: NodeId::default(), + nodes: vec![top_level].into_boxed_slice(), + entry: None, + } + } } } diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index c20827ee67..7d48b480ac 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -12,7 +12,7 @@ pub mod target; pub use qsc_formatter::formatter; pub use qsc_frontend::compile::{ - CompileUnit, PackageStore, RuntimeCapabilityFlags, SourceContents, SourceMap, SourceName, + CompileUnit, PackageStore, SourceContents, SourceMap, SourceName, TargetCapabilityFlags, }; pub mod resolve { @@ -37,7 +37,7 @@ pub mod project { pub use qsc_data_structures::{language_features::LanguageFeatures, span::Span}; -pub use qsc_passes::{PackageType, PassContext}; +pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext}; pub mod line_column { pub use qsc_data_structures::line_column::{Encoding, Position, Range}; diff --git a/compiler/qsc/src/location.rs b/compiler/qsc/src/location.rs index 11bb0e0cfd..851c679fbd 100644 --- a/compiler/qsc/src/location.rs +++ b/compiler/qsc/src/location.rs @@ -63,7 +63,7 @@ mod tests { use qsc_data_structures::{ language_features::LanguageFeatures, line_column::Encoding, span::Span, }; - use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags, SourceMap}; + use qsc_frontend::compile::{PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::hir::PackageId; use qsc_passes::PackageType; @@ -236,7 +236,7 @@ mod tests { let mut store = PackageStore::new(compile::core()); let mut dependencies = Vec::new(); - let (package_type, capabilities) = (PackageType::Lib, RuntimeCapabilityFlags::all()); + let (package_type, capabilities) = (PackageType::Lib, TargetCapabilityFlags::all()); let std = compile::std(&store, capabilities); let std_package_id = store.insert(std); diff --git a/compiler/qsc/src/target.rs b/compiler/qsc/src/target.rs index 04b9bf9b32..f34de2557b 100644 --- a/compiler/qsc/src/target.rs +++ b/compiler/qsc/src/target.rs @@ -3,13 +3,13 @@ use std::str::FromStr; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Profile { Unrestricted, Base, - Adaptive, + Quantinuum, } impl Profile { @@ -18,17 +18,17 @@ impl Profile { match self { Self::Unrestricted => "Unrestricted", Self::Base => "Base", - Self::Adaptive => "Adaptive", + Self::Quantinuum => "Quantinuum", } } } -impl From for RuntimeCapabilityFlags { +impl From for TargetCapabilityFlags { fn from(value: Profile) -> Self { match value { Profile::Unrestricted => Self::all(), Profile::Base => Self::empty(), - Profile::Adaptive => Self::ForwardBranching, + Profile::Quantinuum => Self::Adaptive | Self::IntegerComputations | Self::QubitReset, } } } @@ -38,7 +38,7 @@ impl FromStr for Profile { fn from_str(s: &str) -> Result { match s { - "Adaptive" | "adaptive" => Ok(Self::Adaptive), + "Quantinuum" | "quantinuum" => Ok(Self::Quantinuum), "Base" | "base" => Ok(Self::Base), "Unrestricted" | "unrestricted" => Ok(Self::Unrestricted), _ => Err(()), diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs index 558282cd5d..b34b2796e9 100644 --- a/compiler/qsc_circuit/src/builder.rs +++ b/compiler/qsc_circuit/src/builder.rs @@ -206,7 +206,13 @@ impl Backend for Builder { Some(classical_args) }, )); - Some(Ok(Value::unit())) + + match name { + // Special case this known intrinsic to match the simulator + // behavior, so that our samples will work + "BeginEstimateCaching" => Some(Ok(Value::Bool(true))), + _ => Some(Ok(Value::unit())), + } } } diff --git a/compiler/qsc_circuit/src/operations/tests.rs b/compiler/qsc_circuit/src/operations/tests.rs index f7dcefd619..94254aba26 100644 --- a/compiler/qsc_circuit/src/operations/tests.rs +++ b/compiler/qsc_circuit/src/operations/tests.rs @@ -4,13 +4,13 @@ use super::*; use expect_test::expect; use qsc_data_structures::{functors::FunctorApp, language_features::LanguageFeatures}; -use qsc_frontend::compile::{compile, core, std, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{compile, core, std, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::hir::{Item, ItemKind}; fn compile_one_operation(code: &str) -> (Item, String) { let core_pkg = core(); let mut store = PackageStore::new(core_pkg); - let std = std(&store, RuntimeCapabilityFlags::empty()); + let std = std(&store, TargetCapabilityFlags::empty()); let std = store.insert(std); let sources = SourceMap::new([("test".into(), code.into())], None); @@ -18,7 +18,7 @@ fn compile_one_operation(code: &str) -> (Item, String) { &store, &[std], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ); let mut callables = unit.package.items.values().filter_map(|i| { diff --git a/compiler/qsc_codegen/Cargo.toml b/compiler/qsc_codegen/Cargo.toml index 0ffe796e3e..285f1ea1d8 100644 --- a/compiler/qsc_codegen/Cargo.toml +++ b/compiler/qsc_codegen/Cargo.toml @@ -12,9 +12,11 @@ license.workspace = true num-bigint = { workspace = true } num-complex = { workspace = true } rustc-hash = { workspace = true } +qsc_ast = { path = "../qsc_ast" } qsc_data_structures = { path = "../qsc_data_structures" } qsc_eval = { path = "../qsc_eval" } qsc_fir = { path = "../qsc_fir" } +qsc_formatter = { path = "../qsc_formatter" } qsc_frontend = { path = "../qsc_frontend" } qsc_hir = { path = "../qsc_hir" } qsc_lowerer = { path = "../qsc_lowerer" } @@ -26,6 +28,7 @@ qsc_rir = { path = "../qsc_rir" } expect-test = { workspace = true } indoc = { workspace = true } qsc_passes = { path = "../qsc_passes" } +difference = { workspace = true } [lints] workspace = true diff --git a/compiler/qsc_codegen/src/lib.rs b/compiler/qsc_codegen/src/lib.rs index da19b0df66..8fb76b4e12 100644 --- a/compiler/qsc_codegen/src/lib.rs +++ b/compiler/qsc_codegen/src/lib.rs @@ -3,4 +3,5 @@ pub mod qir; pub mod qir_base; +pub mod qsharp; pub mod remapper; diff --git a/compiler/qsc_codegen/src/qir.rs b/compiler/qsc_codegen/src/qir.rs index 2e3f3fa9bb..03085b0b81 100644 --- a/compiler/qsc_codegen/src/qir.rs +++ b/compiler/qsc_codegen/src/qir.rs @@ -7,10 +7,9 @@ mod instruction_tests; #[cfg(test)] mod tests; -use qsc_frontend::compile::RuntimeCapabilityFlags; -use qsc_hir::hir; +use qsc_frontend::compile::TargetCapabilityFlags; use qsc_lowerer::map_hir_package_to_fir; -use qsc_partial_eval::partially_evaluate; +use qsc_partial_eval::{partially_evaluate, ProgramEntry}; use qsc_rca::PackageStoreComputeProperties; use qsc_rir::{ passes::{check_and_transform, defer_quantum_measurements}, @@ -30,22 +29,21 @@ fn lower_store(package_store: &qsc_frontend::compile::PackageStore) -> qsc_fir:: /// converts the given sources to QIR using the given language features. pub fn hir_to_qir( package_store: &qsc_frontend::compile::PackageStore, - package_id: hir::PackageId, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, compute_properties: Option, + entry: &ProgramEntry, ) -> Result { let fir_store = lower_store(package_store); - let fir_package_id = map_hir_package_to_fir(package_id); - fir_to_qir(&fir_store, fir_package_id, capabilities, compute_properties) + fir_to_qir(&fir_store, capabilities, compute_properties, entry) } pub fn fir_to_qir( fir_store: &qsc_fir::fir::PackageStore, - fir_package_id: qsc_fir::fir::PackageId, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, compute_properties: Option, + entry: &ProgramEntry, ) -> Result { - let mut program = get_rir_from_compilation(fir_store, fir_package_id, compute_properties)?; + let mut program = get_rir_from_compilation(fir_store, compute_properties, entry)?; check_and_transform(&mut program); if capabilities.is_empty() { defer_quantum_measurements(&mut program); @@ -55,15 +53,15 @@ pub fn fir_to_qir( fn get_rir_from_compilation( fir_store: &qsc_fir::fir::PackageStore, - fir_package_id: qsc_fir::fir::PackageId, compute_properties: Option, + entry: &ProgramEntry, ) -> Result { let compute_properties = compute_properties.unwrap_or_else(|| { let analyzer = qsc_rca::Analyzer::init(fir_store); analyzer.analyze_all() }); - partially_evaluate(fir_package_id, fir_store, &compute_properties) + partially_evaluate(fir_store, &compute_properties, entry) } /// A trait for converting a type into QIR of type `T`. diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs index 84c332bf3e..4002d546eb 100644 --- a/compiler/qsc_codegen/src/qir_base/tests.rs +++ b/compiler/qsc_codegen/src/qir_base/tests.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; use crate::qir_base::generate_qir; @@ -18,12 +18,12 @@ fn check(program: &str, expr: Option<&str>, expect: &Expect) { let mut core = compile::core(); assert!(run_core_passes(&mut core).is_empty()); let mut store = PackageStore::new(core); - let mut std = compile::std(&store, RuntimeCapabilityFlags::empty()); + let mut std = compile::std(&store, TargetCapabilityFlags::empty()); assert!(run_default_passes( store.core(), &mut std, PackageType::Lib, - RuntimeCapabilityFlags::empty() + TargetCapabilityFlags::empty() ) .is_empty()); let std = store.insert(std); @@ -35,7 +35,7 @@ fn check(program: &str, expr: Option<&str>, expect: &Expect) { &store, &[std], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); @@ -43,7 +43,7 @@ fn check(program: &str, expr: Option<&str>, expect: &Expect) { store.core(), &mut unit, PackageType::Exe, - RuntimeCapabilityFlags::empty() + TargetCapabilityFlags::empty() ) .is_empty()); let package = store.insert(unit); diff --git a/compiler/qsc_codegen/src/qsharp.rs b/compiler/qsc_codegen/src/qsharp.rs new file mode 100644 index 0000000000..51afd385de --- /dev/null +++ b/compiler/qsc_codegen/src/qsharp.rs @@ -0,0 +1,761 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#[cfg(test)] +mod spec_decls; + +#[cfg(test)] +mod tests; + +#[cfg(test)] +mod test_utils; + +use std::io::Write; +use std::vec; + +use qsc_ast::ast::{ + self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Functor, + FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Lit, Mutability, Pat, PatKind, Path, + Pauli, QubitInit, QubitInitKind, QubitSource, SetOp, SpecBody, SpecDecl, SpecGen, Stmt, + StmtKind, StringComponent, TernOp, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, UnOp, + Visibility, VisibilityKind, +}; +use qsc_ast::ast::{Namespace, Package}; +use qsc_ast::visit::Visitor; +use qsc_formatter::formatter::format_str; +use qsc_frontend::compile::PackageStore; + +fn write(output: W, packages: &[&Package]) { + let mut gen = QSharpGen::new(output); + for package in packages { + gen.visit_package(package); + } +} + +pub fn write_store(output: W, store: &PackageStore) { + let mut gen = QSharpGen::new(output); + for (_, unit) in store { + gen.visit_package(&unit.ast.package); + } +} + +#[must_use] +pub fn write_store_string(store: &PackageStore) -> Vec { + let mut package_strings: Vec<_> = vec![]; + for (_, unit) in store { + package_strings.push(write_package_string(&unit.ast.package)); + } + package_strings +} + +#[must_use] +pub fn write_package_string(package: &Package) -> String { + let mut output = Vec::new(); + write(&mut output, &[package]); + let s = match std::str::from_utf8(&output) { + Ok(v) => v.to_owned(), + Err(e) => format!("Invalid UTF-8 sequence: {e}"), + }; + + output.clear(); + format_str(&s) +} + +struct QSharpGen { + pub(crate) output: W, +} + +impl QSharpGen +where + W: Write, +{ + pub fn new(output: W) -> Self { + Self { output } + } + + pub fn write(&mut self, args: &str) { + write!(&mut self.output, "{args}").expect("write failed"); + } + + pub fn writeln(&mut self, args: &str) { + self.write(args); + self.write("\n"); + } + + /// special case for tuple with one element + /// otherwise we are changing the semantics of the program + fn ensure_trailing_comma_for_arity_one_tuples(&mut self, most: &[T]) { + if most.is_empty() { + self.write(","); + } + } +} + +impl Visitor<'_> for QSharpGen { + fn visit_package(&mut self, package: &'_ Package) { + package.nodes.iter().for_each(|n| match n { + TopLevelNode::Namespace(ns) => { + self.visit_namespace(ns); + } + TopLevelNode::Stmt(stmt) => self.visit_stmt(stmt), + }); + package.entry.iter().for_each(|e| self.visit_expr(e)); + } + + fn visit_namespace(&mut self, namespace: &'_ Namespace) { + self.write("namespace "); + self.visit_ident(&namespace.name); + self.writeln("{"); + namespace.items.iter().for_each(|i| { + self.visit_item(i); + }); + self.write("}"); + } + + fn visit_item(&mut self, item: &'_ Item) { + item.attrs.iter().for_each(|a| self.visit_attr(a)); + item.visibility + .iter() + .for_each(|v| self.visit_visibility(v)); + match &*item.kind { + ItemKind::Err => { + unreachable!() + } + ItemKind::Callable(decl) => self.visit_callable_decl(decl), + ItemKind::Open(ns, alias) => { + self.write("open "); + self.visit_ident(ns); + if let Some(alias) = alias { + self.write(" as "); + self.visit_ident(alias); + } + self.writeln(";"); + } + ItemKind::Ty(ident, def) => { + self.write("newtype "); + self.visit_ident(ident); + self.write(" = "); + self.visit_ty_def(def); + self.writeln(";"); + } + } + } + + fn visit_attr(&mut self, attr: &'_ Attr) { + self.write("@"); + self.visit_ident(&attr.name); + self.visit_expr(&attr.arg); + self.writeln(""); + } + + fn visit_visibility(&mut self, vis: &'_ Visibility) { + match vis.kind { + VisibilityKind::Public => {} + VisibilityKind::Internal => self.write("internal "), + } + } + + fn visit_ty_def(&mut self, def: &'_ TyDef) { + match &*def.kind { + TyDefKind::Field(name, ty) => { + for n in name { + self.visit_ident(n); + self.write(": "); + } + self.visit_ty(ty); + } + TyDefKind::Paren(def) => self.visit_ty_def(def), + TyDefKind::Tuple(defs) => { + self.write("("); + if let Some((last, most)) = defs.split_last() { + for i in most { + self.visit_ty_def(i); + self.write(", "); + } + self.visit_ty_def(last); + self.ensure_trailing_comma_for_arity_one_tuples(most); + } + self.write(")"); + } + TyDefKind::Err => {} + } + } + + fn visit_callable_decl(&mut self, decl: &'_ CallableDecl) { + match decl.kind { + CallableKind::Function => self.write("function "), + CallableKind::Operation => self.write("operation "), + } + self.visit_ident(&decl.name); + if !decl.generics.is_empty() { + self.write("<"); + if let Some((last, most)) = decl.generics.split_last() { + for i in most { + self.visit_ident(i); + self.write(", "); + } + self.visit_ident(last); + } + + self.write(">"); + } + + self.visit_pat(&decl.input); + self.write(" : "); + self.visit_ty(&decl.output); + if let Some(functors) = decl.functors.as_deref() { + self.write(" is "); + self.visit_functor_expr(functors); + } + + match &*decl.body { + CallableBody::Block(block) => { + self.visit_block(block); + } + CallableBody::Specs(specs) => { + self.writeln("{"); + specs.iter().for_each(|s| self.visit_spec_decl(s)); + self.writeln("}"); + } + } + } + + fn visit_spec_decl(&mut self, decl: &'_ SpecDecl) { + match decl.spec { + ast::Spec::Body => self.write("body "), + ast::Spec::Adj => self.write("adjoint "), + ast::Spec::Ctl => self.write("controlled "), + ast::Spec::CtlAdj => self.write("controlled adjoint "), + } + match &decl.body { + SpecBody::Gen(spec) => match spec { + SpecGen::Auto => self.writeln("auto;"), + SpecGen::Distribute => self.writeln("distribute;"), + SpecGen::Intrinsic => self.writeln("intrinsic;"), + SpecGen::Invert => self.writeln("invert;"), + SpecGen::Slf => self.writeln("self;"), + }, + SpecBody::Impl(pat, block) => { + self.visit_pat(pat); + self.visit_block(block); + } + } + } + + fn visit_functor_expr(&mut self, expr: &'_ FunctorExpr) { + match &*expr.kind { + FunctorExprKind::BinOp(op, lhs, rhs) => { + self.visit_functor_expr(lhs); + match op { + SetOp::Union => self.write(" + "), + SetOp::Intersect => self.write(" * "), + } + self.visit_functor_expr(rhs); + } + FunctorExprKind::Lit(functor) => match functor { + Functor::Adj => self.write("Adj"), + Functor::Ctl => self.write("Ctl"), + }, + FunctorExprKind::Paren(expr) => { + self.write("("); + self.visit_functor_expr(expr); + self.write(")"); + } + } + } + + fn visit_ty(&mut self, ty: &'_ Ty) { + match &*ty.kind { + TyKind::Array(item) => { + self.visit_ty(item); + self.write("[]"); + } + TyKind::Arrow(kind, lhs, rhs, functors) => { + self.visit_ty(lhs); + match kind { + CallableKind::Function => self.write(" -> "), + CallableKind::Operation => self.write(" => "), + } + self.visit_ty(rhs); + if let Some(functors) = functors.as_deref() { + self.write(" is "); + self.visit_functor_expr(functors); + } + } + TyKind::Hole => self.write("_"), + TyKind::Paren(ty) => { + self.write("("); + self.visit_ty(ty); + self.write(")"); + } + TyKind::Path(path) => self.visit_path(path), + TyKind::Param(name) => self.visit_ident(name), + TyKind::Tuple(tys) => { + if tys.is_empty() { + self.write("()"); + } else { + self.write("("); + if let Some((last, most)) = tys.split_last() { + for t in most { + self.visit_ty(t); + self.write(", "); + } + self.visit_ty(last); + self.ensure_trailing_comma_for_arity_one_tuples(most); + } + self.write(")"); + } + } + TyKind::Err => unreachable!(), + } + } + + fn visit_block(&mut self, block: &'_ Block) { + self.writeln(" {"); + block.stmts.iter().for_each(|s| { + self.visit_stmt(s); + }); + self.writeln("}"); + } + + fn visit_stmt(&mut self, stmt: &'_ Stmt) { + match &*stmt.kind { + StmtKind::Empty | StmtKind::Err => {} + StmtKind::Semi(expr) => { + self.visit_expr(expr); + self.writeln(";"); + } + StmtKind::Expr(expr) => { + self.visit_expr(expr); + } + StmtKind::Item(item) => self.visit_item(item), + StmtKind::Local(mutability, pat, value) => { + match mutability { + Mutability::Mutable => self.write("mutable "), + Mutability::Immutable => self.write("let "), + } + self.visit_pat(pat); + self.write(" = "); + self.visit_expr(value); + self.writeln(";"); + } + StmtKind::Qubit(source, pat, init, block) => { + match source { + QubitSource::Dirty => self.write("borrow "), + QubitSource::Fresh => self.write("use "), + } + self.visit_pat(pat); + self.write(" = "); + self.visit_qubit_init(init); + if let Some(b) = block { + self.visit_block(b); + } else { + self.writeln(";"); + } + } + } + } + + #[allow(clippy::too_many_lines)] + fn visit_expr(&mut self, expr: &'_ Expr) { + match &*expr.kind { + ExprKind::Array(exprs) => { + self.write("["); + if let Some((last, most)) = exprs.split_last() { + for e in most { + self.visit_expr(e); + self.write(", "); + } + self.visit_expr(last); + } + self.write("]"); + } + ExprKind::ArrayRepeat(item, size) => { + self.write("["); + self.visit_expr(item); + self.write(", size = "); + self.visit_expr(size); + self.write("]"); + } + ExprKind::Assign(lhs, rhs) => { + self.write("set "); + self.visit_expr(lhs); + self.write(" = "); + self.visit_expr(rhs); + } + ExprKind::AssignOp(op, lhs, rhs) => { + self.write("set "); + self.visit_expr(lhs); + self.write(" "); + let op_str = binop_as_str(op); + self.write(op_str); + self.write("= "); + self.visit_expr(rhs); + } + ExprKind::BinOp(op, lhs, rhs) => { + self.visit_expr(lhs); + self.write(" "); + let op_str = binop_as_str(op); + self.write(op_str); + self.write(" "); + self.visit_expr(rhs); + } + ExprKind::AssignUpdate(record, index, value) => { + self.write("set "); + self.visit_expr(record); + self.write(" w/= "); + self.visit_expr(index); + self.write(" <- "); + self.visit_expr(value); + } + ExprKind::Block(block) => self.visit_block(block), + ExprKind::Call(callee, arg) => { + self.visit_expr(callee); + self.visit_expr(arg); + } + ExprKind::Conjugate(within, apply) => { + self.write("within"); + self.visit_block(within); + self.write("apply"); + self.visit_block(apply); + } + ExprKind::Fail(msg) => { + self.write("fail "); + self.visit_expr(msg); + } + ExprKind::Field(record, name) => { + self.visit_expr(record); + self.write("::"); + self.visit_ident(name); + } + ExprKind::For(pat, iter, block) => { + self.write("for "); + self.visit_pat(pat); + self.write(" in "); + self.visit_expr(iter); + self.write(" "); + self.visit_block(block); + } + ExprKind::If(cond, body, otherwise) => { + self.write("if "); + self.visit_expr(cond); + self.write(" "); + self.visit_block(body); + for expr in otherwise { + if matches!(*expr.kind, ExprKind::If(..)) { + // visiting expr as if writes 'if' to make 'elif' + self.write(" el"); + } else { + self.write(" else "); + } + self.visit_expr(expr); + } + } + ExprKind::Index(array, index) => { + self.visit_expr(array); + self.write("["); + self.visit_expr(index); + self.write("]"); + } + ExprKind::Interpolate(components) => { + self.write("$\""); + for component in components.as_ref() { + match component { + StringComponent::Expr(expr) => { + self.write("{"); + self.visit_expr(expr.as_ref()); + self.write("}"); + } + StringComponent::Lit(lit) => { + self.write(lit); + } + } + } + self.write("\""); + } + ExprKind::Lambda(kind, pat, expr) => { + self.visit_pat(pat); + match kind { + CallableKind::Function => self.write(" -> "), + CallableKind::Operation => self.write(" => "), + } + self.visit_expr(expr); + } + ExprKind::Paren(expr) => { + self.write("("); + self.visit_expr(expr); + self.write(")"); + } + ExprKind::Return(expr) => { + self.write("return "); + self.visit_expr(expr); + } + ExprKind::UnOp(op, expr) => { + let op_str = unop_as_str(op); + if op == &UnOp::Unwrap { + self.visit_expr(expr); + self.write(op_str); + } else { + self.write(op_str); + self.visit_expr(expr); + } + } + ExprKind::Path(path) => self.visit_path(path), + ExprKind::Range(start, step, end) => { + // A range: `start..step..end`, `start..end`, `start...`, `...end`, or `...`. + match (start, step, end) { + (None, None, None) => { + self.write("..."); + } + (None, None, Some(end)) => { + self.write("..."); + self.visit_expr(end); + } + (None, Some(step), None) => { + self.write("..."); + self.visit_expr(step); + self.write("..."); + } + (None, Some(step), Some(end)) => { + self.write("..."); + self.visit_expr(step); + self.write(".."); + self.visit_expr(end); + } + (Some(start), None, None) => { + self.visit_expr(start); + self.write("..."); + } + (Some(start), None, Some(end)) => { + self.visit_expr(start); + self.write(".."); + self.visit_expr(end); + } + (Some(start), Some(step), None) => { + self.visit_expr(start); + self.write(".."); + self.visit_expr(step); + self.write("..."); + } + (Some(start), Some(step), Some(end)) => { + self.visit_expr(start); + self.write(".."); + self.visit_expr(step); + self.write(".."); + self.visit_expr(end); + } + } + } + ExprKind::Repeat(body, until, fixup) => { + self.write("repeat "); + self.visit_block(body); + self.write("until "); + self.visit_expr(until); + for fixup in fixup { + self.write(" fixup "); + self.visit_block(fixup); + } + } + ExprKind::TernOp(op, e1, e2, e3) => { + match op { + TernOp::Cond => { + // Conditional: `a ? b | c`. + self.visit_expr(e1); + self.write(" ? "); + self.visit_expr(e2); + self.write(" | "); + self.visit_expr(e3); + } + TernOp::Update => { + // Aggregate update: `a w/ b <- c`. + self.visit_expr(e1); + self.write(" w/ "); + self.visit_expr(e2); + self.write(" <- "); + self.visit_expr(e3); + } + } + } + ExprKind::Tuple(exprs) => { + self.write("("); + if let Some((last, most)) = exprs.split_last() { + for e in most { + self.visit_expr(e); + self.write(", "); + } + self.visit_expr(last); + self.ensure_trailing_comma_for_arity_one_tuples(most); + } + self.write(")"); + } + ExprKind::While(cond, block) => { + self.write("while "); + self.visit_expr(cond); + self.visit_block(block); + } + ExprKind::Lit(lit) => match lit.as_ref() { + Lit::BigInt(value) => { + self.write(value.to_string().as_str()); + self.write("L"); + } + Lit::Bool(value) => { + if *value { + self.write("true"); + } else { + self.write("false"); + } + } + Lit::Double(value) => { + let num_str = if value.fract() == 0.0 { + format!("{value}.") + } else { + format!("{value}") + }; + self.write(&num_str); + } + Lit::Int(value) => self.write(&value.to_string()), + Lit::Pauli(value) => match value { + Pauli::I => self.write("PauliI"), + Pauli::X => self.write("PauliX"), + Pauli::Y => self.write("PauliY"), + Pauli::Z => self.write("PauliZ"), + }, + Lit::Result(value) => match value { + ast::Result::One => self.write("One"), + ast::Result::Zero => self.write("Zero"), + }, + Lit::String(value) => { + self.write("\""); + self.write(value.as_ref()); + self.write("\""); + } + }, + ExprKind::Hole => { + self.write("_"); + } + ExprKind::Err => { + unreachable!(); + } + } + } + + fn visit_pat(&mut self, pat: &'_ Pat) { + match &*pat.kind { + PatKind::Bind(name, ty) => { + self.visit_ident(name); + + for t in ty { + self.write(": "); + self.visit_ty(t); + } + } + PatKind::Discard(ty) => { + self.write("_"); + for t in ty { + self.write(": "); + self.visit_ty(t); + } + } + PatKind::Elided => { + self.write("..."); + } + PatKind::Paren(pat) => { + self.write("("); + self.visit_pat(pat); + self.write(")"); + } + PatKind::Tuple(pats) => { + self.write("("); + if let Some((last, most)) = pats.split_last() { + for pat in most { + self.visit_pat(pat); + self.write(", "); + } + self.visit_pat(last); + self.ensure_trailing_comma_for_arity_one_tuples(most); + } + self.write(")"); + } + PatKind::Err => { + unreachable!(); + } + } + } + + fn visit_qubit_init(&mut self, init: &'_ QubitInit) { + match &*init.kind { + QubitInitKind::Array(len) => { + self.write("Qubit["); + self.visit_expr(len); + self.write("]"); + } + QubitInitKind::Paren(init) => self.visit_qubit_init(init), + QubitInitKind::Single => { + self.write("Qubit()"); + } + QubitInitKind::Tuple(inits) => { + self.write("("); + if let Some((last, most)) = inits.split_last() { + for init in most { + self.visit_qubit_init(init); + self.write(", "); + } + self.visit_qubit_init(last); + self.ensure_trailing_comma_for_arity_one_tuples(most); + } + self.write(")"); + } + QubitInitKind::Err => unreachable!(), + } + } + + fn visit_path(&mut self, path: &'_ Path) { + for ns in &path.namespace { + self.visit_ident(ns); + self.write("."); + } + self.visit_ident(&path.name); + } + + fn visit_ident(&mut self, id: &'_ Ident) { + self.write(&id.name); + } +} + +fn binop_as_str(op: &BinOp) -> &str { + match op { + BinOp::Add => "+", + BinOp::AndB => "&&&", + BinOp::AndL => "and", + BinOp::Div => "/", + BinOp::Eq => "==", + BinOp::Exp => "^", + BinOp::Gt => ">", + BinOp::Gte => ">=", + BinOp::Lt => "<", + BinOp::Lte => "<=", + BinOp::Mod => "%", + BinOp::Mul => "*", + BinOp::Neq => "!=", + BinOp::OrB => "|||", + BinOp::OrL => "or", + BinOp::Shl => "<<<", + BinOp::Shr => ">>>", + BinOp::Sub => "-", + BinOp::XorB => "^^^", + } +} + +fn unop_as_str(op: &UnOp) -> &str { + match op { + UnOp::Functor(functor) => match functor { + Functor::Adj => "Adjoint ", + Functor::Ctl => "Controlled ", + }, + UnOp::Neg => "-", + UnOp::NotB => "~~~", + UnOp::NotL => "not ", + UnOp::Pos => "+", + UnOp::Unwrap => "!", + } +} diff --git a/compiler/qsc_codegen/src/qsharp/spec_decls.rs b/compiler/qsc_codegen/src/qsharp/spec_decls.rs new file mode 100644 index 0000000000..26079647c2 --- /dev/null +++ b/compiler/qsc_codegen/src/qsharp/spec_decls.rs @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::too_many_lines)] +#![allow(clippy::needless_raw_string_hashes)] + +use expect_test::expect; +use indoc::indoc; + +use super::test_utils::check; + +#[test] +fn body_with_implicit_return() { + check( + indoc! {r#" + namespace A { + operation B() : Int { + let x = 5; + x + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Int { + let x = 5; + x + } + }"#]], + ); +} + +#[test] +fn attributes() { + check( + indoc! {r#" + namespace Sample { + @EntryPoint() + @Config(Unrestricted) + operation Entry() : Unit {} + }"#}, + None, + &expect![[r#" + namespace Sample { + @EntryPoint() + @Config(Unrestricted) + operation Entry() : Unit {} + }"#]], + ); +} + +#[test] +fn comments_are_omitted() { + check( + indoc! {r#" + // NS comment + namespace A { + // op comment here + operation B() : Unit { + // comment here + // another comment + } // trailing comment + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit {} + }"#]], + ); +} + +#[test] +fn visibility() { + check( + indoc! {r#" + // NS comment + namespace A { + // op comment here + internal operation B() : Unit { + // comment here + // another comment + } // trailing comment + }"#}, + None, + &expect![[r#" + namespace A { + internal operation B() : Unit {} + }"#]], + ); +} + +#[test] +fn callable_specs() { + check( + indoc! {r#" + namespace Sample { + @EntryPoint() + operation Entry() : Result { + use q = Qubit(); + // comment here + H(q); + // implicit return + M(q) + } + operation Op1(q: Qubit[]) : Unit is Ctl + Adj { + body ... { + Microsoft.Quantum.Intrinsic.H(q[0]); + } + adjoint invert; + controlled distribute; + controlled adjoint auto; + } + operation op2(q: Qubit) : Unit is Adj + Ctl { + body ... { + H(q); + } + adjoint self; + controlled auto; + controlled adjoint invert; + } + operation op3(q: Qubit) : Unit is Ctl + Adj { + body ... { + H(q); + } + adjoint auto; + controlled adjoint self; + } + operation op4() : Unit { + body intrinsic; + } + operation op5(q: Qubit) : Unit is Ctl { + body ... { + H(q); + } + controlled auto; + } + operation op6(q: Qubit) : Unit is Adj { + body ... { + H(q); + } + adjoint auto; + } + operation op7() : Unit is Adj * Adj { + body ... {} + } + operation op8() : Unit is (Adj) {} + operation op9(bar: () => Unit is Ctl) : Unit {} + operation op10(bar: () => Unit is Adj) : Unit {} + operation op11(bar: () => Unit is Adj + Ctl) : Unit {} + operation op12(b: Unit => Unit is Adj) : Unit {} + }"#}, + None, + &expect![[r#" + namespace Sample { + @EntryPoint() + operation Entry() : Result { + use q = Qubit(); + H(q); + M(q) + } + operation Op1(q : Qubit[]) : Unit is Ctl + Adj { + body ... { + Microsoft.Quantum.Intrinsic.H(q[0]); + } + adjoint invert; + controlled distribute; + controlled adjoint auto; + } + operation op2(q : Qubit) : Unit is Adj + Ctl { + body ... { + H(q); + } + adjoint self; + controlled auto; + controlled adjoint invert; + } + operation op3(q : Qubit) : Unit is Ctl + Adj { + body ... { + H(q); + } + adjoint auto; + controlled adjoint self; + } + operation op4() : Unit { + body intrinsic; + } + operation op5(q : Qubit) : Unit is Ctl { + body ... { + H(q); + } + controlled auto; + } + operation op6(q : Qubit) : Unit is Adj { + body ... { + H(q); + } + adjoint auto; + } + operation op7() : Unit is Adj * Adj { + body ... {} + } + operation op8() : Unit is (Adj) {} + operation op9(bar : () => Unit is Ctl) : Unit {} + operation op10(bar : () => Unit is Adj) : Unit {} + operation op11(bar : () => Unit is Adj + Ctl) : Unit {} + operation op12(b : Unit => Unit is Adj) : Unit {} + }"#]], + ); +} + +#[test] +fn callable_core_types() { + check( + indoc! {r#" + namespace A { + operation B() : Int { + let x = 5; + x + } + function C() : Int { + let x = 42; + x + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Int { + let x = 5; + x + } + function C() : Int { + let x = 42; + x + } + }"#]], + ); +} diff --git a/compiler/qsc_codegen/src/qsharp/test_utils.rs b/compiler/qsc_codegen/src/qsharp/test_utils.rs new file mode 100644 index 0000000000..2a8f8f2953 --- /dev/null +++ b/compiler/qsc_codegen/src/qsharp/test_utils.rs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::too_many_lines)] +#![allow(clippy::needless_raw_string_hashes)] + +use std::sync::Arc; + +use expect_test::Expect; +use qsc_ast::{ast::Package, mut_visit::MutVisitor}; +use qsc_data_structures::{language_features::LanguageFeatures, span::Span}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; +use qsc_hir::hir::PackageId; +use qsc_passes::{run_core_passes, run_default_passes, PackageType}; + +use crate::qsharp::write_package_string; + +pub(crate) fn check(program: &str, expr: Option<&str>, expect: &Expect) { + let (qsharp, src_ast_str) = compile_program(expr, program); + expect.assert_eq(&qsharp); + // Run the output against the compiler to ensure that input + // and output both generate the same qsharp. + let (round_trip_qsharp, gen_ast_str) = compile_program(expr, &qsharp); + expect.assert_eq(&round_trip_qsharp); + // we've validated the output, now validate the ASTs + // We may have generated the same Q#, but may have changed semantics + difference::assert_diff!(&src_ast_str, &gen_ast_str, "\n", 0); +} + +pub(crate) fn get_compilation(sources: Option) -> (PackageId, PackageStore) { + let mut core = compile::core(); + assert!(run_core_passes(&mut core).is_empty()); + let mut store = PackageStore::new(core); + let mut std = compile::std(&store, TargetCapabilityFlags::empty()); + assert!(run_default_passes( + store.core(), + &mut std, + PackageType::Lib, + TargetCapabilityFlags::empty() + ) + .is_empty()); + let std = store.insert(std); + + let mut unit = compile( + &store, + &[std], + sources.unwrap_or_default(), + TargetCapabilityFlags::all(), + LanguageFeatures::empty(), + ); + assert!(unit.errors.is_empty(), "{:?}", unit.errors); + assert!(run_default_passes( + store.core(), + &mut unit, + PackageType::Lib, + TargetCapabilityFlags::all() + ) + .is_empty()); + let package_id = store.insert(unit); + (package_id, store) +} + +pub(crate) fn compile_program(expr: Option<&str>, program: &str) -> (String, String) { + let expr_as_arc: Option> = expr.map(|s| Arc::from(s.to_string())); + let sources = SourceMap::new([("test".into(), program.into())], expr_as_arc); + + let (package_id, store) = get_compilation(Some(sources)); + let package = &store.get(package_id).expect("package must exist"); + + let despanned_ast = AstDespanner.despan(&package.ast.package); + let qsharp = write_package_string(&despanned_ast); + let ast = format!("{despanned_ast}"); + (qsharp, ast) +} + +struct AstDespanner; +impl AstDespanner { + fn despan(&mut self, package: &Package) -> Package { + let mut p = package.clone(); + self.visit_package(&mut p); + p + } +} + +impl qsc_ast::mut_visit::MutVisitor for AstDespanner { + fn visit_span(&mut self, span: &mut Span) { + span.hi = 0; + span.lo = 0; + } + fn visit_visibility(&mut self, vis: &mut qsc_ast::ast::Visibility) { + self.visit_span(&mut vis.span); + } +} diff --git a/compiler/qsc_codegen/src/qsharp/tests.rs b/compiler/qsc_codegen/src/qsharp/tests.rs new file mode 100644 index 0000000000..b103c6bffb --- /dev/null +++ b/compiler/qsc_codegen/src/qsharp/tests.rs @@ -0,0 +1,924 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::too_many_lines)] +#![allow(clippy::needless_raw_string_hashes)] + +use expect_test::expect; +use indoc::indoc; + +use super::test_utils::check; + +#[test] +fn simple_entry_program_is_valid() { + check( + indoc! {r#" + namespace Sample { + @EntryPoint() + operation Entry() : Result { + use q = Qubit(); + H(q); + M(q) + } + }namespace Sample {}"#}, + None, + &expect![[r#" + namespace Sample { + @EntryPoint() + operation Entry() : Result { + use q = Qubit(); + H(q); + M(q) + } + } + namespace Sample {}"#]], + ); +} + +#[test] +fn open() { + check( + indoc! {r#" + namespace Sample { + open Microsoft.Quantum.Intrinsic as sics; + + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic as intrin; + @EntryPoint() + operation Entry() : Unit { + } + }"#}, + None, + &expect![[r#" + namespace Sample { + open Microsoft.Quantum.Intrinsic as sics; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic as intrin; + @EntryPoint() + operation Entry() : Unit {} + }"#]], + ); +} + +#[test] +fn newtype() { + check( + indoc! {r#" + namespace Sample { + newtype A = (First : Int, (Second : Double, Third : Bool)); + newtype B = (First : Result, Second : BigInt); + newtype C = (Int, Bool); + newtype D = (First : Int, Second: C); + newtype E = (Real : Double, Imag : Double); + newtype F = (Real : Double, Imaginary : Double, Bool); + @EntryPoint() + operation Entry() : Unit { + } + }"#}, + None, + &expect![[r#" + namespace Sample { + newtype A = (First : Int, (Second : Double, Third : Bool)); + newtype B = (First : Result, Second : BigInt); + newtype C = (Int, Bool); + newtype D = (First : Int, Second : C); + newtype E = (Real : Double, Imag : Double); + newtype F = (Real : Double, Imaginary : Double, Bool); + @EntryPoint() + operation Entry() : Unit {} + }"#]], + ); +} + +#[test] +fn statements() { + check( + indoc! {r#" + namespace A { + @EntryPoint() + operation Entry() : Unit { + mutable x = 7; + let y = 5; + set x = y; + let z = [Zero, One]; + mutable w = z; + let mask = [false, size = 10]; + + for i in Length(mask)-2 .. -1 .. 0 { + let nbPair = mask + w/ i <- true + w/ i + 1 <- true; + } + } + function RichTrippleFor(func : Int[]) : Int[] { + mutable res = func; + for m in 0..(Length(func) - 1) { + mutable s = 1 <<< m >>> 2; + set s >>>= 2; + set s <<<= 2; + for i in 0..(2 * s)..Length(func) - 1 { + mutable k = i + s; + for j in i..i + s - 1 { + mutable t = res[j]; + set res w/= j <- res[j] + res[k]; + set res w/= k <- t - res[k]; + set k = k + 1; + } + } + } + return res; + } + }"#}, + None, + &expect![[r#" + namespace A { + @EntryPoint() + operation Entry() : Unit { + mutable x = 7; + let y = 5; + set x = y; + let z = [Zero, One]; + mutable w = z; + let mask = [false, size = 10]; + for i in Length(mask) - 2..-1..0 { + let nbPair = mask w/ i <- true w/ i + 1 <- true; + } + } + function RichTrippleFor(func : Int[]) : Int[] { + mutable res = func; + for m in 0..(Length(func) - 1) { + mutable s = 1 <<< m >>> 2; + set s >>>= 2; + set s <<<= 2; + for i in 0..(2 * s)..Length(func) - 1 { + mutable k = i + s; + for j in i..i + s - 1 { + mutable t = res[j]; + set res w/= j <- res[j] + res[k]; + set res w/= k <- t - res[k]; + set k = k + 1; + } + } + } + return res; + } + }"#]], + ); +} + +#[test] +fn qubits() { + check( + indoc! {r#" + namespace A { + operation B() : Unit { + use q = Qubit(); + borrow q = Qubit(); + use (q1, q2) = (Qubit(), Qubit()); + borrow (q1, q2) = (Qubit(), Qubit()); + use qubits = Qubit[2]; + borrow qubits = Qubit[2]; + let inputSize = 5; + use (control, target) = (Qubit[inputSize], Qubit[inputSize]); + borrow (control, target) = (Qubit[inputSize], Qubit[inputSize]); + use (q,) = (Qubit(),); + borrow (q,) = (Qubit(),); + use q = Qubit() { + X(q); + X(q); + } + borrow q = Qubit() { + X(q); + X(q); + } + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit { + use q = Qubit(); + borrow q = Qubit(); + use (q1, q2) = (Qubit(), Qubit()); + borrow (q1, q2) = (Qubit(), Qubit()); + use qubits = Qubit[2]; + borrow qubits = Qubit[2]; + let inputSize = 5; + use (control, target) = (Qubit[inputSize], Qubit[inputSize]); + borrow (control, target) = (Qubit[inputSize], Qubit[inputSize]); + use (q, ) = (Qubit(), ); + borrow (q, ) = (Qubit(), ); + use q = Qubit() { + X(q); + X(q); + } + borrow q = Qubit() { + X(q); + X(q); + } + } + }"#]], + ); +} + +#[test] +fn boolean_ops() { + check( + indoc! {r#" + namespace A { + operation B() : Unit { + let a = true and false or true and (false or true); + let b = not a; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit { + let a = true and false or true and (false or true); + let b = not a; + } + }"#]], + ); +} + +#[test] +fn unary_ops() { + check( + indoc! {r#" + namespace A { + newtype Pair = (Int, Int); + operation B() : Unit { + let a = -1; + let b = not false; + let c = +1; + let f = ~~~1; + let g = Pair(a, c); + let (h, i) = g!; + } + }"#}, + None, + &expect![[r#" + namespace A { + newtype Pair = (Int, Int); + operation B() : Unit { + let a = -1; + let b = not false; + let c = + 1; + let f = ~~~1; + let g = Pair(a, c); + let (h, i) = g!; + } + }"#]], + ); +} + +#[test] +fn binary_ops() { + check( + indoc! {r#" + namespace A { + operation B() : Unit { + let a = 1 + 2 - 3 * 4 / 5 % 6 ^ 7; + let b = (1 < 2); + let c = a <= 3 and a > 4 and a >= 5 and a == 6 or a != 7; + let d = 1 &&& 2 ||| 3 ^^^ 4 <<< 5 >>> 6; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit { + let a = 1 + 2 - 3 * 4 / 5 % 6^7; + let b = (1 < 2); + let c = a <= 3 and a > 4 and a >= 5 and a == 6 or a != 7; + let d = 1 &&& 2 ||| 3 ^^^ 4 <<< 5 >>> 6; + } + }"#]], + ); +} + +#[test] +fn assign_update() { + check( + indoc! {r#" + namespace A { + operation B() : Unit { + mutable a = 1; + set a += 1; + set a &&&= a; + set a /= a; + set a /= a; + set a ^= a; + set a %= a; + set a *= a; + set a |||= a; + set a <<<= a; + set a >>>= a; + set a ^^^= a; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit { + mutable a = 1; + set a += 1; + set a &&&= a; + set a /= a; + set a /= a; + set a ^= a; + set a %= a; + set a *= a; + set a |||= a; + set a <<<= a; + set a >>>= a; + set a ^^^= a; + } + }"#]], + ); +} + +#[test] +fn lambda_fns() { + check( + indoc! {r#" + namespace A { + open Microsoft.Quantum.Arrays; + operation B() : Unit { + let add = (x, y) -> x + y; + let intArray = [1, 2, 3, 4, 5]; + let sum = Fold(add, 0, intArray); + let incremented = Mapped(x -> x + 1, intArray); + + use control = Qubit(); + let cnotOnControl = q => CNOT(control, q); + use q = Qubit(); + cnotOnControl(q); + let incrementByOne = Add(_, 1); + let incrementByOneLambda = x -> Add(x, 1); + let five = incrementByOne(4); + let sumAndAddOne = AddMany(_, _, _, 1); + let sumAndAddOneLambda = (a, b, c) -> AddMany(a, b, c, 1); + let intArray = [1, 2, 3, 4, 5]; + let incremented = Mapped(Add(_, 1), intArray); + } + function Add(x : Int, y : Int) : Int { + return x + y; + } + function AddMany(a : Int, b : Int, c : Int, d : Int) : Int { + return a + b + c + d; + } + }"#}, + None, + &expect![[r#" + namespace A { + open Microsoft.Quantum.Arrays; + operation B() : Unit { + let add = (x, y) -> x + y; + let intArray = [1, 2, 3, 4, 5]; + let sum = Fold(add, 0, intArray); + let incremented = Mapped(x -> x + 1, intArray); + use control = Qubit(); + let cnotOnControl = q => CNOT(control, q); + use q = Qubit(); + cnotOnControl(q); + let incrementByOne = Add(_, 1); + let incrementByOneLambda = x -> Add(x, 1); + let five = incrementByOne(4); + let sumAndAddOne = AddMany(_, _, _, 1); + let sumAndAddOneLambda = (a, b, c) -> AddMany(a, b, c, 1); + let intArray = [1, 2, 3, 4, 5]; + let incremented = Mapped(Add(_, 1), intArray); + } + function Add(x : Int, y : Int) : Int { + return x + y; + } + function AddMany(a : Int, b : Int, c : Int, d : Int) : Int { + return a + b + c + d; + } + }"#]], + ); +} + +#[test] +fn ranges() { + check( + indoc! {r#" + namespace A { + open Microsoft.Quantum.Arrays; + operation B() : Unit { + let range = 1..3; + let range = 2..2..5; + let range = 2..2..6; + let range = 6..-2..2; + let range = 2..-2..2; + let range = 2..1; + mutable array = []; + for i in 0..10 { + set array += [i^2]; + } + let newArray = array[0..2..10]; + let newArray = array[...4]; + let newArray = array[5...]; + let newArray = array[2..3...]; + let newArray = array[...3..7]; + let newArray = array[...]; + let newArray = array[...-3...]; + } + }"#}, + None, + &expect![[r#" + namespace A { + open Microsoft.Quantum.Arrays; + operation B() : Unit { + let range = 1..3; + let range = 2..2..5; + let range = 2..2..6; + let range = 6..-2..2; + let range = 2..-2..2; + let range = 2..1; + mutable array = []; + for i in 0..10 { + set array += [i^2]; + } + let newArray = array[0..2..10]; + let newArray = array[...4]; + let newArray = array[5...]; + let newArray = array[2..3...]; + let newArray = array[...3..7]; + let newArray = array[...]; + let newArray = array[...-3...]; + } + }"#]], + ); +} + +#[test] +fn unary_functors() { + check( + indoc! {r#" + namespace A { + operation B() : Unit { + let v = q => H(q); + use qubit = Qubit(); + Adjoint v(qubit); + Controlled Adjoint v([qubit], qubit); + Adjoint Controlled v([qubit], qubit); + Controlled Controlled Adjoint v([qubit], ([qubit], qubit)); + Controlled Adjoint Controlled v([qubit], ([qubit], qubit)); + Adjoint Controlled Controlled v([qubit], ([qubit], qubit)); + } + }"#}, + None, + &expect![[r#" + namespace A { + operation B() : Unit { + let v = q => H(q); + use qubit = Qubit(); + Adjoint v(qubit); + Controlled Adjoint v([qubit], qubit); + Adjoint Controlled v([qubit], qubit); + Controlled Controlled Adjoint v([qubit], ([qubit], qubit)); + Controlled Adjoint Controlled v([qubit], ([qubit], qubit)); + Adjoint Controlled Controlled v([qubit], ([qubit], qubit)); + } + }"#]], + ); +} + +#[test] +fn field_access_and_string_interning() { + check( + indoc! {r#" + namespace A { + open Microsoft.Quantum.Math; + function ComplexAsString(x : Complex) : String { + if x::Imag < 0.0 { + $"{x::Real} - {AbsD(x::Imag)}i" + } else { + $"{x::Real} + {x::Imag}i" + } + } + }"#}, + None, + &expect![[r#" + namespace A { + open Microsoft.Quantum.Math; + function ComplexAsString(x : Complex) : String { + if x::Imag < 0. { + $"{x::Real} - {AbsD(x::Imag)}i" + } else { + $"{x::Real} + {x::Imag}i" + } + } + }"#]], + ); +} + +#[test] +fn if_exprs() { + check( + indoc! {r#" + namespace A { + function A() : Unit { + mutable x = 0; + // if + if true or false { + set x = 1; + } + // if else + if true and false { + set x = 2; + } else { + set x = 3; + } + // if elif + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } + // if elif else + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } else { + set x = 6; + } + // if elif elif else + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } elif true or false { + set x = 5; + } else { + set x = 6; + } + } + }"#}, + None, + &expect![[r#" + namespace A { + function A() : Unit { + mutable x = 0; + if true or false { + set x = 1; + } + if true and false { + set x = 2; + } else { + set x = 3; + } + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } else { + set x = 6; + } + if true and false { + set x = 4; + } elif true or false { + set x = 5; + } elif true or false { + set x = 5; + } else { + set x = 6; + } + } + }"#]], + ); +} + +#[test] +fn copy_update_range_indices() { + check( + indoc! {r#" + namespace A { + operation A() : Result[] { + let mask = [false, size = 6]; + for i in Length(mask) - 2 ..-1.. 0 { + let nbPair = mask w/ i... <- [true, true]; + Message($"{nbPair}"); + } + return []; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Result[] { + let mask = [false, size = 6]; + for i in Length(mask) - 2..-1..0 { + let nbPair = mask w/ i... <- [true, true]; + Message($"{nbPair}"); + } + return []; + } + }"#]], + ); +} + +#[test] +fn for_loops() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + // For loop over `Range` + for i in 0..5 { + for j in 0..4 { + for k in 0..3 { + let x = i * j * k; + } + } + } + // For loop over `Array` + for element in [10, 11, 12] { + let x = 7 * element; + } + // For loop over array slice + let array = [1.0, 2.0, 3.0, 4.0]; + for element in array[2...] { + let x = 2.0 * element; + } + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + for i in 0..5 { + for j in 0..4 { + for k in 0..3 { + let x = i * j * k; + } + } + } + for element in [10, 11, 12] { + let x = 7 * element; + } + let array = [1., 2., 3., 4.]; + for element in array[2...] { + let x = 2. * element; + } + } + }"#]], + ); +} + +#[test] +fn while_loops() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + mutable x = 0; + while x < 30 { + mutable y = 0; + while y < 3 { + mutable z = 0; + while z < 1 { + set z += 1; + set x += 1; + } + set y += 1; + } + } + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + mutable x = 0; + while x < 30 { + mutable y = 0; + while y < 3 { + mutable z = 0; + while z < 1 { + set z += 1; + set x += 1; + } + set y += 1; + } + } + } + }"#]], + ); +} + +#[test] +fn repeat_loops() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + mutable x = 0; + repeat { + set x += 1; + } until x > 3; + use qubit = Qubit(); + repeat { + H(qubit); + } until M(qubit) == Zero + fixup { + Reset(qubit); + } + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + mutable x = 0; + repeat { + set x += 1; + } until x > 3; + use qubit = Qubit(); + repeat { + H(qubit); + } until M(qubit) == Zero + fixup { + Reset(qubit); + } + } + }"#]], + ); +} + +#[test] +fn ternary() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + let fahrenheit = 40; + let absoluteValue = fahrenheit > 0 ? fahrenheit | fahrenheit * -1; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + let fahrenheit = 40; + let absoluteValue = fahrenheit > 0 ? fahrenheit | fahrenheit * -1; + } + }"#]], + ); +} + +#[test] +fn within_apply() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + use qubit = Qubit(); + within { + H(qubit); + } apply { + X(qubit); + } + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + use qubit = Qubit(); + within { + H(qubit); + } apply { + X(qubit); + } + } + }"#]], + ); +} + +#[test] +fn type_decls() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + newtype Point3d = (X : Double, Y : Double, Z : Double); + newtype DoubleInt = (Double, ItemName : Int); + newtype Nested = (Double, (ItemName : Int, String)); + let point = Point3d(1.0, 2.0, 3.0); + let x : Double = point::X; + let (x, _, _) = point!; + let unwrappedTuple = point!; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + newtype Point3d = (X : Double, Y : Double, Z : Double); + newtype DoubleInt = (Double, ItemName : Int); + newtype Nested = (Double, (ItemName : Int, String)); + let point = Point3d(1., 2., 3.); + let x : Double = point::X; + let (x, _, _) = point!; + let unwrappedTuple = point!; + } + }"#]], + ); +} + +#[test] +fn pauli() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + use q = Qubit(); + mutable pauliDimension = PauliX; + // Measuring along a dimension returns a `Result`: + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliY; + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliZ; + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliI; + let result = Measure([pauliDimension], [q]); + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + use q = Qubit(); + mutable pauliDimension = PauliX; + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliY; + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliZ; + let result = Measure([pauliDimension], [q]); + set pauliDimension = PauliI; + let result = Measure([pauliDimension], [q]); + } + }"#]], + ); +} + +#[test] +fn bases_and_readable_values() { + check( + indoc! {r#" + namespace A { + operation A() : Unit { + let foo = 0x42; + let foo = 0o42; + let foo = 42; + let foo = 0b101010; + let integer : Int = 42; + let unit : Unit = (); + let binaryBigInt : BigInt = 0b101010L; + let octalBigInt = 0o52L; + let decimalBigInt = 42L; + let hexadecimalBigInt = 0x2aL; + let foo : BigInt = 2L^74; + let foo = foo + 1L; + let foo = foo % 2L; + let foo = foo^2; + let foo = 1e-9; + let foo = 1E-15; + let foo = 1000_0000; + } + }"#}, + None, + &expect![[r#" + namespace A { + operation A() : Unit { + let foo = 66; + let foo = 34; + let foo = 42; + let foo = 42; + let integer : Int = 42; + let unit : Unit = (); + let binaryBigInt : BigInt = 42L; + let octalBigInt = 42L; + let decimalBigInt = 42L; + let hexadecimalBigInt = 42L; + let foo : BigInt = 2L^74; + let foo = foo + 1L; + let foo = foo % 2L; + let foo = foo^2; + let foo = 0.000000001; + let foo = 0.000000000000001; + let foo = 10000000; + } + }"#]], + ); +} diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs index 2a19ce79ff..e03b76b46b 100644 --- a/compiler/qsc_doc_gen/src/generate_docs.rs +++ b/compiler/qsc_doc_gen/src/generate_docs.rs @@ -7,7 +7,7 @@ mod tests; use crate::display::{increase_header_level, parse_doc_for_summary}; use crate::display::{CodeDisplay, Lookup}; use qsc_ast::ast; -use qsc_frontend::compile::{self, PackageStore, RuntimeCapabilityFlags}; +use qsc_frontend::compile::{self, PackageStore, TargetCapabilityFlags}; use qsc_frontend::resolve; use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Visibility}; use qsc_hir::{hir, ty}; @@ -29,7 +29,7 @@ impl Compilation { /// Creates a new `Compilation` by compiling sources. pub(crate) fn new() -> Self { let mut package_store = PackageStore::new(compile::core()); - package_store.insert(compile::std(&package_store, RuntimeCapabilityFlags::all())); + package_store.insert(compile::std(&package_store, TargetCapabilityFlags::all())); Self { package_store } } diff --git a/compiler/qsc_doc_gen/src/generate_docs/tests.rs b/compiler/qsc_doc_gen/src/generate_docs/tests.rs index 9308e0a678..b24f920e27 100644 --- a/compiler/qsc_doc_gen/src/generate_docs/tests.rs +++ b/compiler/qsc_doc_gen/src/generate_docs/tests.rs @@ -24,7 +24,7 @@ fn docs_generation() { qsharp.kind: function qsharp.namespace: Microsoft.Quantum.Core qsharp.name: Length - qsharp.summary: "Returns the number of elements in an array." + qsharp.summary: "Returns the number of elements in the input array `a`." --- # Length function @@ -36,14 +36,19 @@ fn docs_generation() { ``` ## Summary - Returns the number of elements in an array. + Returns the number of elements in the input array `a`. ## Input ### a Input array. ## Output - The total count of elements in an array. + The total number of elements in the input array `a`. + + ## Example + ```qsharp + Message($"{ Length([0, 0, 0]) }"); // Prints 3 + ``` "#]] .assert_eq(full_contents.as_str()); } diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 8b3f458214..30a3184ad5 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -18,7 +18,7 @@ use indoc::indoc; use num_bigint::BigInt; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::map_hir_package_to_fir; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; @@ -151,13 +151,13 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result Result Result, - value: Value, - span: Span, +pub struct Variable { + pub name: Rc, + pub value: Value, + pub span: Span, } #[derive(Debug, Clone)] @@ -359,6 +359,14 @@ impl Env { } } + pub fn bind_variable_in_top_frame(&mut self, local_var_id: LocalVarId, var: Variable) { + let Some(scope) = self.0.last_mut() else { + panic!("no frames in scope"); + }; + + scope.bindings.insert(local_var_id, var); + } + #[must_use] pub fn get_variables_in_top_frame(&self) -> Vec { if let Some(scope) = self.0.last() { diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index 1fc6f67ae0..1d74c7ba37 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3,7 +3,6 @@ #![allow(clippy::needless_raw_string_hashes)] -use core::panic; use std::rc::Rc; use crate::{ @@ -18,7 +17,7 @@ use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir::{self, ExecGraphNode, StmtId}; use qsc_fir::fir::{PackageId, PackageStoreLookup}; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::map_hir_package_to_fir; use qsc_passes::{run_core_passes, run_default_passes, PackageType}; @@ -50,13 +49,13 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { let core_fir = fir_lowerer.lower_package(&core.package); let mut store = PackageStore::new(core); - let mut std = compile::std(&store, RuntimeCapabilityFlags::all()); + let mut std = compile::std(&store, TargetCapabilityFlags::all()); assert!(std.errors.is_empty()); assert!(run_default_passes( store.core(), &mut std, PackageType::Lib, - RuntimeCapabilityFlags::all() + TargetCapabilityFlags::all() ) .is_empty()); let std_fir = fir_lowerer.lower_package(&std.package); @@ -67,7 +66,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { &store, &[std_id], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); @@ -75,7 +74,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) { store.core(), &mut unit, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), ); assert!(pass_errors.is_empty(), "{pass_errors:?}"); let unit_fir = fir_lowerer.lower_package(&unit.package); @@ -116,13 +115,13 @@ fn check_partial_eval_stmt( let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package); let mut store = PackageStore::new(core); - let mut std = compile::std(&store, RuntimeCapabilityFlags::all()); + let mut std = compile::std(&store, TargetCapabilityFlags::all()); assert!(std.errors.is_empty()); assert!(run_default_passes( store.core(), &mut std, PackageType::Lib, - RuntimeCapabilityFlags::all() + TargetCapabilityFlags::all() ) .is_empty()); let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package); @@ -133,7 +132,7 @@ fn check_partial_eval_stmt( &store, &[std_id], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); @@ -141,7 +140,7 @@ fn check_partial_eval_stmt( store.core(), &mut unit, PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), ); assert!(pass_errors.is_empty(), "{pass_errors:?}"); let unit_fir = qsc_lowerer::Lowerer::new().lower_package(&unit.package); @@ -173,7 +172,7 @@ fn check_partial_eval_stmt( &mut GenericReceiver::new(&mut out), ) { Ok(_) => {} - Err(err) => panic!("Unexpected error: {:?}", err), + Err(err) => panic!("Unexpected error: {err:?}"), } } @@ -440,16 +439,16 @@ fn block_qubit_use_array_invalid_count_expr() { 0, ), span: Span { - lo: 1566, - hi: 1623, + lo: 2034, + hi: 2091, }, }, ), [ Frame { span: Span { - lo: 1566, - hi: 1623, + lo: 2034, + hi: 2091, }, id: StoreItemId { package: PackageId( diff --git a/compiler/qsc_eval/src/val.rs b/compiler/qsc_eval/src/val.rs index 34ea518cb4..e4f0908ed8 100644 --- a/compiler/qsc_eval/src/val.rs +++ b/compiler/qsc_eval/src/val.rs @@ -326,6 +326,17 @@ impl Value { v } + /// Convert the [Value] into a var + /// # Panics + /// This will panic if the [Value] is not a [`Value::Var`]. + #[must_use] + pub fn unwrap_var(self) -> Var { + let Value::Var(v) = self else { + panic!("value should be Var, got {}", self.type_name()); + }; + v + } + #[must_use] pub fn type_name(&self) -> &'static str { match self { diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs index 36e50978cb..0f89a4f547 100644 --- a/compiler/qsc_frontend/src/compile.rs +++ b/compiler/qsc_frontend/src/compile.rs @@ -39,61 +39,34 @@ use thiserror::Error; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct RuntimeCapabilityFlags: u32 { - const ForwardBranching = 0b0000_0001; + pub struct TargetCapabilityFlags: u32 { + const Adaptive = 0b0000_0001; const IntegerComputations = 0b0000_0010; const FloatingPointComputations = 0b0000_0100; const BackwardsBranching = 0b0000_1000; const HigherLevelConstructs = 0b0001_0000; + const QubitReset = 0b0010_0000; } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ConfigAttr { - Adaptive, - Base, - Unrestricted, -} - -impl ConfigAttr { - #[must_use] - pub fn to_str(&self) -> &'static str { - match self { - Self::Adaptive => "Adaptive", - Self::Base => "Base", - Self::Unrestricted => "Unrestricted", - } - } - - #[must_use] - pub fn is_target_str(s: &str) -> bool { - Self::from_str(s).is_ok() - } -} - -impl FromStr for ConfigAttr { +impl FromStr for TargetCapabilityFlags { type Err = (); fn from_str(s: &str) -> Result { match s { - "Adaptive" => Ok(ConfigAttr::Adaptive), - "Base" => Ok(ConfigAttr::Base), - "Unrestricted" => Ok(ConfigAttr::Unrestricted), + "Base" => Ok(TargetCapabilityFlags::empty()), + "Adaptive" => Ok(TargetCapabilityFlags::Adaptive), + "IntegerComputations" => Ok(TargetCapabilityFlags::IntegerComputations), + "FloatingPointComputations" => Ok(TargetCapabilityFlags::FloatingPointComputations), + "BackwardsBranching" => Ok(TargetCapabilityFlags::BackwardsBranching), + "HigherLevelConstructs" => Ok(TargetCapabilityFlags::HigherLevelConstructs), + "QubitReset" => Ok(TargetCapabilityFlags::QubitReset), + "Unrestricted" => Ok(TargetCapabilityFlags::all()), _ => Err(()), } } } -impl From for RuntimeCapabilityFlags { - fn from(value: ConfigAttr) -> Self { - match value { - ConfigAttr::Unrestricted => Self::all(), - ConfigAttr::Base => Self::empty(), - ConfigAttr::Adaptive => Self::ForwardBranching, - } - } -} - #[derive(Debug, Default)] pub struct CompileUnit { pub package: hir::Package, @@ -341,15 +314,35 @@ impl MutVisitor for Offsetter { } } +#[must_use] pub fn compile( store: &PackageStore, dependencies: &[PackageId], sources: SourceMap, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> CompileUnit { - let (mut ast_package, parse_errors) = parse_all(&sources, language_features); + let (ast_package, parse_errors) = parse_all(&sources, language_features); + + compile_ast( + store, + dependencies, + ast_package, + sources, + capabilities, + parse_errors, + ) +} +#[allow(clippy::module_name_repetitions)] +pub fn compile_ast( + store: &PackageStore, + dependencies: &[PackageId], + mut ast_package: ast::Package, + sources: SourceMap, + capabilities: TargetCapabilityFlags, + parse_errors: Vec, +) -> CompileUnit { let mut cond_compile = preprocess::Conditional::new(capabilities); cond_compile.visit_package(&mut ast_package); let dropped_names = cond_compile.into_names(); @@ -420,7 +413,7 @@ pub fn core() -> CompileUnit { &store, &[], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ); assert_no_errors(&unit.sources, &mut unit.errors); @@ -433,7 +426,7 @@ pub fn core() -> CompileUnit { /// /// Panics if the standard library does not compile without errors. #[must_use] -pub fn std(store: &PackageStore, capabilities: RuntimeCapabilityFlags) -> CompileUnit { +pub fn std(store: &PackageStore, capabilities: TargetCapabilityFlags) -> CompileUnit { let std: Vec<(SourceName, SourceContents)> = library::STD_LIB .iter() .map(|(name, contents)| ((*name).into(), (*contents).into())) diff --git a/compiler/qsc_frontend/src/compile/preprocess.rs b/compiler/qsc_frontend/src/compile/preprocess.rs index 8d8228bb62..081dadf8a2 100644 --- a/compiler/qsc_frontend/src/compile/preprocess.rs +++ b/compiler/qsc_frontend/src/compile/preprocess.rs @@ -9,7 +9,10 @@ use qsc_ast::{ use qsc_hir::hir; use std::rc::Rc; -use super::{ConfigAttr, RuntimeCapabilityFlags}; +use super::TargetCapabilityFlags; + +#[cfg(test)] +mod tests; #[derive(PartialEq, Hash, Clone, Debug)] pub struct TrackedName { @@ -18,13 +21,13 @@ pub struct TrackedName { } pub(crate) struct Conditional { - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, dropped_names: Vec, included_names: Vec, } impl Conditional { - pub(crate) fn new(capabilities: RuntimeCapabilityFlags) -> Self { + pub(crate) fn new(capabilities: TargetCapabilityFlags) -> Self { Self { capabilities, dropped_names: Vec::new(), @@ -118,34 +121,39 @@ impl MutVisitor for Conditional { } } -fn matches_config(attrs: &[Box], capabilities: RuntimeCapabilityFlags) -> bool { +fn matches_config(attrs: &[Box], capabilities: TargetCapabilityFlags) -> bool { + let attrs: Vec<_> = attrs + .iter() + .filter(|attr| hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::Config)) + .collect(); + if attrs.is_empty() { return true; } - attrs.iter().any(|attr| { - if hir::Attr::from_str(attr.name.name.as_ref()) == Ok(hir::Attr::Config) { - if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() { - match inner.kind.as_ref() { - // If there is no config attribute, then we assume that the item matches - // the target. We can't do membership tests on the capabilities because - // Base is not a subset of any capabilities, it is a lack of capabilities. - ExprKind::Path(path) => match ConfigAttr::from_str(path.name.name.as_ref()) { - Ok(ConfigAttr::Unrestricted) => capabilities.is_all(), - Ok(ConfigAttr::Base) => capabilities.is_empty(), - Ok(ConfigAttr::Adaptive) => { - capabilities == RuntimeCapabilityFlags::ForwardBranching - } - _ => true, - }, - _ => true, // Unknown config attribute, so we assume it matches + let mut found_capabilities = TargetCapabilityFlags::empty(); + + for attr in attrs { + if let ExprKind::Paren(inner) = attr.arg.kind.as_ref() { + match inner.kind.as_ref() { + ExprKind::Path(path) => { + if let Ok(capability) = TargetCapabilityFlags::from_str(path.name.name.as_ref()) + { + found_capabilities |= capability; + } else { + return true; // Unknown capability, so we assume it matches + } } - } else { - // Something other than a parenthesized expression, so we assume it matches - true + _ => return true, // Unknown config attribute, so we assume it matches } } else { - // Unknown attribute, so we assume it matches - true + // Something other than a parenthesized expression, so we assume it matches + return true; } - }) + } + if found_capabilities == TargetCapabilityFlags::empty() { + // There was at least one config attribute, but it was None + // Therefore, we only match if there are no capabilities + return capabilities == TargetCapabilityFlags::empty(); + } + capabilities.contains(found_capabilities) } diff --git a/compiler/qsc_frontend/src/compile/preprocess/tests.rs b/compiler/qsc_frontend/src/compile/preprocess/tests.rs new file mode 100644 index 0000000000..8feb47d0b8 --- /dev/null +++ b/compiler/qsc_frontend/src/compile/preprocess/tests.rs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use qsc_ast::ast::{Attr, Expr, ExprKind, Ident, NodeId, Path}; +use qsc_data_structures::span::Span; + +use crate::compile::{preprocess::matches_config, TargetCapabilityFlags}; + +fn named_attr(name: &str) -> Attr { + Attr { + name: Box::new(Ident { + name: name.into(), + span: Span::default(), + id: NodeId::default(), + }), + arg: Box::new(Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Tuple(Box::new([]))), + }), + span: Span::default(), + id: NodeId::default(), + } +} + +fn name_value_attr(name: &str, value: &str) -> Attr { + Attr { + name: Box::new(Ident { + name: name.into(), + span: Span::default(), + id: NodeId::default(), + }), + arg: Box::new(Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Paren(Box::new(Expr { + id: NodeId::default(), + span: Span::default(), + kind: Box::new(ExprKind::Path(Box::new(Path { + id: NodeId::default(), + span: Span::default(), + namespace: None, + name: Box::new(Ident { + name: value.into(), + span: Span::default(), + id: NodeId::default(), + }), + }))), + }))), + }), + span: Span::default(), + id: NodeId::default(), + } +} + +#[test] +fn no_attrs_matches() { + assert!(matches_config(&[], TargetCapabilityFlags::empty())); +} + +#[test] +fn unknown_attrs_matches() { + assert!(matches_config( + &[Box::new(named_attr("unknown"))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn none_attrs_matches_empty() { + assert!(matches_config( + &[Box::new(name_value_attr("Config", "Base"))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn none_attrs_does_not_match_all() { + assert!(!matches_config( + &[Box::new(name_value_attr("Config", "Base"))], + TargetCapabilityFlags::all() + )); +} + +#[test] +fn none_attrs_does_not_match_adaptive() { + assert!(!matches_config( + &[Box::new(name_value_attr("Config", "Base"))], + TargetCapabilityFlags::Adaptive + )); +} + +#[test] +fn adaptive_attrs_does_not_match_empty() { + assert!(!matches_config( + &[Box::new(name_value_attr("Config", "Adaptive"))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn integercomputations_attrs_does_not_match_empty() { + assert!(!matches_config( + &[Box::new(name_value_attr("Config", "IntegerComputations"))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn floatingpointcomputations_attrs_does_not_match_empty() { + assert!(!matches_config( + &[Box::new(name_value_attr( + "Config", + "FloatingPointComputations" + ))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn unrestricted_attrs_does_not_match_empty() { + assert!(!matches_config( + &[Box::new(name_value_attr("Config", "Unrestricted"))], + TargetCapabilityFlags::empty() + )); +} + +#[test] +fn unrestricted_attrs_matches_all() { + assert!(matches_config( + &[Box::new(name_value_attr("Config", "Unrestricted"))], + TargetCapabilityFlags::all() + )); +} diff --git a/compiler/qsc_frontend/src/compile/tests.rs b/compiler/qsc_frontend/src/compile/tests.rs index b5f86a937d..32c611a476 100644 --- a/compiler/qsc_frontend/src/compile/tests.rs +++ b/compiler/qsc_frontend/src/compile/tests.rs @@ -3,7 +3,7 @@ #![allow(clippy::needless_raw_string_hashes)] -use crate::compile::RuntimeCapabilityFlags; +use crate::compile::TargetCapabilityFlags; use super::{compile, CompileUnit, Error, PackageStore, SourceMap}; use expect_test::expect; @@ -58,7 +58,7 @@ fn default_compile(sources: SourceMap) -> CompileUnit { &PackageStore::new(super::core()), &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) } @@ -440,7 +440,7 @@ fn package_dependency() { &store, &[], sources1, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit1.errors.is_empty(), "{:#?}", unit1.errors); @@ -464,7 +464,7 @@ fn package_dependency() { &store, &[package1], sources2, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit2.errors.is_empty(), "{:#?}", unit2.errors); @@ -513,7 +513,7 @@ fn package_dependency_internal_error() { &store, &[], sources1, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit1.errors.is_empty(), "{:#?}", unit1.errors); @@ -537,7 +537,7 @@ fn package_dependency_internal_error() { &store, &[package1], sources2, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); @@ -593,7 +593,7 @@ fn package_dependency_udt() { &store, &[], sources1, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit1.errors.is_empty(), "{:#?}", unit1.errors); @@ -617,7 +617,7 @@ fn package_dependency_udt() { &store, &[package1], sources2, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit2.errors.is_empty(), "{:#?}", unit2.errors); @@ -668,7 +668,7 @@ fn package_dependency_nested_udt() { &store, &[], sources1, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit1.errors.is_empty(), "{:#?}", unit1.errors); @@ -697,7 +697,7 @@ fn package_dependency_nested_udt() { &store, &[package1], sources2, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit2.errors.is_empty(), "{:#?}", unit2.errors); @@ -754,7 +754,7 @@ fn package_dependency_nested_udt() { #[test] fn std_dependency() { let mut store = PackageStore::new(super::core()); - let std = store.insert(super::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(super::std(&store, TargetCapabilityFlags::all())); let sources = SourceMap::new( [( "test".into(), @@ -777,7 +777,7 @@ fn std_dependency() { &store, &[std], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:#?}", unit.errors); @@ -786,7 +786,7 @@ fn std_dependency() { #[test] fn std_dependency_base_profile() { let mut store = PackageStore::new(super::core()); - let std = store.insert(super::std(&store, RuntimeCapabilityFlags::empty())); + let std = store.insert(super::std(&store, TargetCapabilityFlags::empty())); let sources = SourceMap::new( [( "test".into(), @@ -809,7 +809,7 @@ fn std_dependency_base_profile() { &store, &[std], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:#?}", unit.errors); @@ -818,7 +818,7 @@ fn std_dependency_base_profile() { #[test] fn introduce_prelude_ambiguity() { let mut store = PackageStore::new(super::core()); - let std = store.insert(super::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(super::std(&store, TargetCapabilityFlags::all())); let sources = SourceMap::new( [( "test".into(), @@ -837,7 +837,7 @@ fn introduce_prelude_ambiguity() { &store, &[std], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let errors: Vec = unit.errors; @@ -926,7 +926,7 @@ fn unimplemented_call_from_dependency_produces_error() { &store, &[], lib_sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(lib.errors.is_empty(), "{:#?}", lib.errors); @@ -951,7 +951,7 @@ fn unimplemented_call_from_dependency_produces_error() { &store, &[lib], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); expect![[r#" @@ -1063,7 +1063,7 @@ fn unimplemented_attribute_avoids_ambiguous_error_with_duplicate_names_in_scope( &store, &[], lib_sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(lib.errors.is_empty(), "{:#?}", lib.errors); @@ -1092,7 +1092,7 @@ fn unimplemented_attribute_avoids_ambiguous_error_with_duplicate_names_in_scope( &store, &[lib], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); expect![[r#" @@ -1121,7 +1121,7 @@ fn duplicate_intrinsic_from_dependency() { &store, &[], lib_sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(lib.errors.is_empty(), "{:#?}", lib.errors); @@ -1144,7 +1144,7 @@ fn duplicate_intrinsic_from_dependency() { &store, &[lib], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); expect![[r#" @@ -1168,7 +1168,7 @@ fn duplicate_intrinsic_from_dependency() { #[test] fn reject_use_qubit_block_syntax_if_preview_feature_is_on() { let mut store = PackageStore::new(super::core()); - let std = store.insert(super::std(&store, RuntimeCapabilityFlags::empty())); + let std = store.insert(super::std(&store, TargetCapabilityFlags::empty())); let sources = SourceMap::new( [( "test".into(), @@ -1182,7 +1182,7 @@ fn reject_use_qubit_block_syntax_if_preview_feature_is_on() { // we have the v2 preview syntax feature enabled X(q); }; - + } } "} @@ -1195,7 +1195,7 @@ fn reject_use_qubit_block_syntax_if_preview_feature_is_on() { &store, &[std], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::V2PreviewSyntax, ); expect![[r#" @@ -1224,7 +1224,7 @@ fn reject_use_qubit_block_syntax_if_preview_feature_is_on() { #[test] fn accept_use_qubit_block_syntax_if_preview_feature_is_off() { let mut store = PackageStore::new(super::core()); - let std = store.insert(super::std(&store, RuntimeCapabilityFlags::empty())); + let std = store.insert(super::std(&store, TargetCapabilityFlags::empty())); let sources = SourceMap::new( [( "test".into(), @@ -1250,7 +1250,7 @@ fn accept_use_qubit_block_syntax_if_preview_feature_is_off() { &store, &[std], sources, - RuntimeCapabilityFlags::empty(), + TargetCapabilityFlags::empty(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:#?}", unit.errors); diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs index 3d435dc7dc..7cd4eb6282 100644 --- a/compiler/qsc_frontend/src/incremental.rs +++ b/compiler/qsc_frontend/src/incremental.rs @@ -6,8 +6,8 @@ mod tests; use crate::{ compile::{ - self, preprocess, AstPackage, CompileUnit, Offsetter, PackageStore, RuntimeCapabilityFlags, - SourceMap, + self, preprocess, AstPackage, CompileUnit, Offsetter, PackageStore, SourceMap, + TargetCapabilityFlags, }, error::WithSource, lower::Lowerer, @@ -38,7 +38,7 @@ pub struct Compiler { resolver: Resolver, checker: Checker, lowerer: Lowerer, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, } @@ -58,7 +58,7 @@ impl Compiler { pub fn new( store: &PackageStore, dependencies: impl IntoIterator, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, language_features: LanguageFeatures, ) -> Self { let mut resolve_globals = resolve::GlobalTable::new(); @@ -110,18 +110,70 @@ impl Compiler { unit: &mut CompileUnit, source_name: &str, source_contents: &str, - mut accumulate_errors: F, + accumulate_errors: F, ) -> Result where F: FnMut(Vec) -> Result<(), E>, { - let (mut ast, parse_errors) = Self::parse_fragments( + let (ast, parse_errors) = Self::parse_fragments( &mut unit.sources, source_name, source_contents, self.language_features, ); + self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors) + } + + /// Compiles Q# AST fragments. + /// + /// Uses the assigners and other mutable state from the passed in + /// `CompileUnit` to guarantee uniqueness, however does not + /// update the `CompileUnit` with the resulting AST and HIR packages. + /// + /// The caller can use the returned packages to perform passes, + /// get information about the newly added items, or do other modifications. + /// It is then the caller's responsibility to merge + /// these packages into the current `CompileUnit`. + /// + /// This method calls an accumulator function with any errors returned + /// from each of the stages instead of failing. + /// If the accumulator succeeds, compilation continues. + /// If the accumulator returns an error, compilation stops and the + /// error is returned to the caller. + pub fn compile_ast_fragments( + &mut self, + unit: &mut CompileUnit, + source_name: &str, + source_contents: &str, + package: ast::Package, + accumulate_errors: F, + ) -> Result + where + F: FnMut(Vec) -> Result<(), E>, + { + // Update the AST with source information offset from the current source map. + let (ast, parse_errors) = Self::offset_ast_fragments( + &mut unit.sources, + source_name, + source_contents, + package, + vec![], + ); + + self.compile_fragments_internal(unit, ast, parse_errors, accumulate_errors) + } + + fn compile_fragments_internal( + &mut self, + unit: &mut CompileUnit, + mut ast: ast::Package, + parse_errors: Vec, + mut accumulate_errors: F, + ) -> Result + where + F: FnMut(Vec) -> Result<(), E>, + { accumulate_errors(parse_errors)?; let (hir, errors) = self.resolve_check_lower(unit, &mut ast); @@ -280,7 +332,6 @@ impl Compiler { features: LanguageFeatures, ) -> (ast::Package, Vec) { let offset = sources.push(source_name.into(), source_contents.into()); - let (mut top_level_nodes, errors) = qsc_parse::top_level_nodes(source_contents, features); let mut offsetter = Offsetter(offset); for node in &mut top_level_nodes { @@ -289,12 +340,32 @@ impl Compiler { ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt), } } - let package = ast::Package { id: ast::NodeId::default(), nodes: top_level_nodes.into_boxed_slice(), entry: None, }; + (package, with_source(errors, sources, offset)) + } + + /// offset all top level nodes based on the source input + /// and return the updated package and errors + fn offset_ast_fragments( + sources: &mut SourceMap, + source_name: &str, + source_contents: &str, + mut package: ast::Package, + errors: Vec, + ) -> (ast::Package, Vec) { + let offset = sources.push(source_name.into(), source_contents.into()); + + let mut offsetter = Offsetter(offset); + for node in package.nodes.iter_mut() { + match node { + ast::TopLevelNode::Namespace(ns) => offsetter.visit_namespace(ns), + ast::TopLevelNode::Stmt(stmt) => offsetter.visit_stmt(stmt), + } + } (package, with_source(errors, sources, offset)) } diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs index d9ea8a47ff..a055b2a377 100644 --- a/compiler/qsc_frontend/src/incremental/tests.rs +++ b/compiler/qsc_frontend/src/incremental/tests.rs @@ -3,7 +3,7 @@ use super::{Compiler, Increment}; use crate::{ - compile::{self, CompileUnit, PackageStore, RuntimeCapabilityFlags}, + compile::{self, CompileUnit, PackageStore, TargetCapabilityFlags}, incremental::Error, }; use expect_test::{expect, Expect}; @@ -19,7 +19,7 @@ fn one_callable() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let unit = compiler @@ -127,7 +127,7 @@ fn one_statement() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let unit = compiler @@ -190,7 +190,7 @@ fn parse_error() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let errors = compiler @@ -235,7 +235,7 @@ fn conditional_compilation_not_available() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let errors = compiler @@ -260,12 +260,12 @@ fn conditional_compilation_not_available() { #[test] fn errors_across_multiple_lines() { let mut store = PackageStore::new(compile::core()); - let std = compile::std(&store, RuntimeCapabilityFlags::all()); + let std = compile::std(&store, TargetCapabilityFlags::all()); let std_id = store.insert(std); let mut compiler = Compiler::new( &store, [std_id], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let mut unit = CompileUnit::default(); @@ -334,7 +334,7 @@ fn continue_after_parse_error() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let mut errors = Vec::new(); @@ -410,7 +410,7 @@ fn continue_after_lower_error() { let mut compiler = Compiler::new( &store, vec![], - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); let mut unit = CompileUnit::default(); diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs index 8ba1b6f609..9d58f804d2 100644 --- a/compiler/qsc_frontend/src/lower.rs +++ b/compiler/qsc_frontend/src/lower.rs @@ -6,7 +6,7 @@ mod tests; use crate::{ closure::{self, Lambda, PartialApp}, - compile::ConfigAttr, + compile::TargetCapabilityFlags, resolve::{self, Names}, typeck::{self, convert}, }; @@ -30,7 +30,7 @@ pub(super) enum Error { UnknownAttr(String, #[label] Span), #[error("invalid attribute arguments: expected {0}")] #[diagnostic(code("Qsc.LowerAst.InvalidAttrArgs"))] - InvalidAttrArgs(&'static str, #[label] Span), + InvalidAttrArgs(String, #[label] Span), #[error("missing callable body")] #[diagnostic(code("Qsc.LowerAst.MissingBody"))] MissingBody(#[label] Span), @@ -213,7 +213,7 @@ impl With<'_> { _ => { self.lowerer .errors - .push(Error::InvalidAttrArgs("()", attr.arg.span)); + .push(Error::InvalidAttrArgs("()".to_string(), attr.arg.span)); None } }, @@ -222,17 +222,17 @@ impl With<'_> { _ => { self.lowerer .errors - .push(Error::InvalidAttrArgs("()", attr.arg.span)); + .push(Error::InvalidAttrArgs("()".to_string(), attr.arg.span)); None } }, Ok(hir::Attr::Config) => { if !matches!(attr.arg.kind.as_ref(), ast::ExprKind::Paren(inner) if matches!(inner.kind.as_ref(), ast::ExprKind::Path(path) - if ConfigAttr::from_str(path.name.name.as_ref()).is_ok())) + if TargetCapabilityFlags::from_str(path.name.name.as_ref()).is_ok())) { self.lowerer.errors.push(Error::InvalidAttrArgs( - "Unrestricted or Base", + "runtime capability".to_string(), attr.arg.span, )); } diff --git a/compiler/qsc_frontend/src/lower/tests.rs b/compiler/qsc_frontend/src/lower/tests.rs index 1aa17cf92b..0f63a88b40 100644 --- a/compiler/qsc_frontend/src/lower/tests.rs +++ b/compiler/qsc_frontend/src/lower/tests.rs @@ -3,7 +3,7 @@ #![allow(clippy::needless_raw_string_hashes)] -use crate::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use crate::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; @@ -14,7 +14,7 @@ fn check_hir(input: &str, expect: &Expect) { &PackageStore::new(compile::core()), &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); expect.assert_eq(&unit.package.to_string()); @@ -26,7 +26,7 @@ fn check_errors(input: &str, expect: &Expect) { &PackageStore::new(compile::core()), &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); @@ -106,23 +106,6 @@ fn test_target_profile_base_attr_allowed() { ); } -#[test] -fn test_target_profile_full_attr_allowed() { - check_errors( - indoc! {" - namespace input { - @Config(Unrestricted) - operation Foo() : Unit { - body ... {} - } - } - "}, - &expect![[r#" - [] - "#]], - ); -} - #[test] fn test_target_profile_attr_wrong_args() { check_errors( @@ -137,7 +120,7 @@ fn test_target_profile_attr_wrong_args() { &expect![[r#" [ InvalidAttrArgs( - "Unrestricted or Base", + "runtime capability", Span { lo: 29, hi: 34, diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs index da9dd59e3f..0969ddd54e 100644 --- a/compiler/qsc_frontend/src/resolve/tests.rs +++ b/compiler/qsc_frontend/src/resolve/tests.rs @@ -6,7 +6,7 @@ use super::{Error, Locals, Names, Res}; use crate::{ compile, - compile::RuntimeCapabilityFlags, + compile::TargetCapabilityFlags, resolve::{LocalKind, Resolver}, }; use expect_test::{expect, Expect}; @@ -104,7 +104,7 @@ fn compile( AstAssigner::new().visit_package(&mut package); - let mut cond_compile = compile::preprocess::Conditional::new(RuntimeCapabilityFlags::all()); + let mut cond_compile = compile::preprocess::Conditional::new(TargetCapabilityFlags::all()); cond_compile.visit_package(&mut package); let dropped_names = cond_compile.into_names(); @@ -1980,13 +1980,13 @@ fn multiple_definition_dropped_is_not_found() { check( indoc! {" namespace A { - @Config(Unrestricted) + @Config(Adaptive) operation B() : Unit {} @Config(Base) operation B() : Unit {} @Config(Base) operation C() : Unit {} - @Config(Unrestricted) + @Config(Adaptive) operation C() : Unit {} } namespace D { @@ -2003,13 +2003,13 @@ fn multiple_definition_dropped_is_not_found() { "}, &expect![[r#" namespace item0 { - @Config(Unrestricted) + @Config(Adaptive) operation item1() : Unit {} @Config(Base) operation B() : Unit {} @Config(Base) operation C() : Unit {} - @Config(Unrestricted) + @Config(Adaptive) operation item2() : Unit {} } namespace item3 { @@ -2024,8 +2024,8 @@ fn multiple_definition_dropped_is_not_found() { } } - // NotFound("B", Span { lo: 265, hi: 266 }) - // NotFound("C", Span { lo: 278, hi: 279 }) + // NotFound("B", Span { lo: 257, hi: 258 }) + // NotFound("C", Span { lo: 270, hi: 271 }) "#]], ); } diff --git a/compiler/qsc_linter/src/tests.rs b/compiler/qsc_linter/src/tests.rs index 051db19653..70c0c6ce1b 100644 --- a/compiler/qsc_linter/src/tests.rs +++ b/compiler/qsc_linter/src/tests.rs @@ -7,7 +7,7 @@ use crate::{ }; use expect_test::{expect, Expect}; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, CompileUnit, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, CompileUnit, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_passes::PackageType; #[test] @@ -162,14 +162,14 @@ fn hir_placeholder() { fn check(source: &str, expected: &Expect) { let source = wrap_in_namespace(source); let mut store = PackageStore::new(compile::core()); - let std = store.insert(compile::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(compile::std(&store, TargetCapabilityFlags::all())); let sources = SourceMap::new([("source.qs".into(), source.clone().into())], None); let (package, _) = qsc::compile::compile( &store, &[std], sources, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); diff --git a/compiler/qsc_lowerer/src/lib.rs b/compiler/qsc_lowerer/src/lib.rs index 570ddde758..c38e98090c 100644 --- a/compiler/qsc_lowerer/src/lib.rs +++ b/compiler/qsc_lowerer/src/lib.rs @@ -110,7 +110,7 @@ impl Lowerer { &mut self, fir_package: &mut fir::Package, hir_package: &hir::Package, - ) -> Vec { + ) { let items: IndexMap = hir_package .items .values() @@ -118,11 +118,9 @@ impl Lowerer { .map(|i| (i.id, i)) .collect(); - let new_stmts = hir_package - .stmts - .iter() - .map(|s| self.lower_stmt(s)) - .collect(); + for stmt in &hir_package.stmts { + let _ = self.lower_stmt(stmt); + } let entry = hir_package.entry.as_ref().map(|e| self.lower_expr(e)); @@ -135,8 +133,6 @@ impl Lowerer { fir_package.entry = entry; qsc_fir::validate::validate(fir_package); - - new_stmts } fn update_package(&mut self, package: &mut fir::Package) { diff --git a/compiler/qsc_partial_eval/Cargo.toml b/compiler/qsc_partial_eval/Cargo.toml index b9191ca5b0..0488a38db3 100644 --- a/compiler/qsc_partial_eval/Cargo.toml +++ b/compiler/qsc_partial_eval/Cargo.toml @@ -10,6 +10,8 @@ license.workspace = true [dependencies] miette = { workspace = true } +num-bigint = { workspace = true } +num-complex = { workspace = true } rustc-hash = { workspace = true } qsc_data_structures = { path = "../qsc_data_structures" } qsc_eval = { path = "../qsc_eval" } diff --git a/compiler/qsc_partial_eval/src/evaluation_context.rs b/compiler/qsc_partial_eval/src/evaluation_context.rs index e6a97e1f50..c78d1c2fa6 100644 --- a/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -2,26 +2,36 @@ // Licensed under the MIT License. use qsc_data_structures::functors::FunctorApp; -use qsc_eval::{val::Value, Env}; +use qsc_eval::{ + val::{Result, Value}, + Env, Variable, +}; use qsc_fir::fir::{ExprId, LocalItemId, LocalVarId, PackageId}; -use qsc_rca::ValueKind; +use qsc_rca::{RuntimeKind, ValueKind}; use qsc_rir::rir::BlockId; use rustc_hash::FxHashMap; pub struct EvaluationContext { - pub current_block: BlockId, + active_blocks: Vec, scopes: Vec, } impl EvaluationContext { - pub fn new(entry_package_id: PackageId, initial_block: BlockId) -> Self { - let entry_callable_scope = Scope::new(entry_package_id, None, Vec::new()); + pub fn new(package_id: PackageId, initial_block: BlockId) -> Self { + let entry_callable_scope = Scope::new(package_id, None, Vec::new()); Self { - current_block: initial_block, + active_blocks: vec![BlockNode { + id: initial_block, + next: None, + }], scopes: vec![entry_callable_scope], } } + pub fn get_current_block_id(&self) -> BlockId { + self.active_blocks.last().expect("no active blocks").id + } + pub fn get_current_scope(&self) -> &Scope { self.scopes .last() @@ -34,22 +44,38 @@ impl EvaluationContext { .expect("the evaluation context does not have a current scope") } + pub fn pop_block_node(&mut self) -> BlockNode { + self.active_blocks + .pop() + .expect("there are no active blocks in the evaluation context") + } + pub fn pop_scope(&mut self) -> Scope { self.scopes .pop() .expect("there are no scopes in the evaluation context") } + pub fn push_block_node(&mut self, b: BlockNode) { + self.active_blocks.push(b); + } + pub fn push_scope(&mut self, s: Scope) { self.scopes.push(s); } } +pub struct BlockNode { + pub id: BlockId, + pub next: Option, +} + pub struct Scope { pub package_id: PackageId, pub callable: Option<(LocalItemId, FunctorApp)>, - pub args_runtime_properties: Vec, + pub args_value_kind: Vec, pub env: Env, + last_expr: Option, hybrid_exprs: FxHashMap, hybrid_vars: FxHashMap, } @@ -58,19 +84,51 @@ impl Scope { pub fn new( package_id: PackageId, callable: Option<(LocalItemId, FunctorApp)>, - args_runtime_properties: Vec, + args: Vec, ) -> Self { + // Determine the runtime kind (static or dynamic) of the arguments. + let args_value_kind: Vec = args + .iter() + .map(|arg| { + let value = match arg { + Arg::Discard(value) => value, + Arg::Var(_, var) => &var.value, + }; + map_eval_value_to_value_kind(value) + }) + .collect(); + + // Add the values to either the environment or the hybrid variables depending on whether the value is static or + // dynamic. + let mut env = Env::default(); + let mut hybrid_vars = FxHashMap::default(); + let arg_runtime_kind_tuple = args.into_iter().zip(args_value_kind.iter()); + for (arg, value_kind) in arg_runtime_kind_tuple { + let Arg::Var(local_var_id, var) = arg else { + continue; + }; + + if value_kind.is_dynamic() { + hybrid_vars.insert(local_var_id, var.value); + } else { + env.bind_variable_in_top_frame(local_var_id, var); + } + } + + // Add the dynamic values to the hybrid variables Self { package_id, callable, - args_runtime_properties, - env: Env::default(), + args_value_kind, + env, + last_expr: None, hybrid_exprs: FxHashMap::default(), - hybrid_vars: FxHashMap::default(), + hybrid_vars, } } - pub fn get_expr_value(&self, expr_id: ExprId) -> &Value { + // Potential candidate for removal if only the last expression value is needed. + pub fn _get_expr_value(&self, expr_id: ExprId) -> &Value { self.hybrid_exprs .get(&expr_id) .expect("expression value does not exist") @@ -83,10 +141,81 @@ impl Scope { } pub fn insert_expr_value(&mut self, expr_id: ExprId, value: Value) { + self.last_expr = Some(expr_id); self.hybrid_exprs.insert(expr_id, value); } pub fn insert_local_var_value(&mut self, local_var_id: LocalVarId, value: Value) { self.hybrid_vars.insert(local_var_id, value); } + + pub fn clear_last_expr(&mut self) { + self.last_expr = None; + } + + pub fn last_expr_value(&self) -> Value { + self.last_expr + .and_then(|expr_id| self.hybrid_exprs.get(&expr_id)) + .map_or_else(Value::unit, Clone::clone) + } +} + +fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { + fn map_array_eval_value_to_value_kind(elements: &[Value]) -> ValueKind { + let mut content_runtime_kind = RuntimeKind::Static; + for element in elements { + let element_value_kind = map_eval_value_to_value_kind(element); + if element_value_kind.is_dynamic() { + content_runtime_kind = RuntimeKind::Dynamic; + break; + } + } + + // The runtime capabilities check pass disallows dynamically-sized arrays for all targets for which we generate + // QIR. Because of this, we assume that during partial evaluation all arrays are statically-sized. + ValueKind::Array(content_runtime_kind, RuntimeKind::Static) + } + + fn map_tuple_eval_value_to_value_kind(elements: &[Value]) -> ValueKind { + let mut runtime_kind = RuntimeKind::Static; + for element in elements { + let element_value_kind = map_eval_value_to_value_kind(element); + if element_value_kind.is_dynamic() { + runtime_kind = RuntimeKind::Dynamic; + break; + } + } + ValueKind::Element(runtime_kind) + } + + match value { + Value::Array(elements) => map_array_eval_value_to_value_kind(elements), + Value::Tuple(elements) => map_tuple_eval_value_to_value_kind(elements), + Value::Result(Result::Id(_)) | Value::Var(_) => ValueKind::Element(RuntimeKind::Dynamic), + Value::BigInt(_) + | Value::Bool(_) + | Value::Closure(_) + | Value::Double(_) + | Value::Global(_, _) + | Value::Int(_) + | Value::Pauli(_) + | Value::Qubit(_) + | Value::Range(_) + | Value::Result(Result::Val(_)) + | Value::String(_) => ValueKind::Element(RuntimeKind::Static), + } +} + +pub enum Arg { + Discard(Value), + Var(LocalVarId, Variable), +} + +impl Arg { + pub fn into_value(self) -> Value { + match self { + Self::Discard(value) => value, + Self::Var(_, var) => var.value, + } + } } diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 18832e2e9d..5c0ec74492 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -4,7 +4,7 @@ mod evaluation_context; mod management; -use evaluation_context::{EvaluationContext, Scope}; +use evaluation_context::{Arg, BlockNode, EvaluationContext, Scope}; use management::{QuantumIntrinsicsChecker, ResourceManager}; use miette::Diagnostic; use qsc_data_structures::functors::FunctorApp; @@ -13,33 +13,41 @@ use qsc_eval::{ self, exec_graph_section, output::GenericReceiver, val::{self, Value, Var}, - State, StepAction, StepResult, + State, StepAction, StepResult, Variable, }; use qsc_fir::{ fir::{ - BinOp, Block, BlockId, CallableDecl, CallableImpl, ExecGraphNode, Expr, ExprId, ExprKind, - Global, Ident, PackageId, PackageStore, PackageStoreLookup, Pat, PatId, PatKind, Res, - SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StoreBlockId, StoreExprId, StoreItemId, + self, BinOp, Block, BlockId, CallableDecl, CallableImpl, ExecGraphNode, Expr, ExprId, + ExprKind, Global, Ident, PackageId, PackageStore, PackageStoreLookup, Pat, PatId, PatKind, + Res, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StoreBlockId, StoreExprId, StoreItemId, StorePatId, StoreStmtId, }, ty::{Prim, Ty}, visit::Visitor, }; use qsc_rca::{ComputeKind, ComputePropertiesLookup, PackageStoreComputeProperties}; -use qsc_rir::rir::{ - self, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, - Program, Variable, +use qsc_rir::{ + builder, + rir::{ + self, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, + Program, + }, }; use rustc_hash::FxHashMap; use std::{collections::hash_map::Entry, rc::Rc, result::Result}; use thiserror::Error; +pub struct ProgramEntry { + pub exec_graph: Rc<[ExecGraphNode]>, + pub expr: fir::StoreExprId, +} + pub fn partially_evaluate( - package_id: PackageId, package_store: &PackageStore, compute_properties: &PackageStoreComputeProperties, + entry: &ProgramEntry, ) -> Result { - let partial_evaluator = PartialEvaluator::new(package_id, package_store, compute_properties); + let partial_evaluator = PartialEvaluator::new(package_store, compute_properties, entry); partial_evaluator.eval() } @@ -48,18 +56,51 @@ pub enum Error { #[error("partial evaluation error: {0}")] #[diagnostic(code("Qsc.PartialEval.EvaluationFailed"))] EvaluationFailed(qsc_eval::Error), + + #[error("failed to evaluate array element expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateArrayElementExpression"))] + FailedToEvaluateArrayElementExpression(#[label] Span), + #[error("failed to evaluate {0}")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCallable"))] FailedToEvaluateCallable(String, #[label] Span), + #[error("failed to evaluate callee expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCalleeExpression"))] FailedToEvaluateCalleeExpression(#[label] Span), + + #[error("failed to evaluate call arguments expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateCallArgsExpression"))] + FailedToEvaluateCallArgsExpression(#[label] Span), + #[error("failed to evaluate tuple element expression")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateTupleElementExpression"))] FailedToEvaluateTupleElementExpression(#[label] Span), + #[error("failed to evaluate binary expression operand")] #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBinaryExpressionOperand"))] FailedToEvaluateBinaryExpressionOperand(#[label] Span), + + #[error("failed to evaluate condition expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateConditionExpression"))] + FailedToEvaluateConditionExpression(#[label] Span), + + #[error("failed to evaluate a branch block of an if expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateIfExpressionBranchBlock"))] + FailedToEvaluateIfExpressionBranchBlock(#[label] Span), + + #[error("failed to evaluate block expression")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateBlockExpression"))] + FailedToEvaluateBlockExpression(#[label] Span), + + #[error("failed to evaluate loop condition")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateLoopCondition"))] + FailedToEvaluateLoopCondition(#[label] Span), + + #[error("failed to evaluate loop body")] + #[diagnostic(code("Qsc.PartialEval.FailedToEvaluateLoopBody"))] + FailedToEvaluateLoopBody(#[label] Span), + #[error("failed to evaluate: {0} not yet implemented")] #[diagnostic(code("Qsc.PartialEval.Unimplemented"))] Unimplemented(String, #[label] Span), @@ -73,21 +114,21 @@ struct PartialEvaluator<'a> { callables_map: FxHashMap, CallableId>, eval_context: EvaluationContext, program: Program, + entry: &'a ProgramEntry, errors: Vec, } impl<'a> PartialEvaluator<'a> { fn new( - entry_package_id: PackageId, package_store: &'a PackageStore, compute_properties: &'a PackageStoreComputeProperties, + entry: &'a ProgramEntry, ) -> Self { // Create the entry-point callable. let mut resource_manager = ResourceManager::default(); let mut program = Program::new(); let entry_block_id = resource_manager.next_block(); - let entry_block = rir::Block(Vec::new()); - program.blocks.insert(entry_block_id, entry_block); + program.blocks.insert(entry_block_id, rir::Block::default()); let entry_point_id = resource_manager.next_callable(); let entry_point = rir::Callable { name: "main".into(), @@ -100,7 +141,7 @@ impl<'a> PartialEvaluator<'a> { program.entry = entry_point_id; // Initialize the evaluation context and create a new partial evaluator. - let context = EvaluationContext::new(entry_package_id, entry_block_id); + let context = EvaluationContext::new(entry.expr.package, entry_block_id); Self { package_store, compute_properties, @@ -109,32 +150,23 @@ impl<'a> PartialEvaluator<'a> { backend: QuantumIntrinsicsChecker::default(), callables_map: FxHashMap::default(), program, + entry, errors: Vec::new(), } } - fn bind_expr_to_pat(&mut self, pat_id: PatId, expr_id: ExprId) { + fn bind_value_to_pat(&mut self, pat_id: PatId, value: Value) { let pat = self.get_pat(pat_id); match &pat.kind { PatKind::Bind(ident) => { - let expr_value = self - .eval_context - .get_current_scope_mut() - .get_expr_value(expr_id) - .clone(); - self.bind_value_to_ident(ident, expr_value); + self.bind_value_to_ident(ident, value); } PatKind::Tuple(pats) => { - let expr = self.get_expr(expr_id); - match &expr.kind { - ExprKind::Tuple(exprs) => { - assert!(pats.len() == exprs.len()); - for (pat_id, expr_id) in pats.iter().zip(exprs.iter()) { - self.bind_expr_to_pat(*pat_id, *expr_id); - } - } - _ => panic!("expected a tuple expression to bind to a tuple pattern"), - }; + let tup = value.unwrap_tuple(); + assert!(pats.len() == tup.len()); + for (pat_id, value) in pats.iter().zip(tup.iter()) { + self.bind_value_to_pat(*pat_id, value.clone()); + } } PatKind::Discard => { // Nothing to bind to. @@ -180,15 +212,15 @@ impl<'a> PartialEvaluator<'a> { } } - fn eval(mut self) -> Result { - let current_package = self.get_current_package_id(); - let entry_package = self.package_store.get(current_package); - let Some(entry_expr_id) = entry_package.entry else { - panic!("package does not have an entry expression"); - }; + fn create_program_block(&mut self) -> rir::BlockId { + let block_id = self.resource_manager.next_block(); + self.program.blocks.insert(block_id, rir::Block::default()); + block_id + } + fn eval(mut self) -> Result { // Visit the entry point expression. - self.visit_expr(entry_expr_id); + self.visit_expr(self.entry.expr.expr); // Return the first error, if any. // We should eventually return all the errors but since that is an interface change, we will do that as its own @@ -197,13 +229,30 @@ impl<'a> PartialEvaluator<'a> { return Err(error); } + // Get the final value from the execution context. + let ret_val = self.eval_context.get_current_scope().last_expr_value(); + let output_recording: Vec = self.generate_output_recording_instructions( + ret_val, + &self.get_expr(self.entry.expr.expr).ty, + ); + // Insert the return expression and return the generated program. - let current_block = self - .program - .blocks - .get_mut(self.eval_context.current_block) - .expect("block does not exist"); + let current_block = self.get_current_block_mut(); + current_block.0.extend(output_recording); current_block.0.push(Instruction::Return); + + // Set the number of qubits and results used by the program. + self.program.num_qubits = self + .resource_manager + .qubit_count() + .try_into() + .expect("qubits count should fit into a u32"); + self.program.num_results = self + .resource_manager + .results_count() + .try_into() + .expect("results count should fit into a u32"); + Ok(self.program) } @@ -260,8 +309,8 @@ impl<'a> PartialEvaluator<'a> { let store_expr_id = StoreExprId::from((current_package_id, expr_id)); let expr = self.package_store.get_expr(store_expr_id); match &expr.kind { - ExprKind::Array(_) => Err(Error::Unimplemented("Array Expr".to_string(), expr.span)), - ExprKind::ArrayLit(_) => Err(Error::Unimplemented("Array Lit".to_string(), expr.span)), + ExprKind::Array(exprs) => self.eval_expr_array(exprs), + ExprKind::ArrayLit(_) => panic!("array of literal values should always be classical"), ExprKind::ArrayRepeat(_, _) => { Err(Error::Unimplemented("Array Repeat".to_string(), expr.span)) } @@ -284,7 +333,7 @@ impl<'a> PartialEvaluator<'a> { ExprKind::BinOp(bin_op, lhs_expr_id, rhs_expr_id) => { self.eval_expr_bin_op(expr_id, *bin_op, *lhs_expr_id, *rhs_expr_id) } - ExprKind::Block(_) => Err(Error::Unimplemented("Block Expr".to_string(), expr.span)), + ExprKind::Block(block_id) => self.eval_expr_block(*block_id), ExprKind::Call(callee_expr_id, args_expr_id) => { self.eval_expr_call(*callee_expr_id, *args_expr_id) } @@ -294,7 +343,12 @@ impl<'a> PartialEvaluator<'a> { ExprKind::Fail(_) => panic!("instruction generation for fail expression is invalid"), ExprKind::Field(_, _) => Err(Error::Unimplemented("Field Expr".to_string(), expr.span)), ExprKind::Hole => panic!("instruction generation for hole expressions is invalid"), - ExprKind::If(_, _, _) => Err(Error::Unimplemented("If Expr".to_string(), expr.span)), + ExprKind::If(condition_expr_id, body_expr_id, otherwise_expr_id) => self.eval_expr_if( + expr_id, + *condition_expr_id, + *body_expr_id, + *otherwise_expr_id, + ), ExprKind::Index(_, _) => Err(Error::Unimplemented("Index Expr".to_string(), expr.span)), ExprKind::Lit(_) => panic!("instruction generation for literal expressions is invalid"), ExprKind::Range(_, _, _) => { @@ -315,7 +369,9 @@ impl<'a> PartialEvaluator<'a> { expr.span, )), ExprKind::Var(res, _) => Ok(self.eval_expr_var(res)), - ExprKind::While(_, _) => Err(Error::Unimplemented("While Expr".to_string(), expr.span)), + ExprKind::While(condition_expr_id, body_block_id) => { + self.eval_expr_while(*condition_expr_id, *body_block_id) + } } } @@ -359,7 +415,7 @@ impl<'a> PartialEvaluator<'a> { let bin_op_expr = self.get_expr(bin_op_expr_id); let variable_id = self.resource_manager.next_var(); let variable_ty = map_fir_type_to_rir_type(&bin_op_expr.ty); - let variable = Variable { + let variable = rir::Variable { variable_id, ty: variable_ty, }; @@ -383,6 +439,14 @@ impl<'a> PartialEvaluator<'a> { Ok(value) } + fn eval_expr_block(&mut self, block_id: BlockId) -> Result { + let maybe_block_value = self.try_eval_block(block_id); + maybe_block_value.map_err(|()| { + let block = self.get_block(block_id); + Error::FailedToEvaluateBlockExpression(block.span) + }) + } + fn eval_expr_call( &mut self, callee_expr_id: ExprId, @@ -399,14 +463,12 @@ impl<'a> PartialEvaluator<'a> { let maybe_args_value = self.try_eval_expr(args_expr_id); let Ok(args_value) = maybe_args_value else { let args_expr = self.get_expr(args_expr_id); - let error = Error::FailedToEvaluateCalleeExpression(args_expr.span); + let error = Error::FailedToEvaluateCallArgsExpression(args_expr.span); return Err(error); }; // Get the callable. - let Value::Global(store_item_id, functor_app) = callable_value else { - panic!("callee expression is expected to be a global"); - }; + let (store_item_id, functor_app) = callable_value.unwrap_global(); let global = self .package_store .get_global(store_item_id) @@ -424,9 +486,13 @@ impl<'a> PartialEvaluator<'a> { self.eval_expr_call_to_intrinsic(store_item_id, callable_decl, args_value); Ok(value) } - CallableImpl::Spec(spec_impl) => { - self.eval_expr_call_to_spec(store_item_id, functor_app, spec_impl, args_expr_id) - } + CallableImpl::Spec(spec_impl) => self.eval_expr_call_to_spec( + store_item_id, + functor_app, + spec_impl, + callable_decl.input, + args_value, + ), } } @@ -436,13 +502,27 @@ impl<'a> PartialEvaluator<'a> { callable_decl: &CallableDecl, args_value: Value, ) -> Value { - // There are a few special cases regarding intrinsic callables: qubit allocation/release and measurements. - // Identify them and handle them properly. + // There are a few special cases regarding intrinsic callables. Identify them and handle them properly. match callable_decl.name.name.as_ref() { + // Qubit allocations and measurements have special handling. "__quantum__rt__qubit_allocate" => self.allocate_qubit(), - "__quantum__rt__qubit_release" => self.release_qubit(&args_value), - "__quantum__qis__m__body" => self.measure_qubit(mz_callable(), &args_value), - "__quantum__qis__mresetz__body" => self.measure_qubit(mresetz_callable(), &args_value), + "__quantum__rt__qubit_release" => self.release_qubit(args_value), + "__quantum__qis__m__body" => self.measure_qubit(builder::mz_decl(), args_value), + "__quantum__qis__mresetz__body" => { + self.measure_qubit(builder::mresetz_decl(), args_value) + } + // The following operations should be conditionally compiled out for all targets for which QIR generation is + // supported. + "CheckZero" | "DrawRandomInt" | "DrawRandomDouble" => panic!( + "`{}` is not a supported by partial evaluation", + callable_decl.name.name + ), + // The following intrinsic operations and functions are no-ops. + "BeginEstimateCaching" => Value::Bool(true), + "DumpRegister" + | "AccountForEstimatesInternal" + | "BeginRepeatEstimatesInternal" + | "EndRepeatEstimatesInternal" => Value::unit(), _ => self.eval_expr_call_to_intrinsic_qis(store_item_id, callable_decl, args_value), } } @@ -453,14 +533,24 @@ impl<'a> PartialEvaluator<'a> { callable_decl: &CallableDecl, args_value: Value, ) -> Value { + // Intrinsic callables that make it to this point are expected to be unitary. + assert_eq!(callable_decl.output, Ty::UNIT); + // Check if the callable is already in the program, and if not add it. let callable = self.create_intrinsic_callable(store_item_id, callable_decl); let callable_id = self.get_or_insert_callable(callable); // Resove the call arguments, create the call instruction and insert it to the current block. - let args = resolve_call_arg_operands(args_value); - // Note that we currently just support calls to unitary operations. - let instruction = Instruction::Call(callable_id, args, None); + let args = self.resolve_args( + (store_item_id.package, callable_decl.input).into(), + args_value, + ); + let args_operands = args + .into_iter() + .map(|arg| map_eval_value_to_rir_operand(&arg.into_value())) + .collect(); + + let instruction = Instruction::Call(callable_id, args_operands, None); let current_block = self.get_current_block_mut(); current_block.0.push(instruction); Value::unit() @@ -471,18 +561,19 @@ impl<'a> PartialEvaluator<'a> { global_callable_id: StoreItemId, functor_app: FunctorApp, spec_impl: &SpecImpl, - _args_expr_id: ExprId, + args_pat: PatId, + args_value: Value, ) -> Result { let spec_decl = get_spec_decl(spec_impl, functor_app); - // We are currently not setting the argument values in a way that supports arbitrary calls, but we'll add that - // support later. - let callable_scope = Scope::new( + // Create new call scope. + let args = self.resolve_args((global_callable_id.package, args_pat).into(), args_value); + let call_scope = Scope::new( global_callable_id.package, Some((global_callable_id.item, functor_app)), - Vec::new(), + args, ); - self.eval_context.push_scope(callable_scope); + self.eval_context.push_scope(call_scope); self.visit_block(spec_decl.block); let popped_scope = self.eval_context.pop_scope(); assert!( @@ -500,9 +591,7 @@ impl<'a> PartialEvaluator<'a> { // Check whether evaluating the block failed. if self.errors.is_empty() { - // Once we have proper support for evaluating all kinds of callables (not just parameterless unitary - // callables), we should the variable that stores the callable return value here. - Ok(Value::unit()) + Ok(popped_scope.last_expr_value()) } else { // Evaluating the block failed, generate an error specific to the callable. let global_callable = self @@ -519,8 +608,169 @@ impl<'a> PartialEvaluator<'a> { } } + fn eval_expr_if( + &mut self, + if_expr_id: ExprId, + condition_expr_id: ExprId, + body_expr_id: ExprId, + otherwise_expr_id: Option, + ) -> Result { + // Visit the both the condition expression to get its value. + let maybe_condition_value = self.try_eval_expr(condition_expr_id); + let Ok(condition_value) = maybe_condition_value else { + let condition_expr = self.get_expr(condition_expr_id); + let error = Error::FailedToEvaluateConditionExpression(condition_expr.span); + return Err(error); + }; + + // If the condition value is a Boolean literal, use the value to decide which branch to + // evaluate. + if let Value::Bool(condition_bool) = condition_value { + return self.eval_expr_if_with_classical_condition( + condition_bool, + body_expr_id, + otherwise_expr_id, + ); + } + + // At this point the condition value is not classical, so we need to generate a branching instruction. + // First, we pop the current block node and generate a new one which the new branches will jump to when their + // instructions end. + let current_block_node = self.eval_context.pop_block_node(); + let continuation_block_node_id = self.create_program_block(); + let continuation_block_node = BlockNode { + id: continuation_block_node_id, + next: current_block_node.next, + }; + self.eval_context.push_block_node(continuation_block_node); + + // Since the if expression can represent a dynamic value, create a variable to store it if the expression is + // non-unit. + let if_expr = self.get_expr(if_expr_id); + let maybe_if_expr_var = if if_expr.ty == Ty::UNIT { + None + } else { + let variable_id = self.resource_manager.next_var(); + let variable_ty = map_fir_type_to_rir_type(&if_expr.ty); + Some(rir::Variable { + variable_id, + ty: variable_ty, + }) + }; + + // Evaluate the body expression. + let if_true_block_id = + self.eval_expr_if_branch(body_expr_id, continuation_block_node_id, maybe_if_expr_var)?; + + // Evaluate the otherwise expression (if any), and determine the block to branch to if the condition is false. + let if_false_block_id = if let Some(otherwise_expr_id) = otherwise_expr_id { + self.eval_expr_if_branch( + otherwise_expr_id, + continuation_block_node_id, + maybe_if_expr_var, + )? + } else { + continuation_block_node_id + }; + + // Finally, we insert the branch instruction. + let condition_value_var = condition_value.unwrap_var(); + let condition_rir_var = rir::Variable { + variable_id: condition_value_var.0.into(), + ty: rir::Ty::Boolean, + }; + let branch_ins = + Instruction::Branch(condition_rir_var, if_true_block_id, if_false_block_id); + self.get_program_block_mut(current_block_node.id) + .0 + .push(branch_ins); + + // Return the value of the if expression. + let if_expr_value = if let Some(if_expr_var) = maybe_if_expr_var { + Value::Var(Var(if_expr_var.variable_id.into())) + } else { + Value::unit() + }; + Ok(if_expr_value) + } + + fn eval_expr_if_branch( + &mut self, + branch_body_expr_id: ExprId, + continuation_block_id: rir::BlockId, + if_expr_var: Option, + ) -> Result { + // Create the block node that corresponds to the branch body and push it as the active one. + let block_node_id = self.create_program_block(); + let block_node = BlockNode { + id: block_node_id, + next: Some(continuation_block_id), + }; + self.eval_context.push_block_node(block_node); + + // Evaluate the branch body expression. + let maybe_body_value = self.try_eval_expr(branch_body_expr_id); + let Ok(body_value) = maybe_body_value else { + let body_body_expr = self.get_expr(branch_body_expr_id); + let error = Error::FailedToEvaluateIfExpressionBranchBlock(body_body_expr.span); + return Err(error); + }; + + // If there is a variable to save the value of the if expression to, add a store instruction. + if let Some(if_expr_var) = if_expr_var { + let body_operand = map_eval_value_to_rir_operand(&body_value); + let store_ins = Instruction::Store(body_operand, if_expr_var); + self.get_current_block_mut().0.push(store_ins); + } + + // Finally, jump to the continuation block and pop the current block node. + let jump_ins = Instruction::Jump(continuation_block_id); + self.get_current_block_mut().0.push(jump_ins); + let _ = self.eval_context.pop_block_node(); + Ok(block_node_id) + } + + fn eval_expr_if_with_classical_condition( + &mut self, + condition_bool: bool, + body_expr_id: ExprId, + otherwise_expr_id: Option, + ) -> Result { + if condition_bool { + let maybe_body_value = self.try_eval_expr(body_expr_id); + maybe_body_value.map_err(|()| { + let body_expr = self.get_expr(body_expr_id); + Error::FailedToEvaluateIfExpressionBranchBlock(body_expr.span) + }) + } else if let Some(otherwise_expr_id) = otherwise_expr_id { + let maybe_otherwise_value = self.try_eval_expr(otherwise_expr_id); + maybe_otherwise_value.map_err(|()| { + let otherwise_expr = self.get_expr(otherwise_expr_id); + Error::FailedToEvaluateIfExpressionBranchBlock(otherwise_expr.span) + }) + } else { + // A the classical condition evaluated to false, but there is not otherwise block so there is nothing to + // evaluate. + // Return unit since it is the only possibility for if expressions with no otherwise block. + Ok(Value::unit()) + } + } + + fn eval_expr_array(&mut self, exprs: &Vec) -> Result { + let mut values = Vec::with_capacity(exprs.len()); + for expr_id in exprs { + let maybe_value = self.try_eval_expr(*expr_id); + let Ok(value) = maybe_value else { + let expr = self.get_expr(*expr_id); + return Err(Error::FailedToEvaluateArrayElementExpression(expr.span)); + }; + values.push(value); + } + Ok(Value::Array(values.into())) + } + fn eval_expr_tuple(&mut self, exprs: &Vec) -> Result { - let mut values = Vec::::new(); + let mut values = Vec::with_capacity(exprs.len()); for expr_id in exprs { let maybe_value = self.try_eval_expr(*expr_id); let Ok(value) = maybe_value else { @@ -550,6 +800,47 @@ impl<'a> PartialEvaluator<'a> { } } + fn eval_expr_while( + &mut self, + condition_expr_id: ExprId, + body_block_id: BlockId, + ) -> Result { + // Verify assumptions. + assert!( + self.is_classical_expr(condition_expr_id), + "loop conditions must be purely classical" + ); + let body_block = self.get_block(body_block_id); + assert_eq!( + body_block.ty, + Ty::UNIT, + "the type of a loop block is expected to be Unit" + ); + + // Evaluate the block until the loop condition is false. + while self.eval_expr_while_condition(condition_expr_id)? { + let maybe_block_value = self.try_eval_block(body_block_id); + if maybe_block_value.is_err() { + let block = self.get_block(body_block_id); + let error = Error::FailedToEvaluateLoopBody(block.span); + return Err(error); + } + } + + Ok(Value::unit()) + } + + fn eval_expr_while_condition(&mut self, condition_expr_id: ExprId) -> Result { + let maybe_condition_expr_value = self.try_eval_expr(condition_expr_id); + if let Ok(condition_expr_value) = maybe_condition_expr_value { + Ok(condition_expr_value.unwrap_bool()) + } else { + let condition_expr = self.get_expr(condition_expr_id); + let error = Error::FailedToEvaluateLoopCondition(condition_expr.span); + Err(error) + } + } + fn eval_result_as_bool_operand(&mut self, result: val::Result) -> Operand { match result { val::Result::Id(id) => { @@ -557,10 +848,11 @@ impl<'a> PartialEvaluator<'a> { let result_operand = Operand::Literal(Literal::Result( id.try_into().expect("could not convert result ID to u32"), )); - let read_result_callable_id = self.get_or_insert_callable(read_result_callable()); + let read_result_callable_id = + self.get_or_insert_callable(builder::read_result_decl()); let variable_id = self.resource_manager.next_var(); let variable_ty = rir::Ty::Boolean; - let variable = Variable { + let variable = rir::Variable { variable_id, ty: variable_ty, }; @@ -578,10 +870,7 @@ impl<'a> PartialEvaluator<'a> { } fn get_current_block_mut(&mut self) -> &mut rir::Block { - self.program - .blocks - .get_mut(self.eval_context.current_block) - .expect("block does not exist") + self.get_program_block_mut(self.eval_context.get_current_block_id()) } fn get_current_package_id(&self) -> PackageId { @@ -592,9 +881,7 @@ impl<'a> PartialEvaluator<'a> { if let Some(spec_decl) = self.get_current_scope_spec_decl() { &spec_decl.exec_graph } else { - let package_id = self.get_current_package_id(); - let package = self.package_store.get(package_id); - &package.entry_exec_graph + &self.entry.exec_graph } } @@ -618,6 +905,22 @@ impl<'a> PartialEvaluator<'a> { Some(spec_decl) } + fn get_expr_compute_kind(&self, expr_id: ExprId) -> ComputeKind { + let current_package_id = self.get_current_package_id(); + let store_expr_id = StoreExprId::from((current_package_id, expr_id)); + let expr_generator_set = self.compute_properties.get_expr(store_expr_id); + let callable_scope = self.eval_context.get_current_scope(); + expr_generator_set.generate_application_compute_kind(&callable_scope.args_value_kind) + } + + fn get_stmt_compute_kind(&self, stmt_id: StmtId) -> ComputeKind { + let current_package_id = self.get_current_package_id(); + let store_stmt_id = StoreStmtId::from((current_package_id, stmt_id)); + let stmt_generator_set = self.compute_properties.get_stmt(store_stmt_id); + let callable_scope = self.eval_context.get_current_scope(); + stmt_generator_set.generate_application_compute_kind(&callable_scope.args_value_kind) + } + fn get_or_insert_callable(&mut self, callable: Callable) -> CallableId { // Check if the callable is already in the program, and if not add it. let callable_name = callable.name.clone(); @@ -633,13 +936,20 @@ impl<'a> PartialEvaluator<'a> { .expect("callable not present") } + fn get_program_block_mut(&mut self, id: rir::BlockId) -> &mut rir::Block { + self.program + .blocks + .get_mut(id) + .expect("program block does not exist") + } + + fn is_classical_expr(&self, expr_id: ExprId) -> bool { + let compute_kind = self.get_expr_compute_kind(expr_id); + matches!(compute_kind, ComputeKind::Classical) + } + fn is_classical_stmt(&self, stmt_id: StmtId) -> bool { - let current_package_id = self.get_current_package_id(); - let store_stmt_id = StoreStmtId::from((current_package_id, stmt_id)); - let stmt_generator_set = self.compute_properties.get_stmt(store_stmt_id); - let callable_scope = self.eval_context.get_current_scope(); - let compute_kind = stmt_generator_set - .generate_application_compute_kind(&callable_scope.args_runtime_properties); + let compute_kind = self.get_stmt_compute_kind(stmt_id); matches!(compute_kind, ComputeKind::Classical) } @@ -648,12 +958,10 @@ impl<'a> PartialEvaluator<'a> { Value::Qubit(qubit) } - fn measure_qubit(&mut self, measure_callable: Callable, args_value: &Value) -> Value { + fn measure_qubit(&mut self, measure_callable: Callable, args_value: Value) -> Value { // Get the qubit and result IDs to use in the qubit measure instruction. - let Value::Qubit(qubit) = args_value else { - panic!("argument to qubit measure is expected to be a qubit"); - }; - let qubit_value = Value::Qubit(*qubit); + let qubit = args_value.unwrap_qubit(); + let qubit_value = Value::Qubit(qubit); let qubit_operand = map_eval_value_to_rir_operand(&qubit_value); let result_value = Value::Result(self.resource_manager.next_result()); let result_operand = map_eval_value_to_rir_operand(&result_value); @@ -669,30 +977,263 @@ impl<'a> PartialEvaluator<'a> { result_value } - fn release_qubit(&mut self, args_value: &Value) -> Value { - let Value::Qubit(qubit) = args_value else { - panic!("argument to qubit release is expected to be a qubit"); - }; - self.resource_manager.release_qubit(*qubit); + fn release_qubit(&mut self, args_value: Value) -> Value { + let qubit = args_value.unwrap_qubit(); + self.resource_manager.release_qubit(qubit); // The value of a qubit release is unit. Value::unit() } + fn resolve_args(&self, store_pat_id: StorePatId, value: Value) -> Vec { + let pat = self.package_store.get_pat(store_pat_id); + match &pat.kind { + PatKind::Discard => vec![Arg::Discard(value)], + PatKind::Bind(ident) => { + let variable = Variable { + name: ident.name.clone(), + value, + span: ident.span, + }; + vec![Arg::Var(ident.id, variable)] + } + PatKind::Tuple(pats) => { + let values = value.unwrap_tuple(); + assert_eq!( + pats.len(), + values.len(), + "pattern tuple and value tuple have different arity" + ); + let mut args = Vec::new(); + let pat_value_tuples = pats.iter().zip(values.to_vec()); + for (pat_id, value) in pat_value_tuples { + let mut element_args = + self.resolve_args((store_pat_id.package, *pat_id).into(), value); + args.append(&mut element_args); + } + args + } + } + } + + fn try_eval_block(&mut self, block_id: BlockId) -> Result { + self.visit_block(block_id); + if self.errors.is_empty() { + Ok(self.eval_context.get_current_scope().last_expr_value()) + } else { + Err(()) + } + } + fn try_eval_expr(&mut self, expr_id: ExprId) -> Result { // Visit the expression, which will either populate the expression entry in the scope's value map or add an // error. self.visit_expr(expr_id); if self.errors.is_empty() { - let expr_value = self - .eval_context - .get_current_scope() - .get_expr_value(expr_id); - Ok(expr_value.clone()) + Ok(self.eval_context.get_current_scope().last_expr_value()) } else { Err(()) } } + + fn generate_output_recording_instructions( + &mut self, + ret_val: Value, + ty: &Ty, + ) -> Vec { + let mut instrs = Vec::new(); + + match ret_val { + Value::Array(vals) => self.record_array(ty, &mut instrs, &vals), + Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals), + Value::Result(res) => self.record_result(&mut instrs, res), + Value::Var(var) => self.record_variable(ty, &mut instrs, var), + Value::Bool(val) => self.record_bool(&mut instrs, val), + Value::Int(val) => self.record_int(&mut instrs, val), + + Value::BigInt(_) + | Value::Closure(_) + | Value::Double(_) + | Value::Global(_, _) + | Value::Pauli(_) + | Value::Qubit(_) + | Value::Range(_) + | Value::String(_) => panic!("unsupported value type in output recording"), + } + + instrs + } + + fn record_int(&mut self, instrs: &mut Vec, val: i64) { + let int_record_callable_id = self.get_int_record_callable(); + instrs.push(Instruction::Call( + int_record_callable_id, + vec![ + Operand::Literal(Literal::Integer(val)), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_bool(&mut self, instrs: &mut Vec, val: bool) { + let bool_record_callable_id = self.get_bool_record_callable(); + instrs.push(Instruction::Call( + bool_record_callable_id, + vec![ + Operand::Literal(Literal::Bool(val)), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_variable(&mut self, ty: &Ty, instrs: &mut Vec, var: Var) { + let (record_callable_id, record_ty) = match ty { + Ty::Prim(Prim::Bool) => (self.get_bool_record_callable(), rir::Ty::Boolean), + Ty::Prim(Prim::Int) => (self.get_int_record_callable(), rir::Ty::Integer), + _ => panic!("unsupported variable type in output recording"), + }; + instrs.push(Instruction::Call( + record_callable_id, + vec![ + Operand::Variable(rir::Variable { + variable_id: var.0.into(), + ty: record_ty, + }), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_result(&mut self, instrs: &mut Vec, res: val::Result) { + let result_record_callable_id = self.get_result_record_callable(); + instrs.push(Instruction::Call( + result_record_callable_id, + vec![ + Operand::Literal(Literal::Result( + res.unwrap_id() + .try_into() + .expect("result id should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + } + + fn record_tuple(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc<[Value]>) { + let Ty::Tuple(elem_tys) = ty else { + panic!("expected tuple type for tuple value"); + }; + let tuple_record_callable_id = self.get_tuple_record_callable(); + instrs.push(Instruction::Call( + tuple_record_callable_id, + vec![ + Operand::Literal(Literal::Integer( + vals.len() + .try_into() + .expect("tuple length should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + for (val, elem_ty) in vals.iter().zip(elem_tys.iter()) { + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + } + } + + fn record_array(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc>) { + let Ty::Array(elem_ty) = ty else { + panic!("expected array type for array value"); + }; + let array_record_callable_id = self.get_array_record_callable(); + instrs.push(Instruction::Call( + array_record_callable_id, + vec![ + Operand::Literal(Literal::Integer( + vals.len() + .try_into() + .expect("array length should fit into u32"), + )), + Operand::Literal(Literal::Pointer), + ], + None, + )); + for val in vals.iter() { + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + } + } + + fn get_array_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__array_record_output") { + return *id; + } + + let callable = builder::array_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__array_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_tuple_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__tuple_record_output") { + return *id; + } + + let callable = builder::tuple_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__tuple_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_result_record_callable(&mut self) -> CallableId { + if let Some(id) = self + .callables_map + .get("__quantum__rt__result_record_output") + { + return *id; + } + + let callable = builder::result_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__result_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_bool_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__bool_record_output") { + return *id; + } + + let callable = builder::bool_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__bool_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_int_record_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__rt__int_record_output") { + return *id; + } + + let callable = builder::int_record_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__rt__int_record_output".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } } impl<'a> Visitor<'a> for PartialEvaluator<'a> { @@ -733,14 +1274,8 @@ impl<'a> Visitor<'a> for PartialEvaluator<'a> { "visiting an expression when errors have already happened should never happen" ); - // Determine whether the expression is classical since how we evaluate it depends on it. - let current_package_id = self.get_current_package_id(); - let store_expr_id = StoreExprId::from((current_package_id, expr_id)); - let expr_generator_set = self.compute_properties.get_expr(store_expr_id); - let callable_scope = self.eval_context.get_current_scope(); - let compute_kind = expr_generator_set - .generate_application_compute_kind(&callable_scope.args_runtime_properties); - let expr_result = if matches!(compute_kind, ComputeKind::Classical) { + // We evaluate an expression differently depending on whether it is classical or not. + let expr_result = if self.is_classical_expr(expr_id) { self.eval_classical_expr(expr_id) } else { self.eval_expr(expr_id) @@ -767,12 +1302,17 @@ impl<'a> Visitor<'a> for PartialEvaluator<'a> { let store_stmt_id = StoreStmtId::from((self.get_current_package_id(), stmt_id)); let stmt = self.package_store.get_stmt(store_stmt_id); match stmt.kind { - StmtKind::Expr(expr_id) | StmtKind::Semi(expr_id) => { + StmtKind::Expr(expr_id) => { self.visit_expr(expr_id); } - StmtKind::Local(_, pat_id, expr_id) => { + StmtKind::Semi(expr_id) => { self.visit_expr(expr_id); - self.bind_expr_to_pat(pat_id, expr_id); + self.eval_context.get_current_scope_mut().clear_last_expr(); + } + StmtKind::Local(_, pat_id, expr_id) => { + if let Ok(value) = self.try_eval_expr(expr_id) { + self.bind_value_to_pat(pat_id, value); + } } StmtKind::Item(_) => { // Do nothing. @@ -842,48 +1382,3 @@ fn map_fir_type_to_rir_type(ty: &Ty) -> rir::Ty { Prim::Result => rir::Ty::Result, } } - -fn mresetz_callable() -> Callable { - Callable { - name: "__quantum__qis__mresetz__body".to_string(), - input_type: vec![rir::Ty::Qubit, rir::Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn mz_callable() -> Callable { - Callable { - name: "__quantum__qis__mz__body".to_string(), - input_type: vec![rir::Ty::Qubit, rir::Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn read_result_callable() -> Callable { - Callable { - name: "__quantum__rt__read_result__body".to_string(), - input_type: vec![rir::Ty::Result], - output_type: Some(rir::Ty::Boolean), - body: None, - call_type: CallableType::Readout, - } -} - -fn resolve_call_arg_operands(args_value: Value) -> Vec { - let mut operands = Vec::::new(); - if let Value::Tuple(elements) = args_value { - for value in elements.iter() { - let operand = map_eval_value_to_rir_operand(value); - operands.push(operand); - } - } else { - let operand = map_eval_value_to_rir_operand(&args_value); - operands.push(operand); - } - - operands -} diff --git a/compiler/qsc_partial_eval/src/management.rs b/compiler/qsc_partial_eval/src/management.rs index 20fff94865..7b9d16d5f6 100644 --- a/compiler/qsc_partial_eval/src/management.rs +++ b/compiler/qsc_partial_eval/src/management.rs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use num_bigint::BigUint; +use num_complex::Complex; use qsc_eval::{ backend::Backend, - val::{Qubit, Result}, + val::{Qubit, Result, Value}, }; use qsc_rir::rir::{BlockId, CallableId, VariableId}; @@ -17,6 +19,14 @@ pub struct ResourceManager { } impl ResourceManager { + pub fn qubit_count(&self) -> usize { + self.qubits_in_use.len() + } + + pub fn results_count(&self) -> usize { + self.next_result + } + pub fn allocate_qubit(&mut self) -> Qubit { if let Some(qubit_id) = self.qubits_in_use.iter().position(|in_use| !in_use) { self.qubits_in_use[qubit_id] = true; @@ -72,4 +82,22 @@ impl Backend for QuantumIntrinsicsChecker { // true to avoid a panic. true } + + // Needed for calls to `DumpMachine` and `DumpRegister`. + fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex)>, usize) { + (Vec::new(), 0) + } + + // Only intrinsic functions are supported here since they're the only ones that will be classically evaluated. + fn custom_intrinsic( + &mut self, + name: &str, + _arg: Value, + ) -> Option> { + match name { + "BeginEstimateCaching" => Some(Ok(Value::Bool(true))), + "EndEstimateCaching" => Some(Ok(Value::unit())), + _ => None, + } + } } diff --git a/compiler/qsc_partial_eval/tests/branching.rs b/compiler/qsc_partial_eval/tests/branching.rs new file mode 100644 index 0000000000..f8364ee00d --- /dev/null +++ b/compiler/qsc_partial_eval/tests/branching.rs @@ -0,0 +1,1291 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow( + clippy::needless_raw_string_hashes, + clippy::similar_names, + clippy::too_many_lines +)] + +pub mod test_utils; + +use expect_test::expect; +use indoc::indoc; +use qsc_rir::rir::CallableId; +use test_utils::{assert_blocks, assert_callable, compile_and_partially_evaluate}; + +#[test] +fn if_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if true { + opA(q); + } + } + } + "#, + }); + let op_a_callable_id = CallableId(1); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn if_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } + } + } + "#, + }); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn if_else_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if true { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + let op_a_callable_id = CallableId(1); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn if_else_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + let op_b_callable_id = CallableId(1); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn if_elif_else_expression_with_true_elif_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if false { + opA(q); + } elif true { + opB(q); + } else { + opC(q); + } + } + } + "#, + }); + let op_b_callable_id = CallableId(1); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn if_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + opA(q); + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], + ); +} + +#[test] +fn if_else_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + opB(q); + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], + ); +} + +#[test] +fn if_elif_else_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == One { + opA(q2); + } elif r1 == One { + opB(q2); + } else { + opC(q2); + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_c_callable_id = CallableId(5); + assert_callable( + &program, + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(6), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(2), ) + Jump(1) + Block 3:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Branch Variable(3, Boolean), 5, 6 + Block 4:Block: + Jump(1) + Block 5:Block: + Call id(4), args( Qubit(2), ) + Jump(4) + Block 6:Block: + Call id(5), args( Qubit(2), ) + Jump(4)"#]], + ); +} + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + if true { + opA(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], + ); +} + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + if false { + opA(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(3), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Jump(1)"#]], + ); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_true_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + if true { + opB(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], + ); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_nested_if_expression_with_false_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + if false { + opB(q); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Jump(1)"#]], + ); +} + +#[test] +fn if_expression_with_dynamic_condition_and_nested_if_expression_with_dynamic_condition() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == Zero { + if r1 == One { + opA(q2); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Branch Variable(3, Boolean), 4, 3 + Block 3:Block: + Jump(1) + Block 4:Block: + Call id(3), args( Qubit(2), ) + Jump(3)"#]], + ); +} + +#[allow(clippy::too_many_lines)] +#[test] +fn doubly_nested_if_else_expressions_with_dynamic_conditions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + operation opD(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); + let r0 = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let r1 = QIR.Intrinsic.__quantum__qis__mresetz__body(q1); + if r0 == Zero { + if r1 == Zero { + opA(q2); + } else { + opB(q2); + } + } else { + if r1 == One{ + opC(q2); + } else { + opD(q2); + } + } + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_c_callable_id = CallableId(5); + assert_callable( + &program, + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_d_callable_id = CallableId(6); + assert_callable( + &program, + op_d_callable_id, + &expect![[r#" + Callable: + name: opD + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 6 + Block 1:Block: + Call id(7), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Boolean) = Call id(2), args( Result(1), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(false) + Branch Variable(3, Boolean), 4, 5 + Block 3:Block: + Jump(1) + Block 4:Block: + Call id(3), args( Qubit(2), ) + Jump(3) + Block 5:Block: + Call id(4), args( Qubit(2), ) + Jump(3) + Block 6:Block: + Variable(4, Boolean) = Call id(2), args( Result(1), ) + Variable(5, Boolean) = Icmp Eq, Variable(4, Boolean), Bool(true) + Branch Variable(5, Boolean), 8, 9 + Block 7:Block: + Jump(1) + Block 8:Block: + Call id(5), args( Qubit(2), ) + Jump(7) + Block 9:Block: + Call id(6), args( Qubit(2), ) + Jump(7)"#]], + ); +} + +#[test] +fn if_expression_with_dynamic_condition_and_subsequent_call_to_operation() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == Zero { + opA(q); + } + opB(q); + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Qubit(0), ) + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1)"#]], + ); +} + +#[test] +fn if_else_expression_with_dynamic_condition_and_subsequent_call_to_operation() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + operation opC(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + if r == One { + opA(q); + } else { + opB(q); + } + opC(q); + } + } + "#, + }); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: opA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: opB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_c_callable_id = CallableId(5); + assert_callable( + &program, + op_c_callable_id, + &expect![[r#" + Callable: + name: opC + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Qubit(0), ) + Call id(6), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(0), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(0), ) + Jump(1)"#]], + ); +} diff --git a/compiler/qsc_partial_eval/tests/calls.rs b/compiler/qsc_partial_eval/tests/calls.rs new file mode 100644 index 0000000000..209b448228 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/calls.rs @@ -0,0 +1,709 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow( + clippy::needless_raw_string_hashes, + clippy::similar_names, + clippy::too_many_lines +)] + +pub mod test_utils; + +use expect_test::expect; +use indoc::indoc; +use qsc_rir::rir::{BlockId, CallableId}; +use test_utils::{ + assert_block_instructions, assert_blocks, assert_callable, compile_and_partially_evaluate, +}; + +#[test] +fn call_to_single_qubit_unitary_with_two_calls_to_the_same_intrinsic() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Op(q : Qubit) : Unit { body intrinsic; } + operation OpSquared(q : Qubit) : Unit { + Op(q); + Op(q); + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + OpSquared(q); + } + } + "#}); + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_single_qubit_unitary_with_calls_to_different_intrinsics() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation OpA(q : Qubit) : Unit { body intrinsic; } + operation OpB(q : Qubit) : Unit { body intrinsic; } + operation Combined(q : Qubit) : Unit { + OpA(q); + OpB(q); + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + Combined(q); + } + } + "#}); + let op_a_callable_id = CallableId(1); + let op_b_callable_id = CallableId(2); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: OpA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: OpB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Qubit(0), ) + Call id(3), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_two_qubit_unitary() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Op(q0 : Qubit, q1 : Qubit) : Unit { body intrinsic; } + operation ApplyOpCombinations(q0 : Qubit, q1 : Qubit) : Unit { + Op(q0, q1); + Op(q1, q0); + } + @EntryPoint() + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + ApplyOpCombinations(q0, q1); + } + } + "#}); + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(1), args( Qubit(1), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_unitary_that_receives_double_and_qubit() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation DoubleFirst(d : Double, q : Qubit) : Unit { body intrinsic; } + operation QubitFirst(q : Qubit, d : Double) : Unit { body intrinsic; } + operation Op(d : Double, q : Qubit) : Unit { + DoubleFirst(d, q); + QubitFirst(q, d); + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + Op(1.0, q); + } + } + "#}); + let double_first_callable_id = CallableId(1); + let qubit_first_callable_id = CallableId(2); + assert_callable( + &program, + double_first_callable_id, + &expect![[r#" + Callable: + name: DoubleFirst + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); + assert_callable( + &program, + qubit_first_callable_id, + &expect![[r#" + Callable: + name: QubitFirst + call_type: Regular + input_type: + [0]: Qubit + [1]: Double + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Double(1), Qubit(0), ) + Call id(2), args( Qubit(0), Double(1), ) + Call id(3), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn calls_to_unitary_that_conditionally_calls_intrinsic_with_classical_bool() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation OpA(q : Qubit) : Unit { body intrinsic; } + operation OpB(q : Qubit) : Unit { body intrinsic; } + operation ConditionallyCallOp(b : Bool, q : Qubit) : Unit { + if b { + OpA(q); + } else { + OpB(q); + } + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + ConditionallyCallOp(true, q); + ConditionallyCallOp(false, q); + } + } + "#}); + let op_a_callable_id = CallableId(1); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: OpA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(2); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: OpB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(3); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Qubit(0), ) + Call id(3), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn calls_to_unitary_that_conditionally_calls_intrinsic_with_dynamic_bool() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation OpA(q : Qubit) : Unit { body intrinsic; } + operation OpB(q : Qubit) : Unit { body intrinsic; } + operation ConditionallyCallOp(b : Bool, q : Qubit) : Unit { + if b { + OpA(q); + } else { + OpB(q); + } + } + @EntryPoint() + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + let r = QIR.Intrinsic.__quantum__qis__m__body(q0); + ConditionallyCallOp(r == One, q1); + } + } + "#}); + let measure_callable_id = CallableId(1); + assert_callable( + &program, + measure_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_a_callable_id = CallableId(3); + assert_callable( + &program, + op_a_callable_id, + &expect![[r#" + Callable: + name: OpA + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let op_b_callable_id = CallableId(4); + assert_callable( + &program, + op_b_callable_id, + &expect![[r#" + Callable: + name: OpB + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(5); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(5), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(1), ) + Jump(1) + Block 3:Block: + Call id(4), args( Qubit(1), ) + Jump(1)"#]], + ); +} + +#[test] +fn call_to_unitary_rotation_unitary_with_computation() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Rotation(d : Double, q : Qubit) : Unit { body intrinsic; } + operation RotationWithComputation(d : Double, q : Qubit) : Unit { + Rotation(2.0 * d, q); + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + RotationWithComputation(2.0, q); + RotationWithComputation(3.0, q); + } + } + "#}); + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &expect![[r#" + Callable: + name: Rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Double(4), Qubit(0), ) + Call id(1), args( Double(6), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_operation_that_returns_measurement_result() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Op(q : Qubit) : Result { + QIR.Intrinsic.__quantum__qis__m__body(q) + } + @EntryPoint() + operation Main() : Result { + use q = Qubit(); + Op(q) + } + } + "#}); + let measure_callable_id = CallableId(1); + assert_callable( + &program, + measure_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(2); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Result(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_operation_that_returns_dynamic_bool() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Op(q : Qubit) : Bool { + let r = QIR.Intrinsic.__quantum__qis__m__body(q); + r == Zero + } + @EntryPoint() + operation Main() : Bool { + use q = Qubit(); + Op(q) + } + } + "#}); + let measure_callable_id = CallableId(1); + assert_callable( + &program, + measure_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let output_recording_callable_id = CallableId(3); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Call id(3), args( Variable(1, Boolean), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_boolean_function_using_result_literal_as_argument_yields_constant() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + operation Op(q : Qubit) : Unit { body intrinsic; } + function ResultAsBool(r : Result) : Bool { + r == One + } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + // Only one call to `Op` should be generated. + if ResultAsBool(Zero) { + Op(q); + } + if ResultAsBool(One) { + Op(q); + } + } + } + "#}); + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(2); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_boolean_function_using_dynamic_result_as_argument_generates_branches() { + let program = compile_and_partially_evaluate(indoc! {r#" + namespace Test { + open QIR.Intrinsic; + operation Op(q : Qubit) : Unit { body intrinsic; } + function ResultAsBool(r : Result) : Bool { + r == One + } + @EntryPoint() + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + let r = __quantum__qis__m__body(q0); + // Only one call to `Op` should be generated. + if ResultAsBool(r) { + Op(q1); + } + } + } + "#}); + let measure_callable_id = CallableId(1); + assert_callable( + &program, + measure_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_callable_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(4); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Branch Variable(1, Boolean), 2, 1 + Block 1:Block: + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Call id(3), args( Qubit(1), ) + Jump(1)"#]], + ); +} diff --git a/compiler/qsc_partial_eval/tests/classical_args.rs b/compiler/qsc_partial_eval/tests/classical_args.rs index e7c5c439d5..d3a85f6786 100644 --- a/compiler/qsc_partial_eval/tests/classical_args.rs +++ b/compiler/qsc_partial_eval/tests/classical_args.rs @@ -3,24 +3,13 @@ #![allow(clippy::needless_raw_string_hashes)] -mod test_utils; +pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn double_to_unit_intrinsic_op() -> Callable { - Callable { - name: "op".to_string(), - input_type: vec![Ty::Double], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - #[test] fn call_to_intrinsic_operation_using_double_literal() { let program = compile_and_partially_evaluate(indoc! {r#" @@ -33,18 +22,26 @@ fn call_to_intrinsic_operation_using_double_literal() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -63,28 +60,28 @@ fn calls_to_intrinsic_operation_using_inline_expressions() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(0.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(1.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(0), ) + Call id(1), args( Double(1), ) + Call id(1), args( Double(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -106,27 +103,27 @@ fn calls_to_intrinsic_operation_using_variables() { } "#}); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &double_to_unit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Double + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(2.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(4.0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Double(8.0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Double(2), ) + Call id(1), args( Double(4), ) + Call id(1), args( Double(8), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } diff --git a/compiler/qsc_partial_eval/tests/dynamic_vars.rs b/compiler/qsc_partial_eval/tests/dynamic_vars.rs new file mode 100644 index 0000000000..a3c9c7b7c0 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/dynamic_vars.rs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes, clippy::similar_names)] + +pub mod test_utils; + +use expect_test::expect; +use indoc::indoc; +use qsc_rir::rir::CallableId; +use test_utils::{assert_callable, compile_and_partially_evaluate}; + +use crate::test_utils::assert_blocks; + +#[test] +fn dynamic_int_from_if_expression_with_single_measurement_comparison_and_classical_blocks() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + let b = if r == Zero { 0 } else { 1 }; + } + } + "#, + }); + println!("{program}"); + + // Verify the callables added to the program. + let mresetz_callable_id = CallableId(1); + assert_callable( + &program, + mresetz_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(3), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Integer) = Store Integer(0) + Jump(1) + Block 3:Block: + Variable(2, Integer) = Store Integer(1) + Jump(1)"#]], + ); +} + +#[test] +#[should_panic(expected = "() cannot be mapped to a RIR operand")] +fn dynamic_int_from_if_expression_with_single_measurement_comparison_and_non_classical_blocks() { + let _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation opA(q : Qubit) : Unit { body intrinsic; } + operation opB(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use (q0, q1) = (Qubit(), Qubit()); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q0); + let b = if r == Zero { + opA(q1); + 0 + } else { + opB(q1); + 1 + }; + } + } + "#, + }); +} diff --git a/compiler/qsc_partial_eval/tests/intrinsics.rs b/compiler/qsc_partial_eval/tests/intrinsics.rs index 0290820f1f..427e7a76ff 100644 --- a/compiler/qsc_partial_eval/tests/intrinsics.rs +++ b/compiler/qsc_partial_eval/tests/intrinsics.rs @@ -3,16 +3,19 @@ #![allow(clippy::needless_raw_string_hashes)] -mod test_utils; +pub mod test_utils; +use expect_test::{expect, Expect}; use indoc::{formatdoc, indoc}; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, +use qsc_rir::rir::{BlockId, CallableId}; +use test_utils::{ + assert_block_instructions, assert_blocks, assert_callable, compile_and_partially_evaluate, }; -use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; fn check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -30,27 +33,14 @@ fn check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &single_qubit_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -68,30 +58,14 @@ fn check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_in .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &single_qubit_rotation_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Double(0.0)), - Operand::Literal(Literal::Qubit(0)), - ], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -109,31 +83,14 @@ fn check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_inst .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &two_qubits_rotation_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Double(0.0)), - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - ], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -151,30 +108,14 @@ fn check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &two_qubits_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - ], - None, - ), - Instruction::Return, - ], - ); + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } fn check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction( intrinsic_name: &str, + expected_callable: &Expect, + expected_block: &Expect, ) { let program = compile_and_partially_evaluate( formatdoc! { @@ -192,103 +133,27 @@ fn check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction .as_str(), ); let op_callable_id = CallableId(1); - assert_callable( - &program, - op_callable_id, - &three_qubits_intrinsic_op(intrinsic_name), - ); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Qubit(2)), - ], - None, - ), - Instruction::Return, - ], - ); -} - -fn measurement_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn reset_intrinsic_op() -> Callable { - Callable { - name: "__quantum__qis__reset__body".to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Reset, - } -} - -fn single_qubit_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn single_qubit_rotation_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Double, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn two_qubits_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn two_qubits_rotation_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Double, Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - -fn three_qubits_intrinsic_op(name: &str) -> Callable { - Callable { - name: name.to_string(), - input_type: vec![Ty::Qubit, Ty::Qubit, Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } + assert_callable(&program, op_callable_id, expected_callable); + assert_block_instructions(&program, BlockId(0), expected_block); } #[test] fn call_to_intrinsic_h_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__h__body", + &expect![[r#" + Callable: + name: __quantum__qis__h__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -296,6 +161,19 @@ fn call_to_intrinsic_h_adds_callable_and_generates_instruction() { fn call_to_intrinsic_s_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__s__body", + &expect![[r#" + Callable: + name: __quantum__qis__s__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -303,6 +181,19 @@ fn call_to_intrinsic_s_adds_callable_and_generates_instruction() { fn call_to_intrinsic_adjoint_s_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__s__adj", + &expect![[r#" + Callable: + name: __quantum__qis__s__adj + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -310,6 +201,19 @@ fn call_to_intrinsic_adjoint_s_adds_callable_and_generates_instruction() { fn call_to_intrinsic_t_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__t__body", + &expect![[r#" + Callable: + name: __quantum__qis__t__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -317,6 +221,19 @@ fn call_to_intrinsic_t_adds_callable_and_generates_instruction() { fn call_to_intrinsic_adjoint_t_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__t__adj", + &expect![[r#" + Callable: + name: __quantum__qis__t__adj + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -324,6 +241,19 @@ fn call_to_intrinsic_adjoint_t_adds_callable_and_generates_instruction() { fn call_to_intrinsic_x_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__x__body", + &expect![[r#" + Callable: + name: __quantum__qis__x__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -331,6 +261,19 @@ fn call_to_intrinsic_x_adds_callable_and_generates_instruction() { fn call_to_intrinsic_y_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__y__body", + &expect![[r#" + Callable: + name: __quantum__qis__y__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -338,6 +281,19 @@ fn call_to_intrinsic_y_adds_callable_and_generates_instruction() { fn call_to_intrinsic_z_adds_callable_and_generates_instruction() { check_call_to_single_qubit_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__z__body", + &expect![[r#" + Callable: + name: __quantum__qis__z__body + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -345,6 +301,20 @@ fn call_to_intrinsic_z_adds_callable_and_generates_instruction() { fn call_to_intrinsic_swap_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__swap__body", + &expect![[r#" + Callable: + name: __quantum__qis__swap__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -352,6 +322,20 @@ fn call_to_intrinsic_swap_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cx_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cx__body", + &expect![[r#" + Callable: + name: __quantum__qis__cx__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -359,6 +343,20 @@ fn call_to_intrinsic_cx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cy_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cy__body", + &expect![[r#" + Callable: + name: __quantum__qis__cy__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -366,6 +364,20 @@ fn call_to_intrinsic_cy_adds_callable_and_generates_instruction() { fn call_to_intrinsic_cz_adds_callable_and_generates_instruction() { check_call_to_two_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__cz__body", + &expect![[r#" + Callable: + name: __quantum__qis__cz__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -373,6 +385,21 @@ fn call_to_intrinsic_cz_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ccx_adds_callable_and_generates_instruction() { check_call_to_three_qubits_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ccx__body", + &expect![[r#" + Callable: + name: __quantum__qis__ccx__body + call_type: Regular + input_type: + [0]: Qubit + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Qubit(1), Qubit(2), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -380,6 +407,20 @@ fn call_to_intrinsic_ccx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rx_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rx__body", + &expect![[r#" + Callable: + name: __quantum__qis__rx__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -387,6 +428,21 @@ fn call_to_intrinsic_rx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rxx_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rxx__body", + &expect![[r#" + Callable: + name: __quantum__qis__rxx__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -394,6 +450,20 @@ fn call_to_intrinsic_rxx_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ry_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ry__body", + &expect![[r#" + Callable: + name: __quantum__qis__ry__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -401,6 +471,21 @@ fn call_to_intrinsic_ry_adds_callable_and_generates_instruction() { fn call_to_intrinsic_ryy_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__ryy__body", + &expect![[r#" + Callable: + name: __quantum__qis__ryy__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -408,6 +493,20 @@ fn call_to_intrinsic_ryy_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rz_adds_callable_and_generates_instruction() { check_call_to_single_qubit_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rz__body", + &expect![[r#" + Callable: + name: __quantum__qis__rz__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -415,6 +514,21 @@ fn call_to_intrinsic_rz_adds_callable_and_generates_instruction() { fn call_to_intrinsic_rzz_adds_callable_and_generates_instruction() { check_call_to_two_qubits_rotation_instrinsic_adds_callable_and_generates_instruction( "__quantum__qis__rzz__body", + &expect![[r#" + Callable: + name: __quantum__qis__rzz__body + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + [2]: Qubit + output_type: + body: "#]], + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), Qubit(1), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -432,18 +546,26 @@ fn check_partial_eval_for_call_to_reset() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &reset_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__reset__body + call_type: Reset + input_type: + [0]: Qubit + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -464,22 +586,24 @@ fn call_to_intrinsic_m_adds_callable_and_generates_instruction() { assert_callable( &program, op_callable_id, - &measurement_intrinsic_op("__quantum__qis__mz__body"), + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); } @@ -500,21 +624,354 @@ fn call_to_intrinsic_mresetz_adds_callable_and_generates_instruction() { assert_callable( &program, op_callable_id, - &measurement_intrinsic_op("__quantum__qis__mresetz__body"), + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn calls_to_intrinsic_begin_estimate_caching_with_classical_values_always_yield_true() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + operation Op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + if BeginEstimateCaching("test0", 0) { + Op(q); + } + if BeginEstimateCaching("test1", 1) { + Op(q); + } + } + } + "#, + }); + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_intrinsic_begin_estimate_caching_with_dynamic_values_yields_true() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + open QIR.Intrinsic; + operation Op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let i = __quantum__qis__m__body(q) == Zero ? 0 | 1; + if BeginEstimateCaching("test0", i) { + Op(q); + } + } + } + "#, + }); + let measure_callable_id = CallableId(1); + assert_callable( + &program, + measure_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + let read_result_callable_id = CallableId(2); + assert_callable( + &program, + read_result_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let op_callable_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: Op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let output_recording_callable_id = CallableId(4); + assert_callable( + &program, + output_recording_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_blocks( + &program, + &expect![[r#" + Blocks: + Block 0:Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Branch Variable(1, Boolean), 2, 3 + Block 1:Block: + Call id(3), args( Qubit(0), ) + Call id(4), args( Integer(0), Pointer, ) + Return + Block 2:Block: + Variable(2, Integer) = Store Integer(0) + Jump(1) + Block 3:Block: + Variable(2, Integer) = Store Integer(1) + Jump(1)"#]], + ); +} + +#[test] +fn call_to_intrinsic_end_estimate_caching_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + @EntryPoint() + operation Main() : Unit { + EndEstimateCaching(); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_account_for_estimates_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + @EntryPoint() + operation Main() : Unit { + // Calls to internal operation `AccountForEstimatesInternal`, which is intrinsic. + AccountForEstimates([], 0, []); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_begin_repeat_estimates_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + @EntryPoint() + operation Main() : Unit { + // Calls to internal operation `BeginRepeatEstimatesInternal`, which is intrinsic. + BeginRepeatEstimates(0); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_end_repeat_estimates_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.ResourceEstimation; + @EntryPoint() + operation Main() : Unit { + // Calls to internal operation `EndRepeatEstimatesInternal`, which is intrinsic. + EndRepeatEstimates(); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn call_to_dump_machine_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.Diagnostics; + @EntryPoint() + operation Main() : Unit { + DumpMachine(); + } + } + "#, + }); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], ); +} + +#[test] +fn call_to_dump_register_does_not_generate_instructions() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.Diagnostics; + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + DumpRegister([q]); + } + } + "#, + }); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Integer(0), Pointer, ) + Return"#]], ); } + +#[test] +#[should_panic(expected = "`CheckZero` is not a supported by partial evaluation")] +fn call_to_check_zero_panics() { + _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.Diagnostics; + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let _ = CheckZero(q); + } + } + "#, + }); +} + +#[test] +#[should_panic(expected = "`DrawRandomInt` is not a supported by partial evaluation")] +fn call_to_draw_random_int_panics() { + _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.Random; + @EntryPoint() + operation Main() : Unit { + let _ = DrawRandomInt(0, 1); + } + } + "#, + }); +} + +#[test] +#[should_panic(expected = "`DrawRandomDouble` is not a supported by partial evaluation")] +fn call_to_draw_random_double_panics() { + _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + open Microsoft.Quantum.Random; + @EntryPoint() + operation Main() : Unit { + let _ = DrawRandomDouble(0.0, 1.0); + } + } + "#, + }); +} diff --git a/compiler/qsc_partial_eval/tests/loops.rs b/compiler/qsc_partial_eval/tests/loops.rs new file mode 100644 index 0000000000..541e143adc --- /dev/null +++ b/compiler/qsc_partial_eval/tests/loops.rs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +pub mod test_utils; + +use expect_test::expect; +use indoc::indoc; +use qsc_rir::rir::{BlockId, CallableId}; +use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; + +#[test] +fn unitary_call_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for _ in 1..3 { + op(q); + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn unitary_call_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + while idx < 3 { + op(q); + set idx += 1; + } + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn unitary_call_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + repeat { + op(q); + set idx += 1; + } until idx >= 3; + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn rotation_call_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for theta in [0.0, 1.0, 2.0] { + rotation(theta, q); + } + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn rotation_call_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let angles = [0.0, 1.0, 2.0]; + mutable idx = 0; + while idx < 3 { + rotation(angles[idx], q); + set idx += 1; + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn rotation_call_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation rotation(theta : Double, q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let angles = [0.0, 1.0, 2.0]; + mutable idx = 0; + repeat { + rotation(angles[idx], q); + set idx += 1; + } until idx >= 3; + } + } + "#, + }); + + let rotation_callable_id = CallableId(1); + assert_callable( + &program, + rotation_callable_id, + &expect![[r#" + Callable: + name: rotation + call_type: Regular + input_type: + [0]: Double + [1]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Double(0), Qubit(0), ) + Call id(1), args( Double(1), Qubit(0), ) + Call id(1), args( Double(2), Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} diff --git a/compiler/qsc_partial_eval/tests/misc.rs b/compiler/qsc_partial_eval/tests/misc.rs new file mode 100644 index 0000000000..77940e2a43 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/misc.rs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +pub mod test_utils; + +use expect_test::expect; +use indoc::indoc; +use qsc_rir::rir::{BlockId, CallableId}; +use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_for_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + for idx in 0..5 { + if idx % 2 == 0 { + op(q); + } + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_while_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + while idx <= 5 { + if idx % 2 == 0 { + op(q); + } + set idx += 1; + } + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} + +#[test] +fn unitary_call_within_an_if_with_classical_condition_within_a_repeat_until_loop() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + operation op(q : Qubit) : Unit { body intrinsic; } + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + mutable idx = 0; + repeat { + if idx % 2 == 0 { + op(q); + } + set idx += 1; + } until idx > 5; + } + } + "#, + }); + + let op_callable_id = CallableId(1); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], + ); +} diff --git a/compiler/qsc_partial_eval/tests/output_recording.rs b/compiler/qsc_partial_eval/tests/output_recording.rs new file mode 100644 index 0000000000..3c9cd3c6f9 --- /dev/null +++ b/compiler/qsc_partial_eval/tests/output_recording.rs @@ -0,0 +1,526 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(clippy::needless_raw_string_hashes)] + +use expect_test::expect; +use indoc::indoc; +use test_utils::compile_and_partially_evaluate; + +pub mod test_utils; + +#[test] +fn output_recording_for_tuple_of_different_types() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, r == Zero) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(5), args( Variable(1, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_nested_tuples() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, (Bool, Result), (Bool,)) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, (r == Zero, r), (r == One,)) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(3), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(3), args( Integer(2), Pointer, ) + Call id(5), args( Variable(1, Boolean), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(3), args( Integer(1), Pointer, ) + Call id(5), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_tuple_of_arrays() { + // This program would not actually pass RCA checks as it shows up as using a dynamically sized array. + // However, the output recording should still be correct if/when we support this kind of return in the future. + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool[]) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, [r == Zero, r == One]) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 6: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Result(0), Pointer, ) + Call id(5), args( Integer(2), Pointer, ) + Call id(6), args( Variable(1, Boolean), Pointer, ) + Call id(6), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_array_of_tuples() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool)[] { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + [(r, r == Zero), (r, r == One)] + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: + Callable 3: Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 5: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 6: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(false) + Variable(2, Boolean) = Call id(2), args( Result(0), ) + Variable(3, Boolean) = Icmp Eq, Variable(2, Boolean), Bool(true) + Call id(3), args( Integer(2), Pointer, ) + Call id(4), args( Integer(2), Pointer, ) + Call id(5), args( Result(0), Pointer, ) + Call id(6), args( Variable(1, Boolean), Pointer, ) + Call id(4), args( Integer(2), Pointer, ) + Call id(5), args( Result(0), Pointer, ) + Call id(6), args( Variable(3, Boolean), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_literal_bool() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Bool { + true + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Bool(true), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 0 + num_results: 0"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_literal_int() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Int { + 42 + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__rt__integer_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Integer(42), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 0 + num_results: 0"#]] + .assert_eq(&program.to_string()); +} + +#[test] +fn output_recording_for_mix_of_literal_and_variable() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Bool) { + use q = Qubit(); + let r = QIR.Intrinsic.__quantum__qis__mresetz__body(q); + (r, true) + } + } + "#, + }); + + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: + body: 0 + Callable 1: Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: + Callable 2: Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: + Callable 3: Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: + Callable 4: Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: + blocks: + Block 0: Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Integer(2), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(4), args( Bool(true), Pointer, ) + Return + config: Config: + remap_qubits_on_reuse: false + defer_measurements: false + num_qubits: 1 + num_results: 1"#]] + .assert_eq(&program.to_string()); +} diff --git a/compiler/qsc_partial_eval/tests/qubits.rs b/compiler/qsc_partial_eval/tests/qubits.rs index 099e0bfdc3..bfaf208138 100644 --- a/compiler/qsc_partial_eval/tests/qubits.rs +++ b/compiler/qsc_partial_eval/tests/qubits.rs @@ -3,24 +3,13 @@ #![allow(clippy::needless_raw_string_hashes)] -mod test_utils; +pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Ty, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn single_qubit_intrinsic_op() -> Callable { - Callable { - name: "op".to_string(), - input_type: vec![Ty::Qubit], - output_type: None, - body: None, - call_type: CallableType::Regular, - } -} - #[test] fn qubit_ids_are_correct_for_allocate_use_release_one_qubit() { let program = compile_and_partially_evaluate(indoc! { @@ -36,20 +25,31 @@ fn qubit_ids_are_correct_for_allocate_use_release_one_qubit() { } "#, }); - let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); - assert_block_instructions( - &program, - BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], - ); + expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]] + .assert_eq(&program.get_callable(CallableId(1)).to_string()); + expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]] + .assert_eq(&program.get_callable(CallableId(2)).to_string()); + expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]] + .assert_eq(&program.get_block(BlockId(0)).to_string()); } #[test] @@ -74,29 +74,45 @@ fn qubit_ids_are_correct_for_allocate_use_release_multiple_qubits() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(1))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(1), ) + Call id(1), args( Qubit(2), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 3); + assert_eq!(program.num_results, 0); } #[test] @@ -121,29 +137,45 @@ fn qubit_ids_are_correct_for_allocate_use_release_one_qubit_multiple_times() { "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(0), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 0); } #[test] @@ -174,37 +206,45 @@ fn qubit_ids_are_correct_for_allocate_use_release_multiple_qubits_interleaved() "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &single_qubit_intrinsic_op()); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: op + call_type: Regular + input_type: + [0]: Qubit + output_type: + body: "#]], + ); + let tuple_callable_id = CallableId(2); + assert_callable( + &program, + tuple_callable_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(0))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(1))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(2))], - None, - ), - Instruction::Call( - op_callable_id, - vec![Operand::Literal(Literal::Qubit(3))], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), ) + Call id(1), args( Qubit(1), ) + Call id(1), args( Qubit(2), ) + Call id(1), args( Qubit(2), ) + Call id(1), args( Qubit(3), ) + Call id(2), args( Integer(0), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 4); + assert_eq!(program.num_results, 0); } diff --git a/compiler/qsc_partial_eval/tests/results.rs b/compiler/qsc_partial_eval/tests/results.rs index 72ebcd8b07..c6ef1c7303 100644 --- a/compiler/qsc_partial_eval/tests/results.rs +++ b/compiler/qsc_partial_eval/tests/results.rs @@ -3,75 +3,65 @@ #![allow(clippy::needless_raw_string_hashes)] -mod test_utils; +pub mod test_utils; +use expect_test::expect; use indoc::indoc; -use qsc_rir::rir::{ - BlockId, Callable, CallableId, CallableType, ConditionCode, Instruction, Literal, Operand, Ty, - Variable, VariableId, -}; +use qsc_rir::rir::{BlockId, CallableId}; use test_utils::{assert_block_instructions, assert_callable, compile_and_partially_evaluate}; -fn m_intrinsic_op() -> Callable { - Callable { - name: "__quantum__qis__mz__body".to_string(), - input_type: vec![Ty::Qubit, Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn mresetz_intrinsic_op() -> Callable { - Callable { - name: "__quantum__qis__mresetz__body".to_string(), - input_type: vec![Ty::Qubit, Ty::Result], - output_type: None, - body: None, - call_type: CallableType::Measurement, - } -} - -fn read_reasult_intrinsic_op() -> Callable { - Callable { - name: "__quantum__rt__read_result__body".to_string(), - input_type: vec![Ty::Result], - output_type: Some(Ty::Boolean), - body: None, - call_type: CallableType::Readout, - } -} - #[test] fn result_ids_are_correct_for_measuring_and_resetting_one_qubit() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__mresetz__body(q); + QIR.Intrinsic.__quantum__qis__mresetz__body(q) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &mresetz_intrinsic_op()); + let result_record_id = CallableId(2); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mresetz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Result(0), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 1); } #[test] @@ -80,30 +70,52 @@ fn result_ids_are_correct_for_measuring_one_qubit() { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__m__body(q); + QIR.Intrinsic.__quantum__qis__m__body(q) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let result_record_id = CallableId(2); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(2), args( Result(0), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 1); } #[test] @@ -112,98 +124,221 @@ fn result_ids_are_correct_for_measuring_one_qubit_multiple_times() { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : (Result, Result, Result) { use q = Qubit(); - QIR.Intrinsic.__quantum__qis__m__body(q); - QIR.Intrinsic.__quantum__qis__m__body(q); - QIR.Intrinsic.__quantum__qis__m__body(q); + (QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q)) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let tuple_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + tuple_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(2)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(0), Result(1), ) + Call id(1), args( Qubit(0), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], ); } +#[test] +fn result_ids_are_correct_for_measuring_one_qubit_multiple_times_into_array() { + let program = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Result[] { + use q = Qubit(); + [QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q), + QIR.Intrinsic.__quantum__qis__m__body(q)] + } + } + "#, + }); + let op_callable_id = CallableId(1); + let array_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + array_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__array_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); + assert_block_instructions( + &program, + BlockId(0), + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(0), Result(1), ) + Call id(1), args( Qubit(0), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], + ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 3); +} + #[test] fn result_ids_are_correct_for_measuring_multiple_qubits() { let program = compile_and_partially_evaluate(indoc! { r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : (Result, Result, Result) { use (q0, q1, q2) = (Qubit(), Qubit(), Qubit()); - QIR.Intrinsic.__quantum__qis__m__body(q0); - QIR.Intrinsic.__quantum__qis__m__body(q1); - QIR.Intrinsic.__quantum__qis__m__body(q2); + (QIR.Intrinsic.__quantum__qis__m__body(q0), + QIR.Intrinsic.__quantum__qis__m__body(q1), + QIR.Intrinsic.__quantum__qis__m__body(q2)) } } "#, }); let op_callable_id = CallableId(1); - assert_callable(&program, op_callable_id, &m_intrinsic_op()); + let tuple_record_id = CallableId(2); + let result_record_id = CallableId(3); + assert_callable( + &program, + op_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); + assert_callable( + &program, + tuple_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__tuple_record_output + call_type: OutputRecording + input_type: + [0]: Integer + [1]: Pointer + output_type: + body: "#]], + ); + assert_callable( + &program, + result_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__result_record_output + call_type: OutputRecording + input_type: + [0]: Result + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - op_callable_id, - vec![ - Operand::Literal(Literal::Qubit(2)), - Operand::Literal(Literal::Result(2)), - ], - None, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Call id(1), args( Qubit(2), Result(2), ) + Call id(2), args( Integer(3), Pointer, ) + Call id(3), args( Result(0), Pointer, ) + Call id(3), args( Result(1), Pointer, ) + Call id(3), args( Result(2), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 3); + assert_eq!(program.num_results, 3); } #[test] @@ -212,73 +347,71 @@ fn comparing_measurement_results_for_equality_adds_read_result_and_comparison_in r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use (q0, q1) = (Qubit(), Qubit()); let r0 = QIR.Intrinsic.__quantum__qis__m__body(q0); let r1 = QIR.Intrinsic.__quantum__qis__m__body(q1); - let b = r0 == r1; + r0 == r1 } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &vec![ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(1))], - Some(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Eq, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Variable(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - Variable { - variable_id: VariableId(2), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Call id(2), args( Result(1), ) + Variable(2, Boolean) = Icmp Eq, Variable(0, Boolean), Variable(1, Boolean) + Call id(3), args( Variable(2, Boolean), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 2); + assert_eq!(program.num_results, 2); } #[test] @@ -287,73 +420,71 @@ fn comparing_measurement_results_for_inequality_adds_read_result_and_comparison_ r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use (q0, q1) = (Qubit(), Qubit()); let r0 = QIR.Intrinsic.__quantum__qis__m__body(q0); let r1 = QIR.Intrinsic.__quantum__qis__m__body(q1); - let b = r0 != r1; + r0 != r1 } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &vec![ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(1)), - Operand::Literal(Literal::Result(1)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(1))], - Some(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Ne, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Variable(Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }), - Variable { - variable_id: VariableId(2), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Call id(1), args( Qubit(1), Result(1), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Call id(2), args( Result(1), ) + Variable(2, Boolean) = Icmp Ne, Variable(0, Boolean), Variable(1, Boolean) + Call id(3), args( Variable(2, Boolean), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 2); + assert_eq!(program.num_results, 2); } #[test] @@ -363,53 +494,68 @@ fn comparing_measurement_result_against_result_literal_for_equality_adds_read_re r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use q = Qubit(); let r = QIR.Intrinsic.__quantum__qis__m__body(q); - let b = r == One; + r == One } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Eq, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Literal(Literal::Bool(true)), - Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Eq, Variable(0, Boolean), Bool(true) + Call id(3), args( Variable(1, Boolean), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 1); } #[test] @@ -419,51 +565,66 @@ fn comparing_measurement_result_against_result_literal_for_inequality_adds_read_ r#" namespace Test { @EntryPoint() - operation Main() : Unit { + operation Main() : Bool { use q = Qubit(); let r = QIR.Intrinsic.__quantum__qis__m__body(q); - let b = r != Zero; + r != Zero } } "#, }); let measurement_callable_id = CallableId(1); - assert_callable(&program, measurement_callable_id, &m_intrinsic_op()); + assert_callable( + &program, + measurement_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__mz__body + call_type: Measurement + input_type: + [0]: Qubit + [1]: Result + output_type: + body: "#]], + ); let readout_callable_id = CallableId(2); - assert_callable(&program, readout_callable_id, &read_reasult_intrinsic_op()); + assert_callable( + &program, + readout_callable_id, + &expect![[r#" + Callable: + name: __quantum__qis__read_result__body + call_type: Readout + input_type: + [0]: Result + output_type: Boolean + body: "#]], + ); + let bool_record_id = CallableId(3); + assert_callable( + &program, + bool_record_id, + &expect![[r#" + Callable: + name: __quantum__rt__bool_record_output + call_type: OutputRecording + input_type: + [0]: Boolean + [1]: Pointer + output_type: + body: "#]], + ); assert_block_instructions( &program, BlockId(0), - &[ - Instruction::Call( - measurement_callable_id, - vec![ - Operand::Literal(Literal::Qubit(0)), - Operand::Literal(Literal::Result(0)), - ], - None, - ), - Instruction::Call( - readout_callable_id, - vec![Operand::Literal(Literal::Result(0))], - Some(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - ), - Instruction::Icmp( - ConditionCode::Ne, - Operand::Variable(Variable { - variable_id: VariableId(0), - ty: Ty::Boolean, - }), - Operand::Literal(Literal::Bool(false)), - Variable { - variable_id: VariableId(1), - ty: Ty::Boolean, - }, - ), - Instruction::Return, - ], + &expect![[r#" + Block: + Call id(1), args( Qubit(0), Result(0), ) + Variable(0, Boolean) = Call id(2), args( Result(0), ) + Variable(1, Boolean) = Icmp Ne, Variable(0, Boolean), Bool(false) + Call id(3), args( Variable(1, Boolean), Pointer, ) + Return"#]], ); + assert_eq!(program.num_qubits, 1); + assert_eq!(program.num_results, 1); } diff --git a/compiler/qsc_partial_eval/tests/test_utils.rs b/compiler/qsc_partial_eval/tests/test_utils.rs index f0cde17b53..2552d8083d 100644 --- a/compiler/qsc_partial_eval/tests/test_utils.rs +++ b/compiler/qsc_partial_eval/tests/test_utils.rs @@ -1,46 +1,43 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use expect_test::Expect; use qsc::{incremental::Compiler, PackageType}; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_fir::fir::{PackageId, PackageStore}; -use qsc_frontend::compile::{PackageStore as HirPackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_fir::fir::PackageStore; +use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; -use qsc_partial_eval::partially_evaluate; +use qsc_partial_eval::{partially_evaluate, ProgramEntry}; use qsc_rca::{Analyzer, PackageStoreComputeProperties}; -use qsc_rir::rir::{BlockId, Callable, CallableId, Instruction, Program}; +use qsc_rir::rir::{BlockId, CallableId, Program}; -pub fn assert_block_instructions( - program: &Program, - block_id: BlockId, - expected_insts: &[Instruction], -) { - let block = program.blocks.get(block_id).expect("block does not exist"); - assert_eq!( - block.0.len(), - expected_insts.len(), - "expected number of instructions is different than actual number of instructions" - ); - for (expected_inst, actual_inst) in expected_insts.iter().zip(block.0.iter()) { - assert_eq!(expected_inst, actual_inst); - } +pub fn assert_block_instructions(program: &Program, block_id: BlockId, expected_insts: &Expect) { + let block = program.get_block(block_id); + expected_insts.assert_eq(&block.to_string()); } -pub fn assert_callable(program: &Program, callable_id: CallableId, expected_callable: &Callable) { - let actual_callable = program - .callables - .get(callable_id) - .expect("callable does not exist "); - assert_eq!(expected_callable, actual_callable); +pub fn assert_blocks(program: &Program, expected_blocks: &Expect) { + let all_blocks = program + .blocks + .iter() + .fold("Blocks:".to_string(), |acc, (id, block)| { + acc + &format!("\nBlock {}:", id.0) + &block.to_string() + }); + expected_blocks.assert_eq(&all_blocks); +} + +pub fn assert_callable(program: &Program, callable_id: CallableId, expected_callable: &Expect) { + let actual_callable = program.get_callable(callable_id); + expected_callable.assert_eq(&actual_callable.to_string()); } #[must_use] pub fn compile_and_partially_evaluate(source: &str) -> Program { let compilation_context = CompilationContext::new(source); let maybe_program = partially_evaluate( - compilation_context.package_id, &compilation_context.fir_store, &compilation_context.compute_properties, + &compilation_context.entry, ); match maybe_program { Ok(program) => program, @@ -51,7 +48,7 @@ pub fn compile_and_partially_evaluate(source: &str) -> Program { struct CompilationContext { fir_store: PackageStore, compute_properties: PackageStoreComputeProperties, - package_id: PackageId, + entry: ProgramEntry, } impl CompilationContext { @@ -61,7 +58,7 @@ impl CompilationContext { true, source_map, PackageType::Exe, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("should be able to create a new compiler"); @@ -69,10 +66,22 @@ impl CompilationContext { let fir_store = lower_hir_package_store(compiler.package_store()); let analyzer = Analyzer::init(&fir_store); let compute_properties = analyzer.analyze_all(); + let package = fir_store.get(package_id); + let entry = ProgramEntry { + exec_graph: package.entry_exec_graph.clone(), + expr: ( + package_id, + package + .entry + .expect("package must have an entry expression"), + ) + .into(), + }; + Self { fir_store, compute_properties, - package_id, + entry, } } } diff --git a/compiler/qsc_passes/src/baseprofck/tests.rs b/compiler/qsc_passes/src/baseprofck/tests.rs index fdba6f3bfa..9f87f98327 100644 --- a/compiler/qsc_passes/src/baseprofck/tests.rs +++ b/compiler/qsc_passes/src/baseprofck/tests.rs @@ -6,19 +6,19 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use crate::baseprofck::check_base_profile_compliance; fn check(expr: &str, expect: &Expect) { let mut store = PackageStore::new(compile::core()); - let std = store.insert(compile::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(compile::std(&store, TargetCapabilityFlags::all())); let sources = SourceMap::new([("test".into(), "".into())], Some(expr.into())); let unit = compile( &store, &[std], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/borrowck/tests.rs b/compiler/qsc_passes/src/borrowck/tests.rs index 42eeb59410..c6c146f7b0 100644 --- a/compiler/qsc_passes/src/borrowck/tests.rs +++ b/compiler/qsc_passes/src/borrowck/tests.rs @@ -6,7 +6,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::visit::Visitor; use crate::borrowck::Checker; @@ -18,7 +18,7 @@ fn check(expr: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/callable_limits/tests.rs b/compiler/qsc_passes/src/callable_limits/tests.rs index 0c82839c50..5bd2a0cdba 100644 --- a/compiler/qsc_passes/src/callable_limits/tests.rs +++ b/compiler/qsc_passes/src/callable_limits/tests.rs @@ -6,7 +6,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::visit::Visitor; use crate::callable_limits::CallableLimits; @@ -18,7 +18,7 @@ fn check(file: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/capabilitiesck.rs b/compiler/qsc_passes/src/capabilitiesck.rs index e0478f4825..a7e5d64874 100644 --- a/compiler/qsc_passes/src/capabilitiesck.rs +++ b/compiler/qsc_passes/src/capabilitiesck.rs @@ -25,7 +25,7 @@ use qsc_fir::{ ty::FunctorSetValue, visit::Visitor, }; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; use qsc_lowerer::map_hir_package_to_fir; use qsc_rca::{ Analyzer, ComputeKind, ItemComputeProperties, PackageComputeProperties, @@ -188,7 +188,7 @@ pub fn lower_store( pub fn run_rca_pass( fir_store: &qsc_fir::fir::PackageStore, package_id: qsc_fir::fir::PackageId, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Result> { let analyzer = Analyzer::init(fir_store); let compute_properties = analyzer.analyze_all(); @@ -213,7 +213,7 @@ pub fn run_rca_pass( pub fn check_supported_capabilities( package: &Package, compute_properties: &PackageComputeProperties, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Vec { let checker = Checker { package, @@ -229,7 +229,7 @@ pub fn check_supported_capabilities( struct Checker<'a> { package: &'a Package, compute_properties: &'a PackageComputeProperties, - target_capabilities: RuntimeCapabilityFlags, + target_capabilities: TargetCapabilityFlags, current_callable: Option, missing_features_map: FxHashMap, } @@ -583,9 +583,9 @@ fn generate_errors_from_runtime_features( fn get_missing_runtime_features( runtime_features: RuntimeFeatureFlags, - target_capabilities: RuntimeCapabilityFlags, + target_capabilities: TargetCapabilityFlags, ) -> RuntimeFeatureFlags { - let missing_capabilities = !target_capabilities & runtime_features.runtime_capabilities(); + let missing_capabilities = !target_capabilities & runtime_features.target_capabilities(); runtime_features.contributing_features(missing_capabilities) } diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs index 1a4d10bba6..1f16269328 100644 --- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs +++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs @@ -15,10 +15,10 @@ use super::tests_common::{ USE_DYNAMIC_QUBIT, USE_DYNAMIC_RANGE, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT, }; use expect_test::{expect, Expect}; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; fn check_profile(source: &str, expect: &Expect) { - check(source, expect, RuntimeCapabilityFlags::ForwardBranching); + check(source, expect, TargetCapabilityFlags::Adaptive); } #[test] diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs index 927c7a89d5..c38f8d39dd 100644 --- a/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs +++ b/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs @@ -17,13 +17,13 @@ use super::tests_common::{ USE_DYNAMIC_QUBIT, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT, }; use expect_test::{expect, Expect}; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; fn check_profile(source: &str, expect: &Expect) { check( source, expect, - RuntimeCapabilityFlags::ForwardBranching | RuntimeCapabilityFlags::IntegerComputations, + TargetCapabilityFlags::Adaptive | TargetCapabilityFlags::IntegerComputations, ); } diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs index 7126376f67..8410b7e4f9 100644 --- a/compiler/qsc_passes/src/capabilitiesck/tests_base.rs +++ b/compiler/qsc_passes/src/capabilitiesck/tests_base.rs @@ -15,10 +15,10 @@ use super::tests_common::{ USE_DYNAMIC_QUBIT, USE_DYNAMIC_RANGE, USE_DYNAMIC_STRING, USE_DYNAMIC_UDT, }; use expect_test::{expect, Expect}; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; fn check_profile(source: &str, expect: &Expect) { - check(source, expect, RuntimeCapabilityFlags::empty()); + check(source, expect, TargetCapabilityFlags::empty()); } #[test] diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs index 9c91f9549f..b0b6fd9d0e 100644 --- a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs +++ b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs @@ -9,11 +9,11 @@ use crate::capabilitiesck::check_supported_capabilities; use qsc::{incremental::Compiler, PackageType}; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir::{Package, PackageId, PackageStore}; -use qsc_frontend::compile::{PackageStore as HirPackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_rca::{Analyzer, PackageComputeProperties, PackageStoreComputeProperties}; -pub fn check(source: &str, expect: &Expect, capabilities: RuntimeCapabilityFlags) { +pub fn check(source: &str, expect: &Expect, capabilities: TargetCapabilityFlags) { let compilation_context = CompilationContext::new(source); let (package, compute_properties) = compilation_context.get_package_compute_properties_tuple(); let errors = check_supported_capabilities(package, compute_properties, capabilities); @@ -46,7 +46,7 @@ impl CompilationContext { true, SourceMap::default(), PackageType::Lib, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ) .expect("should be able to create a new compiler"); diff --git a/compiler/qsc_passes/src/conjugate_invert/tests.rs b/compiler/qsc_passes/src/conjugate_invert/tests.rs index 12a1887eb6..34c8898bf9 100644 --- a/compiler/qsc_passes/src/conjugate_invert/tests.rs +++ b/compiler/qsc_passes/src/conjugate_invert/tests.rs @@ -7,7 +7,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::{validate::Validator, visit::Visitor}; use crate::conjugate_invert::invert_conjugate_exprs; @@ -19,7 +19,7 @@ fn check(file: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/entry_point.rs b/compiler/qsc_passes/src/entry_point.rs index 784964a52f..d7cc7a8d76 100644 --- a/compiler/qsc_passes/src/entry_point.rs +++ b/compiler/qsc_passes/src/entry_point.rs @@ -98,7 +98,7 @@ fn create_entry_from_callables( }; let call = Expr { id: assigner.next_node(), - span: ep.span, + span: ep.name.span, ty: block.ty.clone(), kind: ExprKind::Call(Box::new(callee), Box::new(arg)), }; diff --git a/compiler/qsc_passes/src/entry_point/tests.rs b/compiler/qsc_passes/src/entry_point/tests.rs index 08bb16b828..4639d431dd 100644 --- a/compiler/qsc_passes/src/entry_point/tests.rs +++ b/compiler/qsc_passes/src/entry_point/tests.rs @@ -7,7 +7,7 @@ use crate::entry_point::generate_entry_expr; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; fn check(file: &str, expr: &str, expect: &Expect) { let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into())); @@ -15,7 +15,7 @@ fn check(file: &str, expr: &str, expect: &Expect) { &PackageStore::new(compile::core()), &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); @@ -44,7 +44,7 @@ fn test_entry_point_attr_to_expr() { }"}, "", &expect![[r#" - Expr 12 [40-73] [Type Int]: Call: + Expr 12 [50-54] [Type Int]: Call: Expr 11 [40-73] [Type Int]: Var: Item 1 Expr 10 [40-73] [Type Unit]: Unit"#]], ); diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs index ae82a9cecf..9116ee6081 100644 --- a/compiler/qsc_passes/src/lib.rs +++ b/compiler/qsc_passes/src/lib.rs @@ -21,7 +21,7 @@ use entry_point::generate_entry_expr; use loop_unification::LoopUni; use miette::Diagnostic; use qsc_fir::fir; -use qsc_frontend::compile::{CompileUnit, RuntimeCapabilityFlags}; +use qsc_frontend::compile::{CompileUnit, TargetCapabilityFlags}; use qsc_hir::{ assigner::Assigner, global::{self, Table}, @@ -54,14 +54,24 @@ pub enum PackageType { Lib, } +#[must_use] +pub fn lower_hir_to_fir( + package_store: &qsc_frontend::compile::PackageStore, + package_id: qsc_hir::hir::PackageId, +) -> (fir::PackageStore, fir::PackageId) { + let fir_store = lower_store(package_store); + let fir_package_id = map_hir_package_to_fir(package_id); + (fir_store, fir_package_id) +} + pub struct PassContext { - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, borrow_check: borrowck::Checker, } impl PassContext { #[must_use] - pub fn new(capabilities: RuntimeCapabilityFlags) -> Self { + pub fn new(capabilities: TargetCapabilityFlags) -> Self { Self { capabilities, borrow_check: borrowck::Checker::default(), @@ -103,7 +113,7 @@ impl PassContext { ReplaceQubitAllocation::new(core, assigner).visit_package(package); Validator::default().visit_package(package); - let base_prof_errors = if self.capabilities == RuntimeCapabilityFlags::empty() { + let base_prof_errors = if self.capabilities == TargetCapabilityFlags::empty() { baseprofck::check_base_profile_compliance(package) } else { Vec::new() @@ -120,20 +130,10 @@ impl PassContext { .collect() } - pub fn run_fir_passes_on_hir( - package_store: &qsc_frontend::compile::PackageStore, - package_id: qsc_hir::hir::PackageId, - capabilities: RuntimeCapabilityFlags, - ) -> Result> { - let fir_store = lower_store(package_store); - let fir_package_id = map_hir_package_to_fir(package_id); - Self::run_fir_passes_on_fir(&fir_store, fir_package_id, capabilities) - } - pub fn run_fir_passes_on_fir( fir_store: &qsc_fir::fir::PackageStore, package_id: qsc_fir::fir::PackageId, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Result> { run_rca_pass(fir_store, package_id, capabilities) } @@ -144,7 +144,7 @@ pub fn run_default_passes( core: &Table, unit: &mut CompileUnit, package_type: PackageType, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Vec { PassContext::new(capabilities).run_default_passes( &mut unit.package, @@ -182,7 +182,7 @@ pub fn run_core_passes(core: &mut CompileUnit) -> Vec { pub fn run_fir_passes( package: &fir::Package, compute_properties: &PackageComputeProperties, - capabilities: RuntimeCapabilityFlags, + capabilities: TargetCapabilityFlags, ) -> Vec { let capabilities_errors = check_supported_capabilities(package, compute_properties, capabilities); diff --git a/compiler/qsc_passes/src/logic_sep/tests.rs b/compiler/qsc_passes/src/logic_sep/tests.rs index 64c6bd459a..5a99cd528f 100644 --- a/compiler/qsc_passes/src/logic_sep/tests.rs +++ b/compiler/qsc_passes/src/logic_sep/tests.rs @@ -6,7 +6,7 @@ use expect_test::{expect, Expect}; use qsc_data_structures::{language_features::LanguageFeatures, span::Span}; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::{ hir::{ExprKind, NodeId, Stmt}, visit::{walk_stmt, Visitor}, @@ -28,12 +28,12 @@ impl<'a> Visitor<'a> for StmtSpans { fn check(block_str: &str, expect: &Expect) { let mut store = PackageStore::new(compile::core()); - let std = store.insert(compile::std(&store, RuntimeCapabilityFlags::all())); + let std = store.insert(compile::std(&store, TargetCapabilityFlags::all())); let unit = compile( &store, &[std], SourceMap::new([], Some(block_str.into())), - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs index 147bbb4e24..7edef04211 100644 --- a/compiler/qsc_passes/src/loop_unification.rs +++ b/compiler/qsc_passes/src/loop_unification.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use core::panic; use std::mem::take; use qsc_data_structures::span::Span; diff --git a/compiler/qsc_passes/src/loop_unification/tests.rs b/compiler/qsc_passes/src/loop_unification/tests.rs index 79cf44cf25..9ccf984ce4 100644 --- a/compiler/qsc_passes/src/loop_unification/tests.rs +++ b/compiler/qsc_passes/src/loop_unification/tests.rs @@ -6,7 +6,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::{mut_visit::MutVisitor, validate::Validator, visit::Visitor}; use crate::loop_unification::LoopUni; @@ -18,7 +18,7 @@ fn check(file: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs index 4c6594a021..42fa1be385 100644 --- a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs +++ b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs @@ -5,7 +5,7 @@ use crate::replace_qubit_allocation::ReplaceQubitAllocation; use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::{mut_visit::MutVisitor, validate::Validator, visit::Visitor}; fn check(file: &str, expect: &Expect) { @@ -15,7 +15,7 @@ fn check(file: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_passes/src/spec_gen/tests.rs b/compiler/qsc_passes/src/spec_gen/tests.rs index 43c8ac0f5f..3743801e48 100644 --- a/compiler/qsc_passes/src/spec_gen/tests.rs +++ b/compiler/qsc_passes/src/spec_gen/tests.rs @@ -7,7 +7,7 @@ use expect_test::{expect, Expect}; use indoc::indoc; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_frontend::compile::{self, compile, PackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{self, compile, PackageStore, SourceMap, TargetCapabilityFlags}; use qsc_hir::{validate::Validator, visit::Visitor}; use crate::spec_gen::generate_specs; @@ -19,7 +19,7 @@ fn check(file: &str, expect: &Expect) { &store, &[], sources, - RuntimeCapabilityFlags::all(), + TargetCapabilityFlags::all(), LanguageFeatures::default(), ); assert!(unit.errors.is_empty(), "{:?}", unit.errors); diff --git a/compiler/qsc_rca/src/lib.rs b/compiler/qsc_rca/src/lib.rs index c323f4cbad..7a7544ea32 100644 --- a/compiler/qsc_rca/src/lib.rs +++ b/compiler/qsc_rca/src/lib.rs @@ -26,7 +26,7 @@ use qsc_fir::{ }, ty::Ty, }; -use qsc_frontend::compile::RuntimeCapabilityFlags; +use qsc_frontend::compile::TargetCapabilityFlags; use std::{ cmp::Ord, fmt::{self, Debug, Display, Formatter, Write}, @@ -635,7 +635,8 @@ impl ValueKind { } } - pub(crate) fn is_dynamic(self) -> bool { + #[must_use] + pub fn is_dynamic(self) -> bool { match self { Self::Array(content_runtime_kind, size_runtime_kind) => { matches!(content_runtime_kind, RuntimeKind::Dynamic) @@ -752,15 +753,12 @@ bitflags! { } impl RuntimeFeatureFlags { - /// Determines the runtime features that contribute to the provided runtime capabilities. + /// Determines the runtime features that contribute to the provided target capabilities. #[must_use] - pub fn contributing_features(&self, runtime_capabilities: RuntimeCapabilityFlags) -> Self { + pub fn contributing_features(&self, capabilities: TargetCapabilityFlags) -> Self { let mut contributing_features = Self::empty(); for feature in self.iter() { - if feature - .runtime_capabilities() - .intersects(runtime_capabilities) - { + if feature.target_capabilities().intersects(capabilities) { contributing_features |= feature; } } @@ -768,76 +766,76 @@ impl RuntimeFeatureFlags { contributing_features } - /// Maps program contructs to runtime capabilities. + /// Maps program constructs to target capabilities. #[must_use] - pub fn runtime_capabilities(&self) -> RuntimeCapabilityFlags { - let mut runtume_capabilities = RuntimeCapabilityFlags::empty(); + pub fn target_capabilities(&self) -> TargetCapabilityFlags { + let mut capabilities = TargetCapabilityFlags::empty(); if self.contains(RuntimeFeatureFlags::UseOfDynamicBool) { - runtume_capabilities |= RuntimeCapabilityFlags::ForwardBranching; + capabilities |= TargetCapabilityFlags::Adaptive; } if self.contains(RuntimeFeatureFlags::UseOfDynamicInt) { - runtume_capabilities |= RuntimeCapabilityFlags::IntegerComputations; + capabilities |= TargetCapabilityFlags::IntegerComputations; } if self.contains(RuntimeFeatureFlags::UseOfDynamicPauli) { - runtume_capabilities |= RuntimeCapabilityFlags::IntegerComputations; + capabilities |= TargetCapabilityFlags::IntegerComputations; } if self.contains(RuntimeFeatureFlags::UseOfDynamicRange) { - runtume_capabilities |= RuntimeCapabilityFlags::IntegerComputations; + capabilities |= TargetCapabilityFlags::IntegerComputations; } if self.contains(RuntimeFeatureFlags::UseOfDynamicDouble) { - runtume_capabilities |= RuntimeCapabilityFlags::FloatingPointComputations; + capabilities |= TargetCapabilityFlags::FloatingPointComputations; } if self.contains(RuntimeFeatureFlags::UseOfDynamicQubit) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicBigInt) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicString) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicallySizedArray) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicUdt) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicArrowFunction) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::UseOfDynamicArrowOperation) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::CyclicOperationSpec) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::CallToCyclicOperation) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::CallToDynamicCallee) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::CallToUnresolvedCallee) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::MeasurementWithinDynamicScope) { - runtume_capabilities |= RuntimeCapabilityFlags::ForwardBranching; + capabilities |= TargetCapabilityFlags::Adaptive; } if self.contains(RuntimeFeatureFlags::UseOfDynamicIndex) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } if self.contains(RuntimeFeatureFlags::ReturnWithinDynamicScope) { - runtume_capabilities |= RuntimeCapabilityFlags::ForwardBranching; + capabilities |= TargetCapabilityFlags::Adaptive; } if self.contains(RuntimeFeatureFlags::LoopWithDynamicCondition) { - runtume_capabilities |= RuntimeCapabilityFlags::BackwardsBranching; + capabilities |= TargetCapabilityFlags::BackwardsBranching; } if self.contains(RuntimeFeatureFlags::UseOfClosure) { - runtume_capabilities |= RuntimeCapabilityFlags::HigherLevelConstructs; + capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } - runtume_capabilities + capabilities } } diff --git a/compiler/qsc_rca/tests/callables.rs b/compiler/qsc_rca/tests/callables.rs index 3ae340ee2f..adbe9d038e 100644 --- a/compiler/qsc_rca/tests/callables.rs +++ b/compiler/qsc_rca/tests/callables.rs @@ -6,7 +6,7 @@ pub mod test_utils; use expect_test::expect; -use qsc::RuntimeCapabilityFlags; +use qsc::TargetCapabilityFlags; use test_utils::{ check_callable_compute_properties, check_last_statement_compute_properties, CompilationContext, }; @@ -200,7 +200,7 @@ fn check_rca_for_unrestricted_h() { #[test] fn check_rca_for_base_h() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -304,7 +304,7 @@ fn check_rca_for_unrestricted_r1() { #[test] fn check_rca_for_base_r1() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -420,7 +420,7 @@ fn check_rca_for_unrestricted_rx() { #[test] fn check_rca_for_base_rx() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -548,7 +548,7 @@ fn check_rca_for_unrestricted_rxx() { #[test] fn check_rca_for_base_rxx() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -676,7 +676,7 @@ fn check_rca_for_unrestricted_ry() { #[test] fn check_rca_for_base_ry() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -804,7 +804,7 @@ fn check_rca_for_unrestricted_ryy() { #[test] fn check_rca_for_base_ryy() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -932,7 +932,7 @@ fn check_rca_for_unrestricted_rz() { #[test] fn check_rca_for_base_rz() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1060,7 +1060,7 @@ fn check_rca_for_unrestricted_rzz() { #[test] fn check_rca_for_base_rzz() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1176,7 +1176,7 @@ fn check_rca_for_unrestricted_s() { #[test] fn check_rca_for_base_s() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1268,7 +1268,7 @@ fn check_rca_for_unrestricted_t() { #[test] fn check_rca_for_base_t() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1360,7 +1360,7 @@ fn check_rca_for_unrestricted_x() { #[test] fn check_rca_for_base_x() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1452,7 +1452,7 @@ fn check_rca_for_unrestricted_y() { #[test] fn check_rca_for_base_y() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), @@ -1544,7 +1544,7 @@ fn check_rca_for_unrestricted_z() { #[test] fn check_rca_for_base_z() { - let compilation_context = CompilationContext::new(RuntimeCapabilityFlags::empty()); + let compilation_context = CompilationContext::new(TargetCapabilityFlags::empty()); check_callable_compute_properties( &compilation_context.fir_store, compilation_context.get_compute_properties(), diff --git a/compiler/qsc_rca/tests/test_utils.rs b/compiler/qsc_rca/tests/test_utils.rs index 9e5c7c2d1b..a0b2db0b0b 100644 --- a/compiler/qsc_rca/tests/test_utils.rs +++ b/compiler/qsc_rca/tests/test_utils.rs @@ -5,7 +5,7 @@ use expect_test::Expect; use qsc::incremental::Compiler; use qsc_data_structures::language_features::LanguageFeatures; use qsc_fir::fir::{ItemKind, LocalItemId, Package, PackageStore, StoreItemId}; -use qsc_frontend::compile::{PackageStore as HirPackageStore, RuntimeCapabilityFlags, SourceMap}; +use qsc_frontend::compile::{PackageStore as HirPackageStore, SourceMap, TargetCapabilityFlags}; use qsc_lowerer::{map_hir_package_to_fir, Lowerer}; use qsc_passes::PackageType; use qsc_rca::{Analyzer, ComputePropertiesLookup, PackageStoreComputeProperties}; @@ -19,7 +19,7 @@ pub struct CompilationContext { impl CompilationContext { #[must_use] - pub fn new(runtime_capabilities: RuntimeCapabilityFlags) -> Self { + pub fn new(runtime_capabilities: TargetCapabilityFlags) -> Self { let compiler = Compiler::new( true, SourceMap::default(), @@ -68,7 +68,7 @@ impl CompilationContext { impl Default for CompilationContext { fn default() -> Self { - Self::new(RuntimeCapabilityFlags::all()) + Self::new(TargetCapabilityFlags::all()) } } diff --git a/compiler/qsc_rir/src/builder.rs b/compiler/qsc_rir/src/builder.rs index 5ed292af2d..135f74c79a 100644 --- a/compiler/qsc_rir/src/builder.rs +++ b/compiler/qsc_rir/src/builder.rs @@ -116,6 +116,28 @@ pub fn result_record_decl() -> Callable { } } +#[must_use] +pub fn int_record_decl() -> Callable { + Callable { + name: "__quantum__rt__integer_record_output".to_string(), + input_type: vec![Ty::Integer, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + +#[must_use] +pub fn bool_record_decl() -> Callable { + Callable { + name: "__quantum__rt__bool_record_output".to_string(), + input_type: vec![Ty::Boolean, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + #[must_use] pub fn array_record_decl() -> Callable { Callable { @@ -127,6 +149,17 @@ pub fn array_record_decl() -> Callable { } } +#[must_use] +pub fn tuple_record_decl() -> Callable { + Callable { + name: "__quantum__rt__tuple_record_output".to_string(), + input_type: vec![Ty::Integer, Ty::Pointer], + output_type: None, + body: None, + call_type: CallableType::OutputRecording, + } +} + /// Creates a new program with a single, entry callable that has block 0 as its body. #[must_use] pub fn new_program() -> Program { diff --git a/compiler/qsc_rir/src/passes.rs b/compiler/qsc_rir/src/passes.rs index 6079204eb4..d6cd307665 100644 --- a/compiler/qsc_rir/src/passes.rs +++ b/compiler/qsc_rir/src/passes.rs @@ -7,6 +7,7 @@ mod reindex_qubits; mod remap_block_ids; mod ssa_check; mod ssa_transform; +mod type_check; mod unreachable_code_check; use build_dominator_graph::build_dominator_graph; @@ -15,6 +16,7 @@ use reindex_qubits::reindex_qubits; use remap_block_ids::remap_block_ids; use ssa_check::check_ssa_form; use ssa_transform::transform_to_ssa; +pub use type_check::check_types; pub use unreachable_code_check::check_unreachable_code; use crate::{rir::Program, utils::build_predecessors_map}; @@ -27,11 +29,14 @@ use crate::{rir::Program, utils::build_predecessors_map}; /// - Checking that the program is in SSA form pub fn check_and_transform(program: &mut Program) { check_unreachable_code(program); + check_types(program); remap_block_ids(program); let preds = build_predecessors_map(program); transform_to_ssa(program, &preds); let doms = build_dominator_graph(program, &preds); check_ssa_form(program, &preds, &doms); + check_unreachable_code(program); + check_types(program); } /// Run the RIR passes that are necessary for targets with no mid-program measurement. diff --git a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs index 41f3e1a015..da9d64064d 100644 --- a/compiler/qsc_rir/src/passes/ssa_transform/tests.rs +++ b/compiler/qsc_rir/src/passes/ssa_transform/tests.rs @@ -6,29 +6,25 @@ use expect_test::expect; use crate::{ - builder::{bell_program, new_program}, - passes::{build_dominator_graph, check_ssa_form, check_unreachable_code, remap_block_ids}, + builder::{bell_program, new_program, teleport_program}, + passes::check_and_transform, rir::{ Block, BlockId, Callable, CallableId, CallableType, Instruction, Operand, Program, Ty, Variable, VariableId, }, - utils::build_predecessors_map, }; - -use super::transform_to_ssa; - fn transform_program(program: &mut Program) { - check_unreachable_code(program); - remap_block_ids(program); - let preds = build_predecessors_map(program); - transform_to_ssa(program, &preds); - let doms = build_dominator_graph(program, &preds); - check_ssa_form(program, &preds, &doms); + // When this configuration is replaced by target capabilities, set them to "all" here. + program.config.defer_measurements = false; + program.config.remap_qubits_on_reuse = false; + check_and_transform(program); } #[test] fn ssa_transform_leaves_program_without_store_instruction_unchanged() { let mut program = bell_program(); + program.config.defer_measurements = false; + program.config.remap_qubits_on_reuse = false; let program_string_orignal = program.to_string(); transform_program(&mut program); @@ -38,7 +34,7 @@ fn ssa_transform_leaves_program_without_store_instruction_unchanged() { #[test] fn ssa_transform_leaves_branching_program_without_store_instruction_unchanged() { - let mut program = bell_program(); + let mut program = teleport_program(); let program_string_orignal = program.to_string(); transform_program(&mut program); diff --git a/compiler/qsc_rir/src/passes/type_check.rs b/compiler/qsc_rir/src/passes/type_check.rs new file mode 100644 index 0000000000..757657ff40 --- /dev/null +++ b/compiler/qsc_rir/src/passes/type_check.rs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::rir::{Callable, Instruction, Operand, Program, Ty, Variable}; + +#[cfg(test)] +mod tests; + +pub fn check_types(program: &Program) { + for (_, block) in program.blocks.iter() { + for instr in &block.0 { + check_instr_types(program, instr); + } + } +} + +fn check_instr_types(program: &Program, instr: &Instruction) { + match instr { + Instruction::Call(id, args, var) => check_call_types(program.get_callable(*id), args, *var), + + Instruction::Branch(var, _, _) => assert_eq!(var.ty, Ty::Boolean), + + Instruction::Add(opr1, opr2, var) + | Instruction::Sub(opr1, opr2, var) + | Instruction::Mul(opr1, opr2, var) + | Instruction::Sdiv(opr1, opr2, var) + | Instruction::Srem(opr1, opr2, var) + | Instruction::Shl(opr1, opr2, var) + | Instruction::Ashr(opr1, opr2, var) + | Instruction::LogicalAnd(opr1, opr2, var) + | Instruction::LogicalOr(opr1, opr2, var) + | Instruction::BitwiseAnd(opr1, opr2, var) + | Instruction::BitwiseOr(opr1, opr2, var) + | Instruction::BitwiseXor(opr1, opr2, var) + | Instruction::Icmp(_, opr1, opr2, var) => { + assert_eq!(opr1.get_type(), opr2.get_type()); + assert_eq!(opr1.get_type(), var.ty); + } + + Instruction::Store(opr, var) + | Instruction::LogicalNot(opr, var) + | Instruction::BitwiseNot(opr, var) => { + assert_eq!(opr.get_type(), var.ty); + } + + Instruction::Phi(args, var) => { + for (opr, _) in args { + assert_eq!(opr.get_type(), var.ty); + } + } + + Instruction::Jump(_) | Instruction::Return => {} + } +} + +fn check_call_types(callable: &Callable, args: &[Operand], var: Option) { + assert_eq!( + callable.input_type.len(), + args.len(), + "incorrect number of arguments" + ); + for (arg, ty) in args.iter().zip(callable.input_type.iter()) { + assert_eq!(arg.get_type(), *ty); + } + + match (var, callable.output_type) { + (Some(var), Some(ty)) => assert_eq!(ty, var.ty), + (None, None) => {} + _ => panic!("expected return type to be present in both the instruction and the callable"), + } +} diff --git a/compiler/qsc_rir/src/passes/type_check/tests.rs b/compiler/qsc_rir/src/passes/type_check/tests.rs new file mode 100644 index 0000000000..79da706d0c --- /dev/null +++ b/compiler/qsc_rir/src/passes/type_check/tests.rs @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::rir::{ + BlockId, Callable, CallableId, CallableType, Instruction, Literal, Operand, Program, Ty, + Variable, VariableId, +}; + +use super::check_instr_types; + +#[test] +fn binop_instr_matching_types_passes_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr1 = Operand::Variable(var); + let opr2 = Operand::Literal(Literal::Integer(0)); + + check_instr_types(&Program::new(), &Instruction::Add(opr1, opr2, var)); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn binop_instr_mismatching_types_fails_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr1 = Operand::Variable(var); + let opr2 = Operand::Literal(Literal::Bool(false)); + + check_instr_types(&Program::new(), &Instruction::Add(opr1, opr2, var)); +} + +#[test] +fn unop_instr_matching_types_passes_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Boolean, + }; + let opr = Operand::Variable(var); + + check_instr_types(&Program::new(), &Instruction::BitwiseNot(opr, var)); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn unop_instr_mismatching_types_fails_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + check_instr_types( + &Program::new(), + &Instruction::BitwiseNot( + opr, + Variable { + variable_id: VariableId(1), + ty: Ty::Boolean, + }, + ), + ); +} + +#[test] +fn phi_instr_matching_types_passes_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + check_instr_types( + &Program::new(), + &Instruction::Phi(vec![(opr, BlockId(0)), (opr, BlockId(1))], var), + ); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn phi_instr_mismatching_types_fails_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + check_instr_types( + &Program::new(), + &Instruction::Phi( + vec![(opr, BlockId(0)), (opr, BlockId(1))], + Variable { + variable_id: VariableId(1), + ty: Ty::Boolean, + }, + ), + ); +} + +#[test] +fn call_instr_matching_types_passes_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: Some(Ty::Integer), + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types( + &program, + &Instruction::Call( + CallableId(0), + vec![opr], + Some(Variable { + variable_id: VariableId(1), + ty: Ty::Integer, + }), + ), + ); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn call_instr_mismatching_output_types_fails_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: Some(Ty::Integer), + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types( + &program, + &Instruction::Call( + CallableId(0), + vec![opr], + Some(Variable { + variable_id: VariableId(1), + ty: Ty::Boolean, + }), + ), + ); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn call_instr_mismatching_input_types_fails_check() { + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: Some(Ty::Integer), + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types( + &program, + &Instruction::Call( + CallableId(0), + vec![Operand::Literal(Literal::Bool(true))], + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }), + ), + ); +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed")] +fn call_instr_too_many_args_fails_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: Some(Ty::Integer), + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types( + &program, + &Instruction::Call( + CallableId(0), + vec![opr, opr], + Some(Variable { + variable_id: VariableId(1), + ty: Ty::Integer, + }), + ), + ); +} + +#[test] +fn call_instr_no_return_type_no_output_var_passes_check() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: None, + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types(&program, &Instruction::Call(CallableId(0), vec![opr], None)); +} + +#[test] +#[should_panic( + expected = "expected return type to be present in both the instruction and the callable" +)] +fn call_instr_return_type_without_output_var_fails() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: Some(Ty::Integer), + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types(&program, &Instruction::Call(CallableId(0), vec![opr], None)); +} + +#[test] +#[should_panic( + expected = "expected return type to be present in both the instruction and the callable" +)] +fn call_instr_output_var_without_return_type_fails() { + let var = Variable { + variable_id: VariableId(0), + ty: Ty::Integer, + }; + let opr = Operand::Variable(var); + + let mut program = Program::new(); + program.callables.insert( + CallableId(0), + Callable { + name: "foo".to_string(), + input_type: vec![Ty::Integer], + output_type: None, + call_type: CallableType::Regular, + body: None, + }, + ); + + check_instr_types( + &program, + &Instruction::Call( + CallableId(0), + vec![opr], + Some(Variable { + variable_id: VariableId(1), + ty: Ty::Integer, + }), + ), + ); +} diff --git a/compiler/qsc_rir/src/passes/unreachable_code_check.rs b/compiler/qsc_rir/src/passes/unreachable_code_check.rs index 31066bb875..ebe20d797e 100644 --- a/compiler/qsc_rir/src/passes/unreachable_code_check.rs +++ b/compiler/qsc_rir/src/passes/unreachable_code_check.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use core::panic; use std::iter::once; use crate::{ diff --git a/katas/README.md b/katas/README.md index 51eed18275..1ebf26c403 100644 --- a/katas/README.md +++ b/katas/README.md @@ -1,10 +1,19 @@ # Katas A Kata is a top-level container of educational items which are used to explain a particular quantum computing topic using Q#. They are organized in sections which can be of three types: + - Lessons: text content that sometimes contains Q# code examples. - Exercises: problems that the user solves by writting Q# code. - Questions: analytical problems that have a text answer. +## Run Katas Online + +Visit [Learn with Azure Quantum katas](https://quantum.microsoft.com/experience/quantum-katas) to try the new online Azure Quantum katas experience, with integrated assistance from Copilot in Azure Quantum. + +## Build Katas Locally + +We need to build the `playground` module to see the built katas locally. For the detailed instructions, refer to [Building Playground Locally](../playground/README.md#building-the-playground-locally). + ## Rust crate The katas crate exposes an API to check solutions for exercises. diff --git a/katas/content/KatasLibrary.qs b/katas/content/KatasLibrary.qs index 00ddc7f11f..fc3cf17f3c 100644 --- a/katas/content/KatasLibrary.qs +++ b/katas/content/KatasLibrary.qs @@ -4,6 +4,7 @@ namespace Microsoft.Quantum.Katas { open Microsoft.Quantum.Arrays; open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; open Microsoft.Quantum.Random; /// # Summary @@ -88,48 +89,46 @@ namespace Microsoft.Quantum.Katas { isCorrect } - /// # Summary - /// Shows the effect a quantum operation has on the quantum state. - operation ShowEffectOnQuantumState(targetRegister : Qubit[], op : (Qubit[] => Unit is Adj + Ctl)) : Unit { - Message("Quantum state before applying the operation:"); - DumpMachine(); - - // Apply the operation, dump the simulator state and "undo" the operation by applying the adjoint. - Message("Quantum state after applying the operation:"); - op(targetRegister); - DumpMachine(); - Adjoint op(targetRegister); - } /// # Summary /// Shows the comparison of the quantum state between a specific operation and a reference operation. operation ShowQuantumStateComparison( - targetRegister : Qubit[], - op : (Qubit[] => Unit is Adj + Ctl), - reference : (Qubit[] => Unit is Adj + Ctl)) + registerSize : Int, + initialState : Qubit[] => Unit, + op : Qubit[] => Unit, + reference : Qubit[] => Unit) : Unit { - Message("Initial quantum state:"); - DumpMachine(); - - // Apply the reference operation, dump the simulator state and "undo" the operation by applying the adjoint. - reference(targetRegister); - Message("Expected quantum state after applying the operation:"); - DumpMachine(); - Adjoint reference(targetRegister); - - // Apply the specific operation, dump the simulator state and "undo" the operation by applying the adjoint. - op(targetRegister); - Message("Actual quantum state after applying the operation:"); - DumpMachine(); - Adjoint op(targetRegister); + { + use register = Qubit[registerSize]; + initialState(register); + + Message("Initial quantum state:"); + DumpMachine(); + + // Apply the reference operation and dump the simulator state + reference(register); + Message("Expected quantum state after applying the operation:"); + DumpMachine(); + ResetAll(register); + } + + { + use register = Qubit[registerSize]; + initialState(register); + // Apply the comparison operation and dump the simulator state + op(register); + Message("Actual quantum state after applying the operation:"); + DumpMachine(); + ResetAll(register); + } } /// # Summary /// Given two operations, checks whether they act identically on the zero state |0〉 ⊗ |0〉 ⊗ ... ⊗ |0〉 composed of /// `inputSize` qubits. If they don't, prints user feedback. operation CheckOperationsEquivalenceOnZeroStateWithFeedback( - testImpl : (Qubit[] => Unit is Adj + Ctl), - refImpl : (Qubit[] => Unit is Adj + Ctl), + testImpl : (Qubit[] => Unit), + refImpl : (Qubit[] => Unit is Adj), inputSize : Int ) : Bool { @@ -140,9 +139,7 @@ namespace Microsoft.Quantum.Katas { Message("Correct!"); } else { Message("Incorrect."); - use target = Qubit[inputSize]; - ShowQuantumStateComparison(target, testImpl, refImpl); - ResetAll(target); + ShowQuantumStateComparison(inputSize, (qs => ()), testImpl, refImpl); } isCorrect } @@ -169,4 +166,129 @@ namespace Microsoft.Quantum.Katas { Ry(DrawRandomDouble(0.01, 0.99) * 2.0, q); } } + + + // "Framework" operation for testing single-qubit tasks for distinguishing states of one qubit + // with Bool return + operation DistinguishTwoStates_SingleQubit( + statePrep : ((Qubit, Int) => Unit is Adj), + testImpl : (Qubit => Bool), + stateNames : String[], + preserveState : Bool) : Bool { + + let nTotal = 100; + let nStates = 2; + mutable misclassifications = [0, size=nStates]; + + use q = Qubit(); + for _ in 1 .. nTotal { + // get a random bit to define whether qubit will be in a state corresponding to true return (1) or to false one (0) + // state = 0 false return + // state = 1 true return + let state = DrawRandomInt(0, 1); + + // do state prep: convert |0⟩ to outcome with false return or to outcome with true return depending on state + statePrep(q, state); + + // get the solution's answer and verify if NOT a match, then differentiate what kind of mismatch + let ans = testImpl(q); + if ans != (state == 1) { + set misclassifications w/= state <- misclassifications[state] + 1; + } + + // If the final state is to be preserved, check if it was not modified + if preserveState { + Adjoint statePrep(q, state); + if not CheckZero(q) { + Message($"Input quantum state {stateNames[state]} was not preserved during the measurement."); + Reset(q); + return false; + } + } else { + Reset(q); + } + } + + mutable totalMisclassifications = 0; + for i in 0 .. nStates - 1 { + if misclassifications[i] != 0 { + set totalMisclassifications += misclassifications[i]; + Message($"Misclassified {stateNames[i]} as {stateNames[1 - i]} in {misclassifications[i]} test runs."); + } + } + + totalMisclassifications == 0 + } + + + // "Framework" operation for testing multi-qubit tasks for distinguishing states of an array of qubits + // with Int return + operation DistinguishStates_MultiQubit( + nQubits : Int, + nStates : Int, + statePrep : ((Qubit[], Int, Double) => Unit is Adj), + testImpl : (Qubit[] => Int), + preserveState : Bool, + stateNames : String[]) : Bool { + + let nTotal = 100; + // misclassifications will store the number of times state i has been classified as state j (dimension nStates^2) + mutable misclassifications = [0, size = nStates * nStates]; + // unknownClassifications will store the number of times state i has been classified as some invalid state (index < 0 or >= nStates) + mutable unknownClassifications = [0, size = nStates]; + + use qs = Qubit[nQubits]; + for _ in 1 .. nTotal { + // get a random integer to define the state of the qubits + let state = DrawRandomInt(0, nStates - 1); + // get a random rotation angle to define the exact state of the qubits + // for some exercises, this value might be a dummy variable which does not matter + let alpha = DrawRandomDouble(0.0, 1.0) * PI(); + + // do state prep: convert |0...0⟩ to outcome with return equal to state + statePrep(qs, state, alpha); + + // get the solution's answer and verify that it's a match, if not, increase the exact mismatch count + let ans = testImpl(qs); + if ans >= 0 and ans < nStates { + // classification result is a valid state index - check if is it correct + if ans != state { + set misclassifications w/= ((state * nStates) + ans) <- (misclassifications[(state * nStates) + ans] + 1); + } + } + else { + // classification result is an invalid state index - file it separately + set unknownClassifications w/= state <- (unknownClassifications[state] + 1); + } + + if preserveState { + // check that the state of the qubit after the operation is unchanged + Adjoint statePrep(qs, state, alpha); + if not CheckAllZero(qs) { + Message($"Input quantum state {stateNames[state]} was not preserved during the measurement."); + ResetAll(qs); + return false; + } + } else { + // we're not checking the state of the qubit after the operation + ResetAll(qs); + } + } + + mutable totalMisclassifications = 0; + for i in 0 .. nStates - 1 { + for j in 0 .. nStates - 1 { + if misclassifications[(i * nStates) + j] != 0 { + set totalMisclassifications += misclassifications[i * nStates + j]; + Message($"Misclassified {stateNames[i]} as {stateNames[j]} in {misclassifications[(i * nStates) + j]} test runs."); + } + } + if unknownClassifications[i] != 0 { + set totalMisclassifications += unknownClassifications[i]; + Message($"Misclassified {stateNames[i]} as Unknown State in {unknownClassifications[i]} test runs."); + } + } + totalMisclassifications == 0 + } + } diff --git a/katas/content/deutsch_algo/one_minus_x_oracle/Verification.qs b/katas/content/deutsch_algo/one_minus_x_oracle/Verification.qs index b0d49c3a56..bd8fa19939 100644 --- a/katas/content/deutsch_algo/one_minus_x_oracle/Verification.qs +++ b/katas/content/deutsch_algo/one_minus_x_oracle/Verification.qs @@ -20,10 +20,7 @@ namespace Kata.Verification { Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have. Note that the simulator might drop the global phase -1, so if you're getting " + "verdict \"Incorrect\" but the actual state matches the expected one, check that you're handling the global phase correctly."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, (qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0])), solution, reference); } isCorrect } diff --git a/katas/content/deutsch_jozsa/msb_oracle/Verification.qs b/katas/content/deutsch_jozsa/msb_oracle/Verification.qs index c31b4d26b4..7c9aaf78b1 100644 --- a/katas/content/deutsch_jozsa/msb_oracle/Verification.qs +++ b/katas/content/deutsch_jozsa/msb_oracle/Verification.qs @@ -16,10 +16,7 @@ namespace Kata.Verification { Message($"Hint: examine the effect your solution has on the {N}-qubit and compare it with the effect it " + "is expected to have. Note that the simulator might drop the global phase -1, so if you're getting " + "verdict \"Incorrect\" but the actual state matches the expected one, check that you're handling the global phase correctly."); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, solution, reference); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, solution, reference); return false; } } diff --git a/katas/content/deutsch_jozsa/parity_oracle/Verification.qs b/katas/content/deutsch_jozsa/parity_oracle/Verification.qs index e8ba3a2002..8326c00fc8 100644 --- a/katas/content/deutsch_jozsa/parity_oracle/Verification.qs +++ b/katas/content/deutsch_jozsa/parity_oracle/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message($"Hint: examine the effect your solution has on the {N}-qubit and compare it with the effect it " + "is expected to have. Note that the simulator might drop the global phase -1, so if you're getting " + "verdict \"Incorrect\" but the actual state matches the expected one, check that you're handling the global phase correctly."); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, solution, reference); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, solution, reference); return false; } } diff --git a/katas/content/distinguishing_states/index.md b/katas/content/distinguishing_states/index.md new file mode 100644 index 0000000000..b37e78e256 --- /dev/null +++ b/katas/content/distinguishing_states/index.md @@ -0,0 +1,48 @@ +# Distinguishing Quantum States + +@[section]({ + "id": "distinguishing_states__overview", + "title": "Overview" +}) + +This kata is designed to get you familiar with the concept of measurements and using them for analyzing quantum states and with Q# programming. + +**This kata covers the following topics:** + +- single-qubit measurements, +- distinguishing orthogonal and non-orthogonal states. + +**What you should know to start working on this kata:** + +- Dirac notation for single-qubit and multi-qubit quantum systems +- Basic single-qubit and multi-qubit gates +- Single-qubit quantum measurements and their effect on quantum systems + +@[section]({ + "id": "distinguishing_states__orthogonal_states", + "title": "Distinguishing Orthogonal States" +}) + +@[exercise]({ + "id": "distinguishing_states__zero_one", + "title": "|0〉 or |1〉?", + "path": "./zero_one/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + + +@[section]({ + "id": "distinguishing_states__nonorthogonal_states", + "title": "Distinguishing Non-orthogonal States" +}) + +@[section]({ + "id": "distinguishing_states__conclusion", + "title": "Conclusion" +}) + +Congratulations! In this kata you learned to use measurements and basic quantum computing gates to distinguish quantum states. Here are a few key concepts to keep in mind: + +- TODO diff --git a/katas/content/distinguishing_states/zero_one/Placeholder.qs b/katas/content/distinguishing_states/zero_one/Placeholder.qs new file mode 100644 index 0000000000..aba0673316 --- /dev/null +++ b/katas/content/distinguishing_states/zero_one/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation IsQubitOne (q : Qubit) : Bool { + // Implement your solution here... + + return false; + } +} diff --git a/katas/content/distinguishing_states/zero_one/Solution.qs b/katas/content/distinguishing_states/zero_one/Solution.qs new file mode 100644 index 0000000000..7174961f55 --- /dev/null +++ b/katas/content/distinguishing_states/zero_one/Solution.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation IsQubitOne (q : Qubit) : Bool { + return M(q) == One; + } +} diff --git a/katas/content/distinguishing_states/zero_one/Verification.qs b/katas/content/distinguishing_states/zero_one/Verification.qs new file mode 100644 index 0000000000..b5b2158d3d --- /dev/null +++ b/katas/content/distinguishing_states/zero_one/Verification.qs @@ -0,0 +1,27 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Katas; + + operation StatePrep_IsQubitOne (q : Qubit, state : Int) : Unit is Adj { + if state == 1 { + // convert |0⟩ to |1⟩ + X(q); + } + } + + @EntryPoint() + operation CheckSolution() : Bool { + let isCorrect = DistinguishTwoStates_SingleQubit( + StatePrep_IsQubitOne, + Kata.IsQubitOne, + ["|0⟩", "|1⟩"], + false); + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect."); + } + isCorrect + } + +} diff --git a/katas/content/distinguishing_states/zero_one/index.md b/katas/content/distinguishing_states/zero_one/index.md new file mode 100644 index 0000000000..4c66f8a942 --- /dev/null +++ b/katas/content/distinguishing_states/zero_one/index.md @@ -0,0 +1,3 @@ +**Input:** A qubit which is guaranteed to be in either the $|0\rangle$ or the $|1\rangle$ state. + +**Output:** `true` if the qubit was in the $|1\rangle$ state, or `false` if it was in the $|0\rangle$ state. The state of the qubit at the end of the operation does not matter. diff --git a/katas/content/distinguishing_states/zero_one/solution.md b/katas/content/distinguishing_states/zero_one/solution.md new file mode 100644 index 0000000000..6049b4f32f --- /dev/null +++ b/katas/content/distinguishing_states/zero_one/solution.md @@ -0,0 +1,8 @@ +The input qubit is guaranteed to be either in basis state $|0\rangle$ or $|1\rangle$. This means that when measuring the qubit in the computational basis, the measurement will report the input state without any doubt. + +In Q# the operation `M` can be used to measure a single qubit in the computational basis. The measurement result is a value of type `Result` - the operation `M` will return `One` if the input qubit was in the $|1\rangle$ state and `Zero` if the input qubit was in the $|0\rangle$ state. Since we need to encode the first case as `true` and the second one as `false`, we can return the result of equality comparison between measurement result and `One`. + +@[solution]({ + "id": "single_qubit_measurements__zero_one_solution", + "codePath": "Solution.qs" +}) diff --git a/katas/content/getting_started/flip_qubit/Verification.qs b/katas/content/getting_started/flip_qubit/Verification.qs index 9d51799003..0d4899250d 100644 --- a/katas/content/getting_started/flip_qubit/Verification.qs +++ b/katas/content/getting_started/flip_qubit/Verification.qs @@ -20,9 +20,7 @@ namespace Kata.Verification { Message("Look out for hints when your solution is incorrect."); Message("Hint: examine the effect your solution has on the |0〉 state and compare it with the effect it " + "is expected to have."); - use target = Qubit[1]; // |0〉 - ShowQuantumStateComparison(target, solution, reference); - ResetAll(target); + ShowQuantumStateComparison(1, (qs => ()), solution, reference); } isCorrect } diff --git a/katas/content/multi_qubit_gates/compound_gate/Verification.qs b/katas/content/multi_qubit_gates/compound_gate/Verification.qs index 8aba18d54e..6fa6658769 100644 --- a/katas/content/multi_qubit_gates/compound_gate/Verification.qs +++ b/katas/content/multi_qubit_gates/compound_gate/Verification.qs @@ -20,12 +20,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + "transformation"); - use initial = Qubit[3]; // |000〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial[0]); - Ry(ArcTan2(0.7, 0.4) * 2.0, initial[1]); - Ry(ArcTan2(0.6, 0.5) * 2.0, initial[2]); - ShowQuantumStateComparison(initial, solution, reference); - ResetAll(initial); + ShowQuantumStateComparison(3, PrepRandomState, solution, reference); } isCorrect diff --git a/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs b/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs new file mode 100644 index 0000000000..f5ee355d30 --- /dev/null +++ b/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs b/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs new file mode 100644 index 0000000000..3a2b89e267 --- /dev/null +++ b/katas/content/multi_qubit_gates/entangle_qubits/Solution.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl { + CNOT(qs[0], qs[1]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs b/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs new file mode 100644 index 0000000000..822d40def7 --- /dev/null +++ b/katas/content/multi_qubit_gates/entangle_qubits/Verification.qs @@ -0,0 +1,52 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + + operation EntangleQubits (qs : Qubit[]) : Unit is Adj + Ctl { + CNOT(qs[0], qs[1]); + } + + operation CheckOperationsEquivalenceOnInitialStateStrict( + initialState : Qubit[] => Unit is Adj, + op : (Qubit[] => Unit is Adj + Ctl), + reference : (Qubit[] => Unit is Adj + Ctl), + inputSize : Int + ) : Bool { + use (control, target) = (Qubit(), Qubit[inputSize]); + within { + H(control); + initialState(target); + } + apply { + Controlled op([control], target); + Adjoint Controlled reference([control], target); + } + let isCorrect = CheckAllZero([control] + target); + ResetAll([control] + target); + isCorrect + } + + operation CheckSolution() : Bool { + let range = 10; + for i in 0 .. range - 1 { + let angle = 2.0 * PI() * IntAsDouble(i) / IntAsDouble(range); + let initialState = qs => Ry(2.0 * angle, qs[0]); + let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict( + initialState, + Kata.EntangleQubits, + EntangleQubits, + 2); + if not isCorrect { + Message("Incorrect"); + Message($"Test fails for alpha = {Cos(angle)}, beta = {Sin(angle)}."); + ShowQuantumStateComparison(2, initialState, Kata.EntangleQubits, EntangleQubits); + return false; + } + } + + Message("Correct!"); + true + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/entangle_qubits/index.md b/katas/content/multi_qubit_gates/entangle_qubits/index.md new file mode 100644 index 0000000000..07ce922724 --- /dev/null +++ b/katas/content/multi_qubit_gates/entangle_qubits/index.md @@ -0,0 +1,5 @@ +**Input:** Two unentangled qubits (stored in an array of length 2). +The first qubit will be in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$, the second - in state $|0\rangle$ +(this can be written as two-qubit state $\big(\alpha |0\rangle + \beta |1\rangle \big) \otimes |0\rangle = \alpha |00\rangle + \beta |10\rangle$). + +**Goal:** Change the two-qubit state to $\alpha |00\rangle + \beta |11\rangle$. diff --git a/katas/content/multi_qubit_gates/entangle_qubits/solution.md b/katas/content/multi_qubit_gates/entangle_qubits/solution.md new file mode 100644 index 0000000000..7326c09106 --- /dev/null +++ b/katas/content/multi_qubit_gates/entangle_qubits/solution.md @@ -0,0 +1,11 @@ +Let's denote the first qubit in state $\alpha |0\rangle + \beta |1\rangle$ as A and the second qubit in state $|0\rangle$ as B. + +Compare our input state $\alpha |0_A0_B\rangle + \beta |1_A0_B\rangle$ with the goal state $\alpha |0_A0_B\rangle + \beta |1_A1_B\rangle$. +We want to pass our input qubit through a gate or gates (to be decided) that do the following. If qubit A is in the $|0\rangle$ state, then we want to leave qubit B alone (the first term of the superposition). +However, if A is in the $|1\rangle$ state, we want to flip qubit B from $|0\rangle$ into $|1\rangle$ state. In other words, the state of B is to be made contingent upon the state of A. +This is exactly the effect of the $CNOT$ gate. Depending upon the state of the **control** qubit (A in our case), the value of the controlled or **target** qubit (B in our case) is inverted or unchanged. Thus, we get the goal state $\alpha |00\rangle + \beta |11\rangle$. + +@[solution]({ + "id": "multi_qubit_gates__entangle_qubits_solution", + "codePath": "./Solution.qs" +}) diff --git a/katas/content/multi_qubit_gates/index.md b/katas/content/multi_qubit_gates/index.md index c5009d5e50..9fa2736944 100644 --- a/katas/content/multi_qubit_gates/index.md +++ b/katas/content/multi_qubit_gates/index.md @@ -121,6 +121,16 @@ $$\alpha|00\rangle + \beta|11\rangle$$ The $CNOT$ gate is self-adjoint: applying it for the second time reverses its effect. + +@[exercise]({ + "id": "multi_qubit_gates__entangle_qubits", + "title": "Entangle Qubits", + "path": "./entangle_qubits/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + @[exercise]({ "id": "multi_qubit_gates__preparing_bell_state", "title": "Preparing a Bell State", @@ -130,6 +140,62 @@ The $CNOT$ gate is self-adjoint: applying it for the second time reverses its ef ] }) +@[section]({ + "id": "multi_qubit_gates__cz_gate", + "title": "CZ Gate" +}) + + +The $CZ$ ("controlled-Z") gate is a two-qubit gate, with one qubit referred to as the **control** qubit, and the other as the **target** qubit. Interestingly, for the $CZ$ gate it doesn't matter which qubit is control and which is target - the effect of the gate is the same either way! + +The $CZ$ gate acts as a conditional gate: if the control qubit is in state $|1\rangle$, it applies the $Z$ gate to the target qubit, otherwise it does nothing. + + + + + + + + + + + + + + +
GateMatrixApplying to $|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle + \delta|11\rangle$Applying to basis states
$CZ$ + $$\begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{bmatrix}$$ + $CZ|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle - \delta|11\rangle$ + $$CZ|00\rangle = |00\rangle$$ + $$CZ|01\rangle = |01\rangle$$ + $$CZ|10\rangle = |10\rangle$$ + $$CZ|11\rangle = -|11\rangle$$ +
+ +The $CZ$ gate is particularly useful for creating and manipulating entangled states where the phase of the quantum state is crucial. Consider the following separable state: + +$$\big(\alpha|0\rangle + \beta|1\rangle\big) \otimes \big(\gamma|0\rangle + \delta|1\rangle\big) = \alpha\gamma|00\rangle + \alpha\delta|01\rangle + \beta\gamma|10\rangle + \beta\delta|11\rangle$$ + +If we apply the $CZ$ gate to it, with the first qubit as the control and the second as the target (or vice versa), we get the following state, which can no longer be separated: + +$$\alpha\gamma|00\rangle + \alpha\delta|01\rangle + \beta\gamma|10\rangle - \beta\delta|11\rangle$$ + +The $CZ$ gate is also self-adjoint: applying it a second time reverses its effect, similar to the $CNOT$ gate. + +@[exercise]({ + "id": "multi_qubit_gates__relative_phase_minusone", + "title": "Relative Phase -1", + "path": "./relative_phase_minusone/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + @[section]({ "id": "multi_qubit_gates__ket_bra_representation", "title": "Ket-Bra Representation" diff --git a/katas/content/multi_qubit_gates/preparing_bell_state/Verification.qs b/katas/content/multi_qubit_gates/preparing_bell_state/Verification.qs index 67ae366c32..7183842ee0 100644 --- a/katas/content/multi_qubit_gates/preparing_bell_state/Verification.qs +++ b/katas/content/multi_qubit_gates/preparing_bell_state/Verification.qs @@ -15,12 +15,10 @@ namespace Kata.Verification { if isCorrect { Message("Correct!"); } else { - Message("Incorrect :("); + Message("Incorrect."); Message("Hint: examine the state prepared by your solution and compare it with the state it " + "is expected to prepare."); - use initial = Qubit[2]; // |00〉 - ShowQuantumStateComparison(initial, solution, reference); - ResetAll(initial); + ShowQuantumStateComparison(2, (qs => ()), solution, reference); } isCorrect diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs new file mode 100644 index 0000000000..c5b83b3112 --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs new file mode 100644 index 0000000000..119e4afa7c --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionA.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl { + CZ(qs[0], qs[1]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs new file mode 100644 index 0000000000..57fcf1a75e --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/SolutionB.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl { + Controlled Z([qs[0]], qs[1]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs new file mode 100644 index 0000000000..e0c35e8609 --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/Verification.qs @@ -0,0 +1,47 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Diagnostics; + + operation PrepareState(qs : Qubit[]) : Unit is Adj + Ctl { + ApplyToEachCA(H, qs); + } + operation RelativePhaseMinusOne (qs : Qubit[]) : Unit is Adj + Ctl { + CZ(qs[0], qs[1]); + } + + operation CheckOperationsEquivalenceOnInitialStateStrict( + initialState : Qubit[] => Unit is Adj, + op : (Qubit[] => Unit is Adj + Ctl), + reference : (Qubit[] => Unit is Adj + Ctl), + inputSize : Int + ) : Bool { + use (control, target) = (Qubit(), Qubit[inputSize]); + within { + H(control); + initialState(target); + } + apply { + Controlled op([control], target); + Adjoint Controlled reference([control], target); + } + let isCorrect = CheckAllZero([control] + target); + ResetAll([control] + target); + isCorrect + } + + operation CheckSolution() : Bool { + let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict( + PrepareState, + Kata.RelativePhaseMinusOne, + RelativePhaseMinusOne, + 2); + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect"); + ShowQuantumStateComparison(2, PrepareState, Kata.RelativePhaseMinusOne, RelativePhaseMinusOne); + } + + return isCorrect; + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/index.md b/katas/content/multi_qubit_gates/relative_phase_minusone/index.md new file mode 100644 index 0000000000..33bcf050f4 --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/index.md @@ -0,0 +1,3 @@ +**Input:** Two unentangled qubits (stored in an array of length 2) in state $|+\rangle \otimes |+\rangle = \frac{1}{2} \big( |00\rangle + |01\rangle + |10\rangle {\color{blue}+} |11\rangle \big)$. + +**Goal:** Change the two-qubit state to $\frac{1}{2} \big( |00\rangle + |01\rangle + |10\rangle {\color{red}-} |11\rangle \big)$. diff --git a/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md b/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md new file mode 100644 index 0000000000..b94bb4f436 --- /dev/null +++ b/katas/content/multi_qubit_gates/relative_phase_minusone/solution.md @@ -0,0 +1,22 @@ +Firstly we notice that we are dealing with an unentangled pair of qubits. +In vector form the transformation we need is +$$ +\frac{1}{2}\begin{bmatrix}1\\\ 1\\\ 1\\\ 1\\\ \end{bmatrix} +\rightarrow +\frac{1}{2}\begin{bmatrix}1\\\ 1\\\ 1\\\ -1\\\ \end{bmatrix} +$$ + +All that needs to happen to change the input into the goal is that the $|11\rangle$ basis state needs to have its sign flipped. + +We remember that the Pauli $Z$ gate flips signs in the single qubit case, and that $CZ$ is the 2-qubit version of this gate. And indeed, the effect of the $CZ$ gate is exactly the transformation we're looking for here. + +@[solution]({ +"id": "multi_qubit_gates__two_qubit_gate_2_solution_a", +"codePath": "./SolutionA.qs" +}) +Alternatively, we can express this gate using the intrinsic gate Z and its controlled variant using the Controlled functor: + +@[solution]({ +"id": "multi_qubit_gates__two_qubit_gate_2_solution_b", +"codePath": "./SolutionB.qs" +}) diff --git a/katas/content/multi_qubit_measurements/Common.qs b/katas/content/multi_qubit_measurements/Common.qs deleted file mode 100644 index 8ffa132dda..0000000000 --- a/katas/content/multi_qubit_measurements/Common.qs +++ /dev/null @@ -1,81 +0,0 @@ -namespace Kata.Verification { - open Microsoft.Quantum.Intrinsic; - open Microsoft.Quantum.Canon; - open Microsoft.Quantum.Diagnostics; - open Microsoft.Quantum.Convert; - open Microsoft.Quantum.Math; - open Microsoft.Quantum.Arrays; - open Microsoft.Quantum.Measurement; - open Microsoft.Quantum.Random; - - // "Framework" operation for testing multi-qubit tasks for distinguishing states of an array of qubits - // with Int return - operation DistinguishStates_MultiQubit( - nQubits : Int, - nStates : Int, - statePrep : ((Qubit[], Int, Double) => Unit is Adj), - testImpl : (Qubit[] => Int), - preserveState : Bool, - stateNames : String[]) : Bool { - - let nTotal = 100; - // misclassifications will store the number of times state i has been classified as state j (dimension nStates^2) - mutable misclassifications = [0, size = nStates * nStates]; - // unknownClassifications will store the number of times state i has been classified as some invalid state (index < 0 or >= nStates) - mutable unknownClassifications = [0, size = nStates]; - - use qs = Qubit[nQubits]; - for i in 1 .. nTotal { - // get a random integer to define the state of the qubits - let state = DrawRandomInt(0, nStates - 1); - // get a random rotation angle to define the exact state of the qubits - // for some exercises, this value might be a dummy variable which does not matter - let alpha = DrawRandomDouble(0.0, 1.0) * PI(); - - // do state prep: convert |0...0⟩ to outcome with return equal to state - statePrep(qs, state, alpha); - - // get the solution's answer and verify that it's a match, if not, increase the exact mismatch count - let ans = testImpl(qs); - if ((ans >= 0) and (ans < nStates)) { - // classification result is a valid state index - check if is it correct - if ans != state { - set misclassifications w/= ((state * nStates) + ans) <- (misclassifications[(state * nStates) + ans] + 1); - } - } - else { - // classification result is an invalid state index - file it separately - set unknownClassifications w/= state <- (unknownClassifications[state] + 1); - } - - if preserveState { - // check that the state of the qubit after the operation is unchanged - Adjoint statePrep(qs, state, alpha); - if not CheckAllZero(qs) { - Message($"Input quantum state {stateNames[state]} was not preserved during the measurement."); - ResetAll(qs); - return false; - } - } else { - // we're not checking the state of the qubit after the operation - ResetAll(qs); - } - } - ResetAll(qs); - - mutable totalMisclassifications = 0; - for i in 0 .. nStates - 1 { - for j in 0 .. nStates - 1 { - if misclassifications[(i * nStates) + j] != 0 { - set totalMisclassifications += misclassifications[i * nStates + j]; - Message($"Misclassified {stateNames[i]} as {stateNames[j]} in {misclassifications[(i * nStates) + j]} test runs."); - } - } - if unknownClassifications[i] != 0 { - set totalMisclassifications += unknownClassifications[i]; - Message($"Misclassified {stateNames[i]} as Unknown State in {unknownClassifications[i]} test runs."); - } - } - totalMisclassifications == 0 - } -} diff --git a/katas/content/multi_qubit_measurements/full_measurements/Verification.qs b/katas/content/multi_qubit_measurements/full_measurements/Verification.qs index 9161fb88ea..0769fe058d 100644 --- a/katas/content/multi_qubit_measurements/full_measurements/Verification.qs +++ b/katas/content/multi_qubit_measurements/full_measurements/Verification.qs @@ -1,4 +1,5 @@ namespace Kata.Verification { + open Microsoft.Quantum.Katas; // Distinguish four basis states operation StatePrep_BasisStateMeasurement(qs : Qubit[], state : Int, dummyVar : Double) : Unit is Adj { diff --git a/katas/content/multi_qubit_measurements/index.md b/katas/content/multi_qubit_measurements/index.md index 3518bf629e..5cee9e6559 100644 --- a/katas/content/multi_qubit_measurements/index.md +++ b/katas/content/multi_qubit_measurements/index.md @@ -245,7 +245,6 @@ Full measurements can also be used to identify the state of the system, if it is "title": "Distinguish Four Basis States", "path": "./full_measurements/", "qsDependencies": [ - "./Common.qs", "../KatasLibrary.qs" ] }) @@ -357,7 +356,6 @@ In certain situations, it is possible to distinguish between orthogonal states o "title": "Distinguish Orthogonal States Using Partial Measurements", "path": "./partial_measurements_for_system/", "qsDependencies": [ - "./Common.qs", "../KatasLibrary.qs" ] }) @@ -416,7 +414,6 @@ For certain multi-qubit systems prepared in a superposition state, it is possibl "title": "State Selection Using Partial Measurements", "path": "./state_modification/", "qsDependencies": [ - "./Common.qs", "../KatasLibrary.qs" ] }) @@ -435,7 +432,6 @@ You could prepare a simpler state involving additional qubits, which, when measu "title": "State Preparation Using Partial Measurements", "path": "./state_preparation/", "qsDependencies": [ - "./Common.qs", "../KatasLibrary.qs" ] }) @@ -564,7 +560,6 @@ Similarly, a parity measurement on a higher number of qubits can be implemented "title": "Two-Qubit Parity Measurement", "path": "./joint_measurements/", "qsDependencies": [ - "./Common.qs", "../KatasLibrary.qs" ] }) diff --git a/katas/content/multi_qubit_measurements/joint_measurements/Verification.qs b/katas/content/multi_qubit_measurements/joint_measurements/Verification.qs index 1bf841a1c7..8440beda6d 100644 --- a/katas/content/multi_qubit_measurements/joint_measurements/Verification.qs +++ b/katas/content/multi_qubit_measurements/joint_measurements/Verification.qs @@ -1,4 +1,5 @@ namespace Kata.Verification { + open Microsoft.Quantum.Katas; // Two qubit parity Measurement operation StatePrep_ParityMeasurement(qs : Qubit[], state : Int, alpha : Double) : Unit is Adj { diff --git a/katas/content/multi_qubit_measurements/partial_measurements_for_system/Verification.qs b/katas/content/multi_qubit_measurements/partial_measurements_for_system/Verification.qs index d2db7ecf52..63885d1563 100644 --- a/katas/content/multi_qubit_measurements/partial_measurements_for_system/Verification.qs +++ b/katas/content/multi_qubit_measurements/partial_measurements_for_system/Verification.qs @@ -1,4 +1,5 @@ namespace Kata.Verification { + open Microsoft.Quantum.Katas; // Distinguish orthogonal states using partial measurements operation StatePrep_IsPlusPlusMinus(qs : Qubit[], state : Int, dummyVar : Double) : Unit is Adj { diff --git a/katas/content/multi_qubit_measurements/state_preparation/Verification.qs b/katas/content/multi_qubit_measurements/state_preparation/Verification.qs index da80fc54f8..d9af9bb16a 100644 --- a/katas/content/multi_qubit_measurements/state_preparation/Verification.qs +++ b/katas/content/multi_qubit_measurements/state_preparation/Verification.qs @@ -3,7 +3,7 @@ namespace Kata.Verification { open Microsoft.Quantum.Katas; open Microsoft.Quantum.Math; - // State preparation using partial measurements + // Reference solution that does not use measurements (to be adjointable) operation StatePrep_Rotations(qs : Qubit[]) : Unit is Adj { // Rotate first qubit to (sqrt(2) |0⟩ + |1⟩) / sqrt(3) let theta = ArcSin(1.0 / Sqrt(3.0)); @@ -16,23 +16,6 @@ namespace Kata.Verification { @EntryPoint() operation CheckSolution() : Bool { - let isCorrect = CheckOperationsEquivalenceOnZeroState(Kata.PostSelection, StatePrep_Rotations, 2); - if (isCorrect) { - Message("Correct!"); - } else { - Message("Incorrect."); - // TODO #1207: refactor kata libraries ShowQuantumStateComparison to not require adjoint - use qs = Qubit[2]; - Message("Expected quantum state after applying the operation:"); - StatePrep_Rotations(qs); - DumpMachine(); - ResetAll(qs); - - Kata.PostSelection(qs); - Message("Actual quantum state after applying the operation:"); - DumpMachine(); - ResetAll(qs); - } - isCorrect + CheckOperationsEquivalenceOnZeroStateWithFeedback(Kata.PostSelection, StatePrep_Rotations, 2) } } diff --git a/katas/content/multi_qubit_systems/bell_state_change_1/Placeholder.qs b/katas/content/multi_qubit_systems/bell_state_change_1/Placeholder.qs new file mode 100644 index 0000000000..f17bd7edf7 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_1/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation BellStateChange1 (qs : Qubit[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_1/Solution.qs b/katas/content/multi_qubit_systems/bell_state_change_1/Solution.qs new file mode 100644 index 0000000000..404c630ca4 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_1/Solution.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation BellStateChange1 (qs : Qubit[]) : Unit is Adj + Ctl { + Z(qs[0]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_1/Verification.qs b/katas/content/multi_qubit_systems/bell_state_change_1/Verification.qs new file mode 100644 index 0000000000..8e8f52eb98 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_1/Verification.qs @@ -0,0 +1,62 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Katas; + + operation PrepareBellState(qs : Qubit[]) : Unit is Adj + Ctl { + H(qs[0]); + CNOT(qs[0], qs[1]); + } + + + operation BellStateChange1_Reference(qs : Qubit[]) : Unit is Adj + Ctl { + Z(qs[0]); + } + + + operation CheckOperationsEquivalenceOnInitialStateStrict( + initialState : Qubit[] => Unit is Adj, + op : (Qubit[] => Unit is Adj + Ctl), + reference : (Qubit[] => Unit is Adj + Ctl), + inputSize : Int + ) : Bool { + use (control, target) = (Qubit(), Qubit[inputSize]); + within { + H(control); + initialState(target); + } + apply { + Controlled op([control], target); + Adjoint Controlled reference([control], target); + } + + + let isCorrect = CheckAllZero([control] + target); + ResetAll([control] + target); + isCorrect + } + + + @EntryPoint() + operation CheckSolution() : Bool { + let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict( + PrepareBellState, + Kata.BellStateChange1, + BellStateChange1_Reference, + 2); + + + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect"); + ShowQuantumStateComparison(2, PrepareBellState, Kata.BellStateChange1, BellStateChange1_Reference); + } + + + return isCorrect; + } + + +} diff --git a/katas/content/multi_qubit_systems/bell_state_change_1/index.md b/katas/content/multi_qubit_systems/bell_state_change_1/index.md new file mode 100644 index 0000000000..74b1d6fc57 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_1/index.md @@ -0,0 +1,3 @@ +**Input:** Two entangled qubits in Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} \big(|00\rangle + |11\rangle\big)$. + +**Goal:** Change the two-qubit state to $|\Phi^{-}\rangle = \frac{1}{\sqrt{2}} \big(|00\rangle - |11\rangle\big)$. diff --git a/katas/content/multi_qubit_systems/bell_state_change_1/solution.md b/katas/content/multi_qubit_systems/bell_state_change_1/solution.md new file mode 100644 index 0000000000..070ba5263e --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_1/solution.md @@ -0,0 +1,16 @@ +We recognize that the goal is another Bell state. In fact, it is one of the four Bell states. + +We remember from the Single-Qubit Gates kata that the Pauli Z gate will change the state of the $|1\rangle$ basis state of a single qubit, so this gate seems like a good candidate for what we want to achieve. This gate leaves the sign of the $|0\rangle$ basis state of a superposition unchanged, but flips the sign of the $|1\rangle$ basis state of the superposition. + +Don't forget that the Z gate acts on only a single qubit, and we have two here. +Let's also remember how the Bell state is made up from its individual qubits. + +If the two qubits are A and B, where A is `qs[0]` and B is `qs[1]`, we can write that +$|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} \big(|0_{A}0_{B}\rangle + |1_{A}1_{B}\rangle\big)$. +If we apply the Z gate to the qubit A, it will flip the phase of the basis state $|1_A\rangle$. As this phase is in a sense spread across the entangled state, with $|1_A\rangle$ basis state being part of the second half of the superposition, this application has the effect of flipping the sign of the whole basis state $|1_A1_B\rangle$, as you can see by running the solution below. + +The exact same calculations can be done if we apply Z to the qubit B, so that's another possible solution. +@[solution]({ +"id": "multi_qubit_systems__bell_state_change_1_solution", +"codePath": "Solution.qs" +}) diff --git a/katas/content/multi_qubit_systems/bell_state_change_2/Placeholder.qs b/katas/content/multi_qubit_systems/bell_state_change_2/Placeholder.qs new file mode 100644 index 0000000000..3d68892d08 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_2/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation BellStateChange2 (qs : Qubit[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_2/Solution.qs b/katas/content/multi_qubit_systems/bell_state_change_2/Solution.qs new file mode 100644 index 0000000000..ffad742359 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_2/Solution.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation BellStateChange2 (qs : Qubit[]) : Unit is Adj + Ctl { + X(qs[0]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_2/Verification.qs b/katas/content/multi_qubit_systems/bell_state_change_2/Verification.qs new file mode 100644 index 0000000000..10810e0389 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_2/Verification.qs @@ -0,0 +1,62 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Katas; + + operation PrepareBellState(qs : Qubit[]) : Unit is Adj + Ctl { + H(qs[0]); + CNOT(qs[0], qs[1]); + } + + + operation BellStateChange2_Reference(qs : Qubit[]) : Unit is Adj + Ctl { + X(qs[0]); + } + + + operation CheckOperationsEquivalenceOnInitialStateStrict( + initialState : Qubit[] => Unit is Adj, + op : (Qubit[] => Unit is Adj + Ctl), + reference : (Qubit[] => Unit is Adj + Ctl), + inputSize : Int + ) : Bool { + use (control, target) = (Qubit(), Qubit[inputSize]); + within { + H(control); + initialState(target); + } + apply { + Controlled op([control], target); + Adjoint Controlled reference([control], target); + } + + + let isCorrect = CheckAllZero([control] + target); + ResetAll([control] + target); + isCorrect + } + + + @EntryPoint() + operation CheckSolution() : Bool { + let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict( + PrepareBellState, + Kata.BellStateChange2, + BellStateChange2_Reference, + 2); + + + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect"); + ShowQuantumStateComparison(2, PrepareBellState, Kata.BellStateChange2, BellStateChange2_Reference); + } + + + return isCorrect; + } + + +} diff --git a/katas/content/multi_qubit_systems/bell_state_change_2/index.md b/katas/content/multi_qubit_systems/bell_state_change_2/index.md new file mode 100644 index 0000000000..2d97bc3f49 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_2/index.md @@ -0,0 +1,3 @@ +**Input:** Two entangled qubits in Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} \big(|00\rangle + |11\rangle\big)$. + +**Goal:** Change the two-qubit state to $|\Psi^{+}\rangle = \frac{1}{\sqrt{2}} \big(|01\rangle + |10\rangle\big)$. \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_2/solution.md b/katas/content/multi_qubit_systems/bell_state_change_2/solution.md new file mode 100644 index 0000000000..263966d3ec --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_2/solution.md @@ -0,0 +1,10 @@ +We have seen in the Single-Qubit Gates kata that the Pauli X gate flips $|0\rangle$ to $|1\rangle$ and vice versa, and as we seem to need some flipping of states, perhaps this gate may be of use. (Bearing in mind, of course, that the X gate operates on a single qubit). + +Let's compare the starting state $\frac{1}{\sqrt{2}} \big(|0_A0_B\rangle + |1_A1_B\rangle\big)$ with the goal state $\frac{1}{\sqrt{2}} \big(1_A0_B\rangle + |0_A1_B\rangle\big)$ term by term and see how we need to transform it to reach the goal. + +Using our nomenclature from "Bell state change 1", we can now see by comparing terms that $|0_{A}\rangle$ has flipped to $|1_A\rangle$ to get the first term, and $|1_{A}\rangle$ has flipped to $|0_A\rangle$ to get the second term. This allows us to say that the correct gate to use is Pauli X, applied to `qs[0]`. + +@[solution]({ +"id": "multi_qubit_systems__bell_state_change_2_solution", +"codePath": "Solution.qs" +}) diff --git a/katas/content/multi_qubit_systems/bell_state_change_3/Placeholder.qs b/katas/content/multi_qubit_systems/bell_state_change_3/Placeholder.qs new file mode 100644 index 0000000000..c8605a172a --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_3/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation BellStateChange3(qs : Qubit[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_3/Solution.qs b/katas/content/multi_qubit_systems/bell_state_change_3/Solution.qs new file mode 100644 index 0000000000..1ca5339915 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_3/Solution.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation BellStateChange3(qs : Qubit[]) : Unit is Adj + Ctl { + X(qs[0]); + Z(qs[0]); + } +} \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_3/Verification.qs b/katas/content/multi_qubit_systems/bell_state_change_3/Verification.qs new file mode 100644 index 0000000000..874988a7b9 --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_3/Verification.qs @@ -0,0 +1,63 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Katas; + + operation PrepareBellState(qs : Qubit[]) : Unit is Adj + Ctl { + H(qs[0]); + CNOT(qs[0], qs[1]); + } + + + operation BellStateChange3_Reference(qs : Qubit[]) : Unit is Adj + Ctl { + X(qs[0]); + Z(qs[0]); + } + + + operation CheckOperationsEquivalenceOnInitialStateStrict( + initialState : Qubit[] => Unit is Adj, + op : (Qubit[] => Unit is Adj + Ctl), + reference : (Qubit[] => Unit is Adj + Ctl), + inputSize : Int + ) : Bool { + use (control, target) = (Qubit(), Qubit[inputSize]); + within { + H(control); + initialState(target); + } + apply { + Controlled op([control], target); + Adjoint Controlled reference([control], target); + } + + + let isCorrect = CheckAllZero([control] + target); + ResetAll([control] + target); + isCorrect + } + + + @EntryPoint() + operation CheckSolution() : Bool { + let isCorrect = CheckOperationsEquivalenceOnInitialStateStrict( + PrepareBellState, + Kata.BellStateChange3, + BellStateChange3_Reference, + 2); + + + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect"); + ShowQuantumStateComparison(2, PrepareBellState, Kata.BellStateChange3, BellStateChange3_Reference); + } + + + return isCorrect; + } + + +} diff --git a/katas/content/multi_qubit_systems/bell_state_change_3/index.md b/katas/content/multi_qubit_systems/bell_state_change_3/index.md new file mode 100644 index 0000000000..389db465be --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_3/index.md @@ -0,0 +1,3 @@ +**Input:** Two entangled qubits in Bell state $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} \big(|00\rangle + |11\rangle\big)$. + +**Goal:** Change the two-qubit state, without adding a global phase, to $|\Psi^{-}\rangle = \frac{1}{\sqrt{2}} \big(|01\rangle - |10\rangle\big)$. \ No newline at end of file diff --git a/katas/content/multi_qubit_systems/bell_state_change_3/solution.md b/katas/content/multi_qubit_systems/bell_state_change_3/solution.md new file mode 100644 index 0000000000..c5b30bc3be --- /dev/null +++ b/katas/content/multi_qubit_systems/bell_state_change_3/solution.md @@ -0,0 +1,11 @@ +We remember from the Single-Qubit Gates kata that the Pauli Z gate leaves the sign of the $|0\rangle$ component of the single qubit superposition unchanged but flips the sign of the $|1\rangle$ component of the superposition. We have also just seen in "Bell State Change 2" how to change our input state to the state $\frac{1}{\sqrt{2}} \big(|01\rangle + |10\rangle\big)$, which is almost our goal state (disregarding the phase change for the moment). So it would seem that a combination of these two gates will be what we need here. The remaining question is in what order to apply them, and to which qubit. + +First of all, which qubit? Looking back at the task "Bell state change 2", it seems clear that we need to use qubit `qs[0]`, like we did there. + +Second, in what order should we apply the gates? Remember that the Pauli Z gate flips the phase of the $|1\rangle$ component of the superposition and leaves the $|0\rangle$ component alone. +Let's experiment with applying X to `qs[0]` first. Looking at our "halfway answer" state $\frac{1}{\sqrt{2}} \big(|01\rangle + |10\rangle\big)$, we can see that if we apply the Z gate to `qs[0]`, it will leave the $|0_{A}\rangle$ alone but flip the phase of $|1_{A}\rangle$ to $-|1_{A}\rangle$, thus flipping the phase of the $|11\rangle$ component of our Bell state. + +@[solution]({ +"id": "multi_qubit_systems__bell_state_change_3_solution", +"codePath": "./Solution.qs" +}) diff --git a/katas/content/multi_qubit_systems/index.md b/katas/content/multi_qubit_systems/index.md index 18c485b767..2efa8043d7 100644 --- a/katas/content/multi_qubit_systems/index.md +++ b/katas/content/multi_qubit_systems/index.md @@ -230,10 +230,10 @@ Just like with single qubits, we can put arbitrary symbols within the kets the s Whether a ket represents a single qubit or an entire system depends on the context. Some ket symbols have a commonly accepted usage, such as the symbols for the Bell basis: -$$|\phi^+\rangle = \frac{1}{\sqrt{2}}\big(|00\rangle + |11\rangle\big)$$ -$$|\phi^-\rangle = \frac{1}{\sqrt{2}}\big(|00\rangle - |11\rangle\big)$$ -$$|\psi^+\rangle = \frac{1}{\sqrt{2}}\big(|01\rangle + |10\rangle\big)$$ -$$|\psi^-\rangle = \frac{1}{\sqrt{2}}\big(|01\rangle - |10\rangle\big)$$ +$$|\Phi^+\rangle = \frac{1}{\sqrt{2}}\big(|00\rangle + |11\rangle\big)$$ +$$|\Phi^-\rangle = \frac{1}{\sqrt{2}}\big(|00\rangle - |11\rangle\big)$$ +$$|\Psi^+\rangle = \frac{1}{\sqrt{2}}\big(|01\rangle + |10\rangle\big)$$ +$$|\Psi^-\rangle = \frac{1}{\sqrt{2}}\big(|01\rangle - |10\rangle\big)$$ >## Endianness > @@ -364,6 +364,40 @@ Array elements are indexed starting with 0, the first array element corresponds ] }) +@[section]({ + "id": "multi_qubit_systems__modifying_entangled_states", + "title": "Modifying Entangled States" +}) + +Entangled quantum states can be manipulated using single-qubit gates. For example, each state in the Bell basis is entangled and can be transformed into another Bell state through the application of single-qubit gates. In this lesson, you'll learn how to do that. (And we will learn more about applying single-qubit gates to multi-qubit states in the next kata.) + +@[exercise]({ + "id": "multi_qubit_systems__bell_state_change_1 ", + "title": "Bell State Change 1", + "path": "./bell_state_change_1/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + +@[exercise]({ + "id": "multi_qubit_systems__bell_state_change_2 ", + "title": "Bell State Change 2", + "path": "./bell_state_change_2/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + +@[exercise]({ + "id": "multi_qubit_systems__bell_state_change_3 ", + "title": "Bell State Change 3", + "path": "./bell_state_change_3/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + @[section]({ "id": "multi_qubit_systems__conclusion", "title": "Conclusion" diff --git a/katas/content/oracles/bit_pattern_challenge/Verification.qs b/katas/content/oracles/bit_pattern_challenge/Verification.qs index 819d742232..07cb3f8991 100644 --- a/katas/content/oracles/bit_pattern_challenge/Verification.qs +++ b/katas/content/oracles/bit_pattern_challenge/Verification.qs @@ -29,10 +29,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle for pattern = {pattern}"); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, sol, ref); return false; } } diff --git a/katas/content/oracles/bit_pattern_oracle/Verification.qs b/katas/content/oracles/bit_pattern_oracle/Verification.qs index f2a09bc774..6238038f89 100644 --- a/katas/content/oracles/bit_pattern_oracle/Verification.qs +++ b/katas/content/oracles/bit_pattern_oracle/Verification.qs @@ -21,10 +21,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle for the pattern {pattern}"); - use initial = Qubit[N + 1]; - PrepRandomState(initial[...N - 1]); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N + 1, qs => PrepRandomState(qs[...N - 1]), sol, ref); return false; } } diff --git a/katas/content/oracles/kth_bit_oracle/Verification.qs b/katas/content/oracles/kth_bit_oracle/Verification.qs index 7f914ffab2..640a50951b 100644 --- a/katas/content/oracles/kth_bit_oracle/Verification.qs +++ b/katas/content/oracles/kth_bit_oracle/Verification.qs @@ -17,10 +17,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle for k = {k}"); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, sol, ref); return false; } } diff --git a/katas/content/oracles/marking_oracle_as_phase/Verification.qs b/katas/content/oracles/marking_oracle_as_phase/Verification.qs index b37238cebb..97eb03c607 100644 --- a/katas/content/oracles/marking_oracle_as_phase/Verification.qs +++ b/katas/content/oracles/marking_oracle_as_phase/Verification.qs @@ -31,10 +31,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle that marks the bit string {pattern}"); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, sol, ref); return false; } } diff --git a/katas/content/oracles/marking_oracle_seven/Verification.qs b/katas/content/oracles/marking_oracle_seven/Verification.qs index 02b37fb9f6..fdc0ecfc98 100644 --- a/katas/content/oracles/marking_oracle_seven/Verification.qs +++ b/katas/content/oracles/marking_oracle_seven/Verification.qs @@ -19,10 +19,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + "transformation"); - use initial = Qubit[4]; // |000〉 - PrepRandomState(initial[...2]); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(4, qs => PrepRandomState(qs[...2]), sol, ref); } isCorrect } diff --git a/katas/content/oracles/or_but_kth_oracle/Verification.qs b/katas/content/oracles/or_but_kth_oracle/Verification.qs index 069c276f40..5c1d794267 100644 --- a/katas/content/oracles/or_but_kth_oracle/Verification.qs +++ b/katas/content/oracles/or_but_kth_oracle/Verification.qs @@ -28,10 +28,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle for k = {k}"); - use initial = Qubit[N]; - PrepRandomState(initial); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N, PrepRandomState, sol, ref); return false; } } diff --git a/katas/content/oracles/or_oracle/Verification.qs b/katas/content/oracles/or_oracle/Verification.qs index 5e9aded69b..bdcf790fde 100644 --- a/katas/content/oracles/or_oracle/Verification.qs +++ b/katas/content/oracles/or_oracle/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + $"transformation for the {N}-bit oracle"); - use initial = Qubit[N + 1]; - PrepRandomState(initial[...N - 1]); - ShowQuantumStateComparison(initial, sol, ref); - ResetAll(initial); + ShowQuantumStateComparison(N + 1, qs => PrepRandomState(qs[...N - 1]), sol, ref); return false; } } diff --git a/katas/content/oracles/phase_oracle_seven/Verification.qs b/katas/content/oracles/phase_oracle_seven/Verification.qs index d3f8550d38..b5e9ed9ff3 100644 --- a/katas/content/oracles/phase_oracle_seven/Verification.qs +++ b/katas/content/oracles/phase_oracle_seven/Verification.qs @@ -20,10 +20,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine how your solution transforms the given state and compare it with the expected " + "transformation"); - use initial = Qubit[3]; // |000〉 - PrepRandomState(initial); - ShowQuantumStateComparison(initial, Kata.IsSeven_PhaseOracle, IsSeven_PhaseOracle_Reference); - ResetAll(initial); + ShowQuantumStateComparison(3, PrepRandomState, Kata.IsSeven_PhaseOracle, IsSeven_PhaseOracle_Reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/amplitude_change/Verification.qs b/katas/content/single_qubit_gates/amplitude_change/Verification.qs index 355d17176d..1a588a031b 100644 --- a/katas/content/single_qubit_gates/amplitude_change/Verification.qs +++ b/katas/content/single_qubit_gates/amplitude_change/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message($"The solution was incorrect for the test case alpha = {alpha}."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); return false; } } diff --git a/katas/content/single_qubit_gates/basis_change/Verification.qs b/katas/content/single_qubit_gates/basis_change/Verification.qs index f426589f96..3462be07df 100644 --- a/katas/content/single_qubit_gates/basis_change/Verification.qs +++ b/katas/content/single_qubit_gates/basis_change/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/complex_phase/Placeholder.qs b/katas/content/single_qubit_gates/complex_phase/Placeholder.qs new file mode 100644 index 0000000000..add005a78d --- /dev/null +++ b/katas/content/single_qubit_gates/complex_phase/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} + diff --git a/katas/content/single_qubit_gates/complex_phase/Solution.qs b/katas/content/single_qubit_gates/complex_phase/Solution.qs new file mode 100644 index 0000000000..53baafa686 --- /dev/null +++ b/katas/content/single_qubit_gates/complex_phase/Solution.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj + Ctl { + R1(alpha, q); + } +} + diff --git a/katas/content/single_qubit_gates/complex_phase/Verification.qs b/katas/content/single_qubit_gates/complex_phase/Verification.qs new file mode 100644 index 0000000000..e2f3de5a18 --- /dev/null +++ b/katas/content/single_qubit_gates/complex_phase/Verification.qs @@ -0,0 +1,29 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + + operation PhaseChange (alpha : Double, q : Qubit) : Unit is Adj+Ctl { + R1(alpha, q); + } + + operation CheckSolution() : Bool { + for i in 0 .. 36 { + let alpha = ((2.0 * PI()) * IntAsDouble(i)) / 36.0; + let solution = register => Kata.PhaseChange(alpha, register[0]); + let reference = register => PhaseChange(alpha, register[0]); + let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1); + if not isCorrect { + Message("Incorrect."); + Message($"The solution was incorrect for the test case alpha = {alpha}."); + Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + + "is expected to have."); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); + return false; + } + } + + Message("Correct!"); + true + } +} \ No newline at end of file diff --git a/katas/content/single_qubit_gates/complex_phase/index.md b/katas/content/single_qubit_gates/complex_phase/index.md new file mode 100644 index 0000000000..2c827d4dfa --- /dev/null +++ b/katas/content/single_qubit_gates/complex_phase/index.md @@ -0,0 +1,10 @@ +**Inputs:** + +1. Angle $\alpha$, in radians, represented as Double. +2. A qubit in state $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$. + +**Goal:** Change the state of the qubit as follows: + +- If the qubit is in state $|0\rangle$, don't change its state. +- If the qubit is in state $|1\rangle$, change its state to $e^{i\alpha} |1\rangle$. +- If the qubit is in superposition, change its state according to the effect on basis vectors: $\beta |0\rangle + {\color{red}{e^{i\alpha}}} \gamma |1\rangle$. diff --git a/katas/content/single_qubit_gates/complex_phase/solution.md b/katas/content/single_qubit_gates/complex_phase/solution.md new file mode 100644 index 0000000000..299549c6d2 --- /dev/null +++ b/katas/content/single_qubit_gates/complex_phase/solution.md @@ -0,0 +1,28 @@ +We know that: + +$$ +R1(\alpha)= + \begin{bmatrix}1 & 0\\\0 & \color{red}{e^{i\alpha}}\end{bmatrix} +$$ + +So we have: + +$$ +R1(\beta |0\rangle + \gamma |1\rangle) = + \begin{bmatrix}1 & 0 \\\0 & \color{red}{e^{i\alpha}} \end{bmatrix} + \begin{bmatrix}\beta\\\ \gamma\\\ \end{bmatrix}= +\begin{bmatrix}1 \cdot \beta + 0 \cdot \gamma\\\ 0 \cdot \beta + {\color{red}{e^{i\alpha}}} \cdot \gamma\end{bmatrix}= + \begin{bmatrix}\beta\\\ {\color{red}{e^{i\alpha}}}\gamma\end{bmatrix}= + \beta |0\rangle + {\color{red}{e^{i\alpha}}} \gamma |1\rangle +$$ + +> Suppose now that $\alpha = \frac{\pi}{2}$. +> Then $e^{i\alpha}= \cos\frac{\pi}{2} + i\sin\frac{\pi}{2}$. +> And, since $\cos\frac{\pi}{2}= 0$ and $\sin\frac{\pi}{2} = 1$, then we have that $\cos\frac{\pi}{2} + i \sin\frac{\pi}{2} = i$, and +> $R1(\frac{\pi}{2}) = S$, which we used in the second solution to the task "Relative Phase i". + +@[solution]({ + "id": "single_qubit_gates__complex_phase_solution", + "codePath": "./Solution.qs" + +}) diff --git a/katas/content/single_qubit_gates/global_phase_i/Verification.qs b/katas/content/single_qubit_gates/global_phase_i/Verification.qs index ee618b0029..c8c7aaa0c2 100644 --- a/katas/content/single_qubit_gates/global_phase_i/Verification.qs +++ b/katas/content/single_qubit_gates/global_phase_i/Verification.qs @@ -20,10 +20,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/global_phase_minusone/Placeholder.qs b/katas/content/single_qubit_gates/global_phase_minusone/Placeholder.qs new file mode 100644 index 0000000000..9035212af1 --- /dev/null +++ b/katas/content/single_qubit_gates/global_phase_minusone/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} + diff --git a/katas/content/single_qubit_gates/global_phase_minusone/Solution.qs b/katas/content/single_qubit_gates/global_phase_minusone/Solution.qs new file mode 100644 index 0000000000..3e95f1e368 --- /dev/null +++ b/katas/content/single_qubit_gates/global_phase_minusone/Solution.qs @@ -0,0 +1,8 @@ +namespace Kata { + operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl { + Z(q); + X(q); + Z(q); + X(q); + } +} diff --git a/katas/content/single_qubit_gates/global_phase_minusone/Verification.qs b/katas/content/single_qubit_gates/global_phase_minusone/Verification.qs new file mode 100644 index 0000000000..9b5441140c --- /dev/null +++ b/katas/content/single_qubit_gates/global_phase_minusone/Verification.qs @@ -0,0 +1,32 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Intrinsic; + + + operation GlobalPhaseChange (q : Qubit) : Unit is Adj + Ctl { + Z(q); + X(q); + Z(q); + X(q); + } + + operation CheckSolution() : Bool { + let solution = register => Kata.GlobalPhaseChange(register[0]); + let reference = register => GlobalPhaseChange(register[0]); + let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1); + + // Output different feedback to the user depending on whether the solution was correct. + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect."); + Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + + "is expected to have."); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); + } + isCorrect + } +} diff --git a/katas/content/single_qubit_gates/global_phase_minusone/index.md b/katas/content/single_qubit_gates/global_phase_minusone/index.md new file mode 100644 index 0000000000..c24dbae504 --- /dev/null +++ b/katas/content/single_qubit_gates/global_phase_minusone/index.md @@ -0,0 +1,7 @@ +**Input:** A qubit in state $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$. + +**Goal**: Change the state of the qubit to $- \beta |0\rangle - \gamma |1\rangle$. + +> This change on its own is not observable - there is no experiment you can do on a standalone qubit to figure out whether it acquired the global phase or not. +> However, you can use a controlled version of this operation to observe the global phase it introduces. +> This is used in later katas as part of more complicated tasks. diff --git a/katas/content/single_qubit_gates/global_phase_minusone/solution.md b/katas/content/single_qubit_gates/global_phase_minusone/solution.md new file mode 100644 index 0000000000..a295bc51a5 --- /dev/null +++ b/katas/content/single_qubit_gates/global_phase_minusone/solution.md @@ -0,0 +1,30 @@ +A global phase is a phase factor that multiplies the entire quantum state. It is not observable when measuring the qubit's state, as the probabilities remain unchanged. However, it is significant when considering quantum state transformations. + +Our task is to implement an operation that transforms the given qubit state from $|\psi\rangle = \beta |0\rangle + \gamma |1\rangle$ to $- \beta |0\rangle - \gamma |1\rangle$. + +To do that, we utilize a sequence of gates. The Pauli Z gate followed by the Pauli X gate can be used to achieve this effect when applied in succession twice. + +1. **Apply the Pauli Z gate**: The Z gate multiplies the $|1\rangle$ state by $-1$ but does not change the $|0\rangle$ state, converting our state $\beta |0\rangle + \gamma |1\rangle$ to $\beta |0\rangle - \gamma |1\rangle$. + + The matrix representation of the Z gate is: + + $$ + Z = + \begin{bmatrix}1 & 0 \\\ 0 & -1 \end{bmatrix} + $$ + +2. **Apply the Pauli X gate**: The X gate flips the $|0\rangle$ and $|1\rangle$ basis states, converting our $\beta |0\rangle - \gamma |1\rangle$ state to $\beta |1\rangle - \gamma |0\rangle$. + + The matrix representation of the X gate is: + + $$ + X = + \begin{bmatrix}0 & 1 \\\1 & 0\end{bmatrix} + $$ + +3. **Repeat the Z and X gates**: Applying the Z gate again will multiply the $|1\rangle$ state (that used to be $|0\rangle$), converting our state $\beta |1\rangle - \gamma |0\rangle$ to $- \beta |1\rangle - \gamma |0\rangle$. Finally, the second X gate will restore the original basis states, but now with both amplitudes having acquired an additional phase of $-1$. This means our state has been multiplied by $-1$, achieving the required global phase change. + +@[solution]({ +"id": "single_qubit_gates__global_phase_minusone_solution", +"codePath": "./Solution.qs" +}) diff --git a/katas/content/single_qubit_gates/index.md b/katas/content/single_qubit_gates/index.md index 264fad9dba..0e41691d47 100644 --- a/katas/content/single_qubit_gates/index.md +++ b/katas/content/single_qubit_gates/index.md @@ -353,23 +353,33 @@ All the basic gates we will be covering in this kata are part of the Intrinsic n }) @[exercise]({ - "id": "single_qubit_gates__global_phase_i", - "title": "Applying a Global Phase", - "path": "./global_phase_i/", + "id": "single_qubit_gates__sign_flip_on_zero", + "title": "Sign Flip on Zero", + "path": "./sign_flip_on_zero/", "qsDependencies": [ "../KatasLibrary.qs" ] }) @[exercise]({ - "id": "single_qubit_gates__sign_flip_on_zero", - "title": "Sign Flip on Zero", - "path": "./sign_flip_on_zero/", + "id": "single_qubit_gates__global_phase_minusone", + "title": "Global Phase -1", + "path": "./global_phase_minusone/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + +@[exercise]({ + "id": "single_qubit_gates__global_phase_i", + "title": "Global Phase i", + "path": "./global_phase_i/", "qsDependencies": [ "../KatasLibrary.qs" ] }) + @[section]({ "id": "identity_gate", "title": "Identity Gate" @@ -488,6 +498,15 @@ The next two gates are known as phase shift gates. They apply a phase to the $|1 > Notice that applying the $T$ gate twice is equivalent to applying the $S$ gate, and applying the $S$ gate twice is equivalent to applying the $Z$ gate: $$T^2 = S, S^2 = Z$$ +@[exercise]({ + "id": "single_qubit_gates__phase_i", + "title": "Relative Phase i", + "path": "./phase_i/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + @[exercise]({ "id": "single_qubit_gates__three_quarters_pi_phase", "title": "Three-Fourths Phase", @@ -571,14 +590,24 @@ In addition, the rotation gates are very closely related to their respective Pau $$X = iR_x(\pi), Y = iR_y(\pi), Z = iR_z(\pi)$$ +@[exercise]({ + "id": "single_qubit_gates__complex_phase", + "title": "Complex Relative Phase", + "path": "./complex_phase/", + "qsDependencies": [ + "../KatasLibrary.qs" + ] +}) + @[exercise]({ "id": "single_qubit_gates__amplitude_change", - "title": "Amplitude_Change", + "title": "Amplitude Change", "path": "./amplitude_change/", "qsDependencies": [ "../KatasLibrary.qs" ] }) + @[exercise]({ "id": "single_qubit_gates__prepare_rotated_state", "title": "Prepare Rotated State", diff --git a/katas/content/single_qubit_gates/phase_i/Placeholder.qs b/katas/content/single_qubit_gates/phase_i/Placeholder.qs new file mode 100644 index 0000000000..09eb8f8cf4 --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} + diff --git a/katas/content/single_qubit_gates/phase_i/SolutionA.qs b/katas/content/single_qubit_gates/phase_i/SolutionA.qs new file mode 100644 index 0000000000..23b1f2ccb0 --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/SolutionA.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl { + S(q); + } +} \ No newline at end of file diff --git a/katas/content/single_qubit_gates/phase_i/SolutionB.qs b/katas/content/single_qubit_gates/phase_i/SolutionB.qs new file mode 100644 index 0000000000..d87e676c0a --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/SolutionB.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl { + open Microsoft.Quantum.Math; + R1(0.5 * PI(), q); + } +} \ No newline at end of file diff --git a/katas/content/single_qubit_gates/phase_i/Verification.qs b/katas/content/single_qubit_gates/phase_i/Verification.qs new file mode 100644 index 0000000000..56b1d6e0d2 --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/Verification.qs @@ -0,0 +1,26 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Convert; + + operation PhaseFlip (q : Qubit) : Unit is Adj + Ctl { + S(q); + } + + operation CheckSolution() : Bool { + let solution = register => Kata.PhaseFlip(register[0]); + let reference = register => PhaseFlip(register[0]); + let isCorrect = CheckOperationsEquivalenceStrict(solution, reference, 1); + + // Output different feedback to the user depending on whether the solution was correct. + if isCorrect { + Message("Correct!"); + } else { + Message("Incorrect."); + Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + + "is expected to have."); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); + } + isCorrect + } +} diff --git a/katas/content/single_qubit_gates/phase_i/index.md b/katas/content/single_qubit_gates/phase_i/index.md new file mode 100644 index 0000000000..cc4ebd4bde --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/index.md @@ -0,0 +1,3 @@ +**Input:** A qubit in state $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$. + +**Goal:** Change the qubit state to $\alpha |0\rangle + {\color{red}i}\beta |1\rangle$ (add a relative phase $i$ to $|1\rangle$ component of the superposition). diff --git a/katas/content/single_qubit_gates/phase_i/solution.md b/katas/content/single_qubit_gates/phase_i/solution.md new file mode 100644 index 0000000000..ed9d065159 --- /dev/null +++ b/katas/content/single_qubit_gates/phase_i/solution.md @@ -0,0 +1,32 @@ +#### Solution 1 + +We can recognize that the $S$ gate performs this particular relative phase addition to the $|1\rangle$ basis state. As a reminder, + +$$ +S = +\begin{bmatrix}1 & 0\\\ 0 & i\end{bmatrix} +$$ + +Let's see the effect of this gate on the general superposition $|\psi\rangle = \alpha |0\rangle + \beta |1\rangle$. + +$$ + \begin{bmatrix}1 & 0 \\\ 0 & i \end{bmatrix} + \begin{bmatrix}\alpha\\\ \beta\\\ \end{bmatrix}= +\begin{bmatrix}1\cdot\alpha + 0\cdot\beta\\\ 0\cdot\alpha + i\cdot\beta \end{bmatrix}= + \begin{bmatrix}\alpha\\\ i\beta\\\ \end{bmatrix} +$$ + +It is therefore easy to see that when $|\psi\rangle = 0.6|0\rangle + 0.8|1\rangle, S|\psi\rangle = 0.6|0\rangle + 0.8i|1\rangle$. +@[solution]({ + "id": "single_qubit_gates__phase_i_solution_a", + "codePath": "./SolutionA.qs" +}) + +#### Solution 2 + +Alternatively, see the Complex Relative Phase task later in the kata for an explanation of using $R1$ gate to implement the same transformation. + +@[solution]({ + "id": "single_qubit_gates_phase_i_solution_b", + "codePath": "./SolutionB.qs" +}) diff --git a/katas/content/single_qubit_gates/prepare_arbitrary_state/Verification.qs b/katas/content/single_qubit_gates/prepare_arbitrary_state/Verification.qs index 1d6a60c23d..123bfb9e46 100644 --- a/katas/content/single_qubit_gates/prepare_arbitrary_state/Verification.qs +++ b/katas/content/single_qubit_gates/prepare_arbitrary_state/Verification.qs @@ -23,9 +23,7 @@ namespace Kata.Verification { Message($"The solution was incorrect for the test case alpha = {alpha}, beta = {beta}, theta = {theta}."); Message("Hint: examine the state prepared by your solution and compare it with the state it " + "is expected to prepare."); - use initial = Qubit(); // |0〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => (), solution, reference); return false; } } diff --git a/katas/content/single_qubit_gates/prepare_minus/Verification.qs b/katas/content/single_qubit_gates/prepare_minus/Verification.qs index 56fa9c0797..a6f46732a1 100644 --- a/katas/content/single_qubit_gates/prepare_minus/Verification.qs +++ b/katas/content/single_qubit_gates/prepare_minus/Verification.qs @@ -18,9 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the state prepared by your solution and compare it with the state it " + "is expected to prepare."); - use initial = Qubit(); // |0〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => (), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/prepare_rotated_state/Verification.qs b/katas/content/single_qubit_gates/prepare_rotated_state/Verification.qs index b1045668ec..fc29dc6387 100644 --- a/katas/content/single_qubit_gates/prepare_rotated_state/Verification.qs +++ b/katas/content/single_qubit_gates/prepare_rotated_state/Verification.qs @@ -21,9 +21,7 @@ namespace Kata.Verification { Message($"The solution was incorrect for the test case alpha = {alpha}, beta = {beta}."); Message("Hint: examine the state prepared by your solution and compare it with the state it " + "is expected to prepare."); - use initial = Qubit(); // |0〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => (), solution, reference); return false; } } diff --git a/katas/content/single_qubit_gates/sign_flip/Verification.qs b/katas/content/single_qubit_gates/sign_flip/Verification.qs index 14e5bf17f9..6981d5493e 100644 --- a/katas/content/single_qubit_gates/sign_flip/Verification.qs +++ b/katas/content/single_qubit_gates/sign_flip/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/sign_flip_on_zero/Verification.qs b/katas/content/single_qubit_gates/sign_flip_on_zero/Verification.qs index 505f54fb22..d2a89ea877 100644 --- a/katas/content/single_qubit_gates/sign_flip_on_zero/Verification.qs +++ b/katas/content/single_qubit_gates/sign_flip_on_zero/Verification.qs @@ -20,10 +20,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/state_flip/Verification.qs b/katas/content/single_qubit_gates/state_flip/Verification.qs index 2c4303f5f6..51e0139bc5 100644 --- a/katas/content/single_qubit_gates/state_flip/Verification.qs +++ b/katas/content/single_qubit_gates/state_flip/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/three_quarters_pi_phase/Verification.qs b/katas/content/single_qubit_gates/three_quarters_pi_phase/Verification.qs index 95b39ca4f0..7c8a17c10f 100644 --- a/katas/content/single_qubit_gates/three_quarters_pi_phase/Verification.qs +++ b/katas/content/single_qubit_gates/three_quarters_pi_phase/Verification.qs @@ -19,10 +19,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_gates/y_gate/Verification.qs b/katas/content/single_qubit_gates/y_gate/Verification.qs index d597cd3b15..5317d3c348 100644 --- a/katas/content/single_qubit_gates/y_gate/Verification.qs +++ b/katas/content/single_qubit_gates/y_gate/Verification.qs @@ -18,10 +18,7 @@ namespace Kata.Verification { Message("Incorrect."); Message("Hint: examine the effect your solution has on the state 0.6|0〉 + 0.8|1〉 and compare it with the effect it " + "is expected to have."); - use initial = Qubit(); // |0〉 - Ry(ArcTan2(0.8, 0.6) * 2.0, initial); // 0.6|0〉 + 0.8|1〉 - ShowQuantumStateComparison([initial], solution, reference); - Reset(initial); + ShowQuantumStateComparison(1, qs => Ry(ArcTan2(0.8, 0.6) * 2.0, qs[0]), solution, reference); } isCorrect } diff --git a/katas/content/single_qubit_measurements/Common.qs b/katas/content/single_qubit_measurements/Common.qs deleted file mode 100644 index 94fd302020..0000000000 --- a/katas/content/single_qubit_measurements/Common.qs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Kata.Verification { - open Microsoft.Quantum.Diagnostics; - open Microsoft.Quantum.Intrinsic; - open Microsoft.Quantum.Random; - - // "Framework" operation for testing single-qubit tasks for distinguishing states of one qubit - // with Bool return - operation DistinguishTwoStates( - statePrep : ((Qubit, Int) => Unit is Adj), - testImpl : (Qubit => Bool), - stateName : String[], - checkFinalState : Bool) : Bool { - - let nTotal = 100; - let nStates = 2; - mutable misclassifications = Repeated(0, nStates); - - use q = Qubit(); - for i in 1 .. nTotal { - // get a random bit to define whether qubit will be in a state corresponding to true return (1) or to false one (0) - // state = 0 false return - // state = 1 true return - let state = DrawRandomInt(0, 1); - - // do state prep: convert |0⟩ to outcome with false return or to outcome with true return depending on state - statePrep(q, state); - - // get the solution's answer and verify if NOT a match, then differentiate what kind of mismatch - let ans = testImpl(q); - if ans != (state == 1) { - set misclassifications w/= state <- misclassifications[state] + 1; - } - - // If the final state is to be verified, check if it matches the measurement outcome - if checkFinalState { - Adjoint statePrep(q, state); - Fact(CheckZero(q), "Returned Bool value does not match the expected qubit state."); - } else { - Reset(q); - } - } - - mutable totalMisclassifications = 0; - for i in 0 .. nStates - 1 { - if misclassifications[i] != 0 { - set totalMisclassifications += misclassifications[i]; - Message($"Misclassified {stateName[i]} as {stateName[1 - i]} in {misclassifications[i]} test runs."); - } - } - - return totalMisclassifications == 0; - } - -} diff --git a/katas/content/single_qubit_measurements/a_b_basis_measurements/Verification.qs b/katas/content/single_qubit_measurements/a_b_basis_measurements/Verification.qs index 3e4bb56b04..79057e37c2 100644 --- a/katas/content/single_qubit_measurements/a_b_basis_measurements/Verification.qs +++ b/katas/content/single_qubit_measurements/a_b_basis_measurements/Verification.qs @@ -1,6 +1,7 @@ namespace Kata.Verification { open Microsoft.Quantum.Convert; open Microsoft.Quantum.Math; + open Microsoft.Quantum.Katas; // Measure state in {|A❭, |B❭} basis // |A⟩ = cos(alpha) * |0⟩ - i sin(alpha) * |1⟩, @@ -20,7 +21,7 @@ namespace Kata.Verification { operation CheckSolution() : Bool { for i in 0 .. 10 { let alpha = (PI() * IntAsDouble(i)) / 10.0; - let isCorrect = DistinguishTwoStates( + let isCorrect = DistinguishTwoStates_SingleQubit( StatePrep_IsQubitA(alpha, _, _), q => Kata.MeasureInABBasis(alpha, q) == Zero, [$"|B⟩=(-i sin({i}π/10)|0⟩ + cos({i}π/10)|1⟩)", $"|A⟩=(cos({i}π/10)|0⟩ + i sin({i}π/10)|1⟩)"], diff --git a/katas/content/single_qubit_measurements/a_b_basis_measurements/solution.md b/katas/content/single_qubit_measurements/a_b_basis_measurements/solution.md index 8a95bb25c3..5ef28c8373 100644 --- a/katas/content/single_qubit_measurements/a_b_basis_measurements/solution.md +++ b/katas/content/single_qubit_measurements/a_b_basis_measurements/solution.md @@ -10,6 +10,5 @@ The final rotation ensures that the state of the qubit is in the state correspon @[solution]({ "id": "single_qubit_measurements__a_b_basis_measurements_solution", - "exerciseId": "a_b_basis_measurements", "codePath": "Solution.qs" }) diff --git a/katas/content/single_qubit_measurements/distinguish_0_and_1/Verification.qs b/katas/content/single_qubit_measurements/distinguish_0_and_1/Verification.qs index 6cda22249c..ebb1b59fe8 100644 --- a/katas/content/single_qubit_measurements/distinguish_0_and_1/Verification.qs +++ b/katas/content/single_qubit_measurements/distinguish_0_and_1/Verification.qs @@ -1,6 +1,6 @@ namespace Kata.Verification { open Microsoft.Quantum.Diagnostics; - open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Katas; operation StatePrep_IsQubitZero (q : Qubit, state : Int) : Unit is Adj { if state == 0 { @@ -11,7 +11,7 @@ namespace Kata.Verification { @EntryPoint() operation CheckSolution() : Bool { - let isCorrect = DistinguishTwoStates( + let isCorrect = DistinguishTwoStates_SingleQubit( StatePrep_IsQubitZero, Kata.IsQubitZero, ["|1⟩", "|0⟩"], diff --git a/katas/content/single_qubit_measurements/distinguish_0_and_1/solution.md b/katas/content/single_qubit_measurements/distinguish_0_and_1/solution.md index 9d27623ff6..fa0b837641 100644 --- a/katas/content/single_qubit_measurements/distinguish_0_and_1/solution.md +++ b/katas/content/single_qubit_measurements/distinguish_0_and_1/solution.md @@ -4,6 +4,5 @@ In Q# the operation `M` can be used to measure a single qubit in the computation @[solution]({ "id": "single_qubit_measurements__distinguish_0_and_1_solution", - "exerciseId": "distinguish_0_and_1", "codePath": "Solution.qs" }) diff --git a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/Verification.qs b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/Verification.qs index b0d4e88c94..a3c3102fdf 100644 --- a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/Verification.qs +++ b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/Verification.qs @@ -1,5 +1,6 @@ namespace Kata.Verification { open Microsoft.Quantum.Math; + open Microsoft.Quantum.Katas; // Distinguish specific orthogonal states // |ψ₊⟩ = 0.6 * |0⟩ + 0.8 * |1⟩, @@ -16,7 +17,7 @@ namespace Kata.Verification { } operation CheckSolution() : Bool { - let isCorrect = DistinguishTwoStates( + let isCorrect = DistinguishTwoStates_SingleQubit( StatePrep_IsQubitPsiPlus, Kata.IsQubitPsiPlus, ["|ψ₋⟩", "|ψ₊⟩"], diff --git a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/solution.md b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/solution.md index b0f3363f30..dc429a712e 100644 --- a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/solution.md +++ b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_1/solution.md @@ -13,6 +13,5 @@ Hence, if we apply $R_y(-\theta)$ to the qubit, its state will be transformed to @[solution]({ "id": "single_qubit_measurements__distinguish_orthogonal_states_1_solution", - "exerciseId": "distinguish_orthogonal_states_1", "codePath": "Solution.qs" }) diff --git a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/Verification.qs b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/Verification.qs index ba0a61048c..536b9654ee 100644 --- a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/Verification.qs +++ b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/Verification.qs @@ -1,6 +1,7 @@ namespace Kata.Verification { open Microsoft.Quantum.Convert; open Microsoft.Quantum.Math; + open Microsoft.Quantum.Katas; // Distinguish states |A❭ and |B❭ // |A⟩ = cos(alpha) * |0⟩ - i sin(alpha) * |1⟩, @@ -20,7 +21,7 @@ namespace Kata.Verification { operation CheckSolution() : Bool { for i in 0 .. 10 { let alpha = (PI() * IntAsDouble(i)) / 10.0; - let isCorrect = DistinguishTwoStates( + let isCorrect = DistinguishTwoStates_SingleQubit( StatePrep_IsQubitA(alpha, _, _), Kata.IsQubitA(alpha, _), [$"|B⟩ = -i sin({i}π/10)|0⟩ + cos({i}π/10)|1⟩", $"|A⟩ = cos({i}π/10)|0⟩ + i sin({i}π/10)|1⟩"], diff --git a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/solution.md b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/solution.md index f13481300e..21ae49a421 100644 --- a/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/solution.md +++ b/katas/content/single_qubit_measurements/distinguish_orthogonal_states_2/solution.md @@ -11,6 +11,5 @@ Therefore, if we apply $R_x(-\theta)$ to the qubit and measure it using `M`, a m @[solution]({ "id": "single_qubit_measurements__distinguish_orthogonal_states_2_solution", - "exerciseId": "distinguish_orthogonal_states_2", "codePath": "Solution.qs" }) diff --git a/katas/content/single_qubit_measurements/distinguish_plus_and_minus/Verification.qs b/katas/content/single_qubit_measurements/distinguish_plus_and_minus/Verification.qs index 3d699e10a8..5939a5ceb7 100644 --- a/katas/content/single_qubit_measurements/distinguish_plus_and_minus/Verification.qs +++ b/katas/content/single_qubit_measurements/distinguish_plus_and_minus/Verification.qs @@ -1,4 +1,5 @@ namespace Kata.Verification { + open Microsoft.Quantum.Katas; // Distinguish |+❭ and |-❭ using Measure operation operation StatePrep_IsQubitMinus (q : Qubit, state : Int) : Unit is Adj { @@ -14,7 +15,7 @@ namespace Kata.Verification { @EntryPoint() operation CheckSolution() : Bool { - let isCorrect = DistinguishTwoStates( + let isCorrect = DistinguishTwoStates_SingleQubit( StatePrep_IsQubitMinus, Kata.IsQubitMinus, ["|+⟩", "|-⟩"], diff --git a/katas/content/single_qubit_measurements/distinguish_plus_and_minus/solution.md b/katas/content/single_qubit_measurements/distinguish_plus_and_minus/solution.md index e13cb9130c..177b84b9bc 100644 --- a/katas/content/single_qubit_measurements/distinguish_plus_and_minus/solution.md +++ b/katas/content/single_qubit_measurements/distinguish_plus_and_minus/solution.md @@ -7,6 +7,5 @@ Note that since `Measure` operation generally works with multiple qubits to perf @[solution]({ "id": "single_qubit_measurements__distinguish_plus_and_minus_solution", - "exerciseId": "distinguish_plus_and_minus", "codePath": "Solution.qs" }) diff --git a/katas/content/single_qubit_measurements/index.md b/katas/content/single_qubit_measurements/index.md index d9b233c0c6..9c372fa0fa 100644 --- a/katas/content/single_qubit_measurements/index.md +++ b/katas/content/single_qubit_measurements/index.md @@ -112,8 +112,7 @@ Measurements can be used to distinguish orthogonal states. We start with an exer "title": "Distinguish |0〉 and |1〉", "path": "./distinguish_0_and_1/", "qsDependencies": [ - "../KatasLibrary.qs", - "./Common.qs" + "../KatasLibrary.qs" ] }) @@ -142,8 +141,7 @@ The probabilities of outcomes $\ket{b_0}$ and $\ket{b_1}$ will be defined as $|c "title": "Distinguish |+〉 and |-〉", "path": "./distinguish_plus_and_minus/", "qsDependencies": [ - "../KatasLibrary.qs", - "./Common.qs" + "../KatasLibrary.qs" ] }) @@ -302,8 +300,7 @@ This procedure can be used to distinguish arbitrary orthogonal states as well, a "title": "Distinguishing Orthogonal States: 1", "path": "./distinguish_orthogonal_states_1/", "qsDependencies": [ - "../KatasLibrary.qs", - "./Common.qs" + "../KatasLibrary.qs" ] }) @@ -312,8 +309,7 @@ This procedure can be used to distinguish arbitrary orthogonal states as well, a "title": "Distinguishing Orthogonal States: 2", "path": "./distinguish_orthogonal_states_2/", "qsDependencies": [ - "../KatasLibrary.qs", - "./Common.qs" + "../KatasLibrary.qs" ] }) @@ -322,8 +318,7 @@ This procedure can be used to distinguish arbitrary orthogonal states as well, a "title": "Measurement in the |A〉, |B〉 Basis", "path": "./a_b_basis_measurements/", "qsDependencies": [ - "../KatasLibrary.qs", - "./Common.qs" + "../KatasLibrary.qs" ] }) diff --git a/katas/content/superposition/four_bitstrings/Placeholder.qs b/katas/content/superposition/four_bitstrings/Placeholder.qs new file mode 100644 index 0000000000..97e8bf3295 --- /dev/null +++ b/katas/content/superposition/four_bitstrings/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit { + // Hint: remember that you can allocate extra qubits. + + // Implement your solution here... + } +} diff --git a/katas/content/superposition/four_bitstrings/SolutionA.qs b/katas/content/superposition/four_bitstrings/SolutionA.qs new file mode 100644 index 0000000000..fcaf4bf190 --- /dev/null +++ b/katas/content/superposition/four_bitstrings/SolutionA.qs @@ -0,0 +1,26 @@ +namespace Kata { + operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit { + use anc = Qubit[2]; + // Put two ancillas into equal superposition of 2-qubit basis states + ApplyToEachA(H, anc); + + // Set up the right pattern on the main qubits with control on ancillas + for i in 0 .. 3 { + for j in 0 .. Length(qs) - 1 { + if bits[i][j] { + ApplyControlledOnInt(i, X, anc, qs[j]); + } + } + } + + // Uncompute the ancillas, using patterns on main qubits as control + for i in 0 .. 3 { + if i % 2 == 1 { + ApplyControlledOnBitString(bits[i], X, qs, anc[0]); + } + if i / 2 == 1 { + ApplyControlledOnBitString(bits[i], X, qs, anc[1]); + } + } + } +} diff --git a/katas/content/superposition/four_bitstrings/SolutionB.qs b/katas/content/superposition/four_bitstrings/SolutionB.qs new file mode 100644 index 0000000000..10c62a3a0f --- /dev/null +++ b/katas/content/superposition/four_bitstrings/SolutionB.qs @@ -0,0 +1,40 @@ +namespace Kata { + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Math; + + operation FourBitstringSuperposition (qs : Qubit[], bits : Bool[][]) : Unit { + FourBitstringSuperposition_Recursive([], qs, bits); + } + + operation FourBitstringSuperposition_Recursive (currentBitString : Bool[], qs : Qubit[], bits : Bool[][]) : Unit { + // an array of bit strings whose columns we are considering begin with |0⟩ + mutable zeroLeads = []; + // an array of bit strings whose columns we are considering begin with |1⟩ + mutable oneLeads = []; + // the number of bit strings we're considering + let rows = Length(bits); + // the current position we're considering + let currentIndex = Length(currentBitString); + + if rows >= 1 and currentIndex < Length(qs) { + // figure out what percentage of the bits should be |0⟩ + for row in 0 .. rows - 1 { + if bits[row][currentIndex] { + set oneLeads = oneLeads + [bits[row]]; + } else { + set zeroLeads = zeroLeads + [bits[row]]; + } + } + // rotate the qubit to adjust coefficients based on the previous bit string + // for the first path through, when the bit string has zero length, + // the Controlled version of the rotation will perform a regular rotation + let theta = ArcCos(Sqrt(IntAsDouble(Length(zeroLeads)) / IntAsDouble(rows))); + ApplyControlledOnBitString(currentBitString, Ry, qs[0 .. currentIndex - 1], + (2.0 * theta, qs[currentIndex])); + + // call state preparation recursively based on the bit strings so far + FourBitstringSuperposition_Recursive(currentBitString + [false], qs, zeroLeads); + FourBitstringSuperposition_Recursive(currentBitString + [true], qs, oneLeads); + } + } +} diff --git a/katas/content/superposition/four_bitstrings/Verification.qs b/katas/content/superposition/four_bitstrings/Verification.qs new file mode 100644 index 0000000000..3adcf6100e --- /dev/null +++ b/katas/content/superposition/four_bitstrings/Verification.qs @@ -0,0 +1,61 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Math; + open Microsoft.Quantum.Random; + + operation FourBitstringSuperposition_Reference (qs : Qubit[], bits : Bool[][]) : Unit is Adj { + use anc = Qubit[2]; + ApplyToEachA(H, anc); + + for i in 0 .. 3 { + for j in 0 .. Length(qs) - 1 { + if bits[i][j] { + ApplyControlledOnInt(i, X, anc, qs[j]); + } + } + } + + for i in 0 .. 3 { + if i % 2 == 1 { + ApplyControlledOnBitString(bits[i], X, qs, anc[0]); + } + if i / 2 == 1 { + ApplyControlledOnBitString(bits[i], X, qs, anc[1]); + } + } + } + + @EntryPoint() + operation CheckSolution() : Bool { + + let bits = [[false, false], [false, true], [true, false], [true, true]]; + Message($"Testing for bits = {bits}..."); + if not CheckOperationsEquivalenceOnZeroStateWithFeedback( + Kata.FourBitstringSuperposition(_, bits), + ApplyToEachA(H, _), + 2 + ) { + return false; + } + + let bitstrings = [ + [[false, true, false], [true, false, false], [false, false, true], [true, true, false]], + [[true, false, false], [false, false, true], [false, true, false], [true, true, true]], + [[false, false, false], [false, true, false], [true, true, false], [true, false, true]] + ]; + + for bitstring in bitstrings { + Message($"Testing for bits = {bitstring}..."); + if not CheckOperationsEquivalenceOnZeroStateWithFeedback( + Kata.FourBitstringSuperposition(_, bitstring), + FourBitstringSuperposition_Reference(_, bitstring), + 3 + ) { + return false; + } + } + + return true; + } +} diff --git a/katas/content/superposition/four_bitstrings/index.md b/katas/content/superposition/four_bitstrings/index.md new file mode 100644 index 0000000000..25b98c4cb8 --- /dev/null +++ b/katas/content/superposition/four_bitstrings/index.md @@ -0,0 +1,14 @@ +**Inputs:** + +1. $N$ qubits in the $|0 \dots 0\rangle$ state. +2. Four bit strings represented as `Bool[][]` `bits`. + + `bits` is an array of size $4 \times N$ which describes the bit strings as follows: + - `bits[i]` describes the *i*th bit string and has $N$ elements. + - All four bit strings will be distinct. + +**Goal:** Create an equal superposition of the four basis states given by the bit strings. + +**Example:** + +For $N = 3$ and `bits = [[false, true, false], [true, false, false], [false, false, true], [true, true, false]]`, the state you need to prepare is $\frac{1}{2} \big(|010\rangle + |100\rangle + |001\rangle + |110\rangle\big)$. diff --git a/katas/content/superposition/four_bitstrings/solution.md b/katas/content/superposition/four_bitstrings/solution.md new file mode 100644 index 0000000000..4009f1f46b --- /dev/null +++ b/katas/content/superposition/four_bitstrings/solution.md @@ -0,0 +1,60 @@ +We are going to use the same trick of auxiliary qubits that we used in the previous task. + +Since the desired superposition has 4 basis states with equal amplitudes, we are going to need two qubits to define a unique basis to control preparation of each of the basis states in the superposition. + +We start by allocating two extra qubits and preparing an equal superposition of all 2-qubit states on them by applying an H gate to each of them: + +$$\frac12 (|00\rangle + |01\rangle + |10\rangle + |11\rangle)_a \otimes |0 \dots 0\rangle_r$$ + +Then, for each of the four given bit strings, we walk through it and prepare the matching basis state on the main register of qubits, using controlled X gates with the corresponding basis state of the auxiliary qubits as control. + +For example, when preparing the bit string `bits[0]`, we apply X gates controlled on the basis state $|00\rangle$; when preparing the bit string `bits[1]`, we apply X gates controlled on $|10\rangle$, and so on. + +> We can choose an arbitrary matching of the 2-qubit basis states used as controls and the bit strings prepared on the main register. +> Since all amplitudes are the same, the result does not depend on which state controlled which bit string preparation. +> It can be convenient to use indices of the bit strings, converted to little-endian, to control preparation of the bit strings. +> Q# library function [`ApplyControlledOnInt`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonint) does exactly that. + +After this the system will be in the state + +$$\frac12 (|00\rangle_a |bits_0\rangle_r + |10\rangle_a |bits_1\rangle_r + |01\rangle_a |bits_2\rangle_r + |11\rangle_a |bits_3\rangle_r)$$ + +As the last step, we must uncompute the auxiliary qubits, i.e., return them to the $|00\rangle$ state to unentangle them from the main register. + +Same as we did in the previous task, we will use [`ApplyControlledOnBitString`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonbitstring) with the corresponding bit string and the X operation as arguments, the quantum register as the control, and the auxiliary qubits as the target. + +We will uncompute each of them separately, so one of the auxiliary qubits will be uncomputed with the `bits[1]` and `bits[3]` bit strings as controls, and the other - with the `bits[2]` and `bits[3]`. + +@[solution]({ + "id": "superposition__four_bitstrings_solution_a", + "codePath": "./SolutionA.qs" +}) + +Alternatively, we can leverage the recursion abilities of Q# to create a superposition of the four bit strings. This solution also extends to an arbitrary number of bit strings with no code changes. + +For this process we will look at the first bits of each string and adjust the probability of measuring a $|0\rangle$ or $|1\rangle$ accordingly on the first qubit of our answer. We will then recursively call (as needed) the process again to adjust the probabilities of measurement on the second bit depending on the first bit. This process recurses until no more input bits are provided. + +Consider, for example, the following four bit strings on which to create a superposition: $|001\rangle, |101\rangle, |111\rangle, |110\rangle$. + +We can rewrite the superposition state we need to prepare as + +$$\frac12 \big(|001\rangle + |101\rangle + |111\rangle + |110\rangle \big) = \frac12 |0\rangle \otimes |01\rangle + \frac{\sqrt3}{2} |1\rangle \otimes \frac{1}{\sqrt3} \big(|10\rangle + |11\rangle + |10\rangle \big)$$ + +As the first step of the solution, we need to prepare a state $\frac12 |0\rangle + \frac{\sqrt3}{2} |1\rangle$ on the first qubit (to measure $|0\rangle$ with $\frac14$ probability and to measure $|1\rangle$ with $\frac34$ probability). To do this, we will apply an [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation to the first qubit. + +After this, we'll need to prepare the rest of the qubits in appropriate states depending on the state of the first qubit - state $|01\rangle$ if the first qubit is in state $|0\rangle$ and state $\frac{1}{\sqrt3} \big(|10\rangle + |11\rangle + |10\rangle \big)$ if the first qubit is in state $|1\rangle$. We can do this recursively using the same logic. Let's finish walking through this example in detail. + +The second qubit of the recursion follows similarly but depends on the first qubit. If the first qubit measures $|0\rangle$, then we want the second qubit to measure $|0\rangle$ with 100% probability, but if it measures $|1\rangle$, we want it to measure $|0\rangle$ with $\frac13$ probability and $|1\rangle$ with $\frac23$ probability. For this, we can do a controlled [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation on the second qubit with the first qubit as control. + +The third qubit in this example will have three cases because it depends on the first two qubits; this follows naturally from the recursion. + +1. If the first two qubits measure $|00\rangle$, then we need the third qubit to measure $|0\rangle$ with 100% probability. +2. If the first two qubits measure $|10\rangle$, then we need the third qubit to measure $|1\rangle$ with 100% probability. +3. If the first two qubits measure $|11\rangle$, then we need the third qubit to measure $|0\rangle$ with $\frac12$ probability and $|1\rangle$ with $\frac12$ probability. Just as with the second qubit, a controlled [`Ry`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.intrinsic/ry) rotation on the third qubit will accomplish this goal. + +> We will use [`ApplyControlledOnBitString`](https://learn.microsoft.com/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonbitstring) operation to perform rotations depending on the state of several previous qubits. + +@[solution]({ + "id": "superposition__four_bitstrings_solution_b", + "codePath": "./SolutionB.qs" +}) diff --git a/katas/content/superposition/index.md b/katas/content/superposition/index.md index bd1c0207ad..9104292a2c 100644 --- a/katas/content/superposition/index.md +++ b/katas/content/superposition/index.md @@ -123,11 +123,52 @@ This kata is designed to get you familiar with the concept of superposition and ] }) +@[exercise]({ + "id": "superposition__zero_and_bitstring", + "title": "Zero and Bitstring", + "path": "./zero_and_bitstring/", + "qsDependencies": [ + "../KatasLibrary.qs", + "./Common.qs" + ] +}) + +@[exercise]({ + "id": "superposition__two_bitstrings", + "title": "Two Bitstrings", + "path": "./two_bitstrings/", + "qsDependencies": [ + "../KatasLibrary.qs", + "./Common.qs" + ] +}) + +@[exercise]({ + "id": "superposition__four_bitstrings", + "title": "Four Bit Strings", + "path": "./four_bitstrings/", + "qsDependencies": [ + "../KatasLibrary.qs", + "./Common.qs" + ] +}) + @[section]({ - "id": "superposition__uneven_superpositions", - "title": "Uneven superpositions" + "id": "superposition__arbitrary_rotations", + "title": "Arbitrary Rotations" +}) + +@[exercise]({ + "id": "superposition__unequal_superposition", + "title": "Unequal Superposition", + "path": "./unequal_superposition/", + "qsDependencies": [ + "../KatasLibrary.qs", + "./Common.qs" + ] }) + @[section]({ "id": "superposition__conclusion", "title": "Conclusion" diff --git a/katas/content/superposition/two_bitstrings/Placeholder.qs b/katas/content/superposition/two_bitstrings/Placeholder.qs new file mode 100644 index 0000000000..03ca580a85 --- /dev/null +++ b/katas/content/superposition/two_bitstrings/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation TwoBitstringSuperposition (qs : Qubit[], bits1 : Bool[], bits2 : Bool[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} + diff --git a/katas/content/superposition/two_bitstrings/SolutionA.qs b/katas/content/superposition/two_bitstrings/SolutionA.qs new file mode 100644 index 0000000000..daeaff5eac --- /dev/null +++ b/katas/content/superposition/two_bitstrings/SolutionA.qs @@ -0,0 +1,18 @@ +namespace Kata { + operation TwoBitstringSuperposition (qs : Qubit[], bits1 : Bool[], bits2 : Bool[]) : Unit is Adj + Ctl { + use q = Qubit(); + H(q); + + for i in 0 .. Length(qs) - 1 { + if bits1[i] { + ApplyControlledOnInt(0, X, [q], qs[i]); + } + if bits2[i] { + ApplyControlledOnInt(1, X, [q], qs[i]); + } + } + + // uncompute the auxiliary qubit to release it + ApplyControlledOnBitString(bits2, X, qs, q); + } +} diff --git a/katas/content/superposition/two_bitstrings/SolutionB.qs b/katas/content/superposition/two_bitstrings/SolutionB.qs new file mode 100644 index 0000000000..ea518a5b59 --- /dev/null +++ b/katas/content/superposition/two_bitstrings/SolutionB.qs @@ -0,0 +1,36 @@ +namespace Kata { + function FindFirstDiff (bits1 : Bool[], bits2 : Bool[]) : Int { + for i in 0 .. Length(bits1) - 1 { + if bits1[i] != bits2[i] { + return i; + } + } + return -1; + } + + operation TwoBitstringSuperposition (qs : Qubit[], bits1 : Bool[], bits2 : Bool[]) : Unit is Adj + Ctl { + // find the index of the first bit at which the bit strings are different + let firstDiff = FindFirstDiff(bits1, bits2); + + // Hadamard corresponding qubit to create superposition + H(qs[firstDiff]); + + // iterate through the bit strings again setting the final state of qubits + for i in 0 .. Length(qs) - 1 { + if bits1[i] == bits2[i] { + // if two bits are the same, apply X or nothing + if bits1[i] { + X(qs[i]); + } + } else { + // if two bits are different, set their difference using CNOT + if i > firstDiff { + CNOT(qs[firstDiff], qs[i]); + if bits1[i] != bits1[firstDiff] { + X(qs[i]); + } + } + } + } + } +} diff --git a/katas/content/superposition/two_bitstrings/Verification.qs b/katas/content/superposition/two_bitstrings/Verification.qs new file mode 100644 index 0000000000..d59842bccd --- /dev/null +++ b/katas/content/superposition/two_bitstrings/Verification.qs @@ -0,0 +1,76 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Katas; + + function FindFirstDiff (bits1 : Bool[], bits2 : Bool[]) : Int { + for i in 0 .. Length(bits1) - 1 { + if bits1[i] != bits2[i] { + return i; + } + } + return -1; + } + + operation TwoBitstringSuperposition_Reference (qs : Qubit[], bits1 : Bool[], bits2 : Bool[]) : Unit is Adj + Ctl { + // find the index of the first bit at which the bit strings are different + let firstDiff = FindFirstDiff(bits1, bits2); + + // Hadamard corresponding qubit to create superposition + H(qs[firstDiff]); + + // iterate through the bit strings again setting the final state of qubits + for i in 0 .. Length(qs) - 1 { + if bits1[i] == bits2[i] { + // if two bits are the same, apply X or nothing + if bits1[i] { + X(qs[i]); + } + } else { + // if two bits are different, set their difference using CNOT + if i > firstDiff { + CNOT(qs[firstDiff], qs[i]); + if bits1[i] != bits1[firstDiff] { + X(qs[i]); + } + } + } + } + } + + @EntryPoint() + operation CheckSolution() : Bool { + let bitstrings_one = [ + [false], + [true], + [false, false], + [true, false], + [false, false], + [false, false, false], + [false, false, false] + ]; + + let bitstrings_two = [ + [true], + [false], + [false, true], + [false, true], + [true, true], + [false, false, true], + [true, false, true] + ]; + + for i in 0 .. Length(bitstrings_one) - 1 { + let bits1 = bitstrings_one[i]; + let bits2 = bitstrings_two[i]; + Message($"Testing for bits1 = {bits1} and bits2 = {bits2}..."); + if not CheckOperationsEquivalenceOnZeroStateWithFeedback( + Kata.TwoBitstringSuperposition(_, bits1, bits2), + TwoBitstringSuperposition_Reference(_, bits1, bits2), + Length(bits1)) { + return false; + } + } + + true + } +} diff --git a/katas/content/superposition/two_bitstrings/index.md b/katas/content/superposition/two_bitstrings/index.md new file mode 100644 index 0000000000..028e745ca9 --- /dev/null +++ b/katas/content/superposition/two_bitstrings/index.md @@ -0,0 +1,8 @@ +**Inputs:** + +1. $N$ ($N \ge 1$) qubits in the $|0 \dots 0\rangle$ state. +2. Two bit strings of length $N$ represented as `Bool[]`s. Bit values `false` and `true` correspond to $|0\rangle$ and $|1\rangle$ states. You are guaranteed that the two bit strings differ in at least one bit. + +**Goal:** Change the state of the qubits to an equal superposition of the basis states given by the bit strings. + +> For example, for bit strings `[false, true, false]` and `[false, false, true]` the state required is $\frac{1}{\sqrt{2}}\big(|010\rangle + |001\rangle\big)$. \ No newline at end of file diff --git a/katas/content/superposition/two_bitstrings/solution.md b/katas/content/superposition/two_bitstrings/solution.md new file mode 100644 index 0000000000..b3e49610e0 --- /dev/null +++ b/katas/content/superposition/two_bitstrings/solution.md @@ -0,0 +1,33 @@ +The strategy of using an auxiliary qubit to control the preparation process described in the previous task can be applied to this task as well. + +We will start by allocating an auxiliary qubit and preparing it in the $\frac{1}{\sqrt2} (|0\rangle + |1\rangle)$ state using the $H$ gate. The overall state of the system will be + +$$\frac{1}{\sqrt2} (|0\rangle + |1\rangle)_a \otimes |0 \dots 0\rangle_r = \frac{1}{\sqrt2} (|0\rangle_a \otimes |0 \dots 0\rangle_r + |1\rangle_a \otimes |0 \dots 0\rangle_r)$$ + +At this point, we can prepare the two basis states of the target state separately, bit by bit, controlling the preparation of one of them on the $|0\rangle$ state of the auxiliary qubit and the preparation of the other one - on the $|1\rangle$ state. +If a bit in one of the bit strings is `true`, we will apply a controlled $X$ gate with the auxiliary qubit as control, the qubit in the corresponding position of the register as target, and control it on the $|0\rangle$ or the $|1\rangle$ state depending on which bit string we are considering at the moment. +Such controlled gate can be implemented using [`ApplyControlledOnInt`](https://learn.microsoft.com/en-us/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonint) library function. + +After this the state of the system will be +$$\frac{1}{\sqrt2} (|0\rangle_a \otimes |bits_1\rangle_r + |1\rangle_a \otimes |bits_2\rangle_r)$$ + +Finally, we will uncompute the auxiliary qubit by using [`ApplyControlledOnBitString`](https://learn.microsoft.com/en-us/qsharp/api/qsharp-lang/microsoft.quantum.canon/applycontrolledonbitstring) library function with the second bit string and the `X` operation as arguments, the quantum register as the control, and the auxiliary qubit as the target. +This will affect only the $|1\rangle_a \otimes |bits_2\rangle_r$ term, flipping the state of the auxiliary qubit in it and bringing the system to its final state: + +$$|0\rangle_a \otimes \frac{1}{\sqrt2} (|bits_1\rangle + |bits_2\rangle)_r$$ + +@[solution]({ + "id": "superposition__two_bitstrings_solution_a", + "codePath": "./SolutionA.qs" +}) + +It is also possible to solve the task without using an extra qubit, if instead we use one of the qubits in the register in this role. +While walking through the register and bit strings, the first time the bit strings disagreed, the qubit in the corresponding position would take on the role of the auxiliary qubit; we would put it in superposition using the $H$ gate and perform all subsequent bit flips using that qubit as the control. + +This saves us an additional qubit and allows to skip the uncomputing step, though the code becomes less elegant. +We will move the classical logic of comparing two bit strings to find the first position in which they differ to a function `FindFirstDiff`. + +@[solution]({ + "id": "superposition__two_bitstrings_solution_b", + "codePath": "./SolutionB.qs" +}) diff --git a/katas/content/superposition/unequal_superposition/Placeholder.qs b/katas/content/superposition/unequal_superposition/Placeholder.qs new file mode 100644 index 0000000000..5803f99dd6 --- /dev/null +++ b/katas/content/superposition/unequal_superposition/Placeholder.qs @@ -0,0 +1,6 @@ +namespace Kata { + operation UnequalSuperposition(q : Qubit, alpha : Double) : Unit { + // Implement your solution here... + + } +} diff --git a/katas/content/superposition/unequal_superposition/Solution.qs b/katas/content/superposition/unequal_superposition/Solution.qs new file mode 100644 index 0000000000..225e0ffecb --- /dev/null +++ b/katas/content/superposition/unequal_superposition/Solution.qs @@ -0,0 +1,5 @@ +namespace Kata { + operation UnequalSuperposition(q : Qubit, alpha : Double) : Unit { + Ry(2.0 * alpha, q); + } +} diff --git a/katas/content/superposition/unequal_superposition/Verification.qs b/katas/content/superposition/unequal_superposition/Verification.qs new file mode 100644 index 0000000000..9ebc411a98 --- /dev/null +++ b/katas/content/superposition/unequal_superposition/Verification.qs @@ -0,0 +1,28 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Katas; + open Microsoft.Quantum.Math; + + operation UnequalSuperposition_Reference(q : Qubit, alpha : Double) : Unit is Adj + Ctl { + Ry(2.0 * alpha, q); + } + + @EntryPoint() + operation CheckSolution() : Bool { + let limit = 36; + for i in 0 .. limit { + let alpha = 2.0 * PI() * IntAsDouble(i) / IntAsDouble(limit); + let solution = Kata.UnequalSuperposition(_, alpha); + let reference = UnequalSuperposition_Reference(_, alpha); + Message($"Testing for alpha = {alpha}..."); + if not CheckOperationsEquivalenceOnZeroStateWithFeedback( + qs => solution(qs[0]), + qs => reference(qs[0]), + 1) { + return false; + } + } + + true + } +} diff --git a/katas/content/superposition/unequal_superposition/index.md b/katas/content/superposition/unequal_superposition/index.md new file mode 100644 index 0000000000..decae4db3f --- /dev/null +++ b/katas/content/superposition/unequal_superposition/index.md @@ -0,0 +1,12 @@ +**Inputs:** + +1. A qubit in the $|0\rangle$ state. +2. Angle $\alpha$, in radians, represented as `Double`. + +**Goal**: Change the state of the qubit to $\cos{α} |0\rangle + \sin{α} |1\rangle$. + +
+ Need a hint? + Experiment with rotation gates you learned about in the Single-Qubit Gates kata. + Note that all rotation operators rotate the state by half of its angle argument. +
diff --git a/katas/content/superposition/unequal_superposition/solution.md b/katas/content/superposition/unequal_superposition/solution.md new file mode 100644 index 0000000000..61604fc99f --- /dev/null +++ b/katas/content/superposition/unequal_superposition/solution.md @@ -0,0 +1,20 @@ +We want to convert the $\ket{0}$ state to a parameterized superposition of $\ket{0}$ and $\ket{1}$, which suggests +that we are looking for some kind of a rotation operation. There are three main gates that implement rotations around various axes of the Bloch Sphere: + +- $R_x(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix}$ +- $R_y(\theta) = \begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix}$ +- $R_z(\theta) = \begin{bmatrix} e^{-i\theta/2} & 0 \\\ 0 & e^{i\theta/2} \end{bmatrix}$ + +If we were to apply the $R_x$ gate to a qubit in the $|0\rangle$ state, we would introduce complex coefficients to the amplitudes, which is clearly not what we're looking for. Similarly, the $R_z$ gate introduces only a global phase when applied to $|0\rangle$ state, so we can rule it out as well. This leaves only the $R_y$ as a starting point for the solution. + +Applying the $R_y$ gate to the $|0\rangle$ state, we get: +$$R_y(\theta) |0\rangle = +\begin{bmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{bmatrix} \begin{bmatrix} 1 \\\ 0 \end{bmatrix} = +\begin{bmatrix} \cos\frac{\theta}{2} \\\ \sin\frac{\theta}{2} \end{bmatrix} = \cos\frac{\theta}{2}|0\rangle + \sin\frac{\theta}{2}|1\rangle$$ + +Therefore, applying the $R_y(2\alpha)$ gate to $|0\rangle$ is the solution to our problem. + +@[solution]({ + "id": "superposition__unequal_superposition_solution", + "codePath": "./Solution.qs" +}) diff --git a/katas/content/superposition/zero_and_bitstring/Placeholder.qs b/katas/content/superposition/zero_and_bitstring/Placeholder.qs new file mode 100644 index 0000000000..68496ee96b --- /dev/null +++ b/katas/content/superposition/zero_and_bitstring/Placeholder.qs @@ -0,0 +1,7 @@ +namespace Kata { + operation ZeroAndBitstringSuperposition (qs : Qubit[], bits : Bool[]) : Unit is Adj + Ctl { + // Implement your solution here... + + } +} + diff --git a/katas/content/superposition/zero_and_bitstring/Solution.qs b/katas/content/superposition/zero_and_bitstring/Solution.qs new file mode 100644 index 0000000000..372593918d --- /dev/null +++ b/katas/content/superposition/zero_and_bitstring/Solution.qs @@ -0,0 +1,11 @@ +namespace Kata { + operation ZeroAndBitstringSuperposition (qs : Qubit[], bits : Bool[]) : Unit is Adj + Ctl { + H(qs[0]); + + for i in 1 .. Length(qs) - 1 { + if bits[i] { + CNOT(qs[0], qs[i]); + } + } + } +} diff --git a/katas/content/superposition/zero_and_bitstring/Verification.qs b/katas/content/superposition/zero_and_bitstring/Verification.qs new file mode 100644 index 0000000000..dadee06727 --- /dev/null +++ b/katas/content/superposition/zero_and_bitstring/Verification.qs @@ -0,0 +1,39 @@ +namespace Kata.Verification { + open Microsoft.Quantum.Convert; + open Microsoft.Quantum.Katas; + + operation ZeroAndBitstringSuperposition_Reference (qs : Qubit[], bits : Bool[]) : Unit is Adj + Ctl { + H(qs[0]); + + for i in 1 .. Length(qs) - 1 { + if bits[i] { + CNOT(qs[0], qs[i]); + } + } + } + + @EntryPoint() + operation CheckSolution() : Bool { + let bitstrings = [ + [true], + [true, false], + [true, true], + [true, false, false], + [true, false, true], + [true, true, false], + [true, true, true] + ]; + + for bits in bitstrings { + Message($"Testing for bits = {bits}..."); + if not CheckOperationsEquivalenceOnZeroStateWithFeedback( + Kata.ZeroAndBitstringSuperposition(_, bits), + ZeroAndBitstringSuperposition_Reference(_, bits), + Length(bits)) { + return false; + } + } + + true + } +} diff --git a/katas/content/superposition/zero_and_bitstring/index.md b/katas/content/superposition/zero_and_bitstring/index.md new file mode 100644 index 0000000000..7848379759 --- /dev/null +++ b/katas/content/superposition/zero_and_bitstring/index.md @@ -0,0 +1,8 @@ +**Inputs:** + +1. $N$ ($N \ge 1$) qubits in the $|0 \dots 0\rangle$ state. +2. A bit string of length $N$ represented as `Bool[]`. Bit values `false` and `true` correspond to $|0\rangle$ and $|1\rangle$ states. You are guaranteed that the first bit of the bit string is `true`. + +**Goal:** Change the state of the qubits to an equal superposition of $|0 \dots 0\rangle$ and the basis state given by the bit string. + +> For example, for the bit string `[true, false]` the state required is $\frac{1}{\sqrt{2}}\big(|00\rangle + |10\rangle\big)$. \ No newline at end of file diff --git a/katas/content/superposition/zero_and_bitstring/solution.md b/katas/content/superposition/zero_and_bitstring/solution.md new file mode 100644 index 0000000000..0a930fd1ef --- /dev/null +++ b/katas/content/superposition/zero_and_bitstring/solution.md @@ -0,0 +1,24 @@ +> A common strategy for preparing a superposition state in a qubit register is using an auxiliary qubit (or several, for more complicated states). The auxiliary qubit can be put into a superposition state through the usual means of applying a Hadamard gate (or a rotation about the Y axis for an uneven superposition). +> Then the basis states of the desired superposition are prepared individually based on the auxiliary qubit state by using it as the control qubit for a $CNOT$ gate. One of the basis states will be prepared controlled on the $|0\rangle$ component of the auxiliary state, and the other - controlled on the $|1\rangle$ component. +> Finally, you have to return the auxiliary qubit to the $|0\rangle$ state by uncomputing it, i.e., by using the basis state prepared from the $|1\rangle$ component as the control qubits for a $CNOT$ gate with the auxiliary qubit as the target. +> +> More details on using this approach can be found in the solution to "Superposition of Four Bitstrings" and "W State on $2^k$ Qubits" tasks. However, for this task we can come up with a simpler solution. +> Instead of allocating a new qubit to use as the auxiliary, we can use the first qubit in the register for this purpose, because we are guaranteed that the first bit in the two basis vectors that comprise the required superposition is different. +> This saves us the need to allocate a new qubit and lets us skip the uncomputing step, as the qubit acting as the control for the next preparation steps is part of the desired result. + +Consider the earlier tasks in this kata that asked to prepare Bell states and GHZ state; the structure of the superposition state in this task is a more general case of those scenarios: all of them ask to prepare an equal superposition of two different basis states. + +The first step of the solution is the same as in those tasks: put the first qubit in the register into an equal superposition of $|0\rangle$ and $|1\rangle$ using the H gate to get the following state: + +$$\frac{1}{\sqrt2} (|0\rangle + |1\rangle) \otimes |0 \dots 0\rangle = \frac{1}{\sqrt2} (|00 \dots 0\rangle + |10 \dots 0\rangle)$$ + +The first term of the superposition already matches the desired state, so we need to fix the second term. +To do that, we will walk through the remaining qubits in the register, checking if the bit in the corresponding position of the bit string `bits` is `true`. +If it is, that qubit's state needs to be adjusted from $0$ to $1$ in the second term of our superposition (and left unchanged in the first term). +We can do this change using the $CNOT$ gate with the first qubit as the control and the current qubit as the target. +When we have finished walking through the register like this, the register will be in the desired superposition. + +@[solution]({ + "id": "superposition__zero_and_bitstring_solution", + "codePath": "./Solution.qs" +}) diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs index a006ea0c90..e721f911d3 100644 --- a/language_service/src/compilation.rs +++ b/language_service/src/compilation.rs @@ -77,25 +77,13 @@ impl Compilation { .get(package_id) .expect("expected to find user package"); - // baseprofchk will handle the case where the target profile is Base - if errors.is_empty() - && target_profile != Profile::Unrestricted - && target_profile != Profile::Base - { - let cap_results = PassContext::run_fir_passes_on_hir( - &package_store, - package_id, - target_profile.into(), - ); - if let Err(caps_errors) = cap_results { - for err in caps_errors { - errors.push(WithSource::from_map( - &unit.sources, - compile::ErrorKind::Pass(err), - )); - } - } - } + run_fir_passes( + &mut errors, + target_profile, + &package_store, + package_id, + unit, + ); let lints = qsc::linter::run_lints(unit, Some(lints_config)); for lint in lints { @@ -146,6 +134,17 @@ impl Compilation { } let (package_store, package_id) = compiler.into_package_store(); + let unit = package_store + .get(package_id) + .expect("expected to find user package"); + + run_fir_passes( + &mut errors, + target_profile, + &package_store, + package_id, + unit, + ); Self { package_store, @@ -244,6 +243,44 @@ impl Compilation { } } +/// Runs the passes required for code generation +/// appending any errors to the `errors` vector. +/// This function only runs passes if there are no compile +/// errors in the package and if the target profile is not `Base` +/// or `Unrestricted`. +fn run_fir_passes( + errors: &mut Vec>, + target_profile: Profile, + package_store: &PackageStore, + package_id: PackageId, + unit: &CompileUnit, +) { + if !errors.is_empty() { + // can't run passes on a package with errors + return; + } + + if target_profile == Profile::Base { + // baseprofchk will handle the case where the target profile is Base + return; + } + + if target_profile == Profile::Unrestricted { + // no point in running passes on unrestricted profile + return; + } + + let (fir_store, fir_package_id) = qsc::lower_hir_to_fir(package_store, package_id); + let caps_results = + PassContext::run_fir_passes_on_fir(&fir_store, fir_package_id, target_profile.into()); + if let Err(caps_errors) = caps_results { + for err in caps_errors { + let err = WithSource::from_map(&unit.sources, compile::ErrorKind::Pass(err)); + errors.push(err); + } + } +} + impl Lookup for Compilation { /// Looks up the type of a node in user code fn get_ty(&self, id: ast::NodeId) -> Option<&hir::ty::Ty> { diff --git a/language_service/src/completion.rs b/language_service/src/completion.rs index 25a9c0c6ec..7f068323f4 100644 --- a/language_service/src/completion.rs +++ b/language_service/src/completion.rs @@ -152,16 +152,26 @@ pub(crate) fn get_completions( } fn get_first_non_whitespace_in_source(compilation: &Compilation, package_offset: u32) -> u32 { + const QSHARP_MAGIC: &str = "//qsharp"; let source = compilation .user_unit() .sources .find_by_offset(package_offset) .expect("source should exist in the user source map"); - let first = source - .contents - .find(|c: char| !c.is_whitespace()) - .unwrap_or(source.contents.len()); + // Skip the //qsharp magic if it exists (notebook cells) + let start = if let Some(qsharp_magic_start) = source.contents.find(QSHARP_MAGIC) { + qsharp_magic_start + QSHARP_MAGIC.len() + } else { + 0 + }; + + let source_after_magic = &source.contents[start..]; + + let first = start + + source_after_magic + .find(|c: char| !c.is_whitespace()) + .unwrap_or(source_after_magic.len()); let first = u32::try_from(first).expect("source length should fit into u32"); diff --git a/language_service/src/completion/tests.rs b/language_service/src/completion/tests.rs index 34a6c24517..1e0bcaaf5e 100644 --- a/language_service/src/completion/tests.rs +++ b/language_service/src/completion/tests.rs @@ -1038,8 +1038,20 @@ fn notebook_block() { fn notebook_auto_open_start_of_cell_empty() { check_notebook( &[ - ("cell1", "namespace Foo { operation Bar() : Unit {} }"), - ("cell2", "↘"), + ( + "cell1", + indoc! {" + //qsharp + namespace Foo { operation Bar() : Unit {} }" + }, + ), + ( + "cell2", + indoc! {" + //qsharp + ↘" + }, + ), ], &["Fake"], &expect![[r#" @@ -1060,11 +1072,11 @@ fn notebook_auto_open_start_of_cell_empty() { new_text: "open FakeStdLib;\n", range: Range { start: Position { - line: 0, + line: 1, column: 0, }, end: Position { - line: 0, + line: 1, column: 0, }, }, @@ -1082,8 +1094,21 @@ fn notebook_auto_open_start_of_cell_empty() { fn notebook_auto_open_start_of_cell() { check_notebook( &[ - ("cell1", "namespace Foo { operation Bar() : Unit {} }"), - ("cell2", r#" Message("hi") ↘"#), + ( + "cell1", + indoc! {" + //qsharp + namespace Foo { operation Bar() : Unit {} }" + }, + ), + ( + "cell2", + indoc! {r#" + //qsharp + Message("hi") + ↘"# + }, + ), ], &["Fake"], &expect![[r#" @@ -1101,15 +1126,15 @@ fn notebook_auto_open_start_of_cell() { additional_text_edits: Some( [ TextEdit { - new_text: "open FakeStdLib;\n ", + new_text: "open FakeStdLib;\n", range: Range { start: Position { - line: 0, - column: 3, + line: 1, + column: 0, }, end: Position { - line: 0, - column: 3, + line: 1, + column: 0, }, }, }, diff --git a/language_service/src/state/tests.rs b/language_service/src/state/tests.rs index 3b26515d8f..483617f2cf 100644 --- a/language_service/src/state/tests.rs +++ b/language_service/src/state/tests.rs @@ -305,6 +305,58 @@ async fn compile_error() { ); } +#[tokio::test] +async fn rca_errors_are_reported_when_compilation_succeeds() { + let errors = RefCell::new(Vec::new()); + let mut updater = new_updater(&errors); + + updater.update_configuration(WorkspaceConfigurationUpdate { + target_profile: Some(Profile::Quantinuum), + package_type: Some(PackageType::Lib), + }); + + updater + .update_document("single/foo.qs", 1, "namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }") + .await; + + // we expect two errors, one for `set x = 2` and one for `x` + expect_errors( + &errors, + &expect![[r#" + [ + ( + "single/foo.qs", + Some( + 1, + ), + [ + Pass( + CapabilitiesCk( + UseOfDynamicDouble( + Span { + lo: 106, + hi: 117, + }, + ), + ), + ), + Pass( + CapabilitiesCk( + UseOfDynamicDouble( + Span { + lo: 121, + hi: 122, + }, + ), + ), + ), + ], + ), + ] + "#]], + ); +} + #[tokio::test] async fn package_type_update_causes_error() { let errors = RefCell::new(Vec::new()); diff --git a/library/README.md b/library/README.md index de906317db..23dce7d6ed 100644 --- a/library/README.md +++ b/library/README.md @@ -19,3 +19,30 @@ One notable exception to this is the `Microsoft.Quantum.Core.Length` function, w The library includes a set of one- and two-qubit gates represented as `__quantum__qis__*__body` or `__quantum__qis__*__adj` QIR declarations. The expectation is that these gates form a common QIR API surface that Q# is compiled into. Any target would either need to support these quantum gate instructions natively or provide a QIR definition of that gate instruction in terms of supported native gates. These definitions can then be linked with the QIR program via LLVM tools and resolve to the target specific gate set. This approach provides broader compatibility at the QIR level and avoids the need for front end language targetting; as a result, this Q# library design is not expected to require any target packages or operation substitution. To avoid using any opaque array types in the quantum gate instruction set, the library utilizes decomposition strategies to express the Q# controlled specialization (which allows an arbitrary number of controls) in terms of singly- and doubly-controlled gates. While this makes simulation somewhat more expensive in terms of allocating extra controls and performing more operations, it more closely matches the patterns used by hardware and provides better API surface for QIR programs to have hardware compatibility. + +### Doc comments + +Use the following guidance when providing doc comments for functions and operations in the standard library. These doc comments are used to generate standard library documentation automatically. + +| Section | LaTeX allowed? | Mandatory? | What is it for +|---------------:|:--------------:|:----------:|---------------- +| Summary | No | Yes | Short summary of the functionality provided. +| Description | Yes | No | Detailed explanation of the functionality provided. +| Remarks | Yes | No | General knowledge that may be useful for understanding of the functionality provided. +| Type Parameters| No | No | Description of type parameters (for each type parameter). +| Input | No | No | Description of input parameters (for each input parameter) +| Output | No | No | Description of the output. +| Example | No | No | Example(s) of use. Should be a code fragment that compiles. +| References | No | No | References to papers and other information published on the web or elsewhere. +| See Also | No | No | References to other functions of the standard library that are similar or relevant. + +* Only `Summary` section is mandatory. There's no need to include other + sections just to repeat the information that is already stated in the summary. +* For sections where LaTeX is not allowed, Unicode characters may be used + to represent mathematical formulas even though it is not recommended in + general. These sections are used in places where LaTeX rendering is + not supported. +* For sections where LaTeX is supported, unicode characters may be used to + represent mathematical formulas only if the rendering fidelity is + not compromised. +* Use playground to check documentation rendering. diff --git a/library/core/core.qs b/library/core/core.qs index b8abde5509..c3dec923e8 100644 --- a/library/core/core.qs +++ b/library/core/core.qs @@ -3,20 +3,32 @@ namespace Microsoft.Quantum.Core { /// # Summary - /// Returns the number of elements in an array. + /// Returns the number of elements in the input array `a`. /// /// # Input /// ## a /// Input array. /// /// # Output - /// The total count of elements in an array. + /// The total number of elements in the input array `a`. + /// + /// # Example + /// ```qsharp + /// Message($"{ Length([0, 0, 0]) }"); // Prints 3 + /// ``` function Length<'T>(a : 'T[]) : Int { body intrinsic; } /// # Summary - /// Creates an array of given length with all elements equal to given value. + /// Creates an array of given `length` with all elements equal to given + /// `value`. `length` must be a non-negative integer. + /// + /// # Description + /// Use this function to create an array of length `length` where each + /// element is equal to `value`. This way of creating an array is preferred + /// over other methods if all elements of the array must be the same and + /// the length is known upfront. /// /// # Input /// ## value @@ -28,8 +40,8 @@ namespace Microsoft.Quantum.Core { /// A new array of length `length`, such that every element is `value`. /// /// # Example - /// The following code creates an array of 3 Boolean values, each equal to `true`: /// ```qsharp + /// // Create an array of 3 Boolean values, each equal to `true` /// let array = Repeated(true, 3); /// ``` function Repeated<'T>(value : 'T, length : Int) : 'T[] { diff --git a/library/std/arrays.qs b/library/std/arrays.qs index beffbc38cc..f5fe24496f 100644 --- a/library/std/arrays.qs +++ b/library/std/arrays.qs @@ -203,7 +203,7 @@ namespace Microsoft.Quantum.Arrays { /// /// # Example /// ```qsharp - /// let evensCount = Count(x -> x % 2 == 0, [1, 3, 6, 7, 9]); + /// let evensCount = Count(x -> x % 2 == 0, [1, 3, 6, 7, 9]); /// // evensCount is 1. /// ``` function Count<'T>(predicate : ('T -> Bool), array : 'T[]) : Int { diff --git a/library/std/canon.qs b/library/std/canon.qs index 8b1d62a345..7116b8c10a 100644 --- a/library/std/canon.qs +++ b/library/std/canon.qs @@ -173,10 +173,12 @@ namespace Microsoft.Quantum.Canon { /// This operation can be simulated by the unitary matrix /// $$ /// \begin{align} - /// 1 & 0 & 0 & 0 \\\\ - /// 0 & 1 & 0 & 0 \\\\ - /// 0 & 0 & 0 & -i \\\\ - /// 0 & 0 & i & 0 + /// \left(\begin{matrix} + /// 1 & 0 & 0 & 0 \\\\ + /// 0 & 1 & 0 & 0 \\\\ + /// 0 & 0 & 0 & -i \\\\ + /// 0 & 0 & i & 0 + /// \end{matrix}\right) /// \end{align}, /// $$ /// where rows and columns are organized as in the quantum concepts guide. @@ -208,10 +210,12 @@ namespace Microsoft.Quantum.Canon { /// This operation can be simulated by the unitary matrix /// $$ /// \begin{align} - /// 1 & 0 & 0 & 0 \\\\ - /// 0 & 1 & 0 & 0 \\\\ - /// 0 & 0 & 1 & 0 \\\\ - /// 0 & 0 & 0 & -1 + /// \left(\begin{matrix} + /// 1 & 0 & 0 & 0 \\\\ + /// 0 & 1 & 0 & 0 \\\\ + /// 0 & 0 & 1 & 0 \\\\ + /// 0 & 0 & 0 & -1 + /// \end{matrix}\right) /// \end{align}, /// $$ /// where rows and columns are organized as in the quantum concepts guide. @@ -432,8 +436,17 @@ namespace Microsoft.Quantum.Canon { } /// # Summary - /// Applies a unitary operation on the target, - /// controlled on a state specified by a given bit mask. + /// Applies `oracle` on `target` when `controlRegister` + /// is in the state specified by `bits`. + /// + /// # Description + /// Applies a unitary operation `oracle` on the `target`, controlled + /// on a state specified by a given bit mask `bits`. + /// The bit at `bits[i]` corresponds to qubit at `controlRegister[i]`. + /// The pattern given by `bits` may be shorter than `controlRegister`, + /// in which case additional control qubits are ignored (that is, neither + /// controlled on |0⟩ nor |1⟩). + /// If `bits` is longer than `controlRegister`, an error is raised. /// /// # Input /// ## bits @@ -445,14 +458,16 @@ namespace Microsoft.Quantum.Canon { /// ## controlRegister /// A quantum register that controls application of `oracle`. /// - /// # Remarks - /// The pattern given by `bits` may be shorter than `controlRegister`, - /// in which case additional control qubits are ignored (that is, neither - /// controlled on $\ket{0}$ nor $\ket{1}$). - /// If `bits` is longer than `controlRegister`, an error is raised. - /// - /// For example, `bits = [0,1,0,0,1]` means that `oracle` is applied if and only if `controlRegister` - /// is in the state $\ket{0}\ket{1}\ket{0}\ket{0}\ket{1}$. + /// # Example + /// ```qsharp + /// // When bits = [1,0,0] oracle is applied if and only if controlRegister + /// // is in the state |100⟩. + /// use t = Qubit(); + /// use c = Qubit[3]; + /// X(c[0]); + /// ApplyControlledOnBitString([true, false, false], X, c, t); + /// Message($"{M(t)}"); // Prints `One` since oracle `X` was applied. + /// ``` operation ApplyControlledOnBitString<'T>( bits : Bool[], oracle : ('T => Unit is Adj + Ctl), diff --git a/library/std/convert.qs b/library/std/convert.qs index f0dd3710cd..db5c86278b 100644 --- a/library/std/convert.qs +++ b/library/std/convert.qs @@ -6,13 +6,27 @@ namespace Microsoft.Quantum.Convert { open Microsoft.Quantum.Math; /// # Summary - /// Converts a given integer to an equivalent double-precision floating-point number. + /// Converts a given integer `number` to an equivalent + /// double-precision floating-point number. + /// + /// # Description + /// Converts a given integer to a double-precision floating point number. + /// Please note that the double-precision representation may have fewer + /// bits allocated to represent [significant digits](https://en.wikipedia.org/wiki/Significand) + /// so the conversion may be approximate for large numbers. For example, + /// the current simulator converts 4,611,686,018,427,387,919 = 2^64+15 + /// to 4,611,686,018,427,387,904.0 = 2^64. + /// + /// # Example + /// ```qsharp + /// Message($"{IntAsDouble(1)}"); // Prints 1.0 rather than 1 + /// ``` function IntAsDouble(number : Int) : Double { body intrinsic; } /// # Summary - /// Converts a given integer to an equivalent big integer. + /// Converts a given integer `number` to an equivalent big integer. function IntAsBigInt(number : Int) : BigInt { body intrinsic; } @@ -28,7 +42,6 @@ namespace Microsoft.Quantum.Convert { /// # Output /// A `Bool` representing the `input`. @Config(Adaptive) - @Config(Unrestricted) function ResultAsBool(input : Result) : Bool { input == One } @@ -44,13 +57,13 @@ namespace Microsoft.Quantum.Convert { /// # Output /// A `Result` representing the `input`. @Config(Adaptive) - @Config(Unrestricted) function BoolAsResult(input : Bool) : Result { if input { One } else { Zero } } /// # Summary - /// Produces a non-negative integer from a string of bits in little endian format. + /// Produces a non-negative integer from a string of bits in little-endian format. + /// `bits[0]` represents the least significant bit. /// /// # Input /// ## bits @@ -171,7 +184,6 @@ namespace Microsoft.Quantum.Convert { /// let int1 = ResultArrayAsInt([One,Zero]) /// ``` @Config(Adaptive) - @Config(Unrestricted) function ResultArrayAsInt(results : Result[]) : Int { let nBits = Length(results); Fact(nBits < 64, $"`Length(bits)` must be less than 64, but was {nBits}."); @@ -197,7 +209,6 @@ namespace Microsoft.Quantum.Convert { /// # Output /// A `Bool[]` representing the `input`. @Config(Adaptive) - @Config(Unrestricted) function ResultArrayAsBoolArray(input : Result[]) : Bool[] { mutable output = []; for r in input { @@ -218,7 +229,6 @@ namespace Microsoft.Quantum.Convert { /// # Output /// A `Result[]` representing the `input`. @Config(Adaptive) - @Config(Unrestricted) function BoolArrayAsResultArray(input : Bool[]) : Result[] { mutable output = []; for b in input { diff --git a/library/std/core.qs b/library/std/core.qs index 8442c91202..93c477db35 100644 --- a/library/std/core.qs +++ b/library/std/core.qs @@ -19,11 +19,15 @@ namespace Microsoft.Quantum.Core { /// /// Note that the defined start value of a range is the same as the first element of the sequence, /// unless the range specifies an empty sequence (for example, 2 .. 1). + /// + /// # Example + /// ```qsharp + /// Message($"{ RangeStart(7..-1..3) }"); // Prints 7 + /// ``` function RangeStart(r : Range) : Int { r::Start } - /// # Summary /// Returns the defined end value of the given range, /// which is not necessarily the last element in the sequence. diff --git a/library/std/diagnostics.qs b/library/std/diagnostics.qs index 147adc3330..669e1853b0 100644 --- a/library/std/diagnostics.qs +++ b/library/std/diagnostics.qs @@ -69,13 +69,11 @@ namespace Microsoft.Quantum.Diagnostics { body intrinsic; } - @Config(Adaptive) @Config(Unrestricted) operation CheckZero(qubit : Qubit) : Bool { body intrinsic; } - @Config(Adaptive) @Config(Unrestricted) operation CheckAllZero(qubits : Qubit[]) : Bool { for q in qubits { @@ -122,7 +120,6 @@ namespace Microsoft.Quantum.Diagnostics { /// Operation defining the expected behavior for the operation under test. /// # Output /// True if operations are equal, false otherwise. - @Config(Adaptive) @Config(Unrestricted) operation CheckOperationsAreEqual( nQubits : Int, diff --git a/library/std/internal.qs b/library/std/internal.qs index 35b7913101..55e3d06672 100644 --- a/library/std/internal.qs +++ b/library/std/internal.qs @@ -132,7 +132,6 @@ namespace Microsoft.Quantum.Intrinsic { } @Config(Adaptive) - @Config(Unrestricted) internal operation AND(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj { body ... { __quantum__qis__ccx__body(control1, control2, target); diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index e89a9a2646..82d0adc500 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -219,7 +219,6 @@ namespace Microsoft.Quantum.Intrinsic { /// Measure([PauliZ], [qubit]); /// ``` @Config(Adaptive) - @Config(Unrestricted) operation M(qubit : Qubit) : Result { __quantum__qis__m__body(qubit) } @@ -292,7 +291,6 @@ namespace Microsoft.Quantum.Intrinsic { /// If the basis array and qubit array are different lengths, then the /// operation will fail. @Config(Adaptive) - @Config(Unrestricted) operation Measure(bases : Pauli[], qubits : Qubit[]) : Result { if Length(bases) != Length(qubits) { fail "Arrays 'bases' and 'qubits' must be of the same length."; diff --git a/library/std/logical.qs b/library/std/logical.qs index 5008a37add..8ff39f9315 100644 --- a/library/std/logical.qs +++ b/library/std/logical.qs @@ -4,7 +4,8 @@ namespace Microsoft.Quantum.Logical { /// # Summary - /// Returns the boolean exclusive disjunction (XOR) of two input boolean values. + /// Returns the boolean exclusive disjunction (eXclusive OR, XOR) + /// of two input boolean values. /// /// # Input /// ## first @@ -16,9 +17,11 @@ namespace Microsoft.Quantum.Logical { /// # Output /// A `Bool` which is `true` if and only if exactly one of `first` and `second` is `true`. /// + /// # Remarks + /// In Q#, `Xor(a, b)` is equivalent to `a != b`. + /// /// # Example /// ```qsharp - /// /// let result = Xor(true, false); /// // result is true /// ``` diff --git a/library/std/math.qs b/library/std/math.qs index fd59370132..0dc774d4b7 100644 --- a/library/std/math.qs +++ b/library/std/math.qs @@ -10,11 +10,16 @@ namespace Microsoft.Quantum.Math { // /// # Summary - /// Represents the ratio of the circumference of a circle to its diameter. + /// Returns a double-precision approximation of the + /// matematical constant 𝝅 ≈ 3.14159265358979323846 /// - /// # Output - /// A double-precision approximation of the circumference of a circle - /// to its diameter, π ≈ 3.14159265358979323846. + /// # Remarks + /// Mathematical constant 𝝅 represents the ratio of the circumference + /// of a circle to its diameter. It is useful in many applications + /// such as rotations and complex arithmetic. + /// + /// # References + /// [Wikipedia article - Pi](https://en.wikipedia.org/wiki/Pi) /// /// # See Also /// - Microsoft.Quantum.Math.E @@ -23,11 +28,15 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the natural logarithmic base to double-precision. + /// Returns a double-precision approximation of the + /// mathematical constant 𝒆 ≈ 2.7182818284590452354 /// - /// # Output - /// A double-precision approximation of the natural logarithmic base, - /// e ≈ 2.7182818284590452354. + /// # Remarks + /// Mathematical constant 𝒆 is the base of the natural logarithm + /// also known as the Euler's number + /// + /// # References + /// [Wikipedia article - e](https://en.wikipedia.org/wiki/E_(mathematical_constant)) /// /// # See Also /// - Microsoft.Quantum.Math.PI @@ -36,10 +45,14 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the natural logarithm of 2. + /// Returns a double-precision approximation of the constant + /// ㏑2 ≈ 0.6931471805599453 /// - /// # Output - /// Returns a `Double` equal to 0.6931471805599453. + /// # Remarks + /// ㏑2 is the natural logarithm of 2, or the logarithm of 2 base 𝒆. + /// + /// # References + /// [Wikipedia article - Natural logarithm](https://en.wikipedia.org/wiki/Natural_logarithm) function LogOf2() : Double { 0.6931471805599453 } @@ -383,7 +396,7 @@ namespace Microsoft.Quantum.Math { /// # Summary /// Returns the nearest integer to the specified number. - /// For example: Floor(3.7) = 4; Floor(-3.7) = -4 + /// For example: Round(3.7) = 4; Round(-3.7) = -4 function Round(value : Double) : Int { let (truncated, remainder, isPositive) = ExtendedTruncation(value); if AbsD(remainder) <= 1e-15 { diff --git a/library/std/measurement.qs b/library/std/measurement.qs index d23c6b18b2..8b9f6ec172 100644 --- a/library/std/measurement.qs +++ b/library/std/measurement.qs @@ -13,23 +13,31 @@ namespace Microsoft.Quantum.Measurement { /// # Description /// Measures a register of qubits in the `Z ⊗ Z ⊗ ••• ⊗ Z` /// basis, representing the parity of the entire register. + /// This operation does not reset the measured qubits to the |0⟩ state, + /// leaving them in the state that corresponds to the measurement result. /// /// # Input /// ## register - /// The register to be measured. + /// The register to be jointly measured. /// /// # Output - /// The result of measuring `Z ⊗ Z ⊗ ••• ⊗ Z`. + /// The result of measuring in the `Z ⊗ Z ⊗ ••• ⊗ Z` basis. /// - /// # Remarks - /// This operation does not reset the measured qubits to the |0⟩ state, - /// leaving them in the state that corresponds to the measurement result. + /// # See also + /// - Microsoft.Quantum.Measurement.MeasureEachZ operation MeasureAllZ(register : Qubit[]) : Result { Measure(Repeated(PauliZ, Length(register)), register) } /// # Summary /// Measures each qubit in a given array in the standard basis. + /// + /// # Description + /// Measures each qubit in a register in the `Z` basis + /// and retuns the result of each measurement. + /// This operation does not reset the measured qubits to the |0⟩ state, + /// leaving them in the state that corresponds to the measurement results. + /// /// # Input /// ## targets /// An array of qubits to be measured. @@ -37,8 +45,17 @@ namespace Microsoft.Quantum.Measurement { /// An array of measurement results. /// /// # Remarks - /// This operation does not reset the measured qubits to the |0⟩ state, - /// leaving them in the state that corresponds to the measurement results. + /// Please note the following differences: + /// - Operation `MeasureEachZ` performs one measurement for each qubit and retuns + /// an array of results. The operation does not reset the qubits. + /// - Operation `MResetEachZ` performs one measurement for each qubit and retuns + /// an array of results. The operation resets all qubits to |0⟩ state. + /// - Operation `MeasureAllZ` performs a joint measurement on all qubits + /// and returns one result. The operation does not reset the qubits. + /// + /// # See also + /// - Microsoft.Quantum.Measurement.MeasureAllZ + /// - Microsoft.Quantum.Measurement.MResetEachZ operation MeasureEachZ(register : Qubit[]) : Result[] { mutable results = []; for qubit in register { @@ -50,11 +67,16 @@ namespace Microsoft.Quantum.Measurement { /// # Summary /// Measures each qubit in a given array in the Z basis /// and resets them to a fixed initial state. + /// /// # Input /// ## targets /// An array of qubits to be measured. + /// /// # Output /// An array of measurement results. + /// + /// # See also + /// - Microsoft.Quantum.Measurement.MeasureEachZ operation MResetEachZ(register : Qubit[]) : Result[] { mutable results = []; for qubit in register { @@ -147,7 +169,6 @@ namespace Microsoft.Quantum.Measurement { /// This operation resets its input register to the |00...0> state, /// suitable for releasing back to a target machine. @Config(Adaptive) - @Config(Unrestricted) operation MeasureInteger(target : Qubit[]) : Int { let nBits = Length(target); Fact(nBits < 64, $"`Length(target)` must be less than 64, but was {nBits}."); diff --git a/library/std/random.qs b/library/std/random.qs index 00509e97d9..c568ccbcb2 100644 --- a/library/std/random.qs +++ b/library/std/random.qs @@ -4,7 +4,8 @@ namespace Microsoft.Quantum.Random { /// # Summary - /// Draws a random integer in a given inclusive range. + /// Draws a random integer from a uniform distribution + /// in a given inclusive range. Fails if `max < min`. /// /// # Input /// ## min @@ -16,22 +17,19 @@ namespace Microsoft.Quantum.Random { /// An integer in the inclusive range from `min` to `max` with uniform /// probability. /// - /// # Remarks - /// Fails if `max < min`. - /// /// # Example /// The following Q# snippet randomly rolls a six-sided die: /// ```qsharp /// let roll = DrawRandomInt(1, 6); /// ``` - @Config(Adaptive) @Config(Unrestricted) operation DrawRandomInt(min : Int, max : Int) : Int { body intrinsic; } /// # Summary - /// Draws a random real number in a given inclusive interval. + /// Draws a random real number from a uniform distribution + /// in a given inclusive interval. Fails if `max < min`. /// /// # Input /// ## min @@ -43,15 +41,11 @@ namespace Microsoft.Quantum.Random { /// A random real number in the inclusive interval from `min` to `max` with /// uniform probability. /// - /// # Remarks - /// Fails if `max < min`. - /// /// # Example /// The following Q# snippet randomly draws an angle between 0 and 2π: /// ```qsharp /// let angle = DrawRandomDouble(0.0, 2.0 * PI()); /// ``` - @Config(Adaptive) @Config(Unrestricted) operation DrawRandomDouble(min : Double, max : Double) : Double { body intrinsic; diff --git a/library/std/re.qs b/library/std/re.qs index 3f0cdc2ce2..433b5aea62 100644 --- a/library/std/re.qs +++ b/library/std/re.qs @@ -33,8 +33,6 @@ namespace Microsoft.Quantum.ResourceEstimation { /// needs to be executed in order to collect and cache estimates. /// `false` indicates if cached estimates have been incorporated into the overall costs /// and the code fragment should be skipped. - @Config(Adaptive) - @Config(Unrestricted) function BeginEstimateCaching(name : String, variant : Int) : Bool { body intrinsic; } @@ -129,7 +127,6 @@ namespace Microsoft.Quantum.ResourceEstimation { } /// # Summary - /// /// Instructs the resource estimator to assume that the resources from the /// call of this operation until a call to `EndRepeatEstimates` are /// accounted for `count` times, without the need to execute the code that many @@ -152,7 +149,6 @@ namespace Microsoft.Quantum.ResourceEstimation { } /// # Summary - /// /// Companion operation to `BeginRepeatEstimates`. operation EndRepeatEstimates() : Unit { body ... { @@ -166,7 +162,6 @@ namespace Microsoft.Quantum.ResourceEstimation { } /// # Summary - /// /// Instructs the resource estimator to assume that the resources from the /// call of this operation until a call to `Adjoint RepeatEstimates` are /// accounted for `count` times, without the need to execute the code that many diff --git a/library/std/unstable_arithmetic.qs b/library/std/unstable_arithmetic.qs index 4841801318..4269e934ac 100644 --- a/library/std/unstable_arithmetic.qs +++ b/library/std/unstable_arithmetic.qs @@ -36,16 +36,14 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// where each |i⟩ is a basis state representing an integer i, /// reflects the state of the register about the basis state |j⟩ /// for a given integer j: ∑ᵢ(-1)^(δᵢⱼ)(αᵢ|i⟩) + /// This operation is implemented in-place, without explicit allocation of + /// additional auxiliary qubits. /// /// # Input /// ## index /// The classical integer j indexing the basis state about which to reflect. /// ## reg /// Little-endian quantum register to reflect. - /// - /// # Remarks - /// This operation is implemented in-place, without explicit allocation of - /// additional auxiliary qubits. operation ReflectAboutInteger(index : Int, reg : Qubit[]) : Unit is Adj + Ctl { within { // Evaluation optimization for case index == 0 @@ -147,10 +145,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// qubits otherwise. /// /// # References - /// - [arXiv:0910.2530](https://arxiv.org/abs/0910.2530) - /// "Quantum Addition Circuits and Unbounded Fan-Out" - /// by Yasuhiro Takahashi, Seiichiro Tani, Noboru Kunihiro - /// + /// - [arXiv:0910.2530](https://arxiv.org/abs/0910.2530) + /// "Quantum Addition Circuits and Unbounded Fan-Out", + /// Yasuhiro Takahashi, Seiichiro Tani, Noboru Kunihiro operation RippleCarryTTKIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl { let xsLen = Length(xs); let ysLen = Length(ys); @@ -198,8 +195,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// This operation uses the ripple-carry algorithm. /// /// # Reference - /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf) - /// "Halving the cost of quantum addition" by Craig Gidney. + /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf) + /// "Halving the cost of quantum addition", Craig Gidney. operation RippleCarryCGIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl { let xsLen = Length(xs); let ysLen = Length(ys); @@ -255,8 +252,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// NOTE: `zs[Length(xs)]` can be used as carry-out, if `zs` is longer than `xs`. /// /// # Reference - /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf) - /// "Halving the cost of quantum addition" by Craig Gidney. + /// - [arXiv:1709.06648](https://arxiv.org/pdf/1709.06648.pdf) + /// "Halving the cost of quantum addition", Craig Gidney. operation RippleCarryCGAddLE(xs : Qubit[], ys : Qubit[], zs : Qubit[]) : Unit is Adj { let xsLen = Length(xs); let zsLen = Length(zs); @@ -289,9 +286,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// This operation uses the carry-lookahead algorithm. /// /// # Reference - /// - [arXiv:quant-ph/0406142](https://arxiv.org/abs/quant-ph/0406142) - /// "A logarithmic-depth quantum carry-lookahead adder" by - /// Thomas G. Draper, Samuel A. Kutin, Eric M. Rains, Krysta M. Svore + /// - [arXiv:quant-ph/0406142](https://arxiv.org/abs/quant-ph/0406142) + /// "A logarithmic-depth quantum carry-lookahead adder", + /// Thomas G. Draper, Samuel A. Kutin, Eric M. Rains, Krysta M. Svore operation LookAheadDKRSAddLE(xs : Qubit[], ys : Qubit[], zs : Qubit[]) : Unit is Adj { let xsLen = Length(xs); let zsLen = Length(zs); @@ -338,8 +335,8 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// This operation uses Quantum Fourier Transform. /// /// # Reference - /// - [arXiv:quant-ph/0008033](https://arxiv.org/abs/quant-ph/0008033) - /// "Addition on a Quantum Computer" by Thomas G. Draper + /// - [arXiv:quant-ph/0008033](https://arxiv.org/abs/quant-ph/0008033) + /// "Addition on a Quantum Computer", Thomas G. Draper operation FourierTDIncByLE(xs : Qubit[], ys : Qubit[]) : Unit is Adj + Ctl { within { ApplyQFT(ys); @@ -430,9 +427,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// the adders is controlled, /// /// # Reference - /// - [arXiv:2012.01624](https://arxiv.org/abs/2012.01624) - /// "Quantum block lookahead adders and the wait for magic states" - /// by Craig Gidney. + /// - [arXiv:2012.01624](https://arxiv.org/abs/2012.01624) + /// "Quantum block lookahead adders and the wait for magic states", + /// Craig Gidney. operation IncByLEUsingAddLE( forwardAdder : (Qubit[], Qubit[], Qubit[]) => Unit is Adj, backwardAdder : (Qubit[], Qubit[], Qubit[]) => Unit is Adj, diff --git a/library/std/unstable_arithmetic_internal.qs b/library/std/unstable_arithmetic_internal.qs index 31eda30325..c30155cdc9 100644 --- a/library/std/unstable_arithmetic_internal.qs +++ b/library/std/unstable_arithmetic_internal.qs @@ -244,13 +244,9 @@ namespace Microsoft.Quantum.Unstable.Arithmetic { /// [arXiv:1212.5069](https://arxiv.org/abs/1212.5069) /// doi:10.1103/PhysRevA.87.022328 @Config(Adaptive) - @Config(Unrestricted) internal operation ApplyAndAssuming0Target(control1 : Qubit, control2 : Qubit, target : Qubit) : Unit is Adj { // NOTE: Eventually this operation will be public and intrinsic. body (...) { - if not CheckZero(target) { - fail "ApplyAndAssuming0Target expects `target` to be in |0> state."; - } CCNOT(control1, control2, target); } adjoint (...) { diff --git a/library/std/unstable_state_preparation.qs b/library/std/unstable_state_preparation.qs index cefe72bd78..57376878a0 100644 --- a/library/std/unstable_state_preparation.qs +++ b/library/std/unstable_state_preparation.qs @@ -46,9 +46,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation { /// ``` /// /// # References - /// - Synthesis of Quantum Logic Circuits + /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176) + /// "Synthesis of Quantum Logic Circuits", /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov - /// https://arxiv.org/abs/quant-ph/0406176 /// /// # See Also /// - Microsoft.Quantum.Unstable.StatePreparation.ApproximatelyPreparePureStateCP @@ -101,9 +101,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation { /// specified. /// /// # References - /// - Synthesis of Quantum Logic Circuits + /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176) + /// "Synthesis of Quantum Logic Circuits", /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov - /// https://arxiv.org/abs/quant-ph/0406176 operation ApproximatelyPreparePureStateCP( tolerance : Double, coefficients : ComplexPolar[], @@ -314,9 +314,9 @@ namespace Microsoft.Quantum.Unstable.StatePreparation { /// fewer than $2^n$ are specified. /// /// # References - /// - Synthesis of Quantum Logic Circuits + /// - [arXiv:quant-ph/0406176](https://arxiv.org/abs/quant-ph/0406176) + /// "Synthesis of Quantum Logic Circuits", /// Vivek V. Shende, Stephen S. Bullock, Igor L. Markov - /// https://arxiv.org/abs/quant-ph/0406176 internal operation ApproximatelyMultiplexZ( tolerance : Double, coefficients : Double[], @@ -385,4 +385,3 @@ namespace Microsoft.Quantum.Unstable.StatePreparation { } } - diff --git a/library/std/unstable_table_lookup.qs b/library/std/unstable_table_lookup.qs index a3ee06225d..b748aa07ff 100644 --- a/library/std/unstable_table_lookup.qs +++ b/library/std/unstable_table_lookup.qs @@ -36,15 +36,14 @@ namespace Microsoft.Quantum.Unstable.TableLookup { /// is not optimized using this technique. /// /// # References - /// [1] [arXiv:1805.03662](https://arxiv.org/abs/1805.03662) - /// "Encoding Electronic Spectra in Quantum Circuits with Linear T - /// Complexity" - /// [2] [arXiv:1905.07682](https://arxiv.org/abs/1905.07682) - /// "Windowed arithmetic" - /// [3] [arXiv:2211.01133](https://arxiv.org/abs/2211.01133) - /// "Space-time optimized table lookup" + /// 1. [arXiv:1805.03662](https://arxiv.org/abs/1805.03662) + /// "Encoding Electronic Spectra in Quantum Circuits with Linear T + /// Complexity" + /// 2. [arXiv:1905.07682](https://arxiv.org/abs/1905.07682) + /// "Windowed arithmetic" + /// 3. [arXiv:2211.01133](https://arxiv.org/abs/2211.01133) + /// "Space-time optimized table lookup" @Config(Adaptive) - @Config(Unrestricted) operation Select( data : Bool[][], address : Qubit[], @@ -98,8 +97,6 @@ namespace Microsoft.Quantum.Unstable.TableLookup { } } - @Config(Adaptive) - @Config(Unrestricted) internal operation SinglyControlledSelect( ctl : Qubit, data : Bool[][], @@ -169,7 +166,6 @@ namespace Microsoft.Quantum.Unstable.TableLookup { /// - [arXiv:1905.07682](https://arxiv.org/abs/1905.07682) /// "Windowed arithmetic" @Config(Adaptive) - @Config(Unrestricted) internal operation Unlookup( lookup : (Bool[][], Qubit[], Qubit[]) => Unit, data : Bool[][], diff --git a/npm/qsharp/src/compiler/compiler.ts b/npm/qsharp/src/compiler/compiler.ts index 76dd5d12e1..2fea46a352 100644 --- a/npm/qsharp/src/compiler/compiler.ts +++ b/npm/qsharp/src/compiler/compiler.ts @@ -81,6 +81,7 @@ export interface ICompiler { getCircuit( config: ProgramConfig, target: TargetProfile, + simulate: boolean, operation?: IOperationInfo, ): Promise; @@ -236,13 +237,15 @@ export class Compiler implements ICompiler { async getCircuit( config: ProgramConfig, target: TargetProfile, + simulate: boolean, operation?: IOperationInfo, ): Promise { return this.wasm.get_circuit( config.sources, target, - operation, config.languageFeatures || [], + simulate, + operation, ); } diff --git a/npm/qsharp/ux/circuit.tsx b/npm/qsharp/ux/circuit.tsx index 1a63141762..1457dec8ff 100644 --- a/npm/qsharp/ux/circuit.tsx +++ b/npm/qsharp/ux/circuit.tsx @@ -2,8 +2,9 @@ // Licensed under the MIT License. import * as qviz from "@microsoft/quantum-viz.js/lib"; -import { useEffect, useRef } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks"; import { CircuitProps } from "./data.js"; +import { Spinner } from "./spinner.js"; // For perf reasons we set a limit on how many gates/qubits // we attempt to render. This is still a lot higher than a human would @@ -12,56 +13,165 @@ import { CircuitProps } from "./data.js"; const MAX_OPERATIONS = 10000; const MAX_QUBITS = 1000; -/* This component is shared by the Python widget and the VS Code panel */ +// This component is shared by the Python widget and the VS Code panel export function Circuit(props: { circuit: qviz.Circuit }) { + const circuit = props.circuit; + const unrenderable = + circuit.qubits.length === 0 || + circuit.operations.length > MAX_OPERATIONS || + circuit.qubits.length > MAX_QUBITS; + + return ( +
+ {unrenderable ? ( + + ) : ( + + )} +
+ ); +} + +function ZoomableCircuit(props: { circuit: qviz.Circuit }) { const circuitDiv = useRef(null); + const [zoomLevel, setZoomLevel] = useState(100); + const [rendering, setRendering] = useState(true); + + useEffect(() => { + // Enable "rendering" text while the circuit is being drawn + setRendering(true); + const container = circuitDiv.current!; + container.innerHTML = ""; + }, [props.circuit]); + + useEffect(() => { + if (rendering) { + const container = circuitDiv.current!; + // Draw the circuit - may take a while for large circuits + const svg = renderCircuit(props.circuit, container); + // Calculate the initial zoom level based on the container width + const initialZoom = calculateZoomToFit(container, svg as SVGElement); + // Set the initial zoom level + setZoomLevel(initialZoom); + // Resize the SVG to fit + updateWidth(); + // Disable "rendering" text + setRendering(false); + } + }, [rendering]); + + useEffect(() => { + updateWidth(); + }, [zoomLevel]); + + return ( +
+
+ {rendering ? null : ( + + )} +
+
+ {rendering + ? `Rendering diagram with ${props.circuit.operations.length} gates...` + : ""} +
+
+
+ ); + + function updateWidth() { + const svg = circuitDiv.current?.querySelector(".qviz"); + if (svg) { + // The width attribute contains the true width, generated by qviz. + // We'll leave this attribute untouched, so we can use it again if the + // zoom level is ever updated. + const width = svg.getAttribute("width")!; + + // We'll set the width in the style attribute to (true width * zoom level). + // This value takes precedence over the true width in the width attribute. + svg.setAttribute( + "style", + `max-width: ${width}; width: ${(parseInt(width) * (zoomLevel || 100)) / 100}; height: auto`, + ); + } + } + + function renderCircuit(circuit: qviz.Circuit, container: HTMLDivElement) { + qviz.draw(circuit, container); + // quantum-viz hardcodes the styles in the SVG. + // Remove the style elements -- we'll define the styles in our own CSS. + const styleElements = container.querySelectorAll("style"); + styleElements?.forEach((tag) => tag.remove()); + + return container.getElementsByClassName("qviz")[0]!; + } + + function calculateZoomToFit(container: HTMLDivElement, svg: SVGElement) { + const containerWidth = container.clientWidth; + // width and height are the true dimensions generated by qviz + const width = parseInt(svg.getAttribute("width")!); + const height = svg.getAttribute("height")!; + + svg.setAttribute("viewBox", `0 0 ${width} ${height}`); + const zoom = Math.min(Math.ceil((containerWidth / width) * 100), 100); + return zoom; + } +} + +function Unrenderable(props: { qubits: number; operations: number }) { const errorDiv = - props.circuit.qubits.length === 0 ? ( + props.qubits === 0 ? (

No circuit to display. No qubits have been allocated.

- ) : props.circuit.operations.length > MAX_OPERATIONS ? ( + ) : props.operations > MAX_OPERATIONS ? (

- This circuit has too many gates to display. It has{" "} - {props.circuit.operations.length} gates, but the maximum supported is{" "} - {MAX_OPERATIONS}. + This circuit has too many gates to display. It has {props.operations}{" "} + gates, but the maximum supported is {MAX_OPERATIONS}.

- ) : props.circuit.qubits.length > MAX_QUBITS ? ( + ) : props.qubits > MAX_QUBITS ? (

- This circuit has too many qubits to display. It has{" "} - {props.circuit.qubits.length} qubits, but the maximum supported is{" "} - {MAX_QUBITS}. + This circuit has too many qubits to display. It has {props.qubits}{" "} + qubits, but the maximum supported is {MAX_QUBITS}.

) : undefined; - useEffect(() => { - if (errorDiv !== undefined) { - circuitDiv.current!.innerHTML = ""; - return; - } - - qviz.draw(props.circuit, circuitDiv.current!); - - // quantum-viz hardcodes the styles in the SVG. - // Remove the style elements -- we'll define the styles in our own CSS. - const styleElements = circuitDiv.current?.querySelectorAll("style"); - styleElements?.forEach((tag) => tag.remove()); - }, [props.circuit]); + return
{errorDiv}
; +} +function ZoomControl(props: { + zoom: number; + onChange: (zoom: number) => void; +}) { return ( -
-
{errorDiv}
-
-
+

+ + + props.onChange(parseInt((e.target as HTMLInputElement).value) || 0) + } + /> + % +

); } -/* This component is exclusive to the VS Code panel */ +// This component is exclusive to the VS Code panel export function CircuitPanel(props: CircuitProps) { const error = props.errorHtml ? (
@@ -78,41 +188,29 @@ export function CircuitPanel(props: CircuitProps) { return (
-

{props.title}

+

+ {props.title} {props.simulated ? "(Trace)" : ""} +

- {props.circuit ? : null}
{error}

{props.targetProfile}

- {props.simulating ? ( -

- This circuit diagram was generated while running the program in the - simulator. -
-
- If your program contains behavior that is conditional on a qubit - measurement result, note that this circuit only shows the outcome that - was encountered during this simulation. Running the program again may - result in a different circuit being generated. -

- ) : null} - { - // show tip when the circuit is empty and we didn't run under the simulator (i.e. debugging) - !props.simulating && - !props.errorHtml && - props.circuit?.qubits.length === 0 ? ( -

- - Tip: you can generate a circuit diagram for any operation that - takes qubits or arrays of qubits as input. - -

- ) : null - }

- - Learn more - + { + props.simulated + ? "WARNING: This diagram shows the result of tracing a dynamic circuit, and may change from run to run." + : "\xa0" // nbsp to keep line height consistent + }

+

+ Learn more at{" "} + https://aka.ms/qdk.circuits +

+ {props.calculating ? ( +
+ +
+ ) : null} + {props.circuit ? : null}
); } diff --git a/npm/qsharp/ux/data.ts b/npm/qsharp/ux/data.ts index b048a2eb46..72a3ab4be4 100644 --- a/npm/qsharp/ux/data.ts +++ b/npm/qsharp/ux/data.ts @@ -69,7 +69,10 @@ export type CircuitProps = { circuit?: CircuitData; errorHtml?: string; targetProfile: string; - simulating: boolean; + /** Circuit was generated by running the simulator */ + simulated: boolean; + /** Circuit is still being generated */ + calculating: boolean; }; export type CircuitData = import("@microsoft/quantum-viz.js/lib").Circuit; diff --git a/npm/qsharp/ux/estimatesPanel.tsx b/npm/qsharp/ux/estimatesPanel.tsx index 5ac0b98a5e..4f7093bc3e 100644 --- a/npm/qsharp/ux/estimatesPanel.tsx +++ b/npm/qsharp/ux/estimatesPanel.tsx @@ -6,6 +6,7 @@ import { type ReData, SingleEstimateResult } from "./data.js"; import { EstimatesOverview } from "./estimatesOverview.js"; import { ReTable } from "./reTable.js"; import { SpaceChart } from "./spaceChart.js"; +import { Spinner } from "./spinner.js"; export function EstimatesPanel(props: { estimatesData: ReData[]; @@ -52,16 +53,7 @@ export function EstimatesPanel(props: { {props.calculating ? ( - - - + ) : null}

Azure Quantum Resource Estimator

diff --git a/npm/qsharp/ux/spinner.tsx b/npm/qsharp/ux/spinner.tsx new file mode 100644 index 0000000000..70132318c3 --- /dev/null +++ b/npm/qsharp/ux/spinner.tsx @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export function Spinner(props: { style?: string }) { + return ( + + + + ); +} diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi index 8c5afcdf2a..2a4d848740 100644 --- a/pip/qsharp/_native.pyi +++ b/pip/qsharp/_native.pyi @@ -2,22 +2,14 @@ # Licensed under the MIT License. from enum import Enum -from typing import Any, Callable, ClassVar, Tuple, Optional, Dict, List +from typing import Any, Callable, ClassVar, Optional, Dict, List class TargetProfile: """ A Q# target profile. - The target is the hardware or simulator which will be used to run the Q# program. - The target profile is a description of a target's capabilities. - """ - - Adaptive: ClassVar[Any] - """ - Target supports the core set of adaptive. - - This option maps to the Adaptive Profile as defined by the QIR specification - without any extensions. + A target profile describes the capabilities of the hardware or simulator + which will be used to run the Q# program. """ Base: ClassVar[Any] @@ -28,6 +20,15 @@ class TargetProfile: This option maps to the Base Profile as defined by the QIR specification. """ + Quantinuum: ClassVar[Any] + """ + Target supports Quantinuum profile. + + This profile includes all of the required Adaptive Profile + capabilities, as well as the optional integer computation and qubit + reset capabilities, as defined by the QIR specification. + """ + Unrestricted: ClassVar[Any] """ Describes the unrestricted set of capabilities required to run any Q# program. diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py index ed65bdaf06..e9367d28e8 100644 --- a/pip/qsharp/_qsharp.py +++ b/pip/qsharp/_qsharp.py @@ -29,9 +29,9 @@ def __init__( language_features: Optional[List[str]], manifest: Optional[str], ): - if target_profile == TargetProfile.Adaptive: - self._config = {"targetProfile": "adaptive"} - warn("The adaptive target profile is a preview feature.") + if target_profile == TargetProfile.Quantinuum: + self._config = {"targetProfile": "quantinuum"} + warn("The Quantinuum target profile is a preview feature.") warn("Functionality may be incomplete or incorrect.") elif target_profile == TargetProfile.Base: self._config = {"targetProfile": "base"} diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs index be262c15a4..a8c3ef3fa8 100644 --- a/pip/src/interpreter.rs +++ b/pip/src/interpreter.rs @@ -44,6 +44,7 @@ fn _native(py: Python, m: &PyModule) -> PyResult<()> { Ok(()) } +// This ordering must match the _native.pyi file. #[derive(Clone, Copy)] #[pyclass(unsendable)] /// A Q# target profile. @@ -51,14 +52,16 @@ fn _native(py: Python, m: &PyModule) -> PyResult<()> { /// A target profile describes the capabilities of the hardware or simulator /// which will be used to run the Q# program. pub(crate) enum TargetProfile { - /// Target supports the core set of adaptive. - /// - /// This option maps to the Adaptive Profile as defined by the QIR specification without any extensions. - Adaptive, /// Target supports the minimal set of capabilities required to run a quantum program. /// /// This option maps to the Base Profile as defined by the QIR specification. Base, + /// Target supports Quantinuum profile. + /// + /// This profile includes all of the required Adaptive Profile + /// capabilities, as well as the optional integer computation and qubit + /// reset capabilities, as defined by the QIR specification. + Quantinuum, /// Target supports the full set of capabilities required to run any Q# program. /// /// This option maps to the Full Profile as defined by the QIR specification. @@ -111,7 +114,7 @@ impl Interpreter { list_directory: Option, ) -> PyResult { let target = match target { - TargetProfile::Adaptive => Profile::Adaptive, + TargetProfile::Quantinuum => Profile::Quantinuum, TargetProfile::Base => Profile::Base, TargetProfile::Unrestricted => Profile::Unrestricted, }; @@ -252,7 +255,7 @@ impl Interpreter { } }; - match self.interpreter.circuit(entrypoint) { + match self.interpreter.circuit(entrypoint, false) { Ok(circuit) => Ok(Circuit(circuit).into_py(py)), Err(errors) => Err(QSharpError::new_err(format_errors(errors))), } diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py index 66d322ec2e..bab27dcfbe 100644 --- a/pip/tests/test_interpreter.py +++ b/pip/tests/test_interpreter.py @@ -274,56 +274,54 @@ def test_entry_expr_circuit() -> None: # this is by design def test_callables_failing_profile_validation_are_still_registered() -> None: - e = Interpreter(TargetProfile.Adaptive) + e = Interpreter(TargetProfile.Quantinuum) with pytest.raises(Exception) as excinfo: e.interpret( - "operation Foo() : Int { use q = Qubit(); mutable x = 1; if MResetZ(q) == One { set x = 2; } x }" + "operation Foo() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x }" ) - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) with pytest.raises(Exception) as excinfo: e.interpret("Foo()") - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) # this is by design def test_once_rca_validation_fails_following_calls_also_fail() -> None: - e = Interpreter(TargetProfile.Adaptive) + e = Interpreter(TargetProfile.Quantinuum) with pytest.raises(Exception) as excinfo: e.interpret( - "operation Foo() : Int { use q = Qubit(); mutable x = 1; if MResetZ(q) == One { set x = 2; } x }" + "operation Foo() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x }" ) - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) with pytest.raises(Exception) as excinfo: e.interpret("let x = 5;") - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) def test_adaptive_errors_are_raised_when_interpreting() -> None: - e = Interpreter(TargetProfile.Adaptive) + e = Interpreter(TargetProfile.Quantinuum) with pytest.raises(Exception) as excinfo: e.interpret( - "operation Foo() : Int { use q = Qubit(); mutable x = 1; if MResetZ(q) == One { set x = 2; } x }" + "operation Foo() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x }" ) - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) def test_adaptive_errors_are_raised_from_entry_expr() -> None: - e = Interpreter(TargetProfile.Adaptive) + e = Interpreter(TargetProfile.Quantinuum) e.interpret("use q = Qubit();") with pytest.raises(Exception) as excinfo: - e.run("{mutable x = 1; if MResetZ(q) == One { set x = 2; }}") - assert "Qsc.CapabilitiesCk.UseOfDynamicInt" in str(excinfo) + e.run("{mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; }}") + assert "Qsc.CapabilitiesCk.UseOfDynamicDouble" in str(excinfo) -# This is temporary but asserts that the functionality is -# Not yet implemented. -def test_adaptive_qir_cannot_be_generated() -> None: +def test_adaptive_qir_can_be_generated() -> None: adaptive_input = """ namespace Test { open Microsoft.Quantum.Math; open QIR.Intrinsic; @EntryPoint() - operation Main() : Unit { + operation Main() : Result { use q = Qubit(); let pi_over_two = 4.0 / 2.0; __quantum__qis__rz__body(pi_over_two, q); @@ -331,15 +329,47 @@ def test_adaptive_qir_cannot_be_generated() -> None: __quantum__qis__rz__body(some_angle, q); set some_angle = ArcCos(-1.0) / PI(); __quantum__qis__rz__body(some_angle, q); + __quantum__qis__mresetz__body(q) } } """ - e = Interpreter(TargetProfile.Adaptive) + e = Interpreter(TargetProfile.Quantinuum) e.interpret(adaptive_input) - with pytest.raises(Exception) as excinfo: - e.qir("Test.Main()") + qir = e.qir("Test.Main()") + assert qir == dedent( + """\ + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__rz__body(double 2.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 0.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 1.0, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__rz__body(double, %Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) - assert "UnsupportedRuntimeCapabilities" in str(excinfo) + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + """ + ) def test_operation_circuit() -> None: diff --git a/playground/README.md b/playground/README.md index 459883de75..62d0a869ff 100644 --- a/playground/README.md +++ b/playground/README.md @@ -2,7 +2,18 @@ This is a simple web site built using the Monaco editor and the qsharp npm package. -Build the entire repo by running `./build.py` in the root directory. -If you only want to build the functionality necessary to run the playground, you can use `python .\build.py --wasm --npm --play`. -Then make `./playground` your current directory and run `npm start` to start the web server. -Copy the URL that will be printed to console and open it in a browser to use the playground. +## Building the Playground Locally + +1. Build the entire repo by running `./build.py` in the root directory. + If you only want to build the functionality necessary to run the playground, you can use `python .\build.py --wasm --npm --play`. +2. Then make `./playground` your current directory and run `npm start` to start the web server. +3. Copy the URL that will be printed to console and open it in a browser to use the playground. + +## Building the Playground Locally in Watch Mode + +1. Build the entire repo by running `./build.py` in the root directory. + If you only want to build the functionality necessary to run the playground, you can use `python .\build.py --wasm --npm --play`. +2. Run `node watch.mjs` at the root of the repo. +3. Copy the URL that will be printed to the console and open it in a browser to use the playground. + +Any time you make changes to any of the source files, the changes will be automatically compiled. Refresh the page in the browser to reload the playground with your changes. diff --git a/playground/src/docs.tsx b/playground/src/docs.tsx index 651e1395e4..bc95158625 100644 --- a/playground/src/docs.tsx +++ b/playground/src/docs.tsx @@ -54,8 +54,8 @@ export function DocumentationDisplay(props: { const docsDiv = useRef(null); useEffect(() => { - if (!docsDiv.current) return; - docsDiv.current.innerHTML = props.documentation!.get( + if (!docsDiv.current || !props.documentation) return; + docsDiv.current.innerHTML = props.documentation.get( props.currentNamespace, )!; MathJax.typeset(); diff --git a/samples/notebooks/circuits.ipynb b/samples/notebooks/circuits.ipynb index df462406e0..dcc340678b 100644 --- a/samples/notebooks/circuits.ipynb +++ b/samples/notebooks/circuits.ipynb @@ -88,7 +88,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e8f7b9102d4d4a8b8b14c404af5a191e", + "model_id": "4a760784b95c464cbeeb20e6cb39493f", "version_major": 2, "version_minor": 0 }, @@ -111,7 +111,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can syntheisize a circuit diagram for any program by calling `qsharp.circuit()` with an entry expression." + "You can synthesize a circuit diagram for any program by calling `qsharp.circuit()` with an entry expression." ] }, { @@ -149,7 +149,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d83baba969474c70940287978e39abe0", + "model_id": "739e08c87275454186529bcd76156a0d", "version_major": 2, "version_minor": 0 }, @@ -203,7 +203,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "90a8e20a35fa4591be278aa0a7982861", + "model_id": "17f2fea978c24ccb8ae137888ac4aa05", "version_major": 2, "version_minor": 0 }, @@ -262,7 +262,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8790540b2821494f82beea3fc5496a23", + "model_id": "2dac021b8bf449b5a8df9fef0894f7b2", "version_major": 2, "version_minor": 0 }, @@ -340,7 +340,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bc7c91299732407999d10edf4e316715", + "model_id": "df689aa58fa842ccb668294f5ce1e56b", "version_major": 2, "version_minor": 0 }, @@ -421,30 +421,45 @@ "name": "stdout", "output_type": "stream", "text": [ - "result was One, applying X gate\n", + "Simulating program...\n", "result was Zero\n", - "result was One, applying X gate\n" - ] - }, - { - "ename": "QSharpError", - "evalue": "Error: Result comparison is unsupported for this backend\nCall stack:\n at ResetIfOne in line_0\n\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n\n \u001b[31m×\u001b[0m runtime error\n\u001b[31m ╰─▶ \u001b[0mResult comparison is unsupported for this backend\n ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n \u001b[2m5\u001b[0m │ let r = M(q);\n \u001b[2m6\u001b[0m │ if (r == One) {\n · \u001b[35;1m ─┬─\u001b[0m\n · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n ╰────\n", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mQSharpError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[16], line 5\u001b[0m\n\u001b[0;32m 2\u001b[0m qsharp\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mResetIfOne()\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m3\u001b[39m)\n\u001b[0;32m 4\u001b[0m \u001b[38;5;66;03m# The same program cannot be synthesized as a circuit because of the conditional X gate.\u001b[39;00m\n\u001b[1;32m----> 5\u001b[0m qsharp\u001b[38;5;241m.\u001b[39mcircuit(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mResetIfOne()\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "File \u001b[1;32mc:\\src\\qsharp\\.venv\\Lib\\site-packages\\qsharp\\_qsharp.py:259\u001b[0m, in \u001b[0;36mcircuit\u001b[1;34m(entry_expr, operation)\u001b[0m\n\u001b[0;32m 244\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcircuit\u001b[39m(\n\u001b[0;32m 245\u001b[0m entry_expr: Optional[\u001b[38;5;28mstr\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;241m*\u001b[39m, operation: Optional[\u001b[38;5;28mstr\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m 246\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Circuit:\n\u001b[0;32m 247\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 248\u001b[0m \u001b[38;5;124;03m Synthesizes a circuit for a Q# program. Either an entry\u001b[39;00m\n\u001b[0;32m 249\u001b[0m \u001b[38;5;124;03m expression or an operation must be provided.\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 257\u001b[0m \u001b[38;5;124;03m :raises QSharpError: If there is an error synthesizing the circuit.\u001b[39;00m\n\u001b[0;32m 258\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m--> 259\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m get_interpreter()\u001b[38;5;241m.\u001b[39mcircuit(entry_expr, operation)\n", - "\u001b[1;31mQSharpError\u001b[0m: Error: Result comparison is unsupported for this backend\nCall stack:\n at ResetIfOne in line_0\n\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n\n \u001b[31m×\u001b[0m runtime error\n\u001b[31m ╰─▶ \u001b[0mResult comparison is unsupported for this backend\n ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n \u001b[2m5\u001b[0m │ let r = M(q);\n \u001b[2m6\u001b[0m │ if (r == One) {\n · \u001b[35;1m ─┬─\u001b[0m\n · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n ╰────\n" + "result was Zero\n", + "result was Zero\n", + "\n", + "Synthesizing circuit for program (should raise error)...\n", + "Error: cannot compare measurement results\n", + "Call stack:\n", + " at ResetIfOne in line_0\n", + "\u001b[31mQsc.Eval.ResultComparisonUnsupported\u001b[0m\n", + "\n", + " \u001b[31m×\u001b[0m runtime error\n", + "\u001b[31m ╰─▶ \u001b[0mcannot compare measurement results\n", + " ╭─[\u001b[36;1;4mline_0\u001b[0m:5:1]\n", + " \u001b[2m5\u001b[0m │ let r = M(q);\n", + " \u001b[2m6\u001b[0m │ if (r == One) {\n", + " · \u001b[35;1m ─┬─\u001b[0m\n", + " · \u001b[35;1m╰── \u001b[35;1mcannot compare to result\u001b[0m\u001b[0m\n", + " \u001b[2m7\u001b[0m │ Message(\"result was One, applying X gate\");\n", + " ╰────\n", + "\u001b[36m help: \u001b[0mcomparing measurement results is not supported when performing\n", + " circuit synthesis or base profile QIR generation\n", + "\n" ] } ], "source": [ "# Program can be simulated. Differerent shots may produce different results.\n", + "print(\"Simulating program...\")\n", "qsharp.run(\"ResetIfOne()\", 3)\n", "\n", + "print()\n", + "\n", "# The same program cannot be synthesized as a circuit because of the conditional X gate.\n", - "qsharp.circuit(\"ResetIfOne()\")" + "print(\"Synthesizing circuit for program (should raise error)...\")\n", + "try:\n", + " qsharp.circuit(\"ResetIfOne()\")\n", + "except qsharp.QSharpError as e:\n", + " print(e)" ] }, { @@ -502,7 +517,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c36cf04725e0491aa72a621f0d44f933", + "model_id": "c4ef7327e1194d37991f5b46b35f3a9d", "version_major": 2, "version_minor": 0 }, @@ -536,7 +551,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/version.py b/version.py index cdf26323f6..c545a5a1c1 100755 --- a/version.py +++ b/version.py @@ -69,7 +69,7 @@ def update_file(file, old_text, new_text): ) update_file( - os.path.join(root_dir, "npm/package.json"), + os.path.join(root_dir, "npm/qsharp/package.json"), r'"version": "0.0.0",', r'"version": "{}",'.format(npm_version), ) diff --git a/vscode/package.json b/vscode/package.json index bd4281febc..db5b02ab51 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -102,12 +102,12 @@ "enum": [ "unrestricted", "base", - "adaptive" + "quantinuum" ], "enumDescriptions": [ "The set of all capabilities required to run any Q# program.", "The minimal set of capabilities required to run a quantum program. This option maps to the Base Profile as defined by the QIR specification.", - "The set of capabilities required to run an adaptive quantum program. This option maps to the Adaptive Profile as defined by the QIR specification." + "The Quantinuum target profile includes all of the required Adaptive Profile capabilities, as well as the optional integer computation and qubit reset capabilities, as defined by the QIR specification." ], "description": "Setting the target profile allows the Q# extension to generate programs that are compatible with a specific target. The target is the hardware or simulator which will be used to run the Q# program. The target profile is a description of a target's capabilities." }, diff --git a/vscode/src/circuit.ts b/vscode/src/circuit.ts index 911c7e4c59..22b492ba32 100644 --- a/vscode/src/circuit.ts +++ b/vscode/src/circuit.ts @@ -4,8 +4,11 @@ import { type Circuit as CircuitData } from "@microsoft/quantum-viz.js/lib"; import { escapeHtml } from "markdown-it/lib/common/utils"; import { + ICompilerWorker, IOperationInfo, IRange, + ProgramConfig, + TargetProfile, VSDiagnostic, getCompilerWorker, log, @@ -20,6 +23,37 @@ import { sendMessageToPanel } from "./webviewPanel"; const compilerRunTimeoutMs = 1000 * 60 * 5; // 5 minutes +/** + * Input parameters for generating a circuit. + */ +type CircuitParams = { + program: ProgramConfig; + targetProfile: TargetProfile; + operation?: IOperationInfo; +}; + +/** + * Result of a circuit generation attempt. + */ +type CircuitOrError = { + simulated: boolean; +} & ( + | { + result: "success"; + circuit: CircuitData; + } + | { + result: "error"; + errors: { + document: string; + diag: VSDiagnostic; + stack: string; + }[]; + hasResultComparisonError: boolean; + timeout: boolean; + } +); + export async function showCircuitCommand( extensionUri: Uri, operation: IOperationInfo | undefined, @@ -27,127 +61,265 @@ export async function showCircuitCommand( const associationId = getRandomGuid(); sendTelemetryEvent(EventType.TriggerCircuit, { associationId }, {}); - const compilerWorkerScriptPath = Uri.joinPath( - extensionUri, - "./out/compilerWorker.js", - ).toString(); - const editor = window.activeTextEditor; if (!editor || !isQsharpDocument(editor.document)) { throw new Error("The currently active window is not a Q# file"); } - sendMessageToPanel("circuit", true, undefined); - - let timeout = false; - - // Start the worker, run the code, and send the results to the webview - const worker = getCompilerWorker(compilerWorkerScriptPath); - const compilerTimeout = setTimeout(() => { - timeout = true; - sendTelemetryEvent(EventType.CircuitEnd, { - associationId, - reason: "timeout", - flowStatus: UserFlowStatus.Aborted, - }); - log.info("terminating circuit worker due to timeout"); - worker.terminate(); - }, compilerRunTimeoutMs); - const docUri = editor.document.uri; - const sources = await loadProject(docUri); + const program = await loadProject(docUri); const targetProfile = getTarget(); - try { - sendTelemetryEvent(EventType.CircuitStart, { associationId }, {}); - - const circuit = await worker.getCircuit(sources, targetProfile, operation); - clearTimeout(compilerTimeout); - - updateCircuitPanel( + sendTelemetryEvent( + EventType.CircuitStart, + { + associationId, targetProfile, - docUri.path, - true, // reveal - { circuit, operation }, - ); + isOperation: (!!operation).toString(), + }, + {}, + ); + + // Generate the circuit and update the panel. + // generateCircuits() takes care of handling timeouts and + // falling back to the simulator for dynamic circuits. + const result = await generateCircuit(extensionUri, docUri, { + program: program, + targetProfile, + operation, + }); + if (result.result === "success") { sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), associationId, flowStatus: UserFlowStatus.Succeeded, }); - } catch (e: any) { - log.error("Circuit error. ", e.toString()); - clearTimeout(compilerTimeout); - - const errors: [string, VSDiagnostic, string][] = - typeof e === "string" ? JSON.parse(e) : undefined; - let errorHtml = "There was an error generating the circuit."; - if (errors) { - errorHtml = errorsToHtml(errors, docUri); - } + } else { + if (result.timeout) { + sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), + associationId, + reason: "timeout", + flowStatus: UserFlowStatus.Aborted, + }); + } else { + const reason = + result.errors.length > 0 ? result.errors[0].diag.code : "unknown"; - if (!timeout) { sendTelemetryEvent(EventType.CircuitEnd, { + simulated: result.simulated.toString(), associationId, - reason: errors && errors[0] ? errors[0][1].code : undefined, + reason, flowStatus: UserFlowStatus.Failed, }); } + } +} + +/** + * Generate the circuit and update the panel with the results. + * We first attempt to generate a circuit without running the simulator, + * which should be fast. + * + * If that fails, specifically due to a result comparison error, + * that means this is a dynamic circuit. We fall back to using the + * simulator in this case ("trace" mode), which is slower. + */ +async function generateCircuit( + extensionUri: Uri, + docUri: Uri, + params: CircuitParams, +): Promise { + const programPath = docUri.path; + + // Before we start, reveal the panel with the "calculating" spinner + updateCircuitPanel( + params.targetProfile, + programPath, + true, // reveal + { operation: params.operation, calculating: true }, + ); + + // First, try without simulating + let result = await getCircuitOrErrorWithTimeout( + extensionUri, + params, + false, // simulate + ); + + if (result.result === "error" && result.hasResultComparisonError) { + // Retry with the simulator if circuit generation failed because + // there was a result comparison (i.e. if this is a dynamic circuit) updateCircuitPanel( - targetProfile, - docUri.path, + params.targetProfile, + programPath, + false, // reveal + { + operation: params.operation, + calculating: true, + simulated: true, + }, + ); + + // try again with the simulator + result = await getCircuitOrErrorWithTimeout( + extensionUri, + params, + true, // simulate + ); + } + + // Update the panel with the results + + if (result.result === "success") { + updateCircuitPanel( + params.targetProfile, + programPath, + false, // reveal + { + circuit: result.circuit, + operation: params.operation, + simulated: result.simulated, + }, + ); + } else { + log.error("Circuit error. ", result); + let errorHtml = "There was an error generating the circuit."; + if (result.errors.length > 0) { + errorHtml = errorsToHtml(result.errors); + } else if (result.timeout) { + errorHtml = `The circuit generation exceeded the timeout of ${compilerRunTimeoutMs}ms.`; + } + + updateCircuitPanel( + params.targetProfile, + programPath, false, // reveal - { errorHtml, operation }, + { + errorHtml, + operation: params.operation, + simulated: result.simulated, + }, ); - } finally { - log.info("terminating circuit worker"); + } + + return result; +} + +/** + * Wrapper around getCircuit() that enforces a timeout. + * Won't throw for known errors. + */ +async function getCircuitOrErrorWithTimeout( + extensionUri: Uri, + params: CircuitParams, + simulate: boolean, +): Promise { + let timeout = false; + + const compilerWorkerScriptPath = Uri.joinPath( + extensionUri, + "./out/compilerWorker.js", + ).toString(); + + const worker = getCompilerWorker(compilerWorkerScriptPath); + const compilerTimeout = setTimeout(() => { + timeout = true; + log.info("terminating circuit worker due to timeout"); worker.terminate(); + }, compilerRunTimeoutMs); + + const result = await getCircuitOrError(worker, params, simulate); + clearTimeout(compilerTimeout); + + if (result.result === "error") { + return { + ...result, + timeout, + }; + } else { + return result; } } +/** + * Wrapper around compiler getCircuit() that handles exceptions + * and converts to strongly typed error object. + * Won't throw for known errors. + */ +async function getCircuitOrError( + worker: ICompilerWorker, + params: CircuitParams, + simulate: boolean, +): Promise { + try { + const circuit = await worker.getCircuit( + params.program, + params.targetProfile, + simulate, + params.operation, + ); + return { result: "success", simulated: simulate, circuit }; + } catch (e: any) { + let errors: { document: string; diag: VSDiagnostic; stack: string }[] = []; + let resultCompError = false; + if (typeof e === "string") { + try { + const rawErrors: [string, VSDiagnostic, string][] = JSON.parse(e); + errors = rawErrors.map(([document, diag, stack]) => ({ + document, + diag, + stack, + })); + resultCompError = hasResultComparisonError(e); + } catch (e) { + // couldn't parse the error - would indicate a bug. + // will get reported up the stack as a generic error + } + } + return { + result: "error", + simulated: simulate, + errors, + hasResultComparisonError: resultCompError, + timeout: false, + }; + } +} + +function hasResultComparisonError(e: unknown) { + const errors: [string, VSDiagnostic, string][] = + typeof e === "string" ? JSON.parse(e) : undefined; + const hasResultComparisonError = + errors && + errors.findIndex( + ([, diag]) => diag.code === "Qsc.Eval.ResultComparisonUnsupported", + ) >= 0; + return hasResultComparisonError; +} + /** * Formats an array of compiler/runtime errors into HTML to be presented to the user. * - * @param errors - * The first string is the document URI or "" if the error isn't associated with a specific document. - * The VSDiagnostic is the error information. - * The last string is the stack trace. - * + * @param errors The list of errors to format. * @returns The HTML formatted errors, to be set as the inner contents of a container element. */ function errorsToHtml( - errors: [string, VSDiagnostic, string][], - programUri: Uri, + errors: { document: string; diag: VSDiagnostic; stack: string }[], ) { let errorHtml = ""; for (const error of errors) { - const [document, diag, rawStack] = error; - - if (diag.code === "Qsc.Eval.ResultComparisonUnsupported") { - const commandUri = Uri.parse( - `command:qsharp-vscode.runEditorContentsWithCircuit?${encodeURIComponent(JSON.stringify([programUri]))}`, - true, - ); - const messageHtml = - `

Synthesizing circuits is unsupported for programs that ` + - `contain behavior that is conditional on a qubit measurement result, ` + - `since the resulting circuit may depend on the outcome of the measurement.

` + - `

If you would like to generate a circuit for this program, you can ` + - `run the program in the simulator and show the resulting circuit, ` + - `or edit your code to avoid the result comparison indicated by the call stack below.

`; - - errorHtml += messageHtml; - } else { - const location = documentHtml(document, diag.range); + const { document, diag, stack: rawStack } = error; - const message = escapeHtml(`(${diag.code}) ${diag.message}`).replace( - "\n", - "

", - ); + const location = documentHtml(document, diag.range); + const message = escapeHtml(`(${diag.code}) ${diag.message}`).replace( + "\n", + "

", + ); - errorHtml += `

${location}: ${message}

`; - } + errorHtml += `

${location}: ${message}

`; if (rawStack) { const stack = rawStack @@ -172,18 +344,19 @@ function errorsToHtml( export function updateCircuitPanel( targetProfile: string, - docPath: string, + programPath: string, reveal: boolean, params: { circuit?: CircuitData; errorHtml?: string; - simulating?: boolean; + simulated?: boolean; operation?: IOperationInfo | undefined; + calculating?: boolean; }, ) { const title = params?.operation ? `${params.operation.operation} with ${params.operation.totalNumQubits} input qubits` - : basename(docPath) || "Circuit"; + : basename(programPath) || "Circuit"; // Trim the Q#: prefix from the target profile name - that's meant for the ui text in the status bar const target = `Target profile: ${getTargetFriendlyName(targetProfile).replace("Q#: ", "")} `; @@ -191,7 +364,8 @@ export function updateCircuitPanel( const props = { title, targetProfile: target, - simulating: params?.simulating || false, + simulated: params?.simulated || false, + calculating: params?.calculating || false, circuit: params?.circuit, errorHtml: params?.errorHtml, }; @@ -229,7 +403,7 @@ function documentHtml(maybeUri: string, range?: IRange) { ); const fsPath = escapeHtml(uri.fsPath); const lineColumn = range - ? escapeHtml(`:${range.start.line}:${range.start.character}`) + ? escapeHtml(`:${range.start.line + 1}:${range.start.character + 1}`) : ""; location = `${fsPath}${lineColumn}`; } catch (e) { diff --git a/vscode/src/config.ts b/vscode/src/config.ts index 6b65bdf70e..9177ef48d5 100644 --- a/vscode/src/config.ts +++ b/vscode/src/config.ts @@ -9,8 +9,8 @@ export function getTarget(): TargetProfile { .getConfiguration("Q#") .get("targetProfile", "unrestricted"); switch (target) { - case "adaptive": case "base": + case "quantinuum": case "unrestricted": return target; default: @@ -30,10 +30,10 @@ export async function setTarget(target: TargetProfile) { export function getTargetFriendlyName(targetProfile?: string) { switch (targetProfile) { - case "adaptive": - return "Q#: QIR adaptive"; case "base": return "Q#: QIR base"; + case "quantinuum": + return "Q#: QIR Quantinuum"; case "unrestricted": return "Q#: unrestricted"; default: diff --git a/vscode/src/debugger/activate.ts b/vscode/src/debugger/activate.ts index 840de76318..34b8c52111 100644 --- a/vscode/src/debugger/activate.ts +++ b/vscode/src/debugger/activate.ts @@ -74,6 +74,11 @@ function registerCommands(context: vscode.ExtensionContext) { config: { name: string; [key: string]: any }, options?: vscode.DebugSessionOptions, ) { + if (vscode.debug.activeDebugSession?.type === "qsharp") { + // Multiple debug sessions disallowed, to reduce confusion + return; + } + let targetResource = resource; if (!targetResource && vscode.window.activeTextEditor) { targetResource = vscode.window.activeTextEditor.document.uri; diff --git a/vscode/src/debugger/session.ts b/vscode/src/debugger/session.ts index 366f938973..bbeb369c00 100644 --- a/vscode/src/debugger/session.ts +++ b/vscode/src/debugger/session.ts @@ -42,6 +42,7 @@ import { getRandomGuid } from "../utils"; import { createDebugConsoleEventTarget } from "./output"; import { ILaunchRequestArguments } from "./types"; import { escapeHtml } from "markdown-it/lib/common/utils"; +import { isPanelOpen } from "../webviewPanel"; const ErrorProgramHasErrors = "program contains compile errors(s): cannot run. See debug console for more details."; @@ -316,9 +317,7 @@ export class QscDebugSession extends LoggingDebugSession { error = e; } - if (this.config.showCircuit) { - await this.showCircuit(error); - } + await this.updateCircuit(error); if (!result) { // Can be a runtime failure in the program @@ -389,17 +388,15 @@ export class QscDebugSession extends LoggingDebugSession { bps, this.eventTarget, ); - if (this.config.showCircuit) { - this.showCircuit(); - } + + await this.updateCircuit(); + if (result.id != StepResultId.Return) { await this.endSession(`execution didn't run to completion`, -1); return; } } catch (error) { - if (this.config.showCircuit) { - await this.showCircuit(error); - } + await this.updateCircuit(error); await this.endSession(`ending session due to error: ${error}`, 1); return; } @@ -819,13 +816,14 @@ export class QscDebugSession extends LoggingDebugSession { // This will get invoked when the "Quantum Circuit" scope is expanded // in the Variables view, but instead of showing any values in the variables // view, we can pop open the circuit diagram panel. - this.showCircuit(); - - // Keep updating the circuit for the rest of this session, even if - // the Variables scope gets collapsed by the user. If we don't do this, - // the diagram won't get updated with each step even though the circuit - // panel is still being shown, which is misleading. - this.config.showCircuit = true; + if (!this.config.showCircuit) { + // Keep updating the circuit for the rest of this session, even if + // the Variables scope gets collapsed by the user. If we don't do this, + // the diagram won't get updated with each step even though the circuit + // panel is still being shown, which is misleading. + this.config.showCircuit = true; + await this.updateCircuit(); + } response.body = { variables: [ { @@ -934,31 +932,34 @@ export class QscDebugSession extends LoggingDebugSession { } } - private async showCircuit(error?: any) { - // Error returned from the debugger has a message and a stack (which also includes the message). - // We would ideally retrieve the original runtime error, and format it to be consistent - // with the other runtime errors that can be shown in the circuit panel, but that will require - // a bit of refactoring. - const stack = - error && typeof error === "object" && typeof error.stack === "string" - ? escapeHtml(error.stack) - : undefined; - - const circuit = await this.debugService.getCircuit(); - - updateCircuitPanel( - this.targetProfile, - vscode.Uri.parse(this.sources[0][0]).path, - !this.revealedCircuit, - { - circuit, - errorHtml: stack ? `
${stack}
` : undefined, - simulating: true, - }, - ); + /* Updates the circuit panel if `showCircuit` is true or if panel is already open */ + private async updateCircuit(error?: any) { + if (this.config.showCircuit || isPanelOpen("circuit")) { + // Error returned from the debugger has a message and a stack (which also includes the message). + // We would ideally retrieve the original runtime error, and format it to be consistent + // with the other runtime errors that can be shown in the circuit panel, but that will require + // a bit of refactoring. + const stack = + error && typeof error === "object" && typeof error.stack === "string" + ? escapeHtml(error.stack) + : undefined; + + const circuit = await this.debugService.getCircuit(); + + updateCircuitPanel( + this.targetProfile, + vscode.Uri.parse(this.sources[0][0]).path, + !this.revealedCircuit, + { + circuit, + errorHtml: stack ? `
${stack}
` : undefined, + simulated: true, + }, + ); - // Only reveal the panel once per session, to keep it from - // moving around while stepping - this.revealedCircuit = true; + // Only reveal the panel once per session, to keep it from + // moving around while stepping + this.revealedCircuit = true; + } } } diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 1338a76aed..608cf15cd7 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -154,6 +154,43 @@ function registerDocumentUpdateHandlers(languageService: ILanguageService) { }), ); + // Watch manifest changes and update each document in the same project as the manifest. + subscriptions.push( + vscode.workspace.onDidSaveTextDocument((manifest) => { + updateProjectDocuments(manifest.uri); + }), + ); + + // Trigger an update on all .qs child documents when their manifest is deleted, + // so that they can get reparented to single-file-projects. + subscriptions.push( + vscode.workspace.onDidDeleteFiles((event) => { + event.files.forEach((uri) => { + updateProjectDocuments(uri); + }); + }), + ); + + // Checks if the URI belongs to a qsharp manifest, and updates all + // open documents in the same project as the manifest. + function updateProjectDocuments(manifest: vscode.Uri) { + if (manifest.scheme === "file" && manifest.fsPath.endsWith("qsharp.json")) { + const project_folder = manifest.fsPath.slice( + 0, + manifest.fsPath.length - "qsharp.json".length, + ); + vscode.workspace.textDocuments.forEach((document) => { + if ( + !document.isClosed && + // Check that the document is on the same project as the manifest. + document.fileName.startsWith(project_folder) + ) { + updateIfQsharpDocument(document); + } + }); + } + } + function updateIfQsharpDocument(document: vscode.TextDocument) { if (isQsharpDocument(document) && !isQsharpNotebookCell(document)) { // Regular (not notebook) Q# document. @@ -278,8 +315,8 @@ async function updateLanguageServiceProfile(languageService: ILanguageService) { const targetProfile = getTarget(); switch (targetProfile) { - case "adaptive": case "base": + case "quantinuum": case "unrestricted": break; default: diff --git a/vscode/src/qirGeneration.ts b/vscode/src/qirGeneration.ts index 3ba1a039ea..870e7a783a 100644 --- a/vscode/src/qirGeneration.ts +++ b/vscode/src/qirGeneration.ts @@ -34,7 +34,7 @@ export async function getQirForActiveWindow(): Promise { const targetProfile = getTarget(); const enablePreviewQirGen = getEnablePreviewQirGen(); if (targetProfile !== "base") { - const allowed = targetProfile === "adaptive" && enablePreviewQirGen; + const allowed = targetProfile === "quantinuum" && enablePreviewQirGen; if (!allowed) { const result = await vscode.window.showWarningMessage( "Submitting to Azure is only supported when targeting the QIR base profile.", diff --git a/vscode/src/statusbar.ts b/vscode/src/statusbar.ts index f1feccc93d..849600020b 100644 --- a/vscode/src/statusbar.ts +++ b/vscode/src/statusbar.ts @@ -100,8 +100,8 @@ function registerTargetProfileCommand() { } const targetProfiles = [ - { configName: "adaptive", uiText: "Q#: QIR adaptive" }, { configName: "base", uiText: "Q#: QIR base" }, + { configName: "quantinuum", uiText: "Q#: QIR Quantinuum" }, { configName: "unrestricted", uiText: "Q#: unrestricted" }, ]; @@ -109,22 +109,22 @@ function getTargetProfiles(): { configName: string; uiText: string; }[] { - const allow_adaptive = getEnableAdaptiveProfile(); - if (allow_adaptive) { + const allow_quantinuum = getEnableAdaptiveProfile(); + if (allow_quantinuum) { return targetProfiles; } else { return targetProfiles.filter( - (profile) => profile.configName !== "adaptive", + (profile) => profile.configName !== "quantinuum", ); } } function getTargetProfileSetting(uiText: string): TargetProfile { switch (uiText) { - case "Q#: QIR adaptive": - return "adaptive"; case "Q#: QIR base": return "base"; + case "Q#: QIR Quantinuum": + return "quantinuum"; case "Q#: unrestricted": return "unrestricted"; default: diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts index ca28a6cf25..6caf4dfb99 100644 --- a/vscode/src/telemetry.ts +++ b/vscode/src/telemetry.ts @@ -225,15 +225,22 @@ type EventTypes = { measurements: Empty; }; [EventType.TriggerCircuit]: { - properties: { associationId: string }; + properties: { + associationId: string; + }; measurements: Empty; }; [EventType.CircuitStart]: { - properties: { associationId: string }; + properties: { + associationId: string; + isOperation: string; + targetProfile: string; + }; measurements: Empty; }; [EventType.CircuitEnd]: { properties: { + simulated: string; associationId: string; reason?: string; flowStatus: UserFlowStatus; @@ -292,6 +299,10 @@ export function sendTelemetryEvent( log.trace(`No telemetry reporter. Omitting telemetry event ${event}`); return; } + + // If you get a type error here, it's likely because you defined a + // non-string property or non-number measurement in `EventTypes`. + // For booleans, use `.toString()` to convert to string and store in `properties`. reporter.sendTelemetryEvent(event, properties, measurements); log.debug( `Sent telemetry: ${event} ${JSON.stringify(properties)} ${JSON.stringify( diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts index e2dd6cb459..dca9460936 100644 --- a/vscode/src/webviewPanel.ts +++ b/vscode/src/webviewPanel.ts @@ -426,6 +426,10 @@ export function sendMessageToPanel( if (message) panelRecord.panel.sendMessage(message); } +export function isPanelOpen(panelType: PanelType) { + return panelTypeToPanel[panelType].panel !== undefined; +} + export class QSharpWebViewPanel { public static extensionUri: Uri; private _ready = false; diff --git a/wasm/src/diagnostic.rs b/wasm/src/diagnostic.rs index e334a40cef..b29d3803cb 100644 --- a/wasm/src/diagnostic.rs +++ b/wasm/src/diagnostic.rs @@ -216,6 +216,7 @@ fn interpret_error_labels(err: &interpret::Error) -> Vec