Repository: microsoft/qsharp
Pull request #1372 (2024-04-10 13:47:07)

Add `check_eq` for `StateDump` in Python
This adds a utility to the `StateDump` object in Python to help with writing tests that verify quantum state. The check ignores global phase, so allows for passing in any dictionary where the states differ from the dump by a constant factor, including unnormalized states.

github-actions[bot]: ## Benchmark for 1963f3b
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 334.1±1.65µs | 334.0±8.21µs | -0.03% |
| Array literal evaluation | 180.0±1.70µs | 181.5±2.39µs | +0.83% |
| Array update evaluation | 419.3±2.05µs | **416.7±1.05µs** | **-0.62%** |
| Core + Standard library compilation | 19.3±1.09ms | 19.3±1.46ms | 0.00% |
| Deutsch-Jozsa evaluation | 5.2±0.06ms | 5.2±0.06ms | 0.00% |
| Large file parity evaluation | 33.8±0.10ms | 33.7±0.25ms | -0.30% |
| Large input file compilation | 13.2±0.80ms | 12.8±0.46ms | -3.03% |
| Large input file compilation (interpreter) | 48.2±1.38ms | 47.8±1.30ms | -0.83% |
| Large nested iteration | 32.8±0.44ms | 32.8±0.74ms | 0.00% |
| Perform Runtime Capabilities Analysis (RCA) on Deutsch-Jozsa sample | 1538.4±130.85µs | 1540.9±108.91µs | +0.16% |
| Perform Runtime Capabilities Analysis (RCA) on large file sample | 8.0±0.11ms | 8.1±0.20ms | +1.25% |
| Perform Runtime Capabilities Analysis (RCA) on teleport sample | 1453.7±141.41µs | 1438.1±61.27µs | -1.07% |
| Perform Runtime Capabilities Analysis (RCA) on the core and std libraries | 22.1±0.46ms | 22.2±0.37ms | +0.45% |
| Teleport evaluation | 83.1±3.51µs | 83.5±5.57µs | +0.48% |

  </details>
  
github-actions[bot]: ## Benchmark for fbfd2d7
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 342.2±2.33µs | **333.7±1.78µs** | **-2.48%** |
| Array literal evaluation | 199.7±7.02µs | **181.0±4.76µs** | **-9.36%** |
| Array update evaluation | 430.2±11.36µs | **417.7±1.89µs** | **-2.91%** |
| Core + Standard library compilation | **19.1±0.84ms** | 23.2±1.18ms | **+21.47%** |
| Deutsch-Jozsa evaluation | 5.1±0.05ms | 5.1±0.11ms | 0.00% |
| Large file parity evaluation | 34.4±0.27ms | **34.1±0.43ms** | **-0.87%** |
| Large input file compilation | 12.6±0.54ms | 12.8±0.64ms | +1.59% |
| Large input file compilation (interpreter) | **48.4±1.33ms** | 50.6±1.63ms | **+4.55%** |
| Large nested iteration | 33.5±0.24ms | **32.7±0.85ms** | **-2.39%** |
| Perform Runtime Capabilities Analysis (RCA) on Deutsch-Jozsa sample | 1524.7±124.37µs | 1546.0±143.92µs | +1.40% |
| Perform Runtime Capabilities Analysis (RCA) on large file sample | 8.0±0.11ms | 7.9±0.18ms | -1.25% |
| Perform Runtime Capabilities Analysis (RCA) on teleport sample | 1430.8±79.33µs | 1459.6±129.69µs | +2.01% |
| Perform Runtime Capabilities Analysis (RCA) on the core and std libraries | 22.4±0.48ms | 22.5±0.49ms | +0.45% |
| Teleport evaluation | 81.9±3.61µs | 81.5±7.44µs | -0.49% |

  </details>
  
