Skip to content

Commit

Permalink
Controlled rotation gates don't check that target and controls are di…
Browse files Browse the repository at this point in the history
…stinct qubits (#1643)

Fixes #1339
  • Loading branch information
swernli authored Jun 25, 2024
1 parent be3181b commit 6c3ca7a
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 20 deletions.
30 changes: 27 additions & 3 deletions compiler/qsc_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use qsc_fir::fir::{
use qsc_fir::ty::Ty;
use qsc_lowerer::map_fir_package_to_hir;
use rand::{rngs::StdRng, SeedableRng};
use rustc_hash::FxHashSet;
use std::ops;
use std::{
cell::RefCell,
Expand Down Expand Up @@ -1013,9 +1014,10 @@ impl State {
callee.input,
spec_decl.input,
arg,
arg_span,
functor.controlled,
fixed_args,
);
)?;
Ok(())
}
CallableImpl::SimulatableIntrinsic(spec_decl) => {
Expand All @@ -1028,9 +1030,10 @@ impl State {
callee.input,
spec_decl.input,
arg,
arg_span,
functor.controlled,
fixed_args,
);
)?;
Ok(())
}
}
Expand Down Expand Up @@ -1401,9 +1404,10 @@ impl State {
decl_pat: PatId,
spec_pat: Option<PatId>,
args_val: Value,
args_span: PackageSpan,
ctl_count: u8,
fixed_args: Option<Rc<[Value]>>,
) {
) -> Result<(), Error> {
match spec_pat {
Some(spec_pat) => {
assert!(
Expand All @@ -1421,6 +1425,10 @@ impl State {
tup = rest.clone();
}

if !are_ctls_unique(&ctls, &tup) {
return Err(Error::QubitUniqueness(args_span));
}

self.bind_value(env, globals, spec_pat, Value::Array(ctls.into()));
self.bind_value(env, globals, decl_pat, merge_fixed_args(fixed_args, tup));
}
Expand All @@ -1431,6 +1439,7 @@ impl State {
merge_fixed_args(fixed_args, args_val),
),
}
Ok(())
}

fn to_global_span(&self, span: Span) -> PackageSpan {
Expand All @@ -1441,6 +1450,21 @@ impl State {
}
}

pub fn are_ctls_unique(ctls: &[Value], tup: &Value) -> bool {
let mut qubits = FxHashSet::default();
for ctl in ctls.iter().flat_map(Value::qubits) {
if !qubits.insert(ctl) {
return false;
}
}
for qubit in tup.qubits() {
if qubits.contains(&qubit) {
return false;
}
}
true
}

fn merge_fixed_args(fixed_args: Option<Rc<[Value]>>, arg: Value) -> Value {
if let Some(fixed_args) = fixed_args {
Value::Tuple(fixed_args.iter().cloned().chain(iter::once(arg)).collect())
Expand Down
117 changes: 117 additions & 0 deletions compiler/qsc_eval/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3690,6 +3690,123 @@ fn partial_app_mutable_arg() {
);
}

#[test]
fn controlled_operation_with_duplicate_controls_fails() {
check_expr(
"",
"{
use ctl = Qubit();
use q = Qubit();
Controlled I([ctl, ctl], q);
}",
&expect![[r#"
(
QubitUniqueness(
PackageSpan {
package: PackageId(
2,
),
span: Span {
lo: 86,
hi: 101,
},
},
),
[
Frame {
span: Span {
lo: 74,
hi: 101,
},
id: StoreItemId {
package: PackageId(
1,
),
item: LocalItemId(
123,
),
},
caller: PackageId(
2,
),
functor: FunctorApp {
adjoint: false,
controlled: 1,
},
},
],
)
"#]],
);
}

#[test]
fn controlled_operation_with_target_in_controls_fails() {
check_expr(
"",
"{
use ctl = Qubit();
use q = Qubit();
Controlled I([ctl, q], q);
}",
&expect![[r#"
(
QubitUniqueness(
PackageSpan {
package: PackageId(
2,
),
span: Span {
lo: 86,
hi: 99,
},
},
),
[
Frame {
span: Span {
lo: 74,
hi: 99,
},
id: StoreItemId {
package: PackageId(
1,
),
item: LocalItemId(
123,
),
},
caller: PackageId(
2,
),
functor: FunctorApp {
adjoint: false,
controlled: 1,
},
},
],
)
"#]],
);
}

#[test]
fn controlled_operation_with_unique_controls_duplicate_targets_allowed() {
check_expr(
"",
"{
operation DoubleI(q0 : Qubit, q1 : Qubit) : Unit is Ctl {
I(q0);
I(q1);
}
use ctl = Qubit();
use q = Qubit();
Controlled DoubleI([ctl], (q, q));
}",
&expect!["()"],
);
}

#[test]
fn partial_eval_simple_stmt() {
check_partial_eval_stmt(
Expand Down
25 changes: 24 additions & 1 deletion compiler/qsc_eval/src/val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl From<usize> for Result {
}
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Qubit(pub usize);

#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -381,6 +381,29 @@ impl Value {
Value::Var(_) => "Var",
}
}

/// Returns any qubits contained in the value as a vector. This does not
/// consume the value, and will recursively search through any nested values.
#[must_use]
pub fn qubits(&self) -> Vec<Qubit> {
match self {
Value::Array(arr) => arr.iter().flat_map(Value::qubits).collect(),
Value::Closure(closure) => closure.fixed_args.iter().flat_map(Value::qubits).collect(),
Value::Qubit(q) => vec![*q],
Value::Tuple(tup) => tup.iter().flat_map(Value::qubits).collect(),

Value::BigInt(_)
| Value::Bool(_)
| Value::Double(_)
| Value::Global(..)
| Value::Int(_)
| Value::Pauli(_)
| Value::Range(_)
| Value::Result(_)
| Value::String(_)
| Value::Var(_) => Vec::new(),
}
}
}

pub fn index_array(
Expand Down
46 changes: 30 additions & 16 deletions compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use management::{QuantumIntrinsicsChecker, ResourceManager};
use miette::Diagnostic;
use qsc_data_structures::{functors::FunctorApp, span::Span, target::TargetCapabilityFlags};
use qsc_eval::{
self, exec_graph_section,
self, are_ctls_unique, exec_graph_section,
output::GenericReceiver,
resolve_closure,
val::{
Expand Down Expand Up @@ -1137,6 +1137,7 @@ impl<'a> PartialEvaluator<'a> {
callee_expr_id: ExprId,
args_expr_id: ExprId,
) -> Result<EvalControlFlow, Error> {
let args_span = self.get_expr_package_span(args_expr_id);
let (callee_control_flow, args_control_flow) =
self.try_eval_callee_and_args(callee_expr_id, args_expr_id)?;

Expand Down Expand Up @@ -1183,9 +1184,10 @@ impl<'a> PartialEvaluator<'a> {
let (args, ctls_arg) = self.resolve_args(
(store_item_id.package, callable_decl.input).into(),
args_value.clone(),
Some(args_span),
ctls,
fixed_args,
);
)?;
let call_scope = Scope::new(
store_item_id.package,
Some((store_item_id.item, functor_app)),
Expand Down Expand Up @@ -1339,12 +1341,15 @@ impl<'a> PartialEvaluator<'a> {
let callable_id = self.get_or_insert_callable(callable);

// Resove the call arguments, create the call instruction and insert it to the current block.
let (args, ctls_arg) = self.resolve_args(
(store_item_id.package, callable_decl.input).into(),
args_value,
None,
None,
);
let (args, ctls_arg) = self
.resolve_args(
(store_item_id.package, callable_decl.input).into(),
args_value,
None,
None,
None,
)
.expect("no controls to verify");
assert!(
ctls_arg.is_none(),
"intrinsic operations cannot have controls"
Expand Down Expand Up @@ -2253,9 +2258,10 @@ impl<'a> PartialEvaluator<'a> {
&self,
store_pat_id: StorePatId,
value: Value,
args_span: Option<PackageSpan>,
ctls: Option<(StorePatId, u8)>,
fixed_args: Option<Rc<[Value]>>,
) -> (Vec<Arg>, Option<Arg>) {
) -> Result<(Vec<Arg>, Option<Arg>), Error> {
let mut value = value;
let ctls_arg = if let Some((ctls_pat_id, ctls_count)) = ctls {
let mut ctls = vec![];
Expand All @@ -2266,6 +2272,10 @@ impl<'a> PartialEvaluator<'a> {
ctls.extend_from_slice(&c.clone().unwrap_array());
value = rest.clone();
}
if !are_ctls_unique(&ctls, &value) {
let span = args_span.expect("span should be present");
return Err(EvalError::QubitUniqueness(span).into());
}
let ctls_pat = self.package_store.get_pat(ctls_pat_id);
let ctls_value = Value::Array(ctls.into());
match &ctls_pat.kind {
Expand Down Expand Up @@ -2315,20 +2325,24 @@ impl<'a> PartialEvaluator<'a> {
let pat_value_tuples = pats.iter().zip(values.to_vec());
for (pat_id, value) in pat_value_tuples {
// At this point we should no longer have control qubits so pass None.
let (mut element_args, None) = self.resolve_args(
(store_pat_id.package, *pat_id).into(),
value,
None,
None,
) else {
let (mut element_args, None) = self
.resolve_args(
(store_pat_id.package, *pat_id).into(),
value,
None,
None,
None,
)
.expect("no controls to verify")
else {
panic!("no control qubits are expected");
};
args.append(&mut element_args);
}
args
}
};
(args, ctls_arg)
Ok((args, ctls_arg))
}

fn try_eval_block(&mut self, block_id: BlockId) -> Result<EvalControlFlow, Error> {
Expand Down

0 comments on commit 6c3ca7a

Please sign in to comment.