from getpass import getpass
from typing import TYPE_CHECKING
from termcolor import colored
from typeguard import typechecked
if TYPE_CHECKING:
from qiskit_ibm_provider import IBMProvider
from qiskit.providers.backend import BackendV1
from qiskit_ibm_runtime import QiskitRuntimeService
from mpqp.execution.connection.env_manager import get_env_variable, save_env_variable
from mpqp.execution.devices import IBMDevice
from mpqp.tools.errors import IBMRemoteExecutionError
Ibm_Provider = None
Runtime_Service = None
[docs]@typechecked
def config_ibm_account(token: str):
"""Configure and save locally IBM Quantum account's information.
Args:
token: IBM Quantum API token.
Raises:
IBMRemoteExecutionError: If the account could not be saved.
"""
from qiskit_ibm_provider import IBMProvider
try:
IBMProvider.save_account(token=token, overwrite=True)
save_env_variable("IBM_CONFIGURED", "True")
save_env_variable("IBM_TOKEN", token)
except Exception as err:
# if an error occurred, we put False in the mpqp config file
save_env_variable("IBM_CONFIGURED", "False")
raise IBMRemoteExecutionError(
"Error when saving the account.\nTrace: " + str(err)
)
[docs]def setup_ibm_account():
"""Setups and updates the IBM Q account using the existing configuration and by asking for the token ."""
was_configured = get_env_variable("IBM_CONFIGURED") == "True"
if was_configured:
decision = input(
"An IBMQ account is already configured. Do you want to update it? [y/N]"
)
if decision.lower().strip() != "y":
return "Canceled.", []
token = getpass("Enter your IBM token (hidden): ")
if token == "":
print(colored("Empty credentials", "red"))
getpass("Press 'Enter' to continue")
return "", []
old_token = get_env_variable("IBM_TOKEN")
config_ibm_account(token)
if test_connection():
return "IBM Q account correctly configured", []
else:
if was_configured:
config_ibm_account(old_token)
else:
save_env_variable("IBM_CONFIGURED", "False")
getpass("Press 'Enter' to continue")
return "", []
[docs]def test_connection() -> bool:
"""Tests if the connection to the provider works.
Returns:
``False`` if login failed.
"""
from qiskit_ibm_provider import IBMProvider
from qiskit_ibm_provider.api.exceptions import RequestsApiError
try:
IBMProvider()
except RequestsApiError as err:
if "Login failed" in str(err):
print(colored("Wrong credentials", "red"))
return False
else:
raise err
return True
[docs]def get_IBMProvider() -> "IBMProvider":
"""Returns the IBMProvider needed to get one or several backends for
execution.
Raises:
IBMRemoteExecutionError: In the account was not properly configured
previously.
Example:
>>> instance = get_IBMProvider()
>>> instance.backends()
[<IBMBackend('ibmq_qasm_simulator')>,
<IBMBackend('simulator_extended_stabilizer')>,
<IBMBackend('simulator_mps')>,
<IBMBackend('simulator_stabilizer')>,
<IBMBackend('simulator_statevector')>,
<IBMBackend('ibm_brisbane')>,
<IBMBackend('ibm_kyoto')>,
<IBMBackend('ibm_osaka')>]
"""
from qiskit_ibm_provider import IBMProvider
from qiskit_ibm_provider.accounts import AccountNotFoundError
from qiskit_ibm_provider.api.exceptions import RequestsApiError
global Ibm_Provider
if Ibm_Provider is None:
if get_env_variable("IBM_CONFIGURED") == "False":
raise IBMRemoteExecutionError(
"Error when instantiating IBM Provider. No IBM Q account configured."
)
try:
Ibm_Provider = IBMProvider()
except RequestsApiError as err:
raise IBMRemoteExecutionError(
"Error when instantiating IBM Provider (probably wrong token "
"saved in the account).\nTrace: " + str(err)
)
except AccountNotFoundError as err:
raise IBMRemoteExecutionError(
"Error when instantiating IBM Provider. No IBM Q account "
"configured.\nTrace: " + str(err)
)
return Ibm_Provider
[docs]def get_QiskitRuntimeService() -> "QiskitRuntimeService":
"""Returns the QiskitRuntimeService needed for remote connection and
execution.
Raises:
IBMRemoteExecutionError: When the ``qiskit`` runtime is not configured
or the configuration cannot be retrieved.
Example:
>>> service = get_QiskitRuntimeService()
>>> service.jobs()
[<RuntimeJob('cmdj3b4nktricigarn8g', 'estimator')>,
<RuntimeJob('cmdj3a74mi97k7j7ujv0', 'sampler')>,
<RuntimeJob('cmama29054sir2cq94og', 'estimator')>,
<RuntimeJob('cmama14pduldih1q4ktg', 'sampler')>,
<RuntimeJob('cm7vds4pduldih1k1mq0', 'sampler')>]
"""
from qiskit_ibm_runtime import QiskitRuntimeService
global Runtime_Service
if Runtime_Service is None:
if get_env_variable("IBM_CONFIGURED") == "False":
raise IBMRemoteExecutionError(
"Error when instantiating QiskitRuntimeService. No IBM account configured."
)
try:
Runtime_Service = QiskitRuntimeService()
except Exception as err:
raise IBMRemoteExecutionError(
"Error when instantiating QiskitRuntimeService (probably wrong token saved "
"in the account).\nTrace: " + str(err)
)
return Runtime_Service
[docs]def get_active_account_info() -> str:
"""Returns the information concerning the active IBMQ account.
Returns:
The description containing the account information.
Example:
>>> print(get_active_account_info())
Channel: ibm_quantum
Instance: ibm-q-startup/colibritd/default
Token: bf5e5*****
URL: https://auth.quantum-computing.ibm.com/api
Verify: True
"""
provider = get_IBMProvider()
account = provider.active_account()
assert account is not None
return f""" Channel: {account["channel"]}
Instance: {account["instance"]}
Token: {account["token"][:5]}*****
URL: {account["url"]}
Verify: {account["verify"]}"""
[docs]@typechecked
def get_backend(device: IBMDevice) -> "BackendV1":
"""Retrieves the IBM Q remote device corresponding to the device in
parameter.
Args:
device: The IBMDevice to get from IBMQ provider.
Returns:
The requested backend.
Raises:
ValueError: If the required backend is a local simulator.
IBMRemoteExecutionError: If the device was not found.
Example:
>>> brisbane = get_backend(IBMDevice.IBM_BRISBANE)
>>> brisbane.properties().gates[0].parameters
[Nduv(datetime.datetime(2024, 1, 9, 11, 3, 18, tzinfo=tzlocal()), gate_error, , 0.00045619997922344296),
Nduv(datetime.datetime(2024, 1, 9, 15, 41, 39, tzinfo=tzlocal()), gate_length, ns, 60)]
"""
# NOTE:
# Question : when a backend is present in several IBMQ instances, which instance does it use to submit jobs
# on this backend ? Typically if with colibritd instance i have more priority and by default it uses ibmq
# instance, then i lose something here.
# Answer : it takes the default instance attached to the account (the higher plan, usually).
if not device.is_remote():
raise ValueError("Expected a remote IBMQ device but got a local simulator.")
from qiskit.providers import QiskitBackendNotFoundError
provider = get_IBMProvider()
try:
backend = provider.get_backend(device.value)
except QiskitBackendNotFoundError as err:
raise IBMRemoteExecutionError(
f"Requested device {device} not found. Verify if your instances "
"allows to access this machine, or the device's name.\n"
f"Trace: {err}"
)
return backend
[docs]def get_all_job_ids() -> list[str]:
"""Retrieves all the job ids of this account from the several IBM remote
providers (IBMProvider, QiskitRuntimeService, ...).
Returns:
The list of job ids.
Example:
>>> get_all_job_ids()
['cm6pp7e879ps6bbo7m30', 'cm6ou0q70abqioeudkd0', 'cm6opgcpduldih1hq7j0', 'cm01vp4pduldih0uoi2g',
'cnvw8z3b08x0008y3e4g', 'cnvw7qyb08x0008y3e0g', 'cnvw7fdvn4c0008a6ztg', 'cnvw79dvn4c0008a6zt0',
'cnvw64rb08x0008y3dx0', 'cnvw5z7wsx00008wybcg', 'cmdj3b4nktricigarn8g', 'cmdj3a74mi97k7j7ujv0',
'cmama29054sir2cq94og', 'cmama14pduldih1q4ktg', 'cm80qmi70abqiof0o170', 'cm80qlkpduldih1k4png',
'cm80pb1054sir2ck9i3g', 'cm80pa6879ps6bbqg2pg', 'cm7vdugiidfp3m8rg02g', 'cm7vds4pduldih1k1mq0']
"""
all_job_ids = []
if get_env_variable("IBM_CONFIGURED") != "True":
return all_job_ids
ibm_provider = get_IBMProvider()
service = get_QiskitRuntimeService()
if ibm_provider:
all_job_ids.extend([job.job_id() for job in ibm_provider.jobs()])
if service:
all_job_ids.extend([job.job_id() for job in service.jobs()])
return all_job_ids