github-actions[bot]: ## Benchmark for 3eecd87
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | **334.5±2.18µs** | 345.1±2.53µs | **+3.17%** |
| Array literal evaluation | **180.1±1.33µs** | 198.9±1.85µs | **+10.44%** |
| Array update evaluation | **418.7±2.36µs** | 430.1±2.29µs | **+2.72%** |
| Core + Standard library compilation | 18.9±1.56ms | **17.0±0.49ms** | **-10.05%** |
| Deutsch-Jozsa evaluation | 5.1±0.04ms | 5.1±0.05ms | 0.00% |
| Large file parity evaluation | 33.7±0.15ms | 33.7±0.45ms | 0.00% |
| Large input file compilation | 11.5±0.15ms | **11.2±0.11ms** | **-2.61%** |
| Large input file compilation (interpreter) | 46.4±1.55ms | **43.8±1.00ms** | **-5.60%** |
| Large nested iteration | **32.6±0.28ms** | 34.0±0.59ms | **+4.29%** |
| Perform Runtime Capabilities Analysis (RCA) on Deutsch-Jozsa sample | 1491.9±54.47µs | 1495.8±26.65µs | +0.26% |
| Perform Runtime Capabilities Analysis (RCA) on large file sample | 7.7±0.08ms | **7.6±0.11ms** | **-1.30%** |
| Perform Runtime Capabilities Analysis (RCA) on teleport sample | 1403.3±32.37µs | 1415.9±40.17µs | +0.90% |
| Perform Runtime Capabilities Analysis (RCA) on the core and std libraries | 21.3±0.28ms | 21.3±0.37ms | 0.00% |
| Teleport evaluation | 81.2±3.53µs | 82.3±3.49µs | +1.35% |

  </details>
  

Patch:
diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs
index d423bd55..593ce375 100644
--- a/compiler/qsc/src/compile.rs
+++ b/compiler/qsc/src/compile.rs
@@ -33,6 +33,27 @@ 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<Error>) {
+    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,
@@ -42,13 +63,24 @@ pub fn compile(
     capabilities: TargetCapabilityFlags,
     language_features: LanguageFeatures,
 ) -> (CompileUnit, Vec<Error>) {
-    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<Error>) {
     let mut errors = Vec::new();
     for error in unit.errors.drain(..) {
         errors.push(WithSource::from_map(&unit.sources, error.into()));
diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs
index d2120809..a4473ce6 100644
--- a/compiler/qsc/src/incremental.rs
+++ b/compiler/qsc/src/incremental.rs
@@ -3,6 +3,7 @@
 
 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, SourceMap, TargetCapabilityFlags},
@@ -80,6 +81,24 @@ impl Compiler {
         })
     }
 
+    pub fn from(
+        store: PackageStore,
+        source_package_id: PackageId,
+        capabilities: TargetCapabilityFlags,
+        language_features: LanguageFeatures,
+    ) -> Result<Self, Errors> {
+        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<Increment, Errors> {
+        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<F>(
+        &mut self,
+        source_name: &str,
+        source_contents: &str,
+        package: ast::Package,
+        mut accumulate_errors: F,
+    ) -> Result<Increment, Errors>
+    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 a9d1af79..b1f9dbce 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -60,6 +60,7 @@ use qsc_fir::{
 use qsc_frontend::{
     compile::{CompileUnit, PackageStore, Source, SourceMap, TargetCapabilityFlags},
     error::WithSource,
+    incremental::Increment,
 };
 use qsc_passes::{PackageType, PassContext};
 use rustc_hash::FxHashSet;
@@ -218,6 +219,42 @@ impl Interpreter {
         })
     }
 
+    pub fn from(
+        store: PackageStore,
+        source_package_id: qsc_hir::hir::PackageId,
+        capabilities: TargetCapabilityFlags,
+        language_features: LanguageFeatures,
+    ) -> std::result::Result<Self, Vec<Error>> {
+        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),
+            source_package: map_hir_package_to_fir(source_package_id),
+        })
+    }
+
     pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
         self.quantum_seed = seed;
         self.sim.set_seed(seed);
