microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/compiler/qsc_circuit/src/json_to_circuit.rs
242lines · modecode
| 1 | use std::vec; |
| 2 | |
| 3 | use regex_lite::Regex; |
| 4 | use serde::de::Error as DeError; |
| 5 | use serde_json::{Error, Map, Value, from_str, from_value}; |
| 6 | |
| 7 | use 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. |
| 28 | pub 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 | |
| 40 | fn 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 | |
| 141 | fn is_circuit_group(json: &Map<String, Value>) -> bool { |
| 142 | json.contains_key("circuits") && json["circuits"].is_array() |
| 143 | } |
| 144 | |
| 145 | fn 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 | |
| 152 | fn 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 | |
| 176 | fn 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 | |
| 225 | fn 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 | |