Skip to content

Commit

Permalink
Add support for target.num_qubits=None in transpiler pipeline. Set nu…
Browse files Browse the repository at this point in the history
…m_qubits in BasicSimulator to None to avoid circuits being transpiled to the simulator's max number of qubits, thus blowing up the size of the computed statevector.

Apply suggestions from Julien's code review

Co-authored-by: Julien Gacon <gaconju@gmail.com>
  • Loading branch information
ElePT and Cryoris committed Jan 12, 2024
1 parent 7ae48fa commit 4f8ba72
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 20 deletions.
2 changes: 1 addition & 1 deletion qiskit/providers/backend_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def convert_to_target(
"switch_case": SwitchCaseOp,
}

in_data = {"num_qubits": configuration.n_qubits}
in_data = {"num_qubits": getattr(configuration, "n_qubits", None)}

# Parse global configuration properties
if hasattr(configuration, "dt"):
Expand Down
2 changes: 1 addition & 1 deletion qiskit/providers/basic_provider/basic_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# that they have been altered from the originals.


"""Provider for test simulator backends, formerly known as `BasicAer`."""
"""Provider for basic simulator backends, formerly known as `BasicAer`."""

from __future__ import annotations

Expand Down
21 changes: 13 additions & 8 deletions qiskit/providers/basic_provider/basic_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
BasicSimulator().run(run_input)
Where the input is a ``QuantumCircuit`` object and the output is a ``BasicProviderJob object``,
Where the input is a ``QuantumCircuit`` object and the output is a ``BasicProviderJob`` object,
which can later be queried for the Result object. The result will contain a 'memory' data
field, which is a result of measurements for each shot.
"""
Expand Down Expand Up @@ -62,10 +62,11 @@


class BasicSimulator(BackendV2):
"""Python implementation of a basic (slow, non-efficient) quantum simulator. This implementation
was originally based on the :class:`.BackendV1` interface, and later migrated to :class:`.BackendV2`.
However, certain legacy methods and properties from :class:`.BackendV1` have been kept for
backwards compatibility purposes, these are:
"""Python implementation of a basic (slow, non-efficient) quantum simulator.
This implementation was originally based on the :class:`.BackendV1` interface, and later
migrated to :class:`.BackendV2`. However, certain legacy methods and properties from
:class:`.BackendV1` have been kept for backwards compatibility purposes, these are:
#. :meth:`.BasicSimulator.configuration`
#. :meth:`.BasicSimulator.properties`
Expand Down Expand Up @@ -148,9 +149,13 @@ def _build_basic_target(self) -> Target:
Returns:
Target: the configured target.
"""
# Set num_qubits to None so that the transpiler doesn't unnecessarily
# resize the circuit to fit a specific (potentially too large)
# number of qubits. The number of qubits in the circuits given to the
# `run` method will determine the size of the simulated statevector.
target = Target(
description="Basic Target",
num_qubits=min(24, self.MAX_QUBITS_MEMORY),
num_qubits=None,
)
basis_gates = ["h", "u", "p", "u1", "u2", "u3", "rz", "sx", "x", "cx", "id", "unitary"]
required = ["measure", "delay", "reset"]
Expand All @@ -172,7 +177,7 @@ def configuration(self) -> BackendConfiguration:
"""Return the simulator backend configuration.
Returns:
BackendConfiguration: the configuration for the backend.
The configuration for the backend.
"""
# The ``target`` input argument overrides the ``configuration`` if provided:
if self._configuration and not self._target:
Expand Down Expand Up @@ -737,7 +742,7 @@ def run_experiment(self, experiment: QasmQobjExperiment) -> dict[str, ...]:
def _validate(self, qobj: QasmQobj) -> None:
"""Semantic validations of the qobj which cannot be done via schemas."""
n_qubits = qobj.config.n_qubits
max_qubits = self.configuration().n_qubits
max_qubits = self.MAX_QUBITS_MEMORY
if n_qubits > max_qubits:
raise BasicProviderError(
f"Number of qubits {n_qubits} is greater than maximum ({max_qubits}) "
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/passes/layout/full_ancilla_allocation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
# (C) Copyright IBM 2017, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -81,7 +81,7 @@ def run(self, dag):

idle_physical_qubits = [q for q in layout_physical_qubits if q not in physical_bits]

if self.target:
if self.target and self.target.num_qubits is not None:
idle_physical_qubits = [
q for q in range(self.target.num_qubits) if q not in physical_bits
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
# (C) Copyright IBM 2017, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -94,7 +94,8 @@ def __init__(self, basis=None, target=None):
self.error_map = self._build_error_map()

def _build_error_map(self):
if self._target is not None:
# include path for when target exists but target.num_qubits is None (BasicSimulator)
if self._target is not None and self._target.num_qubits is not None:
error_map = euler_one_qubit_decomposer.OneQubitGateErrorMap(self._target.num_qubits)
for qubit in range(self._target.num_qubits):
gate_error = {}
Expand All @@ -118,7 +119,8 @@ def _resynthesize_run(self, matrix, qubit=None):
When multiple synthesis options are available, it prefers the one with the lowest
error when the circuit is applied to `qubit`.
"""
if self._target:
# include path for when target exists but target.num_qubits is None (BasicSimulator)
if self._target and self._target.num_qubits is not None:
if qubit is not None:
qubits_tuple = (qubit,)
else:
Expand All @@ -128,9 +130,9 @@ def _resynthesize_run(self, matrix, qubit=None):
else:
available_1q_basis = set(self._target.operation_names_for_qargs(qubits_tuple))
decomposers = _possible_decomposers(available_1q_basis)
self._local_decomposers_cache[qubits_tuple] = decomposers
else:
decomposers = self._global_decomposers

best_synth_circuit = euler_one_qubit_decomposer.unitary_to_gate_sequence(
matrix,
decomposers,
Expand Down
6 changes: 4 additions & 2 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ def __init__(

self._top_level_only = self._basis_gates is None and self._target is None

if not self._top_level_only and self._target is None:
# include path for when target exists but target.num_qubits is None (BasicSimulator)
if not self._top_level_only and (self._target is None or self._target.num_qubits is None):
basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"}
self._device_insts = basic_insts | set(self._basis_gates)

Expand Down Expand Up @@ -314,12 +315,13 @@ def _recursively_handle_op(
controlled_gate_open_ctrl = isinstance(op, ControlledGate) and op._open_ctrl
if not controlled_gate_open_ctrl:
qargs = tuple(qubits) if qubits is not None else None
# include path for when target exists but target.num_qubits is None (BasicSimulator)
inst_supported = (
self._target.instruction_supported(
operation_name=op.name,
qargs=qargs,
)
if self._target is not None
if self._target is not None and self._target.num_qubits is not None
else op.name in self._device_insts
)
if inst_supported or (self._equiv_lib is not None and self._equiv_lib.has_entry(op)):
Expand Down
12 changes: 10 additions & 2 deletions qiskit/transpiler/target.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021
# (C) Copyright IBM 2021, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -702,6 +702,9 @@ def operation_names_for_qargs(self, qargs):
Raises:
KeyError: If ``qargs`` is not in target
"""
# if num_qubits == 0, we will return globally defined operations
if self.num_qubits == 0 or self.num_qubits is None:
qargs = None
if qargs is not None and any(x not in range(0, self.num_qubits) for x in qargs):
raise KeyError(f"{qargs} not in target.")
res = self._qarg_gate_map.get(qargs, set())
Expand Down Expand Up @@ -784,6 +787,9 @@ def check_obj_params(parameters, obj):
return False
return True

# Handle case where num_qubits is None by always checking globally supported operations
if self.num_qubits is None:
qargs = None
# Case a list if passed in by mistake
if qargs is not None:
qargs = tuple(qargs)
Expand Down Expand Up @@ -1502,7 +1508,9 @@ def target_to_backend_properties(target: Target):
}
)
else:
qubit_props: dict[int, Any] = {x: None for x in range(target.num_qubits)}
qubit_props: dict[int, Any] = {}
if target.num_qubits:
qubit_props = {x: None for x in range(target.num_qubits)}
for qargs, props in qargs_list.items():
if qargs is None:
continue
Expand Down

0 comments on commit 4f8ba72

Please sign in to comment.