Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: #521: exclude isa dead qubits from quilc ISA #522

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/lib/src/compiler/isa/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ impl Edge {

Ok(())
}

pub(crate) fn has_valid_operations(&self) -> bool {
!self.dead
}

pub(crate) fn qubits(&self) -> impl Iterator<Item = &i64> + '_ {
self.id.0.iter()
}
}

/// All the error which can occur from within this module.
Expand Down
161 changes: 110 additions & 51 deletions crates/lib/src/compiler/isa/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;

use itertools::{Either, Itertools};
use serde::{Deserialize, Serialize};

use edge::{convert_edges, Edge, Id};
Expand Down Expand Up @@ -61,11 +62,20 @@ impl TryFrom<InstructionSetArchitecture> for Compiler {
}
}

let qubits = qubits
let (qubits, dead_qubits): (_, HashSet<_>) = qubits
.into_iter()
.partition_map(|(id, q)| {
if q.has_valid_operations() {
Either::Left((id.to_string(), q))
} else {
Either::Right(id)
}
});
let edges = edges
.into_iter()
.filter(|(_, e)| e.has_valid_operations() && !e.qubits().any(|q| dead_qubits.contains(q)))
.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 })
}
}
Expand All @@ -90,76 +100,72 @@ pub enum Error {

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

use float_cmp::{approx_eq, F64Margin};
use qcs_api_client_openapi::models::InstructionSetArchitecture;
use qcs_api_client_openapi::models::{InstructionSetArchitecture, Node};
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
/// Panics if there is any inequality.
fn assert_json_is_equivalent<'a>(
expected: &'a Value,
actual: &'a Value,
) {
assert_json_is_equivalent_inner(expected, actual, "");
}

fn assert_json_is_equivalent_inner<'a>(
expected: &'a Value,
actual: &'a Value,
path: &str,
) {
match (expected, actual) {
(Value::Number(expected_num), Value::Number(actual_num)) => {
if !expected_num.is_f64() || !actual_num.is_f64() {
assert_eq!(expected_num, actual_num, "path '{}': non-f64 numeric inequality: expected: {}, actual: {}", path, expected_num, actual_num);
} else {
let first_f64 = first_num.as_f64().unwrap();
let second_f64 = second_num.as_f64().unwrap();
approx_eq!(
let expected_f64 = expected_num.as_f64().unwrap();
let actual_f64 = actual_num.as_f64().unwrap();
assert!(approx_eq!(
f64,
first_f64,
second_f64,
expected_f64,
actual_f64,
F64Margin {
ulps: 1,
epsilon: 0.000_000_1
}
)
), "path '{}': numeric inequality out of range: expected: {}, actual: {}", path, expected_f64, actual_f64);
}
}
(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;
(Value::Object(expected_map), Value::Object(actual_map)) => {

let mut expected_key_missing_from_actual = None;
for (key, expected_value) in expected_map {
let actual_value = actual_map.get(key);
if actual_value.is_none() {
expected_key_missing_from_actual = Some(key);
break;
}
let cmp = json_is_equivalent(first_value, second_value.unwrap());
cmp?;
assert_json_is_equivalent_inner(expected_value, actual_value.unwrap(), &(format!("{}.{}", path, key)));
}
assert!(expected_key_missing_from_actual.is_none(), "path '{}': expected map has key not in actual map: {}", path, expected_key_missing_from_actual.unwrap());
for (key, _) in actual_map {
assert!(expected_map.contains_key(key), "path '{}': actual map has key not in expected map: {}", path, key);
}
!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);
(Value::Array(expected_array), Value::Array(actual_array)) => {
assert!(expected_array.len() == actual_array.len(), "expected array has more elements than actual array");
for (index, (expected_value, actual_value)) in expected_array.iter().zip(actual_array).enumerate() {
assert_json_is_equivalent_inner(expected_value, actual_value, &(format!("{}[{}]", path, index)));
}
true
}
(first, second) => first == second,
(expected, actual) => assert_eq!(expected, actual, "path '{}': inequality: expected: {:?}, actual: {:?}", path, expected, actual),
};
if equal {
Ok(())
} else {
Err((first, second))
}
}

#[test]
Expand All @@ -175,10 +181,63 @@ mod describe_compiler_isa {

let compiler_isa =
Compiler::try_from(qcs_isa).expect("Could not convert ISA to CompilerIsa");

assert!(
!compiler_isa.edges.contains_key("31-32"),
"edge with Qubit 31 is excluded from the CompilerIsa"
);
assert!(
!compiler_isa.qubits.contains_key("31"),
"Qubit 31 is excluded from the CompilerIsa"
);

let serialized =
serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa");

assert_json_is_equivalent(&expected, &serialized);
}

#[test]
fn compiler_excludes_qubits_with_no_operations() {
let input = read_to_string("tests/qcs-isa-edges-without-gates.json")
.expect("Could not read ISA with edges without gates");
let qcs_isa: InstructionSetArchitecture =
serde_json::from_str(&input).expect("Could not deserialize ISA with edges without gates");

assert!(
qcs_isa.architecture.nodes.contains(&Node::new(31)),
"Qubit 31 is in the source ISA"
);
assert!(
qcs_isa.architecture.edges.iter().any(|e| e.node_ids.contains(&31)),
"edge with Qubit 31 is in the source ISA"
);

let compiler_isa = Compiler::try_from(qcs_isa)
.expect("Could not convert ISA with edges without gates to CompilerIsa");

assert!(
!compiler_isa.qubits.contains_key("31"),
"Qubit 31 should not be in the CompilerIsa"
);
assert!(
!compiler_isa.edges.contains_key("31-32"),
"edge with Qubit 31 should not be in the CompilerIsa"
);

let input_without_dead = read_to_string("tests/qcs-isa-excluding-dead-edges.json")
.expect("Could not read ISA that excludes dead edges");
let isa_without_dead: InstructionSetArchitecture =
serde_json::from_str(&input_without_dead)
.expect("Could not read ISA that excludes dead edges");
let compiler_isa_excluding_dead = Compiler::try_from(isa_without_dead)
.expect("Could not convert ISA with manually excluded dead edges to CompilerIsa");

let serialized =
serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa");
let serialized_without_dead = serde_json::to_value(compiler_isa_excluding_dead)
.expect("Unable to serialize CompilerIsa");

let result = json_is_equivalent(&serialized, &expected);
result.expect("JSON was not equivalent");
assert_json_is_equivalent(&serialized, &serialized_without_dead);
}
}
5 changes: 5 additions & 0 deletions crates/lib/src/compiler/isa/qubit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ impl Qubit {
self.dead = false;
Ok(())
}

