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

Qiskit / qiskit / 13052215048

30 Jan 2025 12:21PM UTC coverage: 88.942% (-0.008%) from 88.95%
13052215048

Pull #13743

github

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

68 of 69 new or added lines in 2 files covered. (98.55%)

309 existing lines in 15 files now uncovered.

79623 of 89522 relevant lines covered (88.94%)

349616.37 hits per line

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

97.33
/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

104
        # Internal simulator variables
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._local_rng = None
1✔
110

111
    @property
1✔
112
    def max_circuits(self) -> None:
1✔
113
        return None
1✔
114

115
    @property
1✔
116
    def target(self) -> Target:
1✔
117
        if not self._target:
1✔
118
            self._target = self._build_basic_target()
1✔
119
        return self._target
1✔
120

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

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

207
    @classmethod
1✔
208
    def _default_options(cls) -> Options:
1✔
209
        return Options(
1✔
210
            shots=1024,
211
            memory=True,
212
            initial_statevector=None,
213
            seed_simulator=None,
214
        )
215

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

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

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

237
        Args:
238
            qubit: index indicating the qubit to measure
239

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

255
    def _add_sample_measure(
1✔
256
        self, measure_params: list[tuple[int, int]], num_samples: int
257
    ) -> list[hex]:
258
        """Generate memory samples from current statevector.
259

260
        Args:
261
            measure_params: List of (qubit, cmembit) values for
262
                                   measure instructions to sample.
263
            num_samples: The number of memory samples to generate.
264

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

300
    def _add_measure(self, qubit: int, cmembit: int) -> None:
1✔
301
        """Apply a measure instruction to a qubit.
302

303
        Args:
304
            qubit: index of the qubit measured.
305
            cmembit: index of the classical memory bit to store outcome in.
306
        """
307
        # get measure outcome
308
        outcome, probability = self._get_measure_outcome(qubit)
1✔
309
        # update classical state
310
        membit = 1 << cmembit
1✔
311
        self._classical_memory = (self._classical_memory & (~membit)) | (int(outcome) << cmembit)
1✔
312

313
        # update quantum state
314
        if outcome == "0":
1✔
315
            update_diag = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
316
        else:
317
            update_diag = [[0, 0], [0, 1 / math.sqrt(probability)]]
1✔
318
        # update classical state
319
        self._add_unitary(update_diag, [qubit])
1✔
320

321
    def _add_reset(self, qubit: int) -> None:
1✔
322
        """Apply a reset instruction to a qubit.
323

324
        Args:
325
            qubit: the qubit being rest
326

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

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

354
    def _set_run_options(self, run_options: dict | None = None) -> None:
1✔
355
        """Set the backend run options for all circuits"""
356

357
        # Reset internal variables every time "run" is called using saved options
358
        self._shots = self.options.get("shots")
1✔
359
        self._memory = self.options.get("memory")
1✔
360
        self._initial_statevector = self.options.get("initial_statevector")
1✔
361
        self._seed_simulator = self.options.get("seed_simulator")
1✔
362

363
        # Apply custom run options
364
        if run_options.get("initial_statevector", None) is not None:
1✔
365
            self._initial_statevector = np.array(run_options["initial_statevector"], dtype=complex)
1✔
366
        if self._initial_statevector is not None:
1✔
367
            # Check the initial statevector is normalized
368
            norm = np.linalg.norm(self._initial_statevector)
1✔
369
            if round(norm, 12) != 1:
1✔
NEW
370
                raise BasicProviderError(f"Initial statevector is not normalized: norm {norm} != 1")
×
371
        if "shots" in run_options:
1✔
372
            self._shots = run_options["shots"]
1✔
373
        if "seed_simulator" in run_options:
1✔
374
            self._seed_simulator = run_options["seed_simulator"]
1✔
375
        elif self._seed_simulator is None:
1✔
376
            # For compatibility on Windows force dtype to be int32
377
            # and set the maximum value to be (2 ** 31) - 1
378
            self._seed_simulator = np.random.randint(2147483647, dtype="int32")
1✔
379
        if "memory" in run_options:
1✔
380
            self._memory = run_options["memory"]
1✔
381
        # Set seed for local random number gen.
382
        self._local_rng = np.random.default_rng(seed=self._seed_simulator)
1✔
383

384
    def _initialize_statevector(self) -> None:
1✔
385
        """Set the initial statevector for simulation"""
386
        if self._initial_statevector is None:
1✔
387
            # Set to default state of all qubits in |0>
388
            self._statevector = np.zeros(2**self._number_of_qubits, dtype=complex)
1✔
389
            self._statevector[0] = 1
1✔
390
        else:
391
            self._statevector = self._initial_statevector.copy()
1✔
392
        # Reshape to rank-N tensor
393
        self._statevector = np.reshape(self._statevector, self._number_of_qubits * [2])
1✔
394

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

419
    def run(
1✔
420
        self, run_input: QuantumCircuit | list[QuantumCircuit], **run_options
421
    ) -> BasicProviderJob:
422
        """Run on the backend.
423

424
                Args:
425
                    run_input (QuantumCircuit or list): the QuantumCircuit (or list
426
                        of QuantumCircuit objects) to run
427
                    run_options (kwargs): additional runtime backend options
428

429
                Returns:
430
                    BasicProviderJob: derived from BaseJob
431

432
                Additional Information:
433
                    * kwarg options specified in ``run_options`` will temporarily override
434
                      any set options of the same name for the current run. These may include:
435

436
                        * "initial_statevector": vector_like. The "initial_statevector" option specifies a custom
437
                            initial statevector for the simulator to be used instead of the all
438
                            zero state. This size of this vector must be correct for the number
439
                            of qubits in the ``run_input`` parameter.
440
                        * "seed_simulator": int. This is the internal seed for sample generation.
441
                        * "shots": int. number of shots used in the simulation.
442
                        * "memory": bool. If True, the result will contained the results of every individual shot
443
                            simulation.
444
                    Example::
445
        D
446
                        backend.run(circuit, initial_statevector = np.array([1, 0, 0, 1j]) / math.sqrt(2))
447
        """
448
        out_options = {}
1✔
449
        for key, value in run_options.items():
1✔
450
            if not hasattr(self.options, key):
1✔
451
                warnings.warn(
1✔
452
                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
453
                )
454
            else:
455
                out_options[key] = value
1✔
456
        self._set_run_options(run_options=run_options)
1✔
457
        job_id = str(uuid.uuid4())
1✔
458
        job = BasicProviderJob(self, job_id, self._run_job(job_id, run_input))
1✔
459
        return job
1✔
460

461
    def _run_job(self, job_id: str, run_input) -> Result:
1✔
462
        """Run circuits in run_input.
463

464
        Args:
465
            job_id: unique id for the job.
466
            run_input: circuits to be run.
467

468
        Returns:
469
            Result object
470
        """
471
        if isinstance(run_input, QuantumCircuit):
1✔
472
            run_input = [run_input]
1✔
473

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

491
        return Result.from_dict(result)
1✔
492

493
    def _run_circuit(self, circuit) -> dict:
1✔
494
        """Simulate a single circuit run.
495

496
        Args:
497
            circuit: circuit to be run.
498

499
        Returns:
500
             A result dictionary which looks something like::
501
                {
502
                "name": name of this experiment
503
                "seed": random seed used for simulation
504
                "shots": number of shots used in the simulation
505
                "header: {
506
                    "name": "circuit-206",
507
                    "n_qubits": 3,
508
                    "qreg_sizes": [['qr', 3]],
509
                    "creg_sizes": [['cr', 3]],
510
                    "qubit_labels": [['qr', 0], ['qr', 1], ['qr', 2]],
511
                    "clbit_labels": [['cr', 0], ['cr', 1], ['cr', 2]],
512
                    "memory_slots": 3,
513
                    "global_phase": 0.0,
514
                    "metadata": {},
515
                    }
516
                "data":
517
                    {
518
                    "counts": {'0x9: 5, ...},
519
                    "memory": ['0x9', '0xF', '0x1D', ..., '0x9']
520
                    },
521
                "status": status string for the simulation
522
                "success": boolean
523
                "time_taken": simulation time of this single experiment
524
                }
525
        Raises:
526
            BasicProviderError: if an error occurred.
527
        """
528
        start = time.time()
1✔
529

530
        self._number_of_qubits = circuit.num_qubits
1✔
531
        self._number_of_cmembits = circuit.num_clbits
1✔
532
        self._statevector = 0
1✔
533
        self._classical_memory = 0
1✔
534

535
        # Validate the dimension of initial statevector if set
536
        self._validate_initial_statevector()
1✔
537

538
        # Check if measure sampling is supported for current circuit
539
        self._validate_measure_sampling(circuit)
1✔
540

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

553
        for _ in range(shots):
1✔
554
            self._initialize_statevector()
1✔
555
            # apply global_phase
556
            self._statevector *= np.exp(1j * circuit.global_phase)
1✔
557
            # Initialize classical memory to all 0
558
            self._classical_memory = 0
1✔
559

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

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

633
        # Add counts to result data
634
        data = {"counts": dict(Counter(memory))}
1✔
635
        # Optionally, add memory list to result data
636
        if self._memory:
1✔
637
            data["memory"] = memory
1✔
638
        end = time.time()
1✔
639

640
        # Define header to be used by Result class to interpret counts
641
        header = {
1✔
642
            "name": circuit.name,
643
            "n_qubits": circuit.num_qubits,
644
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
645
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
646
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
647
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
648
            "memory_slots": circuit.num_clbits,
649
            "global_phase": circuit.global_phase,
650
            "metadata": circuit.metadata if circuit.metadata is not None else {},
651
        }
652
        print(header)
1✔
653
        # Return result dictionary
654
        return {
1✔
655
            "name": circuit.name,
656
            "seed_simulator": self._seed_simulator,
657
            "shots": self._shots,
658
            "data": data,
659
            "status": "DONE",
660
            "success": True,
661
            "header": header,
662
            "time_taken": (end - start),
663
        }
664

665
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
666
        """Semantic validations of the input."""
667
        max_qubits = self.MAX_QUBITS_MEMORY
1✔
668

669
        for circuit in run_input:
1✔
670
            if circuit.num_qubits > max_qubits:
1✔
671
                raise BasicProviderError(
1✔
672
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
673
                    f'for "{self.name}".'
674
                )
675
            name = circuit.name
1✔
676
            if len(circuit.cregs) == 0:
1✔
677
                logger.warning(
1✔
678
                    'No classical registers in circuit "%s", counts will be empty.', name
679
                )
680
            elif "measure" not in [op.name for op in circuit.data]:
1✔
681
                logger.warning(
1✔
682
                    'No measurements in circuit "%s", classical register will remain all zeros.',
683
                    name,
684
                )
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