• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 13011048655

28 Jan 2025 01:03PM UTC coverage: 88.951% (+0.001%) from 88.95%
13011048655

Pull #13743

github

web-flow
Merge 35d9d34a2 into 8ab91faea
Pull Request #13743: Remove use of deprecated objects in `BasicSimulator`

60 of 62 new or added lines in 1 file covered. (96.77%)

292 existing lines in 17 files now uncovered.

79634 of 89526 relevant lines covered (88.95%)

351307.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.98
/qiskit/providers/basic_provider/basic_simulator.py
1
# This code is part of Qiskit.
2
#
3
# (C) Copyright IBM 2017, 2023.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""Contains a (slow) Python simulator.
14

15
It simulates a quantum circuit (an experiment) that has been compiled
16
to run on the simulator. It is exponential in the number of qubits.
17

18
The simulator is run using
19

20
.. plot::
21
   :include-source:
22
   :nofigs:
23

24
   BasicSimulator().run(run_input)
25

26
Where the input is a :class:`.QuantumCircuit` object and the output is a
27
:class:`.BasicProviderJob` object,
28
which can later be queried for the Result object. The result will contain a 'memory' data
29
field, which is a result of measurements for each shot.
30
"""
31

32
from __future__ import annotations
1✔
33

34
import math
1✔
35
import uuid
1✔
36
import time
1✔
37
import logging
1✔
38
import warnings
1✔
39

40
from collections import Counter
1✔
41
import numpy as np
1✔
42

43
from qiskit.circuit import QuantumCircuit
1✔
44
from qiskit.circuit.library import UnitaryGate
1✔
45
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping, GlobalPhaseGate
1✔
46
from qiskit.providers import Provider
1✔
47
from qiskit.providers.backend import BackendV2
1✔
48
from qiskit.providers.options import Options
1✔
49
from qiskit.result import Result
1✔
50
from qiskit.transpiler import Target
1✔
51

52
from .basic_provider_job import BasicProviderJob
1✔
53
from .basic_provider_tools import single_gate_matrix
1✔
54
from .basic_provider_tools import (
1✔
55
    SINGLE_QUBIT_GATES,
56
    TWO_QUBIT_GATES,
57
    TWO_QUBIT_GATES_WITH_PARAMETERS,
58
    THREE_QUBIT_GATES,
59
)
60
from .basic_provider_tools import einsum_vecmul_index
1✔
61
from .exceptions import BasicProviderError
1✔
62

63
logger = logging.getLogger(__name__)
1✔
64

65

66
class BasicSimulator(BackendV2):
1✔
67
    """Python implementation of a basic (non-efficient) quantum simulator."""
68

69
    # Formerly calculated as `int(log2(local_hardware_info()["memory"]*(1024**3)/16))`.
70
    # After the removal of `local_hardware_info()`, it's hardcoded to 24 qubits,
71
    # which matches the ~268 MB of required memory.
72
    MAX_QUBITS_MEMORY = 24
1✔
73

74
    def __init__(
1✔
75
        self,
76
        provider: Provider | None = None,
77
        target: Target | None = None,
78
        **fields,
79
    ) -> None:
80
        """
81
        Args:
82
            provider: An optional backwards reference to the
83
                :class:`~qiskit.providers.Provider` object that the backend
84
                is from.
85
            target: An optional target to configure the simulator.
86
            fields: kwargs for the values to use to override the default
87
                options.
88

89
        Raises:
90
            AttributeError: If a field is specified that's outside the backend's
91
                options.
92
        """
93

94
        super().__init__(
1✔
95
            provider=provider,
96
            name="basic_simulator",
97
            description="A Python simulator for basic quantum experiments",
98
            backend_version="0.1",
99
            **fields,
100
        )
101

102
        self._target = target
1✔
103
        # Internal simulator variables
104
        self._local_random = None
1✔
105
        self._classical_memory = 0
1✔
106
        self._statevector = 0
1✔
107
        self._number_of_cmembits = 0
1✔
108
        self._number_of_qubits = 0
1✔
109
        self._shots = self.options.get("shots")
1✔
110
        self._memory = self.options.get("memory")
1✔
111
        self._initial_statevector = self.options.get("initial_statevector")
1✔
112
        self._seed_simulator = self.options.get("seed_simulator")
1✔
113
        self._sample_measure = False
1✔
114

115
    @property
1✔
116
    def max_circuits(self) -> None:
1✔
117
        return None
1✔
118

119
    @property
1✔
120
    def target(self) -> Target:
1✔
121
        if not self._target:
1✔
122
            self._target = self._build_basic_target()
1✔
123
        return self._target
1✔
124

125
    def _build_basic_target(self) -> Target:
1✔
126
        """Helper method that returns a minimal target with a basis gate set but
