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

Qiskit / qiskit / 20333466808

18 Dec 2025 10:12AM UTC coverage: 88.329% (+0.03%) from 88.297%
20333466808

Pull #15350

github

web-flow
Merge f70ee4025 into d19e714ca
Pull Request #15350: chore: update comments to fix translation problems in documentation

96735 of 109517 relevant lines covered (88.33%)

1206183.48 hits per line

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

96.77
/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
# The simulator supports:
33
# - Up to 24 qubits for statevector simulation (memory scales exponentially)
34
# - Up to 2048 qubits for Clifford/Stabilizer simulation (memory scales quadratically)
35

36

37
"""
38

39
from __future__ import annotations
1✔
40

41
import math
1✔
42
import uuid
1✔
43
import time
1✔
44
import logging
1✔
45
import warnings
1✔
46
from collections import Counter
1✔
47

48
import numpy as np
1✔
49
from qiskit.circuit import QuantumCircuit
1✔
50
from qiskit.circuit.library import UnitaryGate
1✔
51
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping, GlobalPhaseGate
1✔
52
from qiskit.providers.backend import BackendV2
1✔
53
from qiskit.providers.options import Options
1✔
54
from qiskit.result import Result
1✔
55
from qiskit.transpiler import Target
1✔
56

57

58
from qiskit.quantum_info import Clifford, StabilizerState
1✔
59
from qiskit.exceptions import QiskitError
1✔
60

61

62
from .basic_provider_job import BasicProviderJob
1✔
63
from .basic_provider_tools import single_gate_matrix
1✔
64
from .basic_provider_tools import (
1✔
65
    SINGLE_QUBIT_GATES,
66
    TWO_QUBIT_GATES,
67
    TWO_QUBIT_GATES_WITH_PARAMETERS,
68
    THREE_QUBIT_GATES,
69
)
70
from .basic_provider_tools import einsum_vecmul_index
1✔
71
from .exceptions import BasicProviderError
1✔
72

73
logger = logging.getLogger(__name__)
1✔
74

75

76
class BasicSimulator(BackendV2):
1✔
77
    """Python implementation of a basic (non-efficient) quantum simulator."""
78

79
    # Formerly calculated as `int(log2(local_hardware_info()["memory"]*(1024**3)/16))`.
80
    # After the removal of `local_hardware_info()`, Statevector simulation is limited to 24 qubits.
81
    # Clifford/Stabilizer simulation can use 2048 qubits.
82
    # which matches the ~268 MB of required memory.
83

84
    MAX_QUBITS_STATEVECTOR = 24  # For statevector
1✔
85
    MAX_QUBITS_CLIFFORD = 2048  # For Clifford/Stabilizer simulation
1✔
86

87
    def __init__(
1✔
88
        self,
89
        provider=None,
90
        target: Target | None = None,
91
        **fields,
92
    ) -> None:
93
        """
94
        Args:
95
            provider: An optional backwards reference to the provider object that the backend
96
                is from.
97
            target: An optional target to configure the simulator.
98
            fields: kwargs for the values to use to override the default
99
                options.
100

101
        Raises:
102
            AttributeError: If a field is specified that's outside the backend's
103
                options.
104
        """
105

106
        super().__init__(
1✔
107
            provider=provider,
108
            name="basic_simulator",
109
            description="A Python simulator for basic quantum experiments",
110
            backend_version="0.1",
111
            **fields,
112
        )
113

114
        self._target = target
1✔
115

116
        # Internal simulator variables
117
        self._classical_memory = 0
1✔
118
        self._statevector = 0
1✔
119
        self._number_of_cmembits = 0
1✔
120
        self._number_of_qubits = 0
1✔
121
        self._local_rng = None
1✔
122
        self._sample_measure = False
1✔
123
        self._shots = self.options.get("shots")
1✔
124
        self._memory = self.options.get("memory")
1✔
125
        self._initial_statevector = self.options.get("initial_statevector")
1✔
126
        self._seed_simulator = self.options.get("seed_simulator")
1✔
127
        self._use_clifford_optimization = self.options.get(
1✔
128
            "use_clifford_optimization"
129
        )  # ADDED FOR CLIFFORD
130

131
    @property
1✔
132
    def max_circuits(self) -> None:
1✔
133
        return None
1✔
134

135
    @property
1✔
136
    def target(self) -> Target:
1✔
137
        if not self._target:
1✔
138
            self._target = self._build_basic_target()
1✔
139
        return self._target
1✔
140

141
    def _build_basic_target(self) -> Target:
1✔
142
        """Helper method that returns a minimal target with a basis gate set but
