Skip to content
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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ build = "build.rs"

[dependencies]
libc = "0.2"
monostate = "0.1.9"
serde = "1.0.180"
serde-aux = "4.2.0"
serde_json = "1.0.104"

[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
pkg-config = "0.3"
bindgen = "0.53.1"

[dev-dependencies]
pretty_assertions = "1.4.0"
quil-rs = "0.19.0"
rstest = "0.18.1"

4 changes: 3 additions & 1 deletion examples/bell_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ffi::CString;

use libquil_sys::{compile_program, get_chip, parse_program, print_program};

const PROGRAM: &str = r#"
Expand All @@ -9,7 +11,7 @@ CNOT 0 1
"#;

fn main() {
let parsed_program = parse_program(PROGRAM.to_string());
let parsed_program = parse_program(CString::new(PROGRAM).unwrap());
let chip = get_chip();
let compiled_program = compile_program(&parsed_program, &chip);
print_program(&compiled_program)
Expand Down
304 changes: 304 additions & 0 deletions src/chip/isa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
//! The supported Instruction Set Architecture for a chip specification

use serde::{de::Deserializer, Deserialize, Serializer};

use std::collections::HashMap;

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct Isa {
/// The degree-0 (i.e. single qubit) hardware objects and their
/// supported instructions.
#[serde(rename(deserialize = "1Q"))]
#[serde(rename(serialize = "1Q"))]
Comment on lines +11 to +12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: throughout, why not rename these together?

Suggested change
#[serde(rename(deserialize = "1Q"))]
#[serde(rename(serialize = "1Q"))]
#[serde(rename = "1Q"))]

Comment on lines +11 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[serde(rename(deserialize = "1Q"))]
#[serde(rename(serialize = "1Q"))]
#[serde(rename = "1Q")]

pub one_q: HashMap<String, Option<OneQ>>,
/// The degree-1 (i.e. two qubit) hardware objects and their
/// supported instructions.
#[serde(rename(deserialize = "2Q"))]
#[serde(rename(serialize = "2Q"))]
Comment on lines +16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[serde(rename(deserialize = "2Q"))]
#[serde(rename(serialize = "2Q"))]
#[serde(rename = "2Q")]

pub two_q: HashMap<String, Option<TwoQ>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct Metadata {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this include a flattened map of miscellaneous fields, so that it's round-trip-able?

https://serde.rs/attr-flatten.html#capture-additional-fields

Comment on lines +23 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this struct?


#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(rename(serialize = "1Q"))]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum OneQ {
/// The set of supported gates
Gates { gates: Vec<Gate> },
/// DEPRECATED. A gateset identifier known to quilc
///
/// In practice, the only supported identifier here is "Xhalves"
/// and this style of specifying gates in the ISA is deprecated.
Ty {
#[serde(rename(deserialize = "type"))]
#[serde(rename(serialize = "type"))]
Comment on lines +38 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[serde(rename(deserialize = "type"))]
#[serde(rename(serialize = "type"))]
#[serde(rename = "type")]

ty: String,
Comment on lines +38 to +40

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: why not just make the field-name type?

},
/// DEPRECATED. Define gates by their associated specs.
Specs { specs: crate::chip::specs::SpecsMap },
/// Qubit exists physically but should not be used for computation
Dead { dead: bool },
/// Use a set of (quilc-specified) default gates
Defaults {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not this?

Suggested change
Defaults {},
Defaults,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about this as well and did some experimentation with how serde_json distinguishes the different types of "empty" variants. I believe the issue is that it needs to be serialized as {}. You can see examples e.g. here: https://github.com/rigetti/libquil-sys/pull/27/files#diff-a4d56291525b84ae456e7026eb9eb879aa08bd7ac45a12f4a2efa29b4171a1c5R8

}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(rename(serialize = "1Q"))]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum TwoQ {
Comment on lines +51 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[serde(rename(serialize = "1Q"))]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum TwoQ {
#[serde(rename(serialize = "2Q"))]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum TwoQ {

/// The set of supported gates
Gates { gates: Vec<Gate> },
/// See documentation for [`OneQ::Ty`].
Ty {
#[serde(rename(deserialize = "type"))]
#[serde(rename(serialize = "type"))]
ty: Vec<String>,
},
/// See documentation for [`OneQ::Specs`].
Specs { specs: crate::chip::specs::SpecsMap },
/// Qubit exists physically but should not be used for computation
Dead { dead: bool },
/// Use a set of (quilc-specified) default gates
Defaults {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here

Suggested change
Defaults {},
Defaults,

}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(rename = "lowercase")]
#[serde(untagged)]
pub enum Gate {
Measure {
operator: monostate::MustBe!("MEASURE"),
qubit: Qubit,
target: Option<MeasurementTarget>,
duration: f64,
fidelity: f64,
},
Quantum {
operator: String,
parameters: Vec<Parameter>,
arguments: Vec<Argument>,
duration: f64,
fidelity: f64,
},
}
Comment on lines +73 to +89
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this does the same thing and does not require the extra crate.

Suggested change
#[serde(untagged)]
pub enum Gate {
Measure {
operator: monostate::MustBe!("MEASURE"),
qubit: Qubit,
target: Option<MeasurementTarget>,
duration: f64,
fidelity: f64,
},
Quantum {
operator: String,
parameters: Vec<Parameter>,
arguments: Vec<Argument>,
duration: f64,
fidelity: f64,
},
}
#[serde(tag = "operator")]
pub enum Gate {
#[serde(rename = "MEASURE")]
Measure {
qubit: Qubit,
target: Option<MeasurementTarget>,
duration: f64,
fidelity: f64,
},
#[serde(other)]
Quantum {
operator: String,
parameters: Vec<Parameter>,
arguments: Vec<Argument>,
duration: f64,
fidelity: f64,
},
}


#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
#[serde(untagged)]
pub enum Qubit {
#[serde(deserialize_with = "deserialize_wildcard")]
#[serde(serialize_with = "serialize_wildcard")]
Wildcard,
Comment on lines +95 to +97

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would Wildcard(monostate::MustBe!("_")) work here?

Index(usize),
}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(untagged)]
pub enum MeasurementTarget {
#[serde(deserialize_with = "deserialize_wildcard")]
#[serde(serialize_with = "serialize_wildcard")]
Wildcard,
MemoryReference(String),
}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Parameter {
#[serde(deserialize_with = "deserialize_wildcard")]
#[serde(serialize_with = "serialize_wildcard")]
Wildcard,
Numeric(f64),
}

#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Argument {
#[serde(deserialize_with = "deserialize_wildcard")]
#[serde(serialize_with = "serialize_wildcard")]
Comment on lines +122 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this do the same thing and be simpler?

Suggested change
#[serde(deserialize_with = "deserialize_wildcard")]
#[serde(serialize_with = "serialize_wildcard")]
#[serde(rename = "_")]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Same for all the other cases as well)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't rename change the key name, not the value?

Wildcard,
Index(usize),
}

fn deserialize_wildcard<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
let buf = String::deserialize(deserializer)?;
if &buf == "_" {
Ok(())
} else {
Err(serde::de::Error::custom("input does not match wildcard"))
}
}

fn serialize_wildcard<S>(serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str("_")
}

#[cfg(test)]
mod test {
use super::*;
use monostate::MustBe;
use serde_json::json;

#[test]
fn deserialize_oneq() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see a test for 2Q

struct Test {
input: serde_json::Value,
expected: OneQ,
}
let tests = [
Test {
input: json!({}),
expected: OneQ::Defaults {},
},
Test {
input: json!({"gates": []}),
expected: OneQ::Gates { gates: vec![] },
},
];
for test in tests {
let actual: OneQ = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}

#[test]
fn deserialize_gate() {
struct Test {
input: serde_json::Value,
expected: Gate,
}
let tests = [
Test {
input: json!({"operator": "MEASURE", "qubit": 42, "target": "ro", "duration": 0.1, "fidelity": 0.9}),
expected: Gate::Measure {
operator: MustBe!("MEASURE"),
qubit: Qubit::Index(42),
target: Some(MeasurementTarget::MemoryReference("ro".to_string())),
duration: 0.1,
fidelity: 0.9,
},
},
Test {
input: json!({"operator": "RX", "parameters": [1.5], "arguments": [42], "duration": 0.1, "fidelity": 0.9}),
expected: Gate::Quantum {
operator: "RX".to_string(),
parameters: vec![Parameter::Numeric(1.5)],
arguments: vec![Argument::Index(42)],
duration: 0.1,
fidelity: 0.9,
},
},
];

for test in tests {
let actual: Gate = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}

#[test]
fn deserialize_qubit() {
struct Test {
input: serde_json::Value,
expected: Qubit,
}
let tests = [
Test {
input: json!("_"),
expected: Qubit::Wildcard,
},
Test {
input: json!(42),
expected: Qubit::Index(42),
},
];

for test in tests {
let actual: Qubit = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}

#[test]
fn deserialize_argument() {
struct Test {
input: serde_json::Value,
expected: Argument,
}

let tests = [
Test {
input: json!("_"),
expected: Argument::Wildcard,
},
Test {
input: json!(42),
expected: Argument::Index(42),
},
];

for test in tests {
let actual: Argument = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}

#[test]
fn deserialize_parameter() {
struct Test {
input: serde_json::Value,
expected: Parameter,
}

let tests = [
Test {
input: json!("_"),
expected: Parameter::Wildcard,
},
Test {
input: json!(1.5),
expected: Parameter::Numeric(1.5),
},
];

for test in tests {
let actual: Parameter = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}

#[test]
fn deserialize_measurement_target() {
struct Test {
input: serde_json::Value,
expected: MeasurementTarget,
}

let tests = [
Test {
input: json!("_"),
expected: MeasurementTarget::Wildcard,
},
Test {
input: json!("ro"),
expected: MeasurementTarget::MemoryReference("ro".to_string()),
},
];

for test in tests {
let actual: MeasurementTarget = serde_json::from_value(test.input).unwrap();
assert_eq!(actual, test.expected);
}
}
}
45 changes: 45 additions & 0 deletions src/chip/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Quilc-compatible chip specification
use serde::Serializer;
use serde_aux::prelude::deserialize_string_from_number;

pub mod isa;
mod parity_test;
pub mod specs;

/// A `ChipSpec` defines the various hardware objects that are available
/// when compiling a quantum program.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ChipSpec {
/// The "Instruction Set Architecture" of the chip; i.e. the
/// instructions (or "gates") that a particular hardware object
/// supports.
pub isa: isa::Isa,
/// The various operating characteristics of a hardware object (e.g
/// readout fidelity).
#[serde(skip_serializing_if = "Option::is_none")]
pub specs: Option<specs::Specs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Metadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(deserialize_with = "deserialize_string_from_number")]
#[serde(serialize_with = "maybe_serialize_string_to_integer")]
pub version: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it might be better to have a Version struct instead of the manual serialization? Would also mean not needing the extra crate.

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum Version {
    Number(i32),
    String(String),
}

}

/// Serialize the input to an `i32` if it can be parsed as such; otherwise
/// serialize it as a string.
fn maybe_serialize_string_to_integer<S>(s: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match s.parse::<i32>() {
Ok(i) => serializer.serialize_i32(i),
Err(_) => serializer.serialize_str(s),
}
}
Loading