127
        no coupling map, instruction properties or calibrations.
128

129
        Returns:
130
            The configured target.
131
        """
132
        # Set num_qubits to None to signal the transpiler not to
133
        # resize the circuit to fit a specific (potentially too large)
134
        # number of qubits. The number of qubits in the circuits given to the
135
        # `run` method will determine the size of the simulated statevector.
136
        target = Target(
1✔
137
            description="Basic Target",
138
            num_qubits=None,
139
        )
140
        basis_gates = [
1✔
141
            "ccx",
142
            "ccz",
143
            "ch",
144
            "cp",
145
            "crx",
146
            "cry",
147
            "crz",
148
            "cs",
149
            "csdg",
150
            "cswap",
151
            "csx",
152
            "cu",
153
            "cu1",
154
            "cu3",
155
            "cx",
156
            "cy",
157
            "cz",
158
            "dcx",
159
            "delay",
160
            "ecr",
161
            "global_phase",
162
            "h",
163
            "id",
164
            "iswap",
165
            "measure",
166
            "p",
167
            "r",
168
            "rccx",
169
            "reset",
170
            "rx",
171
            "rxx",
172
            "ry",
173
            "ryy",
174
            "rz",
175
            "rzx",
176
            "rzz",
177
            "s",
178
            "sdg",
179
            "swap",
180
            "sx",
181
            "sxdg",
182
            "t",
183
            "tdg",
184
            "u",
185
            "u1",
186
            "u2",
187
            "u3",
188
            "unitary",
189
            "x",
190
            "xx_minus_yy",
191
            "xx_plus_yy",
192
            "y",
193
            "z",
194
        ]
195
        inst_mapping = get_standard_gate_name_mapping()
1✔
196
        for name in basis_gates:
1✔
197
            if name in inst_mapping:
1✔
198
                instruction = inst_mapping[name]
1✔
199
                target.add_instruction(instruction, properties=None, name=name)
1✔
200
            elif name == "unitary":
1✔
201
                # This is a placeholder for a UnitaryGate instance,
202
                # to signal the transpiler not to decompose unitaries
203
                # in the circuit.
204
                target.add_instruction(UnitaryGate, name="unitary")
1✔
205
            else:
206
                raise BasicProviderError(
×
207
                    f"Gate is not a valid basis gate for this simulator: {name}"
208
                )
209
        return target
1✔
210

211
    @classmethod
1✔
212
    def _default_options(cls) -> Options:
1✔
213
        return Options(
1✔
214
            shots=1024,
215
            memory=False,
216
            initial_statevector=None,
217
            allow_sample_measuring=True,
218
            seed_simulator=None,
219
            parameter_binds=None,
220
        )
221

222
    def _add_unitary(self, gate: np.ndarray, qubits: list[int]) -> None:
1✔
223
        """Apply an N-qubit unitary matrix.
224

225
        Args:
226
            gate (matrix_like): an N-qubit unitary matrix
227
            qubits (list): the list of N-qubits.
228
        """
229
        # Get the number of qubits
230
        num_qubits = len(qubits)
1✔
231
        # Compute einsum index string for 1-qubit matrix multiplication
232
        indexes = einsum_vecmul_index(qubits, self._number_of_qubits)
1✔
233
        # Convert to complex rank-2N tensor
234
        gate_tensor = np.reshape(np.array(gate, dtype=complex), num_qubits * [2, 2])
1✔
235
        # Apply matrix multiplication
236
        self._statevector = np.einsum(
1✔
237
            indexes, gate_tensor, self._statevector, dtype=complex, casting="no"
238
        )
239

240
    def _get_measure_outcome(self, qubit: int) -> tuple[str, int]:
1✔
241
        """Simulate the outcome of measurement of a qubit.
242

243
        Args:
244
            qubit: index indicating the qubit to measure
245

246
        Return:
247
            pair (outcome, probability) where outcome is '0' or '1' and
248
            probability is the probability of the returned outcome.
