From a7123126e67a831c45ff650890fe2d2eb3534973 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:39:04 -0800 Subject: [PATCH 01/43] refactor(lib): remove unused module --- crates/lib/src/configuration/mod.rs | 198 ----------------------- crates/lib/src/configuration/path.rs | 14 -- crates/lib/src/configuration/secrets.rs | 93 ----------- crates/lib/src/configuration/settings.rs | 151 ----------------- 4 files changed, 456 deletions(-) delete mode 100644 crates/lib/src/configuration/mod.rs delete mode 100644 crates/lib/src/configuration/path.rs delete mode 100644 crates/lib/src/configuration/secrets.rs delete mode 100644 crates/lib/src/configuration/settings.rs diff --git a/crates/lib/src/configuration/mod.rs b/crates/lib/src/configuration/mod.rs deleted file mode 100644 index 50513e0e0..000000000 --- a/crates/lib/src/configuration/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! This module is used for loading configuration that will be used to connect either to real QPUs -//! (and supporting services) or the QVM. -//! -//! By default, all settings are loaded from files located -//! under your home directory in a `.qcs` folder. `settings.toml` will be used to load general -//! settings (e.g. which URLs to connect to) and `secrets.toml` will be used to load tokens for -//! authentication. Both "settings" and "secrets" files should contain profiles. The -//! `default_profile_name` in settings sets the profile to be used when there is no override. You -//! can set the [`PROFILE_NAME_VAR`] to select a different profile. You can also use -//! [`SECRETS_PATH_VAR`] and [`SETTINGS_PATH_VAR`] to change which files are loaded. - -use std::path::PathBuf; - -use futures::future::try_join; -use serde::{Deserialize, Serialize}; - -use qcs_api::apis::configuration as api; -use secrets::Secrets; -pub use secrets::SECRETS_PATH_VAR; -pub use settings::SETTINGS_PATH_VAR; -use settings::{AuthServer, Pyquil, Settings}; - -use crate::configuration::LoadError::AuthServerNotFound; - -mod path; -mod secrets; -mod settings; - -/// All the config data that's parsed from config sources -#[derive(Clone, Debug)] -pub struct Configuration { - api_config: api::Configuration, - auth_server: AuthServer, - refresh_token: Option, - /// The URL for the quilc server. - pub quilc_url: String, - /// The URL for the QVM server. - pub qvm_url: String, -} - -/// Setting this environment variable will change which profile is used from the loaded config files -pub const PROFILE_NAME_VAR: &str = "QCS_PROFILE_NAME"; - -/// Errors raised when attempting to refresh the user's token -#[derive(Debug, thiserror::Error)] -pub enum RefreshError { - /// Error due to no token available to refresh. - #[error("No refresh token is in secrets")] - NoRefreshToken, - /// Error when trying to fetch the new token. - #[error("Error fetching new token")] - FetchError(#[from] reqwest::Error), -} - -/// Errors raised when attempting to load the user's configuration files. -#[derive(Debug, thiserror::Error)] -pub enum LoadError { - /// Error due to requested profile missing in the user's configuration. - #[error("Expected profile {0} in settings.profiles but it didn't exist")] - ProfileNotFound(String), - /// Error due to authentication server missing in the user's configuration. - #[error("Expected auth server {0} in settings.auth_servers but it didn't exist")] - AuthServerNotFound(String), - /// Error due to failing to find the user's home directory. - #[error("Failed to determine home directory. You can use an explicit path by setting the {env} environment variable")] - HomeDirError { - /// Environment variable to set to configure the home directory. - env: String, - }, - /// Error due to failing to open a file. - #[error("Could not open file at {path}")] - FileOpenError { - /// Path to file that could not be opened. - path: PathBuf, - /// The source error. - source: std::io::Error, - }, - /// Error due to failing to parse a file. - #[error("Could not parse file at {path}")] - FileParseError { - /// Path to file that could not be parsed. - path: PathBuf, - /// Source error. - source: toml::de::Error, - }, -} - -impl Configuration { - /// Attempt to load config files from ~/.qcs and create a Configuration object - /// for use with qcs-api. - /// - /// # Errors - /// - /// See [`LoadError`]. - pub async fn load() -> Result { - let (settings, secrets) = try_join(settings::load(), secrets::load()).await?; - Self::new(settings, secrets) - } - - /// Refresh the `access_token` and return a new `Configuration` if successful. - /// - /// # Errors - /// - /// See [`RefreshError`]. - pub async fn refresh(mut self) -> Result { - let refresh_token = self.refresh_token.ok_or(RefreshError::NoRefreshToken)?; - let token_url = format!("{}/v1/token", &self.auth_server.issuer); - let data = TokenRequest::new(&self.auth_server.client_id, &refresh_token); - let resp = self - .api_config - .client - .post(token_url) - .form(&data) - .send() - .await?; - let response_data: TokenResponse = resp.error_for_status()?.json().await?; - self.api_config.bearer_access_token = Some(response_data.access_token); - self.refresh_token = Some(response_data.refresh_token); - Ok(self) - } - - fn new(settings: Settings, mut secrets: Secrets) -> Result { - let Settings { - default_profile_name, - mut profiles, - mut auth_servers, - } = settings; - let profile_name = std::env::var(PROFILE_NAME_VAR).unwrap_or(default_profile_name); - let profile = profiles - .remove(&profile_name) - .ok_or(LoadError::ProfileNotFound(profile_name))?; - let auth_server = auth_servers - .remove(&profile.auth_server_name) - .ok_or_else(|| AuthServerNotFound(profile.auth_server_name.clone()))?; - - let credential = secrets.credentials.remove(&profile.credentials_name); - let (access_token, refresh_token) = match credential { - Some(secrets::Credential { - token_payload: Some(token_payload), - }) => (token_payload.access_token, token_payload.refresh_token), - _ => (None, None), - }; - - Ok(Self { - api_config: api::Configuration { - base_path: profile.api_url, - bearer_access_token: access_token, - ..api::Configuration::default() - }, - auth_server, - refresh_token, - quilc_url: profile.applications.pyquil.quilc_url, - qvm_url: profile.applications.pyquil.qvm_url, - }) - } -} - -#[derive(Debug, Serialize, Copy, Clone, Eq, PartialEq)] -struct TokenRequest<'a> { - grant_type: &'static str, - client_id: &'a str, - refresh_token: &'a str, -} - -impl<'a> TokenRequest<'a> { - fn new(client_id: &'a str, refresh_token: &'a str) -> TokenRequest<'a> { - Self { - grant_type: "refresh_token", - client_id, - refresh_token, - } - } -} - -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] -struct TokenResponse { - refresh_token: String, - access_token: String, -} - -impl AsRef for Configuration { - fn as_ref(&self) -> &api::Configuration { - &self.api_config - } -} - -impl Default for Configuration { - fn default() -> Self { - let Pyquil { quilc_url, qvm_url } = Pyquil::default(); - Self { - quilc_url, - qvm_url, - api_config: api::Configuration::default(), - auth_server: AuthServer::default(), - refresh_token: None, - } - } -} diff --git a/crates/lib/src/configuration/path.rs b/crates/lib/src/configuration/path.rs deleted file mode 100644 index fbd5b2c6b..000000000 --- a/crates/lib/src/configuration/path.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::path::PathBuf; - -use super::LoadError; - -pub(crate) fn path_from_env_or_home(env: &str, file_name: &str) -> Result { - match std::env::var(env) { - Ok(path) => Ok(PathBuf::from(path)), - Err(_) => dirs::home_dir() - .map(|path| path.join(".qcs").join(file_name)) - .ok_or_else(|| LoadError::HomeDirError { - env: env.to_string(), - }), - } -} diff --git a/crates/lib/src/configuration/secrets.rs b/crates/lib/src/configuration/secrets.rs deleted file mode 100644 index 31690a2df..000000000 --- a/crates/lib/src/configuration/secrets.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -use crate::configuration::LoadError; - -use super::path::path_from_env_or_home; - -/// Setting the `QCS_SECRETS_FILE_PATH` environment variable will change which file is used for loading secrets -pub const SECRETS_PATH_VAR: &str = "QCS_SECRETS_FILE_PATH"; - -pub(crate) async fn load() -> Result { - let path = path_from_env_or_home(SECRETS_PATH_VAR, "secrets.toml")?; - let content = - tokio::fs::read_to_string(&path) - .await - .map_err(|source| LoadError::FileOpenError { - path: path.clone(), - source, - })?; - toml::from_str(&content).map_err(|source| LoadError::FileParseError { path, source }) -} - -#[cfg(test)] -mod describe_load { - use std::io::Write; - - use tempfile::NamedTempFile; - - use super::{load, Credential, Secrets, SECRETS_PATH_VAR}; - - #[tokio::test] - async fn it_returns_default_if_missing_path() { - std::env::set_var(SECRETS_PATH_VAR, "/blah/doesnt_exist.toml"); - - let settings = load().await; - - std::env::remove_var(SECRETS_PATH_VAR); - assert!(settings.is_err()); - } - - #[tokio::test] - async fn it_loads_from_env_var_path() { - let mut file = NamedTempFile::new().expect("Failed to create temporary settings file"); - let mut secrets = Secrets::default(); - secrets - .credentials - .insert("test".to_string(), Credential::default()); - let secrets_string = toml::to_string(&secrets).expect("Could not serialize test settings"); - let _ = file - .write(secrets_string.as_bytes()) - .expect("Failed to write test settings"); - std::env::set_var(SECRETS_PATH_VAR, file.path()); - - let loaded = load().await.expect("Failed to load secrets"); - - assert_eq!(secrets, loaded); - } -} - -#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Secrets { - pub credentials: HashMap, -} - -impl Default for Secrets { - fn default() -> Self { - Self { - credentials: default_credentials(), - } - } -} - -fn default_credentials() -> HashMap { - let mut map = HashMap::with_capacity(1); - map.insert("default".to_string(), Credential::default()); - map -} - -#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Credential { - pub token_payload: Option, -} - -#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct TokenPayload { - pub refresh_token: Option, - pub access_token: Option, - scope: Option, - expires_in: Option, - id_token: Option, - token_type: Option, -} diff --git a/crates/lib/src/configuration/settings.rs b/crates/lib/src/configuration/settings.rs deleted file mode 100644 index c7f9b8934..000000000 --- a/crates/lib/src/configuration/settings.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -use super::path::path_from_env_or_home; -use super::LoadError; - -/// Setting the `QCS_SETTINGS_FILE_PATH` environment variable will change which file is used for loading settings -pub const SETTINGS_PATH_VAR: &str = "QCS_SETTINGS_FILE_PATH"; - -pub(crate) async fn load() -> Result { - let path = path_from_env_or_home(SETTINGS_PATH_VAR, "settings.toml")?; - let content = - tokio::fs::read_to_string(&path) - .await - .map_err(|source| LoadError::FileOpenError { - path: path.clone(), - source, - })?; - toml::from_str(&content).map_err(|source| LoadError::FileParseError { path, source }) -} - -#[cfg(test)] -mod describe_load { - use std::io::Write; - - use tempfile::NamedTempFile; - - use crate::configuration::{settings::load, SETTINGS_PATH_VAR}; - - use super::Settings; - - #[tokio::test] - async fn it_returns_default_if_missing_path() { - std::env::set_var(SETTINGS_PATH_VAR, "/blah/doesnt_exist.toml"); - - let settings = load().await; - - std::env::remove_var(SETTINGS_PATH_VAR); - - assert!(settings.is_err()); - } - - #[tokio::test] - async fn it_loads_from_env_var_path() { - let mut file = NamedTempFile::new().expect("Failed to create temporary settings file"); - let settings = Settings { - default_profile_name: "THIS IS A TEST".to_string(), - ..Default::default() - }; - let settings_string = - toml::to_string(&settings).expect("Could not serialize test settings"); - let _ = file - .write(settings_string.as_bytes()) - .expect("Failed to write test settings"); - std::env::set_var(SETTINGS_PATH_VAR, file.path()); - - let loaded = load().await.expect("Failed to load settings"); - - assert_eq!(settings, loaded); - } -} - -#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Settings { - /// Which profile to select settings from when none is specified. - pub default_profile_name: String, - /// All available configuration profiles, keyed by profile name. - #[serde(default = "default_profiles")] - pub profiles: HashMap, - #[serde(default)] - pub(crate) auth_servers: HashMap, -} - -impl Default for Settings { - fn default() -> Self { - Self { - default_profile_name: "default".to_string(), - profiles: default_profiles(), - auth_servers: default_auth_servers(), - } - } -} - -fn default_profiles() -> HashMap { - let mut map = HashMap::with_capacity(1); - map.insert("default".to_string(), Profile::default()); - map -} - -fn default_auth_servers() -> HashMap { - let mut map = HashMap::with_capacity(1); - map.insert("default".to_string(), AuthServer::default()); - map -} - -#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Profile { - /// URL of the QCS API to use for all API calls - pub api_url: String, - pub auth_server_name: String, - pub credentials_name: String, - #[serde(default)] - pub applications: Applications, -} - -impl Default for Profile { - fn default() -> Self { - Self { - api_url: "https://api.qcs.rigetti.com".to_string(), - auth_server_name: "default".to_string(), - credentials_name: "default".to_string(), - applications: Applications::default(), - } - } -} - -#[derive(Deserialize, Debug, Default, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Applications { - pub pyquil: Pyquil, -} - -#[derive(Deserialize, Debug, PartialEq, Serialize, Clone, Eq)] -pub(crate) struct Pyquil { - pub qvm_url: String, - pub quilc_url: String, -} - -impl Default for Pyquil { - fn default() -> Self { - Self { - qvm_url: "http://127.0.0.1:5000".to_string(), - quilc_url: "tcp://127.0.0.1:5555".to_string(), - } - } -} - -#[derive(Clone, Deserialize, Debug, PartialEq, Serialize, Eq)] -pub(crate) struct AuthServer { - pub(crate) client_id: String, - pub(crate) issuer: String, -} - -impl Default for AuthServer { - fn default() -> Self { - Self { - client_id: "0oa3ykoirzDKpkfzk357".to_string(), - issuer: "https://auth.qcs.rigetti.com/oauth2/aus8jcovzG0gW2TUG355".to_string(), - } - } -} From 47bf6e0346d50d5c4c8865db34c790a1245fc74c Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:43:02 -0800 Subject: [PATCH 02/43] refactor(lib)!: use num::Complex instead of custom typedef --- Cargo.lock | 277 ++++++++++++++++++++++++------------------ crates/lib/Cargo.toml | 2 +- crates/lib/src/api.rs | 21 ++-- 3 files changed, 172 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec4ccbf36..acecda02d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "async-stream" @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -55,7 +55,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -68,9 +68,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.17" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" dependencies = [ "async-trait", "axum-core", @@ -86,9 +86,9 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", + "rustversion", "serde", "sync_wrapper", - "tokio", "tower", "tower-http", "tower-layer", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" dependencies = [ "async-trait", "bytes", @@ -107,6 +107,7 @@ dependencies = [ "http", "http-body", "mime", + "rustversion", "tower-layer", "tower-service", ] @@ -162,15 +163,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -222,11 +223,21 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -281,9 +292,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" +checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" dependencies = [ "serde", ] @@ -443,6 +454,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghost" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "h2" version = "0.3.15" @@ -508,6 +530,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -580,9 +611,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", @@ -615,9 +646,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -625,9 +656,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780" [[package]] name = "instant" @@ -638,11 +669,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16fe3b35d64bd1f72917f06425e7573a2f63f74f42c8f56e53ea6826dde3a2b5" +dependencies = [ + "ctor", + "ghost", +] + [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "itertools" @@ -655,9 +696,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -670,9 +711,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.1.1" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" +checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" dependencies = [ "base64", "pem", @@ -763,9 +804,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "lock_api" @@ -794,9 +835,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "matchit" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "memchr" @@ -884,9 +925,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -926,6 +967,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "serde", ] [[package]] @@ -935,6 +977,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -968,6 +1011,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "serde", ] [[package]] @@ -981,19 +1025,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "openssl-probe" @@ -1013,9 +1057,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", @@ -1026,9 +1070,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pbjson" @@ -1123,9 +1167,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" dependencies = [ "proc-macro2", "syn", @@ -1133,18 +1177,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ "bytes", "prost-derive", @@ -1152,9 +1196,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ "bytes", "heck", @@ -1174,9 +1218,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools", @@ -1187,9 +1231,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ "bytes", "prost", @@ -1203,8 +1247,10 @@ checksum = "268be0c73583c183f2b14052337465768c07726936a260f480f0857cb95ba543" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", + "num-complex", "parking_lot", "pyo3-build-config", "pyo3-ffi", @@ -1268,16 +1314,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pythonize" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7f0c136f5fbc01868185eef462800e49659eb23acca83b9e884367a006acb6" -dependencies = [ - "pyo3", - "serde", -] - [[package]] name = "qcs" version = "0.9.2" @@ -1308,7 +1344,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.5.9", + "toml 0.5.10", "tonic", "uuid", "warp", @@ -1341,7 +1377,7 @@ dependencies = [ "serde", "thiserror", "tokio", - "toml 0.5.9", + "toml 0.5.10", ] [[package]] @@ -1383,11 +1419,11 @@ dependencies = [ "pyo3", "pyo3-asyncio", "pyo3-build-config", - "pythonize", "qcs", "qcs-api", "qcs-api-client-common", "quil-rs", + "rigetti-pyo3", "serde_json", "tokio", ] @@ -1417,9 +1453,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1540,6 +1576,17 @@ dependencies = [ "winreg", ] +[[package]] +name = "rigetti-pyo3" +version = "0.0.1" +dependencies = [ + "num-complex", + "num-traits", + "paste", + "pyo3", + "time", +] + [[package]] name = "ring" version = "0.16.20" @@ -1621,15 +1668,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "safemem" @@ -1694,27 +1741,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1723,9 +1770,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1746,9 +1793,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", @@ -1859,9 +1906,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.103" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1896,18 +1943,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1958,9 +2005,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes", @@ -1973,7 +2020,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1988,9 +2035,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -2053,18 +2100,18 @@ checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] [[package]] name = "tonic" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b9af819e54b8f33d453655bef9b9acc171568fb49523078d0cc4e7484200ec" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", @@ -2097,9 +2144,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c6fd7c2581e36d63388a9e04c350c21beb7a8b059580b2e93993c526899ddc" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ "prettyplease", "proc-macro2", @@ -2130,9 +2177,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags", "bytes", @@ -2238,9 +2285,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicase" @@ -2259,9 +2306,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -2274,9 +2321,9 @@ dependencies = [ [[package]] name = "unindent" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ee9362deb4a96cef4d437d1ad49cffc9b9e92d202b6995674e928ce684f112" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "untrusted" @@ -2451,9 +2498,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index e8a9dbaf8..e1f69b3ed 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.24" indexmap = "1.9.1" lazy_static = "1.4.0" log = "0.4.17" -num = "0.4.0" +num = { version = "0.4.0", features = ["serde"] } qcs-api = "0.2.1" qcs-api-client-common = "0.2.7" qcs-api-client-openapi = "0.3.8" diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index f53e33bc7..dabea8f8c 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, str::FromStr}; +use num::complex::Complex32; use qcs_api_client_grpc::{ models::controller::{readout_values, ControllerJobExecutionResult}, services::controller::{ @@ -55,7 +56,7 @@ pub enum RewriteArithmeticError { /// The result of a call to [`rewrite_arithmetic`] which provides the /// information necessary to later patch-in memory values to a compiled program. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct RewriteArithmeticResult { /// The rewritten program pub program: String, @@ -193,17 +194,13 @@ pub fn build_patch_values( .iter() .map(|expr| Expression::from_str(expr)) .collect::>() - .map_err(|e| format!("Unable to interpret recalculation table: {:?}", e))?; + .map_err(|e| format!("Unable to interpret recalculation table: {e:?}"))?; rewrite_arithmetic::get_substitutions(&substitutions, memory) } -/// A convenience type that describes a Complex-64 value whose real -/// and imaginary parts of both f32. -pub type Complex64 = [f32; 2]; - /// Data from an individual register. Each variant contains a vector with the expected data type /// where each value in the vector corresponds to a shot. -#[derive(Debug, PartialEq, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] // Don't include the discriminant name in serialized output. pub enum Register { /// A register of 64-bit floating point numbers @@ -213,7 +210,7 @@ pub enum Register { /// A register of 32-bit integers I32(Vec), /// A register of 64-bit complex numbers - Complex64(Vec), + Complex64(Vec), /// A register of 8-bit integers (bytes) I8(Vec), } @@ -224,7 +221,7 @@ impl From for Register { runner::Register::F64(f) => Register::F64(f), runner::Register::I16(i) => Register::I16(i), runner::Register::Complex32(c) => { - Register::Complex64(c.iter().map(|c| [c.re, c.im]).collect()) + Register::Complex64(c.iter().map(|c| Complex32::new(c.re, c.im)).collect()) } runner::Register::I8(i) => Register::I8(i), } @@ -232,7 +229,7 @@ impl From for Register { } /// The execution readout data from a particular memory location. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct ExecutionResult { shape: Vec, data: Register, @@ -248,7 +245,7 @@ impl From for ExecutionResult { data: Register::Complex64( c.values .iter() - .map(|c| [c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0)]) + .map(|c| Complex32::new(c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0))) .collect(), ), }, @@ -262,7 +259,7 @@ impl From for ExecutionResult { } /// Execution readout data for all memory locations. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct ExecutionResults { buffers: HashMap, execution_duration_microseconds: Option, From 8a68b8350c0364f01d8d484fe301afc23403455e Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:44:30 -0800 Subject: [PATCH 03/43] chore(lib): remove newly removed lint --- crates/lib/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index eaa5444aa..0182bbbca 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -10,7 +10,6 @@ absolute_paths_not_starting_with_crate, anonymous_parameters, bad_style, - const_err, dead_code, deprecated_in_future, keyword_idents, From fd97469476ddaa1c8e98555b0d649c9710c6eac6 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:46:42 -0800 Subject: [PATCH 04/43] refactor(lib)!: use Cow instead of &str for execution --- crates/lib/src/executable.rs | 24 ++++++++++++++---------- crates/lib/src/qpu/quilc/mod.rs | 9 +++++++-- crates/lib/src/qvm/execution.rs | 6 +++--- crates/lib/src/qvm/mod.rs | 10 ++++++---- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/lib/src/executable.rs b/crates/lib/src/executable.rs index 7319ffed4..5f2a28239 100644 --- a/crates/lib/src/executable.rs +++ b/crates/lib/src/executable.rs @@ -1,6 +1,7 @@ //! This module contains the public-facing API for executing programs. [`Executable`] is the how //! users will interact with QCS, quilc, and QVM. +use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -66,7 +67,7 @@ use quil_rs::Program; pub struct Executable<'executable, 'execution> { quil: Arc, shots: u16, - readout_memory_region_names: Option>, + readout_memory_region_names: Option>>, params: Parameters, compile_with_quilc: bool, compiler_options: CompilerOpts, @@ -160,9 +161,12 @@ impl<'executable> Executable<'executable, '_> { /// } /// ``` #[must_use] - pub fn read_from(mut self, register: &'executable str) -> Self { + pub fn read_from(mut self, register: S) -> Self + where + S: Into>, + { let mut readouts = self.readout_memory_region_names.take().unwrap_or_default(); - readouts.push(register); + readouts.push(register.into()); self.readout_memory_region_names = Some(readouts); self } @@ -264,11 +268,11 @@ impl Executable<'_, '_> { self } - fn get_readouts(&self) -> &[&str] { + fn get_readouts(&self) -> &[Cow<'_, str>] { return self .readout_memory_region_names .as_ref() - .map_or(&["ro"], Vec::as_slice); + .map_or(&[Cow::Borrowed("ro")], Vec::as_slice); } /// Execute on a QVM which must be available at the configured URL (default ). @@ -527,11 +531,11 @@ pub enum Service { impl From for Error { fn from(err: ExecutionError) -> Self { match err { - ExecutionError::Unexpected(inner) => Self::Unexpected(format!("{:?}", inner)), + ExecutionError::Unexpected(inner) => Self::Unexpected(format!("{inner:?}")), ExecutionError::Quilc { .. } => Self::Connection(Service::Quilc), - ExecutionError::QcsClient(v) => Self::Unexpected(format!("{:?}", v)), - ExecutionError::IsaError(v) => Self::Unexpected(format!("{:?}", v)), - ExecutionError::ReadoutParse(v) => Self::Unexpected(format!("{:?}", v)), + ExecutionError::QcsClient(v) => Self::Unexpected(format!("{v:?}")), + ExecutionError::IsaError(v) => Self::Unexpected(format!("{v:?}")), + ExecutionError::ReadoutParse(v) => Self::Unexpected(format!("{v:?}")), ExecutionError::Quil(e) => Self::Quil(e), ExecutionError::Compilation { details } => Self::Compilation(details), ExecutionError::RewriteArithmetic(e) => Self::RewriteArithmetic(e), @@ -548,7 +552,7 @@ impl From for Error { | qvm::Error::ShotsMustBePositive | qvm::Error::RegionSizeMismatch { .. } | qvm::Error::RegionNotFound { .. } - | qvm::Error::Qvm { .. } => Self::Compilation(format!("{}", err)), + | qvm::Error::Qvm { .. } => Self::Compilation(format!("{err}")), } } } diff --git a/crates/lib/src/qpu/quilc/mod.rs b/crates/lib/src/qpu/quilc/mod.rs index a86285ddf..fc3a9a392 100644 --- a/crates/lib/src/qpu/quilc/mod.rs +++ b/crates/lib/src/qpu/quilc/mod.rs @@ -194,7 +194,7 @@ mod tests { use super::*; use qcs_api_client_openapi::models::InstructionSetArchitecture; use regex::Regex; - use std::fs::File; + use std::{borrow::Cow, fs::File}; const EXPECTED_H0_OUTPUT: &str = "MEASURE 0\n"; @@ -239,7 +239,12 @@ MEASURE 1 ro[1] .expect("Could not compile"); let mut results = crate::qvm::Execution::new(&output.to_string(true)) .unwrap() - .run(10, &["ro"], &HashMap::default(), &client.get_config()) + .run( + 10, + &[Cow::Borrowed("ro")], + &HashMap::default(), + &client.get_config(), + ) .await .expect("Could not run program on QVM"); for shot in results diff --git a/crates/lib/src/qvm/execution.rs b/crates/lib/src/qvm/execution.rs index 2f3715cad..1ab839cac 100644 --- a/crates/lib/src/qvm/execution.rs +++ b/crates/lib/src/qvm/execution.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use std::str::FromStr; +use std::{borrow::Cow, collections::HashMap}; use qcs_api_client_common::ClientConfiguration; use quil_rs::{ @@ -58,7 +58,7 @@ impl Execution { pub(crate) async fn run( &mut self, shots: u16, - readouts: &[&str], + readouts: &[Cow<'_, str>], params: &Parameters, config: &ClientConfiguration, ) -> Result, RegisterData>, Error> { @@ -108,7 +108,7 @@ impl Execution { async fn execute( &self, shots: u16, - readouts: &[&str], + readouts: &[Cow<'_, str>], config: &ClientConfiguration, ) -> Result, RegisterData>, Error> { let request = Request::new(&self.program.to_string(true), shots, readouts); diff --git a/crates/lib/src/qvm/mod.rs b/crates/lib/src/qvm/mod.rs index e17921675..33dd3f8c9 100644 --- a/crates/lib/src/qvm/mod.rs +++ b/crates/lib/src/qvm/mod.rs @@ -1,7 +1,7 @@ //! This module contains all the functionality for running Quil programs on a QVM. Specifically, //! the [`Execution`] struct in this module. -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use serde::{Deserialize, Serialize}; @@ -41,8 +41,8 @@ struct Request<'request> { } impl<'request> Request<'request> { - fn new(program: &str, shots: u16, readouts: &[&'request str]) -> Self { - let addresses: HashMap<&str, bool> = readouts.iter().map(|v| (*v, true)).collect(); + fn new(program: &str, shots: u16, readouts: &'request [Cow<'request, str>]) -> Self { + let addresses: HashMap<&str, bool> = readouts.iter().map(|v| (v.as_ref(), true)).collect(); Self { quil_instructions: program.to_string(), addresses, @@ -60,6 +60,8 @@ enum RequestType { #[cfg(test)] mod describe_request { + use std::borrow::Cow; + use super::Request; #[test] @@ -71,7 +73,7 @@ mod describe_request { #[test] fn it_uses_kebab_case_for_json() { - let request = Request::new("H 0", 10, &["ro"]); + let request = Request::new("H 0", 10, &[Cow::Borrowed("ro")]); let json_string = serde_json::to_string(&request).expect("Could not serialize QVMRequest"); assert_eq!( serde_json::from_str::(&json_string).unwrap(), From c2879aaa966707772751e92d27ed169b50fa74db Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:47:18 -0800 Subject: [PATCH 05/43] style(lib): resolve clippy lints --- crates/lib/src/qpu/execution.rs | 4 ++-- crates/lib/src/qpu/quilc/isa/mod.rs | 2 +- crates/lib/src/qpu/rewrite_arithmetic.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/lib/src/qpu/execution.rs b/crates/lib/src/qpu/execution.rs index b8867772e..48ebfa8ac 100644 --- a/crates/lib/src/qpu/execution.rs +++ b/crates/lib/src/qpu/execution.rs @@ -58,10 +58,10 @@ pub(crate) enum Error { impl From for Error { fn from(source: quilc::Error) -> Self { match source { - quilc::Error::Isa(source) => Self::Unexpected(Unexpected::Isa(format!("{:?}", source))), + quilc::Error::Isa(source) => Self::Unexpected(Unexpected::Isa(format!("{source:?}"))), quilc::Error::QuilcConnection(uri, details) => Self::Quilc { uri, - details: format!("{:?}", details), + details: format!("{details:?}"), }, quilc::Error::QuilcCompilation(details) => Self::Compilation { details }, quilc::Error::Parse(details) => Self::Compilation { diff --git a/crates/lib/src/qpu/quilc/isa/mod.rs b/crates/lib/src/qpu/quilc/isa/mod.rs index 75a056131..02049a4ae 100644 --- a/crates/lib/src/qpu/quilc/isa/mod.rs +++ b/crates/lib/src/qpu/quilc/isa/mod.rs @@ -178,7 +178,7 @@ mod describe_compiler_isa { 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"); + serde_json::to_value(compiler_isa).expect("Unable to serialize CompilerIsa"); let result = json_is_equivalent(&serialized, &expected); result.expect("JSON was not equivalent"); diff --git a/crates/lib/src/qpu/rewrite_arithmetic.rs b/crates/lib/src/qpu/rewrite_arithmetic.rs index 3094c8541..4e20fb619 100644 --- a/crates/lib/src/qpu/rewrite_arithmetic.rs +++ b/crates/lib/src/qpu/rewrite_arithmetic.rs @@ -129,7 +129,7 @@ pub(crate) fn get_substitutions( .map(|substitution: &Expression| { substitution .evaluate(&HashMap::new(), ¶ms) - .map_err(|e| format!("Could not evaluate expression {}: {:?}", substitution, e)) + .map_err(|e| format!("Could not evaluate expression {substitution}: {e:?}")) .and_then(|complex| { if complex.im == 0.0 { Ok(complex.re) From aa729d17814c3b1922e273df365cfea08b9c9bd8 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Fri, 6 Jan 2023 12:50:05 -0800 Subject: [PATCH 06/43] feat(python): start directly wrapping types from the Rust SDK --- crates/python/Cargo.toml | 2 +- crates/python/src/api.rs | 216 ++++++++++++++++++++++++++++ crates/python/src/executable.rs | 11 ++ crates/python/src/execution_data.rs | 17 +++ crates/python/src/lib.rs | 195 +++++-------------------- crates/python/src/qpu/client.rs | 42 ++++++ crates/python/src/qpu/mod.rs | 17 +++ crates/python/src/register_data.rs | 15 ++ 8 files changed, 353 insertions(+), 162 deletions(-) create mode 100644 crates/python/src/api.rs create mode 100644 crates/python/src/executable.rs create mode 100644 crates/python/src/execution_data.rs create mode 100644 crates/python/src/qpu/client.rs create mode 100644 crates/python/src/qpu/mod.rs create mode 100644 crates/python/src/register_data.rs diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index cfafcd44d..227e17f7c 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -20,10 +20,10 @@ qcs = { path = "../lib" } qcs-api-client-common = "0.2.7" pyo3 = { version = "0.17", features = ["extension-module"] } pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } -pythonize = "0.17" quil-rs = "0.15" tokio = "1.21" qcs-api = "0.2.1" +rigetti-pyo3 = { version = "0.0.1", features = ["extension-module", "complex"], path = "../../../rigetti-pyo3" } # git = "ssh://git@github.com/rigetti/rigetti-pyo3", branch = "support-fieldless-enums-data-structs" } serde_json = "1.0.86" [build-dependencies] diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs new file mode 100644 index 000000000..f0b215d12 --- /dev/null +++ b/crates/python/src/api.rs @@ -0,0 +1,216 @@ +use std::collections::HashMap; + +use pyo3::{ + create_exception, + exceptions::{PyRuntimeError, PyValueError}, + prelude::*, + pyfunction, + types::{PyComplex, PyDict, PyFloat, PyInt, PyList, PyString}, + Py, PyResult, +}; +use qcs::{ + api::{ + ExecutionResult, ExecutionResults, Register, RewriteArithmeticResult, TranslationResult, + }, + qpu::{ + quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}, + Qcs, + }, +}; +use rigetti_pyo3::{ + create_init_submodule, py_wrap_data_struct, py_wrap_type, py_wrap_union_enum, ToPython, +}; + +create_init_submodule! { + classes: [ + PyExecutionResults, + PyRewriteArithmeticResult, + PyTranslationResult + ], + errors: [ + InvalidConfigError, + ExecutionError, + TranslationError, + CompilationError, + RewriteArithmeticError, + DeviceIsaError + ], + funcs: [ + compile, + rewrite_arithmetic, + translate, + submit, + retrieve_results, + build_patch_values, + get_quilc_version + ], +} + +py_wrap_data_struct! { + PyRewriteArithmeticResult(RewriteArithmeticResult) as "RewriteArithmeticResult" { + program: String => Py, + recalculation_table: Vec => Py + } +} + +py_wrap_data_struct! { + PyTranslationResult(TranslationResult) as "TranslationResult" { + program: String => Py, + ro_sources: Option> => Option> + } +} + +py_wrap_type! { + PyExecutionResults(ExecutionResults) as "ExecutionResults"; +} + +py_wrap_union_enum! { + PyRegister(Register) as "Register" { + f64: F64 => Vec>, + i16: I16 => Vec>, + i32: I32 => Vec>, + i8: I8 => Vec>, + complex64: Complex64 => Vec> + } +} + +py_wrap_type! { + PyExecutionResult(ExecutionResult) as "ExecutionResult"; +} + +create_exception!(qcs, InvalidConfigError, PyRuntimeError); +create_exception!(qcs, ExecutionError, PyRuntimeError); +create_exception!(qcs, TranslationError, PyRuntimeError); +create_exception!(qcs, CompilationError, PyRuntimeError); +create_exception!(qcs, RewriteArithmeticError, PyRuntimeError); +create_exception!(qcs, DeviceIsaError, PyValueError); + +#[pyfunction(kwds = "**")] +pub fn compile<'a>( + py: Python<'a>, + quil: String, + target_device: String, + kwds: Option<&PyDict>, +) -> PyResult<&'a PyAny> { + let target_device: TargetDevice = + serde_json::from_str(&target_device).map_err(|e| DeviceIsaError::new_err(e.to_string()))?; + + let mut compiler_timeout = Some(DEFAULT_COMPILER_TIMEOUT); + if let Some(kwargs) = kwds { + if let Some(timeout_arg) = kwargs.get_item("timeout") { + let timeout: Result, _> = timeout_arg.extract(); + if let Ok(option) = timeout { + compiler_timeout = option + } + } + } + + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let options = CompilerOpts::default().with_timeout(compiler_timeout); + let result = qcs::api::compile(&quil, target_device, &client, options) + .map_err(|e| CompilationError::new_err(e.to_string()))?; + Ok(result) + }) +} + +#[pyfunction] +pub fn rewrite_arithmetic(native_quil: String) -> PyResult { + let native_program = native_quil + .parse::() + .map_err(|e| TranslationError::new_err(e.to_string()))?; + let result = qcs::api::rewrite_arithmetic(native_program) + .map_err(|e| RewriteArithmeticError::new_err(e.to_string()))?; + let pyed = result.into(); + Ok(pyed) +} + +#[pyfunction] +pub fn build_patch_values( + py: Python<'_>, + recalculation_table: Vec, + memory: HashMap>, +) -> PyResult> { + let memory = memory + .into_iter() + .map(|(k, v)| (k.into_boxed_str(), v)) + .collect(); + let patch_values = qcs::api::build_patch_values(&recalculation_table, &memory) + .map_err(TranslationError::new_err)?; + patch_values + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect::>() + .to_python(py) +} + +#[pyfunction] +pub fn translate( + py: Python<'_>, + native_quil: String, + num_shots: u16, + quantum_processor_id: String, +) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let result = qcs::api::translate(&native_quil, num_shots, &quantum_processor_id, &client) + .await + .map_err(|e| TranslationError::new_err(e.to_string()))?; + Python::with_gil(|py| PyTranslationResult::from(result).to_python(py)) + }) +} + +#[pyfunction] +pub fn submit( + py: Python<'_>, + program: String, + patch_values: HashMap>, + quantum_processor_id: String, + use_gateway: bool, +) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(|e| InvalidConfigError::new_err(e.to_string()))? + .with_use_gateway(use_gateway); + let job_id = qcs::api::submit(&program, patch_values, &quantum_processor_id, &client) + .await + .map_err(|e| ExecutionError::new_err(e.to_string()))?; + Ok(Python::with_gil(|_py| job_id)) + }) +} + +#[pyfunction] +pub fn retrieve_results( + py: Python<'_>, + job_id: String, + quantum_processor_id: String, + use_gateway: bool, +) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(|e| InvalidConfigError::new_err(e.to_string()))? + .with_use_gateway(use_gateway); + let results = qcs::api::retrieve_results(&job_id, &quantum_processor_id, &client) + .await + .map_err(|e| ExecutionError::new_err(e.to_string()))?; + Ok(PyExecutionResults::from(results)) + }) +} + +#[pyfunction] +pub fn get_quilc_version(py: Python<'_>) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let version = qcs::api::get_quilc_version(&client) + .map_err(|e| CompilationError::new_err(e.to_string()))?; + Ok(version) + }) +} diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs new file mode 100644 index 000000000..858d61b74 --- /dev/null +++ b/crates/python/src/executable.rs @@ -0,0 +1,11 @@ +use pyo3::pymethods; +use qcs::Executable; +use rigetti_pyo3::py_wrap_type; + +// Because Python is garbage-collected, no lifetimes can be guaranteed except `'static`. +py_wrap_type! { + PyExecutable(Executable<'static, 'static>) as "Executable"; +} + +#[pymethods] +impl PyExecutable {} diff --git a/crates/python/src/execution_data.rs b/crates/python/src/execution_data.rs new file mode 100644 index 000000000..2d5d6da16 --- /dev/null +++ b/crates/python/src/execution_data.rs @@ -0,0 +1,17 @@ +use std::{collections::HashMap, time::Duration}; + +use pyo3::{ + types::{PyDelta, PyDict, PyString}, + Py, +}; +use qcs::{Qvm, RegisterData}; +use rigetti_pyo3::py_wrap_data_struct; + +use crate::register_data::PyRegisterData; + +py_wrap_data_struct! { + PyQvm(Qvm) as "QVM" { + registers: HashMap, RegisterData> => HashMap, PyRegisterData> => Py, + duration: Option => Option> + } +} diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index a02336e27..31c518a10 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -1,165 +1,38 @@ -use pythonize::pythonize; -use qcs::api; -use qcs::qpu::client::Qcs; -use qcs::qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}; -use std::collections::HashMap; - -use pyo3::{ - create_exception, - exceptions::{PyRuntimeError, PyValueError}, - prelude::*, - types::PyDict, -}; - -create_exception!(qcs, InvalidConfigError, PyRuntimeError); -create_exception!(qcs, ExecutionError, PyRuntimeError); -create_exception!(qcs, TranslationError, PyRuntimeError); -create_exception!(qcs, CompilationError, PyRuntimeError); -create_exception!(qcs, RewriteArithmeticError, PyRuntimeError); -create_exception!(qcs, DeviceIsaError, PyValueError); - -#[pyfunction(kwds = "**")] -fn compile<'a>( - py: Python<'a>, - quil: String, - target_device: String, - kwds: Option<&PyDict>, -) -> PyResult<&'a PyAny> { - let target_device: TargetDevice = - serde_json::from_str(&target_device).map_err(|e| DeviceIsaError::new_err(e.to_string()))?; - - let mut compiler_timeout = Some(DEFAULT_COMPILER_TIMEOUT); - if let Some(kwargs) = kwds { - if let Some(timeout_arg) = kwargs.get_item("timeout") { - let timeout: Result, _> = timeout_arg.extract(); - if let Ok(option) = timeout { - compiler_timeout = option - } - } - } - - pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; - let options = CompilerOpts::default().with_timeout(compiler_timeout); - let result = api::compile(&quil, target_device, &client, options) - .map_err(|e| CompilationError::new_err(e.to_string()))?; - Ok(Python::with_gil(|_py| result)) - }) -} - -#[pyfunction] -fn rewrite_arithmetic(py: Python<'_>, native_quil: String) -> PyResult { - let native_program = native_quil - .parse::() - .map_err(|e| TranslationError::new_err(e.to_string()))?; - let result = api::rewrite_arithmetic(native_program) - .map_err(|e| RewriteArithmeticError::new_err(e.to_string()))?; - let pyed = pythonize(py, &result).map_err(|e| TranslationError::new_err(e.to_string()))?; - Ok(pyed) -} - -#[pyfunction] -fn build_patch_values( - py: Python<'_>, - recalculation_table: Vec, - memory: HashMap>, -) -> PyResult { - let memory = memory - .into_iter() - .map(|(k, v)| (k.into_boxed_str(), v)) - .collect(); - let patch_values = api::build_patch_values(&recalculation_table, &memory) - .map_err(TranslationError::new_err)?; - let patch_values = - pythonize(py, &patch_values).map_err(|e| TranslationError::new_err(e.to_string()))?; - Ok(patch_values) -} - -#[pyfunction] -fn translate( - py: Python<'_>, - native_quil: String, - num_shots: u16, - quantum_processor_id: String, -) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; - let result = api::translate(&native_quil, num_shots, &quantum_processor_id, &client) - .await - .map_err(|e| TranslationError::new_err(e.to_string()))?; - let result = Python::with_gil(|py| { - pythonize(py, &result).map_err(|e| TranslationError::new_err(e.to_string())) - })?; - Ok(result) - }) -} - -#[pyfunction] -fn submit( - py: Python<'_>, - program: String, - patch_values: HashMap>, - quantum_processor_id: String, - use_gateway: bool, -) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))? - .with_use_gateway(use_gateway); - let job_id = api::submit(&program, patch_values, &quantum_processor_id, &client) - .await - .map_err(|e| ExecutionError::new_err(e.to_string()))?; - Ok(Python::with_gil(|_py| job_id)) - }) -} - -#[pyfunction] -fn retrieve_results( - py: Python<'_>, - job_id: String, - quantum_processor_id: String, - use_gateway: bool, -) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))? - .with_use_gateway(use_gateway); - let results = api::retrieve_results(&job_id, &quantum_processor_id, &client) - .await - .map_err(|e| ExecutionError::new_err(e.to_string()))?; - let results = Python::with_gil(|py| { - pythonize(py, &results).map_err(|e| ExecutionError::new_err(e.to_string())) - })?; - Ok(results) - }) -} - -#[pyfunction] -fn get_quilc_version(py: Python<'_>) -> PyResult<&PyAny> { - pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; - let version = api::get_quilc_version(&client) - .map_err(|e| CompilationError::new_err(e.to_string()))?; - Ok(version) - }) +use pyo3::prelude::*; +use rigetti_pyo3::create_init_submodule; + +pub mod api; +pub mod executable; +pub mod execution_data; +pub mod qpu; +pub mod register_data; + +// pub use executable::{Error, Executable, ExecuteResultQPU, ExecuteResultQVM, JobHandle, Service}; +// pub use execution_data::{Qpu, Qvm, ReadoutMap}; +// pub use register_data::RegisterData; + +create_init_submodule! { + classes: [ + execution_data::PyQpu, + execution_data::PyQvm, + execution_data::PyReadoutMap + ], + funcs: [ + api::compile, + api::rewrite_arithmetic, + api::translate, + api::submit, + api::retrieve_results, + api::build_patch_values, + api::get_quilc_version + ], + submodules: [ + "api": api::init_submodule, + "qpu": qpu::init_submodule + ], } #[pymodule] -fn qcs_sdk(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(compile, m)?)?; - m.add_function(wrap_pyfunction!(rewrite_arithmetic, m)?)?; - m.add_function(wrap_pyfunction!(translate, m)?)?; - m.add_function(wrap_pyfunction!(submit, m)?)?; - m.add_function(wrap_pyfunction!(retrieve_results, m)?)?; - m.add_function(wrap_pyfunction!(build_patch_values, m)?)?; - m.add_function(wrap_pyfunction!(get_quilc_version, m)?)?; - Ok(()) +fn qcs_sdk(py: Python<'_>, m: &PyModule) -> PyResult<()> { + init_submodule("qcs_sdk", py, m) } diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs new file mode 100644 index 000000000..e59385cf6 --- /dev/null +++ b/crates/python/src/qpu/client.rs @@ -0,0 +1,42 @@ +use pyo3::{exceptions::PyRuntimeError, pymethods, Py, PyAny, PyResult, Python}; +use pyo3_asyncio::tokio::future_into_py; +use qcs::qpu::Qcs; +use rigetti_pyo3::{ + create_init_submodule, py_wrap_error, py_wrap_type, wrap_error, ToPython, ToPythonError, +}; + +create_init_submodule! { + classes: [PyQcsClient], + errors: [QcsLoadError], +} + +wrap_error! { + LoadError(qcs::qpu::client::LoadError); +} +py_wrap_error!(client, LoadError, QcsLoadError, PyRuntimeError); + +py_wrap_type! { + PyQcsClient(Qcs) as "QcsClient"; +} + +#[pymethods] +impl PyQcsClient { + // TODO: default arg + #[new] + pub fn new(py: Python<'_>, use_gateway: Option) -> PyResult { + future_into_py(py, async move { + let client = Qcs::load() + .await + .map_err(LoadError) + .map_err(ToPythonError::to_py_err)?; + + let client = match use_gateway { + None => client, + Some(use_gateway) => client.with_use_gateway(use_gateway), + }; + + Python::with_gil(|py| <_ as ToPython>>::to_python(&Self(client), py)) + })? + .extract() + } +} diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs new file mode 100644 index 000000000..313947cad --- /dev/null +++ b/crates/python/src/qpu/mod.rs @@ -0,0 +1,17 @@ +use pyo3::exceptions::PyRuntimeError; +use rigetti_pyo3::{create_init_submodule, py_wrap_error, wrap_error}; + +pub mod client; + +create_init_submodule! { + errors: [QcsIsaError], + submodules: [ + "client": client::init_submodule + ], +} + +wrap_error! { + IsaError(qcs::qpu::IsaError); +} + +py_wrap_error!(qpu, IsaError, QcsIsaError, PyRuntimeError); diff --git a/crates/python/src/register_data.rs b/crates/python/src/register_data.rs new file mode 100644 index 000000000..9f6fbc1ac --- /dev/null +++ b/crates/python/src/register_data.rs @@ -0,0 +1,15 @@ +use pyo3::{ + types::{PyComplex, PyFloat, PyInt}, + Py, +}; +use qcs::RegisterData; +use rigetti_pyo3::py_wrap_union_enum; + +py_wrap_union_enum! { + PyRegisterData(RegisterData) as "RegisterData" { + i8: I8 => Vec>>, + f64: F64 => Vec>>, + i16: I16 => Vec>>, + complex32: Complex32 => Vec>> + } +} From dbbc6e33cd0257452472ac8b64b0edd64c115623 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 12:44:51 -0800 Subject: [PATCH 07/43] refactor(lib)!: replace Box -> String, &str -> Cow --- crates/lib/src/executable.rs | 44 +++++++++++++++++++------------- crates/lib/src/execution_data.rs | 2 +- crates/lib/src/qpu/execution.rs | 21 +++++++++------ crates/lib/src/qvm/execution.rs | 10 +++----- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/crates/lib/src/executable.rs b/crates/lib/src/executable.rs index 5f2a28239..489a6187d 100644 --- a/crates/lib/src/executable.rs +++ b/crates/lib/src/executable.rs @@ -334,12 +334,13 @@ impl Executable<'_, '_> { impl<'execution> Executable<'_, 'execution> { /// Remove and return `self.qpu` if it's set and still valid. Otherwise, create a new one. - async fn qpu_for_id( - &mut self, - id: &'execution str, - ) -> Result, Error> { + async fn qpu_for_id(&mut self, id: S) -> Result, Error> + where + S: Into>, + { + let id = id.into(); if let Some(qpu) = self.qpu.take() { - if qpu.quantum_processor_id == id && qpu.shots == self.shots { + if qpu.quantum_processor_id == id.as_ref() && qpu.shots == self.shots { return Ok(qpu); } } @@ -381,10 +382,10 @@ impl<'execution> Executable<'_, 'execution> { /// 1. Missing parameters that should be filled with [`Executable::with_parameter`] /// /// [quilc]: https://github.com/quil-lang/quilc - pub async fn execute_on_qpu( - &mut self, - quantum_processor_id: &'execution str, - ) -> ExecuteResultQPU { + pub async fn execute_on_qpu(&mut self, quantum_processor_id: S) -> ExecuteResultQPU + where + S: Into>, + { let job_handle = self.submit_to_qpu(quantum_processor_id).await?; self.retrieve_results(job_handle).await } @@ -397,16 +398,20 @@ impl<'execution> Executable<'_, 'execution> { /// # Errors /// /// See [`Executable::execute_on_qpu`]. - pub async fn submit_to_qpu( + pub async fn submit_to_qpu( &mut self, - quantum_processor_id: &'execution str, - ) -> Result, Error> { + quantum_processor_id: S, + ) -> Result, Error> + where + S: Into>, + { + let quantum_processor_id = quantum_processor_id.into(); let JobHandle { job_id, readout_map, .. } = self - .qpu_for_id(quantum_processor_id) + .qpu_for_id(quantum_processor_id.clone()) .await? .submit(&self.params) .await?; @@ -562,20 +567,23 @@ impl From for Error { #[derive(Debug, Clone, PartialEq, Eq)] pub struct JobHandle<'executable> { job_id: JobId, - quantum_processor_id: &'executable str, + quantum_processor_id: Cow<'executable, str>, readout_map: HashMap, } impl<'a> JobHandle<'a> { #[must_use] - pub(crate) fn new( + pub(crate) fn new( job_id: JobId, - quantum_processor_id: &'a str, + quantum_processor_id: S, readout_map: HashMap, - ) -> Self { + ) -> Self + where + S: Into>, + { Self { job_id, - quantum_processor_id, + quantum_processor_id: quantum_processor_id.into(), readout_map, } } diff --git a/crates/lib/src/execution_data.rs b/crates/lib/src/execution_data.rs index cf0b2bb96..e5f602a37 100644 --- a/crates/lib/src/execution_data.rs +++ b/crates/lib/src/execution_data.rs @@ -15,7 +15,7 @@ use crate::RegisterData; pub struct Qvm { /// The readout data that was read from the [`Executable`](crate::Executable). /// Key is the name of the register, value is the data of the register after execution. - pub registers: HashMap, RegisterData>, + pub registers: HashMap, /// 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. /// diff --git a/crates/lib/src/qpu/execution.rs b/crates/lib/src/qpu/execution.rs index 48ebfa8ac..00606abfc 100644 --- a/crates/lib/src/qpu/execution.rs +++ b/crates/lib/src/qpu/execution.rs @@ -1,5 +1,6 @@ //! Contains QPU-specific executable stuff. +use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; use std::sync::Arc; @@ -28,7 +29,7 @@ use super::{get_isa, IsaError}; #[derive(Debug, Clone)] pub(crate) struct Execution<'a> { program: RewrittenProgram, - pub(crate) quantum_processor_id: &'a str, + pub(crate) quantum_processor_id: Cow<'a, str>, pub(crate) shots: u16, client: Arc, } @@ -113,12 +114,12 @@ impl<'a> Execution<'a> { pub(crate) async fn new( quil: Arc, shots: u16, - quantum_processor_id: &'a str, + quantum_processor_id: Cow<'a, str>, client: Arc, compile_with_quilc: bool, compiler_options: CompilerOpts, ) -> Result, Error> { - let isa = get_isa(quantum_processor_id, &client).await?; + let isa = get_isa(quantum_processor_id.as_ref(), &client).await?; let target_device = TargetDevice::try_from(isa)?; let program = if compile_with_quilc { @@ -150,7 +151,7 @@ impl<'a> Execution<'a> { /// Run on a real QPU and wait for the results. pub(crate) async fn submit(&mut self, params: &Parameters) -> Result, Error> { let EncryptedTranslationResult { job, readout_map } = translate( - self.quantum_processor_id, + self.quantum_processor_id.as_ref(), &self.program.to_string().0, self.shots.into(), self.client.as_ref(), @@ -162,7 +163,7 @@ impl<'a> Execution<'a> { .map_err(Error::Substitution)?; let job_id = submit( - self.quantum_processor_id, + self.quantum_processor_id.as_ref(), job, &patch_values, self.client.as_ref(), @@ -171,7 +172,7 @@ impl<'a> Execution<'a> { Ok(JobHandle::new( job_id, - self.quantum_processor_id, + self.quantum_processor_id.clone(), readout_map, )) } @@ -181,8 +182,12 @@ impl<'a> Execution<'a> { job_id: JobId, readout_mappings: HashMap, ) -> Result { - let response = - retrieve_results(job_id, self.quantum_processor_id, self.client.as_ref()).await?; + let response = retrieve_results( + job_id, + self.quantum_processor_id.as_ref(), + self.client.as_ref(), + ) + .await?; Ok(Qpu { readout_data: ReadoutMap::from_mappings_and_values( diff --git a/crates/lib/src/qvm/execution.rs b/crates/lib/src/qvm/execution.rs index 1ab839cac..a2e4182c7 100644 --- a/crates/lib/src/qvm/execution.rs +++ b/crates/lib/src/qvm/execution.rs @@ -61,7 +61,7 @@ impl Execution { readouts: &[Cow<'_, str>], params: &Parameters, config: &ClientConfiguration, - ) -> Result, RegisterData>, Error> { + ) -> Result, Error> { if shots == 0 { return Err(Error::ShotsMustBePositive); } @@ -110,7 +110,7 @@ impl Execution { shots: u16, readouts: &[Cow<'_, str>], config: &ClientConfiguration, - ) -> Result, RegisterData>, Error> { + ) -> Result, Error> { let request = Request::new(&self.program.to_string(true), shots, readouts); let client = reqwest::Client::new(); @@ -129,11 +129,7 @@ impl Execution { qvm_url: config.qvm_url().into(), source, }), - Ok(Response::Success(response)) => Ok(response - .registers - .into_iter() - .map(|(key, value)| (key.into_boxed_str(), value)) - .collect()), + Ok(Response::Success(response)) => Ok(response.registers), Ok(Response::Failure(response)) => Err(Error::Qvm { message: response.status, }), From 87a39e1bcce675f30d471725ef53f8df1fea2787 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 13:01:47 -0800 Subject: [PATCH 08/43] feat(python): wrap more types for Python --- Cargo.lock | 1 + crates/python/Cargo.toml | 1 + crates/python/src/executable.rs | 153 +++++++++++++++++++- crates/python/src/execution_data.rs | 54 ++++++- crates/python/src/grpc/mod.rs | 1 + crates/python/src/grpc/models/controller.rs | 102 +++++++++++++ crates/python/src/grpc/models/mod.rs | 1 + crates/python/src/lib.rs | 1 + crates/python/src/qpu/mod.rs | 1 + crates/python/src/qpu/quilc/mod.rs | 28 ++++ 10 files changed, 333 insertions(+), 10 deletions(-) create mode 100644 crates/python/src/grpc/mod.rs create mode 100644 crates/python/src/grpc/models/controller.rs create mode 100644 crates/python/src/grpc/models/mod.rs create mode 100644 crates/python/src/qpu/quilc/mod.rs diff --git a/Cargo.lock b/Cargo.lock index acecda02d..44c7b5b2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1422,6 +1422,7 @@ dependencies = [ "qcs", "qcs-api", "qcs-api-client-common", + "qcs-api-client-grpc", "quil-rs", "rigetti-pyo3", "serde_json", diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index 227e17f7c..2351d9f6c 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["cdylib"] [dependencies] qcs = { path = "../lib" } qcs-api-client-common = "0.2.7" +qcs-api-client-grpc = "0.2.7" pyo3 = { version = "0.17", features = ["extension-module"] } pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } quil-rs = "0.15" diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs index 858d61b74..072a5643b 100644 --- a/crates/python/src/executable.rs +++ b/crates/python/src/executable.rs @@ -1,11 +1,154 @@ -use pyo3::pymethods; -use qcs::Executable; -use rigetti_pyo3::py_wrap_type; +use std::sync::Arc; + +use pyo3::{ + exceptions::PyRuntimeError, pyclass, pymethods, types::PyDict, FromPyObject, Py, PyAny, + PyResult, Python, +}; +use qcs::{Error, Executable, JobHandle, Qpu, Qvm, Service}; +use rigetti_pyo3::{ + impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type, wrap_error, + PyWrapper, ToPython, ToPythonError, +}; +use tokio::sync::Mutex; + +use crate::qpu::quilc::PyCompilerOpts; + +wrap_error!(ExecutionError(Error)); + +py_wrap_error!( + executable, + ExecutionError, + QcsExecutionError, + PyRuntimeError +); // Because Python is garbage-collected, no lifetimes can be guaranteed except `'static`. +// +// `Arc>` to work around https://github.com/awestlake87/pyo3-asyncio/issues/50 +py_wrap_type! { + PyExecutable(Arc>>) as "Executable"; +} + +impl_as_mut_for_wrapper!(PyExecutable); + +#[pyclass] +#[pyo3(name = "ExeParameter")] +#[derive(FromPyObject)] +pub struct PyParameter { + name: String, + index: usize, + value: f64, +} + +#[pymethods] +impl PyExecutable { + #[new] + pub fn new( + quil: String, + registers: Vec, + parameters: Vec, + shots: Option, + compile_with_quilc: Option, + compiler_options: Option, + ) -> Self { + let mut exe = Executable::from_quil(quil); + + for reg in registers { + exe = exe.read_from(reg); + } + + for param in parameters { + exe.with_parameter(param.name, param.index, param.value); + } + + if let Some(shots) = shots { + exe = exe.with_shots(shots); + } + + if let Some(compile_with_quilc) = compile_with_quilc { + exe = exe.compile_with_quilc(compile_with_quilc); + } + + if let Some(options) = compiler_options { + exe = exe.compiler_options(options.into_inner()); + } + + Self::from(Arc::new(Mutex::new(exe))) + } + + pub fn execute_on_qvm<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { + let arc = self.as_inner().clone(); + pyo3_asyncio::tokio::local_future_into_py(py, async move { + arc.lock() + .await + .execute_on_qvm() + .await + .map(Qvm::from) + .map(|qvm| Python::with_gil(|py| qvm.to_python(py))) + .map_err(ExecutionError::from) + .map_err(ExecutionError::to_py_err)? + }) + } + + pub fn execute_on_qpu<'py>( + &self, + py: Python<'py>, + quantum_processor_id: String, + ) -> PyResult<&'py PyAny> { + let arc = self.as_inner().clone(); + pyo3_asyncio::tokio::local_future_into_py(py, async move { + arc.lock() + .await + .execute_on_qpu(quantum_processor_id) + .await + .map(Qpu::from) + .map(|qpu| Python::with_gil(|py| qpu.to_python(py))) + .map_err(ExecutionError::from) + .map_err(ExecutionError::to_py_err)? + }) + } + + pub fn retrieve_results<'py>( + &mut self, + py: Python<'py>, + job_handle: PyJobHandle, + ) -> PyResult<&'py PyAny> { + let arc = self.as_inner().clone(); + pyo3_asyncio::tokio::local_future_into_py(py, async move { + arc.lock() + .await + .retrieve_results(job_handle.into_inner()) + .await + .map(Qpu::from) + .map(|qpu| Python::with_gil(|py| qpu.to_python(py))) + .map_err(ExecutionError::from) + .map_err(ExecutionError::to_py_err)? + }) + } +} + +py_wrap_simple_enum! { + PyService(Service) as "Service" { + Quilc, + Qvm, + Qcs, + Qpu + } +} + py_wrap_type! { - PyExecutable(Executable<'static, 'static>) as "Executable"; + PyJobHandle(JobHandle<'static>); } #[pymethods] -impl PyExecutable {} +impl PyJobHandle { + #[getter] + pub fn job_id(&self) -> &str { + self.as_inner().job_id() + } + + #[getter] + pub fn readout_map(&self, py: Python) -> PyResult> { + self.as_inner().readout_map().to_python(py) + } +} diff --git a/crates/python/src/execution_data.rs b/crates/python/src/execution_data.rs index 2d5d6da16..076881545 100644 --- a/crates/python/src/execution_data.rs +++ b/crates/python/src/execution_data.rs @@ -1,17 +1,61 @@ use std::{collections::HashMap, time::Duration}; use pyo3::{ - types::{PyDelta, PyDict, PyString}, - Py, + pymethods, + types::{PyDelta, PyDict}, + Py, PyResult, Python, }; -use qcs::{Qvm, RegisterData}; -use rigetti_pyo3::py_wrap_data_struct; +use qcs::{Qpu, Qvm, ReadoutMap, RegisterData}; +use qcs_api_client_grpc::models::controller::{readout_values::Values, ReadoutValues}; +use rigetti_pyo3::{py_wrap_data_struct, py_wrap_type, PyWrapper, ToPython}; +use crate::grpc::models::controller::PyReadoutValuesValues; use crate::register_data::PyRegisterData; py_wrap_data_struct! { PyQvm(Qvm) as "QVM" { - registers: HashMap, RegisterData> => HashMap, PyRegisterData> => Py, + registers: HashMap => HashMap => Py, duration: Option => Option> } } + +py_wrap_data_struct! { + PyQpu(Qpu) as "QPU" { + readout_data: ReadoutMap => PyReadoutMap, + duration: Option => Option> + } +} + +// From gRPC +py_wrap_data_struct! { + PyReadoutValues(ReadoutValues) as "ReadoutValues" { + values: Option => Option + } +} + +py_wrap_type! { + PyReadoutMap(ReadoutMap) as "ReadoutMap"; +} + +#[pymethods] +impl PyReadoutMap { + pub fn get_readout_values(&self, field: String, index: u64) -> Option { + self.as_inner() + .get_readout_values(field, index) + .map(PyReadoutValues::from) + } + + pub fn get_readout_values_for_field( + &self, + py: Python, + field: &str, + ) -> PyResult>>> { + let op = self.as_inner().get_readout_values_for_field(field)?; + op.map(|list| { + list.into_iter() + .map(|op| op.to_python(py)) + .collect::>() + }) + .transpose() + } +} diff --git a/crates/python/src/grpc/mod.rs b/crates/python/src/grpc/mod.rs new file mode 100644 index 000000000..c446ac883 --- /dev/null +++ b/crates/python/src/grpc/mod.rs @@ -0,0 +1 @@ +pub mod models; diff --git a/crates/python/src/grpc/models/controller.rs b/crates/python/src/grpc/models/controller.rs new file mode 100644 index 000000000..790fff7d5 --- /dev/null +++ b/crates/python/src/grpc/models/controller.rs @@ -0,0 +1,102 @@ +use pyo3::{ + prelude::*, + types::{PyComplex, PyInt}, +}; +use qcs_api_client_grpc::models::controller::{ + readout_values::Values, Complex64, Complex64ReadoutValues, IntegerReadoutValues, ReadoutValues, +}; +use rigetti_pyo3::num_complex::Complex32 as NumComplex32; +use rigetti_pyo3::{py_wrap_data_struct, py_wrap_union_enum, PyTryFrom, ToPython}; + +py_wrap_data_struct! { + PyReadoutValues(ReadoutValues) as "ReadoutValues" { + values: Option => Option + } +} + +py_wrap_union_enum! { + PyReadoutValuesValues(Values) as "ReadoutValuesValues" { + integer_values: IntegerValues => PyIntegerReadoutValues, + complex_values: ComplexValues => PyComplexReadoutValues + } +} + +py_wrap_data_struct! { + PyIntegerReadoutValues(IntegerReadoutValues) as "IntegerReadoutValues" { + values: Vec => Vec> + } +} + +#[repr(transparent)] +#[derive(Clone)] +struct Complex64Wrapper(Complex64); + +impl From for Complex64Wrapper { + fn from(value: NumComplex32) -> Self { + Self(Complex64 { + real: Some(value.re), + imaginary: Some(value.im), + }) + } +} + +impl From for NumComplex32 { + fn from(value: Complex64Wrapper) -> Self { + Self { + re: value.0.real.unwrap_or_default(), + im: value.0.imaginary.unwrap_or_default(), + } + } +} + +impl ToPyObject for Complex64Wrapper { + fn to_object(&self, py: Python<'_>) -> PyObject { + NumComplex32::from(self.clone()).to_object(py) + } +} + +impl ToPython> for Complex64Wrapper { + fn to_python(&self, py: Python) -> PyResult> { + NumComplex32::from(self.clone()).to_python(py) + } +} + +impl PyTryFrom for Complex64Wrapper { + fn py_try_from(py: Python, item: &PyComplex) -> PyResult { + let complex = NumComplex32::py_try_from(py, item)?; + Ok(Self::from(complex)) + } +} + +impl PyTryFrom> for Complex64Wrapper { + fn py_try_from(py: Python, item: &Py) -> PyResult { + let complex = NumComplex32::py_try_from(py, item)?; + Ok(Self::from(complex)) + } +} + +impl PyTryFrom for Complex64 { + fn py_try_from(_py: Python, item: &Complex64Wrapper) -> PyResult { + Ok(item.0.clone()) + } +} + +impl ToPython for Complex64 { + fn to_python(&self, _py: Python) -> PyResult { + Ok(Complex64Wrapper(self.clone())) + } +} + +//impl ToPython for Complex64Wrapper {} + +impl PyTryFrom for Complex64Wrapper { + fn py_try_from(_py: Python, item: &Complex64) -> PyResult { + Ok(Self(item.clone())) + } +} + +py_wrap_data_struct! { + PyComplexReadoutValues(Complex64ReadoutValues) as "ComplexReadoutValues" { + values: Vec => Vec => Vec> + } +} diff --git a/crates/python/src/grpc/models/mod.rs b/crates/python/src/grpc/models/mod.rs new file mode 100644 index 000000000..cb9e0ac5c --- /dev/null +++ b/crates/python/src/grpc/models/mod.rs @@ -0,0 +1 @@ +pub mod controller; diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 31c518a10..e4ac3ef66 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -4,6 +4,7 @@ use rigetti_pyo3::create_init_submodule; pub mod api; pub mod executable; pub mod execution_data; +pub mod grpc; pub mod qpu; pub mod register_data; diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs index 313947cad..f445dbfa1 100644 --- a/crates/python/src/qpu/mod.rs +++ b/crates/python/src/qpu/mod.rs @@ -2,6 +2,7 @@ use pyo3::exceptions::PyRuntimeError; use rigetti_pyo3::{create_init_submodule, py_wrap_error, wrap_error}; pub mod client; +pub mod quilc; create_init_submodule! { errors: [QcsIsaError], diff --git a/crates/python/src/qpu/quilc/mod.rs b/crates/python/src/qpu/quilc/mod.rs new file mode 100644 index 000000000..9294653a6 --- /dev/null +++ b/crates/python/src/qpu/quilc/mod.rs @@ -0,0 +1,28 @@ +use pyo3::{exceptions::PyRuntimeError, pymethods}; +use qcs::qpu::quilc::CompilerOpts; +use rigetti_pyo3::{py_wrap_error, py_wrap_type, wrap_error}; + +py_wrap_type! { + #[derive(Default)] + PyCompilerOpts(CompilerOpts) as "CompilerOpts"; +} + +#[pymethods] +impl PyCompilerOpts { + #[new] + pub fn new(timeout: Option) -> Self { + Self::from(CompilerOpts::new().with_timeout(timeout)) + } + + #[staticmethod] + #[allow(clippy::should_implement_trait)] + pub fn default() -> Self { + ::default() + } +} + +wrap_error!(Error(qcs::qpu::quilc::Error)); + +py_wrap_error!(quilc, Error, QuilcError, PyRuntimeError); + +// TODO: TargetDevice From 6ad951b664dbf162a1af1f2ed5e7e56a9f774e7d Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 15:21:24 -0800 Subject: [PATCH 09/43] feat(python): add missing wrappers for remaining types --- crates/python/src/api.rs | 11 +++++++++-- crates/python/src/lib.rs | 9 ++++++++- crates/python/src/qpu/client.rs | 27 ++++++++++++++++++++++++++- crates/python/src/qpu/mod.rs | 3 ++- crates/python/src/qpu/quilc/mod.rs | 20 ++++++++++++++++---- 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs index f0b215d12..ca8bfb975 100644 --- a/crates/python/src/api.rs +++ b/crates/python/src/api.rs @@ -18,12 +18,15 @@ use qcs::{ }, }; use rigetti_pyo3::{ - create_init_submodule, py_wrap_data_struct, py_wrap_type, py_wrap_union_enum, ToPython, + create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, py_wrap_union_enum, + wrap_error, ToPython, }; create_init_submodule! { classes: [ + PyExecutionResult, PyExecutionResults, + PyRegister, PyRewriteArithmeticResult, PyTranslationResult ], @@ -33,7 +36,8 @@ create_init_submodule! { TranslationError, CompilationError, RewriteArithmeticError, - DeviceIsaError + DeviceIsaError, + QcsSubmitError ], funcs: [ compile, @@ -85,6 +89,9 @@ create_exception!(qcs, CompilationError, PyRuntimeError); create_exception!(qcs, RewriteArithmeticError, PyRuntimeError); create_exception!(qcs, DeviceIsaError, PyValueError); +wrap_error!(SubmitError(qcs::api::SubmitError)); +py_wrap_error!(api, SubmitError, QcsSubmitError, PyRuntimeError); + #[pyfunction(kwds = "**")] pub fn compile<'a>( py: Python<'a>, diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index e4ac3ef66..4646a9eeb 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -16,7 +16,14 @@ create_init_submodule! { classes: [ execution_data::PyQpu, execution_data::PyQvm, - execution_data::PyReadoutMap + execution_data::PyReadoutMap, + executable::PyExecutable, + executable::PyJobHandle, + executable::PyService, + register_data::PyRegisterData + ], + errors: [ + executable::QcsExecutionError ], funcs: [ api::compile, diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index e59385cf6..4311262b5 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -7,7 +7,12 @@ use rigetti_pyo3::{ create_init_submodule! { classes: [PyQcsClient], - errors: [QcsLoadError], + errors: [ + QcsGrpcClientError, + QcsGrpcEndpointError, + QcsGrpcError, + QcsLoadError + ], } wrap_error! { @@ -15,6 +20,26 @@ wrap_error! { } py_wrap_error!(client, LoadError, QcsLoadError, PyRuntimeError); +wrap_error! { + GrpcError(qcs::qpu::client::GrpcError); +} +py_wrap_error!(client, GrpcError, QcsGrpcError, PyRuntimeError); + +wrap_error! { + GrpcClientError(qcs::qpu::client::GrpcClientError); +} +py_wrap_error!(client, GrpcClientError, QcsGrpcClientError, PyRuntimeError); + +wrap_error! { + GrpcEndpointError(qcs::qpu::client::GrpcEndpointError); +} +py_wrap_error!( + client, + GrpcEndpointError, + QcsGrpcEndpointError, + PyRuntimeError +); + py_wrap_type! { PyQcsClient(Qcs) as "QcsClient"; } diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs index f445dbfa1..5037ceb22 100644 --- a/crates/python/src/qpu/mod.rs +++ b/crates/python/src/qpu/mod.rs @@ -7,7 +7,8 @@ pub mod quilc; create_init_submodule! { errors: [QcsIsaError], submodules: [ - "client": client::init_submodule + "client": client::init_submodule, + "quilc": quilc::init_submodule ], } diff --git a/crates/python/src/qpu/quilc/mod.rs b/crates/python/src/qpu/quilc/mod.rs index 9294653a6..e5f19c9e6 100644 --- a/crates/python/src/qpu/quilc/mod.rs +++ b/crates/python/src/qpu/quilc/mod.rs @@ -1,6 +1,17 @@ use pyo3::{exceptions::PyRuntimeError, pymethods}; -use qcs::qpu::quilc::CompilerOpts; -use rigetti_pyo3::{py_wrap_error, py_wrap_type, wrap_error}; +use qcs::qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}; +use rigetti_pyo3::{ + create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, +}; + +create_init_submodule! { + classes: [ + PyCompilerOpts, + PyTargetDevice + ], + consts: [DEFAULT_COMPILER_TIMEOUT], + errors: [QuilcError], +} py_wrap_type! { #[derive(Default)] @@ -22,7 +33,8 @@ impl PyCompilerOpts { } wrap_error!(Error(qcs::qpu::quilc::Error)); - py_wrap_error!(quilc, Error, QuilcError, PyRuntimeError); -// TODO: TargetDevice +py_wrap_struct! { + PyTargetDevice(TargetDevice) as "TargetDevice" {} +} From d1cf077bdcbbab75ec779f896637f83548cc5852 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 15:38:32 -0800 Subject: [PATCH 10/43] refactor(python): change how Complex64ReadoutValues converts to/from Python --- crates/python/src/grpc/models/controller.rs | 101 ++++++-------------- 1 file changed, 29 insertions(+), 72 deletions(-) diff --git a/crates/python/src/grpc/models/controller.rs b/crates/python/src/grpc/models/controller.rs index 790fff7d5..cd8302023 100644 --- a/crates/python/src/grpc/models/controller.rs +++ b/crates/python/src/grpc/models/controller.rs @@ -1,11 +1,11 @@ use pyo3::{ prelude::*, - types::{PyComplex, PyInt}, + types::{PyComplex, PyInt, PyList}, }; use qcs_api_client_grpc::models::controller::{ readout_values::Values, Complex64, Complex64ReadoutValues, IntegerReadoutValues, ReadoutValues, }; -use rigetti_pyo3::num_complex::Complex32 as NumComplex32; +use rigetti_pyo3::{num_complex::Complex32 as NumComplex32, py_wrap_struct}; use rigetti_pyo3::{py_wrap_data_struct, py_wrap_union_enum, PyTryFrom, ToPython}; py_wrap_data_struct! { @@ -27,76 +27,33 @@ py_wrap_data_struct! { } } -#[repr(transparent)] -#[derive(Clone)] -struct Complex64Wrapper(Complex64); - -impl From for Complex64Wrapper { - fn from(value: NumComplex32) -> Self { - Self(Complex64 { - real: Some(value.re), - imaginary: Some(value.im), - }) - } -} - -impl From for NumComplex32 { - fn from(value: Complex64Wrapper) -> Self { - Self { - re: value.0.real.unwrap_or_default(), - im: value.0.imaginary.unwrap_or_default(), - } - } -} - -impl ToPyObject for Complex64Wrapper { - fn to_object(&self, py: Python<'_>) -> PyObject { - NumComplex32::from(self.clone()).to_object(py) - } -} - -impl ToPython> for Complex64Wrapper { - fn to_python(&self, py: Python) -> PyResult> { - NumComplex32::from(self.clone()).to_python(py) - } -} - -impl PyTryFrom for Complex64Wrapper { - fn py_try_from(py: Python, item: &PyComplex) -> PyResult { - let complex = NumComplex32::py_try_from(py, item)?; - Ok(Self::from(complex)) - } -} - -impl PyTryFrom> for Complex64Wrapper { - fn py_try_from(py: Python, item: &Py) -> PyResult { - let complex = NumComplex32::py_try_from(py, item)?; - Ok(Self::from(complex)) - } -} - -impl PyTryFrom for Complex64 { - fn py_try_from(_py: Python, item: &Complex64Wrapper) -> PyResult { - Ok(item.0.clone()) - } -} - -impl ToPython for Complex64 { - fn to_python(&self, _py: Python) -> PyResult { - Ok(Complex64Wrapper(self.clone())) - } -} - -//impl ToPython for Complex64Wrapper {} - -impl PyTryFrom for Complex64Wrapper { - fn py_try_from(_py: Python, item: &Complex64) -> PyResult { - Ok(Self(item.clone())) - } -} - -py_wrap_data_struct! { +py_wrap_struct! { PyComplexReadoutValues(Complex64ReadoutValues) as "ComplexReadoutValues" { - values: Vec => Vec => Vec> + py -> rs { + list: Py => Complex64ReadoutValues { + let list = >>::py_try_from(py, &list)?; + let values = list.into_iter().map(|complex| { + let complex = NumComplex32::py_try_from(py, &complex)?; + Ok::<_, PyErr>(Complex64 { + real: Some(complex.re), + imaginary: Some(complex.im), + }) + }).collect::>()?; + + Ok::<_, PyErr>(Complex64ReadoutValues { values }) + } + }, + rs -> py { + values: Complex64ReadoutValues => Py { + let list = values.values.into_iter().map(|complex| { + NumComplex32 { + re: complex.real.unwrap_or_default(), + im: complex.imaginary.unwrap_or_default(), + } + }).collect::>(); + + list.to_python(py) + } + } } } From e847da7e97f8d9644e7c6d91c99f4e5d13f49030 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 16:08:09 -0800 Subject: [PATCH 11/43] chore(python): use rigetti-pyo3 from git, not local path --- Cargo.lock | 1 + crates/python/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 44c7b5b2c..701c32dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1580,6 +1580,7 @@ dependencies = [ [[package]] name = "rigetti-pyo3" version = "0.0.1" +source = "git+ssh://git@github.com/rigetti/rigetti-pyo3?branch=support-fieldless-enums-data-structs#fec44462f47ce2ab63ecdac797842938ddd11903" dependencies = [ "num-complex", "num-traits", diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index 2351d9f6c..f7d363182 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -24,7 +24,7 @@ pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } quil-rs = "0.15" tokio = "1.21" qcs-api = "0.2.1" -rigetti-pyo3 = { version = "0.0.1", features = ["extension-module", "complex"], path = "../../../rigetti-pyo3" } # git = "ssh://git@github.com/rigetti/rigetti-pyo3", branch = "support-fieldless-enums-data-structs" } +rigetti-pyo3 = { version = "0.0.1", features = ["extension-module", "complex"], git = "ssh://git@github.com/rigetti/rigetti-pyo3", branch = "support-fieldless-enums-data-structs" } serde_json = "1.0.86" [build-dependencies] From 1ae12989cca19e61c28b8d9653d2e5a2e61da9e5 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 9 Jan 2023 16:36:12 -0800 Subject: [PATCH 12/43] feat(python): define default arguments to methods --- crates/python/src/executable.rs | 8 ++++++++ crates/python/src/qpu/client.rs | 1 + crates/python/src/qpu/quilc/mod.rs | 1 + 3 files changed, 10 insertions(+) diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs index 072a5643b..b2ae881fe 100644 --- a/crates/python/src/executable.rs +++ b/crates/python/src/executable.rs @@ -43,6 +43,14 @@ pub struct PyParameter { #[pymethods] impl PyExecutable { #[new] + #[args( + "/", + registers = "Vec::new()", + parameters = "Vec::new()", + shots = "None", + compile_with_quilc = "None", + compiler_options = "None" + )] pub fn new( quil: String, registers: Vec, diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index 4311262b5..ca1af0fd0 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -48,6 +48,7 @@ py_wrap_type! { impl PyQcsClient { // TODO: default arg #[new] + #[args("/", use_gateway = "None")] pub fn new(py: Python<'_>, use_gateway: Option) -> PyResult { future_into_py(py, async move { let client = Qcs::load() diff --git a/crates/python/src/qpu/quilc/mod.rs b/crates/python/src/qpu/quilc/mod.rs index e5f19c9e6..9eb74f2aa 100644 --- a/crates/python/src/qpu/quilc/mod.rs +++ b/crates/python/src/qpu/quilc/mod.rs @@ -21,6 +21,7 @@ py_wrap_type! { #[pymethods] impl PyCompilerOpts { #[new] + #[args("/", timeout = "DEFAULT_COMPILER_TIMEOUT")] pub fn new(timeout: Option) -> Self { Self::from(CompilerOpts::new().with_timeout(timeout)) } From 11367996455d37ed2f9dfd01f07950e4f0780704 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 19 Jan 2023 09:06:16 -0700 Subject: [PATCH 13/43] feat: add more constructor helpers to qpu client --- .gitignore | 3 + Cargo.lock | 52 ++++++++++-- crates/lib/Cargo.toml | 6 +- crates/lib/src/executable.rs | 2 +- crates/lib/src/qpu/client.rs | 4 +- crates/python/Cargo.toml | 4 +- crates/python/src/qpu/client.rs | 142 ++++++++++++++++++++++++++++++-- 7 files changed, 190 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 4c9bc5cdc..87b569986 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ qcs-api/docs # macOS .DS_Store + +# Python crate test cache +__pycache__ diff --git a/Cargo.lock b/Cargo.lock index 701c32dce..bc04483e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1330,8 +1330,9 @@ dependencies = [ "maplit", "num", "qcs-api", - "qcs-api-client-common", - "qcs-api-client-grpc", + "qcs-api-client-common 0.3.0", + "qcs-api-client-grpc 0.2.7", + "qcs-api-client-grpc 0.3.0", "qcs-api-client-openapi", "quil-rs", "regex", @@ -1380,6 +1381,22 @@ dependencies = [ "toml 0.5.10", ] +[[package]] +name = "qcs-api-client-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b09ac85ca2a9839e81d72c2cd8c7a0995984e52d5f78c0d0d6ad00da075589" +dependencies = [ + "dirs", + "futures", + "jsonwebtoken", + "reqwest", + "serde", + "thiserror", + "tokio", + "toml 0.5.10", +] + [[package]] name = "qcs-api-client-grpc" version = "0.2.7" @@ -1391,7 +1408,26 @@ dependencies = [ "pbjson-build", "prost", "prost-build", - "qcs-api-client-common", + "qcs-api-client-common 0.2.7", + "serde", + "thiserror", + "tonic", + "tonic-build", + "tower", +] + +[[package]] +name = "qcs-api-client-grpc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "623a691262ebdb46b72d1797682e2cc5fba1c859cacc17d58a2b59c9efe9ac77" +dependencies = [ + "http-body", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "qcs-api-client-common 0.3.0", "serde", "thiserror", "tonic", @@ -1401,11 +1437,11 @@ dependencies = [ [[package]] name = "qcs-api-client-openapi" -version = "0.3.8" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de8ce34a5f6d100626d19f28219a2c791f11d7809a36521979c3817dfaac8f3a" +checksum = "8a99306535343c8d28a013fe7489a9c52c7aeb4d64029f98444f44dadacc8334" dependencies = [ - "qcs-api-client-common", + "qcs-api-client-common 0.3.0", "reqwest", "serde", "serde_json", @@ -1421,8 +1457,8 @@ dependencies = [ "pyo3-build-config", "qcs", "qcs-api", - "qcs-api-client-common", - "qcs-api-client-grpc", + "qcs-api-client-common 0.3.0", + "qcs-api-client-grpc 0.3.0", "quil-rs", "rigetti-pyo3", "serde_json", diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index e1f69b3ed..df4edf7a1 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -21,9 +21,9 @@ lazy_static = "1.4.0" log = "0.4.17" num = { version = "0.4.0", features = ["serde"] } qcs-api = "0.2.1" -qcs-api-client-common = "0.2.7" -qcs-api-client-openapi = "0.3.8" -qcs-api-client-grpc = "0.2.7" +qcs-api-client-common = "0.3.0" +qcs-api-client-openapi = "0.4.0" +qcs-api-client-grpc = "0.3.0" quil-rs = "0.15" reqwest = { version = "0.11.12", default-features = false, features = ["rustls-tls", "json"] } rmp-serde = "1.1.1" diff --git a/crates/lib/src/executable.rs b/crates/lib/src/executable.rs index 489a6187d..78ea5c35a 100644 --- a/crates/lib/src/executable.rs +++ b/crates/lib/src/executable.rs @@ -313,7 +313,7 @@ impl Executable<'_, '_> { if let Some(config) = &self.config { Ok(config.clone()) } else { - let config = ClientConfiguration::load().await?; + let config = ClientConfiguration::load_default().await?; self.config = Some(config.clone()); Ok(config) } diff --git a/crates/lib/src/qpu/client.rs b/crates/lib/src/qpu/client.rs index 6cc8f6a2f..0b00f93bc 100644 --- a/crates/lib/src/qpu/client.rs +++ b/crates/lib/src/qpu/client.rs @@ -36,7 +36,9 @@ pub struct Qcs { impl Qcs { /// Create a [`Qcs`] and initialize it with the user's default [`ClientConfiguration`] pub async fn load() -> Result { - ClientConfiguration::load().await.map(Self::with_config) + ClientConfiguration::load_default() + .await + .map(Self::with_config) } /// Create a [`Qcs`] and initialize it with the given [`ClientConfiguration`] diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index f7d363182..e56a90bd0 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -17,8 +17,8 @@ crate-type = ["cdylib"] [dependencies] qcs = { path = "../lib" } -qcs-api-client-common = "0.2.7" -qcs-api-client-grpc = "0.2.7" +qcs-api-client-common = "0.3.0" +qcs-api-client-grpc = "0.3.0" pyo3 = { version = "0.17", features = ["extension-module"] } pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } quil-rs = "0.15" diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index ca1af0fd0..c50f74a74 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -1,12 +1,24 @@ -use pyo3::{exceptions::PyRuntimeError, pymethods, Py, PyAny, PyResult, Python}; +use pyo3::{ + conversion::IntoPy, exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyErr, + PyResult, Python, +}; use pyo3_asyncio::tokio::future_into_py; use qcs::qpu::Qcs; +use qcs_api_client_common::{ + configuration::{AuthServer, BuildError, ClientConfigurationBuilder, Tokens}, + ClientConfiguration, +}; use rigetti_pyo3::{ - create_init_submodule, py_wrap_error, py_wrap_type, wrap_error, ToPython, ToPythonError, + create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, ToPython, + ToPythonError, }; create_init_submodule! { - classes: [PyQcsClient], + classes: [ + PyQcsClient, + PyQcsClientAuthServer, + PyQcsClientTokens + ], errors: [ QcsGrpcClientError, QcsGrpcEndpointError, @@ -40,19 +52,133 @@ py_wrap_error!( PyRuntimeError ); +wrap_error!(ConfigurationBuildError(BuildError)); +py_wrap_error!( + qcs, + ConfigurationBuildError, + QcsConfigurationBuildError, + PyRuntimeError +); + +fn get_pydict_str(py_dict: &PyDict, key: &str) -> Option { + py_dict.get_item(key)?.extract().ok() +} + +py_wrap_struct! { + PyQcsClientAuthServer(AuthServer) { + py -> rs { + py_dict: Py => AuthServer { + let py_dict = py_dict.as_ref(py); + let mut auth_server = AuthServer::default(); + if let Some(client_id) = get_pydict_str(py_dict, "client_id") { + auth_server = auth_server.set_client_id(client_id); + } + if let Some(issuer) = get_pydict_str(py_dict, "issuer") { + auth_server = auth_server.set_issuer(issuer); + } + Ok::<_, PyErr>(auth_server) + } + }, + rs -> py { + rs_struct: AuthServer => Py { + let obj = PyDict::new(py); + obj.set_item("client_id", rs_struct.client_id())?; + obj.set_item("issuer", rs_struct.issuer())?; + Ok(obj.into_py(py)) + } + } + } +} + +py_wrap_struct! { + PyQcsClientTokens(Tokens) { + py -> rs { + py_dict: Py => Tokens { + let py_dict = py_dict.as_ref(py); + let bearer_access_token = get_pydict_str(py_dict, "bearer_access_token"); + let refresh_token = get_pydict_str(py_dict, "refresh_token"); + Ok::<_, PyErr>(Tokens { bearer_access_token, refresh_token }) + } + }, + rs -> py { + rs_struct: Tokens => Py { + let obj = PyDict::new(py); + obj.set_item("bearer_access_token", rs_struct.bearer_access_token)?; + obj.set_item("refresh_token", rs_struct.refresh_token)?; + Ok(obj.into_py(py)) + } + } + } +} + py_wrap_type! { PyQcsClient(Qcs) as "QcsClient"; } #[pymethods] impl PyQcsClient { - // TODO: default arg #[new] - #[args("/", use_gateway = "None")] - pub fn new(py: Python<'_>, use_gateway: Option) -> PyResult { + #[args( + "/", + tokens = "None", + api_url = "None", + auth_server = "None", + grpc_api_url = "None", + quilc_url = "None", + qvm_url = "None" + )] + pub fn new( + tokens: Option, + api_url: Option, + auth_server: Option, + grpc_api_url: Option, + quilc_url: Option, + qvm_url: Option, + ) -> PyResult { + let mut builder = ClientConfigurationBuilder::default(); + if let Some(tokens) = tokens { + builder = builder.set_tokens(tokens.0); + } + if let Some(api_url) = api_url { + builder = builder.set_api_url(api_url); + } + if let Some(auth_server) = auth_server { + builder = builder.set_auth_server(auth_server.0); + } + if let Some(grpc_api_url) = grpc_api_url { + builder = builder.set_grpc_api_url(grpc_api_url); + } + if let Some(quilc_url) = quilc_url { + builder = builder.set_quilc_url(quilc_url); + } + if let Some(qvm_url) = qvm_url { + builder = builder.set_grpc_api_url(qvm_url); + } + let client = builder + .build() + .map(Qcs::with_config) + .map_err(ConfigurationBuildError::from) + .map_err(ConfigurationBuildError::to_py_err)?; + + Ok(Self(client)) + } + + // TODO: default arg + #[staticmethod] + #[args("/", profile_name = "None", use_gateway = "None")] + pub fn load( + py: Python<'_>, + profile_name: Option, + use_gateway: Option, + ) -> PyResult { future_into_py(py, async move { - let client = Qcs::load() - .await + let config = match profile_name { + Some(profile_name) => ClientConfiguration::load_profile(profile_name).await, + None => ClientConfiguration::load_default().await, + }; + + let client = config + .map(Qcs::with_config) .map_err(LoadError) .map_err(ToPythonError::to_py_err)?; From 2e3171fbac3f38ca0cd1520f034b1e511848b2c3 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 19 Jan 2023 11:27:56 -0700 Subject: [PATCH 14/43] chore: work in progress testing the qpu client --- Cargo.lock | 48 +++----------------- crates/lib/Cargo.toml | 2 +- crates/python/src/api.rs | 47 ++++++++----------- crates/python/src/lib.rs | 3 +- crates/python/src/qpu/client.rs | 27 ++++++++--- crates/python/tests/qcs_config/secrets.toml | 1 + crates/python/tests/qcs_config/settings.toml | 13 +++++- crates/python/tests/test_client.py | 33 ++++++++++++++ 8 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 crates/python/tests/test_client.py diff --git a/Cargo.lock b/Cargo.lock index bc04483e0..8bcc6bb61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1330,9 +1330,8 @@ dependencies = [ "maplit", "num", "qcs-api", - "qcs-api-client-common 0.3.0", - "qcs-api-client-grpc 0.2.7", - "qcs-api-client-grpc 0.3.0", + "qcs-api-client-common", + "qcs-api-client-grpc", "qcs-api-client-openapi", "quil-rs", "regex", @@ -1365,22 +1364,6 @@ dependencies = [ "url", ] -[[package]] -name = "qcs-api-client-common" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a067d003dbf106c4a3ec821ac99bf41e508bddbba760e66e4124ea41619209b5" -dependencies = [ - "dirs", - "futures", - "jsonwebtoken", - "reqwest", - "serde", - "thiserror", - "tokio", - "toml 0.5.10", -] - [[package]] name = "qcs-api-client-common" version = "0.3.0" @@ -1397,25 +1380,6 @@ dependencies = [ "toml 0.5.10", ] -[[package]] -name = "qcs-api-client-grpc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d9d4329f3d8a051417f96a69326965dadb21b10de83cd8c94018d4917b2c8" -dependencies = [ - "http-body", - "pbjson", - "pbjson-build", - "prost", - "prost-build", - "qcs-api-client-common 0.2.7", - "serde", - "thiserror", - "tonic", - "tonic-build", - "tower", -] - [[package]] name = "qcs-api-client-grpc" version = "0.3.0" @@ -1427,7 +1391,7 @@ dependencies = [ "pbjson-build", "prost", "prost-build", - "qcs-api-client-common 0.3.0", + "qcs-api-client-common", "serde", "thiserror", "tonic", @@ -1441,7 +1405,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a99306535343c8d28a013fe7489a9c52c7aeb4d64029f98444f44dadacc8334" dependencies = [ - "qcs-api-client-common 0.3.0", + "qcs-api-client-common", "reqwest", "serde", "serde_json", @@ -1457,8 +1421,8 @@ dependencies = [ "pyo3-build-config", "qcs", "qcs-api", - "qcs-api-client-common 0.3.0", - "qcs-api-client-grpc 0.3.0", + "qcs-api-client-common", + "qcs-api-client-grpc", "quil-rs", "rigetti-pyo3", "serde_json", diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index df4edf7a1..de83543e4 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -42,7 +42,7 @@ erased-serde = "0.3.23" float-cmp = "0.9.0" hex = "0.4.3" maplit = "1.0.2" -qcs-api-client-grpc = { version = "0.2.7", features = ["server"] } +qcs-api-client-grpc = { version = "0.3.0", features = ["server"] } simple_logger = { version = "2.3.0", default-features = false } tempfile = "3.3.0" tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] } diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs index ca8bfb975..430ac873e 100644 --- a/crates/python/src/api.rs +++ b/crates/python/src/api.rs @@ -12,16 +12,15 @@ use qcs::{ api::{ ExecutionResult, ExecutionResults, Register, RewriteArithmeticResult, TranslationResult, }, - qpu::{ - quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}, - Qcs, - }, + qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}, }; use rigetti_pyo3::{ create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, py_wrap_union_enum, wrap_error, ToPython, }; +use crate::qpu::client::PyQcsClient; + create_init_submodule! { classes: [ PyExecutionResult, @@ -92,11 +91,12 @@ create_exception!(qcs, DeviceIsaError, PyValueError); wrap_error!(SubmitError(qcs::api::SubmitError)); py_wrap_error!(api, SubmitError, QcsSubmitError, PyRuntimeError); -#[pyfunction(kwds = "**")] +#[pyfunction(client = "None", kwds = "**")] pub fn compile<'a>( py: Python<'a>, quil: String, target_device: String, + client: Option, kwds: Option<&PyDict>, ) -> PyResult<&'a PyAny> { let target_device: TargetDevice = @@ -113,9 +113,7 @@ pub fn compile<'a>( } pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let client = PyQcsClient::get_or_create_client(client).await?; let options = CompilerOpts::default().with_timeout(compiler_timeout); let result = qcs::api::compile(&quil, target_device, &client, options) .map_err(|e| CompilationError::new_err(e.to_string()))?; @@ -153,17 +151,16 @@ pub fn build_patch_values( .to_python(py) } -#[pyfunction] +#[pyfunction(client = "None")] pub fn translate( py: Python<'_>, native_quil: String, num_shots: u16, quantum_processor_id: String, + client: Option, ) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let client = PyQcsClient::get_or_create_client(client).await?; let result = qcs::api::translate(&native_quil, num_shots, &quantum_processor_id, &client) .await .map_err(|e| TranslationError::new_err(e.to_string()))?; @@ -171,19 +168,16 @@ pub fn translate( }) } -#[pyfunction] +#[pyfunction(client = "None")] pub fn submit( py: Python<'_>, program: String, patch_values: HashMap>, quantum_processor_id: String, - use_gateway: bool, + client: Option, ) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))? - .with_use_gateway(use_gateway); + let client = PyQcsClient::get_or_create_client(client).await?; let job_id = qcs::api::submit(&program, patch_values, &quantum_processor_id, &client) .await .map_err(|e| ExecutionError::new_err(e.to_string()))?; @@ -191,18 +185,15 @@ pub fn submit( }) } -#[pyfunction] +#[pyfunction(client = "None")] pub fn retrieve_results( py: Python<'_>, job_id: String, quantum_processor_id: String, - use_gateway: bool, + client: Option, ) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))? - .with_use_gateway(use_gateway); + let client = PyQcsClient::get_or_create_client(client).await?; let results = qcs::api::retrieve_results(&job_id, &quantum_processor_id, &client) .await .map_err(|e| ExecutionError::new_err(e.to_string()))?; @@ -210,12 +201,10 @@ pub fn retrieve_results( }) } -#[pyfunction] -pub fn get_quilc_version(py: Python<'_>) -> PyResult<&PyAny> { +#[pyfunction(client = "None")] +pub fn get_quilc_version(py: Python<'_>, client: Option) -> PyResult<&PyAny> { pyo3_asyncio::tokio::future_into_py(py, async move { - let client = Qcs::load() - .await - .map_err(|e| InvalidConfigError::new_err(e.to_string()))?; + let client = PyQcsClient::get_or_create_client(client).await?; let version = qcs::api::get_quilc_version(&client) .map_err(|e| CompilationError::new_err(e.to_string()))?; Ok(version) diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 4646a9eeb..7a86abbc3 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -20,7 +20,8 @@ create_init_submodule! { executable::PyExecutable, executable::PyJobHandle, executable::PyService, - register_data::PyRegisterData + register_data::PyRegisterData, + qpu::client::PyQcsClient ], errors: [ executable::QcsExecutionError diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index c50f74a74..1cf69d661 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -9,8 +9,7 @@ use qcs_api_client_common::{ ClientConfiguration, }; use rigetti_pyo3::{ - create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, ToPython, - ToPythonError, + create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, ToPythonError, }; create_init_submodule! { @@ -115,6 +114,18 @@ py_wrap_type! { PyQcsClient(Qcs) as "QcsClient"; } +impl PyQcsClient { + pub(crate) async fn get_or_create_client(client: Option) -> PyResult { + Ok(match client { + Some(client) => client.0, + None => Qcs::load() + .await + .map_err(LoadError::from) + .map_err(LoadError::to_py_err)?, + }) + } +} + #[pymethods] impl PyQcsClient { #[new] @@ -163,14 +174,13 @@ impl PyQcsClient { Ok(Self(client)) } - // TODO: default arg #[staticmethod] #[args("/", profile_name = "None", use_gateway = "None")] pub fn load( py: Python<'_>, profile_name: Option, use_gateway: Option, - ) -> PyResult { + ) -> PyResult<&PyAny> { future_into_py(py, async move { let config = match profile_name { Some(profile_name) => ClientConfiguration::load_profile(profile_name).await, @@ -187,8 +197,11 @@ impl PyQcsClient { Some(use_gateway) => client.with_use_gateway(use_gateway), }; - Python::with_gil(|py| <_ as ToPython>>::to_python(&Self(client), py)) - })? - .extract() + Ok(Self(client)) + }) + } + + fn info(&self) -> String { + format!("{:?}", self.0) } } diff --git a/crates/python/tests/qcs_config/secrets.toml b/crates/python/tests/qcs_config/secrets.toml index 12c409e9f..7e0363cb3 100644 --- a/crates/python/tests/qcs_config/secrets.toml +++ b/crates/python/tests/qcs_config/secrets.toml @@ -7,3 +7,4 @@ id_token = "" refresh_token = "refresh" token_type = "Bearer" + \ No newline at end of file diff --git a/crates/python/tests/qcs_config/settings.toml b/crates/python/tests/qcs_config/settings.toml index e2e9d3f77..943fbdac2 100644 --- a/crates/python/tests/qcs_config/settings.toml +++ b/crates/python/tests/qcs_config/settings.toml @@ -3,8 +3,8 @@ default_profile_name = "default" [auth_servers] [auth_servers.default] - client_id = "" - issuer = "https://rigetticomputing.okta.com/oauth2/ausmoidj5qavd4v310h7" + client_id = "0oa3ykoirzDKpkfzk357" + issuer = "https://auth.qcs.rigetti.com/oauth2/aus8jcovzG0gW2TUG355" [profiles] @@ -13,3 +13,12 @@ default_profile_name = "default" auth_server_name = "default" credentials_name = "default" + [profiles.empty] + api_url = "https://api.qcs.rigetti.com" + auth_server_name = "default" + credentials_name = "does-not-exist" + + [profiles.broken] + api_url = "https://api.qcs.rigetti.com" + auth_server_name = "broken" + credentials_name = "default" diff --git a/crates/python/tests/test_client.py b/crates/python/tests/test_client.py new file mode 100644 index 000000000..b95e941ee --- /dev/null +++ b/crates/python/tests/test_client.py @@ -0,0 +1,33 @@ +import pytest + +import re + +from qcs_sdk import QcsClient +from qcs_sdk.qpu.client import QcsLoadError + +@pytest.fixture +def default_client_info(): + return QcsClient().info() + + +@pytest.mark.asyncio +async def test_client_empty_profile_is_default(default_client_info): + """The profile "empty" is configured to be similar to a default client.""" + client = await QcsClient.load(profile_name="empty") + + assert client.info() == default_client_info + + +@pytest.mark.asyncio +async def test_client_default_profile_is_not_empty(default_client_info): + """The "default" profile is configured to have a token, unlike the default client.""" + client = await QcsClient.load() + + assert client.info() != default_client_info + + +@pytest.mark.asyncio +async def test_client_broken_raises(): + """Using a profile with broken configuration should surface the underlying error.""" + with pytest.raises(QcsLoadError, match=r"Expected auth server broken .* but it didn't exist"): + await QcsClient.load(profile_name="broken") \ No newline at end of file From b352749ffc3a14b50904200efb26d57a30e59788 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 19 Jan 2023 11:45:54 -0700 Subject: [PATCH 15/43] chore: mr cleanup --- crates/python/tests/qcs_config/secrets.toml | 1 - crates/python/tests/qcs_config/settings.toml | 2 +- crates/python/tests/test_client.py | 6 ++---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/python/tests/qcs_config/secrets.toml b/crates/python/tests/qcs_config/secrets.toml index 7e0363cb3..12c409e9f 100644 --- a/crates/python/tests/qcs_config/secrets.toml +++ b/crates/python/tests/qcs_config/secrets.toml @@ -7,4 +7,3 @@ id_token = "" refresh_token = "refresh" token_type = "Bearer" - \ No newline at end of file diff --git a/crates/python/tests/qcs_config/settings.toml b/crates/python/tests/qcs_config/settings.toml index 943fbdac2..4d32338f9 100644 --- a/crates/python/tests/qcs_config/settings.toml +++ b/crates/python/tests/qcs_config/settings.toml @@ -20,5 +20,5 @@ default_profile_name = "default" [profiles.broken] api_url = "https://api.qcs.rigetti.com" - auth_server_name = "broken" + auth_server_name = "does-not-exist" credentials_name = "default" diff --git a/crates/python/tests/test_client.py b/crates/python/tests/test_client.py index b95e941ee..04e7e170a 100644 --- a/crates/python/tests/test_client.py +++ b/crates/python/tests/test_client.py @@ -1,7 +1,5 @@ import pytest -import re - from qcs_sdk import QcsClient from qcs_sdk.qpu.client import QcsLoadError @@ -29,5 +27,5 @@ async def test_client_default_profile_is_not_empty(default_client_info): @pytest.mark.asyncio async def test_client_broken_raises(): """Using a profile with broken configuration should surface the underlying error.""" - with pytest.raises(QcsLoadError, match=r"Expected auth server broken .* but it didn't exist"): - await QcsClient.load(profile_name="broken") \ No newline at end of file + with pytest.raises(QcsLoadError, match=r"Expected auth server .* but it didn't exist"): + await QcsClient.load(profile_name="broken") From bc1857699c4f3c1e1d5154fed9daa9d67ae6484d Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 19 Jan 2023 13:31:46 -0700 Subject: [PATCH 16/43] chore: use nested pyi files --- .gitignore | 3 +- crates/python/qcs_sdk/__init__.py | 2 + crates/python/qcs_sdk/__init__.pyi | 2 + .../{qcs_sdk.pyi => qcs_sdk/api/__init__.pyi} | 42 +++++++++++++++---- .../{py.typed => qcs_sdk/qpu/__init__.pyi} | 0 crates/python/qcs_sdk/qpu/client/__init__.pyi | 9 ++++ crates/python/src/qpu/client.rs | 4 +- 7 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 crates/python/qcs_sdk/__init__.py create mode 100644 crates/python/qcs_sdk/__init__.pyi rename crates/python/{qcs_sdk.pyi => qcs_sdk/api/__init__.pyi} (75%) rename crates/python/{py.typed => qcs_sdk/qpu/__init__.pyi} (100%) create mode 100644 crates/python/qcs_sdk/qpu/client/__init__.pyi diff --git a/.gitignore b/.gitignore index 87b569986..87d8938d8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,6 @@ qcs-api/docs # macOS .DS_Store -# Python crate test cache +# Python artifacts __pycache__ +crates/python/qcs_sdk/*.so diff --git a/crates/python/qcs_sdk/__init__.py b/crates/python/qcs_sdk/__init__.py new file mode 100644 index 000000000..8b32e2b78 --- /dev/null +++ b/crates/python/qcs_sdk/__init__.py @@ -0,0 +1,2 @@ +# See https://www.maturin.rs/project_layout.html#adding-python-type-information +from .qcs_sdk import * \ No newline at end of file diff --git a/crates/python/qcs_sdk/__init__.pyi b/crates/python/qcs_sdk/__init__.pyi new file mode 100644 index 000000000..201a2dc7a --- /dev/null +++ b/crates/python/qcs_sdk/__init__.pyi @@ -0,0 +1,2 @@ +from .api import * +from .qpu.client import QcsClient as QcsClient \ No newline at end of file diff --git a/crates/python/qcs_sdk.pyi b/crates/python/qcs_sdk/api/__init__.pyi similarity index 75% rename from crates/python/qcs_sdk.pyi rename to crates/python/qcs_sdk/api/__init__.pyi index dcb977dc5..07fec7b40 100644 --- a/crates/python/qcs_sdk.pyi +++ b/crates/python/qcs_sdk/api/__init__.pyi @@ -4,6 +4,8 @@ The qcs_sdk module provides an interface to Rigetti Quantum Cloud Services. Allo from typing import Any, Dict, List, Optional, TypedDict from numbers import Number +from ..qpu.client import QcsClient + RecalculationTable = List[str] Memory = Dict[str, List[float]] PatchValues = Dict[str, List[float]] @@ -69,13 +71,20 @@ class ExecutionResults(TypedDict): The time spent executing the program. """ -async def compile(quil: str, target_device: str, *, timeout: int = 30) -> str: +async def compile( + quil: str, + target_device: str, + client: Optional[QcsClient] = None, + *, + timeout: int = 30, +) -> str: """ Uses quilc to convert a quil program to native Quil. Args: quil: A Quil program. target_device: A JSON encoded description of the Quantum Abstract Machine Architecture. + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Keyword Args: timeout: The number of seconds to wait before timing out. If set to None, there is no timeout (default: 30). @@ -85,7 +94,9 @@ async def compile(quil: str, target_device: str, *, timeout: int = 30) -> str: """ ... -def rewrite_arithmetic(native_quil: str) -> RewriteArithmeticResults: +def rewrite_arithmetic( + native_quil: str, +) -> RewriteArithmeticResults: """ Rewrites parametric arithmetic such that all gate parameters are only memory references to a newly declared memory location (__SUBST). @@ -99,7 +110,8 @@ def rewrite_arithmetic(native_quil: str) -> RewriteArithmeticResults: ... def build_patch_values( - recalculation_table: RecalculationTable, memory: Memory + recalculation_table: RecalculationTable, + memory: Memory, ) -> PatchValues: """ Evaluate the expressions in recalculation_table using the numeric values @@ -115,7 +127,10 @@ def build_patch_values( ... async def translate( - native_quil: str, num_shots: int, quantum_processor_id: str + native_quil: str, + num_shots: int, + quantum_processor_id: str, + client: Optional[QcsClient] = None, ) -> TranslationResult: """ Translates a native Quil program into an executable. @@ -124,6 +139,7 @@ async def translate( native_quil: A Quil program. num_shots: The number of shots to perform. quantum_processor_id: The ID of the quantum processor the executable will run on (e.g. "Aspen-M-2"). + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Returns: An Awaitable that resolves to a dictionary with the compiled program, memory descriptors, and readout sources (see `TranslationResult`). @@ -131,7 +147,10 @@ async def translate( ... async def submit( - program: str, patch_values: Dict[str, List[float]], quantum_processor_id: str + program: str, + patch_values: Dict[str, List[float]], + quantum_processor_id: str, + client: Optional[QcsClient] = None, ) -> str: """ Submits an executable `program` to be run on the specified QPU. @@ -140,27 +159,36 @@ async def submit( program: An executable program (see `translate`). patch_values: A mapping of symbols to their desired values (see `build_patch_values`). quantum_processor_id: The ID of the quantum processor to run the executable on. + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Returns: An Awaitable that resolves to the ID of the submitted job. """ ... -async def retrieve_results(job_id: str, quantum_processor_id: str) -> ExecutionResults: +async def retrieve_results( + job_id: str, + quantum_processor_id: str, + client: Optional[QcsClient] = None, +) -> ExecutionResults: """ Fetches results for the corresponding job ID. Args: job_id: The ID of the job to retrieve results for. quantum_processor_id: The ID of the quanutum processor the job ran on. + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Returns: An Awaitable that resolves to a dictionary describing the results of the execution and its duration (see `ExecutionResults`). """ ... -async def get_quilc_version() -> str: +async def get_quilc_version( + client: Optional[QcsClient] = None, +) -> str: """ Returns the version number of the running quilc server. + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration """ ... diff --git a/crates/python/py.typed b/crates/python/qcs_sdk/qpu/__init__.pyi similarity index 100% rename from crates/python/py.typed rename to crates/python/qcs_sdk/qpu/__init__.pyi diff --git a/crates/python/qcs_sdk/qpu/client/__init__.pyi b/crates/python/qcs_sdk/qpu/client/__init__.pyi new file mode 100644 index 000000000..1bae6324c --- /dev/null +++ b/crates/python/qcs_sdk/qpu/client/__init__.pyi @@ -0,0 +1,9 @@ +class QcsClient: + """ + Here we are, deep in the belly of the module + """ + +class QcsLoadError: + """ + Clearly, something is amiss + """ \ No newline at end of file diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index 1cf69d661..d147ddc6a 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -64,7 +64,7 @@ fn get_pydict_str(py_dict: &PyDict, key: &str) -> Option { } py_wrap_struct! { - PyQcsClientAuthServer(AuthServer) { + PyQcsClientAuthServer(AuthServer) as "QcsClientAuthServer" { py -> rs { py_dict: Py => AuthServer { let py_dict = py_dict.as_ref(py); @@ -90,7 +90,7 @@ py_wrap_struct! { } py_wrap_struct! { - PyQcsClientTokens(Tokens) { + PyQcsClientTokens(Tokens) as "QcsClientTokens" { py -> rs { py_dict: Py => Tokens { let py_dict = py_dict.as_ref(py); From 3461ee08d1b76125d315f18f6f374fd19821ea6e Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 19 Jan 2023 14:39:12 -0700 Subject: [PATCH 17/43] chore: clean up imports and declarations --- crates/python/qcs_sdk/__init__.pyi | 15 +++++++++- crates/python/qcs_sdk/api/__init__.pyi | 40 +++++++++++++++++++++++++- crates/python/src/api.rs | 2 -- crates/python/src/lib.rs | 4 ++- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/crates/python/qcs_sdk/__init__.pyi b/crates/python/qcs_sdk/__init__.pyi index 201a2dc7a..ed1de1f06 100644 --- a/crates/python/qcs_sdk/__init__.pyi +++ b/crates/python/qcs_sdk/__init__.pyi @@ -1,2 +1,15 @@ +from enum import Enum + from .api import * -from .qpu.client import QcsClient as QcsClient \ No newline at end of file +from .qpu.client import QcsClient as QcsClient + +class QcsExecutionError(RuntimeError): + """Error during QCS program execution.""" + ... + + +class Service(Enum): + Quilc = "Quilc", + Qvm = "Qvm", + Qcs = "Qcs", + Qpu = "Qpu", diff --git a/crates/python/qcs_sdk/api/__init__.pyi b/crates/python/qcs_sdk/api/__init__.pyi index 07fec7b40..ff747cde9 100644 --- a/crates/python/qcs_sdk/api/__init__.pyi +++ b/crates/python/qcs_sdk/api/__init__.pyi @@ -10,6 +10,42 @@ RecalculationTable = List[str] Memory = Dict[str, List[float]] PatchValues = Dict[str, List[float]] +class ExecutionError(RuntimeError): + """Error encountered during program execution.""" + ... + + +class TranslationError(RuntimeError): + """Error encountered during program translation.""" + ... + + +class CompilationError(RuntimeError): + """Error encountered during program compilation.""" + ... + + +class RewriteArithmeticError(RuntimeError): + """Error encountered rewriting arithmetic for program.""" + ... + + +class DeviceIsaError(ValueError): + """Error while deserializing ISA.""" + ... + + +class JobHandle(TypedDict): + """ + Represents a quantum program running on a QPU. + Can be passed to `retrieve_results` to retrieve the results of the job. + """ + + job_id: str + + readout_map: Dict[str, str] + + class RewriteArithmeticResults(TypedDict): program: str """ @@ -189,6 +225,8 @@ async def get_quilc_version( ) -> str: """ Returns the version number of the running quilc server. - client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration + + Args: + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration """ ... diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs index 430ac873e..02ef9fb2c 100644 --- a/crates/python/src/api.rs +++ b/crates/python/src/api.rs @@ -30,7 +30,6 @@ create_init_submodule! { PyTranslationResult ], errors: [ - InvalidConfigError, ExecutionError, TranslationError, CompilationError, @@ -81,7 +80,6 @@ py_wrap_type! { PyExecutionResult(ExecutionResult) as "ExecutionResult"; } -create_exception!(qcs, InvalidConfigError, PyRuntimeError); create_exception!(qcs, ExecutionError, PyRuntimeError); create_exception!(qcs, TranslationError, PyRuntimeError); create_exception!(qcs, CompilationError, PyRuntimeError); diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 7a86abbc3..6c3a2afcc 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -8,6 +8,8 @@ pub mod grpc; pub mod qpu; pub mod register_data; +use executable::QcsExecutionError; + // pub use executable::{Error, Executable, ExecuteResultQPU, ExecuteResultQVM, JobHandle, Service}; // pub use execution_data::{Qpu, Qvm, ReadoutMap}; // pub use register_data::RegisterData; @@ -24,7 +26,7 @@ create_init_submodule! { qpu::client::PyQcsClient ], errors: [ - executable::QcsExecutionError + QcsExecutionError ], funcs: [ api::compile, From e6fbbd119e21976b25f111daeb9339f1cb5b0580 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 20 Jan 2023 15:47:11 -0700 Subject: [PATCH 18/43] chore: impl eq through __richcmp__ --- crates/python/src/qpu/client.rs | 18 ++++++++++++++---- crates/python/tests/test_client.py | 12 ++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index d147ddc6a..b37eea7c8 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -1,6 +1,6 @@ use pyo3::{ - conversion::IntoPy, exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyErr, - PyResult, Python, + conversion::IntoPy, exceptions::PyRuntimeError, pyclass::CompareOp, pymethods, types::PyDict, + Py, PyAny, PyErr, PyObject, PyResult, Python, }; use pyo3_asyncio::tokio::future_into_py; use qcs::qpu::Qcs; @@ -126,6 +126,12 @@ impl PyQcsClient { } } +impl PartialEq for PyQcsClient { + fn eq(&self, other: &Self) -> bool { + format!("{:?}", self.0) == format!("{:?}", other.0) + } +} + #[pymethods] impl PyQcsClient { #[new] @@ -201,7 +207,11 @@ impl PyQcsClient { }) } - fn info(&self) -> String { - format!("{:?}", self.0) + fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject { + match op { + CompareOp::Eq => (self == other).into_py(py), + CompareOp::Ne => (self != other).into_py(py), + _ => py.NotImplemented(), + } } } diff --git a/crates/python/tests/test_client.py b/crates/python/tests/test_client.py index 04e7e170a..51bd0594d 100644 --- a/crates/python/tests/test_client.py +++ b/crates/python/tests/test_client.py @@ -4,24 +4,24 @@ from qcs_sdk.qpu.client import QcsLoadError @pytest.fixture -def default_client_info(): - return QcsClient().info() +def default_client(): + return QcsClient() @pytest.mark.asyncio -async def test_client_empty_profile_is_default(default_client_info): +async def test_client_empty_profile_is_default(default_client: QcsClient): """The profile "empty" is configured to be similar to a default client.""" client = await QcsClient.load(profile_name="empty") - assert client.info() == default_client_info + assert client == default_client @pytest.mark.asyncio -async def test_client_default_profile_is_not_empty(default_client_info): +async def test_client_default_profile_is_not_empty(default_client: QcsClient): """The "default" profile is configured to have a token, unlike the default client.""" client = await QcsClient.load() - assert client.info() != default_client_info + assert client != default_client @pytest.mark.asyncio From f0fc5b7f89c1abe674878c51a69031b0552fb8a2 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Mon, 23 Jan 2023 10:38:50 -0700 Subject: [PATCH 19/43] chore: type stubs for all exports --- crates/python/qcs_sdk/__init__.py | 2 +- crates/python/qcs_sdk/__init__.pyi | 23 +++- crates/python/qcs_sdk/_executable.pyi | 89 ++++++++++++++ crates/python/qcs_sdk/_execution_data.pyi | 39 ++++++ crates/python/qcs_sdk/_register_data.pyi | 39 ++++++ .../qcs_sdk/{api/__init__.pyi => api.pyi} | 112 +++++++++++++----- crates/python/qcs_sdk/qpu/__init__.pyi | 2 + crates/python/qcs_sdk/qpu/client.pyi | 65 ++++++++++ crates/python/qcs_sdk/qpu/client/__init__.pyi | 9 -- crates/python/qcs_sdk/qpu/quilc.pyi | 27 +++++ 10 files changed, 363 insertions(+), 44 deletions(-) create mode 100644 crates/python/qcs_sdk/_executable.pyi create mode 100644 crates/python/qcs_sdk/_execution_data.pyi create mode 100644 crates/python/qcs_sdk/_register_data.pyi rename crates/python/qcs_sdk/{api/__init__.pyi => api.pyi} (64%) create mode 100644 crates/python/qcs_sdk/qpu/client.pyi delete mode 100644 crates/python/qcs_sdk/qpu/client/__init__.pyi create mode 100644 crates/python/qcs_sdk/qpu/quilc.pyi diff --git a/crates/python/qcs_sdk/__init__.py b/crates/python/qcs_sdk/__init__.py index 8b32e2b78..6bac5b077 100644 --- a/crates/python/qcs_sdk/__init__.py +++ b/crates/python/qcs_sdk/__init__.py @@ -1,2 +1,2 @@ # See https://www.maturin.rs/project_layout.html#adding-python-type-information -from .qcs_sdk import * \ No newline at end of file +from .qcs_sdk import * diff --git a/crates/python/qcs_sdk/__init__.pyi b/crates/python/qcs_sdk/__init__.pyi index ed1de1f06..aaf8fcc87 100644 --- a/crates/python/qcs_sdk/__init__.pyi +++ b/crates/python/qcs_sdk/__init__.pyi @@ -1,11 +1,26 @@ from enum import Enum from .api import * -from .qpu.client import QcsClient as QcsClient -class QcsExecutionError(RuntimeError): - """Error during QCS program execution.""" - ... +from .qpu.client import ( + QcsClient as QcsClient +) + +from ._execution_data import ( + QPU as QPU, + QVM as QVM, + ReadoutMap as ReadoutMap, +) + +from ._executable import ( + Executable as Executable, + JobHandle as JobHandle, + QcsExecutionError as QcsExecutionError, +) + +from ._register_data import ( + RegisterData as RegisterData, +) class Service(Enum): diff --git a/crates/python/qcs_sdk/_executable.pyi b/crates/python/qcs_sdk/_executable.pyi new file mode 100644 index 000000000..202a6e113 --- /dev/null +++ b/crates/python/qcs_sdk/_executable.pyi @@ -0,0 +1,89 @@ +""" +Do not import this file, it has no exports. +It is only here to represent the structure of the rust source code 1:1 +""" + +from typing import Dict, List, Optional +from .qpu.quilc import CompilerOpts + +class QcsExecutionError(RuntimeError): + """Error encounteted when executing programs.""" + ... + + +class Executable: + """""" + def __new__( + registers: Optional[List[str]], + parameters: Optional[List[ExeParameter]], + shots: Optional[int], + compile_with_quilc: Optional[bool], + compiler_options: Optional[CompilerOpts] + ) -> "Executable": ... + + async def execute_on_qvm(): + """ + Execute on a QVM which must be available at the configured URL (default http://localhost:5000). + + Raises: + - ``QcsExecutionError``: When the job failed to execute. + """ + ... + + async def execute_on_qpu( + quantum_processor_id: str + ): + """ + Compile the program and execute it on a QPU, waiting for results. + + Raises: + - ``QcsExecutionError``: When the job failed to execute. + """ + ... + + async def retrieve_results( + job_handle: JobHandle + ): + """ + Wait for the results of a job to complete. + + Raises: + - ``QcsExecutionError``: When there is a problem constructing job results. + """ + ... + + +class JobHandle: + """ + The result of submitting a job to a QPU. + + Used to retrieve the results of a job. + """ + + job_id: str + """Unique ID associated with a single job execution.""" + + readout_map: Dict[str, str] + """ + The readout map from source readout memory locations to the filter pipeline node which publishes the data. + """ + + +class ExeParameter: + """ + Program execution parameters. + + Note: The validity of parameters is not checked until execution. + """ + + param_name: str + """ + References the name of the parameter corresponding to a `DECLARE` statement in the Quil program. + """ + + index: int + """The index into the memory vector that you're setting.""" + + value: float + """The value to set for the specified memory.""" + diff --git a/crates/python/qcs_sdk/_execution_data.pyi b/crates/python/qcs_sdk/_execution_data.pyi new file mode 100644 index 000000000..9d2b8d51e --- /dev/null +++ b/crates/python/qcs_sdk/_execution_data.pyi @@ -0,0 +1,39 @@ +""" +Do not import this file, it has no exports. +It is only here to represent the structure of the rust source code 1:1 +""" + +import datetime +from typing import List, Optional + +class IntegerReadoutValues: + values: List[int] + + +class ComplexReadoutValues: + values: List[complex] + + +class ReadoutValuesValues: + integer_values: IntegerReadoutValues + complex_values: ComplexReadoutValues + + +class ReadoutValues: + values: Optional[ReadoutValuesValues] + + +class ReadoutMap: + def get_readout_values(self, field: str, index: int) -> Optional[ReadoutValues]: + ... + + def get_readout_values_for_field(self, field: str) -> Optional[List[Optional[ReadoutValues]]]: + ... + +class QVM: + registers: dict + duration: Optional[datetime.timedelta] + +class QPU: + readout_data: ReadoutMap + duration: Optional[datetime.timedelta] diff --git a/crates/python/qcs_sdk/_register_data.pyi b/crates/python/qcs_sdk/_register_data.pyi new file mode 100644 index 000000000..c5e2f7d42 --- /dev/null +++ b/crates/python/qcs_sdk/_register_data.pyi @@ -0,0 +1,39 @@ +""" +Do not import this file, it has no exports. +It is only here to represent the structure of the rust source code 1:1 +""" + +from typing import List, Optional + + +class RegisterData: + """ + Values present in a register that are one of a set of variants. + + Variants: + - ``i8``: Corresponds to the Quil `BIT` or `OCTET` types. + - ``i16``: Corresponds to the Quil `INTEGER` type. + - ``f64``: Corresponds to the Quil `REAL` type. + - ``complex32``: Results containing complex numbers. + + Methods (each per variant): + - ``is_*``: if the underlying values are that type. + - ``as_*``: if the underlying values are that type, then those values values, otherwise ``None``. + - ``to_*``: the underlyting values as that type, raises ``ValueError`` if they are not. + + """ + + def is_i8() -> bool: ... + def is_i16() -> bool: ... + def is_f64() -> bool: ... + def is_complex32() -> bool: ... + + def as_i8() -> Optional[List[int]]: ... + def as_i16() -> Optional[List[int]]: ... + def as_f64() -> Optional[List[float]]: ... + def as_complex32() -> Optional[List[complex]]: ... + + def to_i8() -> List[int]: ... + def to_i16() -> List[int]: ... + def to_f64() -> List[float]: ... + def to_complex32() -> List[complex]: ... diff --git a/crates/python/qcs_sdk/api/__init__.pyi b/crates/python/qcs_sdk/api.pyi similarity index 64% rename from crates/python/qcs_sdk/api/__init__.pyi rename to crates/python/qcs_sdk/api.pyi index ff747cde9..b71e6b897 100644 --- a/crates/python/qcs_sdk/api/__init__.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -3,15 +3,16 @@ The qcs_sdk module provides an interface to Rigetti Quantum Cloud Services. Allo """ from typing import Any, Dict, List, Optional, TypedDict from numbers import Number +from enum import Enum -from ..qpu.client import QcsClient +from .qpu.client import QcsClient RecalculationTable = List[str] Memory = Dict[str, List[float]] PatchValues = Dict[str, List[float]] class ExecutionError(RuntimeError): - """Error encountered during program execution.""" + """Error encountered during program execution submission or when retrieving results.""" ... @@ -31,36 +32,28 @@ class RewriteArithmeticError(RuntimeError): class DeviceIsaError(ValueError): - """Error while deserializing ISA.""" + """Error while building Instruction Set Architecture.""" ... -class JobHandle(TypedDict): +class RewriteArithmeticResults(TypedDict): """ - Represents a quantum program running on a QPU. - Can be passed to `retrieve_results` to retrieve the results of the job. + The result of a call to [`rewrite_arithmetic`] which provides the information necessary to later patch-in memory values to a compiled program. """ - job_id: str - - readout_map: Dict[str, str] - - -class RewriteArithmeticResults(TypedDict): program: str """ The resulting program where gate parameter arithmetic has been replaced with memory references. Before execution, the program memory should be updated using the `recalculation_table`. """ - recalculation_table: RecalculationTable + recalculation_table: List[str] """ The recalculation table stores an ordered list of arithmetic expressions, which are to be used when updating the program memory before execution. """ class TranslationResult(TypedDict): - memory_descriptors: Optional[Dict[str, Any]] """ - A map from the name of memory (declared with `DECLARE`) to the size and type of that memory. + The result of a call to [`translate`] which provides information about the translated program. """ program: str @@ -68,33 +61,28 @@ class TranslationResult(TypedDict): The compiled program binary. """ - ro_sources: Optional[List[List[str]]] + ro_sources: Optional[dict] """ A mapping from the program's memory references to the key used to index the results map. """ - settings_timestamp: Optional[str] - """ - The timestamp of the settings used during translation. - """ class ExecutionResult(TypedDict): + """Execution readout data from a particular memory location.""" + shape: List[int] - """ - The shape of the result data. - """ + """The shape of the result data.""" data: List[Number | List[float]] - """ - The result data. Complex numbers are represented as [real, imaginary]. - """ + """The result data. Complex numbers are represented as [real, imaginary].""" dtype: str - """ - The type of the result data (as a `numpy` `dtype`). - """ + """The type of the result data (as a `numpy` `dtype`).""" + class ExecutionResults(TypedDict): + """Execution readout data for all memory locations.""" + buffers: Dict[str, ExecutionResult] """ The readout results of execution, mapping a published filter node to its data. @@ -103,9 +91,45 @@ class ExecutionResults(TypedDict): """ execution_duration_microseconds: Optional[int] + """The time spent executing the program.""" + + +class Register: """ - The time spent executing the program. + Data from an individual register. + + Variants: + - ``i8``: A register of 8-bit integers. + - ``i16``: A register of 16-bit integers. + - ``i32``: A register of 32-bit integers. + - ``f64``: A register of 64-bit floating point numbers. + - ``complex64``: A register of 64-bit complex numbers. + + Methods (each per variant): + - ``is_*``: if the underlying values are that type. + - ``as_*``: if the underlying values are that type, then those values values, otherwise ``None``. + - ``to_*``: the underlyting values as that type, raises ``ValueError`` if they are not. + """ + + def is_i8() -> bool: ... + def is_i16() -> bool: ... + def is_i32() -> bool: ... + def is_f64() -> bool: ... + def is_complex64() -> bool: ... + + def as_i8() -> Optional[List[int]]: ... + def as_i16() -> Optional[List[int]]: ... + def as_i32() -> Optional[List[int]]: ... + def as_f64() -> Optional[List[float]]: ... + def as_complex64() -> Optional[List[complex]]: ... + + def to_i8() -> List[int]: ... + def to_i16() -> List[int]: ... + def to_i32() -> List[int]: ... + def to_f64() -> List[float]: ... + def to_complex64() -> List[complex]: ... + async def compile( quil: str, @@ -127,6 +151,11 @@ async def compile( Returns: An Awaitable that resolves to the native Quil program. + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``DeviceIsaError`` When the `target_device` is misconfigured. + - ``CompilationError`` When the program could not compile. """ ... @@ -142,6 +171,10 @@ def rewrite_arithmetic( Returns: A dictionary with the rewritten program and recalculation table (see `RewriteArithmeticResults`). + + Raises: + - ``TranslationError`` When the program could not be translated. + - ``RewriteArithmeticError`` When the program arithmetic cannot be evaluated. """ ... @@ -159,6 +192,9 @@ def build_patch_values( Returns: A dictionary that maps each symbol to the value it should be patched with. + + Raises: + - ``TranslationError`` When the expressions in `recalculation_table` could not be evaluated. """ ... @@ -179,6 +215,10 @@ async def translate( Returns: An Awaitable that resolves to a dictionary with the compiled program, memory descriptors, and readout sources (see `TranslationResult`). + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``TranslationError`` When the `native_quil` program could not be translated. """ ... @@ -199,6 +239,10 @@ async def submit( Returns: An Awaitable that resolves to the ID of the submitted job. + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``ExecutionError`` When there was a problem during program execution. """ ... @@ -217,6 +261,10 @@ async def retrieve_results( Returns: An Awaitable that resolves to a dictionary describing the results of the execution and its duration (see `ExecutionResults`). + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``ExecutionError`` When there was a problem fetching execution results. """ ... @@ -228,5 +276,9 @@ async def get_quilc_version( Args: client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``CompilationError`` When there is an issue fetching the version from the quilc compiler. """ ... diff --git a/crates/python/qcs_sdk/qpu/__init__.pyi b/crates/python/qcs_sdk/qpu/__init__.pyi index e69de29bb..e596a2f0a 100644 --- a/crates/python/qcs_sdk/qpu/__init__.pyi +++ b/crates/python/qcs_sdk/qpu/__init__.pyi @@ -0,0 +1,2 @@ +class QcsIsaError(RuntimeError): + ... diff --git a/crates/python/qcs_sdk/qpu/client.pyi b/crates/python/qcs_sdk/qpu/client.pyi new file mode 100644 index 000000000..2a0ef4d75 --- /dev/null +++ b/crates/python/qcs_sdk/qpu/client.pyi @@ -0,0 +1,65 @@ +from typing import Optional + + +class QcsClient: + """ + Configuration for connecting and authenticating to QCS API resources. + """ + + def __new__( + tokens: Optional[QcsClientTokens] = None, + api_url: Optional[str] = None, + auth_server: Optional[QcsClientAuthServer] = None, + grpc_api_url: Optional[str] = None, + quilc_url: Optional[str] = None, + qvm_url: Optional[str] = None, + ) -> "QcsClient": + """ + Construct a client from scratch. + + Use ``QcsClient.load`` to construct an environment-based profile. + """ + ... + + @staticmethod + def load( + profile_name: Optional[str] = None, + use_gateway: Optional[bool] = None, + ) -> "QcsClient": + """ + Load a QcsClient configuration using an environment-based configuration. + + See for details: https://docs.rigetti.com/qcs/references/qcs-client-configuration#environment-variables-and-configuration-files + """ + ... + + +class QcsClientAuthServer: + """Authentication server configuration for the QCS API.""" + + client_id: str + issuer: str + + + +class QcsClientTokens: + """Authentication tokens for the QCS API.""" + + bearer_access_token: str + refresh_token: str + + +class QcsGrpcClientError(RuntimeError): + """Error encountered while loading a QCS gRPC API client.""" + + +class QcsGrpcEndpointError(RuntimeError): + """Error when trying to resolve the QCS gRPC API endpoint.""" + + +class QcsGrpcError(RuntimeError): + """Error during QCS gRPC API requests.""" + + +class QcsLoadError(RuntimeError): + """Error encountered while loading the QCS API client configuration.""" diff --git a/crates/python/qcs_sdk/qpu/client/__init__.pyi b/crates/python/qcs_sdk/qpu/client/__init__.pyi deleted file mode 100644 index 1bae6324c..000000000 --- a/crates/python/qcs_sdk/qpu/client/__init__.pyi +++ /dev/null @@ -1,9 +0,0 @@ -class QcsClient: - """ - Here we are, deep in the belly of the module - """ - -class QcsLoadError: - """ - Clearly, something is amiss - """ \ No newline at end of file diff --git a/crates/python/qcs_sdk/qpu/quilc.pyi b/crates/python/qcs_sdk/qpu/quilc.pyi new file mode 100644 index 000000000..dd6e85fce --- /dev/null +++ b/crates/python/qcs_sdk/qpu/quilc.pyi @@ -0,0 +1,27 @@ +from typing import Any, Dict, Optional + +DEFAULT_COMPILER_TIMEOUT: int + + +class QuilcError(RuntimeError): + ... + + +class CompilerOpts: + """A set of options that determine the behavior of compiling programs with quilc.""" + + timeout: Optional[int] + """The number of seconds to wait before timing out. If `None`, there is no timeout.""" + + def __new__( + timeout: Optional[int] = DEFAULT_COMPILER_TIMEOUT + ) -> "CompilerOpts": + ... + + @staticmethod + def default() -> "CompilerOpts": ... + + +class TargetDevice: + isa: Any + specs: Dict[str, str] From ef9a28e7e786ad517a0523633e7d43babb6e8a5f Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 11:50:06 -0800 Subject: [PATCH 20/43] doc(lib): explain seeming type disagreement --- crates/lib/src/api.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index dabea8f8c..b73531e3f 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -210,6 +210,9 @@ pub enum Register { /// A register of 32-bit integers I32(Vec), /// A register of 64-bit complex numbers + /// + /// This type is called `Complex64` because the entire complex number takes 64 bits, + /// while the inner type is called `Complex32` because both components are `f32`. Complex64(Vec), /// A register of 8-bit integers (bytes) I8(Vec), From 5600581e7f48302761c5066b86d847670a69773e Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 11:50:38 -0800 Subject: [PATCH 21/43] refactor(python): simplify code, remove TODO --- crates/python/src/qpu/client.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index ca1af0fd0..842e6fec8 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -1,4 +1,4 @@ -use pyo3::{exceptions::PyRuntimeError, pymethods, Py, PyAny, PyResult, Python}; +use pyo3::{exceptions::PyRuntimeError, pymethods, PyResult, Python}; use pyo3_asyncio::tokio::future_into_py; use qcs::qpu::Qcs; use rigetti_pyo3::{ @@ -46,7 +46,6 @@ py_wrap_type! { #[pymethods] impl PyQcsClient { - // TODO: default arg #[new] #[args("/", use_gateway = "None")] pub fn new(py: Python<'_>, use_gateway: Option) -> PyResult { @@ -61,7 +60,7 @@ impl PyQcsClient { Some(use_gateway) => client.with_use_gateway(use_gateway), }; - Python::with_gil(|py| <_ as ToPython>>::to_python(&Self(client), py)) + Python::with_gil(|py| Self(client).to_python(py)) })? .extract() } From 7ef8bda68c33e96c51daff0cbf029332d6b3d816 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Mon, 23 Jan 2023 15:38:44 -0700 Subject: [PATCH 22/43] fix: typedict base class --- crates/python/qcs_sdk/qpu/client.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/python/qcs_sdk/qpu/client.pyi b/crates/python/qcs_sdk/qpu/client.pyi index 2a0ef4d75..ddd168633 100644 --- a/crates/python/qcs_sdk/qpu/client.pyi +++ b/crates/python/qcs_sdk/qpu/client.pyi @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, TypedDict class QcsClient: @@ -34,7 +34,7 @@ class QcsClient: ... -class QcsClientAuthServer: +class QcsClientAuthServer(TypedDict): """Authentication server configuration for the QCS API.""" client_id: str @@ -42,7 +42,7 @@ class QcsClientAuthServer: -class QcsClientTokens: +class QcsClientTokens(TypedDict): """Authentication tokens for the QCS API.""" bearer_access_token: str From bfda07dad970782f67d711804dfbb4e26a3f12f4 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 16:09:40 -0800 Subject: [PATCH 23/43] chore(python): remove unnecessary comments --- crates/python/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 4646a9eeb..3ca002a28 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -8,10 +8,6 @@ pub mod grpc; pub mod qpu; pub mod register_data; -// pub use executable::{Error, Executable, ExecuteResultQPU, ExecuteResultQVM, JobHandle, Service}; -// pub use execution_data::{Qpu, Qvm, ReadoutMap}; -// pub use register_data::RegisterData; - create_init_submodule! { classes: [ execution_data::PyQpu, From 258a664eee706a5e185163cef22cd3052f32d9df Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 16:10:16 -0800 Subject: [PATCH 24/43] refactor(lib): avoid confusion by changing Complex32 -> Complex --- crates/lib/src/api.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index b73531e3f..85186bf14 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, str::FromStr}; -use num::complex::Complex32; +use num::Complex; use qcs_api_client_grpc::{ models::controller::{readout_values, ControllerJobExecutionResult}, services::controller::{ @@ -210,10 +210,7 @@ pub enum Register { /// A register of 32-bit integers I32(Vec), /// A register of 64-bit complex numbers - /// - /// This type is called `Complex64` because the entire complex number takes 64 bits, - /// while the inner type is called `Complex32` because both components are `f32`. - Complex64(Vec), + Complex64(Vec>), /// A register of 8-bit integers (bytes) I8(Vec), } @@ -224,7 +221,7 @@ impl From for Register { runner::Register::F64(f) => Register::F64(f), runner::Register::I16(i) => Register::I16(i), runner::Register::Complex32(c) => { - Register::Complex64(c.iter().map(|c| Complex32::new(c.re, c.im)).collect()) + Register::Complex64(c.iter().map(|c| Complex::::new(c.re, c.im)).collect()) } runner::Register::I8(i) => Register::I8(i), } @@ -248,7 +245,9 @@ impl From for ExecutionResult { data: Register::Complex64( c.values .iter() - .map(|c| Complex32::new(c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0))) + .map(|c| { + Complex::::new(c.real.unwrap_or(0.0), c.imaginary.unwrap_or(0.0)) + }) .collect(), ), }, From 94c5a99d287d1bc09eec1e139685d9893e12ed39 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 17:06:30 -0800 Subject: [PATCH 25/43] chore(python): bump minimum python version to 3.8 --- crates/python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/python/pyproject.toml b/crates/python/pyproject.toml index 59989028f..fd435aede 100644 --- a/crates/python/pyproject.toml +++ b/crates/python/pyproject.toml @@ -40,7 +40,7 @@ compatibility = "linux" sdist-include = ["README.md"] [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" [tool.poetry.dev-dependencies] maturin = "^0.13.2" From 11002e29f3f701c1edb6d459a46380bc666f934e Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 17:06:48 -0800 Subject: [PATCH 26/43] chore(python): bump rigetti-pyo3 --- Cargo.lock | 5 +++-- crates/python/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 701c32dce..da4bfdb53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1579,8 +1579,9 @@ dependencies = [ [[package]] name = "rigetti-pyo3" -version = "0.0.1" -source = "git+ssh://git@github.com/rigetti/rigetti-pyo3?branch=support-fieldless-enums-data-structs#fec44462f47ce2ab63ecdac797842938ddd11903" +version = "0.1.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298f878d681dbe7bea9652f48816534c16f165c259b2b19d1dc5a4f00f929ad6" dependencies = [ "num-complex", "num-traits", diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index f7d363182..b38fa38f9 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -24,7 +24,7 @@ pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } quil-rs = "0.15" tokio = "1.21" qcs-api = "0.2.1" -rigetti-pyo3 = { version = "0.0.1", features = ["extension-module", "complex"], git = "ssh://git@github.com/rigetti/rigetti-pyo3", branch = "support-fieldless-enums-data-structs" } +rigetti-pyo3 = { version = "0.1.0-rc.0", features = ["extension-module", "complex"] } serde_json = "1.0.86" [build-dependencies] From b9c91f3035115a1ea726ff93d61a238b23af54ab Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 17:07:44 -0800 Subject: [PATCH 27/43] chore(python): gitignore __pycache__ --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4c9bc5cdc..7da525d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ qcs-api/docs # macOS .DS_Store + +# pytest artifacts +**/__pycache__ From 2babc4d3f8cbc895c7a84d1d717f64ae9a359fc5 Mon Sep 17 00:00:00 2001 From: Michael Bryant Date: Mon, 23 Jan 2023 17:19:48 -0800 Subject: [PATCH 28/43] chore(lib): update tokio, disable unused warp features --- Cargo.lock | 333 +++++++++++++----------------------------- crates/lib/Cargo.toml | 4 +- 2 files changed, 100 insertions(+), 237 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da4bfdb53..a256c8bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" dependencies = [ "proc-macro2", "quote", @@ -68,9 +68,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" +checksum = "678c5130a507ae3a7c797f9a17393c14849300b8440eac47cdb90a5bdcb3a543" dependencies = [ "async-trait", "axum-core", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" dependencies = [ "async-trait", "bytes", @@ -118,6 +118,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -133,21 +139,11 @@ dependencies = [ "generic-array", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytecount" @@ -496,7 +492,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -681,9 +677,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "itertools" @@ -715,7 +711,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" dependencies = [ - "base64", + "base64 0.13.1", "pem", "ring", "serde", @@ -896,7 +892,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -905,29 +901,11 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -935,9 +913,9 @@ dependencies = [ [[package]] name = "nom_locate" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605" +checksum = "b1e299bf5ea7b212e811e71174c5d1a5d065c4c0ad0c8691ecb1f97e3e66025e" dependencies = [ "bytecount", "memchr", @@ -972,9 +950,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", "serde", @@ -1057,15 +1035,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1080,7 +1058,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "599fe9aefc2ca0df4a96179b3075faee2cacb89d4cf947a00b9a89152dfffc9d" dependencies = [ - "base64", + "base64 0.13.1", "serde", ] @@ -1098,11 +1076,11 @@ dependencies = [ [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] @@ -1167,9 +1145,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" +checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78" dependencies = [ "proc-macro2", "syn", @@ -1177,18 +1155,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" dependencies = [ "bytes", "prost-derive", @@ -1196,9 +1174,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" +checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e" dependencies = [ "bytes", "heck", @@ -1218,9 +1196,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" dependencies = [ "anyhow", "itertools", @@ -1231,9 +1209,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" dependencies = [ "bytes", "prost", @@ -1344,7 +1322,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.5.10", + "toml 0.5.11", "tonic", "uuid", "warp", @@ -1377,7 +1355,7 @@ dependencies = [ "serde", "thiserror", "tokio", - "toml 0.5.10", + "toml 0.5.11", ] [[package]] @@ -1429,12 +1407,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quil-rs" version = "0.15.0" @@ -1513,9 +1485,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1539,11 +1511,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -1562,7 +1534,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "serde", "serde_json", "serde_urlencoded", @@ -1629,9 +1601,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -1646,7 +1618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "schannel", "security-framework", ] @@ -1657,16 +1629,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] @@ -1681,20 +1653,13 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1721,9 +1686,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "645926f31b250a2dca3c232496c2d898d91036e45ca0e97e0e2390c54e11be36" dependencies = [ "bitflags", "core-foundation", @@ -1734,9 +1699,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -1794,17 +1759,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha1" version = "0.10.5" @@ -2008,9 +1962,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.0" +version = "1.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" dependencies = [ "autocfg", "bytes", @@ -2023,7 +1977,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -2069,18 +2023,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -2103,9 +2045,9 @@ checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -2119,7 +2061,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -2133,7 +2075,7 @@ dependencies = [ "prost", "prost-derive", "rustls-native-certs", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "tokio", "tokio-rustls", "tokio-stream", @@ -2254,37 +2196,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "tungstenite" -version = "0.17.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" @@ -2303,9 +2217,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" @@ -2345,12 +2259,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "uuid" version = "1.2.2" @@ -2391,7 +2299,6 @@ dependencies = [ "log", "mime", "mime_guess", - "multipart", "percent-encoding", "pin-project", "rustls-pemfile 0.2.1", @@ -2401,7 +2308,6 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-stream", - "tokio-tungstenite", "tokio-util", "tower-service", "tracing", @@ -2510,9 +2416,9 @@ dependencies = [ [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -2541,19 +2447,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -2561,85 +2454,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winreg" diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index e1f69b3ed..07436d7fb 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -31,7 +31,7 @@ serde = { version = "1.0.145", features = ["derive"] } serde_bytes = "0.11.7" serde_json = "1.0.86" thiserror = "1.0.37" -tokio = { version = "1.21.2", features = ["fs"] } +tokio = { version = "1.24.2", features = ["fs"] } toml = "0.5.9" uuid = { version = "1.2.1", features = ["v4"] } tonic = { version = "0.8.2", features = ["tls", "tls-roots"] } @@ -46,5 +46,5 @@ qcs-api-client-grpc = { version = "0.2.7", features = ["server"] } simple_logger = { version = "2.3.0", default-features = false } tempfile = "3.3.0" tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] } -warp = "0.3.3" +warp = { version = "0.3.3", default-features = false } regex = "1.7.0" From e921666e8afc5f1bcb6720190f232bbb7be19918 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Tue, 24 Jan 2023 08:59:21 -0700 Subject: [PATCH 29/43] chore: add import --- crates/python/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 02a886fb7..36de3d7be 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -1,3 +1,4 @@ +use executable::QcsExecutionError; use pyo3::prelude::*; use rigetti_pyo3::create_init_submodule; From f57b65f98b5e99b6f2aa420ab4eaff31f0b80357 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Tue, 24 Jan 2023 09:16:38 -0700 Subject: [PATCH 30/43] fix: add methods for variants --- crates/python/qcs_sdk/_register_data.pyi | 26 +++++++++++++++--------- crates/python/qcs_sdk/api.pyi | 9 +++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/python/qcs_sdk/_register_data.pyi b/crates/python/qcs_sdk/_register_data.pyi index c5e2f7d42..fac98416f 100644 --- a/crates/python/qcs_sdk/_register_data.pyi +++ b/crates/python/qcs_sdk/_register_data.pyi @@ -18,8 +18,9 @@ class RegisterData: Methods (each per variant): - ``is_*``: if the underlying values are that type. - - ``as_*``: if the underlying values are that type, then those values values, otherwise ``None``. - - ``to_*``: the underlyting values as that type, raises ``ValueError`` if they are not. + - ``as_*``: if the underlying values are that type, then those values, otherwise ``None``. + - ``to_*``: the underlying values as that type, raises ``ValueError`` if they are not. + - ``from_*``: wrap underlying values as this enum type. """ @@ -28,12 +29,17 @@ class RegisterData: def is_f64() -> bool: ... def is_complex32() -> bool: ... - def as_i8() -> Optional[List[int]]: ... - def as_i16() -> Optional[List[int]]: ... - def as_f64() -> Optional[List[float]]: ... - def as_complex32() -> Optional[List[complex]]: ... + def as_i8() -> Optional[List[List[int]]]: ... + def as_i16() -> Optional[List[List[int]]]: ... + def as_f64() -> Optional[List[List[float]]]: ... + def as_complex32() -> Optional[List[List[complex]]]: ... - def to_i8() -> List[int]: ... - def to_i16() -> List[int]: ... - def to_f64() -> List[float]: ... - def to_complex32() -> List[complex]: ... + def to_i8() -> List[List[int]]: ... + def to_i16() -> List[List[int]]: ... + def to_f64() -> List[List[float]]: ... + def to_complex32() -> List[List[complex]]: ... + + def from_i8(inner: List[List[int]]) -> "RegisterData": ... + def from_i16(inner: List[List[int]]) -> "RegisterData": ... + def from_f64(inner: List[List[float]]) -> "RegisterData": ... + def from_complex32(inner: List[List[complex]]) -> "RegisterData": ... diff --git a/crates/python/qcs_sdk/api.pyi b/crates/python/qcs_sdk/api.pyi index b71e6b897..3e1eb5252 100644 --- a/crates/python/qcs_sdk/api.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -107,8 +107,9 @@ class Register: Methods (each per variant): - ``is_*``: if the underlying values are that type. - - ``as_*``: if the underlying values are that type, then those values values, otherwise ``None``. + - ``as_*``: if the underlying values are that type, then those values, otherwise ``None``. - ``to_*``: the underlyting values as that type, raises ``ValueError`` if they are not. + - ``from_*``: wrap underlying values as this enum type. """ @@ -130,6 +131,12 @@ class Register: def to_f64() -> List[float]: ... def to_complex64() -> List[complex]: ... + def from_i8(inner: List[int]) -> "Register": ... + def from_i16(inner: List[int]) -> "Register": ... + def from_i32(inner: List[int]) -> "Register": ... + def from_f64(inner: List[float]) -> "Register": ... + def from_complex64(inner: List[complex]) -> "Register": ... + async def compile( quil: str, From 601d2a4999fd7830c60d60627bcbb5603a578f5e Mon Sep 17 00:00:00 2001 From: jselig-rigetti <97701976+jselig-rigetti@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:31:44 -0700 Subject: [PATCH 31/43] Add `list_quantum_processors` helper (#234) * chore: add helper for listing qpu names * chore: mr feedback * chore: paginate with timeout * chore: comment update * Update crates/python/src/api.rs Co-authored-by: Michael Bryant * chore: use tokio timeout * chore: better error verbiage * chore: fix imports add test * chore: use namespacing instead of renaming * chore: use error converter helper * chore: remove superfluous gitignore Co-authored-by: Michael Bryant --- crates/lib/src/api.rs | 54 ++++++++++++++++++++++++++++++++- crates/python/qcs_sdk/api.pyi | 35 +++++++++++++++++++++ crates/python/src/api.rs | 39 +++++++++++++++++++++--- crates/python/src/lib.rs | 3 +- crates/python/tests/test_api.py | 6 ++++ 5 files changed, 131 insertions(+), 6 deletions(-) diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index 85186bf14..ece4cbad1 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -1,7 +1,7 @@ //! This module provides convenience functions to handle compilation, //! translation, parameter arithmetic rewriting, and results collection. -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, str::FromStr, time::Duration}; use num::Complex; use qcs_api_client_grpc::{ @@ -10,9 +10,11 @@ use qcs_api_client_grpc::{ get_controller_job_results_request::Target, GetControllerJobResultsRequest, }, }; +use qcs_api_client_openapi::apis::{quantum_processors_api, Error as OpenAPIError}; use quil_rs::expression::Expression; use quil_rs::{program::ProgramError, Program}; use serde::Serialize; +use tokio::time::error::Elapsed; use crate::qpu::{ self, @@ -313,3 +315,53 @@ pub async fn retrieve_results( .map(ExecutionResults::from) .ok_or_else(|| GrpcClientError::ResponseEmpty("Controller Job Execution Results".into())) } + +/// API Errors encountered when trying to list available quantum processors. +#[derive(Debug, thiserror::Error)] +pub enum ListQuantumProcessorsError { + /// Failed the http call + #[error("Failed to list processors via API: {0}")] + ApiError(#[from] OpenAPIError), + + /// Pagination did not finish before timeout + #[error("API pagination did not finish before timeout: {0:?}")] + TimeoutError(#[from] Elapsed), +} + +/// Query the QCS API for the names of all available quantum processors. +/// If `None`, the default `timeout` used is 10 seconds. +pub async fn list_quantum_processors( + client: &Qcs, + timeout: Option, +) -> Result, ListQuantumProcessorsError> { + let timeout = timeout.unwrap_or_else(|| Duration::from_secs(10)); + + tokio::time::timeout(timeout, async move { + let mut quantum_processors = vec![]; + let mut page_token = None; + + loop { + let result = quantum_processors_api::list_quantum_processors( + &client.get_openapi_client(), + Some(100), + page_token.as_deref(), + ) + .await?; + + let mut data = result + .quantum_processors + .into_iter() + .map(|qpu| qpu.id) + .collect::>(); + quantum_processors.append(&mut data); + + page_token = result.next_page_token; + if page_token.is_none() { + break; + } + } + + Ok(quantum_processors) + }) + .await? +} diff --git a/crates/python/qcs_sdk/api.pyi b/crates/python/qcs_sdk/api.pyi index 3e1eb5252..026634236 100644 --- a/crates/python/qcs_sdk/api.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -166,6 +166,7 @@ async def compile( """ ... + def rewrite_arithmetic( native_quil: str, ) -> RewriteArithmeticResults: @@ -185,6 +186,7 @@ def rewrite_arithmetic( """ ... + def build_patch_values( recalculation_table: RecalculationTable, memory: Memory, @@ -205,6 +207,7 @@ def build_patch_values( """ ... + async def translate( native_quil: str, num_shots: int, @@ -229,6 +232,7 @@ async def translate( """ ... + async def submit( program: str, patch_values: Dict[str, List[float]], @@ -253,6 +257,7 @@ async def submit( """ ... + async def retrieve_results( job_id: str, quantum_processor_id: str, @@ -275,6 +280,7 @@ async def retrieve_results( """ ... + async def get_quilc_version( client: Optional[QcsClient] = None, ) -> str: @@ -289,3 +295,32 @@ async def get_quilc_version( - ``CompilationError`` When there is an issue fetching the version from the quilc compiler. """ ... + + +async def get_quilc_version( + client: Optional[QcsClient] = None, +) -> str: + """ + Returns the version number of the running quilc server. + + Args: + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration + + Raises: + - ``LoadError`` When there is an issue loading the QCS Client configuration. + - ``CompilationError`` When there is an issue fetching the version from the quilc compiler. + """ + ... + +async def list_quantum_processors( + client: Optional[QcsClient] = None, + timeout: Optional[float] = None, +) -> List[str]: + """ + Returns all available Quantum Processor IDs. + + Args: + client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration + timeout: Maximum duration to wait for API calls to complete, in seconds. + """ + ... \ No newline at end of file diff --git a/crates/python/src/api.rs b/crates/python/src/api.rs index 02ef9fb2c..3d39eb89f 100644 --- a/crates/python/src/api.rs +++ b/crates/python/src/api.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; use pyo3::{ create_exception, @@ -10,13 +10,14 @@ use pyo3::{ }; use qcs::{ api::{ - ExecutionResult, ExecutionResults, Register, RewriteArithmeticResult, TranslationResult, + list_quantum_processors, ExecutionResult, ExecutionResults, Register, + RewriteArithmeticResult, TranslationResult, }, qpu::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT}, }; use rigetti_pyo3::{ create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, py_wrap_union_enum, - wrap_error, ToPython, + wrap_error, ToPython, ToPythonError, }; use crate::qpu::client::PyQcsClient; @@ -35,6 +36,7 @@ create_init_submodule! { CompilationError, RewriteArithmeticError, DeviceIsaError, + QcsListQuantumProcessorsError, QcsSubmitError ], funcs: [ @@ -44,7 +46,8 @@ create_init_submodule! { submit, retrieve_results, build_patch_values, - get_quilc_version + get_quilc_version, + py_list_quantum_processors ], } @@ -89,6 +92,16 @@ create_exception!(qcs, DeviceIsaError, PyValueError); wrap_error!(SubmitError(qcs::api::SubmitError)); py_wrap_error!(api, SubmitError, QcsSubmitError, PyRuntimeError); +wrap_error!(ListQuantumProcessorsError( + qcs::api::ListQuantumProcessorsError +)); +py_wrap_error!( + api, + ListQuantumProcessorsError, + QcsListQuantumProcessorsError, + PyRuntimeError +); + #[pyfunction(client = "None", kwds = "**")] pub fn compile<'a>( py: Python<'a>, @@ -208,3 +221,21 @@ pub fn get_quilc_version(py: Python<'_>, client: Option) -> PyResul Ok(version) }) } + +#[pyfunction(client = "None", timeout = "None")] +#[pyo3(name = "list_quantum_processors")] +pub fn py_list_quantum_processors( + py: Python<'_>, + client: Option, + timeout: Option, +) -> PyResult<&PyAny> { + pyo3_asyncio::tokio::future_into_py(py, async move { + let client = PyQcsClient::get_or_create_client(client).await?; + let timeout = timeout.map(Duration::from_secs_f64); + let names = list_quantum_processors(&client, timeout) + .await + .map_err(ListQuantumProcessorsError::from) + .map_err(ListQuantumProcessorsError::to_py_err)?; + Ok(names) + }) +} diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 36de3d7be..c1dbe7abe 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -30,7 +30,8 @@ create_init_submodule! { api::submit, api::retrieve_results, api::build_patch_values, - api::get_quilc_version + api::get_quilc_version, + api::py_list_quantum_processors ], submodules: [ "api": api::init_submodule, diff --git a/crates/python/tests/test_api.py b/crates/python/tests/test_api.py index 7abefc608..4864d3382 100644 --- a/crates/python/tests/test_api.py +++ b/crates/python/tests/test_api.py @@ -48,3 +48,9 @@ def test_build_patch_values(): async def test_get_quilc_version(): version = await qcs_sdk.get_quilc_version() assert re.match(r"^([0-9]+)\.([0-9]+)\.([0-9]+)$", version) + + +@pytest.mark.asyncio +async def test_list_quantum_processors(): + qpus = await qcs_sdk.list_quantum_processors() + assert isinstance(qpus, list) From ac2d5ea969ffe6a3438263adbaa181407f33842d Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Tue, 24 Jan 2023 13:01:17 -0700 Subject: [PATCH 32/43] chore: ignore api-bound pytest --- crates/python/tests/test_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/python/tests/test_api.py b/crates/python/tests/test_api.py index 86bf74d1b..ed0b53138 100644 --- a/crates/python/tests/test_api.py +++ b/crates/python/tests/test_api.py @@ -52,6 +52,7 @@ async def test_get_quilc_version(): @pytest.mark.asyncio +@pytest.mark.skip async def test_list_quantum_processors(): qpus = await qcs_sdk.list_quantum_processors() assert isinstance(qpus, list) From e3595c1cc7af2a9749b8141aad2966741d28aff7 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Wed, 25 Jan 2023 10:55:14 -0700 Subject: [PATCH 33/43] chore(python): update pyi definitions and use better struct serialization in client --- crates/python/qcs_sdk/__init__.py | 4 +- crates/python/qcs_sdk/__init__.pyi | 10 +-- crates/python/qcs_sdk/_executable.pyi | 13 ++- crates/python/qcs_sdk/_register_data.pyi | 34 ++++---- crates/python/qcs_sdk/api.pyi | 84 +++++++++--------- crates/python/qcs_sdk/py.typed | 0 crates/python/src/qpu/client.rs | 106 ++++++++++++----------- 7 files changed, 133 insertions(+), 118 deletions(-) create mode 100644 crates/python/qcs_sdk/py.typed diff --git a/crates/python/qcs_sdk/__init__.py b/crates/python/qcs_sdk/__init__.py index 6bac5b077..e0d6434d7 100644 --- a/crates/python/qcs_sdk/__init__.py +++ b/crates/python/qcs_sdk/__init__.py @@ -1,2 +1,4 @@ -# See https://www.maturin.rs/project_layout.html#adding-python-type-information +# See the following documentation for why this file is necessary: +# https://pyo3.rs/v0.18.0/python_typing_hints#__init__py-content + from .qcs_sdk import * diff --git a/crates/python/qcs_sdk/__init__.pyi b/crates/python/qcs_sdk/__init__.pyi index aaf8fcc87..4e79b36fb 100644 --- a/crates/python/qcs_sdk/__init__.pyi +++ b/crates/python/qcs_sdk/__init__.pyi @@ -1,5 +1,3 @@ -from enum import Enum - from .api import * from .qpu.client import ( @@ -16,15 +14,9 @@ from ._executable import ( Executable as Executable, JobHandle as JobHandle, QcsExecutionError as QcsExecutionError, + Service as Service, ) from ._register_data import ( RegisterData as RegisterData, ) - - -class Service(Enum): - Quilc = "Quilc", - Qvm = "Qvm", - Qcs = "Qcs", - Qpu = "Qpu", diff --git a/crates/python/qcs_sdk/_executable.pyi b/crates/python/qcs_sdk/_executable.pyi index 202a6e113..3bedf6eaf 100644 --- a/crates/python/qcs_sdk/_executable.pyi +++ b/crates/python/qcs_sdk/_executable.pyi @@ -2,6 +2,7 @@ Do not import this file, it has no exports. It is only here to represent the structure of the rust source code 1:1 """ +from enum import Enum from typing import Dict, List, Optional from .qpu.quilc import CompilerOpts @@ -26,7 +27,7 @@ class Executable: Execute on a QVM which must be available at the configured URL (default http://localhost:5000). Raises: - - ``QcsExecutionError``: When the job failed to execute. + - ``QcsExecutionError``: If the job fails to execute. """ ... @@ -37,7 +38,7 @@ class Executable: Compile the program and execute it on a QPU, waiting for results. Raises: - - ``QcsExecutionError``: When the job failed to execute. + - ``QcsExecutionError``: If the job fails to execute. """ ... @@ -48,7 +49,7 @@ class Executable: Wait for the results of a job to complete. Raises: - - ``QcsExecutionError``: When there is a problem constructing job results. + - ``QcsExecutionError``: If there is a problem constructing job results. """ ... @@ -87,3 +88,9 @@ class ExeParameter: value: float """The value to set for the specified memory.""" + +class Service(Enum): + Quilc = "Quilc", + Qvm = "Qvm", + Qcs = "Qcs", + Qpu = "Qpu", diff --git a/crates/python/qcs_sdk/_register_data.pyi b/crates/python/qcs_sdk/_register_data.pyi index fac98416f..3e7bd7313 100644 --- a/crates/python/qcs_sdk/_register_data.pyi +++ b/crates/python/qcs_sdk/_register_data.pyi @@ -24,22 +24,26 @@ class RegisterData: """ - def is_i8() -> bool: ... - def is_i16() -> bool: ... - def is_f64() -> bool: ... - def is_complex32() -> bool: ... - - def as_i8() -> Optional[List[List[int]]]: ... - def as_i16() -> Optional[List[List[int]]]: ... - def as_f64() -> Optional[List[List[float]]]: ... - def as_complex32() -> Optional[List[List[complex]]]: ... - - def to_i8() -> List[List[int]]: ... - def to_i16() -> List[List[int]]: ... - def to_f64() -> List[List[float]]: ... - def to_complex32() -> List[List[complex]]: ... - + def is_i8(self) -> bool: ... + def is_i16(self) -> bool: ... + def is_f64(self) -> bool: ... + def is_complex32(self) -> bool: ... + + def as_i8(self) -> Optional[List[List[int]]]: ... + def as_i16(self) -> Optional[List[List[int]]]: ... + def as_f64(self) -> Optional[List[List[float]]]: ... + def as_complex32(self) -> Optional[List[List[complex]]]: ... + + def to_i8(self) -> List[List[int]]: ... + def to_i16(self) -> List[List[int]]: ... + def to_f64(self) -> List[List[float]]: ... + def to_complex32(self) -> List[List[complex]]: ... + + @staticmethod def from_i8(inner: List[List[int]]) -> "RegisterData": ... + @staticmethod def from_i16(inner: List[List[int]]) -> "RegisterData": ... + @staticmethod def from_f64(inner: List[List[float]]) -> "RegisterData": ... + @staticmethod def from_complex32(inner: List[List[complex]]) -> "RegisterData": ... diff --git a/crates/python/qcs_sdk/api.pyi b/crates/python/qcs_sdk/api.pyi index 026634236..983468282 100644 --- a/crates/python/qcs_sdk/api.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -1,9 +1,8 @@ """ The qcs_sdk module provides an interface to Rigetti Quantum Cloud Services. Allowing users to compile and run Quil programs on Rigetti quantum processors. """ -from typing import Any, Dict, List, Optional, TypedDict +from typing import Dict, List, Optional from numbers import Number -from enum import Enum from .qpu.client import QcsClient @@ -36,7 +35,7 @@ class DeviceIsaError(ValueError): ... -class RewriteArithmeticResults(TypedDict): +class RewriteArithmeticResults: """ The result of a call to [`rewrite_arithmetic`] which provides the information necessary to later patch-in memory values to a compiled program. """ @@ -51,7 +50,7 @@ class RewriteArithmeticResults(TypedDict): The recalculation table stores an ordered list of arithmetic expressions, which are to be used when updating the program memory before execution. """ -class TranslationResult(TypedDict): +class TranslationResult: """ The result of a call to [`translate`] which provides information about the translated program. """ @@ -67,7 +66,7 @@ class TranslationResult(TypedDict): """ -class ExecutionResult(TypedDict): +class ExecutionResult: """Execution readout data from a particular memory location.""" shape: List[int] @@ -80,7 +79,7 @@ class ExecutionResult(TypedDict): """The type of the result data (as a `numpy` `dtype`).""" -class ExecutionResults(TypedDict): +class ExecutionResults: """Execution readout data for all memory locations.""" buffers: Dict[str, ExecutionResult] @@ -113,28 +112,33 @@ class Register: """ - def is_i8() -> bool: ... - def is_i16() -> bool: ... - def is_i32() -> bool: ... - def is_f64() -> bool: ... - def is_complex64() -> bool: ... - - def as_i8() -> Optional[List[int]]: ... - def as_i16() -> Optional[List[int]]: ... - def as_i32() -> Optional[List[int]]: ... - def as_f64() -> Optional[List[float]]: ... - def as_complex64() -> Optional[List[complex]]: ... - - def to_i8() -> List[int]: ... - def to_i16() -> List[int]: ... - def to_i32() -> List[int]: ... - def to_f64() -> List[float]: ... - def to_complex64() -> List[complex]: ... - + def is_i8(self) -> bool: ... + def is_i16(self) -> bool: ... + def is_i32(self) -> bool: ... + def is_f64(self) -> bool: ... + def is_complex64(self) -> bool: ... + + def as_i8(self) -> Optional[List[int]]: ... + def as_i16(self) -> Optional[List[int]]: ... + def as_i32(self) -> Optional[List[int]]: ... + def as_f64(self) -> Optional[List[float]]: ... + def as_complex64(self) -> Optional[List[complex]]: ... + + def to_i8(self) -> List[int]: ... + def to_i16(self) -> List[int]: ... + def to_i32(self) -> List[int]: ... + def to_f64(self) -> List[float]: ... + def to_complex64(self) -> List[complex]: ... + + @staticmethod def from_i8(inner: List[int]) -> "Register": ... + @staticmethod def from_i16(inner: List[int]) -> "Register": ... + @staticmethod def from_i32(inner: List[int]) -> "Register": ... + @staticmethod def from_f64(inner: List[float]) -> "Register": ... + @staticmethod def from_complex64(inner: List[complex]) -> "Register": ... @@ -160,9 +164,9 @@ async def compile( An Awaitable that resolves to the native Quil program. Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``DeviceIsaError`` When the `target_device` is misconfigured. - - ``CompilationError`` When the program could not compile. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``DeviceIsaError`` If the `target_device` is misconfigured. + - ``CompilationError`` If the program could not compile. """ ... @@ -181,8 +185,8 @@ def rewrite_arithmetic( A dictionary with the rewritten program and recalculation table (see `RewriteArithmeticResults`). Raises: - - ``TranslationError`` When the program could not be translated. - - ``RewriteArithmeticError`` When the program arithmetic cannot be evaluated. + - ``TranslationError`` If the program could not be translated. + - ``RewriteArithmeticError`` If the program arithmetic cannot be evaluated. """ ... @@ -203,7 +207,7 @@ def build_patch_values( A dictionary that maps each symbol to the value it should be patched with. Raises: - - ``TranslationError`` When the expressions in `recalculation_table` could not be evaluated. + - ``TranslationError`` If the expressions in `recalculation_table` could not be evaluated. """ ... @@ -227,8 +231,8 @@ async def translate( An Awaitable that resolves to a dictionary with the compiled program, memory descriptors, and readout sources (see `TranslationResult`). Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``TranslationError`` When the `native_quil` program could not be translated. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``TranslationError`` If the `native_quil` program could not be translated. """ ... @@ -252,8 +256,8 @@ async def submit( An Awaitable that resolves to the ID of the submitted job. Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``ExecutionError`` When there was a problem during program execution. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``ExecutionError`` If there was a problem during program execution. """ ... @@ -275,8 +279,8 @@ async def retrieve_results( An Awaitable that resolves to a dictionary describing the results of the execution and its duration (see `ExecutionResults`). Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``ExecutionError`` When there was a problem fetching execution results. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``ExecutionError`` If there was a problem fetching execution results. """ ... @@ -291,8 +295,8 @@ async def get_quilc_version( client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``CompilationError`` When there is an issue fetching the version from the quilc compiler. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``CompilationError`` If there is an issue fetching the version from the quilc compiler. """ ... @@ -307,8 +311,8 @@ async def get_quilc_version( client: The QcsClient to use. Loads one using environment configuration if unset - see https://docs.rigetti.com/qcs/references/qcs-client-configuration Raises: - - ``LoadError`` When there is an issue loading the QCS Client configuration. - - ``CompilationError`` When there is an issue fetching the version from the quilc compiler. + - ``LoadError`` If there is an issue loading the QCS Client configuration. + - ``CompilationError`` If there is an issue fetching the version from the quilc compiler. """ ... diff --git a/crates/python/qcs_sdk/py.typed b/crates/python/qcs_sdk/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index b37eea7c8..d497569bc 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -1,17 +1,19 @@ -use pyo3::{ - conversion::IntoPy, exceptions::PyRuntimeError, pyclass::CompareOp, pymethods, types::PyDict, - Py, PyAny, PyErr, PyObject, PyResult, Python, -}; use pyo3_asyncio::tokio::future_into_py; -use qcs::qpu::Qcs; use qcs_api_client_common::{ configuration::{AuthServer, BuildError, ClientConfigurationBuilder, Tokens}, ClientConfiguration, }; use rigetti_pyo3::{ - create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, ToPythonError, + create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, + pyo3::{ + conversion::IntoPy, exceptions::PyRuntimeError, prelude::*, pyclass::CompareOp, pymethods, + types::PyString, Py, PyAny, PyObject, PyResult, Python, + }, + wrap_error, ToPythonError, }; +use qcs::qpu::Qcs; + create_init_submodule! { classes: [ PyQcsClient, @@ -59,54 +61,58 @@ py_wrap_error!( PyRuntimeError ); -fn get_pydict_str(py_dict: &PyDict, key: &str) -> Option { - py_dict.get_item(key)?.extract().ok() +/// The fields on qcs_api_client_common::client::AuthServer are not public. +#[derive(Clone)] +pub struct QcsClientAuthServer { + pub client_id: Option, + pub issuer: Option, +} + +py_wrap_data_struct! { + PyQcsClientAuthServer(QcsClientAuthServer) as "QcsClientAuthServer" { + client_id: Option => Option>, + issuer: Option => Option> + } } -py_wrap_struct! { - PyQcsClientAuthServer(AuthServer) as "QcsClientAuthServer" { - py -> rs { - py_dict: Py => AuthServer { - let py_dict = py_dict.as_ref(py); - let mut auth_server = AuthServer::default(); - if let Some(client_id) = get_pydict_str(py_dict, "client_id") { - auth_server = auth_server.set_client_id(client_id); - } - if let Some(issuer) = get_pydict_str(py_dict, "issuer") { - auth_server = auth_server.set_issuer(issuer); - } - Ok::<_, PyErr>(auth_server) - } - }, - rs -> py { - rs_struct: AuthServer => Py { - let obj = PyDict::new(py); - obj.set_item("client_id", rs_struct.client_id())?; - obj.set_item("issuer", rs_struct.issuer())?; - Ok(obj.into_py(py)) - } +impl From for AuthServer { + fn from(value: PyQcsClientAuthServer) -> Self { + let mut auth_server = AuthServer::default(); + if let Some(client_id) = value.0.client_id { + auth_server = auth_server.set_client_id(client_id); + } + if let Some(issuer) = value.0.issuer { + auth_server = auth_server.set_issuer(issuer); } + auth_server + } +} + +#[pymethods] +impl PyQcsClientAuthServer { + #[new] + #[args(client_id = "None", issuer = "None")] + pub fn new(client_id: Option, issuer: Option) -> Self { + Self(QcsClientAuthServer { client_id, issuer }) } } -py_wrap_struct! { +py_wrap_data_struct! { PyQcsClientTokens(Tokens) as "QcsClientTokens" { - py -> rs { - py_dict: Py => Tokens { - let py_dict = py_dict.as_ref(py); - let bearer_access_token = get_pydict_str(py_dict, "bearer_access_token"); - let refresh_token = get_pydict_str(py_dict, "refresh_token"); - Ok::<_, PyErr>(Tokens { bearer_access_token, refresh_token }) - } - }, - rs -> py { - rs_struct: Tokens => Py { - let obj = PyDict::new(py); - obj.set_item("bearer_access_token", rs_struct.bearer_access_token)?; - obj.set_item("refresh_token", rs_struct.refresh_token)?; - Ok(obj.into_py(py)) - } - } + bearer_access_token: Option => Option>, + refresh_token: Option => Option> + } +} + +#[pymethods] +impl PyQcsClientTokens { + #[new] + #[args(bearer_access_token = "None", refresh_token = "None")] + pub fn new(bearer_access_token: Option, refresh_token: Option) -> Self { + Self(Tokens { + bearer_access_token, + refresh_token, + }) } } @@ -117,7 +123,7 @@ py_wrap_type! { impl PyQcsClient { pub(crate) async fn get_or_create_client(client: Option) -> PyResult { Ok(match client { - Some(client) => client.0, + Some(client) => client.into(), None => Qcs::load() .await .map_err(LoadError::from) @@ -154,13 +160,13 @@ impl PyQcsClient { ) -> PyResult { let mut builder = ClientConfigurationBuilder::default(); if let Some(tokens) = tokens { - builder = builder.set_tokens(tokens.0); + builder = builder.set_tokens(tokens.into()); } if let Some(api_url) = api_url { builder = builder.set_api_url(api_url); } if let Some(auth_server) = auth_server { - builder = builder.set_auth_server(auth_server.0); + builder = builder.set_auth_server(auth_server.into()); } if let Some(grpc_api_url) = grpc_api_url { builder = builder.set_grpc_api_url(grpc_api_url); From 1da3a51bfcfccfe8753f781d5771ce6de6d6f640 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Wed, 25 Jan 2023 10:59:57 -0700 Subject: [PATCH 34/43] chore(python): add pyo3 usage tests --- crates/python/src/qpu/client.rs | 2 +- crates/python/tests/test_client.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index d497569bc..48250baf1 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -6,7 +6,7 @@ use qcs_api_client_common::{ use rigetti_pyo3::{ create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, pyo3::{ - conversion::IntoPy, exceptions::PyRuntimeError, prelude::*, pyclass::CompareOp, pymethods, + conversion::IntoPy, exceptions::PyRuntimeError, pyclass::CompareOp, pymethods, types::PyString, Py, PyAny, PyObject, PyResult, Python, }, wrap_error, ToPythonError, diff --git a/crates/python/tests/test_client.py b/crates/python/tests/test_client.py index 51bd0594d..29552b395 100644 --- a/crates/python/tests/test_client.py +++ b/crates/python/tests/test_client.py @@ -1,7 +1,7 @@ import pytest from qcs_sdk import QcsClient -from qcs_sdk.qpu.client import QcsLoadError +from qcs_sdk.qpu.client import QcsLoadError, QcsClientAuthServer, QcsClientTokens @pytest.fixture def default_client(): @@ -29,3 +29,17 @@ async def test_client_broken_raises(): """Using a profile with broken configuration should surface the underlying error.""" with pytest.raises(QcsLoadError, match=r"Expected auth server .* but it didn't exist"): await QcsClient.load(profile_name="broken") + + +def test_client_auth_server_can_be_manually_defined(): + """Ensures that pyo3 usage is correct.""" + auth_server = QcsClientAuthServer(client_id="foo", issuer="bar") + assert auth_server.client_id == "foo" + assert auth_server.issuer == "bar" + + +def test_client_tokens_can_be_manually_defined(): + """Ensures that pyo3 usage is correct.""" + auth_server = QcsClientTokens(bearer_access_token="foo", refresh_token="bar") + assert auth_server.bearer_access_token == "foo" + assert auth_server.refresh_token == "bar" From a5b06c7e39be68148c6de410559dea1e49534ac0 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Wed, 25 Jan 2023 11:02:55 -0700 Subject: [PATCH 35/43] chore(python): update return type definition for executable methods --- crates/python/qcs_sdk/_executable.pyi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/python/qcs_sdk/_executable.pyi b/crates/python/qcs_sdk/_executable.pyi index 3bedf6eaf..b091f4835 100644 --- a/crates/python/qcs_sdk/_executable.pyi +++ b/crates/python/qcs_sdk/_executable.pyi @@ -6,6 +6,7 @@ from enum import Enum from typing import Dict, List, Optional from .qpu.quilc import CompilerOpts +from ._execution_data import QVM, QPU class QcsExecutionError(RuntimeError): """Error encounteted when executing programs.""" @@ -22,7 +23,7 @@ class Executable: compiler_options: Optional[CompilerOpts] ) -> "Executable": ... - async def execute_on_qvm(): + async def execute_on_qvm() -> QVM: """ Execute on a QVM which must be available at the configured URL (default http://localhost:5000). @@ -33,7 +34,7 @@ class Executable: async def execute_on_qpu( quantum_processor_id: str - ): + ) -> QPU: """ Compile the program and execute it on a QPU, waiting for results. @@ -44,7 +45,7 @@ class Executable: async def retrieve_results( job_handle: JobHandle - ): + ) -> QPU: """ Wait for the results of a job to complete. From 09770322aef5a5ce7b2ab9ef2bf01d0b999290c1 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Thu, 26 Jan 2023 13:48:11 -0700 Subject: [PATCH 36/43] chore: make all attributes into properties --- crates/python/qcs_sdk/__init__.pyi | 1 + crates/python/qcs_sdk/_executable.pyi | 72 +++++++++++--------- crates/python/qcs_sdk/_execution_data.pyi | 41 +++++++----- crates/python/qcs_sdk/api.pyi | 81 ++++++++++++++--------- crates/python/qcs_sdk/qpu/client.pyi | 28 ++++++-- crates/python/qcs_sdk/qpu/quilc.pyi | 10 +-- crates/python/src/executable.rs | 48 ++++++++++---- crates/python/src/lib.rs | 1 + crates/python/tests/test_api.py | 8 +++ 9 files changed, 189 insertions(+), 101 deletions(-) diff --git a/crates/python/qcs_sdk/__init__.pyi b/crates/python/qcs_sdk/__init__.pyi index 4e79b36fb..279ad5cb6 100644 --- a/crates/python/qcs_sdk/__init__.pyi +++ b/crates/python/qcs_sdk/__init__.pyi @@ -12,6 +12,7 @@ from ._execution_data import ( from ._executable import ( Executable as Executable, + ExeParameter as ExeParameter, JobHandle as JobHandle, QcsExecutionError as QcsExecutionError, Service as Service, diff --git a/crates/python/qcs_sdk/_executable.pyi b/crates/python/qcs_sdk/_executable.pyi index b091f4835..2ec80827d 100644 --- a/crates/python/qcs_sdk/_executable.pyi +++ b/crates/python/qcs_sdk/_executable.pyi @@ -14,16 +14,16 @@ class QcsExecutionError(RuntimeError): class Executable: - """""" def __new__( - registers: Optional[List[str]], - parameters: Optional[List[ExeParameter]], - shots: Optional[int], - compile_with_quilc: Optional[bool], - compiler_options: Optional[CompilerOpts] + cls, + registers: Optional[List[str]] = None, + parameters: Optional[List[ExeParameter]] = None, + shots: Optional[int] = None, + compile_with_quilc: Optional[bool] = None, + compiler_options: Optional[CompilerOpts] = None ) -> "Executable": ... - async def execute_on_qvm() -> QVM: + async def execute_on_qvm(self) -> QVM: """ Execute on a QVM which must be available at the configured URL (default http://localhost:5000). @@ -32,9 +32,7 @@ class Executable: """ ... - async def execute_on_qpu( - quantum_processor_id: str - ) -> QPU: + async def execute_on_qpu(self, quantum_processor_id: str) -> QPU: """ Compile the program and execute it on a QPU, waiting for results. @@ -43,9 +41,7 @@ class Executable: """ ... - async def retrieve_results( - job_handle: JobHandle - ) -> QPU: + async def retrieve_results(job_handle: JobHandle) -> QPU: """ Wait for the results of a job to complete. @@ -62,13 +58,19 @@ class JobHandle: Used to retrieve the results of a job. """ - job_id: str - """Unique ID associated with a single job execution.""" + @property + def job_id(self) -> str: + """ + Unique ID associated with a single job execution. + """ + ... - readout_map: Dict[str, str] - """ - The readout map from source readout memory locations to the filter pipeline node which publishes the data. - """ + @property + def readout_map(self) -> Dict[str, str]: + """ + The readout map from source readout memory locations to the filter pipeline node which publishes the data. + """ + ... class ExeParameter: @@ -77,17 +79,27 @@ class ExeParameter: Note: The validity of parameters is not checked until execution. """ - - param_name: str - """ - References the name of the parameter corresponding to a `DECLARE` statement in the Quil program. - """ - - index: int - """The index into the memory vector that you're setting.""" - - value: float - """The value to set for the specified memory.""" + def __new__( + cls: type["ExeParameter"], + name: str, + index: int, + value: float, + ) -> "ExeParameter": ... + + @property + def name(self) -> str: ... + @name.setter + def name(self, value: str): ... + + @property + def index(self) -> int: ... + @index.setter + def index(self, value: int): ... + + @property + def value(self) -> float: ... + @value.setter + def value(self, value: float): ... class Service(Enum): diff --git a/crates/python/qcs_sdk/_execution_data.pyi b/crates/python/qcs_sdk/_execution_data.pyi index 9d2b8d51e..5a10a2785 100644 --- a/crates/python/qcs_sdk/_execution_data.pyi +++ b/crates/python/qcs_sdk/_execution_data.pyi @@ -6,34 +6,45 @@ It is only here to represent the structure of the rust source code 1:1 import datetime from typing import List, Optional -class IntegerReadoutValues: - values: List[int] +class _IntegerReadoutValues: + @property + def values(self) -> List[int]: ... -class ComplexReadoutValues: - values: List[complex] +class _ComplexReadoutValues: + @property + def values(self) -> List[complex]: ... -class ReadoutValuesValues: - integer_values: IntegerReadoutValues - complex_values: ComplexReadoutValues +class _ReadoutValuesValues: + @property + def integer_values(self) -> _IntegerReadoutValues: ... + @property + def complex_values(self) -> _ComplexReadoutValues: ... -class ReadoutValues: - values: Optional[ReadoutValuesValues] +class _ReadoutValues: + @property + def values(self) -> Optional[_ReadoutValuesValues]: ... class ReadoutMap: - def get_readout_values(self, field: str, index: int) -> Optional[ReadoutValues]: + def get_readout_values(self, field: str, index: int) -> Optional[_ReadoutValues]: + """Given a known readout field name and index, return the result's ``ReadoutValues``, if any.""" ... - def get_readout_values_for_field(self, field: str) -> Optional[List[Optional[ReadoutValues]]]: + def get_readout_values_for_field(self, field: str) -> Optional[List[Optional[_ReadoutValues]]]: + """Given a known readout field name, return the result's ``ReadoutValues`` for all indices, if any.""" ... class QVM: - registers: dict - duration: Optional[datetime.timedelta] + @property + def registers(self) -> dict: ... + @property + def duration(self) -> Optional[datetime.timedelta]: ... class QPU: - readout_data: ReadoutMap - duration: Optional[datetime.timedelta] + @property + def readout_data(self) -> ReadoutMap: ... + @property + def duration(self) -> Optional[datetime.timedelta]: ... diff --git a/crates/python/qcs_sdk/api.pyi b/crates/python/qcs_sdk/api.pyi index 983468282..a6769aa54 100644 --- a/crates/python/qcs_sdk/api.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -40,57 +40,76 @@ class RewriteArithmeticResults: The result of a call to [`rewrite_arithmetic`] which provides the information necessary to later patch-in memory values to a compiled program. """ - program: str - """ - The resulting program where gate parameter arithmetic has been replaced with memory references. Before execution, the program memory should be updated using the `recalculation_table`. - """ + @property + def program(self) -> str: + """ + The resulting program where gate parameter arithmetic has been replaced with memory references. Before execution, the program memory should be updated using the `recalculation_table`. + """ + ... + + @property + def recalculation_table(self) -> List[str]: + """ + The recalculation table stores an ordered list of arithmetic expressions, which are to be used when updating the program memory before execution. + """ + ... - recalculation_table: List[str] - """ - The recalculation table stores an ordered list of arithmetic expressions, which are to be used when updating the program memory before execution. - """ class TranslationResult: """ The result of a call to [`translate`] which provides information about the translated program. """ - program: str - """ - The compiled program binary. - """ + @property + def program(self) -> str: + """ + The compiled program binary. + """ + ... - ro_sources: Optional[dict] - """ - A mapping from the program's memory references to the key used to index the results map. - """ + @property + def ro_sources(self) -> Optional[dict]: + """ + A mapping from the program's memory references to the key used to index the results map. + """ + ... class ExecutionResult: """Execution readout data from a particular memory location.""" - shape: List[int] - """The shape of the result data.""" - - data: List[Number | List[float]] - """The result data. Complex numbers are represented as [real, imaginary].""" + @property + def shape(self) -> List[int]: + """The shape of the result data.""" + ... + + @property + def data(self) -> List[Number | List[float]]: + """The result data. Complex numbers are represented as [real, imaginary].""" + ... - dtype: str - """The type of the result data (as a `numpy` `dtype`).""" + @property + def dtype(self) -> str: + """The type of the result data (as a `numpy` `dtype`).""" + ... class ExecutionResults: """Execution readout data for all memory locations.""" - buffers: Dict[str, ExecutionResult] - """ - The readout results of execution, mapping a published filter node to its data. + @property + def buffers(self) -> Dict[str, ExecutionResult]: + """ + The readout results of execution, mapping a published filter node to its data. - See `TranslationResult.ro_sources` which provides the mapping from the filter node name to the name of the memory declaration in the source program. - """ - - execution_duration_microseconds: Optional[int] - """The time spent executing the program.""" + See `TranslationResult.ro_sources` which provides the mapping from the filter node name to the name of the memory declaration in the source program. + """ + ... + + @property + def execution_duration_microseconds(self) -> Optional[int]: + """The time spent executing the program.""" + ... class Register: diff --git a/crates/python/qcs_sdk/qpu/client.pyi b/crates/python/qcs_sdk/qpu/client.pyi index ddd168633..c59311ed5 100644 --- a/crates/python/qcs_sdk/qpu/client.pyi +++ b/crates/python/qcs_sdk/qpu/client.pyi @@ -1,4 +1,4 @@ -from typing import Optional, TypedDict +from typing import Optional class QcsClient: @@ -7,6 +7,7 @@ class QcsClient: """ def __new__( + cls, tokens: Optional[QcsClientTokens] = None, api_url: Optional[str] = None, auth_server: Optional[QcsClientAuthServer] = None, @@ -34,19 +35,32 @@ class QcsClient: ... -class QcsClientAuthServer(TypedDict): +class QcsClientAuthServer: """Authentication server configuration for the QCS API.""" - client_id: str - issuer: str + @property + def client_id(self) -> str: ... + @client_id.setter + def client_id(self, value: str): ... + @property + def issuer(self) -> str: ... + @issuer.setter + def issuer(self, value: str): ... -class QcsClientTokens(TypedDict): +class QcsClientTokens: """Authentication tokens for the QCS API.""" - bearer_access_token: str - refresh_token: str + @property + def bearer_access_token(self) -> Optional[str]: ... + @bearer_access_token.setter + def bearer_access_token(self, value: Optional[str]): ... + + @property + def refresh_token(self) -> Optional[str]: ... + @refresh_token.setter + def refresh_token(self, value: Optional[str]): ... class QcsGrpcClientError(RuntimeError): diff --git a/crates/python/qcs_sdk/qpu/quilc.pyi b/crates/python/qcs_sdk/qpu/quilc.pyi index dd6e85fce..8d36e73b5 100644 --- a/crates/python/qcs_sdk/qpu/quilc.pyi +++ b/crates/python/qcs_sdk/qpu/quilc.pyi @@ -10,10 +10,13 @@ class QuilcError(RuntimeError): class CompilerOpts: """A set of options that determine the behavior of compiling programs with quilc.""" - timeout: Optional[int] - """The number of seconds to wait before timing out. If `None`, there is no timeout.""" + @property + def timeout(self) -> Optional[int]: + """The number of seconds to wait before timing out. If `None`, there is no timeout.""" + ... def __new__( + cls, timeout: Optional[int] = DEFAULT_COMPILER_TIMEOUT ) -> "CompilerOpts": ... @@ -23,5 +26,4 @@ class CompilerOpts: class TargetDevice: - isa: Any - specs: Dict[str, str] + ... diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs index b2ae881fe..6ef31f32d 100644 --- a/crates/python/src/executable.rs +++ b/crates/python/src/executable.rs @@ -1,13 +1,16 @@ use std::sync::Arc; -use pyo3::{ - exceptions::PyRuntimeError, pyclass, pymethods, types::PyDict, FromPyObject, Py, PyAny, - PyResult, Python, -}; use qcs::{Error, Executable, JobHandle, Qpu, Qvm, Service}; use rigetti_pyo3::{ - impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type, wrap_error, - PyWrapper, ToPython, ToPythonError, + impl_as_mut_for_wrapper, impl_repr, py_wrap_data_struct, py_wrap_error, py_wrap_simple_enum, + py_wrap_type, + pyo3::{ + exceptions::PyRuntimeError, + pymethods, + types::{PyDict, PyFloat, PyInt, PyString}, + Py, PyAny, PyResult, Python, + }, + wrap_error, PyWrapper, ToPython, ToPythonError, }; use tokio::sync::Mutex; @@ -31,13 +34,29 @@ py_wrap_type! { impl_as_mut_for_wrapper!(PyExecutable); -#[pyclass] -#[pyo3(name = "ExeParameter")] -#[derive(FromPyObject)] -pub struct PyParameter { - name: String, - index: usize, - value: f64, +#[derive(Clone, Debug)] +pub struct ExeParameter { + pub name: String, + pub index: usize, + pub value: f64, +} + +py_wrap_data_struct! { + PyParameter(ExeParameter) as "ExeParameter" { + name: String => Py, + index: usize => Py, + value: f64 => Py + } +} + +impl_repr!(PyParameter); + +#[pymethods] +impl PyParameter { + #[new] + pub fn new(name: String, index: usize, value: f64) -> Self { + Self(ExeParameter { name, index, value }) + } } #[pymethods] @@ -66,7 +85,8 @@ impl PyExecutable { } for param in parameters { - exe.with_parameter(param.name, param.index, param.value); + let ExeParameter { name, index, value } = param.into(); + exe.with_parameter(name, index, value); } if let Some(shots) = shots { diff --git a/crates/python/src/lib.rs b/crates/python/src/lib.rs index 76deb2e31..6debef875 100644 --- a/crates/python/src/lib.rs +++ b/crates/python/src/lib.rs @@ -16,6 +16,7 @@ create_init_submodule! { execution_data::PyQvm, execution_data::PyReadoutMap, executable::PyExecutable, + executable::PyParameter, executable::PyJobHandle, executable::PyService, register_data::PyRegisterData, diff --git a/crates/python/tests/test_api.py b/crates/python/tests/test_api.py index ed0b53138..37af8cd8f 100644 --- a/crates/python/tests/test_api.py +++ b/crates/python/tests/test_api.py @@ -45,6 +45,14 @@ def test_build_patch_values(): assert patch_values == expected +def test_exe_parameters(): + """Should be able to construct and pass exe parameters""" + exe_parameter = qcs_sdk.ExeParameter("a", 0, 0.25) + assert str(exe_parameter) == 'ExeParameter { name: "a", index: 0, value: 0.25 }' + + qcs_sdk.Executable("quil", parameters=[exe_parameter]) + + @pytest.mark.asyncio async def test_get_quilc_version(): version = await qcs_sdk.get_quilc_version() From a146bb6bd6f92929a8982b75018ff90d96a0763b Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 10:52:06 -0700 Subject: [PATCH 37/43] chore: make default timeout a static value --- crates/lib/src/api.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index ece4cbad1..936cdd0eb 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -26,6 +26,10 @@ use crate::qpu::{ IsaError, }; +/// TODO: make configurable at the client level. +/// https://github.com/rigetti/qcs-sdk-rust/issues/239 +static DEFAULT_HTTP_API_TIMEOUT: Duration = Duration::from_secs(10); + /// Uses quilc to convert a Quil program to native Quil pub fn compile( quil: &str, @@ -334,7 +338,7 @@ pub async fn list_quantum_processors( client: &Qcs, timeout: Option, ) -> Result, ListQuantumProcessorsError> { - let timeout = timeout.unwrap_or_else(|| Duration::from_secs(10)); + let timeout = timeout.unwrap_or(DEFAULT_HTTP_API_TIMEOUT); tokio::time::timeout(timeout, async move { let mut quantum_processors = vec![]; From 09cbd5e945307aeac5b96404be63ca76d7cf5a85 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 11:26:32 -0700 Subject: [PATCH 38/43] chore: use fixed version of rigetti-pyo3 --- Cargo.lock | 4 ++-- crates/python/Cargo.toml | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ce4b3772..35e442965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1551,9 +1551,9 @@ dependencies = [ [[package]] name = "rigetti-pyo3" -version = "0.1.0-rc.0" +version = "0.1.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298f878d681dbe7bea9652f48816534c16f165c259b2b19d1dc5a4f00f929ad6" +checksum = "17d3d8822e4d3ab36de8cff3b8d9990a9d2ccc9c9da3536de9347e29353b2455" dependencies = [ "num-complex", "num-traits", diff --git a/crates/python/Cargo.toml b/crates/python/Cargo.toml index 2d19fb3a1..b3a9a5221 100644 --- a/crates/python/Cargo.toml +++ b/crates/python/Cargo.toml @@ -24,10 +24,7 @@ pyo3-asyncio = { version = "0.17", features = ["tokio-runtime"] } quil-rs = "0.15" tokio = "1.21" qcs-api = "0.2.1" -rigetti-pyo3 = { version = "0.1.0-rc.0", features = [ - "extension-module", - "complex", -] } +rigetti-pyo3 = { version = "0.1.0-rc.1", features = ["extension-module", "complex"] } serde_json = "1.0.86" [build-dependencies] From 8300983b294e706a0403cc5521670da11506c49b Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 11:55:01 -0700 Subject: [PATCH 39/43] fix: mr feedback --- crates/python/qcs_sdk/_execution_data.pyi | 22 ++++++++++++++++++++++ crates/python/tests/test_api.py | 6 ++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/python/qcs_sdk/_execution_data.pyi b/crates/python/qcs_sdk/_execution_data.pyi index 5a10a2785..d64ae413f 100644 --- a/crates/python/qcs_sdk/_execution_data.pyi +++ b/crates/python/qcs_sdk/_execution_data.pyi @@ -9,23 +9,34 @@ from typing import List, Optional class _IntegerReadoutValues: @property def values(self) -> List[int]: ... + @values.setter + def values(self, value: List[int]): ... class _ComplexReadoutValues: @property def values(self) -> List[complex]: ... + @values.setter + def values(self, value: List[complex]): ... class _ReadoutValuesValues: @property def integer_values(self) -> _IntegerReadoutValues: ... + @integer_values.setter + def integer_values(self, value: _IntegerReadoutValues): ... + @property def complex_values(self) -> _ComplexReadoutValues: ... + @complex_values.setter + def complex_values(self, value: _ComplexReadoutValues): ... class _ReadoutValues: @property def values(self) -> Optional[_ReadoutValuesValues]: ... + @values.setter + def values(self, value: Optional[_ReadoutValuesValues]): ... class ReadoutMap: @@ -40,11 +51,22 @@ class ReadoutMap: class QVM: @property def registers(self) -> dict: ... + @registers.setter + def registers(self, value: dict): ... + @property def duration(self) -> Optional[datetime.timedelta]: ... + @duration.setter + def duration(self, value: Optional[datetime.timedelta]): ... + class QPU: @property def readout_data(self) -> ReadoutMap: ... + @readout_data.setter + def readout_data(self, value: ReadoutMap): ... + @property def duration(self) -> Optional[datetime.timedelta]: ... + @duration.setter + def duration(self, value: Optional[datetime.timedelta]): ... diff --git a/crates/python/tests/test_api.py b/crates/python/tests/test_api.py index 37af8cd8f..eb0993991 100644 --- a/crates/python/tests/test_api.py +++ b/crates/python/tests/test_api.py @@ -47,8 +47,10 @@ def test_build_patch_values(): def test_exe_parameters(): """Should be able to construct and pass exe parameters""" - exe_parameter = qcs_sdk.ExeParameter("a", 0, 0.25) - assert str(exe_parameter) == 'ExeParameter { name: "a", index: 0, value: 0.25 }' + exe_parameter = qcs_sdk.ExeParameter("a", 1, 2.5) + assert exe_parameter.name == "a" + assert exe_parameter.index == 1 + assert exe_parameter.value == 2.5 qcs_sdk.Executable("quil", parameters=[exe_parameter]) From a484284137a831201e4f7650bec8bf56b40e4f7e Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 12:09:37 -0700 Subject: [PATCH 40/43] fix: link formatting in comment --- crates/lib/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/src/api.rs b/crates/lib/src/api.rs index 936cdd0eb..7da21c3b6 100644 --- a/crates/lib/src/api.rs +++ b/crates/lib/src/api.rs @@ -27,7 +27,7 @@ use crate::qpu::{ }; /// TODO: make configurable at the client level. -/// https://github.com/rigetti/qcs-sdk-rust/issues/239 +/// static DEFAULT_HTTP_API_TIMEOUT: Duration = Duration::from_secs(10); /// Uses quilc to convert a Quil program to native Quil From db1c96e0594bd6a5d27c46cb6c16ee9f48f1ae64 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 13:09:07 -0700 Subject: [PATCH 41/43] fix: use pyclass instead of wrappers --- crates/python/src/executable.rs | 35 +++++++++++---------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/crates/python/src/executable.rs b/crates/python/src/executable.rs index 6ef31f32d..be5f43c3d 100644 --- a/crates/python/src/executable.rs +++ b/crates/python/src/executable.rs @@ -1,15 +1,10 @@ use std::sync::Arc; +use pyo3::{pyclass, FromPyObject}; use qcs::{Error, Executable, JobHandle, Qpu, Qvm, Service}; use rigetti_pyo3::{ - impl_as_mut_for_wrapper, impl_repr, py_wrap_data_struct, py_wrap_error, py_wrap_simple_enum, - py_wrap_type, - pyo3::{ - exceptions::PyRuntimeError, - pymethods, - types::{PyDict, PyFloat, PyInt, PyString}, - Py, PyAny, PyResult, Python, - }, + impl_as_mut_for_wrapper, py_wrap_error, py_wrap_simple_enum, py_wrap_type, + pyo3::{exceptions::PyRuntimeError, pymethods, types::PyDict, Py, PyAny, PyResult, Python}, wrap_error, PyWrapper, ToPython, ToPythonError, }; use tokio::sync::Mutex; @@ -34,28 +29,23 @@ py_wrap_type! { impl_as_mut_for_wrapper!(PyExecutable); -#[derive(Clone, Debug)] -pub struct ExeParameter { +#[pyclass] +#[pyo3(name = "ExeParameter")] +#[derive(FromPyObject)] +pub struct PyParameter { + #[pyo3(get, set)] pub name: String, + #[pyo3(get, set)] pub index: usize, + #[pyo3(get, set)] pub value: f64, } -py_wrap_data_struct! { - PyParameter(ExeParameter) as "ExeParameter" { - name: String => Py, - index: usize => Py, - value: f64 => Py - } -} - -impl_repr!(PyParameter); - #[pymethods] impl PyParameter { #[new] pub fn new(name: String, index: usize, value: f64) -> Self { - Self(ExeParameter { name, index, value }) + Self { name, index, value } } } @@ -85,8 +75,7 @@ impl PyExecutable { } for param in parameters { - let ExeParameter { name, index, value } = param.into(); - exe.with_parameter(name, index, value); + exe.with_parameter(param.name, param.index, param.value); } if let Some(shots) = shots { From b93dcd4294fb027c233bc19bae89662f34d24e36 Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 13:15:10 -0700 Subject: [PATCH 42/43] fix: remove needless lifetime, per ci --- crates/lib/src/qpu/quilc/isa/edge.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/lib/src/qpu/quilc/isa/edge.rs b/crates/lib/src/qpu/quilc/isa/edge.rs index 98ff1a243..57eff9493 100644 --- a/crates/lib/src/qpu/quilc/isa/edge.rs +++ b/crates/lib/src/qpu/quilc/isa/edge.rs @@ -35,9 +35,9 @@ impl Edge { } } - pub(crate) fn add_operation<'op_name>( + pub(crate) fn add_operation( &mut self, - op_name: &'op_name str, + op_name: &str, characteristics: &[Characteristic], ) -> Result<(), Error> { let operator = match GATE_PARAMS.get_key_value(op_name) { From c095c5982d53f9b9680050f69f83c483c82d6b9f Mon Sep 17 00:00:00 2001 From: Jake Selig Date: Fri, 27 Jan 2023 15:49:42 -0700 Subject: [PATCH 43/43] fix: mr suggestions --- crates/python/qcs_sdk/api.pyi | 18 ++++++++++++++++++ crates/python/src/qpu/client.rs | 25 +++++++++++-------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/crates/python/qcs_sdk/api.pyi b/crates/python/qcs_sdk/api.pyi index a6769aa54..802806990 100644 --- a/crates/python/qcs_sdk/api.pyi +++ b/crates/python/qcs_sdk/api.pyi @@ -46,6 +46,8 @@ class RewriteArithmeticResults: The resulting program where gate parameter arithmetic has been replaced with memory references. Before execution, the program memory should be updated using the `recalculation_table`. """ ... + @program.setter + def program(self, value: str): ... @property def recalculation_table(self) -> List[str]: @@ -53,6 +55,8 @@ class RewriteArithmeticResults: The recalculation table stores an ordered list of arithmetic expressions, which are to be used when updating the program memory before execution. """ ... + @recalculation_table.setter + def recalculation_table(self, value: List[str]): ... class TranslationResult: @@ -66,6 +70,8 @@ class TranslationResult: The compiled program binary. """ ... + @program.setter + def program(self, value: str): ... @property def ro_sources(self) -> Optional[dict]: @@ -73,6 +79,8 @@ class TranslationResult: A mapping from the program's memory references to the key used to index the results map. """ ... + @ro_sources.setter + def ro_sources(self, value: Optional[dict]): ... class ExecutionResult: @@ -82,16 +90,22 @@ class ExecutionResult: def shape(self) -> List[int]: """The shape of the result data.""" ... + @shape.setter + def shape(self, value: List[int]): ... @property def data(self) -> List[Number | List[float]]: """The result data. Complex numbers are represented as [real, imaginary].""" ... + @data.setter + def data(self, value: List[Number | List[float]]): ... @property def dtype(self) -> str: """The type of the result data (as a `numpy` `dtype`).""" ... + @dtype.setter + def dtype(self, value: str): ... class ExecutionResults: @@ -105,11 +119,15 @@ class ExecutionResults: See `TranslationResult.ro_sources` which provides the mapping from the filter node name to the name of the memory declaration in the source program. """ ... + @buffers.setter + def buffers(self, value: Dict[str, ExecutionResult]): ... @property def execution_duration_microseconds(self) -> Optional[int]: """The time spent executing the program.""" ... + @execution_duration_microseconds.setter + def execution_duration_microseconds(self, value: Optional[int]): ... class Register: diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs index 48250baf1..87b3a2f42 100644 --- a/crates/python/src/qpu/client.rs +++ b/crates/python/src/qpu/client.rs @@ -6,8 +6,8 @@ use qcs_api_client_common::{ use rigetti_pyo3::{ create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_type, pyo3::{ - conversion::IntoPy, exceptions::PyRuntimeError, pyclass::CompareOp, pymethods, - types::PyString, Py, PyAny, PyObject, PyResult, Python, + conversion::IntoPy, exceptions::PyRuntimeError, pyclass, pyclass::CompareOp, pymethods, + types::PyString, FromPyObject, Py, PyAny, PyObject, PyResult, Python, }, wrap_error, ToPythonError, }; @@ -62,26 +62,23 @@ py_wrap_error!( ); /// The fields on qcs_api_client_common::client::AuthServer are not public. -#[derive(Clone)] -pub struct QcsClientAuthServer { +#[pyclass] +#[pyo3(name = "QcsClientAuthServer")] +#[derive(FromPyObject)] +pub struct PyQcsClientAuthServer { + #[pyo3(get, set)] pub client_id: Option, + #[pyo3(get, set)] pub issuer: Option, } -py_wrap_data_struct! { - PyQcsClientAuthServer(QcsClientAuthServer) as "QcsClientAuthServer" { - client_id: Option => Option>, - issuer: Option => Option> - } -} - impl From for AuthServer { fn from(value: PyQcsClientAuthServer) -> Self { let mut auth_server = AuthServer::default(); - if let Some(client_id) = value.0.client_id { + if let Some(client_id) = value.client_id { auth_server = auth_server.set_client_id(client_id); } - if let Some(issuer) = value.0.issuer { + if let Some(issuer) = value.issuer { auth_server = auth_server.set_issuer(issuer); } auth_server @@ -93,7 +90,7 @@ impl PyQcsClientAuthServer { #[new] #[args(client_id = "None", issuer = "None")] pub fn new(client_id: Option, issuer: Option) -> Self { - Self(QcsClientAuthServer { client_id, issuer }) + Self { client_id, issuer } } }