Skip to content

Commit 5a108fc

Browse files
authored
Add sample notebooks (#2437)
This PR adds sample Python integration and resource estimation notebooks for OpenQASM. There is more here than just those changes as existing external issues require workarounds. - Qiskit and existing OpenQASM code incorrectly specify 0 for angles which isn't allowed in the spec. So this PR adds the ability to cast literals to angles with a value of 0 for compatibility. This also removes some casting calls as we can now directly create the angle from the literal. - There is a util bitstring function which allows the user to specify that they want the output of simulation to be converted into a bitstring if any of the outputs are bit registers. This matches what Qiskit output would be like. For the moment the code defaults to Q#'s typically output rendering. - The old parser couldn't handle gates, but with the new parser we don't have to fully transpile into the Q# canonical gates. This changes the resource estimation numbers.
1 parent 9d26d96 commit 5a108fc

File tree

15 files changed

+1194
-29
lines changed

15 files changed

+1194
-29
lines changed

compiler/qsc_qasm/src/semantic/lowerer.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1721,9 +1721,21 @@ impl Lowerer {
17211721
}
17221722

17231723
// 1.3. Lower the args.
1724+
let carg_ty = crate::semantic::types::Type::Angle(None, false);
17241725
let args = stmt.args.iter().map(|arg| {
17251726
let arg = self.lower_expr(arg);
1726-
self.cast_expr_to_type(&crate::semantic::types::Type::Angle(None, false), &arg)
1727+
match &arg.kind.as_ref() {
1728+
semantic::ExprKind::Lit(kind) => {
1729+
if can_cast_literal(&carg_ty, &arg.ty)
1730+
|| can_cast_literal_with_value_knowledge(&carg_ty, kind)
1731+
{
1732+
self.coerce_literal_expr_to_type(&carg_ty, &arg, kind)
1733+
} else {
1734+
self.cast_expr_to_type(&carg_ty, &arg)
1735+
}
1736+
}
1737+
_ => self.cast_expr_to_type(&carg_ty, &arg),
1738+
}
17271739
});
17281740
let args = list_from_iter(args);
17291741
// 1.4. Lower the qubits.
@@ -2952,6 +2964,21 @@ impl Lowerer {
29522964
}
29532965
None
29542966
}
2967+
(Type::Angle(width, _), Type::Int(..) | Type::UInt(..)) => {
2968+
// compatibility case for existing code
2969+
if let semantic::LiteralKind::Int(value) = kind {
2970+
if *value == 0 {
2971+
return Some(semantic::Expr {
2972+
span,
2973+
kind: Box::new(semantic::ExprKind::Lit(semantic::LiteralKind::Angle(
2974+
Angle::from_u64_maybe_sized(0, *width),
2975+
))),
2976+
ty: lhs_ty.as_const(),
2977+
});
2978+
}
2979+
}
2980+
None
2981+
}
29552982
(Type::Float(..), Type::Float(..)) => {
29562983
if let semantic::LiteralKind::Float(value) = kind {
29572984
return Some(semantic::Expr {

compiler/qsc_qasm/src/semantic/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,14 @@ pub(crate) fn can_cast_literal_with_value_knowledge(lhs_ty: &Type, kind: &Litera
949949
return *value >= 0;
950950
}
951951
}
952+
// Much existing OpenQASM code uses 0 as a literal for angles
953+
// and Qiskit generates this code. While this is not allowed
954+
// in the spec, we allow it for compatibility.
955+
if matches!(lhs_ty, &Type::Angle(..)) {
956+
if let LiteralKind::Int(value) = kind {
957+
return *value == 0;
958+
}
959+
}
952960
false
953961
}
954962

compiler/qsc_qasm/src/stdlib/angle.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ impl Angle {
2828
Angle { value, size }
2929
}
3030

31+
pub fn from_u64_maybe_sized(value: u64, size: Option<u32>) -> Angle {
32+
Angle {
33+
value,
34+
size: size.unwrap_or(f64::MANTISSA_DIGITS),
35+
}
36+
}
37+
3138
pub fn from_f64_maybe_sized(val: f64, size: Option<u32>) -> Angle {
3239
Self::from_f64_sized(val, size.unwrap_or(f64::MANTISSA_DIGITS))
3340
}

compiler/qsc_qasm/src/tests/expression/implicit_cast_from_int.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,24 @@ fn to_explicit_complex_implicitly() -> miette::Result<(), Vec<Report>> {
200200
.assert_eq(&qsharp);
201201
Ok(())
202202
}
203+
204+
#[test]
205+
fn from_const_0_implicitly() -> miette::Result<(), Vec<Report>> {
206+
let source = "
207+
include \"stdgates.inc\";
208+
qubit q;
209+
rx(0) q;
210+
";
211+
212+
let qsharp = compile_qasm_to_qsharp(source)?;
213+
expect![[r#"
214+
import Std.OpenQASM.Intrinsic.*;
215+
let q = QIR.Runtime.__quantum__rt__qubit_allocate();
216+
rx(new Std.OpenQASM.Angle.Angle {
217+
Value = 0,
218+
Size = 53
219+
}, q);
220+
"#]]
221+
.assert_eq(&qsharp);
222+
Ok(())
223+
}

compiler/qsc_qasm/src/tests/statement/gate_call.rs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ fn u_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
1717
expect![[r#"
1818
import Std.OpenQASM.Intrinsic.*;
1919
let q = QIR.Runtime.__quantum__rt__qubit_allocate();
20-
U(Std.OpenQASM.Angle.DoubleAsAngle(1., 53), Std.OpenQASM.Angle.DoubleAsAngle(2., 53), Std.OpenQASM.Angle.DoubleAsAngle(3., 53), q);
20+
U(new Std.OpenQASM.Angle.Angle {
21+
Value = 1433540284805665,
22+
Size = 53
23+
}, new Std.OpenQASM.Angle.Angle {
24+
Value = 2867080569611330,
25+
Size = 53
26+
}, new Std.OpenQASM.Angle.Angle {
27+
Value = 4300620854416994,
28+
Size = 53
29+
}, q);
2130
"#]]
2231
.assert_eq(&qsharp);
2332
Ok(())
@@ -32,7 +41,10 @@ fn gphase_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
3241
let qsharp = compile_qasm_to_qsharp(source)?;
3342
expect![[r#"
3443
import Std.OpenQASM.Intrinsic.*;
35-
gphase(Std.OpenQASM.Angle.DoubleAsAngle(2., 53));
44+
gphase(new Std.OpenQASM.Angle.Angle {
45+
Value = 2867080569611330,
46+
Size = 53
47+
});
3648
"#]]
3749
.assert_eq(&qsharp);
3850
Ok(())
@@ -57,19 +69,40 @@ fn custom_gates_can_be_called_bypassing_stdgates() -> miette::Result<(), Vec<Rep
5769
expect![[r#"
5870
import Std.OpenQASM.Intrinsic.*;
5971
operation h(a : Qubit) : Unit is Adj + Ctl {
60-
U(Std.OpenQASM.Angle.DoubleAsAngle(3.141592653589793 / 2., 53), Std.OpenQASM.Angle.DoubleAsAngle(0., 53), Std.OpenQASM.Angle.DoubleAsAngle(3.141592653589793, 53), a);
72+
U(Std.OpenQASM.Angle.DoubleAsAngle(3.141592653589793 / 2., 53), new Std.OpenQASM.Angle.Angle {
73+
Value = 0,
74+
Size = 53
75+
}, new Std.OpenQASM.Angle.Angle {
76+
Value = 4503599627370496,
77+
Size = 53
78+
}, a);
6179
gphase(Std.OpenQASM.Angle.DoubleAsAngle(-3.141592653589793 / 4., 53));
6280
}
6381
operation x(a : Qubit) : Unit is Adj + Ctl {
64-
U(Std.OpenQASM.Angle.DoubleAsAngle(3.141592653589793, 53), Std.OpenQASM.Angle.DoubleAsAngle(0., 53), Std.OpenQASM.Angle.DoubleAsAngle(3.141592653589793, 53), a);
82+
U(new Std.OpenQASM.Angle.Angle {
83+
Value = 4503599627370496,
84+
Size = 53
85+
}, new Std.OpenQASM.Angle.Angle {
86+
Value = 0,
87+
Size = 53
88+
}, new Std.OpenQASM.Angle.Angle {
89+
Value = 4503599627370496,
90+
Size = 53
91+
}, a);
6592
gphase(Std.OpenQASM.Angle.DoubleAsAngle(-3.141592653589793 / 2., 53));
6693
}
6794
operation cx(a : Qubit, b : Qubit) : Unit is Adj + Ctl {
6895
Controlled x([a], b);
6996
}
7097
operation rz(λ : Std.OpenQASM.Angle.Angle, a : Qubit) : Unit is Adj + Ctl {
7198
gphase(Std.OpenQASM.Angle.DivideAngleByInt(Std.OpenQASM.Angle.NegAngle(λ), 2));
72-
U(Std.OpenQASM.Angle.DoubleAsAngle(0., 53), Std.OpenQASM.Angle.DoubleAsAngle(0., 53), λ, a);
99+
U(new Std.OpenQASM.Angle.Angle {
100+
Value = 0,
101+
Size = 53
102+
}, new Std.OpenQASM.Angle.Angle {
103+
Value = 0,
104+
Size = 53
105+
}, λ, a);
73106
}
74107
operation rxx(theta : Std.OpenQASM.Angle.Angle, a : Qubit, b : Qubit) : Unit is Adj + Ctl {
75108
h(a);
@@ -308,7 +341,10 @@ fn rx_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec<Report>> {
308341
expect![[r#"
309342
import Std.OpenQASM.Intrinsic.*;
310343
let q = QIR.Runtime.__quantum__rt__qubit_allocate();
311-
rx(Std.OpenQASM.Angle.DoubleAsAngle(2., 53), q);
344+
rx(new Std.OpenQASM.Angle.Angle {
345+
Value = 2867080569611330,
346+
Size = 53
347+
}, q);
312348
"#]]
313349
.assert_eq(&qsharp);
314350
Ok(())
@@ -560,7 +596,10 @@ fn rxx_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec<Report>> {
560596
expect![[r#"
561597
import Std.OpenQASM.Intrinsic.*;
562598
let q = QIR.Runtime.AllocateQubitArray(2);
563-
rxx(Std.OpenQASM.Angle.DoubleAsAngle(2., 53), q[1], q[0]);
599+
rxx(new Std.OpenQASM.Angle.Angle {
600+
Value = 2867080569611330,
601+
Size = 53
602+
}, q[1], q[0]);
564603
"#]]
565604
.assert_eq(&qsharp);
566605
Ok(())
@@ -577,7 +616,10 @@ fn ryy_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec<Report>> {
577616
expect![[r#"
578617
import Std.OpenQASM.Intrinsic.*;
579618
let q = QIR.Runtime.AllocateQubitArray(2);
580-
ryy(Std.OpenQASM.Angle.DoubleAsAngle(2., 53), q[1], q[0]);
619+
ryy(new Std.OpenQASM.Angle.Angle {
620+
Value = 2867080569611330,
621+
Size = 53
622+
}, q[1], q[0]);
581623
"#]]
582624
.assert_eq(&qsharp);
583625
Ok(())
@@ -594,7 +636,10 @@ fn rzz_gate_with_one_angle_can_be_called() -> miette::Result<(), Vec<Report>> {
594636
expect![[r#"
595637
import Std.OpenQASM.Intrinsic.*;
596638
let q = QIR.Runtime.AllocateQubitArray(2);
597-
rzz(Std.OpenQASM.Angle.DoubleAsAngle(2., 53), q[1], q[0]);
639+
rzz(new Std.OpenQASM.Angle.Angle {
640+
Value = 2867080569611330,
641+
Size = 53
642+
}, q[1], q[0]);
598643
"#]]
599644
.assert_eq(&qsharp);
600645
Ok(())

compiler/qsc_qasm/src/tests/statement/implicit_modified_gate_call.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ fn crx_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
115115
import Std.OpenQASM.Intrinsic.*;
116116
let ctl = QIR.Runtime.__quantum__rt__qubit_allocate();
117117
let target = QIR.Runtime.__quantum__rt__qubit_allocate();
118-
Controlled rx([ctl], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), target));
118+
Controlled rx([ctl], (new Std.OpenQASM.Angle.Angle {
119+
Value = 716770142402832,
120+
Size = 53
121+
}, target));
119122
"#]]
120123
.assert_eq(&qsharp);
121124
Ok(())
@@ -135,7 +138,10 @@ fn cry_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
135138
import Std.OpenQASM.Intrinsic.*;
136139
let ctl = QIR.Runtime.__quantum__rt__qubit_allocate();
137140
let target = QIR.Runtime.__quantum__rt__qubit_allocate();
138-
Controlled ry([ctl], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), target));
141+
Controlled ry([ctl], (new Std.OpenQASM.Angle.Angle {
142+
Value = 716770142402832,
143+
Size = 53
144+
}, target));
139145
"#]]
140146
.assert_eq(&qsharp);
141147
Ok(())
@@ -155,7 +161,10 @@ fn crz_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
155161
import Std.OpenQASM.Intrinsic.*;
156162
let ctl = QIR.Runtime.__quantum__rt__qubit_allocate();
157163
let target = QIR.Runtime.__quantum__rt__qubit_allocate();
158-
Controlled rz([ctl], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), target));
164+
Controlled rz([ctl], (new Std.OpenQASM.Angle.Angle {
165+
Value = 716770142402832,
166+
Size = 53
167+
}, target));
159168
"#]]
160169
.assert_eq(&qsharp);
161170
Ok(())
@@ -215,7 +224,10 @@ fn legacy_cphase_gate_can_be_called() -> miette::Result<(), Vec<Report>> {
215224
import Std.OpenQASM.Intrinsic.*;
216225
let ctl = QIR.Runtime.__quantum__rt__qubit_allocate();
217226
let target = QIR.Runtime.__quantum__rt__qubit_allocate();
218-
Controlled phase([ctl], (Std.OpenQASM.Angle.DoubleAsAngle(1., 53), target));
227+
Controlled phase([ctl], (new Std.OpenQASM.Angle.Angle {
228+
Value = 1433540284805665,
229+
Size = 53
230+
}, target));
219231
"#]]
220232
.assert_eq(&qsharp);
221233
Ok(())

compiler/qsc_qasm/src/tests/statement/modified_gate_call.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ fn multiple_controls_on_crx_gate_can_be_called() -> miette::Result<(), Vec<Repor
139139
import Std.OpenQASM.Intrinsic.*;
140140
let q = QIR.Runtime.AllocateQubitArray(4);
141141
let f = QIR.Runtime.__quantum__rt__qubit_allocate();
142-
Controlled Adjoint Controlled rx([q[1], q[0], q[2]], ([f], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), q[3])));
142+
Controlled Adjoint Controlled rx([q[1], q[0], q[2]], ([f], (new Std.OpenQASM.Angle.Angle {
143+
Value = 716770142402832,
144+
Size = 53
145+
}, q[3])));
143146
"#]]
144147
.assert_eq(&qsharp);
145148
Ok(())
@@ -159,7 +162,10 @@ fn neg_ctrl_can_be_applied_and_wrapped_in_another_modifier() -> miette::Result<(
159162
import Std.OpenQASM.Intrinsic.*;
160163
let q = QIR.Runtime.AllocateQubitArray(4);
161164
let f = QIR.Runtime.__quantum__rt__qubit_allocate();
162-
Adjoint ApplyControlledOnInt(0, Adjoint Controlled rx, [q[1], q[0], q[2]], ([f], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), q[3])));
165+
Adjoint ApplyControlledOnInt(0, Adjoint Controlled rx, [q[1], q[0], q[2]], ([f], (new Std.OpenQASM.Angle.Angle {
166+
Value = 716770142402832,
167+
Size = 53
168+
}, q[3])));
163169
"#]]
164170
.assert_eq(&qsharp);
165171
Ok(())
@@ -179,7 +185,10 @@ fn neg_ctrl_can_wrap_another_neg_crtl_modifier() -> miette::Result<(), Vec<Repor
179185
import Std.OpenQASM.Intrinsic.*;
180186
let q = QIR.Runtime.AllocateQubitArray(6);
181187
let f = QIR.Runtime.__quantum__rt__qubit_allocate();
182-
ApplyControlledOnInt(0, ApplyControlledOnInt, [q[1], q[0], q[2]], (0, Controlled rx, [q[3], q[4]], ([f], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), q[5]))));
188+
ApplyControlledOnInt(0, ApplyControlledOnInt, [q[1], q[0], q[2]], (0, Controlled rx, [q[3], q[4]], ([f], (new Std.OpenQASM.Angle.Angle {
189+
Value = 716770142402832,
190+
Size = 53
191+
}, q[5]))));
183192
"#]]
184193
.assert_eq(&qsharp);
185194
Ok(())
@@ -199,7 +208,10 @@ fn modifiers_can_be_repeated_many_times() -> miette::Result<(), Vec<Report>> {
199208
import Std.OpenQASM.Intrinsic.*;
200209
let q = QIR.Runtime.AllocateQubitArray(6);
201210
let f = QIR.Runtime.__quantum__rt__qubit_allocate();
202-
ApplyOperationPowerA(1, ApplyOperationPowerA, (1, ApplyOperationPowerA, (1, Controlled rx, ([f], (Std.OpenQASM.Angle.DoubleAsAngle(0.5, 53), q[5])))));
211+
ApplyOperationPowerA(1, ApplyOperationPowerA, (1, ApplyOperationPowerA, (1, Controlled rx, ([f], (new Std.OpenQASM.Angle.Angle {
212+
Value = 716770142402832,
213+
Size = 53
214+
}, q[5])))));
203215
"#]]
204216
.assert_eq(&qsharp);
205217
Ok(())

pip/qsharp/interop/qiskit/backends/backend_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def _create_results(self, output: Dict[str, Any]) -> Any:
315315
pass
316316

317317
def _transpile(self, circuit: QuantumCircuit, **options) -> QuantumCircuit:
318-
if self._skip_transpilation:
318+
if options.get("skip_transpilation", self._skip_transpilation):
319319
return circuit
320320

321321
circuit = self.run_qiskit_passes(circuit, options)

pip/qsharp/openqasm/_run.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
def run(
2424
source: Union[str, Callable],
25-
shots: int,
25+
shots: int = 1024,
2626
*args,
2727
on_result: Optional[Callable[[ShotResult], None]] = None,
2828
save_events: bool = False,
@@ -35,6 +35,7 @@ def run(
3535
DepolarizingNoise,
3636
]
3737
] = None,
38+
as_bitstring: bool = False,
3839
**kwargs: Optional[Dict[str, Any]],
3940
) -> List[Any]:
4041
"""
@@ -45,11 +46,12 @@ def run(
4546
Args:
4647
source (str): An OpenQASM program. Alternatively, a callable can be provided,
4748
which must be an already imported global callable.
48-
shots: The number of shots to run.
49+
shots: The number of shots to run, Defaults to 1024.
4950
*args: The arguments to pass to the callable, if one is provided.
5051
on_result: A callback function that will be called with each result. Only used when a callable is provided.
5152
save_events: If true, the output of each shot will be saved. If false, they will be printed. Only used when a callable is provided.
5253
noise: The noise to use in simulation.
54+
as_bitstring: If true, the result registers will be converted to bitstrings.
5355
**kwargs: Additional keyword arguments to pass to the compilation when source program is provided.
5456
- name (str): The name of the circuit. This is used as the entry point for the program.
5557
- target_profile (TargetProfile): The target profile to use for code generation.
@@ -158,4 +160,9 @@ def on_save_events(output: Output) -> None:
158160
durationMs = (monotonic() - start_time) * 1000
159161
telemetry_events.on_run_qasm_end(durationMs, shots)
160162

163+
if as_bitstring:
164+
from ._utils import as_bitstring as convert_to_bitstring
165+
166+
results = convert_to_bitstring(results)
167+
161168
return results

0 commit comments

Comments
 (0)