249
        """
250
        # Axis for numpy.sum to compute probabilities
251
        axis = list(range(self._number_of_qubits))
1✔
252
        axis.remove(self._number_of_qubits - 1 - qubit)
1✔
253
        probabilities = np.sum(np.abs(self._statevector) ** 2, axis=tuple(axis))
1✔
254
        # Compute einsum index string for 1-qubit matrix multiplication
255
        random_number = self._local_random.random()
1✔
256
        if random_number < probabilities[0]:
1✔
257
            return "0", probabilities[0]
1✔
258
        # Else outcome was '1'
259
        return "1", probabilities[1]
1✔
260

261
    def _add_sample_measure(
1✔
262
        self, measure_params: list[tuple[int, int]], num_samples: int
263
    ) -> list[hex]:
264
        """Generate memory samples from current statevector.
265

266
        Args:
267
            measure_params: List of (qubit, cmembit) values for
268
                                   measure instructions to sample.
269
            num_samples: The number of memory samples to generate.
270

271
        Returns:
272
            A list of memory values in hex format.
273
        """
274
        # Get unique qubits that are actually measured and sort in
275
        # ascending order
276
        measured_qubits = sorted({qubit for qubit, _ in measure_params})
1✔
277
        num_measured = len(measured_qubits)
1✔
278
        # We use the axis kwarg for numpy.sum to compute probabilities
279
        # this sums over all non-measured qubits to return a vector
280
        # of measure probabilities for the measured qubits
281
        axis = list(range(self._number_of_qubits))
1✔
282
        for qubit in reversed(measured_qubits):
1✔
283
            # Remove from largest qubit to smallest so list position is correct
284
            # with respect to position from end of the list
285
            axis.remove(self._number_of_qubits - 1 - qubit)
1✔
286
        probabilities = np.reshape(
1✔
287
            np.sum(np.abs(self._statevector) ** 2, axis=tuple(axis)), 2**num_measured
288
        )
289
        # Generate samples on measured qubits as ints with qubit
290
        # position in the bit-string for each int given by the qubit
291
        # position in the sorted measured_qubits list
292
        samples = self._local_random.choice(range(2**num_measured), num_samples, p=probabilities)
1✔
293
        # Convert the ints to bitstrings
294
        memory = []
1✔
295
        for sample in samples:
1✔
296
            classical_memory = self._classical_memory
1✔
297
            for qubit, cmembit in measure_params:
1✔
298
                pos = measured_qubits.index(qubit)
1✔
299
                qubit_outcome = int((sample & (1 << pos)) >> pos)
1✔
300
                membit = 1 << cmembit
1✔
301
                classical_memory = (classical_memory & (~membit)) | (qubit_outcome << cmembit)
1✔
302
            value = bin(classical_memory)[2:]
1✔
303
            memory.append(hex(int(value, 2)))
1✔
304
        return memory
1✔
305

306
    def _add_measure(self, qubit: int, cmembit: int) -> None:
1✔
307
        """Apply a measure instruction to a qubit.
308

309
        Args:
310
            qubit: index of the qubit measured.
311
            cmembit: index of the classical memory bit to store outcome in.
312
        """
313
        # get measure outcome
314
        outcome, probability = self._get_measure_outcome(qubit)
1✔
315
        # update classical state
316
        membit = 1 << cmembit
1✔
317
        self._classical_memory = (self._classical_memory & (~membit)) | (int(outcome) << cmembit)
1✔
318

319
        # update quantum state
320
        if outcome == "0":
1✔
321
            update_diag = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
322
        else:
323
            update_diag = [[0, 0], [0, 1 / math.sqrt(probability)]]
1✔
324
        # update classical state
325
        self._add_unitary(update_diag, [qubit])
1✔
326

327
    def _add_reset(self, qubit: int) -> None:
1✔
328
        """Apply a reset instruction to a qubit.
329

330
        Args:
331
            qubit: the qubit being rest
332

333
        This is done by doing a simulating a measurement
334
        outcome and projecting onto the outcome state while
335
        renormalizing.
