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

Qiskit / qiskit / 12994743540

27 Jan 2025 05:29PM UTC coverage: 88.94% (-0.01%) from 88.95%
12994743540

Pull #13743

github

web-flow
Merge b3ec92919 into c085ec50a
Pull Request #13743: Remove use of deprecated objects in `BasicSimulator`

90 of 91 new or added lines in 1 file covered. (98.9%)

35 existing lines in 9 files now uncovered.

79685 of 89594 relevant lines covered (88.94%)

350886.71 hits per line

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

94.08
/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 quantum experiments",
98
            backend_version="0.1",
99
            **fields,
100
        )
101

102
        self._target = target
1✔
103
        # Internal simulator variables
104
        self._local_random = None
1✔
105
        self._classical_memory = 0
1✔
106
        self._classical_register = 0
1✔
107
        self._statevector = 0
1✔
108
        self._number_of_cmembits = 0
1✔
109
        self._number_of_qubits = 0
1✔
110
        self._shots = self.options.get("shots")
1✔
111
        self._memory = self.options.get("memory")
1✔
112
        self._initial_statevector = self.options.get("initial_statevector")
1✔
113
        self._chop_threshold = self.options.get("chop_threashold")
1✔
114
        self._sample_measure = False
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=False,
217
            initial_statevector=None,
218
            chop_threshold=1e-15,
219
            allow_sample_measuring=True,
220
            seed_simulator=None,
221
            parameter_binds=None,
222
        )
223

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

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

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

245
        Args:
246
            qubit: the qubit to measure
247

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

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

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

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

308
    def _add_measure(self, qubit: int, cmembit: int, cregbit: int | None = None) -> None:
1✔
309
        """Apply a measure instruction to a qubit.
310

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

322
        if cregbit is not None:
1✔
323
            regbit = 1 << cregbit
1✔
324
            self._classical_register = (self._classical_register & (~regbit)) | (
1✔
325
                int(outcome) << cregbit
326
            )
327

328
        # update quantum state
329
        if outcome == "0":
1✔
330
            update_diag = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
331
        else:
332
            update_diag = [[0, 0], [0, 1 / math.sqrt(probability)]]
1✔
333
        # update classical state
334
        self._add_unitary(update_diag, [qubit])
1✔
335

336
    def _add_reset(self, qubit: int) -> None:
1✔
337
        """Apply a reset instruction to a qubit.
338

339
        Args:
340
            qubit: the qubit being rest
341

342
        This is done by doing a simulating a measurement
343
        outcome and projecting onto the outcome state while
344
        renormalizing.
345
        """
346
        # get measure outcome
347
        outcome, probability = self._get_measure_outcome(qubit)
1✔
348
        # update quantum state
349
        if outcome == "0":
1✔
350
            update = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
351
            self._add_unitary(update, [qubit])
1✔
352
        else:
353
            update = [[0, 1 / math.sqrt(probability)], [0, 0]]
1✔
354
            self._add_unitary(update, [qubit])
1✔
355

356
    def _validate_initial_statevector(self) -> None:
1✔
357
        """Validate an initial statevector"""
358
        # If the initial statevector isn't set we don't need to validate
359
        if self._initial_statevector is None:
1✔
360
            return
1✔
361
        # Check statevector is correct length for number of qubits
362
        length = len(self._initial_statevector)
×
363
        required_dim = 2**self._number_of_qubits
×
364
        if length != required_dim:
×
365
            raise BasicProviderError(
×
366
                f"initial statevector is incorrect length: {length} != {required_dim}"
367
            )
368

369
    def _set_options(self, backend_options: dict | None = None) -> None:
1✔
370
        """Set the backend options for all experiments"""
371
        # Reset default options
372
        self._initial_statevector = self.options.get("initial_statevector")
1✔
373
        self._chop_threshold = self.options.get("chop_threshold")
1✔
374
        if "backend_options" in backend_options and backend_options["backend_options"]:
1✔
375
            backend_options = backend_options["backend_options"]
×
376

377
        # Check for custom initial statevector in backend_options first,
378
        # then config second
379
        if (
1✔
380
            "initial_statevector" in backend_options
381
            and backend_options["initial_statevector"] is not None
382
        ):
383
            self._initial_statevector = np.array(
×
384
                backend_options["initial_statevector"], dtype=complex
385
            )
386

387
        if self._initial_statevector is not None:
1✔
388
            # Check the initial statevector is normalized
389
            norm = np.linalg.norm(self._initial_statevector)
×
390
            if round(norm, 12) != 1:
×
391
                raise BasicProviderError(f"initial statevector is not normalized: norm {norm} != 1")
×
392
        # Check for custom chop threshold
393
        # Replace with custom options
394
        if "chop_threshold" in backend_options:
1✔
395
            self._chop_threshold = backend_options["chop_threshold"]
×
396

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

408
    def run(
1✔
409
        self, run_input: QuantumCircuit | list[QuantumCircuit], **backend_options
410
    ) -> BasicProviderJob:
411
        """Run on the backend.
