Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions library/src/tests/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tests::test_expression_fails;
use crate::tests::{
test_expression_fails, test_expression_fails_with_lib_and_profile_and_sim,
test_expression_with_lib_and_profile_and_sim,
};

use super::test_expression;
use expect_test::expect;
use qsc::interpret::Value;
use qsc::{interpret::Value, target::Profile};

#[test]
fn check_operations_are_equal() {
Expand Down Expand Up @@ -535,6 +538,24 @@ fn check_post_select_collapses_superposition_to_zero() {
);
}

#[test]
fn check_post_select_collapses_superposition_to_zero_on_clifford() {
test_expression_with_lib_and_profile_and_sim(
"{
import Std.Diagnostics.PostSelectZ;
import Std.Diagnostics.CheckZero;
use q = Qubit();
H(q);
PostSelectZ(Zero, q);
// Qubit must be zero on release.
}",
"",
Profile::Unrestricted,
&mut qsc::CliffordSim::new(1),
&Value::unit(),
);
}

#[test]
fn check_post_select_collapses_superposition_to_one() {
test_expression(
Expand All @@ -552,6 +573,24 @@ fn check_post_select_collapses_superposition_to_one() {
);
}

#[test]
fn check_post_select_collapses_superposition_to_one_on_clifford() {
test_expression_with_lib_and_profile_and_sim(
"{
import Std.Diagnostics.PostSelectZ;
import Std.Diagnostics.CheckZero;
use q = Qubit();
H(q);
PostSelectZ(One, q);
X(q); // Resets the qubit back to zero for release.
}",
"",
Profile::Unrestricted,
&mut qsc::CliffordSim::new(1),
&Value::unit(),
);
}

#[test]
fn check_post_select_fails_with_non_existent_state() {
let err = test_expression_fails(
Expand All @@ -567,3 +606,22 @@ fn check_post_select_fails_with_non_existent_state() {
]
.assert_eq(&err);
}

#[test]
fn check_post_select_fails_with_non_existent_state_on_clifford() {
let err = test_expression_fails_with_lib_and_profile_and_sim(
"{
import Std.Diagnostics.PostSelectZ;
import Std.Diagnostics.CheckZero;
use q = Qubit();
PostSelectZ(One, q);
}",
"",
Profile::Unrestricted,
&mut qsc::CliffordSim::new(1),
);
expect![
"intrinsic callable `PostSelectZ` failed: post-selection condition has zero probability"
]
.assert_eq(&err);
}
6 changes: 5 additions & 1 deletion library/std/src/Std/OpenQASM/Intrinsic.qs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export rxx, ryy, rzz;
// that Qiskit wont emit correctly.
export dcx, ecr, r, rzx, cs, csdg, sxdg, csx, rccx, c3sqrtx, c3x, rc3x, xx_minus_yy, xx_plus_yy, ccz;

export mresetz_checked;
export mresetz_checked, postselectz;

export __quantum__qis__barrier__body;

Expand Down Expand Up @@ -647,6 +647,10 @@ operation mresetz_checked(q : Qubit) : Int {
}
}

operation postselectz(r : Result, q : Qubit) : Unit {
Std.Diagnostics.PostSelectZ(r, q);
}

/// The ``BARRIER`` function is used to implement the `barrier` statement in QASM.
/// The `@SimulatableIntrinsic` attribute is used to mark the operation for QIR
/// generation.
Expand Down
13 changes: 9 additions & 4 deletions source/compiler/qsc_eval/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ impl Backend for CliffordSim {
Err("adjoint T gate is not supported in Clifford simulation".to_string())
}