143
        no coupling map or instruction properties.
144

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

227
    @classmethod
1✔
228
    def _default_options(cls) -> Options:
1✔
229
        return Options(
1✔
230
            shots=1024,
231
            memory=True,
232
            initial_statevector=None,
233
            seed_simulator=None,
234
            use_clifford_optimization=False,  # ADDED FOR CLIFFORD
235
        )
236

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

240
        Args:
241
            gate (matrix_like): an N-qubit unitary matrix
242
            qubits (list): the list of N-qubits.
243
        """
244
        # Get the number of qubits
245
        num_qubits = len(qubits)
1✔
246
        # Compute einsum index string for 1-qubit matrix multiplication
247
        indexes = einsum_vecmul_index(qubits, self._number_of_qubits)
1✔
248
        # Convert to complex rank-2N tensor
249
        gate_tensor = np.reshape(np.array(gate, dtype=complex), num_qubits * [2, 2])
1✔
250
        # Apply matrix multiplication
251
        self._statevector = np.einsum(
1✔
252
            indexes, gate_tensor, self._statevector, dtype=complex, casting="no"
253
        )
254

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

258
        Args:
259
            qubit: index indicating the qubit to measure
260

261
        Return:
262
            pair (outcome, probability) where outcome is '0' or '1' and
263
            probability is the probability of the returned outcome.
264
        """
265
        # Axis for numpy.sum to compute probabilities
266
        axis = list(range(self._number_of_qubits))
1✔
267
        axis.remove(self._number_of_qubits - 1 - qubit)
1✔
268
        probabilities = np.sum(np.abs(self._statevector) ** 2, axis=tuple(axis))
1✔
269
        # Compute einsum index string for 1-qubit matrix multiplication
270
        random_number = self._local_rng.random()
1✔
271
        if random_number < probabilities[0]:
1✔
272
            return "0", probabilities[0]
1✔
273
        # Else outcome was '1'
274
        return "1", probabilities[1]
1✔
275

276
    def _add_sample_measure(
1✔
277
        self, measure_params: list[tuple[int, int]], num_samples: int
278
    ) -> list[hex]:
279
        """Generate memory samples from current statevector.
280

281
        Args:
282
            measure_params: List of (qubit, cmembit) values for
283
                                   measure instructions to sample.
284
            num_samples: The number of memory samples to generate.
285

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

321
    def _add_measure(self, qubit: int, cmembit: int) -> None:
1✔
322
        """Apply a measure instruction to a qubit.
323

324
        Args:
325
            qubit: index of the qubit measured.
326
            cmembit: index of the classical memory bit to store outcome in.
327
        """
328
        # get measure outcome
329
        outcome, probability = self._get_measure_outcome(qubit)
1✔
330
        # update classical state
331
        membit = 1 << cmembit
1✔
332
        self._classical_memory = (self._classical_memory & (~membit)) | (int(outcome) << cmembit)
1✔
333

334
        # update quantum state
335
        if outcome == "0":
1✔
336
            update_diag = [[1 / math.sqrt(probability), 0], [0, 0]]
1✔
337
        else:
338
            update_diag = [[0, 0], [0, 1 / math.sqrt(probability)]]
1✔
339
        # update classical state
340
        self._add_unitary(update_diag, [qubit])
1✔
341

342
    def _add_reset(self, qubit: int) -> None:
1✔
343
        """Apply a reset instruction to a qubit.