412

413
        Args:
414
            run_input: payload of the experiment
415
            backend_options: backend options
416

417
        Returns:
418
            BasicProviderJob: derived from BaseJob
419

420
        Additional Information:
421
            backend_options: Is a dict of options for the backend. It may contain
422
                * "initial_statevector": vector_like
423

424
            The "initial_statevector" option specifies a custom initial
425
            initial statevector for the simulator to be used instead of the all
426
            zero state. This size of this vector must be correct for the number
427
            of qubits in ``run_input`` parameter.
428

429
            Example::
430

431
                backend_options = {
432
                    "initial_statevector": np.array([1, 0, 0, 1j]) / math.sqrt(2),
433
                }
434
        """
435
        out_options = {}
1✔
436
        for key, value in backend_options.items():
1✔
437
            if not hasattr(self.options, key):
1✔
438
                warnings.warn(
1✔
439
                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
440
                )
441
            else:
442
                out_options[key] = value
1✔
443
        self._set_options(backend_options=backend_options)
1✔
444
        job_id = str(uuid.uuid4())
1✔
445
        job = BasicProviderJob(self, job_id, self._run_job(job_id, run_input, **out_options))
1✔
446
        return job
1✔
447

448
    def _run_job(self, job_id: str, run_input, **backend_options) -> Result:
1✔
449
        """Run experiments in job
450

451
        Args:
452
            job_id: unique id for the job.
453
            run_input: circuits to be run.
454
            **backend_options: run options.
455

456
        Returns:
457
            Result object
