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

Qiskit / qiskit / 13008522457

28 Jan 2025 10:33AM UTC coverage: 88.955% (+0.005%) from 88.95%
13008522457

Pull #13743

github

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

62 of 64 new or added lines in 1 file covered. (96.88%)

291 existing lines in 16 files now uncovered.

79636 of 89524 relevant lines covered (88.95%)

350533.73 hits per line

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

93.94
/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
        # Internal simulator variables
104
        self._local_random = None
1✔
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._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
        self._sample_measure = False
1✔
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

330
        Args:
331
            qubit: the qubit being rest
332

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

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

360
    def _set_options(self, backend_options: dict | None = None) -> None:
1✔
361
        """Set the backend options for all circuits"""
362
        # Reset default options
363
        self._initial_statevector = self.options.get("initial_statevector")
1✔
364
        if "backend_options" in backend_options and backend_options["backend_options"]:
1✔
UNCOV
365
            backend_options = backend_options["backend_options"]
×
366

367
        # Check for custom initial statevector in backend_options first,
368
        # then config second
369
        if getattr(backend_options, "initial_statevector", None) is not None:
1✔
UNCOV
370
            self._initial_statevector = np.array(
×
371
                backend_options["initial_statevector"], dtype=complex
372
            )
373
        if self._initial_statevector is not None:
1✔
374
            # Check the initial statevector is normalized
375
            norm = np.linalg.norm(self._initial_statevector)
×
376
            if round(norm, 12) != 1:
×
NEW
377
                raise BasicProviderError(f"Initial statevector is not normalized: norm {norm} != 1")
×
378
        if "shots" in backend_options:
1✔
379
            self._shots = backend_options["shots"]
1✔
380
        if "seed_simulator" in backend_options:
1✔
381
            seed_simulator = backend_options["seed_simulator"]
1✔
382
        elif getattr(self.options, "seed_simulator", None) is not None:
1✔
NEW
383
            seed_simulator = self.options["seed_simulator"]
×
384
        else:
385
            # For compatibility on Windows force dyte to be int32
386
            # and set the maximum value to be (2 ** 31) - 1
387
            seed_simulator = np.random.randint(2147483647, dtype="int32")
1✔
388
        self._seed_simulator = seed_simulator
1✔
389
        self._local_random = np.random.default_rng(seed=seed_simulator)
1✔
390

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

402
    def _validate_measure_sampling(self, circuit: QuantumCircuit) -> None:
1✔
403
        """Determine if measure sampling is allowed for an experiment"""
404
        measure_flag = False
1✔
405
        # If shots=1 we should disable measure sampling.
406
        # This is also required for statevector simulator to return the
407
        # correct final statevector without silently dropping final measurements.
408
        if self._shots > 1:
1✔
409
            for instruction in circuit.data:
1✔
410
                # If circuit contains reset operations we cannot sample
411
                if instruction.name == "reset":
1✔
412
                    measure_flag = False
1✔
413
                # If circuit contains a measure option then we can
414
                # sample only if all following operations are measures
415
                if measure_flag:
1✔
416
                    # If we find a non-measure instruction
417
                    # we cannot do measure sampling
418
                    if instruction.name not in ["measure", "barrier", "id", "u0"]:
1✔
419
                        measure_flag = False
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], **backend_options
426
    ) -> BasicProviderJob:
427
        """Run on the backend.
428

429
        Args:
430
            run_input: payload of the experiment
431
            backend_options: backend options
432

433
        Returns:
434
            BasicProviderJob: derived from BaseJob
435

436
        Additional Information:
437
            backend_options: Is a dict of options for the backend. It may contain:
438
                * "initial_statevector": vector_like
439

440
            The "initial_statevector" option specifies a custom initial
441
            initial statevector for the simulator to be used instead of the all
442
            zero state. This size of this vector must be correct for the number
443
            of qubits in ``run_input`` parameter.
444

445
            Example::
446

447
                backend_options = {
448
                    "initial_statevector": np.array([1, 0, 0, 1j]) / math.sqrt(2),
449
                }
450
        """