344

345
        Args:
346
            qubit: the qubit being rest
347

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

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

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

378
        # Reset internal variables every time "run" is called using saved options
379
        self._shots = self.options.get("shots")
1✔
380
        self._memory = self.options.get("memory")
1✔
381
        self._initial_statevector = self.options.get("initial_statevector")
1✔
382
        self._seed_simulator = self.options.get("seed_simulator")
1✔
383
        self._use_clifford_optimization = self.options.get(
1✔
384
            "use_clifford_optimization"
385
        )  # ADDED FOR CLIFFORD
386
        # Apply custom run options
387
        if run_options.get("initial_statevector", None) is not None:
1✔
388
            self._initial_statevector = np.array(run_options["initial_statevector"], dtype=complex)
1✔
389
        if self._initial_statevector is not None:
1✔
390
            # Check the initial statevector is normalized
391
            norm = np.linalg.norm(self._initial_statevector)
1✔
392
            if round(norm, 12) != 1:
1✔
393
                raise BasicProviderError(f"Initial statevector is not normalized: norm {norm} != 1")
×
394
        if "shots" in run_options:
1✔
395
            self._shots = run_options["shots"]
1✔
396
        if "seed_simulator" in run_options:
1✔
397
            self._seed_simulator = run_options["seed_simulator"]
1✔
398
        elif self._seed_simulator is None:
1✔
399
            # For compatibility on Windows force dtype to be int32
400
            # and set the maximum value to be (2 ** 31) - 1
401
            self._seed_simulator = np.random.randint(2147483647, dtype="int32")
1✔
402
        if "memory" in run_options:
1✔
403
            self._memory = run_options["memory"]
1✔
404

405
        if "use_clifford_optimization" in run_options:
1✔
406
            self._use_clifford_optimization = run_options[
1✔
407
                "use_clifford_optimization"
408
            ]  # ADDED FOR CLIFFORD
409
        # Set seed for local random number gen.
410
        self._local_rng = np.random.default_rng(seed=self._seed_simulator)
1✔
411

412
    def _initialize_statevector(self) -> None:
1✔
413
        """Set the initial statevector for simulation"""
414
        if self._initial_statevector is None:
1✔
415
            # Set to default state of all qubits in |0>
416
            self._statevector = np.zeros(2**self._number_of_qubits, dtype=complex)
1✔
417
            self._statevector[0] = 1
1✔
418
        else:
419
            self._statevector = self._initial_statevector.copy()
1✔
420
        # Reshape to rank-N tensor
421
        self._statevector = np.reshape(self._statevector, self._number_of_qubits * [2])
1✔
422

423
    def _validate_measure_sampling(self, circuit: QuantumCircuit) -> None:
1✔
424
        """Determine if measure sampling is allowed for an experiment"""
425
        measure_flag = False
1✔
426
        # If shots=1 we should disable measure sampling.
427
        # This is also required for statevector simulator to return the
428
        # correct final statevector without silently dropping final measurements.
429
        if self._shots > 1:
1✔
430
            for instruction in circuit.data:
1✔
431
                # If circuit contains reset operations we cannot sample
432
                if instruction.name == "reset":
1✔
433
                    self._sample_measure = False
1✔
434
                    return
1✔
435
                # If circuit contains a measure option then we can
436
                # sample only if all following operations are measures
437
                if measure_flag:
1✔
438
                    # If we find a non-measure instruction
439
                    # we cannot do measure sampling
440
                    if instruction.name not in ["measure", "barrier", "id", "u0"]:
1✔
441
                        self._sample_measure = False
×
442
                        return
×
443
                elif instruction.name == "measure":
1✔
444
                    measure_flag = True
