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/circuit_to_qsharp.rs

352lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use regex_lite::{Captures, Regex};
8use rustc_hash::FxHashMap;
9use std::fmt::Write;
10
11use crate::{
12 Circuit, Operation,
13 circuit::{Ket, Measurement, Unitary},
14 json_to_circuit::json_to_circuits,
15};
16
17pub fn circuits_to_qsharp(file_name: &str, circuits_json: &str) -> Result<String, String> {
18 json_to_circuits(circuits_json).map(|circuits| build_circuits(file_name, &circuits.circuits))
19}
20
21fn build_circuits(file_name: &str, circuits: &[Circuit]) -> String {
22 if circuits.len() == 1 {
23 build_operation_def(file_name, &circuits[0])
24 } else {
25 let mut qsharp_str = String::new();
26 for (index, circuit) in circuits.iter().enumerate() {
27 let circuit_name = format!("{file_name}{index}");
28 let circuit_str = build_operation_def(&circuit_name, circuit);
29 qsharp_str.push_str(&circuit_str);
30 }
31 qsharp_str
32 }
33}
34
35fn build_operation_def(circuit_name: &str, circuit: &Circuit) -> String {
36 let mut indentation_level = 0;
37 let qubits = circuit
38 .qubits
39 .iter()
40 .enumerate()
41 .map(|(i, q)| (q.id, format!("qs[{i}]")))
42 .collect::<FxHashMap<_, _>>();
43
44 let parameter = if qubits.is_empty() {
45 String::new()
46 } else {
47 "qs : Qubit[]".to_string()
48 };
49
50 // The return type is determined by the number of qubits "children".
51 // However, the actual return statement is determined by the variables storing measurements.
52 // If there is an inconsistency between these, which would happen if there was a mismatch between
53 // the number of qubit children specified on the circuit and the number of qubit children specified
54 // on the measurements, incorrect Q# could be generated.
55 let return_type = match circuit.qubits.iter().fold(0, |sum, q| sum + q.num_results) {
56 0 => "Unit",
57 1 => "Result",
58 _ => "Result[]",
59 };
60
61 // Check if all operations are Unitary
62 let is_ctl_adj = !circuit.component_grid.iter().any(|col| {
63 col.components
64 .iter()
65 .any(|op| !matches!(op, Operation::Unitary(_)))
66 });
67
68 let characteristics = if is_ctl_adj { "is Ctl + Adj " } else { "" };
69 let summary = if qubits.is_empty() {
70 String::new()
71 } else {
72 format!(
73 "/// Expects a qubit register of at least {} qubits.\n",
74 qubits.len()
75 )
76 };
77
78 let mut qsharp_str = format!(
79 "{summary}operation {circuit_name}({parameter}) : {return_type} {characteristics}{{\n"
80 );
81 indentation_level += 1;
82
83 let mut measure_results = vec![];
84 let indent = " ".repeat(indentation_level);
85
86 // If there are operation, add an assert for the number of qubits
87 if !circuit.component_grid.is_empty()
88 && circuit
89 .component_grid
90 .iter()
91 .any(|col| !col.components.is_empty())
92 {
93 qsharp_str.push_str(&generate_qubit_validation(
94 circuit_name,
95 &qubits,
96 indentation_level,
97 ));
98 }
99
100 let mut body_str = String::new();
101 let mut should_add_pi = false;
102
103 // Note: In the future we may want to add support for children operations
104 for col in &circuit.component_grid {
105 for op in &col.components {
106 match &op {
107 Operation::Measurement(measurement) => {
108 body_str.push_str(
109 generate_measurement_call(
110 measurement,
111 &qubits,
112 &indent,
113 &mut measure_results,
114 )
115 .as_str(),
116 );
117 }
118 Operation::Unitary(unitary) => {
119 body_str.push_str(generate_unitary_call(unitary, &qubits, &indent).as_str());
120 }
121 Operation::Ket(ket) => {
122 body_str.push_str(generate_ket_call(ket, &qubits, &indent).as_str());
123 }
124 }
125
126 // Look for a "π" in the args
127 let args = op.args();
128 if !should_add_pi && !args.is_empty() {
129 should_add_pi = args.iter().any(|arg| arg.contains("π"));
130 }
131 }
132 }
133
134 if should_add_pi {
135 // Add the π constant
136 writeln!(qsharp_str, "{indent}let π = Std.Math.PI();")
137 .expect("could not write to qsharp_str");
138 }
139
140 qsharp_str.push_str(body_str.as_str());
141 qsharp_str.push_str(&generate_return_statement(&mut measure_results, &indent));
142 qsharp_str.push_str("}\n\n");
143 qsharp_str
144}
145
146fn generate_qubit_validation(
147 circuit_name: &str,
148 qubits: &FxHashMap<usize, String>,
149 indentation_level: usize,
150) -> String {
151 if qubits.is_empty() {
152 return String::new();
153 }
154
155 let indent = " ".repeat(indentation_level);
156 let inner_indent = " ".repeat(indentation_level + 1);
157 format!(
158 "{indent}if Length(qs) < {} {{\n\
159 {inner_indent}fail \"Invalid number of qubits. Operation {circuit_name} expects a qubit register of at least {} qubits.\";\n\
160 {indent}}}\n",
161 qubits.len(),
162 qubits.len()
163 )
164}
165
166fn generate_measurement_call(
167 measurement: &Measurement,
168 qubits: &FxHashMap<usize, String>,
169 indent: &str,
170 measure_results: &mut Vec<(String, (usize, usize))>,
171) -> String {
172 let operation_str = measurement_call(measurement, qubits);
173 let mut op_results = vec![];
174 for reg in &measurement.results {
175 if let Some(c_id) = reg.result {
176 let result = (format!("c{}_{}", reg.qubit, c_id), (reg.qubit, c_id));
177 op_results.push(result.clone());
178 }
179 }
180
181 // Sort first by q_id, then by c_id
182 op_results.sort_by_key(|(_, (q_id, c_id))| (*q_id, *c_id));
183 let result = op_results
184 .iter()
185 .map(|(name, _)| name.as_str())
186 .collect::<Vec<_>>()
187 .join(", ");
188 match op_results.len() {
189 0 => {
190 format!("{indent}{operation_str};\n")
191 }
192 1 => {
193 measure_results.extend(op_results);
194 format!("{indent}let {result} = {operation_str};\n")
195 }
196 _ => {
197 measure_results.extend(op_results);
198 format!("{indent}let ({result}) = {operation_str};\n")
199 }
200 }
201}
202
203fn generate_unitary_call(
204 unitary: &Unitary,
205 qubits: &FxHashMap<usize, String>,
206 indent: &str,
207) -> String {
208 let operation_str = operation_call(unitary, qubits);
209 format!("{indent}{operation_str};\n")
210}
211
212fn generate_ket_call(ket: &Ket, qubits: &FxHashMap<usize, String>, indent: &str) -> String {
213 // Note: The only supported ket operation is "0"
214 if ket.gate == "0" {
215 let ket_str = ket_call(ket, qubits);
216 format!("{indent}{ket_str};\n")
217 } else {
218 format!(
219 "{indent}fail \"Unsupported ket operation: |{}〉\";\n",
220 ket.gate
221 )
222 }
223}
224
225fn generate_return_statement(
226 measure_results: &mut [(String, (usize, usize))],
227 indent: &str,
228) -> String {
229 if measure_results.is_empty() {
230 return String::new();
231 }
232
233 measure_results.sort_by_key(|(_, (q_id, c_id))| (*q_id, *c_id));
234 if measure_results.len() == 1 {
235 let (name, _) = measure_results[0].clone();
236 format!("{indent}return {name};\n")
237 } else {
238 let results = measure_results
239 .iter()
240 .map(|(name, _)| name.as_str())
241 .collect::<Vec<_>>()
242 .join(", ");
243 format!("{indent}return [{results}];\n")
244 }
245}
246
247fn get_qubit_name(qubits: &FxHashMap<usize, String>, q_id: usize) -> String {
248 qubits
249 .get(&q_id)
250 .unwrap_or_else(|| panic!("Qubit with {q_id} not found"))
251 .clone()
252}
253
254fn measurement_call(measurement: &Measurement, qubits: &FxHashMap<usize, String>) -> String {
255 let args = measurement
256 .qubits
257 .iter()
258 .map(|q| get_qubit_name(qubits, q.qubit))
259 .collect::<Vec<_>>();
260 let args_count = args.len();
261
262 let args = args.join(", ");
263 if args_count == 1 {
264 format!("M({args})")
265 } else {
266 // This is a joint measurement operation.
267 // For now, assume PauliZ measurement basis for all measurements.
268 let bases = vec!["PauliZ"; args_count].join(", ");
269 format!("Measure([{bases}], [{args}])")
270 }
271}
272
273fn ket_call(ket: &Ket, qubits: &FxHashMap<usize, String>) -> String {
274 // Note: The only supported ket operation is "0" which is a reset operation
275 let targets = ket
276 .targets
277 .iter()
278 .map(|q| get_qubit_name(qubits, q.qubit))
279 .collect::<Vec<_>>();
280 let args = targets.join(", ");
281 format!("Reset({args})")
282}
283
284fn operation_call(unitary: &Unitary, qubits: &FxHashMap<usize, String>) -> String {
285 let gate = unitary.gate.as_str();
286
287 let is_controlled = !unitary.controls.is_empty();
288
289 let functors = if is_controlled && unitary.is_adjoint {
290 "Controlled Adjoint "
291 } else if is_controlled {
292 "Controlled "
293 } else if unitary.is_adjoint {
294 "Adjoint "
295 } else {
296 ""
297 };
298
299 let mut args = vec![];
300
301 // Create the regex for matching numbers (both integers and doubles)
302 let number_regex = Regex::new(r"((\d+(\.\d*)?)|(\.\d+))").expect("Regex should compile");
303
304 // Convert ints to doubles by appending a `.` to the end of the integer
305 for arg in &unitary.args {
306 // Replace all numbers in the string
307 let updated_arg = number_regex
308 .replace_all(arg, |caps: &Captures| {
309 let number = &caps[0]; // The matched number
310 if number.contains('.') {
311 number.to_string() // If it's already a double, leave it unchanged
312 } else {
313 format!("{number}.") // If it's an integer, append a `.`
314 }
315 })
316 .to_string();
317
318 args.push(updated_arg);
319 }
320
321 let targets = unitary
322 .targets
323 .iter()
324 .map(|t| get_qubit_name(qubits, t.qubit))
325 .collect::<Vec<_>>();
326 args.extend(targets);
327
328 if is_controlled {
329 let controls = unitary
330 .controls
331 .iter()
332 .filter_map(|c| {
333 if c.result.is_none() {
334 Some(get_qubit_name(qubits, c.qubit))
335 } else {
336 None
337 }
338 })
339 .collect::<Vec<_>>()
340 .join(", ");
341 let controls = format!("[{controls}]");
342 let args_count = args.len();
343 let mut inner_args = args.join(", ");
344 if args_count != 1 {
345 inner_args = format!("({inner_args})");
346 }
347 args = vec![controls, inner_args];
348 }
349
350 let args = args.join(", ");
351 format!("{functors}{gate}({args})")
352}
353