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

242lines · modecode

1use std::vec;
2
3use regex_lite::Regex;
4use serde::de::Error as DeError;
5use serde_json::{Error, Map, Value, from_str, from_value};
6
7use crate::{
8 CURRENT_VERSION, Circuit, CircuitGroup, Operation,
9 circuit::{Qubit, operation_list_to_grid},
10};
11
12/// Parses a JSON string into a `CircuitGroup`.
13///
14/// This function attempts to parse the provided JSON string into a `CircuitGroup` object.
15/// If the JSON is in a legacy format, it will attempt to upgrade the schema to the current format.
16///
17/// # Arguments
18/// * `json_str` - A string slice containing the JSON representation of the circuit group or legacy circuit.
19///
20/// # Returns
21/// * `Ok(CircuitGroup)` if the JSON is successfully parsed and upgraded (if necessary).
22/// * `Err(Error)` if the JSON is invalid or does not match any known schema.
23///
24/// # Errors
25/// This function returns an error if:
26/// * The JSON is not valid.
27/// * The JSON does not conform to the expected schema for a circuit group or legacy circuit.
28pub fn json_to_circuits(json_str: &str) -> Result<CircuitGroup, String> {
29 {
30 let parsed_value: Value = from_str(json_str).map_err(|e| format!("Error: {e}"))?;
31 if let Value::Object(map) = parsed_value {
32 to_circuit_group(map)
33 } else {
34 Err(Error::custom("Invalid JSON format"))
35 }
36 }
37 .map_err(|e| format!("Error: {e}"))
38}
39
40fn to_circuit_group(mut json: Map<String, Value>) -> Result<CircuitGroup, Error> {
41 let empty_circuit = Circuit {
42 qubits: vec![],
43 component_grid: vec![],
44 };
45
46 let empty_circuit_group = CircuitGroup {
47 version: CURRENT_VERSION,
48 circuits: vec![empty_circuit.clone()],
49 };
50
51 if json.is_empty() {
52 return Ok(empty_circuit_group);
53 }
54
55 if let Some(version) = json.get("version") {
56 // If it has a "version" field, it is up-to-date
57 if is_circuit_group(&json) {
58 return from_value(Value::Object(json));
59 } else if is_circuit(&json) {
60 return Ok(CircuitGroup {
61 version: from_value(version.clone())?,
62 circuits: vec![from_value(Value::Object(json))?],
63 });
64 }
65 return Err(DeError::custom(
66 "Unknown schema: circuit is neither a CircuitGroup nor a Circuit.",
67 ));
68 } else if is_circuit(&json) {
69 // If it's a Circuit without a version, wrap it in a CircuitGroup
70 return Ok(CircuitGroup {
71 version: CURRENT_VERSION,
72 circuits: vec![from_value(Value::Object(json))?],
73 });
74 } else if json.contains_key("operations") {
75 // Legacy schema: convert to CircuitGroup
76 if !json.contains_key("qubits") || !json["qubits"].is_array() {
77 return Err(DeError::custom(
78 "Unknown schema: circuit is missing qubit information.",
79 ));
80 }
81 if !json.contains_key("operations") || !json["operations"].is_array() {
82 return Err(DeError::custom(
83 "Unknown schema: circuit is missing operation information.",
84 ));
85 }
86
87 let qubits: Vec<Qubit> = if let Some(qubits) = json.get("qubits").and_then(Value::as_array)
88 {
89 qubits
90 .iter()
91 .map(|qubit| {
92 Ok(Qubit {
93 id: usize::try_from(qubit.get("id").and_then(Value::as_u64).ok_or_else(
94 || Error::custom("Expected 'id' field to exist and be a number"),
95 )?)
96 .map_err(|_| Error::custom("Value of 'id' is out of range"))?,
97 num_results: usize::try_from(
98 qubit
99 .get("numChildren")
100 .and_then(Value::as_u64)
101 .unwrap_or(0),
102 )
103 .map_err(|_| Error::custom("Value of 'numChildren' is out of range"))?,
104 declarations: vec![],
105 })
106 })
107 .collect::<Result<Vec<Qubit>, Error>>()?
108 } else {
109 unreachable!("We checked that qubits exists");
110 };
111
112 let component_grid =
113 if let Some(operations) = json.get_mut("operations").and_then(Value::as_array_mut) {
114 // Process each operation in place
115 operations.iter_mut().for_each(to_operation);
116
117 // Convert the processed operations into the `Operation` type
118 let operation_list: Vec<Operation> = operations
119 .drain(..)
120 .map(from_value)
121 .collect::<Result<Vec<Operation>, Error>>()?;
122
123 operation_list_to_grid(operation_list, qubits.len())
124 } else {
125 unreachable!("We checked that operations exists");
126 };
127
128 return Ok(CircuitGroup {
129 version: CURRENT_VERSION,
130 circuits: vec![Circuit {
131 qubits,
132 component_grid,
133 }],
134 });
135 }
136 Err(DeError::custom(
137 "Unknown schema: circuit does not match any known format.",
138 ))
139}
140
141fn is_circuit_group(json: &Map<String, Value>) -> bool {
142 json.contains_key("circuits") && json["circuits"].is_array()
143}
144
145fn is_circuit(json: &Map<String, Value>) -> bool {
146 json.contains_key("qubits")
147 && json["qubits"].is_array()
148 && json.contains_key("componentGrid")
149 && json["componentGrid"].is_array()
150}
151
152fn map_register_field(field: Option<&Value>) -> Vec<Value> {
153 field
154 .and_then(|f| {
155 f.as_array().map(|array| {
156 array
157 .iter()
158 .map(|item| {
159 let mut register = Map::new();
160 if let Some(c_id) = item.get("cId") {
161 register.insert("result".to_string(), c_id.clone());
162 }
163 // Note: if "qId" is missing, the json deserialization later on
164 // will fail and produce the appropriate error
165 if let Some(q_id) = item.get("qId") {
166 register.insert("qubit".to_string(), q_id.clone());
167 }
168 Value::Object(register)
169 })
170 .collect()
171 })
172 })
173 .unwrap_or_default()
174}
175
176fn to_operation(op: &mut Value) {
177 let Value::Object(op) = op else {
178 panic!("Expected an object for operation, but got something else");
179 };
180
181 let targets = map_register_field(op.get("targets"));
182 let controls = map_register_field(op.get("controls"));
183
184 if let Some(children) = op.get_mut("children")
185 && let Some(children_array) = children.as_array_mut()
186 {
187 children_array.iter_mut().for_each(to_operation);
188 let component_column = serde_json::json!({
189 "components": children_array
190 });
191 op.insert("children".to_string(), Value::Array(vec![component_column]));
192 }
193
194 if let Some(display_args) = op.get("displayArgs") {
195 op.insert("args".to_string(), Value::Array(vec![display_args.clone()]));
196 // Assume the parameter is always "theta" for now
197 let mut param = Map::new();
198 param.insert("name".to_string(), Value::String("theta".to_string()));
199 param.insert("type".to_string(), Value::String("Double".to_string()));
200 op.insert(
201 "params".to_string(),
202 Value::Array(vec![Value::Object(param)]),
203 );
204 }
205
206 if op
207 .get("isMeasurement")
208 .and_then(Value::as_bool)
209 .unwrap_or(false)
210 {
211 op.insert("kind".to_string(), Value::String("measurement".to_string()));
212 op.insert("qubits".to_string(), Value::Array(controls));
213 op.insert("results".to_string(), Value::Array(targets));
214 } else if let Some(label) = get_ket_label(op) {
215 op.insert("kind".to_string(), Value::String("ket".to_string()));
216 op.insert("gate".to_string(), Value::String(label));
217 op.insert("targets".to_string(), Value::Array(targets));
218 } else {
219 op.insert("kind".to_string(), Value::String("unitary".to_string()));
220 op.insert("targets".to_string(), Value::Array(targets));
221 op.insert("controls".to_string(), Value::Array(controls));
222 }
223}
224
225fn get_ket_label(op: &Map<String, Value>) -> Option<String> {
226 // Check if the "gate" field exists and is a string
227 if let Some(gate) = op.get("gate").and_then(Value::as_str) {
228 // Define the regex for matching the ket pattern of |{label}> or |{label}⟩
229 #[allow(clippy::unicode_not_nfc)]
230 let ket_regex =
231 Regex::new(r"^\|([^\s〉⟩〉>]+)(?:[〉⟩〉>])$").expect("Invalid regex pattern");
232
233 // Match the string against the regex
234 if let Some(captures) = ket_regex.captures(gate) {
235 // If valid, return the inner label (captured group 1)
236 return captures.get(1).map(|m| m.as_str().to_string());
237 }
238 }
239
240 // Return None if the "gate" field is missing, not a string, or doesn't match the pattern
241 None
242}
243