1✔
445
        self._sample_measure = measure_flag
1✔
446

447
    def run(
1✔
448
        self, run_input: QuantumCircuit | list[QuantumCircuit], **run_options
449
    ) -> BasicProviderJob:
450
        """Run on the backend.
451

452
        Args:
453
            run_input (QuantumCircuit or list): the QuantumCircuit (or list
454
                of QuantumCircuit objects) to run
455
            run_options (kwargs): additional runtime backend options
456

457
        Returns:
458
            BasicProviderJob: derived from BaseJob
459

460
        Additional Information:
461
            * kwarg options specified in ``run_options`` will temporarily override
462
              any set options of the same name for the current run. These may include:
463

464
                * "initial_statevector": vector-like. The "initial_statevector"
465
                  option specifies a custom initial statevector to be used instead
466
                  of the all-zero state. The size of this vector must correspond to
467
                  the number of qubits in the ``run_input`` argument.
468

469
                * "seed_simulator": int. This is the internal seed for sample
470
                  generation.
471

472
                * "shots": int. Number of shots used in the simulation.
473

474
                * "memory": bool. If True, the result will contain the results
475
                  of every individual shot simulation.
476

477
                * "use_clifford_optimization": bool. If True, enables Clifford
478
                  circuit optimization using stabilizer formalism. Default: False.
479

480
            Example::
481

482
                backend.run(
483
                    circuit_2q,
484
                    initial_statevector = np.array([1, 0, 0, 1j]) / math.sqrt(2)
485
                )
486
        """
487
        out_options = {}
1✔
488
        for key, value in run_options.items():
1✔
489
            if not hasattr(self.options, key):
1✔
490
                warnings.warn(
×
491
                    f"Option {key} is not used by this backend", UserWarning, stacklevel=2
492
                )
493
            else:
494
                out_options[key] = value
1✔
495
        self._set_run_options(run_options=run_options)
1✔
496
        job_id = str(uuid.uuid4())
1✔
497
        job = BasicProviderJob(self, job_id, self._run_job(job_id, run_input))
1✔
498
        return job
1✔
499

500
    def _run_job(self, job_id: str, run_input) -> Result:
1✔
501
        """Run circuits in run_input.
502

503
        Args:
504
            job_id: unique id for the job.
505
            run_input: circuits to be run.
506

507
        Returns:
508
            Result object
509
        """
510
        if isinstance(run_input, QuantumCircuit):
1✔
511
            run_input = [run_input]
1✔
512

513
        self._validate(run_input)
1✔
514
        result_list = []
1✔
515
        start = time.time()
1✔
516
        for circuit in run_input:
1✔
517
            result_list.append(self._run_circuit(circuit))
1✔
518
        end = time.time()
1✔
519
        result = {
1✔
520
            "backend_name": self.name,
521
            "backend_version": self.backend_version,
522
            "job_id": job_id,
523
            "results": result_list,
524
            "status": "COMPLETED",
525
            "success": True,
526
            "time_taken": (end - start),
527
        }
528

529
        return Result.from_dict(result)
1✔
530

531
    def _is_clifford_circuit(self, circuit: QuantumCircuit):
1✔
532
        """Check if circuit is Clifford and return Clifford object or None."""
533
        try:
1✔
534
            circ_no_meas = circuit.remove_final_measurements(inplace=False)
1✔
535
            return Clifford(circ_no_meas)
1✔
536
        except QiskitError:
1✔
537
            return None
1✔
538

539
    def _run_clifford_circuit(self, circuit: QuantumCircuit, clifford_obj) -> dict:
1✔
540
        """Simulate a Clifford circuit using StabilizerState.
541

542
        This method provides efficient simulation for Clifford circuits
543
        by using the stabilizer formalism instead of full statevector.
544

545
        Args:
546
            circuit: Clifford circuit to simulate
547
            clifford_obj: Pre-computed Clifford object representing the circuit
548

549
        Returns:
550
            Result dictionary matching _run_circuit format
551
        """