451
        out_options = {}
1✔
452
        for key, value in backend_options.items():
1✔
453
            if not hasattr(self.options, key):
1✔
454
                warnings.warn(
1✔
455
                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
456
                )
457
            else:
458
                out_options[key] = value
1✔
459
        self._set_options(backend_options=backend_options)
1✔
460
        job_id = str(uuid.uuid4())
1✔
461
        job = BasicProviderJob(self, job_id, self._run_job(job_id, run_input))
1✔
462
        return job
1✔
463

464
    def _run_job(self, job_id: str, run_input) -> Result:
1✔
465
        """Run circuits in job.
466

467
        Args:
468
            job_id: unique id for the job.
469
            run_input: circuits to be run.
470

471
        Returns:
472
            Result object
473
        """
474
        if isinstance(run_input, QuantumCircuit):
1✔
475
            run_input = [run_input]
1✔
476

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

495
        return Result.from_dict(result)
1✔
496

497
    def _run_circuit(self, circuit) -> dict:
1✔
498
        """Simulate a single circuit run.
499

500
        Args:
501
            circuit: circuit to be run.
502

503
        Returns:
504
            Dictionary with run result.
505
        """
506
        start = time.time()
1✔
507

508
        self._number_of_qubits = circuit.num_qubits
1✔
509
        self._number_of_cmembits = circuit.num_clbits
1✔
510
        self._statevector = 0
1✔
511
        self._classical_memory = 0
1✔
512

513
        # Validate the dimension of initial statevector if set
514
        self._validate_initial_statevector()
1✔
515

516
        # Check if measure sampling is supported for current circuit
517
        self._validate_measure_sampling(circuit)
1✔
518

519
        # List of final counts for all shots
520
        memory = []
1✔
521
        # Check if we can sample measurements, if so we only perform 1 shot
522
        # and sample all outcomes from the final state vector
523
        if self._sample_measure:
1✔
524
            shots = 1
1✔
525
            # Store (qubit, cmembit) pairs for all measure ops in circuit to
526
            # be sampled
527
            measure_sample_ops = []
1✔
528
        else:
529
            shots = self._shots
1✔
530

531
        for _ in range(shots):
1✔
532
            self._initialize_statevector()
1✔
533
            # apply global_phase
534
            self._statevector *= np.exp(1j * circuit.global_phase)
1✔
535
            # Initialize classical memory to all 0
536
            self._classical_memory = 0
1✔
537

538
            for operation in circuit.data:
1✔
539
                if operation.name == "unitary":
1✔
540
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
541
                    gate = operation.params[0]
1✔
542
                    self._add_unitary(gate, qubits)
1✔
543
                elif operation.name in ("id", "u0", "delay"):
1✔
544
                    pass
1✔
545
                elif operation.name == "global_phase":
1✔
546
                    params = getattr(operation, "params", None)
1✔
547
                    gate = GlobalPhaseGate(*params).to_matrix()
1✔
548
                    self._add_unitary(gate, [])
1✔
549
                # Check if single qubit gate
550
                elif operation.name in SINGLE_QUBIT_GATES:
1✔
551
                    params = getattr(operation, "params", None)
1✔
552
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
553
                    gate = single_gate_matrix(operation.name, params)
1✔
554
                    self._add_unitary(gate, [qubit])
1✔
555
                elif operation.name in TWO_QUBIT_GATES_WITH_PARAMETERS:
1✔
556
                    params = getattr(operation, "params", None)
1✔
557
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
558
                    qubit0 = qubits[0]
1✔
559
                    qubit1 = qubits[1]
1✔
560
                    gate = TWO_QUBIT_GATES_WITH_PARAMETERS[operation.name](*params).to_matrix()
1✔
561
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
562
                elif operation.name in ("id", "u0"):
1✔
563
                    pass
×
564
                elif operation.name in TWO_QUBIT_GATES:
1✔
565
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
566
                    qubit0 = qubits[0]
1✔
567
                    qubit1 = qubits[1]
1✔
568
                    gate = TWO_QUBIT_GATES[operation.name]
1✔
569
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
570
                elif operation.name in THREE_QUBIT_GATES:
1✔
571
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
572
                    qubit0 = qubits[0]
1✔
573
                    qubit1 = qubits[1]
1✔
574
                    qubit2 = qubits[2]
1✔
575
                    gate = THREE_QUBIT_GATES[operation.name]
1✔
576
                    self._add_unitary(gate, [qubit0, qubit1, qubit2])
1✔
577
                # Check if reset
578
                elif operation.name == "reset":
1✔
579
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
580
                    qubit = qubits[0]
1✔
581
                    self._add_reset(qubit)
1✔
582
                # Check if barrier
583
                elif operation.name == "barrier":
1✔
584
                    pass
1✔
585
                # Check if measure
586
                elif operation.name == "measure":
1✔
587
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
588
                    cmembit = [circuit.find_bit(bit).index for bit in operation.clbits][0]
1✔
589
                    if self._sample_measure:
1✔
590
                        # If sampling measurements record the qubit and cmembit
591
                        # for this measurement for later sampling
592
                        measure_sample_ops.append((qubit, cmembit))
1✔
593
                    else:
594
                        # If not sampling perform measurement as normal
595
                        self._add_measure(qubit, cmembit)
1✔
596
                else:
597
                    backend = self.name
×
598
                    err_msg = '{0} encountered unrecognized operation "{1}"'
×
599
                    raise BasicProviderError(err_msg.format(backend, operation.name))
×
600

601
            # Add final creg data to memory list
602
            if self._number_of_cmembits > 0:
1✔
603
                if self._sample_measure:
1✔
604
                    # If sampling we generate all shot samples from the final statevector
605
                    memory = self._add_sample_measure(measure_sample_ops, self._shots)
1✔
606
                else:
607
                    # Turn classical_memory (int) into bit string and pad zero for unused cmembits
608
                    outcome = bin(self._classical_memory)[2:]
1✔
609
                    memory.append(hex(int(outcome, 2)))
1✔
610

611
        # Add counts to result data
612
        data = {"counts": dict(Counter(memory))}
1✔
613
        # Optionally, add memory list to result data
614
        if self._memory:
1✔
615
            data["memory"] = memory
1✔
616
        end = time.time()
1✔
617

618
        # Define header to be used by Result class to interpret counts
619
        header = {
1✔
620
            "name": circuit.name,
621
            "n_qubits": circuit.num_qubits,
622
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
623
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
624
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
625
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
626
            "memory_slots": circuit.num_clbits,
627
            "global_phase": circuit.global_phase,
628
            "metadata": circuit.metadata if circuit.metadata is not None else {},
629
        }
630

631
        # Return result dictionary
632
        return {
1✔
633
            "name": circuit.name,
634
            "seed_simulator": self._seed_simulator,
635
            "shots": self._shots,
636
            "data": data,
637
            "status": "DONE",
638
            "success": True,
639
            "header": header,
640
            "time_taken": (end - start),
641
        }
642

643
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
644
        """Semantic validations of the input."""
645
        max_qubits = self.MAX_QUBITS_MEMORY
1✔
646

647
        for circuit in run_input:
1✔
648
            if circuit.num_qubits > max_qubits:
1✔
649
                raise BasicProviderError(
1✔
650
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
651
                    f'for "{self.name}".'
652
                )
653
            name = circuit.name
1✔
654
            if len(circuit.cregs) == 0:
1✔
655
                logger.warning(
1✔
656
                    'No classical registers in circuit "%s", counts will be empty.', name
657
                )
658
            elif "measure" not in [op.name for op in circuit.data]:
1✔
659
                logger.warning(
1✔
660
                    'No measurements in circuit "%s", classical register will remain all zeros.',
661
                    name,
662
                )
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