@@ -293,6 +330,36 @@ impl Interpreter {
             .compile_fragments_fail_fast(&label, fragments)
             .map_err(into_errors)?;
 
+        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
diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs
index 6aca4b3a..0f89a4f5 100644
--- a/compiler/qsc_frontend/src/compile.rs
+++ b/compiler/qsc_frontend/src/compile.rs
@@ -314,6 +314,7 @@ impl MutVisitor for Offsetter {
     }
 }
 
+#[must_use]
 pub fn compile(
     store: &PackageStore,
     dependencies: &[PackageId],
@@ -321,8 +322,27 @@ pub fn compile(
     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<qsc_parse::Error>,
+) -> CompileUnit {
     let mut cond_compile = preprocess::Conditional::new(capabilities);
     cond_compile.visit_package(&mut ast_package);
     let dropped_names = cond_compile.into_names();
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 22cfd099..7cd4eb62 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -110,18 +110,70 @@ impl Compiler {
         unit: &mut CompileUnit,
         source_name: &str,
         source_contents: &str,
-        mut accumulate_errors: F,
+        accumulate_errors: F,
     ) -> Result<Increment, E>
     where
         F: FnMut(Vec<Error>) -> 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<F, E>(
+        &mut self,
+        unit: &mut CompileUnit,
+        source_name: &str,
+        source_contents: &str,
+        package: ast::Package,
+        accumulate_errors: F,
+    ) -> Result<Increment, E>
+    where
+        F: FnMut(Vec<Error>) -> 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<F, E>(
+        &mut self,
+        unit: &mut CompileUnit,
+        mut ast: ast::Package,
+        parse_errors: Vec<Error>,
+        mut accumulate_errors: F,
+    ) -> Result<Increment, E>
+    where
+        F: FnMut(Vec<Error>) -> 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<Error>) {
         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<qsc_parse::Error>,
+    ) -> (ast::Package, Vec<Error>) {
+        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/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs b/katas/content/multi_qubit_gates/entangle_qubits/Placeholder.qs
new file mode 100644
index 00000000..f5ee355d
--- /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 00000000..3a2b89e2
--- /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 00000000..822d40de
--- /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 00000000..07ce9227
--- /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 00000000..7326c091
--- /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 c5009d5e..9fa27369 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.
+
+<table>
+    <tr>
+        <th>Gate</th>
+        <th>Matrix</th>
+        <th>Applying to $|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle + \delta|11\rangle$</th>
+        <th>Applying to basis states</th>
+    </tr>
+    <tr>
+        <td>$CZ$</td>
+        <td>
+            $$\begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & 1 & 0 & 0 \\
+                0 & 0 & 1 & 0 \\
+                0 & 0 & 0 & -1
+            \end{bmatrix}$$
+        </td>
+        <td>$CZ|\psi\rangle = \alpha|00\rangle + \beta|01\rangle + \gamma|10\rangle - \delta|11\rangle$</td>
+        <td>
+            $$CZ|00\rangle = |00\rangle$$
+            $$CZ|01\rangle = |01\rangle$$
+            $$CZ|10\rangle = |10\rangle$$
+            $$CZ|11\rangle = -|11\rangle$$
+        </td>
+    </tr>
+</table>
+
+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/relative_phase_minusone/Placeholder.qs b/katas/content/multi_qubit_gates/relative_phase_minusone/Placeholder.qs
new file mode 100644
index 00000000..c5b83b31
--- /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 00000000..119e4afa
--- /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 00000000..57fcf1a7
--- /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 00000000..e0c35e86
--- /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 00000000..33bcf050
--- /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 00000000..b94bb4f4
--- /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/superposition/four_bitstrings/Placeholder.qs b/katas/content/superposition/four_bitstrings/Placeholder.qs
new file mode 100644
index 00000000..97e8bf32
--- /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 00000000..fcaf4bf1
--- /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 00000000..10c62a3a
--- /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 00000000..3adcf610
--- /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 00000000..25b98c4c
--- /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 00000000..4009f1f4
--- /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 f5378072..9104292a 100644
--- a/katas/content/superposition/index.md
+++ b/katas/content/superposition/index.md
@@ -143,6 +143,16 @@ This kata is designed to get you familiar with the concept of superposition and
     ]
 })
 