552

553
        start = time.time()
1✔
554

555
        # Find measurement operations
556
        measure_ops = []
1✔
557
        for operation in circuit.data:
1✔
558
            if operation.operation.name == "measure":
1✔
559
                qubit = circuit.find_bit(operation.qubits[0]).index
1✔
560
                clbit = circuit.find_bit(operation.clbits[0]).index
1✔
561
                measure_ops.append((qubit, clbit))
1✔
562

563
        # Sample measurements
564
        memory = []
1✔
565
        if measure_ops:
1✔
566
            # Create StabilizerState once
567
            stab_state = StabilizerState(clifford_obj, validate=False)
1✔
568

569
            # Set seed if provided
570
            if self._seed_simulator is not None:
1✔
571
                stab_state.seed(self._seed_simulator)
1✔
572

573
            # Sample ALL shots at once (much faster than per-shot loop!)
574
            samples = stab_state.sample_memory(self._shots)
1✔
575

576
            # Process each sample
577
            for sample in samples:
1✔
578
                # Map measured qubits to classical bits
579
                classical_memory = 0
1✔
580
                for qubit, clbit in measure_ops:
1✔
581
                    bit_val = int(sample[-(qubit + 1)])
1✔
582
                    membit = 1 << clbit
1✔
583
                    classical_memory = (classical_memory & (~membit)) | (bit_val << clbit)
1✔
584

585
                # Convert to hex format
586
                outcome = bin(classical_memory)[2:]
1✔
587
                memory.append(hex(int(outcome, 2)))
1✔
588

589
        # Build result data
590
        data = {"counts": dict(Counter(memory))}
1✔
591
        if self._memory:
1✔
592
            data["memory"] = memory
1✔
593

594
        end = time.time()
1✔
595

596
        # Build header
597
        header = {
1✔
598
            "name": circuit.name,
599
            "n_qubits": circuit.num_qubits,
600
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
601
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
602
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
603
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
604
            "memory_slots": circuit.num_clbits,
605
            "global_phase": circuit.global_phase,
606
            "metadata": circuit.metadata if circuit.metadata is not None else {},
607
        }
608

609
        return {
1✔
610
            "name": circuit.name,
611
            "seed_simulator": self._seed_simulator,
612
            "shots": self._shots,
613
            "data": data,
614
            "status": "DONE",
615
            "success": True,
616
            "header": header,
617
            "time_taken": (end - start),
618
        }
619

620
    def _run_circuit(self, circuit) -> dict:
1✔
621
        """Simulate a single circuit run.
622

623
        Args:
624
            circuit: circuit to be run.
625

626
        Returns:
627
             A result dictionary which looks something like::
628
                {
629
                "name": name of this experiment
630
                "seed": random seed used for simulation
631
                "shots": number of shots used in the simulation
632
                "header": {
633
                    "name": "circuit-206",
634
                    "n_qubits": 3,
635
                    "qreg_sizes": [['qr', 3]],
636
                    "creg_sizes": [['cr', 3]],
637
                    "qubit_labels": [['qr', 0], ['qr', 1], ['qr', 2]],
638
                    "clbit_labels": [['cr', 0], ['cr', 1], ['cr', 2]],
639
                    "memory_slots": 3,
640
                    "global_phase": 0.0,
641
                    "metadata": {},
642
                    }
643
                "data":
644
                    {
645
                    "counts": {'0x9: 5, ...},
646
                    "memory": ['0x9', '0xF', '0x1D', ..., '0x9']
647
                    },
648
                "status": status string for the simulation
649
                "success": boolean
650
                "time_taken": simulation time of this single experiment
651
                }
652
        Raises:
653
            BasicProviderError: if an error occurred.
654
        """
655

656
        # Set these BEFORE the Clifford check
657
        self._number_of_qubits = circuit.num_qubits
