Skip to content

Commit 88f178e

Browse files
authored
feat!: QVM timeout can now be configured via a new options parameter (#309)
* feat!: QVM timeout can now be configured via a new options parameter * use Duration:from_secs for QVM_DEFAULT_TIMEOUT
1 parent f6fc6e7 commit 88f178e

9 files changed

Lines changed: 248 additions & 52 deletions

File tree

crates/lib/src/executable.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,9 @@ impl From<ExecutionError> for Error {
679679
impl From<qvm::Error> for Error {
680680
fn from(err: qvm::Error) -> Self {
681681
match err {
682-
qvm::Error::QvmCommunication { .. } => Self::Connection(Service::Qvm),
682+
qvm::Error::QvmCommunication { .. } | qvm::Error::Client { .. } => {
683+
Self::Connection(Service::Qvm)
684+
}
683685
qvm::Error::Parsing(_)
684686
| qvm::Error::ShotsMustBePositive
685687
| qvm::Error::RegionSizeMismatch { .. }

crates/lib/src/qvm/api.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
88

99
use crate::{client::Qcs, RegisterData};
1010

11-
use super::Error;
11+
use super::{Error, QvmOptions};
1212

1313
#[derive(Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
1414
#[serde(rename_all = "kebab-case")]
@@ -54,11 +54,11 @@ impl<T: DeserializeOwned> QvmResponse<T> {
5454
}
5555

5656
/// Fetch the version information from the running QVM server.
57-
pub async fn get_version_info(client: &Qcs) -> Result<String, Error> {
57+
pub async fn get_version_info(client: &Qcs, options: &QvmOptions) -> Result<String, Error> {
5858
#[cfg(feature = "tracing")]
5959
tracing::debug!("requesting qvm version information");
6060
let params = HashMap::from([("type", "version")]);
61-
let response = make_request(&params, client).await?;
61+
let response = make_request(&params, client, options).await?;
6262
let qvm_url = client.get_config().qvm_url().into();
6363
if response.status() == 200 {
6464
response
@@ -74,10 +74,14 @@ pub async fn get_version_info(client: &Qcs) -> Result<String, Error> {
7474
}
7575

7676
/// Executes a program on the QVM.
77-
pub async fn run(request: &MultishotRequest, client: &Qcs) -> Result<MultishotResponse, Error> {
77+
pub async fn run(
78+
request: &MultishotRequest,
79+
client: &Qcs,
80+
options: &QvmOptions,
81+
) -> Result<MultishotResponse, Error> {
7882
#[cfg(feature = "tracing")]
7983
tracing::debug!("making a multishot request to the QVM");
80-
let response = make_request(request, client).await?;
84+
let response = make_request(request, client, options).await?;
8185
response
8286
.json::<QvmResponse<MultishotResponse>>()
8387
.await
@@ -174,8 +178,9 @@ pub struct MultishotResponse {
174178
pub async fn run_and_measure(
175179
request: &MultishotMeasureRequest,
176180
client: &Qcs,
181+
options: &QvmOptions,
177182
) -> Result<Vec<Vec<i64>>, Error> {
178-
let response = make_request(request, client).await?;
183+
let response = make_request(request, client, options).await?;
179184
response
180185
.json::<QvmResponse<Vec<Vec<i64>>>>()
181186
.await
@@ -236,8 +241,9 @@ impl MultishotMeasureRequest {
236241
pub async fn measure_expectation(
237242
request: &ExpectationRequest,
238243
client: &Qcs,
244+
options: &QvmOptions,
239245
) -> Result<Vec<f64>, Error> {
240-
let response = make_request(request, client).await?;
246+
let response = make_request(request, client, options).await?;
241247
response
242248
.json::<QvmResponse<Vec<f64>>>()
243249
.await
@@ -280,8 +286,9 @@ impl ExpectationRequest {
280286
pub async fn get_wavefunction(
281287
request: &WavefunctionRequest,
282288
client: &Qcs,
289+
options: &QvmOptions,
283290
) -> Result<Vec<u8>, Error> {
284-
let response = make_request(request, client).await?;
291+
let response = make_request(request, client, options).await?;
285292
if response.status() == 200 {
286293
response
287294
.bytes()
@@ -340,12 +347,18 @@ impl WavefunctionRequest {
340347
}
341348
}
342349

343-
async fn make_request<T>(request: &T, client: &Qcs) -> Result<Response, Error>
350+
async fn make_request<T>(request: &T, client: &Qcs, options: &QvmOptions) -> Result<Response, Error>
344351
where
345352
T: Serialize,
346353
{
347354
let qvm_url = client.get_config().qvm_url();
348-
let client = reqwest::Client::new();
355+
let mut client = reqwest::Client::new();
356+
if let Some(timeout) = options.timeout {
357+
client = reqwest::Client::builder()
358+
.timeout(timeout)
359+
.build()
360+
.map_err(Error::Client)?;
361+
}
349362
client
350363
.post(qvm_url)
351364
.json(request)

crates/lib/src/qvm/execution.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{executable::Parameters, qvm::run_program};
77

88
use crate::client::Qcs;
99

10+
use super::QvmOptions;
1011
use super::{api::AddressRequest, Error, QvmResultData};
1112

1213
/// Contains all the info needed to execute on a QVM a single time, with the ability to be reused for
@@ -69,6 +70,7 @@ impl Execution {
6970
None,
7071
None,
7172
client,
73+
&QvmOptions::default(),
7274
)
7375
.await
7476
}

crates/lib/src/qvm/mod.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This module contains all the functionality for running Quil programs on a QVM. Specifically,
22
//! the [`Execution`] struct in this module.
33
4-
use std::{collections::HashMap, num::NonZeroU16, str::FromStr};
4+
use std::{collections::HashMap, num::NonZeroU16, str::FromStr, time::Duration};
55

66
use quil_rs::{
77
instruction::{ArithmeticOperand, Instruction, MemoryReference, Move},
@@ -19,6 +19,9 @@ use self::api::AddressRequest;
1919
pub mod api;
2020
mod execution;
2121

22+
/// Number of seconds to wait before timing out.
23+
const DEFAULT_QVM_TIMEOUT: Duration = Duration::from_secs(30);
24+
2225
/// Encapsulates data returned after running a program on the QVM
2326
#[allow(clippy::module_name_repetitions)]
2427
#[derive(Debug, Deserialize, Clone, PartialEq)]
@@ -52,6 +55,7 @@ pub async fn run(
5255
gate_noise: Option<(f64, f64, f64)>,
5356
rng_seed: Option<i64>,
5457
client: &Qcs,
58+
options: &QvmOptions,
5559
) -> Result<QvmResultData, Error> {
5660
#[cfg(feature = "tracing")]
5761
tracing::debug!("parsing a program to be executed on the qvm");
@@ -65,6 +69,7 @@ pub async fn run(
6569
gate_noise,
6670
rng_seed,
6771
client,
72+
options,
6873
)
6974
.await
7075
}
@@ -81,6 +86,7 @@ pub async fn run_program(
8186
gate_noise: Option<(f64, f64, f64)>,
8287
rng_seed: Option<i64>,
8388
client: &Qcs,
89+
options: &QvmOptions,
8490
) -> Result<QvmResultData, Error> {
8591
#[cfg(feature = "tracing")]
8692
tracing::debug!(
@@ -98,7 +104,7 @@ pub async fn run_program(
98104
gate_noise,
99105
rng_seed,
100106
);
101-
api::run(&request, client)
107+
api::run(&request, client, options)
102108
.await
103109
.map(|response| QvmResultData::from_memory_map(response.registers))
104110
}
@@ -147,6 +153,33 @@ pub fn apply_parameters_to_program(
147153
Ok(program)
148154
}
149155

156+
/// Options avaialable for running programs on the QVM.
157+
#[allow(clippy::module_name_repetitions)]
158+
#[derive(Clone, Copy, Debug)]
159+
pub struct QvmOptions {
160+
/// The timeout to use for requests to the QVM. If set to [`None`], there is no timeout.
161+
pub timeout: Option<Duration>,
162+
}
163+
164+
impl QvmOptions {
165+
/// Builds a [`QvmOptions`] with the zero value for each option.
166+
///
167+
/// Consider using [`Default`] to get a reasonable set of
168+
/// configuration options as a starting point.
169+
#[must_use]
170+
pub fn new() -> Self {
171+
Self { timeout: None }
172+
}
173+
}
174+
175+
impl Default for QvmOptions {
176+
fn default() -> Self {
177+
Self {
178+
timeout: Some(DEFAULT_QVM_TIMEOUT),
179+
}
180+
}
181+
}
182+
150183
/// All of the errors that can occur when running a Quil program on QVM.
151184
#[allow(missing_docs)]
152185
#[derive(Debug, thiserror::Error)]
@@ -170,6 +203,8 @@ pub enum Error {
170203
},
171204
#[error("QVM reported a problem running your program: {message}")]
172205
Qvm { message: String },
206+
#[error("The client failed to make the request: {0}")]
207+
Client(#[from] reqwest::Error),
173208
}
174209

175210
#[cfg(test)]

crates/lib/tests/qvm_api.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
44
use std::{collections::HashMap, num::NonZeroU16};
55

6-
use qcs::{client::Qcs, qvm::api};
6+
use qcs::{
7+
client::Qcs,
8+
qvm::{api, QvmOptions},
9+
};
710
use regex::Regex;
811

912
const PROGRAM: &str = r##"
@@ -16,7 +19,7 @@ MEASURE 1 ro[1]
1619

1720
#[tokio::test]
1821
async fn test_get_version_info() {
19-
let version = api::get_version_info(&Qcs::default())
22+
let version = api::get_version_info(&Qcs::default(), &QvmOptions::default())
2023
.await
2124
.expect("Should be able to get version info.");
2225
let semver_re = Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap();
@@ -34,7 +37,7 @@ async fn test_run() {
3437
Some((0.1, 0.5, 0.4)),
3538
Some(1),
3639
);
37-
let response = api::run(&request, &client)
40+
let response = api::run(&request, &client, &QvmOptions::default())
3841
.await
3942
.expect("Should be able to run");
4043
assert_eq!(response.registers.len(), 1);
@@ -57,7 +60,7 @@ async fn test_run_and_measure() {
5760
Some((0.1, 0.5, 0.4)),
5861
Some(1),
5962
);
60-
let qubits = api::run_and_measure(&request, &client)
63+
let qubits = api::run_and_measure(&request, &client, &QvmOptions::default())
6164
.await
6265
.expect("Should be able to run and measure");
6366
assert_eq!(qubits.len(), 5);
@@ -75,7 +78,7 @@ Z 2
7578
let operators = vec!["X 0\nY 1\n".to_string(), "Z 2\n".to_string()];
7679
let request = api::ExpectationRequest::new(prep_program.to_string(), &operators, None);
7780

78-
let expectations = api::measure_expectation(&request, &client)
81+
let expectations = api::measure_expectation(&request, &client, &QvmOptions::default())
7982
.await
8083
.expect("Should be able to measure expectation");
8184

@@ -86,7 +89,7 @@ Z 2
8689
async fn test_get_wavefunction() {
8790
let client = Qcs::default();
8891
let request = api::WavefunctionRequest::new(PROGRAM.to_string(), None, None, Some(0));
89-
api::get_wavefunction(&request, &client)
92+
api::get_wavefunction(&request, &client, &QvmOptions::default())
9093
.await
9194
.expect("Should be able to get wavefunction");
9295
}

crates/python/qcs_sdk/qvm/__init__.pyi

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ class QVMResultData:
2525
"""
2626
...
2727

28+
@final
29+
class QVMOptions:
30+
"""
31+
Options avaialable for running programs on the QVM.
32+
"""
33+
34+
def __new__(cls, timeout_seconds: Optional[float] = None) -> QVMOptions: ...
35+
@staticmethod
36+
def default() -> QVMOptions:
37+
"""Get the default set of ``QVMOptions`` used for QVM requests.
38+
39+
Settings:
40+
timeout: 30.0 seconds
41+
"""
42+
...
43+
@property
44+
def timeout(cls):
45+
"""The timeout used for reqeusts to the QVM. If set to none, there is no timeout."""
46+
...
47+
@timeout.setter
48+
def timeout(cls, timeout: Optional[float]):
49+
"""The timeout used for reqeusts to the QVM. If set to none, there is no timeout."""
50+
...
51+
2852
@final
2953
class QVMError(RuntimeError):
3054
"""
@@ -42,6 +66,7 @@ def run(
4266
gate_noise: Optional[Tuple[float, float, float]] = None,
4367
rng_seed: Optional[int] = None,
4468
client: Optional[QCSClient] = None,
69+
options: Optional[QVMOptions] = None,
4570
) -> QVMResultData:
4671
"""
4772
Runs the given program on the QVM.
@@ -51,6 +76,7 @@ def run(
5176
:param addresses: A mapping of memory region names to an ``AddressRequest`` describing what data to get back for that memory region from the QVM at the end of execution.
5277
:param params: A mapping of memory region names to their desired values.
5378
:param client: An optional ``QCSClient`` to use. If unset, creates one using the environemnt configuration (see https://docs.rigetti.com/qcs/references/qcs-client-configuration).
79+
:param options: An optional ``QVMOptions`` to use. If unset, uses ``QVMOptions.default()`` for the request.
5480
5581
:returns: A ``QVMResultData`` containing the final state of of memory for the requested readouts after the program finished running.
5682
@@ -67,6 +93,7 @@ async def run_async(
6793
gate_noise: Optional[Tuple[float, float, float]] = None,
6894
rng_seed: Optional[int] = None,
6995
client: Optional[QCSClient] = None,
96+
options: Optional[QVMOptions] = None,
7097
) -> QVMResultData:
7198
"""
7299
Asynchronously runs the given program on the QVM.
@@ -76,6 +103,7 @@ async def run_async(
76103
:param addresses: A mapping of memory region names to an ``AddressRequest`` describing what data to get back for that memory region from the QVM at the end of execution.
77104
:param params: A mapping of memory region names to their desired values.
78105
:param client: An optional ``QCSClient`` to use. If unset, creates one using the environemnt configuration (see https://docs.rigetti.com/qcs/references/qcs-client-configuration).
106+
:param options: An optional ``QVMOptions`` to use. If unset, uses ``QVMOptions.default()`` for the request.
79107
80108
:returns: A ``QVMResultData`` containing the final state of of memory for the requested readouts after the program finished running.
81109

0 commit comments

Comments
 (0)