fn custom_intrinsic(&mut self, name: &str, _arg: Value) -> Option<Result<Value, String>> {
fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option<Result<Value, String>> {
match name {
"BeginEstimateCaching" => Some(Ok(Value::Bool(true))),
"GlobalPhase"
Expand All @@ -1433,9 +1433,14 @@ impl Backend for CliffordSim {
"Apply" => Some(Err(
"arbitrary unitary application not supported in Clifford simulation".to_string(),
)),
"PostSelectZ" => Some(Err(
"post-selection not supported in Clifford simulation".to_string()
)),
"PostSelectZ" => {
let [result, qubit] = unwrap_tuple(arg);
let id = qubit.unwrap_qubit().deref().0;
let Value::Result(val::Result::Val(val)) = result else {
panic!("first argument to PostSelectZ should be a measurement result");
};
Some(self.sim.post_select_z(val, id).map(|()| Value::unit()))
}
_ => None,
}
}
Expand Down
1,338 changes: 669 additions & 669 deletions source/compiler/qsc_fir_transforms/src/defunctionalize/tests/cross_package.rs

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions source/compiler/qsc_fir_transforms/src/monomorphize/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2008,14 +2008,14 @@ fn cross_package_non_intrinsic_generic_specializes() {
function MappedByIndex_Int___Int__Int__(mapper : ((Int, Int) -> (Int, Int)), array : Int[]) : (Int, Int)[] {
mutable mapped : (Int, Int)[] = [];
{
let _range_id_45755 : Range = 0..Length(array) - 1;
mutable _index_id_45758 : Int = _range_id_45755::Start;
let _step_id_45763 : Int = _range_id_45755::Step;
let _end_id_45768 : Int = _range_id_45755::End;
while _step_id_45763 > 0 and _index_id_45758 <= _end_id_45768 or _step_id_45763 < 0 and _index_id_45758 >= _end_id_45768 {
let index : Int = _index_id_45758;
let _range_id_45771 : Range = 0..Length(array) - 1;
mutable _index_id_45774 : Int = _range_id_45771::Start;
let _step_id_45779 : Int = _range_id_45771::Step;
let _end_id_45784 : Int = _range_id_45771::End;
while _step_id_45779 > 0 and _index_id_45774 <= _end_id_45784 or _step_id_45779 < 0 and _index_id_45774 >= _end_id_45784 {
let index : Int = _index_id_45774;
mapped += [mapper(index, array[index])];
_index_id_45758 += _step_id_45763;
_index_id_45774 += _step_id_45779;
}

}
Expand Down
18 changes: 18 additions & 0 deletions source/compiler/qsc_openqasm_parser/src/semantic/lowerer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ impl Lowerer {
continue;
} else if include.filename.to_lowercase() == "qdk.inc" {
self.define_mresetzchecked();
self.define_postselectz();
continue;
}

Expand Down Expand Up @@ -439,6 +440,23 @@ impl Lowerer {
}
}

fn define_postselectz(&mut self) {
let name = "postselectz";
let symbol = Symbol::new(
name,
Span::default(),
Type::Function(
vec![Type::Bit(false), Type::Qubit].into(),
Type::Void.into(),
),
Span::default(),
Default::default(),
);
if self.symbols.insert_symbol(symbol).is_err() {
self.push_redefined_symbol_error(name, Span::default());
}
}

/// Define the Qiskit standard gates in the symbol table.
/// Qiskit emits QASM3 that can't compile because it omits
/// definitions for many gates that aren't included in the
Expand Down
42 changes: 39 additions & 3 deletions source/paulimer/src/outcome_specific_simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ impl OutcomeSpecificSimulation {
pub fn outcome_vector(&self) -> &Vec<bool> {
&self.outcome_vector
}

pub fn post_select_z(&mut self, value: bool, index: usize) -> Result<(), String> {
measure_pauli_forced(self, &SparsePauli::from([quantum_core::z(index)]), value)
}
}

pub fn apply_hadamard(simulation: &mut OutcomeSpecificSimulation, qubit_index: usize) {
Expand Down Expand Up @@ -113,6 +117,7 @@ pub fn measure_pauli_with_hint<HintBits: PauliBits, HintPhase: PhaseExponent>(
simulation: &mut OutcomeSpecificSimulation,
observable: &SparsePauli,
hint: &PauliUnitary<HintBits, HintPhase>,
forced_bit: Option<bool>,
) {
assert!(
anti_commutes_with(observable, hint),
Expand All @@ -127,7 +132,16 @@ pub fn measure_pauli_with_hint<HintBits: PauliBits, HintPhase: PhaseExponent>(
let mut pauli = observable.clone() * hint;
pauli *= Phase::from_exponent(3u8.wrapping_sub(preimage.xz_phase_exponent().raw_value()));
PauliExponent::new(pauli) * &mut simulation.clifford;
allocate_random_bit(simulation);
if let Some(forced) = forced_bit {
// Since we have a forced bit, use that as ther result instead of a random one.
// To keep the internal state consistent, we also mark this as a random outcome,
// incrementing the count as well.
simulation.outcome_vector.push(forced);
simulation.random_outcome_indicator.push(true);
simulation.num_random_bits += 1;
Comment thread
swernli marked this conversation as resolved.
} else {
allocate_random_bit(simulation);
}
apply_conditional_pauli(
simulation,
hint,
Expand All @@ -153,12 +167,34 @@ pub fn measure_pauli(simulation: &mut OutcomeSpecificSimulation, observable: &Sp
match non_zero_pos {
Some(pos) => {
let hint = simulation.clifford.image_z(pos);
measure_pauli_with_hint(simulation, observable, &hint);
measure_pauli_with_hint(simulation, observable, &hint, None);
}
None => {
measure_deterministic(simulation, &preimage);
}
}
}

pub fn measure_pauli_forced(
simulation: &mut OutcomeSpecificSimulation,
observable: &SparsePauli,
forced_bit: bool,
) -> Result<(), String> {
let preimage = simulation.clifford.preimage(observable);
let non_zero_pos = preimage.x_bits().support().next();
match non_zero_pos {
Some(pos) => {
let hint = simulation.clifford.image_z(pos);
measure_pauli_with_hint(simulation, observable, &hint, Some(forced_bit));
}
None => {
measure_deterministic(simulation, &preimage);
if simulation.outcome_vector().last() != Some(&forced_bit) {
return Err("post-selection condition has zero probability".into());
}
}
}
Ok(())
}

fn measure_deterministic<Bits: PauliBits, Phase: PhaseExponent>(
Expand Down Expand Up @@ -252,7 +288,7 @@ impl Simulation for OutcomeSpecificSimulation {
) -> usize {
let pauli = SparsePauli::from(observable);
let hint = SparsePauli::from(hint);
measure_pauli_with_hint(self, &pauli, &hint);
measure_pauli_with_hint(self, &pauli, &hint, None);
self.outcome_vector().len() - 1
}

Expand Down
Loading
Loading