1✔
658
        self._number_of_cmembits = circuit.num_clbits
1✔
659

660
        # Check if circuit is Clifford and use optimized simulation
661
        # Check initial_statevector first (cheaper)
662
        if self._initial_statevector is None and self._use_clifford_optimization:
1✔
663
            clifford_obj = self._is_clifford_circuit(circuit)
1✔
664
            if clifford_obj is not None:
1✔
665
                return self._run_clifford_circuit(circuit, clifford_obj)
1✔
666

667
        start = time.time()
1✔
668

669
        self._number_of_qubits = circuit.num_qubits
1✔
670
        self._number_of_cmembits = circuit.num_clbits
1✔
671
        self._statevector = 0
1✔
672
        self._classical_memory = 0
1✔
673

674
        # Validate the dimension of initial statevector if set
675
        self._validate_initial_statevector()
1✔
676

677
        # Check if measure sampling is supported for current circuit
678
        self._validate_measure_sampling(circuit)
1✔
679

680
        # List of final counts for all shots
681
        memory = []
1✔
682
        # Check if we can sample measurements, if so we only perform 1 shot
683
        # and sample all outcomes from the final state vector
684
        if self._sample_measure:
1✔
685
            shots = 1
1✔
686
            # Store (qubit, cmembit) pairs for all measure ops in circuit to
687
            # be sampled
688
            measure_sample_ops = []
1✔
689
        else:
690
            shots = self._shots
1✔
691

692
        for _ in range(shots):
1✔
693
            self._initialize_statevector()
1✔
694
            # apply global_phase
695
            self._statevector *= np.exp(1j * circuit.global_phase)
1✔
696
            # Initialize classical memory to all 0
697
            self._classical_memory = 0
1✔
698

699
            for operation in circuit.data:
1✔
700
                if operation.name == "unitary":
1✔
701
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
702
                    gate = operation.operation.params[0]
1✔
703
                    self._add_unitary(gate, qubits)
1✔
704
                elif operation.name in ("id", "u0", "delay"):
1✔
705
                    pass
1✔
706
                elif operation.name == "global_phase":
1✔
707
                    params = getattr(operation, "params", None)
1✔
708
                    gate = GlobalPhaseGate(*params).to_matrix()
1✔
709
                    self._add_unitary(gate, [])
1✔
710
                # Check if single qubit gate
711
                elif operation.name in SINGLE_QUBIT_GATES:
1✔
712
                    params = getattr(operation, "params", None)
1✔
713
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
714
                    gate = single_gate_matrix(operation.name, params)
1✔
715
                    self._add_unitary(gate, [qubit])
1✔
716
                elif operation.name in TWO_QUBIT_GATES_WITH_PARAMETERS:
1✔
717
                    params = getattr(operation, "params", None)
1✔
718
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
719
                    qubit0 = qubits[0]
1✔
720
                    qubit1 = qubits[1]
1✔
721
                    gate = TWO_QUBIT_GATES_WITH_PARAMETERS[operation.name](*params).to_matrix()
1✔
722
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
723
                elif operation.name in ("id", "u0"):
1✔
724
                    pass
×
725
                elif operation.name in TWO_QUBIT_GATES:
1✔
726
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
727
                    qubit0 = qubits[0]
1✔
728
                    qubit1 = qubits[1]
1✔
729
                    gate = TWO_QUBIT_GATES[operation.name]
1✔
730
                    self._add_unitary(gate, [qubit0, qubit1])
1✔
731
                elif operation.name in THREE_QUBIT_GATES:
1✔
732
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
733
                    qubit0 = qubits[0]
1✔
734
                    qubit1 = qubits[1]
1✔
735
                    qubit2 = qubits[2]
1✔
736
                    gate = THREE_QUBIT_GATES[operation.name]
1✔
737
                    self._add_unitary(gate, [qubit0, qubit1, qubit2])
1✔
738
                # Check if reset
739
                elif operation.name == "reset":
