# Copyright (C) 2024 qBraid## This file is part of the qBraid-SDK## The qBraid-SDK is free software released under the GNU General Public License v3# or later. You can redistribute and/or modify it under the terms of the GPL v3.# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.## THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3."""Module defining QbraidProvider class."""from__future__importannotationsimportwarningsfromtypingimportTYPE_CHECKING,Any,Callable,Optionalimportpyqasmfromqbraid_core.exceptionsimportAuthErrorfromqbraid_core.services.quantumimportQuantumClient,QuantumServiceRequestError,process_job_datafromqbraid._cachingimportcached_methodfromqbraid.programsimportQPROGRAM_REGISTRY,ExperimentType,ProgramSpec,load_programfromqbraid.programs.typerimportQasm2StringType,Qasm3StringTypefromqbraid.runtime._displayimportdisplay_jobs_from_datafromqbraid.runtime.exceptionsimportResourceNotFoundErrorfromqbraid.runtime.ionq.providerimportIonQProviderfromqbraid.runtime.noiseimportNoiseModelSetfromqbraid.runtime.profileimportTargetProfilefromqbraid.runtime.providerimportQuantumProviderfromqbraid.runtime.schemas.deviceimportDeviceDatafromqbraid.transpilerimporttranspilefrom.deviceimportQbraidDeviceifTYPE_CHECKING:importpyqirdef_serialize_program(program)->dict[str,str]:qbraid_program=load_program(program)returnqbraid_program.serialize()def_serialize_pyqir(program:pyqir.Module)->dict[str,bytes]:return{"bitcode":program.bitcode}defvalidate_qasm_no_measurements(program:Qasm2StringType|Qasm3StringType,device_id:str)->None:"""Raises a ValueError if the given OpenQASM program contains measurement gates."""qasm_module=pyqasm.loads(program)ifqasm_module.has_measurements():raiseValueError(f"OpenQASM programs submitted to the {device_id} cannot contain measurement gates.")defvalidate_qasm_to_ionq(program:Qasm2StringType|Qasm3StringType,device_id:str)->None:"""Raises a ValueError if the given OpenQASM program is not compatible with IonQ JSON format."""try:transpile(program,"ionq",max_path_depth=1)exceptExceptionaserr:# pylint: disable=broad-exception-caughtraiseValueError(f"OpenQASM programs submitted to the {device_id} ""must be compatible with IonQ JSON format.")fromerrdefget_program_spec_lambdas(program_type_alias:str,device_id:str)->dict[str,Optional[Callable[[Any],None]]]:"""Returns conversion and validation functions for the given program type and device."""ifprogram_type_alias=="pyqir":return{"serialize":_serialize_pyqir,"validate":None}ifprogram_type_aliasin{"qasm2","qasm3"}:device_prefix=device_id.split("_")[0]# pylint: disable=unnecessary-lambda-assignmentvalidations={"quera":lambdap:validate_qasm_no_measurements(p,device_id),"ionq":lambdap:validate_qasm_to_ionq(p,device_id),}# pylint: enable=unnecessary-lambda-assignmentvalidate=validations.get(device_prefix)else:validate=Nonereturn{"serialize":_serialize_program,"validate":validate}
[docs]classQbraidProvider(QuantumProvider):""" This class is responsible for managing the interactions and authentications with qBraid Quantum services. Attributes: client (qbraid_core.services.quantum.QuantumClient): qBraid QuantumClient object """
[docs]def__init__(self,api_key:Optional[str]=None,client:Optional[QuantumClient]=None):""" Initializes the QbraidProvider object """ifapi_keyandclient:raiseValueError("Provide either api_key or client, not both.")self._api_key=api_keyself._client=client
defsave_config(self,**kwargs):"""Save the current configuration."""self.client.session.save_config(**kwargs)@propertydefclient(self)->QuantumClient:"""Return the QuantumClient object."""ifself._clientisNone:try:self._client=QuantumClient(api_key=self._api_key)exceptAuthErroraserr:raiseResourceNotFoundError("Failed to authenticate with the Quantum service.")fromerrreturnself._client@staticmethoddef_get_program_spec(run_package:Optional[str],device_id:str)->Optional[ProgramSpec]:"""Return the program spec for the given run package and device."""ifnotrun_package:returnNoneprogram_type=QPROGRAM_REGISTRY.get(run_package)ifprogram_typeisNone:warnings.warn(f"The default runtime configuration for device '{device_id}' includes "f"transpilation to program type '{run_package}', which is not registered.",RuntimeWarning,)lambdas=get_program_spec_lambdas(run_package,device_id)returnProgramSpec(program_type,alias=run_package,**lambdas)ifprogram_typeelseNone@staticmethoddef_get_program_specs(run_input_types:list[str],device_id:str)->list[ProgramSpec]:"""Return a list of program specs for the given run input types and device."""return[specforrpinrun_input_typesif(spec:=QbraidProvider._get_program_spec(rp,device_id))isnotNone]@staticmethoddef_get_basis_gates(device_data:dict[str,Any])->Optional[list[str]]:"""Return the basis gates for the qBraid device."""provider=device_data["provider"]ifprovider=="IonQ":ionq_id=device_data["objArg"]returnIonQProvider._get_basis_gates(ionq_id)returnNonedef_build_runtime_profile(self,device_data:dict[str,Any])->TargetProfile:"""Builds a runtime profile from qBraid device data."""model=DeviceData(**device_data)simulator=str(model.device_type).upper()=="SIMULATOR"specs=self._get_program_specs(model.run_input_types,model.device_id)program_spec=specs[0]iflen(specs)==1elsespecsorNonenoise_models=(NoiseModelSet.from_iterable(model.noise_models)ifmodel.noise_modelselseNone)device_exp_type="gate_model"ifmodel.paradigm=="gate-based"elsemodel.paradigm.lower()experiment_type=ExperimentType(device_exp_type)basis_gates=self._get_basis_gates(device_data)returnTargetProfile(device_id=model.device_id,simulator=simulator,experiment_type=experiment_type,num_qubits=model.num_qubits,program_spec=program_spec,provider_name=model.provider,noise_models=noise_models,name=model.name,pricing=model.pricing,basis_gates=basis_gates,)@cached_method(ttl=120)defget_devices(self,**kwargs)->list[QbraidDevice]:"""Return a list of devices matching the specified filtering."""query=kwargsorNonetry:devices=self.client.search_devices(query)except(ValueError,QuantumServiceRequestError)aserr:raiseResourceNotFoundError("No devices found matching given criteria.")fromerrfiltered_devices=[devicefordeviceindevicesifdevice["vendor"]=="qBraid"or(device["vendor"]=="AWS"anddevice["provider"]in{"AWS","QuEra","OQC","IQM","Rigetti"})]ifnotfiltered_devices:raiseResourceNotFoundError("No devices found matching given criteria.")profiles=[self._build_runtime_profile(device_data)fordevice_datainfiltered_devices]return[QbraidDevice(profile,client=self.client)forprofileinprofiles]@cached_method(ttl=120)defget_device(self,device_id:str)->QbraidDevice:"""Return quantum device corresponding to the specified qBraid device ID. Returns: QuantumDevice: the quantum device corresponding to the given ID Raises: ResourceNotFoundError: if device cannot be loaded from quantum service data """try:device_data=self.client.get_device(qbraid_id=device_id)except(ValueError,QuantumServiceRequestError)aserr:raiseResourceNotFoundError(f"Device '{device_id}' not found.")fromerrprofile=self._build_runtime_profile(device_data)returnQbraidDevice(profile,client=self.client)# pylint: disable-next=too-many-argumentsdefdisplay_jobs(self,device_id:Optional[str]=None,provider:Optional[str]=None,status:Optional[str]=None,tags:Optional[dict]=None,max_results:int=10,):"""Displays a list of quantum jobs submitted by user, tabulated by job ID, the date/time it was submitted, and status. You can specify filters to narrow the search by supplying a dictionary containing the desired criteria. Args: device_id (optional, str): The qBraid ID of the device used in the job. provider (optional, str): The name of the provider. tags (optional, dict): A list of tags associated with the job. status (optional, str): The status of the job. max_results (optional, int): Maximum number of results to display. Defaults to 10. """query:dict[str,Any]={}ifprovider:query["provider"]=provider.lower()ifdevice_id:query["qbraidDeviceId"]=device_idifstatus:query["status"]=statusiftags:query.update({f"tags.{key}":valueforkey,valueintags.items()})ifmax_results:query["resultsPerPage"]=max_resultsjobs=self.client.search_jobs(query)job_data,msg=process_job_data(jobs,query)returndisplay_jobs_from_data(job_data,msg)def__hash__(self):ifnothasattr(self,"_hash"):user_metadata=self.client._user_metadataorganization_role=f'{user_metadata["organization"]}-{user_metadata["role"]}'hash_value=hash((self.__class__.__name__,self.client.session.api_key,organization_role))object.__setattr__(self,"_hash",hash_value)returnself._hash# pylint: disable=no-member