/// Check if the qubit has any valid operations (gates).
pub(crate) fn has_valid_operations(&self) -> bool {
!self.gates.is_empty()
}
}

/// All the errors that can occur within this module.
Expand Down
95 changes: 1 addition & 94 deletions crates/lib/tests/compiler-isa-Aspen-8.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,6 @@
],
"id": 1
},
"10": {
"dead": true,
"gates": [],
"id": 10
},
"11": {
"gates": [
{
Expand Down Expand Up @@ -1865,11 +1860,6 @@
],
"id": 30
},
"31": {
"dead": true,
"gates": [],
"id": 31
},
"32": {
"gates": [
{
Expand Down Expand Up @@ -2802,14 +2792,6 @@
}
},
"2Q": {
"0-1": {
"dead": true,
"gates": [],
"ids": [
0,
1
]
},
"0-7": {
"gates": [
{
Expand Down Expand Up @@ -2882,22 +2864,6 @@
2
]
},
"10-11": {
"dead": true,
"gates": [],
"ids": [
10,
11
]
},
"10-17": {
"dead": true,
"gates": [],
"ids": [
10,
17
]
},
"11-12": {
"gates": [
{
Expand Down Expand Up @@ -2994,14 +2960,6 @@
13
]
},
"12-25": {
"dead": true,
"gates": [],
"ids": [
12,
25
]
},
"13-14": {
"gates": [
{
Expand Down Expand Up @@ -3522,25 +3480,6 @@
4
]
},
"30-31": {
"gates": [
{
"arguments": [
"_",
"_"
],
"duration": 200.0,
"fidelity": 0.962505082735891,
"operator": "CZ",
"operator_type": "gate",
"parameters": []
}
],
"ids": [
30,
31
]
},
"30-37": {
"gates": [
{
Expand All @@ -3560,38 +3499,6 @@
37
]
},
"31-32": {
"gates": [
{
"arguments": [
"_",
"_"
],
"duration": 200.0,
"fidelity": 0.984330374008766,
"operator": "CZ",
"operator_type": "gate",
"parameters": []
},
{
"arguments": [
"_",
"_"
],
"duration": 200.0,
"fidelity": 0.970416051000658,
"operator": "XY",
"operator_type": "gate",
"parameters": [
"theta"
]
}
],
"ids": [
31,
32
]
},
"32-33": {
"gates": [
{
Expand Down Expand Up @@ -3849,4 +3756,4 @@
]
}
}
}
}
Loading
Loading