1✔
740
                    qubits = [circuit.find_bit(bit).index for bit in operation.qubits]
1✔
741
                    qubit = qubits[0]
1✔
742
                    self._add_reset(qubit)
1✔
743
                # Check if barrier
744
                elif operation.name == "barrier":
1✔
745
                    pass
1✔
746
                # Check if measure
747
                elif operation.name == "measure":
1✔
748
                    qubit = [circuit.find_bit(bit).index for bit in operation.qubits][0]
1✔
749
                    cmembit = [circuit.find_bit(bit).index for bit in operation.clbits][0]
1✔
750
                    if self._sample_measure:
1✔
751
                        # If sampling measurements record the qubit and cmembit
752
                        # for this measurement for later sampling
753
                        measure_sample_ops.append((qubit, cmembit))
1✔
754
                    else:
755
                        # If not sampling perform measurement as normal
756
                        self._add_measure(qubit, cmembit)
1✔
757
                else:
758
                    backend = self.name
×
759
                    err_msg = '{0} encountered unrecognized operation "{1}"'
×
760
                    raise BasicProviderError(err_msg.format(backend, operation.name))
×
761

762
            # Add final creg data to memory list
763
            if self._number_of_cmembits > 0:
1✔
764
                if self._sample_measure:
1✔
765
                    # If sampling we generate all shot samples from the final statevector
766
                    memory = self._add_sample_measure(measure_sample_ops, self._shots)
1✔
767
                else:
768
                    # Turn classical_memory (int) into bit string and pad zero for unused cmembits
769
                    outcome = bin(self._classical_memory)[2:]
1✔
770
                    memory.append(hex(int(outcome, 2)))
1✔
771

772
        # Add counts to result data
773
        data = {"counts": dict(Counter(memory))}
1✔
774
        # Optionally, add memory list to result data
775
        if self._memory:
1✔
776
            data["memory"] = memory
1✔
777
        end = time.time()
1✔
778

779
        # Define header to be used by Result class to interpret counts
780
        header = {
1✔
781
            "name": circuit.name,
782
            "n_qubits": circuit.num_qubits,
783
            "qreg_sizes": [[qreg.name, qreg.size] for qreg in circuit.qregs],
784
            "creg_sizes": [[creg.name, creg.size] for creg in circuit.cregs],
785
            "qubit_labels": [[qreg.name, j] for qreg in circuit.qregs for j in range(qreg.size)],
786
            "clbit_labels": [[creg.name, j] for creg in circuit.cregs for j in range(creg.size)],
787
            "memory_slots": circuit.num_clbits,
788
            "global_phase": circuit.global_phase,
789
            "metadata": circuit.metadata if circuit.metadata is not None else {},
790
        }
791
        # Return result dictionary
792
        return {
1✔
793
            "name": circuit.name,
794
            "seed_simulator": self._seed_simulator,
795
            "shots": self._shots,
796
            "data": data,
797
            "status": "DONE",
798
            "success": True,
799
            "header": header,
800
            "time_taken": (end - start),
801
        }
802

803
    def _validate(self, run_input: list[QuantumCircuit]) -> None:
1✔
804
        """Semantic validations of the input."""
805
        for circuit in run_input:
1✔
806
            # Check which path: Clifford or Statevector
807
            use_clifford = (
1✔
808
                self._use_clifford_optimization and self._is_clifford_circuit(circuit) is not None
809
            )
810
            if use_clifford:
1✔
811
                max_qubits = self.MAX_QUBITS_CLIFFORD
1✔
812
            else:
813
                max_qubits = self.MAX_QUBITS_STATEVECTOR
1✔
814

815
            if circuit.num_qubits > max_qubits:
1✔
816
                raise BasicProviderError(
1✔
817
                    f"Number of qubits {circuit.num_qubits} is greater than maximum ({max_qubits}) "
818
                    f'for "{self.name}".'
819
                )
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