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

Qiskit / qiskit / 13412874666

19 Feb 2025 12:44PM UTC coverage: 88.102% (-0.5%) from 88.599%
13412874666

Pull #12814

github

web-flow
Merge 74e63bb60 into bd3069333
Pull Request #12814: Light Cone Transpiler Pass

79 of 81 new or added lines in 2 files covered. (97.53%)

1477 existing lines in 53 files now uncovered.

78402 of 88990 relevant lines covered (88.1%)

349837.85 hits per line

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

96.99
/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✔
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 to be used instead
443
                  of the all-zero state. The size of this vector must correspond to
444
                  the number of qubits in the ``run_input`` argument.
445

446
                * "seed_simulator": int. This is the internal seed for sample
447
                  generation.
448

449
                * "shots": int. Number of shots used in the simulation.
450

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

454
            Example::
455

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

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

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

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

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

504
        return Result.from_dict(result)
1✔
505

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

509
        Args:
510
            circuit: circuit to be run.
511

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

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

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

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

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

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

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

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

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

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

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

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