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

Qiskit / qiskit / 13587350705

28 Feb 2025 11:37AM UTC coverage: 88.02% (+0.2%) from 87.87%
13587350705

Pull #13877

github

web-flow
Merge 50dc391f3 into b9bdc5a0c
Pull Request #13877: Remove v1 primitive implementations

83 of 95 new or added lines in 14 files covered. (87.37%)

195 existing lines in 14 files now uncovered.

77010 of 87491 relevant lines covered (88.02%)

343425.67 hits per line

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

96.23
/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.backend import BackendV2
1✔
47
from qiskit.providers.options import Options
1✔
48
from qiskit.result import Result
1✔
49
from qiskit.transpiler import Target
1✔
50

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

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

64

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

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

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

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

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

100
        self._target = target
1✔
101

102
        # Internal simulator variables
103
        self._classical_memory = 0
1✔
104
        self._statevector = 0
1✔
105
        self._number_of_cmembits = 0
1✔
106
        self._number_of_qubits = 0
1✔
107
        self._local_rng = None
1✔
108
        self._sample_measure = False
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

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

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

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

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

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

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

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

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

240
        Args:
241
            qubit: index indicating the qubit to measure
242

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

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

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

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

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

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

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

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

327
        Args:
328
            qubit: the qubit being rest
329

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

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

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

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

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

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

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

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

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

432
        Returns:
433
            BasicProviderJob: derived from BaseJob
434

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

439
                * "initial_statevector": vector-like. The "initial_statevector"
440
                  option specifies a custom initial statevector to be used instead
441
                  of the all-zero state. The size of this vector must correspond to
442
                  the number of qubits in the ``run_input`` argument.
443

444
                * "seed_simulator": int. This is the internal seed for sample
445
                  generation.
446

447
                * "shots": int. Number of shots used in the simulation.
448

449
                * "memory": bool. If True, the result will contain the results
450
                  of every individual shot simulation.
451

452
            Example::
453

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

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

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

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

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

502
        return Result.from_dict(result)
1✔
503

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

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

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

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

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

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

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

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

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

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

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

651
        # Define header to be used by Result class to interpret counts
652
        header = {
1✔
653
            "name": circuit.name,
654
            "n_qubits": circuit.num_qubits,
655
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
656
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
657
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
658
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
659
            "memory_slots": circuit.num_clbits,
660
            "global_phase": circuit.global_phase,
661
            "metadata": circuit.metadata if circuit.metadata is not None else {},
662
        }
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