+@[exercise]({
+    "id": "superposition__four_bitstrings",
+    "title": "Four Bit Strings",
+    "path": "./four_bitstrings/",
+    "qsDependencies": [
+        "../KatasLibrary.qs",
+        "./Common.qs"
+    ]
+})
+
 @[section]({
     "id": "superposition__arbitrary_rotations",
     "title": "Arbitrary Rotations"
diff --git a/library/README.md b/library/README.md
index de906317..23dce7d6 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 b8abde55..c3dec923 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 beffbc38..f5fe2449 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 8b1d62a3..7116b8c1 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 a2b3659b..db5c8627 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;
     }
@@ -48,7 +62,8 @@ namespace Microsoft.Quantum.Convert {
     }
 
     /// # 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
diff --git a/library/std/core.qs b/library/std/core.qs
index 8442c912..93c477db 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/logical.qs b/library/std/logical.qs
index 5008a37a..8ff39f93 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 fd593701..0dc774d4 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 70c7facc..8b9f6ec1 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 {
diff --git a/library/std/random.qs b/library/std/random.qs
index f28f9e13..c568ccbc 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,9 +17,6 @@ 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
@@ -30,7 +28,8 @@ namespace Microsoft.Quantum.Random {
     }
 
     /// # 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
@@ -42,9 +41,6 @@ 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
diff --git a/library/std/re.qs b/library/std/re.qs
index af29c0c0..433b5aea 100644
--- a/library/std/re.qs
+++ b/library/std/re.qs
@@ -127,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
@@ -150,7 +149,6 @@ namespace Microsoft.Quantum.ResourceEstimation {
     }
 
     /// # Summary
-    ///
     /// Companion operation to `BeginRepeatEstimates`.
     operation EndRepeatEstimates() : Unit {
         body ... {
@@ -164,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 48418013..4269e934 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_state_preparation.qs b/library/std/unstable_state_preparation.qs
index cefe72bd..57376878 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 7c3b9dd9..b748aa07 100644
--- a/library/std/unstable_table_lookup.qs
+++ b/library/std/unstable_table_lookup.qs
@@ -36,13 +36,13 @@ 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)
     operation Select(
         data : Bool[][],
diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py
index 75d148bb..48eb06e9 100644
--- a/pip/qsharp/_qsharp.py
+++ b/pip/qsharp/_qsharp.py
@@ -10,7 +10,7 @@ from ._native import (
     Circuit,
 )
 from warnings import warn
-from typing import Any, Callable, Dict, Optional, TypedDict, Union, List
+from typing import Any, Callable, Dict, Optional, Tuple, TypedDict, Union, List
 from .estimator._estimator import EstimatorResult, EstimatorParams
 import json
 
@@ -349,6 +349,39 @@ class StateDump:
     def _repr_html_(self) -> str:
         return self.__data._repr_html_()
 
+    def check_eq(
+        self, state: Union[Dict[int, complex], List[complex]], tolerance: float = 1e-10
+    ) -> bool:
+        """
+        Checks if the state dump is equal to the given state. This is not mathematical equality,
+        as the check ignores global phase.
+
+        :param state: The state to check against, provided either as a dictionary of state indices to complex amplitudes,
+            or as a list of real amplitudes.
+        :param tolerance: The tolerance for the check. Defaults to 1e-10.
+        """
+        phase = None
+        # Convert a dense list of real amplitudes to a dictionary of state indices to complex amplitudes
+        if isinstance(state, list):
+            state = {i: state[i] for i in range(len(state))}
+        # Filter out zero states from the state dump and the given state based on tolerance
+        state = {k: v for k, v in state.items() if abs(v) > tolerance}
+        inner_state = {k: v for k, v in self.__inner.items() if abs(v) > tolerance}
+        if len(state) != len(inner_state):
+            return False
+        for key in state:
+            if key not in inner_state:
+                return False
+            if phase is None:
+                # Calculate the phase based on the first state pair encountered.
+                # Every pair of states after this must have the same phase for the states to be equivalent.
+                phase = inner_state[key] / state[key]
+            elif abs(phase - inner_state[key] / state[key]) > tolerance:
+                # This pair of states does not have the same phase,
+                # within tolerance, so the equivalence check fails.
+                return False
+        return True
+
 
 def dump_machine() -> StateDump:
     """


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index aca47931..85cf65e7 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -39,6 +39,17 @@ mod given_interpreter {
         (interpreter.eval_entry(&mut receiver), receiver.dump())
     }
 
+    fn fragment(
+        interpreter: &mut Interpreter,
+        fragments: &str,
+        package: crate::ast::Package,
+    ) -> (Result<Value, Vec<crate::interpret::Error>>, String) {
+        let mut cursor = Cursor::new(Vec::<u8>::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;
@@ -1424,13 +1435,15 @@ 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_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;
 
@@ -1617,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_doc_gen/src/generate_docs/tests.rs b/compiler/qsc_doc_gen/src/generate_docs/tests.rs
index 9308e0a6..b24f920e 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/tests.rs b/compiler/qsc_eval/src/tests.rs
index 479ccbdd..1d74c7ba 100644
--- a/compiler/qsc_eval/src/tests.rs
+++ b/compiler/qsc_eval/src/tests.rs
@@ -439,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/pip/tests/test_qsharp.py b/pip/tests/test_qsharp.py
index 4cd2316d..719ac290 100644
--- a/pip/tests/test_qsharp.py
+++ b/pip/tests/test_qsharp.py
@@ -98,6 +98,34 @@ def test_dump_machine() -> None:
     # Check that the state dump correctly supports iteration and membership checks
     for idx in state_dump:
         assert idx in state_dump
+    # Check that the state dump is correct and equivalence check ignores global phase, allowing passing
+    # in of different, potentially unnormalized states. The state should be
+    # |01⟩: 0.7071+0.0000𝑖, |11⟩: −0.7071+0.0000𝑖
+    assert state_dump.check_eq({1: complex(0.7071, 0.0), 3: complex(-0.7071, 0.0)})
+    assert state_dump.check_eq({1: complex(0.0, 0.7071), 3: complex(0.0, -0.7071)})
+    assert state_dump.check_eq({1: complex(0.5, 0.0), 3: complex(-0.5, 0.0)})
+    assert state_dump.check_eq(
+        {1: complex(0.7071, 0.0), 3: complex(-0.7071, 0.0), 0: complex(0.0, 0.0)}
+    )
+    assert state_dump.check_eq([0.0, 0.5, 0.0, -0.5])
+    assert state_dump.check_eq([0.0, 0.5001, 0.00001, -0.5], tolerance=1e-3)
+    assert state_dump.check_eq(
+        [complex(0.0, 0.0), complex(0.0, -0.5), complex(0.0, 0.0), complex(0.0, 0.5)]
+    )
+    assert not state_dump.check_eq({1: complex(0.7071, 0.0), 3: complex(0.7071, 0.0)})
+    assert not state_dump.check_eq({1: complex(0.5, 0.0), 3: complex(0.0, 0.5)})
+    assert not state_dump.check_eq({2: complex(0.5, 0.0), 3: complex(-0.5, 0.0)})
+    assert not state_dump.check_eq([0.0, 0.5001, 0.0, -0.5], tolerance=1e-6)
+    # Reset the qubits and apply a small rotation to q1, to confirm that tolerance applies to the dump
+    # itself and not just the state.
+    qsharp.eval("ResetAll([q1, q2]);")
+    qsharp.eval("Ry(0.0001, q1);")
+    state_dump = qsharp.dump_machine()
+    assert state_dump.qubit_count == 2
+    assert len(state_dump) == 2
+    assert not state_dump.check_eq([1.0])
+    assert state_dump.check_eq([0.99999999875, 0.0, 4.999999997916667e-05])
+    assert state_dump.check_eq([1.0], tolerance=1e-4)
 
 
 def test_dump_operation() -> None: