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

Qiskit / qiskit / 13055262712

30 Jan 2025 03:13PM UTC coverage: 88.977% (+0.03%) from 88.95%
13055262712

Pull #13743

github

web-flow
Merge 0bc13b796 into 3c805dd79
Pull Request #13743: Remove use of deprecated objects in `BasicSimulator`

72 of 73 new or added lines in 2 files covered. (98.63%)

297 existing lines in 18 files now uncovered.

79660 of 89529 relevant lines covered (88.98%)

349274.78 hits per line

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

97.38
/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
        self._sample_measure = False
1✔
111
        self._shots = self.options.get("shots")
1✔
112
        self._memory = self.options.get("memory")
1✔
113
        self._initial_statevector = self.options.get("initial_statevector")
1✔
114
        self._seed_simulator = self.options.get("seed_simulator")
1✔
115

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

329
        Args:
330
            qubit: the qubit being rest
331

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

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

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

362
        # Reset internal variables every time "run" is called using saved options
363
        self._shots = self.options.get("shots")
1✔
364
        self._memory = self.options.get("memory")
1✔
365
        self._initial_statevector = self.options.get("initial_statevector")
1✔
366
        self._seed_simulator = self.options.get("seed_simulator")
1✔
367

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

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

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

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

429
        Args:
430
            run_input (QuantumCircuit or list): the QuantumCircuit (or list
431
                of QuantumCircuit objects) to run
432
            run_options (kwargs): additional runtime backend options
433

434
        Returns:
435
            BasicProviderJob: derived from BaseJob
436

437
        Additional Information:
438
            * kwarg options specified in ``run_options`` will temporarily override
439
              any set options of the same name for the current run. These may include:
440

441
                * "initial_statevector": vector_like. The "initial_statevector"
442
                option specifies a custom initial statevector for the simulator
443
                to be used instead of the all zero state. The size of this
444
                vector must be correct for the number of qubits in the
445
                ``run_input`` argument.
446
                * "seed_simulator": int. This is the internal seed for sample
447
                generation.
448
                * "shots": int. number of shots used in the simulation.
449
                * "memory": bool. If True, the result will contained the results
450
                of every individual shotsimulation.
451
            Example::
452

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

471
    def _run_job(self, job_id: str, run_input) -> Result:
1✔
472
        """Run circuits in run_input.
473

474
        Args:
475
            job_id: unique id for the job.
476
            run_input: circuits to be run.
477

478
        Returns:
479
            Result object
480
        """
481
        if isinstance(run_input, QuantumCircuit):
1✔
482
            run_input = [run_input]
1✔
483

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

501
        return Result.from_dict(result)
1✔
502

503
    def _run_circuit(self, circuit) -> dict:
1✔
504
        """Simulate a single circuit run.
505

506
        Args:
507
            circuit: circuit to be run.
508

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

540
        self._number_of_qubits = circuit.num_qubits
1✔
541
        self._number_of_cmembits = circuit.num_clbits
1✔
542
        self._statevector = 0
1✔
543
        self._classical_memory = 0
1✔
544

545
        # Validate the dimension of initial statevector if set
546
        self._validate_initial_statevector()
1✔
547

548
        # Check if measure sampling is supported for current circuit
549
        self._validate_measure_sampling(circuit)
1✔
550

551
        # List of final counts for all shots
552
        memory = []
1✔
553
        # Check if we can sample measurements, if so we only perform 1 shot
554
        # and sample all outcomes from the final state vector
555
        if self._sample_measure:
1✔
556
            shots = 1
1✔
557
            # Store (qubit, cmembit) pairs for all measure ops in circuit to
558
            # be sampled
559
            measure_sample_ops = []
1✔
560
        else:
561
            shots = self._shots
1✔
562

563
        for _ in range(shots):
1✔
564
            self._initialize_statevector()
1✔
565
            # apply global_phase
566
            self._statevector *= np.exp(1j * circuit.global_phase)
1✔
567
            # Initialize classical memory to all 0
568
            self._classical_memory = 0
1✔
569

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

633
            # Add final creg data to memory list
634
            if self._number_of_cmembits > 0:
1✔
635
                if self._sample_measure:
1✔
636
                    # If sampling we generate all shot samples from the final statevector
637
                    memory = self._add_sample_measure(measure_sample_ops, self._shots)
1✔
638
                else:
639
                    # Turn classical_memory (int) into bit string and pad zero for unused cmembits
640
                    outcome = bin(self._classical_memory)[2:]
1✔
641
                    memory.append(hex(int(outcome, 2)))
1✔
642

643
        # Add counts to result data
644
        data = {"counts": dict(Counter(memory))}
1✔
645
        # Optionally, add memory list to result data
646
        if self._memory:
1✔
647
            data["memory"] = memory
1✔
648
        end = time.time()
1✔
649

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

675
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
676
        """Semantic validations of the input."""
677
        max_qubits = self.MAX_QUBITS_MEMORY
1✔
678

679
        for circuit in run_input:
1✔
680
            if circuit.num_qubits > max_qubits:
1✔
681
                raise BasicProviderError(
1✔
682
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
683
                    f'for "{self.name}".'
684
                )
685
            name = circuit.name
1✔
686
            if len(circuit.cregs) == 0:
1✔
687
                logger.warning(
1✔
688
                    'No classical registers in circuit "%s", counts will be empty.', name
689
                )
690
            elif "measure" not in [op.name for op in circuit.data]:
1✔
691
                logger.warning(
1✔
692
                    'No measurements in circuit "%s", classical register will remain all zeros.',
693
                    name,
694
                )
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