458
        """
459
        if isinstance(run_input, QuantumCircuit):
1✔
460
            run_input = [run_input]
1✔
461

462
        self._validate(run_input)
1✔
463
        result_list = []
1✔
464
        self._memory = True
1✔
465
        start = time.time()
1✔
466
        for experiment in run_input:
1✔
467
            result_list.append(self._run_circuit(experiment, **backend_options))
1✔
468
        end = time.time()
1✔
469
        result = {
1✔
470
            "backend_name": self.name,
471
            "backend_version": self.backend_version,
472
            "qobj_id": None,
473
            "job_id": job_id,
474
            "results": result_list,
475
            "status": "COMPLETED",
476
            "success": True,
477
            "time_taken": (end - start),
478
        }
479

480
        return Result.from_dict(result)
1✔
481

482
    def _run_circuit(self, circuit, **backend_options):
1✔
483
        start = time.time()
1✔
484

485
        # from _assemble_circuit ==================
486
        num_qubits = 0
1✔
487
        memory_slots = 0
1✔
488
        qubit_labels = []
1✔
489
        clbit_labels = []
1✔
490

491
        qreg_sizes = []
1✔
492
        creg_sizes = []
1✔
493
        for qreg in circuit.qregs:
1✔
494
            qreg_sizes.append([qreg.name, qreg.size])
1✔
495
            for j in range(qreg.size):
1✔
496
                qubit_labels.append([qreg.name, j])
1✔
497
            num_qubits += qreg.size
1✔
498
        for creg in circuit.cregs:
1✔
499
            creg_sizes.append([creg.name, creg.size])
1✔
500
            for j in range(creg.size):
1✔
501
                clbit_labels.append([creg.name, j])
1✔
502
            memory_slots += creg.size
1✔
503

504
        metadata = circuit.metadata
1✔
505
        if metadata is None:
1✔
NEW
506
            metadata = {}
×
507

508
        is_conditional_experiment = any(
1✔
509
            getattr(instruction.operation, "_condition", None) for instruction in circuit.data
510
        )
511

512
        # till here ====================
513
        self._number_of_qubits = num_qubits
1✔
514
        self._number_of_cmembits = memory_slots
1✔
515
        self._statevector = 0
1✔
516
        self._classical_memory = 0
1✔
517
        self._classical_register = 0
1✔
518
        self._sample_measure = False
1✔
519
        global_phase = circuit.global_phase
1✔
520

521
        qubit_indices = {qubit: idx for idx, qubit in enumerate(circuit.qubits)}
1✔
522
        clbit_indices = {clbit: idx for idx, clbit in enumerate(circuit.clbits)}
1✔
523

524
        # Validate the dimension of initial statevector if set
525
        self._validate_initial_statevector()
1✔
526

527
        # Get the seed looking in user options, stored options, and then random.
528
        if "seed_simulator" in backend_options:
1✔
529
            seed_simulator = backend_options["seed_simulator"]
1✔
530
        elif hasattr(self.options, "seed_simulator"):
1✔
531
            seed_simulator = self.options["seed_simulator"]
1✔
532
        else:
533
            # For compatibility on Windows force dyte to be int32
534
            # and set the maximum value to be (2 ** 31) - 1
UNCOV
535
            seed_simulator = np.random.randint(2147483647, dtype="int32")
×
536

537
        self._local_random = np.random.default_rng(seed=seed_simulator)
1✔
538
        # Check if measure sampling is supported for current circuit
539

540
        # From self._validate_measure_sampling ====================
541

542
        if "shots" in backend_options:
1✔
543
            self._shots = backend_options["shots"]
1✔
544

545
        # If shots=1 we should disable measure sampling.
546
        # This is also required for statevector simulator to return the
547
        # correct final statevector without silently dropping final measurements.
548
        if self._shots <= 1:
1✔
549
            measure_flag = False
1✔
550
        else:
551
            measure_flag = False
1✔
552
            for instruction in circuit.data:
1✔
553
                # If circuit contains reset operations we cannot sample
554
                if instruction.name == "reset":
1✔
555
                    measure_flag = False
1✔
556
                # If circuit contains a measure option then we can
557
                # sample only if all following operations are measures
558
                if measure_flag:
1✔
559
                    # If we find a non-measure instruction
560
                    # we cannot do measure sampling
561
                    if instruction.name not in ["measure", "barrier", "id", "u0"]:
1✔
562
                        measure_flag = False
1✔
563
                elif instruction.name == "measure":
1✔
564
                    measure_flag = True
1✔
565
        # measure sampling is allowed
566
        self._sample_measure = measure_flag
1✔
567

568
        # ========================================== up till here
569

570
        # List of final counts for all shots
571
        memory = []
1✔
572
        # Check if we can sample measurements, if so we only perform 1 shot
573
        # and sample all outcomes from the final state vector
574
        if self._sample_measure:
1✔
575
            shots = 1
1✔
576
            # Store (qubit, cmembit) pairs for all measure ops in circuit to
577
            # be sampled
578
            measure_sample_ops = []
1✔
579
        else:
580
            shots = self._shots
1✔
581

582
        for _ in range(shots):
1✔
583
            self._initialize_statevector()
1✔
584
            # apply global_phase
585
            self._statevector *= np.exp(1j * global_phase)
1✔
586
            # Initialize classical memory to all 0
587
            self._classical_memory = 0
1✔
588
            self._classical_register = 0
1✔
589

590
            for operation in circuit.data:
1✔
591
                if operation.name == "unitary":
1✔
592
                    qargs = operation.qubits
1✔
593
                    qubits = [qubit_indices[qubit] for qubit in qargs]
1✔
594
                    gate = operation.params[0]
1✔
595
                    self._add_unitary(gate, qubits)
1✔
596
                elif operation.name in ("id", "u0", "delay"):
1✔
597
                    pass
1✔
598
                elif operation.name == "global_phase":
1✔
599
                    params = getattr(operation, "params", None)
1✔
600
                    gate = GlobalPhaseGate(*params).to_matrix()
1✔
601
                    self._add_unitary(gate, [])
1✔
602
                # Check if single qubit gate
603
                elif operation.name in SINGLE_QUBIT_GATES:
1✔
604
                    params = getattr(operation, "params", None)
1✔
605
                    qargs = operation.qubits
1✔
606
                    qubit = [qubit_indices[qubit] for qubit in qargs][0]
1✔
607
                    gate = single_gate_matrix(operation.name, params)
1✔
608
                    self._add_unitary(gate, [qubit])
1✔
609
                elif operation.name in TWO_QUBIT_GATES_WITH_PARAMETERS:
1✔
610
                    params = getattr(operation, "params", None)
1✔
611
                    qargs = operation.qubits
1✔
612
                    qubits = [qubit_indices[qubit] for qubit in qargs]
1✔
613
                    qubit0 = qubits[0]
1✔
614
                    qubit1 = qubits[1]
1✔
615
                    gate = TWO_QUBIT_GATES_WITH_PARAMETERS[operation.name](*params).to_matrix()
1✔
616
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
617
                elif operation.name in ("id", "u0"):
1✔
618
                    pass
×
619
                elif operation.name in TWO_QUBIT_GATES:
1✔
620
                    qargs = operation.qubits
1✔
621
                    qubits = [qubit_indices[qubit] for qubit in qargs]
1✔
622
                    qubit0 = qubits[0]
1✔
623
                    qubit1 = qubits[1]
1✔
624
                    gate = TWO_QUBIT_GATES[operation.name]
1✔
625
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
626
                elif operation.name in THREE_QUBIT_GATES:
1✔
627
                    qargs = operation.qubits
1✔
628
                    qubits = [qubit_indices[qubit] for qubit in qargs]
1✔
629
                    qubit0 = qubits[0]
1✔
630
                    qubit1 = qubits[1]
1✔
631
                    qubit2 = qubits[2]
1✔
632
                    gate = THREE_QUBIT_GATES[operation.name]
1✔
633
                    self._add_unitary(gate, [qubit0, qubit1, qubit2])
1✔
634
                # Check if reset
635
                elif operation.name == "reset":
1✔
636
                    qargs = operation.qubits
1✔
637
                    qubits = [qubit_indices[qubit] for qubit in qargs]
1✔
638
                    qubit = qubits[0]
1✔
639
                    self._add_reset(qubit)
1✔
640
                # Check if barrier
641
                elif operation.name == "barrier":
1✔
642
                    pass
1✔
643
                # Check if measure
644
                elif operation.name == "measure":
1✔
645
                    qargs = operation.qubits
1✔
646
                    qubit = [qubit_indices[qubit] for qubit in qargs][0]
1✔
647
                    cargs = operation.clbits
1✔
648
                    cmembit = [clbit_indices[clbit] for clbit in cargs][0]
1✔
649
                    cregbit = (
1✔
650
                        [clbit_indices[clbit] for clbit in cargs][0]
651
                        if is_conditional_experiment
652
                        else None
653
                    )
654

655
                    if self._sample_measure:
1✔
656
                        # If sampling measurements record the qubit and cmembit
657
                        # for this measurement for later sampling
658
                        measure_sample_ops.append((qubit, cmembit))
1✔
659
                    else:
660
                        # If not sampling perform measurement as normal
661
                        self._add_measure(qubit, cmembit, cregbit)
1✔
662
                else:
UNCOV
663
                    backend = self.name
×
664
                    err_msg = '{0} encountered unrecognized operation "{1}"'
×
665
                    raise BasicProviderError(err_msg.format(backend, operation.name))
×
666

667
            # Add final creg data to memory list
668
            if self._number_of_cmembits > 0:
1✔
669
                if self._sample_measure:
1✔
670
                    # If sampling we generate all shot samples from the final statevector
671
                    memory = self._add_sample_measure(measure_sample_ops, self._shots)
1✔
672
                else:
673
                    # Turn classical_memory (int) into bit string and pad zero for unused cmembits
674
                    outcome = bin(self._classical_memory)[2:]
1✔
675
                    memory.append(hex(int(outcome, 2)))
1✔
676

677
        # Add data
678
        data = {"counts": dict(Counter(memory))}
1✔
679
        # Optionally add memory list
680
        if self._memory:
1✔
681
            data["memory"] = memory
1✔
682
        end = time.time()
1✔
683

684
        header = {
1✔
685
            "qubit_labels": qubit_labels,
686
            "n_qubits": circuit.num_qubits,
687
            "qreg_sizes": qreg_sizes,
688
            "creg_sizes": creg_sizes,
689
            "clbit_labels": clbit_labels,
690
            "memory_slots": memory_slots,
691
            "name": circuit.name,
692
            "global_phase": circuit.global_phase,
693
            "metadata": circuit.metadata,
694
        }
695

696
        return {
1✔
697
            "name": circuit.name,
698
            "seed_simulator": seed_simulator,
699
            "shots": self._shots,
700
            "data": data,
701
            "status": "DONE",
702
            "success": True,
703
            "header": header,
704
            "time_taken": (end - start),
705
        }
706

707
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
708
        """Semantic validations of the input."""
709
        max_qubits = self.MAX_QUBITS_MEMORY
1✔
710

711
        for circuit in run_input:
1✔
712
            if circuit.num_qubits > max_qubits:
1✔
713
                raise BasicProviderError(
1✔
714
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
715
                    f'for "{self.name}".'
716
                )
717
            name = circuit.name
1✔
718
            if len(circuit.cregs) == 0:
1✔
719
                logger.warning(
1✔
720
                    'No classical registers in circuit "%s", counts will be empty.', name
721
                )
722
            elif "measure" not in [op.name for op in circuit.data]:
1✔
723
                logger.warning(
1✔
724
                    'No measurements in circuit "%s", classical register will remain all zeros.',
725
                    name,
726
                )
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