15
15
import itertools
16
16
import logging
17
17
from ast import literal_eval
18
+ from collections import Counter
18
19
import json
19
20
from typing import (
20
21
cast ,
31
32
32
33
import qiskit # type: ignore
33
34
from qiskit import IBMQ
34
- from qiskit .qobj import QobjExperimentHeader # type: ignore
35
- from qiskit .providers .ibmq .exceptions import IBMQBackendApiError # type: ignore
36
- from qiskit .providers .ibmq .job import IBMQJob # type: ignore
37
- from qiskit .result import Result , models # type: ignore
35
+ from qiskit .primitives import SamplerResult # type: ignore
38
36
from qiskit .tools .monitor import job_monitor # type: ignore
39
- from qiskit .providers .ibmq .utils .utils import api_status_to_job_status # type: ignore
37
+ from qiskit .result .distributions import QuasiDistribution # type: ignore
38
+ from qiskit_ibm_runtime import ( # type: ignore
39
+ QiskitRuntimeService ,
40
+ Session ,
41
+ Options ,
42
+ Sampler ,
43
+ RuntimeJob ,
44
+ )
40
45
41
46
from pytket .circuit import Circuit , OpType # type: ignore
42
47
from pytket .backends import Backend , CircuitNotRunError , CircuitStatus , ResultHandle
71
76
Predicate ,
72
77
)
73
78
from pytket .extensions .qiskit .qiskit_convert import tk_to_qiskit , _tk_gate_set
74
- from pytket .extensions .qiskit .result_convert import (
75
- qiskit_experimentresult_to_backendresult ,
76
- )
77
79
from pytket .architecture import FullyConnected # type: ignore
78
80
from pytket .placement import NoiseAwarePlacement # type: ignore
79
81
from pytket .utils import prepare_circuit
82
+ from pytket .utils .outcomearray import OutcomeArray
80
83
from pytket .utils .results import KwargTypes
81
84
from .ibm_utils import _STATUS_MAP , _batch_circuits
82
85
from .config import QiskitConfig
86
89
IBMQBackend as _QiskIBMQBackend ,
87
90
AccountProvider ,
88
91
)
89
- from qiskit .providers .models import QasmBackendConfiguration # type: ignore
90
92
91
93
_DEBUG_HANDLE_PREFIX = "_MACHINE_DEBUG_"
92
94
93
95
94
- def _gen_debug_results (n_qubits : int , shots : int , index : int ) -> Result :
95
- raw_counts = {"0x0" : shots }
96
- raw_memory = ["0x0" ] * shots
97
- base_result_args = dict (
98
- backend_name = "test_backend" ,
99
- backend_version = "1.0.0" ,
100
- qobj_id = "id-123" ,
101
- job_id = "job-123" ,
102
- success = True ,
103
- )
104
- data = models .ExperimentResultData (counts = raw_counts , memory = raw_memory )
105
- exp_result_header = QobjExperimentHeader (
106
- creg_sizes = [["c" , n_qubits ]], memory_slots = n_qubits
107
- )
108
- exp_result = models .ExperimentResult (
109
- shots = shots ,
110
- success = True ,
111
- meas_level = 2 ,
112
- data = data ,
113
- header = exp_result_header ,
114
- memory = True ,
96
+ def _gen_debug_results (n_qubits : int , shots : int , index : int ) -> SamplerResult :
97
+ debug_dist = {n : 0.0 for n in range (pow (2 , n_qubits ))}
98
+ debug_dist [0 ] = 1.0
99
+ qd = QuasiDistribution (debug_dist )
100
+ return SamplerResult (
101
+ quasi_dists = [qd ] * (index + 1 ),
102
+ metadata = [{"header_metadata" : {}, "shots" : shots }] * (index + 1 ),
115
103
)
116
- results = [exp_result ] * (index + 1 )
117
- return Result (results = results , ** base_result_args )
118
104
119
105
120
106
class NoIBMQAccountError (Exception ):
@@ -128,7 +114,7 @@ def __init__(self) -> None:
128
114
129
115
130
116
class IBMQBackend (Backend ):
131
- _supports_shots = True
117
+ _supports_shots = False
132
118
_supports_counts = True
133
119
_supports_contextual_optimisation = True
134
120
_persistent_handles = True
@@ -141,6 +127,7 @@ def __init__(
141
127
project : Optional [str ] = None ,
142
128
monitor : bool = True ,
143
129
account_provider : Optional ["AccountProvider" ] = None ,
130
+ token : Optional [str ] = None ,
144
131
):
145
132
"""A backend for running circuits on remote IBMQ devices.
146
133
The provider arguments of `hub`, `group` and `project` can
@@ -166,6 +153,8 @@ def __init__(
166
153
Used to pass credentials in if not configured on local machine (as well as hub,
167
154
group and project). Defaults to None.
168
155
:type account_provider: Optional[AccountProvider]
156
+ :param token: Authentication token to use the `QiskitRuntimeService`.
157
+ :type token: Optional[str]
169
158
"""
170
159
super ().__init__ ()
171
160
self ._pytket_config = QiskitConfig .from_default_config_file ()
@@ -175,18 +164,21 @@ def __init__(
175
164
else account_provider
176
165
)
177
166
self ._backend : "_QiskIBMQBackend" = self ._provider .get_backend (backend_name )
178
- self . _config = self ._backend .configuration ()
179
- self ._max_per_job = getattr (self . _config , "max_experiments" , 1 )
167
+ config = self ._backend .configuration ()
168
+ self ._max_per_job = getattr (config , "max_experiments" , 1 )
180
169
181
170
gate_set = _tk_gate_set (self ._backend )
182
171
self ._backend_info = self ._get_backend_info (self ._backend )
183
172
173
+ self ._service = QiskitRuntimeService (channel = "ibm_quantum" , token = token )
174
+ self ._session = Session (service = self ._service , backend = backend_name )
175
+
184
176
self ._standard_gateset = gate_set >= {OpType .X , OpType .SX , OpType .Rz , OpType .CX }
185
177
186
178
self ._monitor = monitor
187
179
188
180
# cache of results keyed by job id and circuit index
189
- self ._ibm_res_cache : Dict [Tuple [str , int ], models . ExperimentResult ] = dict ()
181
+ self ._ibm_res_cache : Dict [Tuple [str , int ], Counter ] = dict ()
190
182
191
183
self ._MACHINE_DEBUG = False
192
184
@@ -370,7 +362,8 @@ def default_compilation_pass(self, optimisation_level: int = 2) -> BasePass:
370
362
371
363
@property
372
364
def _result_id_type (self ) -> _ResultIdTuple :
373
- return (str , int , str )
365
+ # IBMQ job ID, index, number of measurements per shot, post-processing circuit
366
+ return (str , int , int , str )
374
367
375
368
def rebase_pass (self ) -> BasePass :
376
369
return auto_rebase_pass (
@@ -423,40 +416,47 @@ def process_circuits(
423
416
if self ._MACHINE_DEBUG :
424
417
for i , ind in enumerate (indices_chunk ):
425
418
handle_list [ind ] = ResultHandle (
426
- _DEBUG_HANDLE_PREFIX
427
- + str ((batch_chunk [i ].n_qubits , n_shots , batch_id )),
419
+ _DEBUG_HANDLE_PREFIX + str ((n_shots , batch_id )),
428
420
i ,
421
+ batch_chunk [i ].n_qubits ,
429
422
ppcirc_strs [i ],
430
423
)
431
424
else :
432
- job = self ._backend .run (
433
- qcs , shots = n_shots , memory = self ._config .memory
434
- )
435
- jobid = job .job_id ()
425
+ options = Options ()
426
+ options .optimization_level = 0
427
+ options .resilience_level = 0
428
+ options .transpilation .skip_transpilation = True
429
+ options .execution .shots = n_shots
430
+ sampler = Sampler (session = self ._session , options = options )
431
+ job = sampler .run (circuits = qcs )
432
+ job_id = job .job_id
436
433
for i , ind in enumerate (indices_chunk ):
437
- handle_list [ind ] = ResultHandle (jobid , i , ppcirc_strs [i ])
434
+ handle_list [ind ] = ResultHandle (
435
+ job_id , i , qcs [i ].count_ops ()["measure" ], ppcirc_strs [i ]
436
+ )
438
437
batch_id += 1
439
438
for handle in handle_list :
440
439
assert handle is not None
441
440
self ._cache [handle ] = dict ()
442
441
return cast (List [ResultHandle ], handle_list )
443
442
444
- def _retrieve_job (self , jobid : str ) -> IBMQJob :
445
- return self ._backend . retrieve_job (jobid )
443
+ def _retrieve_job (self , jobid : str ) -> RuntimeJob :
444
+ return self ._service . job (jobid )
446
445
447
446
def cancel (self , handle : ResultHandle ) -> None :
448
447
if not self ._MACHINE_DEBUG :
449
448
jobid = cast (str , handle [0 ])
450
449
job = self ._retrieve_job (jobid )
451
- cancelled = job .cancel ()
452
- if not cancelled :
453
- warn (f"Unable to cancel job { jobid } " )
450
+ try :
451
+ job .cancel ()
452
+ except Exception as e :
453
+ warn (f"Unable to cancel job { jobid } : { e } " )
454
454
455
455
def circuit_status (self , handle : ResultHandle ) -> CircuitStatus :
456
456
self ._check_handle_type (handle )
457
457
jobid = cast (str , handle [0 ])
458
- apistatus = self ._provider . _api_client . job_status (jobid )[ "status" ]
459
- ibmstatus = api_status_to_job_status ( apistatus )
458
+ job = self ._service . job (jobid )
459
+ ibmstatus = job . status ( )
460
460
return CircuitStatus (_STATUS_MAP [ibmstatus ], ibmstatus .value )
461
461
462
462
def get_result (self , handle : ResultHandle , ** kwargs : KwargTypes ) -> BackendResult :
@@ -469,33 +469,43 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
469
469
cached_result = self ._cache [handle ]
470
470
if "result" in cached_result :
471
471
return cast (BackendResult , cached_result ["result" ])
472
- jobid , index , ppcirc_str = handle
472
+ jobid , index , n_meas , ppcirc_str = handle
473
473
ppcirc_rep = json .loads (ppcirc_str )
474
474
ppcirc = Circuit .from_dict (ppcirc_rep ) if ppcirc_rep is not None else None
475
475
cache_key = (jobid , index )
476
476
if cache_key not in self ._ibm_res_cache :
477
477
if self ._MACHINE_DEBUG or jobid .startswith (_DEBUG_HANDLE_PREFIX ):
478
478
shots : int
479
- n_qubits : int
480
- n_qubits , shots , _ = literal_eval (jobid [len (_DEBUG_HANDLE_PREFIX ) :])
481
- res = _gen_debug_results (n_qubits , shots , index )
479
+ shots , _ = literal_eval (jobid [len (_DEBUG_HANDLE_PREFIX ) :])
480
+ res = _gen_debug_results (n_meas , shots , index )
482
481
else :
483
482
try :
484
483
job = self ._retrieve_job (jobid )
485
- except IBMQBackendApiError :
484
+ except Exception as e :
485
+ warn (f"Unable to retrieve job { jobid } : { e } " )
486
486
raise CircuitNotRunError (handle )
487
487
488
488
if self ._monitor and job :
489
489
job_monitor (job )
490
- newkwargs = {
491
- key : kwargs [key ] for key in ("wait" , "timeout" ) if key in kwargs
492
- }
493
- res = job .result (** newkwargs )
494
-
495
- for circ_index , r in enumerate (res .results ):
496
- self ._ibm_res_cache [(jobid , circ_index )] = r
497
- result = qiskit_experimentresult_to_backendresult (
498
- self ._ibm_res_cache [cache_key ], ppcirc
499
- )
490
+
491
+ res = job .result (timeout = kwargs .get ("timeout" , None ))
492
+ for circ_index , (r , d ) in enumerate (zip (res .quasi_dists , res .metadata )):
493
+ self ._ibm_res_cache [(jobid , circ_index )] = Counter (
494
+ {n : int (0.5 + d ["shots" ] * p ) for n , p in r .items ()}
495
+ )
496
+
497
+ counts = self ._ibm_res_cache [cache_key ] # Counter[int]
498
+ # Convert to `OutcomeArray`:
499
+ tket_counts : Counter = Counter ()
500
+ for outcome_key , sample_count in counts .items ():
501
+ array = OutcomeArray .from_ints (
502
+ ints = [outcome_key ],
503
+ width = n_meas ,
504
+ big_endian = False ,
505
+ )
506
+ tket_counts [array ] = sample_count
507
+ # Convert to `BackendResult`:
508
+ result = BackendResult (counts = tket_counts , ppcirc = ppcirc )
509
+
500
510
self ._cache [handle ] = {"result" : result }
501
511
return result
0 commit comments