Skip to content

Commit

Permalink
feat!: Return billable QPU execution time in results. (#90)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The return type of `Executable::execute_on_qpu` and `Executable::execute_on_qvm` is a new `ExecutionData` struct. The previous result map is now stored on `ExecutionData::registers` with the renamed `RegisterData` type (previously `ExecutionResult`).
  • Loading branch information
dbanty authored Jun 4, 2022
1 parent 6cb5821 commit 22187af
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 54 deletions.
14 changes: 10 additions & 4 deletions qcs/examples/parametric_compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! [pyquil]: https://pyquil-docs.rigetti.com/en/stable/basics.html?highlight=parametric#parametric-compilation
use std::f64::consts::PI;
use std::time::Duration;

use qcs::Executable;

Expand All @@ -22,19 +23,24 @@ async fn main() {
let mut parametric_measurements = Vec::with_capacity(200);

let step = 2.0 * PI / 50.0;
let mut total_execution_time = Duration::new(0, 0);

for i in 0..=50 {
let theta = step * f64::from(i);
let result = exe
let mut data = exe
.with_parameter("theta", 0, theta)
.execute_on_qpu("Aspen-11")
.await
.expect("Failed to execute")
.remove("ro")
.expect("Missing ro register");
.expect("Failed to execute");
total_execution_time += data
.duration
.expect("Aspen-11 should always report duration");
let result = data.registers.remove("ro").expect("Missing ro register");
parametric_measurements.append(&mut result.into_i16().unwrap()[0])
}

println!("Total execution time: {:?}", total_execution_time);

for measurement in parametric_measurements {
if measurement == 1 {
// We didn't run with all 0 so parametrization worked!
Expand Down
1 change: 1 addition & 0 deletions qcs/examples/quil_t.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ async fn main() {
.execute_on_qpu("Aspen-11")
.await
.expect("Failed to execute")
.registers
.remove("ro")
.expect("Missing ro register");

Expand Down
17 changes: 11 additions & 6 deletions qcs/src/executable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use std::time::Duration;

use crate::configuration::{Configuration, LoadError, RefreshError};
use crate::qpu::ExecutionError;
use crate::{qpu, qvm, ExecutionResult};
use crate::{qpu, qvm, ExecutionData};

/// The builder interface for executing Quil programs on QVMs and QPUs.
///
/// # Example
///
/// ```rust
/// use qcs::{Executable, ExecutionResult};
/// use qcs::{Executable, RegisterData};
///
///
/// const PROGRAM: &str = r##"
Expand All @@ -32,7 +32,7 @@ use crate::{qpu, qvm, ExecutionResult};
/// let mut result = Executable::from_quil(PROGRAM).with_shots(4).execute_on_qvm().await.unwrap();
/// // We know it's i8 because we declared the memory as `BIT` in Quil.
/// // "ro" is the only source read from by default if you don't specify a .read_from()
/// let data = result.remove("ro").expect("Did not receive ro data").into_i8().unwrap();
/// let data = result.registers.remove("ro").expect("Did not receive ro data").into_i8().unwrap();
/// // In this case, we ran the program for 4 shots, so we know the length is 4.
/// assert_eq!(data.len(), 4);
/// for shot in data {
Expand Down Expand Up @@ -127,11 +127,13 @@ impl<'executable> Executable<'executable, '_> {
/// .await
/// .unwrap();
/// let first = result
/// .registers
/// .remove("first")
/// .expect("Did not receive first buffer")
/// .into_f64()
/// .expect("Received incorrect data type for first");
/// let second = result
/// .registers
/// .remove("second")
/// .expect("Did not receive second buffer")
/// .into_f64()
Expand Down Expand Up @@ -177,7 +179,7 @@ impl<'executable> Executable<'executable, '_> {
/// .with_parameter("theta", 0, theta)
/// .with_parameter("theta", 1, theta * 2.0)
/// .execute_on_qvm().await.unwrap();
/// let data = result.remove("theta").expect("Could not read theta").into_f64().unwrap();
/// let data = result.registers.remove("theta").expect("Could not read theta").into_f64().unwrap();
/// assert_eq!(data[0][0], theta);
/// assert_eq!(data[0][1], theta * 2.0);
/// }
Expand Down Expand Up @@ -208,7 +210,7 @@ impl<'executable> Executable<'executable, '_> {
}
}

type ExecuteResult = Result<HashMap<Box<str>, ExecutionResult>, Error>;
type ExecuteResult = Result<ExecutionData, Error>;

impl Executable<'_, '_> {
/// Specify a number of times to run the program for each execution. Defaults to 1 run or "shot".
Expand Down Expand Up @@ -258,7 +260,10 @@ impl Executable<'_, '_> {
.run(self.shots, self.get_readouts(), &self.params, &config)
.await;
self.qvm = Some(qvm);
result.map_err(Error::from)
result.map_err(Error::from).map(|registers| ExecutionData {
registers,
duration: None,
})
}

/// Load `self.config` if not yet loaded, then return a reference to it.
Expand Down
19 changes: 19 additions & 0 deletions qcs/src/execution_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::collections::HashMap;
use std::time::Duration;

use crate::RegisterData;

/// The result of executing an [`Executable`][crate::Executable] via
/// [`Executable::execute_on_qvm`][crate::Executable::execute_on_qvm] or
/// [`Executable::execute_on_qpu`][crate::Executable::execute_on_qpu].
pub struct ExecutionData {
/// The data of all registers that were read from
/// (via [`Executable::read_from`][crate::Executable::read_from]). Key is the name of the
/// register, value is the data of the register after execution.
pub registers: HashMap<Box<str>, RegisterData>,
/// The time it took to execute the program on the QPU, not including any network or queueing
/// time. If paying for on-demand execution, this is the amount you will be billed for.
///
/// This will always be `None` for QVM execution.
pub duration: Option<Duration>,
}
6 changes: 4 additions & 2 deletions qcs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
//! using [`Executable`].
pub use executable::{Error, Executable, Service};
pub use execution_result::ExecutionResult;
pub use execution_data::ExecutionData;
pub use register_data::RegisterData;

pub mod configuration;
mod executable;
mod execution_result;
mod execution_data;
mod qpu;
mod qvm;
mod register_data;
21 changes: 13 additions & 8 deletions qcs/src/qpu/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use qcs_api::models::EngagementWithCredentials;

use crate::configuration::Configuration;
use crate::executable::Parameters;
use crate::{qpu, ExecutionResult};
use crate::{ExecutionData, RegisterData};

use super::quilc::{self, NativeQuil, NativeQuilProgram};
use super::rewrite_arithmetic::{RewrittenProgram, SUBSTITUTION_NAME};
Expand Down Expand Up @@ -110,8 +110,8 @@ pub(crate) enum Unexpected {
Qcs(String),
}

impl From<qpu::IsaError> for Error {
fn from(source: qpu::IsaError) -> Self {
impl From<IsaError> for Error {
fn from(source: IsaError) -> Self {
match source {
IsaError::QpuNotFound => Self::QpuNotFound,
IsaError::Unauthorized => Self::Unauthorized,
Expand Down Expand Up @@ -219,13 +219,13 @@ impl<'a> Execution<'a> {
params: &Parameters,
readouts: &[&str],
config: &Configuration,
) -> Result<HashMap<Box<str>, ExecutionResult>, Error> {
) -> Result<ExecutionData, Error> {
let qcs = self.refresh_qcs(readouts, config).await?;
let qcs_for_thread = qcs.clone();

let patch_values = self.get_substitutions(params).map_err(Error::Quil)?;

let buffers = spawn_blocking(move || {
let response = spawn_blocking(move || {
execute(
&qcs_for_thread.executable,
&qcs_for_thread.engagement,
Expand All @@ -239,9 +239,14 @@ impl<'a> Execution<'a> {
source,
})??;

let registers = process_buffers(buffers, &qcs.buffer_names)?;

ExecutionResult::try_from_registers(registers, self.shots).map_err(Error::from)
let registers = process_buffers(response.buffers, &qcs.buffer_names)?;
let register_data = RegisterData::try_from_registers(registers, self.shots)?;
Ok(ExecutionData {
registers: register_data,
duration: response
.execution_duration_microseconds
.map(Duration::from_micros),
})
}

/// Take or create a [`Qcs`] for this [`Execution`]. This fetches / updates engagements, builds
Expand Down
25 changes: 18 additions & 7 deletions qcs/src/qpu/runner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Interface to the QPU's API.
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt::{Display, Formatter};
Expand All @@ -19,7 +21,7 @@ pub(crate) fn execute(
program: &str,
engagement: &EngagementWithCredentials,
patch_values: &Parameters,
) -> Result<HashMap<String, Buffer>, Error> {
) -> Result<GetExecutionResultsResponse, Error> {
let params = QPUParams::from_program(program, patch_values);
let EngagementWithCredentials {
address,
Expand All @@ -44,7 +46,7 @@ pub(crate) fn execute(
let request = RPCRequest::from(&params);
let job_id: String = client.run_request(&request)?;
debug!("Received job ID {} from QPU", &job_id);
let get_buffers_request = GetBuffersRequest::new(job_id);
let get_buffers_request = GetExecutionResultsRequest::new(job_id);
client
.run_request(&RPCRequest::from(&get_buffers_request))
.map_err(Error::from)
Expand Down Expand Up @@ -115,23 +117,32 @@ impl<'request> QPURequest<'request> {
}

#[derive(Serialize, Debug)]
struct GetBuffersRequest {
struct GetExecutionResultsRequest {
job_id: String,
wait: bool,
}

impl GetBuffersRequest {
impl GetExecutionResultsRequest {
fn new(job_id: String) -> Self {
Self { job_id, wait: true }
}
}

impl<'request> From<&'request GetBuffersRequest> for RPCRequest<'request, GetBuffersRequest> {
fn from(req: &'request GetBuffersRequest) -> Self {
RPCRequest::new("get_buffers", req)
impl<'request> From<&'request GetExecutionResultsRequest>
for RPCRequest<'request, GetExecutionResultsRequest>
{
fn from(req: &'request GetExecutionResultsRequest) -> Self {
RPCRequest::new("get_execution_results", req)
}
}

#[derive(Debug, Deserialize)]
pub(crate) struct GetExecutionResultsResponse {
pub(crate) buffers: HashMap<String, Buffer>,
#[serde(default)]
pub(crate) execution_duration_microseconds: Option<u64>,
}

/// The raw form of the data which comes back from an execution.
///
/// Generally this should not be used directly, but converted into an appropriate
Expand Down
6 changes: 3 additions & 3 deletions qcs/src/qvm/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use quil_rs::{

use crate::configuration::Configuration;
use crate::executable::Parameters;
use crate::ExecutionResult;
use crate::RegisterData;

use super::{Request, Response};

Expand Down Expand Up @@ -59,7 +59,7 @@ impl Execution {
readouts: &[&str],
params: &Parameters,
config: &Configuration,
) -> Result<HashMap<Box<str>, ExecutionResult>, Error> {
) -> Result<HashMap<Box<str>, RegisterData>, Error> {
if shots == 0 {
return Err(Error::ShotsMustBePositive);
}
Expand Down Expand Up @@ -108,7 +108,7 @@ impl Execution {
shots: u16,
readouts: &[&str],
config: &Configuration,
) -> Result<HashMap<Box<str>, ExecutionResult>, Error> {
) -> Result<HashMap<Box<str>, RegisterData>, Error> {
let request = Request::new(&self.program.to_string(true), shots, readouts);

let client = reqwest::Client::new();
Expand Down
4 changes: 2 additions & 2 deletions qcs/src/qvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};

pub(crate) use execution::{Error, Execution};

use crate::ExecutionResult;
use crate::RegisterData;

mod execution;

Expand All @@ -21,7 +21,7 @@ pub(super) enum Response {
#[derive(Debug, Deserialize)]
pub(super) struct Success {
#[serde(flatten)]
pub registers: HashMap<String, ExecutionResult>,
pub registers: HashMap<String, RegisterData>,
}

#[derive(Debug, Deserialize)]
Expand Down
28 changes: 14 additions & 14 deletions qcs/src/execution_result.rs → qcs/src/register_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::qpu::{DecodeError, Register};
/// convert any variant type to its inner data.
#[derive(Debug, Deserialize, EnumAsInner, PartialEq, Serialize)]
#[serde(untagged)]
pub enum ExecutionResult {
pub enum RegisterData {
/// Corresponds to the Quil `BIT` or `OCTET` types.
I8(Vec<Vec<i8>>),
/// Corresponds to the Quil `REAL` type.
Expand All @@ -36,7 +36,7 @@ pub enum ExecutionResult {
Complex32(Vec<Vec<Complex32>>),
}

impl ExecutionResult {
impl RegisterData {
pub(crate) fn try_from_registers(
registers_by_name: HashMap<Box<str>, Vec<Register>>,
shots: u16,
Expand Down Expand Up @@ -135,9 +135,9 @@ mod describe_program_results {
let registers = hashmap! {
Box::from(String::from("ro")) => vec![Register::I8(vec![1, 2, 3]), Register::I8(vec![4, 5, 6])]
};
let results = ExecutionResult::try_from_registers(registers, 3).unwrap();
let results = RegisterData::try_from_registers(registers, 3).unwrap();
let expected = hashmap! {
Box::from(String::from("ro")) => ExecutionResult::I8(vec![vec![1, 4], vec![2, 5], vec![3, 6]])
Box::from(String::from("ro")) => RegisterData::I8(vec![vec![1, 4], vec![2, 5], vec![3, 6]])
};
assert_eq!(results, expected);
}
Expand All @@ -147,9 +147,9 @@ mod describe_program_results {
let registers = hashmap! {
Box::from(String::from("ro")) => vec![Register::I16(vec![1, 2, 3]), Register::I16(vec![4, 5, 6])]
};
let results = ExecutionResult::try_from_registers(registers, 3).unwrap();
let results = RegisterData::try_from_registers(registers, 3).unwrap();
let expected = hashmap! {
Box::from(String::from("ro")) => ExecutionResult::I16(vec![vec![1, 4], vec![2, 5], vec![3, 6]])
Box::from(String::from("ro")) => RegisterData::I16(vec![vec![1, 4], vec![2, 5], vec![3, 6]])
};
assert_eq!(results, expected);
}
Expand All @@ -162,9 +162,9 @@ mod describe_program_results {
Register::F64(vec![4.0, 5.0, 6.0]),
]
};
let results = ExecutionResult::try_from_registers(registers, 3).unwrap();
let results = RegisterData::try_from_registers(registers, 3).unwrap();
let expected = hashmap! {
Box::from(String::from("ro")) => ExecutionResult::F64(vec![vec![1.0, 4.0], vec![2.0, 5.0], vec![3.0, 6.0]])
Box::from(String::from("ro")) => RegisterData::F64(vec![vec![1.0, 4.0], vec![2.0, 5.0], vec![3.0, 6.0]])
};
assert_eq!(results, expected);
}
Expand All @@ -185,9 +185,9 @@ mod describe_program_results {
]),
]
};
let results = ExecutionResult::try_from_registers(registers, 3).unwrap();
let results = RegisterData::try_from_registers(registers, 3).unwrap();
let expected = hashmap! {
Box::from(String::from("ro")) => ExecutionResult::Complex32(vec![
Box::from(String::from("ro")) => RegisterData::Complex32(vec![
vec![Complex32::from(1.0), Complex32::from(4.0)],
vec![Complex32::from(2.0), Complex32::from(5.0)],
vec![Complex32::from(3.0), Complex32::from(6.0)],
Expand All @@ -201,7 +201,7 @@ mod describe_program_results {
let registers = hashmap! {
Box::from(String::from("ro")) => vec![Register::I8(vec![1, 2, 3]), Register::I16(vec![4, 5, 6])]
};
let results = ExecutionResult::try_from_registers(registers, 3);
let results = RegisterData::try_from_registers(registers, 3);
assert!(results.is_err());
}

Expand All @@ -211,10 +211,10 @@ mod describe_program_results {
Box::from(String::from("first")) => vec![Register::I8(vec![1, 2, 3]), Register::I8(vec![4, 5, 6])],
Box::from(String::from("second")) => vec![Register::I16(vec![1, 2, 3]), Register::I16(vec![4, 5, 6])],
};
let results = ExecutionResult::try_from_registers(registers, 3).unwrap();
let results = RegisterData::try_from_registers(registers, 3).unwrap();
let expected = hashmap! {
Box::from(String::from("first")) => ExecutionResult::I8(vec![vec![1, 4], vec![2, 5], vec![3, 6]]),
Box::from(String::from("second")) => ExecutionResult::I16(vec![vec![1, 4], vec![2, 5], vec![3, 6]]),
Box::from(String::from("first")) => RegisterData::I8(vec![vec![1, 4], vec![2, 5], vec![3, 6]]),
Box::from(String::from("second")) => RegisterData::I16(vec![vec![1, 4], vec![2, 5], vec![3, 6]]),
};
assert_eq!(results, expected);
}
Expand Down
Loading

0 comments on commit 22187af

Please sign in to comment.