microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc_circuit/src/operations.rs

206lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use miette::Diagnostic;
8use qsc_hir::{
9 hir::{Item, ItemKind, Pat, PatKind},
10 ty::{Prim, Ty},
11};
12use thiserror::Error;
13
14#[derive(Clone, Debug, Diagnostic, Error)]
15pub enum Error {
16 #[error("expression does not evaluate to an operation that takes qubit parameters")]
17 #[diagnostic(code("Qsc.Circuit.NoCircuitForOperation"))]
18 #[diagnostic(help(
19 "provide the name of a callable or a lambda expression that only takes qubits as parameters"
20 ))]
21 NoQubitParameters,
22 #[error("cannot generate circuit for controlled invocation")]
23 #[diagnostic(code("Qsc.Circuit.ControlledUnsupported"))]
24 #[diagnostic(help(
25 "controlled invocations are not currently supported. consider wrapping the invocation in a lambda expression"
26 ))]
27 ControlledUnsupported,
28}
29
30pub struct QubitParam {
31 /// The number of array dimensions of the qubit input parameter.
32 /// `Qubit` is 0, `Qubit[]` is 1, `Qubit[][]` is 2, etc.
33 pub(crate) dimensions: u32,
34 /// The source offset of the parameter in the operation declaration.
35 pub(crate) source_offset: u32,
36}
37
38impl QubitParam {
39 /// The total number of qubit array elements for this input parameter.
40 #[must_use]
41 pub fn num_qubits(&self) -> u32 {
42 NUM_QUBITS.pow(self.dimensions)
43 }
44}
45
46/// If the item is a callable, returns the information that would
47/// be needed to generate a circuit for it.
48///
49/// If the item is not a callable, returns `None`.
50/// If the callable takes any non-qubit parameters, returns `None`.
51///
52/// If the callable only takes qubit parameters (including qubit arrays) or no parameters,
53/// returns the qubit parameter information.
54#[must_use]
55pub fn qubit_param_info(item: &Item) -> Option<Vec<QubitParam>> {
56 if let ItemKind::Callable(decl) = &item.kind {
57 if decl.input.ty == Ty::UNIT {
58 // Support no parameters by allocating 0 qubits.
59 return Some(vec![]);
60 }
61
62 let param_info = get_qubit_param_info(&decl.input);
63
64 if !param_info.is_empty() {
65 return Some(param_info);
66 }
67 }
68 None
69}
70
71/// Returns an entry expression to directly invoke the operation
72/// for the purposes of generating a circuit for it.
73///
74/// `operation_expr` is the source for the expression that refers to the operation,
75/// e.g. "Test.Foo" or "qs => H(qs[0])".
76///
77/// If the item is not a callable, returns `None`.
78/// If the callable takes any non-qubit parameters, returns `None`.
79pub fn entry_expr_for_qubit_operation(
80 item: &Item,
81 functor_app: qsc_data_structures::functors::FunctorApp,
82 operation_expr: &str,
83) -> Result<String, Error> {
84 if functor_app.controlled > 0 {
85 return Err(Error::ControlledUnsupported);
86 }
87
88 if let Some(param_info) = qubit_param_info(item) {
89 return Ok(operation_circuit_entry_expr(operation_expr, &param_info));
90 }
91
92 Err(Error::NoQubitParameters)
93}
94
95/// Generates the entry expression to call the operation described by `params`.
96/// The expression allocates qubits and invokes the operation.
97#[must_use]
98fn operation_circuit_entry_expr(operation_expr: &str, qubit_params: &[QubitParam]) -> String {
99 let alloc_qubits = format!(
100 "use qs = Qubit[{}];",
101 qubit_params.iter().map(QubitParam::num_qubits).sum::<u32>()
102 );
103
104 let mut qs_start = 0;
105 let mut call_args = vec![];
106 for q in qubit_params {
107 // Q# ranges are end-inclusive
108 let qs_end = qs_start + q.num_qubits() - 1;
109 if q.dimensions == 0 {
110 call_args.push(format!("qs[{qs_start}]"));
111 } else {
112 // Array argument - use a range to index
113 let mut call_arg = format!("qs[{qs_start}..{qs_end}]");
114 for _ in 1..q.dimensions {
115 // Chunk the array for multi-dimensional array arguments
116 call_arg = format!("Microsoft.Quantum.Arrays.Chunks({NUM_QUBITS}, {call_arg})");
117 }
118 call_args.push(call_arg);
119 }
120 qs_start = qs_end + 1;
121 }
122
123 let call_args = call_args.join(", ");
124
125 // We don't reset the qubits since we don't want reset gates
126 // included in circuit output.
127 // We also don't measure the qubits but we have to return a result
128 // array to satisfy Base Profile.
129 format!(
130 r#"{{
131 {alloc_qubits}
132 ({operation_expr})({call_args});
133 let r: Result[] = [];
134 r
135 }}"#
136 )
137}
138
139/// The number of qubits to allocate for each qubit array
140/// in the operation arguments.
141const NUM_QUBITS: u32 = 2;
142
143fn get_qubit_param_info(input: &Pat) -> Vec<QubitParam> {
144 match &input.ty {
145 Ty::Prim(Prim::Qubit) => {
146 return vec![QubitParam {
147 dimensions: 0,
148 source_offset: input.span.lo,
149 }];
150 }
151 Ty::Array(ty) => {
152 if let Some(element_dim) = get_array_dimension(ty) {
153 let dim = element_dim + 1;
154 return vec![QubitParam {
155 dimensions: dim,
156 source_offset: input.span.lo,
157 }];
158 }
159 }
160 Ty::Tuple(tys) => {
161 let params = if let PatKind::Tuple(pats) = &input.kind {
162 pats.iter()
163 .map(|p| {
164 get_array_dimension(&p.ty).map(|dimension| QubitParam {
165 dimensions: dimension,
166 source_offset: p.span.lo,
167 })
168 })
169 .collect::<Vec<_>>()
170 } else {
171 tys.iter()
172 .map(|ty| {
173 get_array_dimension(ty).map(|dimension| QubitParam {
174 dimensions: dimension,
175 source_offset: input.span.lo,
176 })
177 })
178 .collect::<Vec<_>>()
179 };
180
181 if params.iter().all(Option::is_some) {
182 return params.into_iter().map(Option::unwrap).fold(
183 vec![],
184 |mut param_info, param| {
185 param_info.push(param);
186 param_info
187 },
188 );
189 }
190 }
191 _ => {}
192 }
193 vec![]
194}
195
196/// If `Ty` is a qubit or a qubit array, returns the number of dimensions of the array.
197/// A qubit is considered to be a 0-dimensional array.
198/// For example, for a `Qubit` it returns `Some(0)`, for a `Qubit[][]` it returns `Some(2)`.
199/// For a non-qubit type, returns `None`.
200fn get_array_dimension(input: &Ty) -> Option<u32> {
201 match input {
202 Ty::Prim(Prim::Qubit) => Some(0),
203 Ty::Array(ty) => get_array_dimension(ty).map(|d| d + 1),
204 _ => None,
205 }
206}
207