336
        """
337
        # get measure outcome
338
        outcome, probability = self._get_measure_outcome(qubit)
1✔
339
        # update quantum state
340
        if outcome == "0":
1✔
341
            update = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
342
            self._add_unitary(update, [qubit])
1✔
343
        else:
344
            update = [[0, 1 / math.sqrt(probability)], [0, 0]]
1✔
345
            self._add_unitary(update, [qubit])
1✔
346

347
    def _validate_initial_statevector(self) -> None:
1✔
348
        """Validate an initial statevector"""
349
        # If the initial statevector isn't set we don't need to validate
350
        if self._initial_statevector is None:
1✔
351
            return
1✔
352
        # Check statevector is correct length for number of qubits
353
        length = len(self._initial_statevector)
×
354
        required_dim = 2**self._number_of_qubits
×
355
        if length != required_dim:
×
356
            raise BasicProviderError(
×
357
                f"initial statevector is incorrect length: {length} != {required_dim}"
358
            )
359

360
    def _set_options(self, backend_options: dict | None = None) -> None:
1✔
361
        """Set the backend options for all circuits"""
362
        # Reset default options
363
        self._initial_statevector = self.options.get("initial_statevector")
1✔
364
        if "backend_options" in backend_options and backend_options["backend_options"]:
1✔
UNCOV
365
            backend_options = backend_options["backend_options"]
×
366

367
        # Check for custom initial statevector in backend_options first,
368
        # then config second
369
        if getattr(backend_options, "initial_statevector", None) is not None:
1✔
UNCOV
370
            self._initial_statevector = np.array(
×
371
                backend_options["initial_statevector"], dtype=complex
372
            )
373
        if self._initial_statevector is not None:
1✔
374
            # Check the initial statevector is normalized
375
            norm = np.linalg.norm(self._initial_statevector)
×
376
            if round(norm, 12) != 1:
×
NEW
377
                raise BasicProviderError(f"Initial statevector is not normalized: norm {norm} != 1")
×
378
        if "shots" in backend_options:
1✔
379
            self._shots = backend_options["shots"]
1✔
380
        if "seed_simulator" in backend_options:
1✔
381
            seed_simulator = backend_options["seed_simulator"]
1✔
382
        elif getattr(self.options, "seed_simulator", None) is not None:
1✔
NEW
383
            seed_simulator = self.options["seed_simulator"]
×
384
        else:
385
            # For compatibility on Windows force dtype to be int32
386
            # and set the maximum value to be (2 ** 31) - 1
387
            seed_simulator = np.random.randint(2147483647, dtype="int32")
1✔
388
        self._seed_simulator = seed_simulator
1✔
389
        self._local_random = np.random.default_rng(seed=seed_simulator)
1✔
390

391
    def _initialize_statevector(self) -> None:
1✔
392
        """Set the initial statevector for simulation"""
393
        if self._initial_statevector is None:
1✔
394
            # Set to default state of all qubits in |0>
395
            self._statevector = np.zeros(2**self._number_of_qubits, dtype=complex)
1✔
396
            self._statevector[0] = 1
1✔
397
        else:
398
            self._statevector = self._initial_statevector.copy()
×
399
        # Reshape to rank-N tensor
400
        self._statevector = np.reshape(self._statevector, self._number_of_qubits * [2])
1✔
401

402
    def _validate_measure_sampling(self, circuit: QuantumCircuit) -> None:
1✔
403
        """Determine if measure sampling is allowed for an experiment"""
404
        measure_flag = False
1✔
405
        # If shots=1 we should disable measure sampling.
406
        # This is also required for statevector simulator to return the
407
        # correct final statevector without silently dropping final measurements.
408
        if self._shots > 1:
1✔
409
            for instruction in circuit.data:
1✔
410
                # If circuit contains reset operations we cannot sample
411
                if instruction.name == "reset":
1✔
412
                    self._sample_measure = False
1✔
413
                    return
1✔
414
                # If circuit contains a measure option then we can
415
                # sample only if all following operations are measures
416
                if measure_flag:
1✔
417
                    # If we find a non-measure instruction
418
                    # we cannot do measure sampling
419
                    if instruction.name not in ["measure", "barrier", "id", "u0"]:
1✔
420
                        self._sample_measure = False
1✔
421
                        return
1✔
422
                elif instruction.name == "measure":
1✔
423
                    measure_flag = True
1✔
424
        self._sample_measure = measure_flag
1✔
425

426
    def run(
1✔
427
        self, run_input: QuantumCircuit | list[QuantumCircuit], **backend_options
428
    ) -> BasicProviderJob:
429
        """Run on the backend.
430

431
        Args:
432
            run_input: payload of the experiment
433
            backend_options: backend options
434

435
        Returns:
436
            BasicProviderJob: derived from BaseJob
437

438
        Additional Information:
439
            backend_options: Is a dict of options for the backend. It may contain:
440
                * "initial_statevector": vector_like
441

442
            The "initial_statevector" option specifies a custom initial
443
            initial statevector for the simulator to be used instead of the all
444
            zero state. This size of this vector must be correct for the number
445
            of qubits in ``run_input`` parameter.
446

447
            Example::
448

449
                backend_options = {
450
                    "initial_statevector": np.array([1, 0, 0, 1j]) / math.sqrt(2),
451
                }
