Skip to main content

qcs/compiler/isa/
mod.rs

1use std::collections::HashMap;
2use std::convert::TryFrom;
3
4use serde::{Deserialize, Serialize};
5
6use edge::{convert_edges, Edge, Id};
7use qcs_api_client_openapi::models::InstructionSetArchitecture;
8use qubit::{FrbSim1q, Qubit};
9
10mod edge;
11mod operator;
12mod qubit;
13
14/// Restructuring of an [`InstructionSetArchitecture`] for sending to quilc
15#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
16pub(crate) struct Compiler {
17    #[serde(rename = "1Q")]
18    qubits: HashMap<String, Qubit>,
19    #[serde(rename = "2Q")]
20    edges: HashMap<String, Edge>,
21}
22
23impl TryFrom<InstructionSetArchitecture> for Compiler {
24    type Error = Error;
25
26    fn try_from(isa: InstructionSetArchitecture) -> Result<Self, Error> {
27        let architecture = isa.architecture;
28        let mut qubits = Qubit::from_nodes(&architecture.nodes);
29
30        let mut edges = convert_edges(&architecture.edges)?;
31
32        let site_ops = isa
33            .instructions
34            .iter()
35            .flat_map(|op| op.sites.iter().map(move |site| (op, site)));
36        let frb_sim_1q = FrbSim1q::try_from(isa.benchmarks)?;
37
38        for (op, site) in site_ops {
39            match (&op.node_count, &site.node_ids.len()) {
40                (Some(1), 1) => {
41                    let id = &site.node_ids[0];
42                    let qubit = qubits
43                        .get_mut(id)
44                        .ok_or_else(|| Error::QubitDoesNotExist(String::from(&op.name), *id))?;
45                    qubit.add_operation(&op.name, &site.characteristics, &frb_sim_1q)?;
46                }
47                (Some(2), 2) => {
48                    let id = Id::try_from(&site.node_ids)?;
49                    let edge = edges
50                        .get_mut(&id)
51                        .ok_or_else(|| Error::EdgeDoesNotExist(String::from(&op.name), id))?;
52                    edge.add_operation(&op.name, &site.characteristics)?;
53                }
54                (node_count, node_ids) => {
55                    return Err(Error::IncorrectNodes(
56                        (*node_count, *node_ids),
57                        String::from(&op.name),
58                        site.node_ids.clone(),
59                    ))
60                }
61            }
62        }
63
64        let qubits = qubits
65            .into_iter()
66            .map(|(k, v)| (k.to_string(), v))
67            .collect();
68        let edges = edges.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
69        Ok(Self { qubits, edges })
70    }
71}
72
73/// All the errors that can occur from within this module
74#[derive(Debug, thiserror::Error)]
75pub enum Error {
76    #[error("Operation {0} is defined for Qubit {1} but that Qubit does not exist")]
77    QubitDoesNotExist(String, i64),
78    #[error("Operation {0} is defined for Edge {1} but that Edge does not exist")]
79    EdgeDoesNotExist(String, Id),
80    #[error(
81        "The number of nodes for an operation and site_operation must be (1, 1) or (2, 2). \
82                        Got {0:?} while parsing operation {1} at site {2:?}"
83    )]
84    IncorrectNodes((Option<u64>, usize), String, Vec<i64>),
85    #[error(transparent)]
86    Qubit(#[from] qubit::Error),
87    #[error(transparent)]
88    Edge(#[from] edge::Error),
89}
90
91#[cfg(test)]
92mod describe_compiler_isa {
93    use std::{convert::TryFrom, fs::read_to_string};
94
95    use float_cmp::{approx_eq, F64Margin};
96    use qcs_api_client_openapi::models::InstructionSetArchitecture;
97    use serde_json::Value;
98
99    use super::Compiler;
100
101    /// Compare two JSON values and make sure they are equivalent while allowing for some precision
102    /// loss in numbers.
103    ///
104    /// Return Ok if equivalent, or tuple containing the differing elements.
105    fn json_is_equivalent<'a>(
106        first: &'a Value,
107        second: &'a Value,
108    ) -> Result<(), (&'a Value, &'a Value)> {
109        let equal = match (first, second) {
110            (Value::Number(first_num), Value::Number(second_num)) => {
111                if !first_num.is_f64() || !second_num.is_f64() {
112                    first_num == second_num
113                } else {
114                    let first_f64 = first_num.as_f64().unwrap();
115                    let second_f64 = second_num.as_f64().unwrap();
116                    approx_eq!(
117                        f64,
118                        first_f64,
119                        second_f64,
120                        F64Margin {
121                            ulps: 1,
122                            epsilon: 0.000_000_1
123                        }
124                    )
125                }
126            }
127            (Value::Object(first_map), Value::Object(second_map)) => {
128                let mut found_missing = false;
129                for (key, first_value) in first_map {
130                    let second_value = second_map.get(key);
131                    if second_value.is_none() {
132                        found_missing = true;
133                        break;
134                    }
135                    let cmp = json_is_equivalent(first_value, second_value.unwrap());
136                    cmp?;
137                }
138                !found_missing
139            }
140            (Value::Array(first_array), Value::Array(second_array))
141                if first_array.len() != second_array.len() =>
142            {
143                false
144            }
145            (Value::Array(first_array), Value::Array(second_array)) => {
146                let error = first_array.iter().zip(second_array).find(
147                    |(first_value, second_value)| -> bool {
148                        json_is_equivalent(first_value, second_value).is_err()
149                    },
150                );
151                if let Some(values) = error {
152                    return Err(values);
153                }
154                true
155            }
156            (first, second) => first == second,
157        };
158        if equal {
159            Ok(())
160        } else {
161            Err((first, second))
162        }
163    }
164
165    #[test]
166    fn it_correctly_converts_aspen_8() {
167        let input = read_to_string("tests/qcs-isa-Aspen-8.json")
168            .expect("Could not read Aspen 8 input data");
169        let expected_json = read_to_string("tests/compiler-isa-Aspen-8.json")
170            .expect("Could not read Aspen 8 output data");
171        let qcs_isa: InstructionSetArchitecture =
172            serde_json::from_str(&input).expect("Could not deserialize Aspen-8 input");
173        let expected: Value =
174            serde_json::from_str(&expected_json).expect("Could not deserialize Aspen-8 output");
175
176        let compiler_isa =
177            Compiler::try_from(qcs_isa).expect("Could not convert ISA to CompilerIsa");
178        let serialized =
179            serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa");
180
181        let result = json_is_equivalent(&serialized, &expected);
182        result.expect("JSON was not equivalent");
183    }
184}