1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use std::collections::HashMap;
use std::convert::TryFrom;

use serde::{Deserialize, Serialize};

use edge::{convert_edges, Edge, Id};
use qcs_api_client_openapi::models::InstructionSetArchitecture;
use qubit::{FrbSim1q, Qubit};

mod edge;
mod operator;
mod qubit;

/// Restructuring of an [`InstructionSetArchitecture`] for sending to quilc
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct Compiler {
    #[serde(rename = "1Q")]
    qubits: HashMap<String, Qubit>,
    #[serde(rename = "2Q")]
    edges: HashMap<String, Edge>,
}

impl TryFrom<InstructionSetArchitecture> for Compiler {
    type Error = Error;

    fn try_from(isa: InstructionSetArchitecture) -> Result<Self, Error> {
        let architecture = isa.architecture;
        let mut qubits = Qubit::from_nodes(&architecture.nodes);

        let mut edges = convert_edges(&architecture.edges)?;

        let site_ops = isa
            .instructions
            .iter()
            .flat_map(|op| op.sites.iter().map(move |site| (op, site)));
        let frb_sim_1q = FrbSim1q::try_from(isa.benchmarks)?;

        for (op, site) in site_ops {
            match (&op.node_count, &site.node_ids.len()) {
                (Some(1), 1) => {
                    let id = &site.node_ids[0];
                    let qubit = qubits
                        .get_mut(id)
                        .ok_or_else(|| Error::QubitDoesNotExist(String::from(&op.name), *id))?;
                    qubit.add_operation(&op.name, &site.characteristics, &frb_sim_1q)?;
                }
                (Some(2), 2) => {
                    let id = Id::try_from(&site.node_ids)?;
                    let edge = edges
                        .get_mut(&id)
                        .ok_or_else(|| Error::EdgeDoesNotExist(String::from(&op.name), id))?;
                    edge.add_operation(&op.name, &site.characteristics)?;
                }
                (node_count, node_ids) => {
                    return Err(Error::IncorrectNodes(
                        (*node_count, *node_ids),
                        String::from(&op.name),
                        site.node_ids.clone(),
                    ))
                }
            }
        }

        let qubits = qubits
            .into_iter()
            .map(|(k, v)| (k.to_string(), v))
            .collect();
        let edges = edges.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
        Ok(Self { qubits, edges })
    }
}

/// All the errors that can occur from within this module
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Operation {0} is defined for Qubit {1} but that Qubit does not exist")]
    QubitDoesNotExist(String, i64),
    #[error("Operation {0} is defined for Edge {1} but that Edge does not exist")]
    EdgeDoesNotExist(String, Id),
    #[error(
        "The number of nodes for an operation and site_operation must be (1, 1) or (2, 2). \
                        Got {0:?} while parsing operation {1} at site {2:?}"
    )]
    IncorrectNodes((Option<i64>, usize), String, Vec<i64>),
    #[error(transparent)]
    Qubit(#[from] qubit::Error),
    #[error(transparent)]
    Edge(#[from] edge::Error),
}

#[cfg(test)]
mod describe_compiler_isa {
    use std::{convert::TryFrom, fs::read_to_string};

    use float_cmp::{approx_eq, F64Margin};
    use qcs_api_client_openapi::models::InstructionSetArchitecture;
    use serde_json::Value;

    use super::Compiler;

    /// Compare two JSON values and make sure they are equivalent while allowing for some precision
    /// loss in numbers.
    ///
    /// Return Ok if equivalent, or tuple containing the differing elements.
    fn json_is_equivalent<'a>(
        first: &'a Value,
        second: &'a Value,
    ) -> Result<(), (&'a Value, &'a Value)> {
        let equal = match (first, second) {
            (Value::Number(first_num), Value::Number(second_num)) => {
                if !first_num.is_f64() || !second_num.is_f64() {
                    first_num == second_num
                } else {
                    let first_f64 = first_num.as_f64().unwrap();
                    let second_f64 = second_num.as_f64().unwrap();
                    approx_eq!(
                        f64,
                        first_f64,
                        second_f64,
                        F64Margin {
                            ulps: 1,
                            epsilon: 0.000_000_1
                        }
                    )
                }
            }
            (Value::Object(first_map), Value::Object(second_map)) => {
                let mut found_missing = false;
                for (key, first_value) in first_map {
                    let second_value = second_map.get(key);
                    if second_value.is_none() {
                        found_missing = true;
                        break;
                    }
                    let cmp = json_is_equivalent(first_value, second_value.unwrap());
                    cmp?;
                }
                !found_missing
            }
            (Value::Array(first_array), Value::Array(second_array))
                if first_array.len() != second_array.len() =>
            {
                false
            }
            (Value::Array(first_array), Value::Array(second_array)) => {
                let error = first_array.iter().zip(second_array).find(
                    |(first_value, second_value)| -> bool {
                        json_is_equivalent(first_value, second_value).is_err()
                    },
                );
                if let Some(values) = error {
                    return Err(values);
                }
                true
            }
            (first, second) => first == second,
        };
        if equal {
            Ok(())
        } else {
            Err((first, second))
        }
    }

    #[test]
    fn it_correctly_converts_aspen_8() {
        let input = read_to_string("tests/qcs-isa-Aspen-8.json")
            .expect("Could not read Aspen 8 input data");
        let expected_json = read_to_string("tests/compiler-isa-Aspen-8.json")
            .expect("Could not read Aspen 8 output data");
        let qcs_isa: InstructionSetArchitecture =
            serde_json::from_str(&input).expect("Could not deserialize Aspen-8 input");
        let expected: Value =
            serde_json::from_str(&expected_json).expect("Could not deserialize Aspen-8 output");

        let compiler_isa =
            Compiler::try_from(qcs_isa).expect("Could not convert ISA to CompilerIsa");
        let serialized =
            serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa");

        let result = json_is_equivalent(&serialized, &expected);
        result.expect("JSON was not equivalent");
    }
}