452
        """
453
        out_options = {}
1✔
454
        for key, value in backend_options.items():
1✔
455
            if not hasattr(self.options, key):
1✔
456
                warnings.warn(
1✔
457
                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
458
                )
459
            else:
460
                out_options[key] = value
1✔
461
        self._set_options(backend_options=backend_options)
1✔
462
        job_id = str(uuid.uuid4())
1✔
463
        job = BasicProviderJob(self, job_id, self._run_job(job_id, run_input))
1✔
464
        return job
1✔
465

466
    def _run_job(self, job_id: str, run_input) -> Result:
1✔
467
        """Run circuits in job.
468

469
        Args:
470
            job_id: unique id for the job.
471
            run_input: circuits to be run.
472

473
        Returns:
474
            Result object
475
        """
476
        if isinstance(run_input, QuantumCircuit):
1✔
477
            run_input = [run_input]
1✔
478

479
        self._validate(run_input)
1✔
480
        result_list = []
1✔
481
        self._memory = True
1✔
482
        start = time.time()
1✔
483
        for circuit in run_input:
1✔
484
            result_list.append(self._run_circuit(circuit))
1✔
485
        end = time.time()
1✔
486
        result = {
1✔
487
            "backend_name": self.name,
488
            "backend_version": self.backend_version,
489
            "qobj_id": None,
490
            "job_id": job_id,
491
            "results": result_list,
492
            "status": "COMPLETED",
493
            "success": True,
494
            "time_taken": (end - start),
495
        }
496

497
        return Result.from_dict(result)
1✔
498

499
    def _run_circuit(self, circuit) -> dict:
1✔
500
        """Simulate a single circuit run.
501

502
        Args:
503
            circuit: circuit to be run.
504

505
        Returns:
506
             A result dictionary which looks something like::
