microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/bounds

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_circuit/src/operations.rs

176lines · 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},
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
30/// If the item is a callable, returns the information that would
31/// be needed to generate a circuit for it.
32///
33/// If the item is not a callable, returns `None`.
34/// If the callable takes any non-qubit parameters, returns `None`.
35///
36/// If the callable only takes qubit parameters, (including qubit arrays):
37///
38/// The first element of the return tuple is a vector,
39/// where each element corresponds to a parameter, and the
40/// value is the number of dimensions of the parameter.
41///
42/// For example, for input parameters
43/// `(Qubit, Qubit[][], Qubit[])` the parameter info is `vec![0, 2, 1]`.
44///
45/// The second element of the return tuple is the total number of qubits that would
46/// need be allocated to run this operation for the purposes of circuit generation.
47#[must_use]
48pub fn qubit_param_info(item: &Item) -> Option<(Vec<u32>, u32)> {
49 if let ItemKind::Callable(decl) = &item.kind {
50 let (qubit_param_dimensions, total_num_qubits) = get_qubit_param_info(&decl.input.ty);
51
52 if !qubit_param_dimensions.is_empty() {
53 return Some((qubit_param_dimensions, total_num_qubits));
54 }
55 }
56 None
57}
58
59/// Returns an entry expression to directly invoke the operation
60/// for the purposes of generating a circuit for it.
61///
62/// `operation_expr` is the source for the expression that refers to the operation,
63/// e.g. "Test.Foo" or "qs => H(qs[0])".
64///
65/// If the item is not a callable, returns `None`.
66/// If the callable takes any non-qubit parameters, returns `None`.
67pub fn entry_expr_for_qubit_operation(
68 item: &Item,
69 functor_app: qsc_data_structures::functors::FunctorApp,
70 operation_expr: &str,
71) -> Result<String, Error> {
72 if functor_app.controlled > 0 {
73 return Err(Error::ControlledUnsupported);
74 }
75
76 if let Some((qubit_param_dimensions, total_num_qubits)) = qubit_param_info(item) {
77 return Ok(operation_circuit_entry_expr(
78 operation_expr,
79 &qubit_param_dimensions,
80 total_num_qubits,
81 ));
82 }
83
84 Err(Error::NoQubitParameters)
85}
86
87/// Generates the entry expression to call the operation described by `params`.
88/// The expression allocates qubits and invokes the operation.
89#[must_use]
90fn operation_circuit_entry_expr(
91 operation_expr: &str,
92 qubit_param_dimensions: &[u32],
93 total_num_qubits: u32,
94) -> String {
95 let alloc_qubits = format!("use qs = Qubit[{total_num_qubits}];");
96
97 let mut qs_start = 0;
98 let mut call_args = vec![];
99 for dim in qubit_param_dimensions {
100 let dim = *dim;
101 let qs_len = NUM_QUBITS.pow(dim);
102 // Q# ranges are end-inclusive
103 let qs_end = qs_start + qs_len - 1;
104 if dim == 0 {
105 call_args.push(format!("qs[{qs_start}]"));
106 } else {
107 // Array argument - use a range to index
108 let mut call_arg = format!("qs[{qs_start}..{qs_end}]");
109 for _ in 1..dim {
110 // Chunk the array for multi-dimensional array arguments
111 call_arg = format!("Microsoft.Quantum.Arrays.Chunks({NUM_QUBITS}, {call_arg})");
112 }
113 call_args.push(call_arg);
114 }
115 qs_start = qs_end + 1;
116 }
117
118 let call_args = call_args.join(", ");
119
120 // We don't reset the qubits since we don't want reset gates
121 // included in circuit output.
122 // We also don't measure the qubits but we have to return a result
123 // array to satisfy Base Profile.
124 format!(
125 r#"{{
126 {alloc_qubits}
127 ({operation_expr})({call_args});
128 let r: Result[] = [];
129 r
130 }}"#
131 )
132}
133
134/// The number of qubits to allocate for each qubit array
135/// in the operation arguments.
136const NUM_QUBITS: u32 = 2;
137
138fn get_qubit_param_info(input: &Ty) -> (Vec<u32>, u32) {
139 match input {
140 Ty::Prim(Prim::Qubit) => return (vec![0], 1),
141 Ty::Array(ty) => {
142 if let Some(element_dim) = get_array_dimension(ty) {
143 let dim = element_dim + 1;
144 return (vec![dim], NUM_QUBITS.pow(dim));
145 }
146 }
147 Ty::Tuple(tys) => {
148 let params = tys.iter().map(get_array_dimension).collect::<Vec<_>>();
149
150 if params.iter().all(Option::is_some) {
151 return params.into_iter().map(Option::unwrap).fold(
152 (vec![], 0),
153 |(mut dims, mut total_qubits), dim| {
154 dims.push(dim);
155 total_qubits += NUM_QUBITS.pow(dim);
156 (dims, total_qubits)
157 },
158 );
159 }
160 }
161 _ => {}
162 }
163 (vec![], 0)
164}
165
166/// If `Ty` is a qubit or a qubit array, returns the number of dimensions of the array.
167/// A qubit is considered to be a 0-dimensional array.
168/// For example, for a `Qubit` it returns `Some(0)`, for a `Qubit[][]` it returns `Some(2)`.
169/// For a non-qubit type, returns `None`.
170fn get_array_dimension(input: &Ty) -> Option<u32> {
171 match input {
172 Ty::Prim(Prim::Qubit) => Some(0),
173 Ty::Array(ty) => get_array_dimension(ty).map(|d| d + 1),
174 _ => None,
175 }
176}
177