Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ add_executable(test_lucj_fe4s4 samples/test_lucj_fe4s4.c)
target_include_directories(test_lucj_fe4s4 PRIVATE ${PROJECT_INCLUDEDIR})
target_link_libraries(test_lucj_fe4s4 PRIVATE qiskit qiskit_ibm_runtime)

add_executable(test_error_handling_run samples/test_error_handling_run.c)
target_include_directories(test_error_handling_run PRIVATE ${PROJECT_INCLUDEDIR})
target_link_libraries(test_error_handling_run PRIVATE qiskit qiskit_ibm_runtime)

# ---- Tests -------------------------------------------------------------------
enable_testing()

Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,45 @@ To run all tests, you can run the following command from within the build tree,
ctest
```

#### Running Sampler Examples

Before running the sampler examples, you must first set your account credentials.
* `token`: Your IBM Quantum account token
* `instance`: Your IBM Quantum account instance
* `channel`: Your IBM Quantum account channel (optional)

##### Configuration Methods (in priority order)

1. **Environment Variables** (recommended)
```bash
export QISKIT_IBM_TOKEN="your_token"
export QISKIT_IBM_INSTANCE="your_instance"
export QISKIT_IBM_CHANNEL="ibm_quantum" # optional
Comment thread
CedrickMwamba marked this conversation as resolved.
```

2. **Configuration File for local development**

Configuration File to be Created at `$HOME/.qiskit/qiskit-ibm.json` with IBM Quantum account credentials and the desired channel, at your home root directory.

Example of qiskit-ibm.json file:

```json
{
"default-ibm-quantum-platform": {
"token": "YOUR_IBM_QUANTUM_TOKEN",
"instance": "YOUR_INSTANCE_CRN",
"channel": "ibm_quantum"
}
}
```

More details how to set your account can be found [here](https://github.com/Qiskit/qiskit-ibm-runtime?tab=readme-ov-file#save-your-account-on-disk)

##### Running the Examples

After configuring your account, execute the sampler examples as `./test_ghz_sampler_run`


#### License

This software is licensed under the Apache 2.0 license.
Expand Down
25 changes: 24 additions & 1 deletion crates/client/src/c_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use std::ffi::{c_char, CStr, CString};
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::cell::RefCell;

use crate::service::{
get_account_from_config, get_backend, get_backends, get_estimator_job_results, get_job_details,
Expand All @@ -29,20 +30,42 @@ use crate::service::{
Service,
};

thread_local! {
static LAST_ERROR: RefCell<Option<String>> = RefCell::new(None);
}

pub(crate) fn set_last_error(message: String) {
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(message);
});
}
use crate::counts::Counts;

macro_rules! check_result {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(e) => {
log_err(&format!("{:?}", &e));
let error_msg = format!("{}", &e);
log_err(&error_msg);
crate::c_api::set_last_error(error_msg);
return e.code();
}
}
};
}

#[no_mangle]
pub unsafe extern "C" fn qkrt_get_last_error() -> *mut c_char {
LAST_ERROR.with(|e| {
e.borrow()
.as_ref()
.and_then(|msg| CString::new(msg.as_str()).ok())
.map(|cstr| cstr.into_raw())
.unwrap_or(std::ptr::null_mut())
})
}

/// This function only generates ISA static circuit QPY with no parameters
///
/// # Safety
Expand Down
13 changes: 13 additions & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ pub enum ExitCode {
IAMAPINotFound = 304,
/// The IBM IAM returned a 409.
IAMAPIConflict = 305,

/// Configuration error - missing credentials.
ConfigMissingCredentials = 400,
/// Configuration error - home directory not found.
ConfigHomeDirectoryNotFound = 401,
/// Configuration error - config file not found.
ConfigFileNotFound = 403,
/// Configuration error - specified account not found.
ConfigAccountNotFound = 404,
/// Configuration error - invalid JSON in config file.
ConfigInvalidJson = 405,
/// Configuration error - no default account configured.
ConfigNoDefaultAccount = 406,
}

fn log_err(e: &impl AsRef<str>) {
Expand Down
181 changes: 172 additions & 9 deletions crates/client/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,29 +231,191 @@ impl<T: Debug> From<ibmcloud_iam_api::apis::Error<T>> for ServiceError {
}
}

fn get_account_config(filename: Option<&str>, name: Option<&str>) -> AccountEntry {

fn get_account_config(filename: Option<&str>, name: Option<&str>) -> Result<AccountEntry, ConfigError> {
if let Ok(token) = std::env::var("QISKIT_IBM_TOKEN") {

let instance = std::env::var("QISKIT_IBM_INSTANCE")
.map_err(|_| ConfigError::MissingCredentials)?;

return Ok(AccountEntry {
token,
instance: Some(instance),
channel: std::env::var("QISKIT_IBM_CHANNEL").unwrap_or_else(|_| "ibm_quantum".to_string()),
url: std::env::var("QISKIT_IBM_URL").unwrap_or_else(|_| "https://auth.quantum-computing.ibm.com/api".to_string()),
verify: true,
private_endpoint: false,
proxies: None,
});
}

let filename = match filename {
Some(path) => path.to_string(),
None => {
let home = std::env::var("HOME").unwrap();
let home = std::env::var("HOME").map_err(|_| ConfigError::HomeDirectoryNotFound)?;
format!("{}/.qiskit/qiskit-ibm.json", home)
}
};

let file_path = Path::new(&filename);

let file = File::open(file_path).unwrap();
let file = File::open(file_path).map_err(|e| ConfigError::FileNotFound {
path: filename.clone(),
error: e.to_string(),
})?;

let reader = BufReader::new(file);
let accounts: HashMap<String, AccountEntry> = serde_json::from_reader(reader).unwrap();
let accounts: HashMap<String, AccountEntry> = serde_json::from_reader(reader)
.map_err(|e| ConfigError::InvalidJson {
path: filename.clone(),
error: e.to_string(),
})?;

match name {
Some(name) => accounts[name].clone(),
None => accounts
Some(name) => { accounts.get(name)
.cloned()
.ok_or_else(|| ConfigError::AccountNotFound {
name: name.to_string(),
available: accounts.keys().cloned().collect(),
})
}
None => { accounts
.get("default")
.or_else(|| accounts.get("default-ibm-quantum-platform"))
.unwrap_or_else(|| &accounts["default-ibm-cloud"])
.clone(),
.or_else(|| accounts.get("default-ibm-cloud"))
.cloned()
.ok_or_else(|| ConfigError::NoDefaultAccount {
available: accounts.keys().cloned().collect(),
})
}
}
}


#[derive(Debug)]
pub enum ConfigError {
MissingCredentials,
HomeDirectoryNotFound,
FileNotFound { path: String, error: String },
InvalidJson { path: String, error: String },
AccountNotFound { name: String, available: Vec<String> },
NoDefaultAccount { available: Vec<String> },
}


impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ConfigError::HomeDirectoryNotFound => {
write!(f, "[CONFIG ERROR] Could not determine home directory.\n\
\nTroubleshooting:\n\
- Ensure HOME environment variable is set\n\
- Or use QISKIT_IBM_TOKEN environment variable instead")
}
ConfigError::FileNotFound { path, error } => {
write!(f, "[CONFIG ERROR] Configuration file not found\n\
\nFile: {}\n\
Error: {}\n\
\nSolutions:\n\
1. Set environment variables:\n\
export QISKIT_IBM_TOKEN=\"your_token\"\n\
export QISKIT_IBM_INSTANCE=\"your_instance\"\n\
2. Create config file at: ~/.qiskit/qiskit-ibm.json\n\
See: https://github.com/Qiskit/qiskit-ibm-runtime#configuration",
path, error)
}
ConfigError::InvalidJson { path, error } => {
write!(f, "[CONFIG ERROR] Invalid JSON in configuration file\n\
\nFile: {}\n\
Parse error: {}\n\
\nPlease check your JSON syntax at: {}",
path, error, path)
}
ConfigError::AccountNotFound { name, available } => {
write!(f, "[CONFIG ERROR] Account '{}' not found in configuration\n\
\nAvailable accounts: {:?}\n\
\nPlease use one of the available account names or add '{}' to your config file.",
name, available, name)
}
ConfigError::NoDefaultAccount { available } => {
write!(f, "[CONFIG ERROR] No default account found in configuration\n\
\nAvailable accounts: {:?}\n\
\nExpected one of:\n\
- 'default'\n\
- 'default-ibm-quantum-platform'\n\
- 'default-ibm-cloud'\n\
\nPlease rename one of your accounts to a default name.",
available)
}
ConfigError::MissingCredentials => {
write!(f, "[CONFIG ERROR] No credentials found\n\
\nPlease provide credentials using either:\n\
1. Environment variables:\n\
export QISKIT_IBM_TOKEN=\"your_token\"\n\
export QISKIT_IBM_INSTANCE=\"your_instance\"\n\
2. Configuration file at ~/.qiskit/qiskit-ibm.json")
}
}
}
}


impl From<ConfigError> for ServiceError {
fn from(value: ConfigError) -> Self {
match value {
ConfigError::HomeDirectoryNotFound => ServiceError {
code: ExitCode::ConfigHomeDirectoryNotFound,
message: "Could not determine home directory.\n\
\nTroubleshooting:\n\
- Ensure HOME environment variable is set\n\
- Or use QISKIT_IBM_TOKEN environment variable instead".to_string(),
},
ConfigError::FileNotFound { path, error } => ServiceError {
code: ExitCode::ConfigFileNotFound,
message: format!(
"Configuration file not found at '{}': {}\n\
\nPlease either:\n\
1. Set environment variables: QISKIT_IBM_TOKEN, QISKIT_IBM_INSTANCE\n\
2. Create config file at: ~/.qiskit/qiskit-ibm.json",
path, error
),
},
ConfigError::InvalidJson { path, error } => ServiceError {
code: ExitCode::ConfigInvalidJson,
message: format!(
"Invalid JSON in config file '{}':\n{}\n\
\nPlease check your JSON syntax.",
path, error
),
},
ConfigError::AccountNotFound { name, available } => ServiceError {
code: ExitCode::ConfigAccountNotFound,
message: format!(
"Account '{}' not found in configuration.\n\
\nAvailable accounts: {:?}\n\
\nPlease use one of the available account names.",
name, available
),
},
ConfigError::NoDefaultAccount { available } => ServiceError {
code: ExitCode::ConfigNoDefaultAccount,
message: format!(
"No default account found in configuration.\n\
\nAvailable accounts: {:?}\n\
\nExpected one of: 'default', 'default-ibm-quantum-platform', 'default-ibm-cloud'",
available
),
},
ConfigError::MissingCredentials => ServiceError {
code: ExitCode::ConfigMissingCredentials,
message: "No credentials found.\n\
\nPlease set QISKIT_IBM_TOKEN environment variable or create ~/.qiskit/qiskit-ibm.json".to_string(),
},
}
}
}


#[derive(Clone, Debug)]
pub struct Service {
account: Account,
Expand Down Expand Up @@ -302,7 +464,8 @@ pub async fn get_account_from_config(
filename: Option<&str>,
name: Option<&str>,
) -> Result<Account, ServiceError> {
let config = get_account_config(filename, name);
let config = get_account_config(filename, name)?;

let iam_config = Configuration {
base_path: "https://iam.cloud.ibm.com".to_owned(),
user_agent: Some("qiskit-ibm-runtime-rs/0.0.1".to_owned()),
Expand Down
8 changes: 8 additions & 0 deletions include/qiskit_ibm_runtime/qiskit_ibm_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,11 @@ extern void qkrt_expectation_values_free(ExpectationValues *evs);
* @param The string to free.
*/
extern void qkrt_str_free(char *string);

/**
* Get the last error message from the Rust library.
*
* @return Pointer to null-terminated error message string, or NULL if no error.
* The pointer is valid until the next error occurs or the thread exits.
*/
extern const char* qkrt_get_last_error(void);
Loading