507
                {
508
                "name": name of this experiment (obtained from qobj.experiment header)
509
                "seed": random seed used for simulation
510
                "shots": number of shots used in the simulation
511
                "data":
512
                    {
513
                    "counts": {'0x9: 5, ...},
514
                    "memory": ['0x9', '0xF', '0x1D', ..., '0x9']
515
                    },
516
                "status": status string for the simulation
517
                "success": boolean
518
                "time_taken": simulation time of this single experiment
519
                }
520
        Raises:
521
            BasicProviderError: if an error occurred.
522
        """
523
        start = time.time()
1✔
524

525
        self._number_of_qubits = circuit.num_qubits
1✔
526
        self._number_of_cmembits = circuit.num_clbits
1✔
527
        self._statevector = 0
1✔
528
        self._classical_memory = 0
1✔
529

530
        # Validate the dimension of initial statevector if set
531
        self._validate_initial_statevector()
1✔
532

533
        # Check if measure sampling is supported for current circuit
534
        self._validate_measure_sampling(circuit)
1✔
535

536
        # List of final counts for all shots
537
        memory = []
1✔
538
        # Check if we can sample measurements, if so we only perform 1 shot
539
        # and sample all outcomes from the final state vector
540
        if self._sample_measure:
1✔
541
            shots = 1
1✔
542
            # Store (qubit, cmembit) pairs for all measure ops in circuit to
543
            # be sampled
544
            measure_sample_ops = []
1✔
545
        else:
546
            shots = self._shots
1✔
547

548
        for _ in range(shots):
1✔
549
            self._initialize_statevector()
1✔
550
            # apply global_phase
551
            self._statevector *= np.exp(1j * circuit.global_phase)
1✔
552
            # Initialize classical memory to all 0
553
            self._classical_memory = 0
1✔
554

555
            for operation in circuit.data:
1✔
556
                if operation.name == "unitary":
1✔
557
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
558
                    gate = operation.params[0]
1✔
559
                    self._add_unitary(gate, qubits)
1✔
560
                elif operation.name in ("id", "u0", "delay"):
1✔
561
                    pass
1✔
562
                elif operation.name == "global_phase":
1✔
563
                    params = getattr(operation, "params", None)
1✔
564
                    gate = GlobalPhaseGate(*params).to_matrix()
1✔
565
                    self._add_unitary(gate, [])
1✔
566
                # Check if single qubit gate
567
                elif operation.name in SINGLE_QUBIT_GATES:
1✔
568
                    params = getattr(operation, "params", None)
1✔
569
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
570
                    gate = single_gate_matrix(operation.name, params)
1✔
571
                    self._add_unitary(gate, [qubit])
1✔
572
                elif operation.name in TWO_QUBIT_GATES_WITH_PARAMETERS:
1✔
573
                    params = getattr(operation, "params", None)
1✔
574
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
575
                    qubit0 = qubits[0]
1✔
576
                    qubit1 = qubits[1]
1✔
577
                    gate = TWO_QUBIT_GATES_WITH_PARAMETERS[operation.name](*params).to_matrix()
1✔
578
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
579
                elif operation.name in ("id", "u0"):
1✔
580
                    pass
×
581
                elif operation.name in TWO_QUBIT_GATES:
1✔
582
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
583
                    qubit0 = qubits[0]
1✔
584
                    qubit1 = qubits[1]
1✔
585
                    gate = TWO_QUBIT_GATES[operation.name]
1✔
586
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
587
                elif operation.name in THREE_QUBIT_GATES:
1✔
588
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
589
                    qubit0 = qubits[0]
1✔
590
                    qubit1 = qubits[1]
1✔
591
                    qubit2 = qubits[2]
1✔
592
                    gate = THREE_QUBIT_GATES[operation.name]
1✔
593
                    self._add_unitary(gate, [qubit0, qubit1, qubit2])
1✔
594
                # Check if reset
595
                elif operation.name == "reset":
1✔
596
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
597
                    qubit = qubits[0]
1✔
598
                    self._add_reset(qubit)
1✔
599
                # Check if barrier
600
                elif operation.name == "barrier":
1✔
601
                    pass
1✔
602
                # Check if measure
603
                elif operation.name == "measure":
1✔
604
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
605
                    cmembit = [circuit.find_bit(bit).index for bit in operation.clbits][0]
1✔
606
                    if self._sample_measure:
1✔
607
                        # If sampling measurements record the qubit and cmembit
608
                        # for this measurement for later sampling
609
                        measure_sample_ops.append((qubit, cmembit))
1✔
610
                    else:
611
                        # If not sampling perform measurement as normal
612
                        self._add_measure(qubit, cmembit)
1✔
613
                else:
614
                    backend = self.name
×
615
                    err_msg = '{0} encountered unrecognized operation "{1}"'
×
616
                    raise BasicProviderError(err_msg.format(backend, operation.name))
×
617

618
            # Add final creg data to memory list
619
            if self._number_of_cmembits > 0:
1✔
620
                if self._sample_measure:
1✔
621
                    # If sampling we generate all shot samples from the final statevector
622
                    memory = self._add_sample_measure(measure_sample_ops, self._shots)
1✔
623
                else:
624
                    # Turn classical_memory (int) into bit string and pad zero for unused cmembits
625
                    outcome = bin(self._classical_memory)[2:]
1✔
626
                    memory.append(hex(int(outcome, 2)))
1✔
627

628
        # Add counts to result data
629
        data = {"counts": dict(Counter(memory))}
1✔
630
        # Optionally, add memory list to result data
631
        if self._memory:
1✔
632
            data["memory"] = memory
1✔
633
        end = time.time()
1✔
634

635
        # Define header to be used by Result class to interpret counts
636
        header = {
1✔
637
            "name": circuit.name,
638
            "n_qubits": circuit.num_qubits,
639
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
640
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
641
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
642
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
643
            "memory_slots": circuit.num_clbits,
644
            "global_phase": circuit.global_phase,
645
            "metadata": circuit.metadata if circuit.metadata is not None else {},
646
        }
647

648
        # Return result dictionary
649
        return {
1✔
650
            "name": circuit.name,
651
            "seed_simulator": self._seed_simulator,
652
            "shots": self._shots,
653
            "data": data,
654
            "status": "DONE",
655
            "success": True,
656
            "header": header,
657
            "time_taken": (end - start),
658
        }
659

660
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
661
        """Semantic validations of the input."""
662
        max_qubits = self.MAX_QUBITS_MEMORY
1✔
663

664
        for circuit in run_input:
1✔
665
            if circuit.num_qubits > max_qubits:
1✔
666
                raise BasicProviderError(
1✔
667
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
668
                    f'for "{self.name}".'
669
                )
670
            name = circuit.name
1✔
671
            if len(circuit.cregs) == 0:
1✔
672
                logger.warning(
1✔
673
                    'No classical registers in circuit "%s", counts will be empty.', name
674
                )
675
            elif "measure" not in [op.name for op in circuit.data]:
1✔
676
                logger.warning(
1✔
677
                    'No measurements in circuit "%s", classical register will remain all zeros.',
678
                    name,
679
                )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc