diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index b0501caccefd..f45155f02806 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -14,15 +14,9 @@ """Circuit transpile function""" import copy -import io -from itertools import cycle import logging -import os -import pickle from time import time -from typing import List, Union, Dict, Callable, Any, Optional, Tuple, Iterable, TypeVar -from multiprocessing.shared_memory import SharedMemory -from multiprocessing.managers import SharedMemoryManager +from typing import List, Union, Dict, Callable, Any, Optional, TypeVar import warnings from qiskit import user_config @@ -32,19 +26,12 @@ from qiskit.providers.backend import Backend from qiskit.providers.models import BackendProperties from qiskit.pulse import Schedule, InstructionScheduleMap -from qiskit.tools import parallel from qiskit.transpiler import Layout, CouplingMap, PropertySet from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.instruction_durations import InstructionDurations, InstructionDurationsType from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig -from qiskit.transpiler.passmanager_config import PassManagerConfig -from qiskit.transpiler.preset_passmanagers import ( - level_0_pass_manager, - level_1_pass_manager, - level_2_pass_manager, - level_3_pass_manager, -) +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.target import Target, target_to_backend_properties @@ -53,7 +40,7 @@ _CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]]) -def transpile( +def transpile( # pylint: disable=too-many-return-statements circuits: _CircuitT, backend: Optional[Backend] = None, basis_gates: Optional[List[str]] = None, @@ -83,16 +70,9 @@ def transpile( ) -> _CircuitT: """Transpile one or more circuits, according to some desired transpilation targets. - .. deprecated:: 0.23.0 - - Previously, all arguments accepted lists of the same length as ``circuits``, - which was used to specialize arguments for circuits at the corresponding - indices. Support for using such argument lists is now deprecated and will - be removed in the 0.25.0 release. If you need to use multiple values for an - argument, you can use multiple :func:`~.transpile` calls (and potentially - :func:`~.parallel_map` to leverage multiprocessing if needed). - - Transpilation is done in parallel using multiprocessing. + Transpilation is potentially done in parallel using multiprocessing when ``circuits`` + is a list with > 1 :class:`~.QuantumCircuit` object depending on the local environment + and configuration. Args: circuits: Circuit(s) to transpile @@ -319,230 +299,8 @@ def callback_func(**kwargs): UserWarning, ) - unique_transpile_args, shared_args = _parse_transpile_args( - circuits, - backend, - basis_gates, - inst_map, - coupling_map, - backend_properties, - initial_layout, - layout_method, - routing_method, - translation_method, - scheduling_method, - instruction_durations, - dt, - approximation_degree, - seed_transpiler, - optimization_level, - callback, - output_name, - timing_constraints, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - target, - hls_config, - init_method, - optimization_method, - ignore_backend_supplied_default_methods, - ) - # Get transpile_args to configure the circuit transpilation job(s) - if "coupling_map" not in shared_args: - cmap_conf = [a["pass_manager_config"]["coupling_map"] for a in unique_transpile_args] - else: - cmap_conf = [shared_args["coupling_map"]] * len(circuits) - _check_circuits_coupling_map(circuits, cmap_conf, backend) - if ( - len(circuits) > 1 - and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE" - and parallel.PARALLEL_DEFAULT - ): - with SharedMemoryManager() as smm: - with io.BytesIO() as buf: - pickle.dump(shared_args, buf) - data = buf.getvalue() - smb = smm.SharedMemory(size=len(data)) - smb.buf[: len(data)] = data[:] - # Transpile circuits in parallel - circuits = parallel.parallel_map( - _transpile_circuit, - list(zip(circuits, cycle([smb.name]), unique_transpile_args)), - ) - else: - output_circuits = [] - for circuit, unique_args in zip(circuits, unique_transpile_args): - transpile_config, pass_manager = _combine_args(shared_args, unique_args) - output_circuits.append( - _serial_transpile_circuit( - circuit, - pass_manager, - transpile_config["callback"], - transpile_config["output_name"], - ) - ) - circuits = output_circuits - end_time = time() - _log_transpile_time(start_time, end_time) - - if arg_circuits_list: - return circuits - else: - return circuits[0] - - -def _check_circuits_coupling_map(circuits, cmap_conf, backend): - # Check circuit width against number of qubits in coupling_map(s) - coupling_maps_list = cmap_conf - for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): - # If coupling_map is not None or num_qubits == 1 - num_qubits = len(circuit.qubits) - max_qubits = None - if isinstance(parsed_coupling_map, CouplingMap): - max_qubits = parsed_coupling_map.size() - - # If coupling_map is None, the limit might be in the backend (like in 1Q devices) - elif backend is not None: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if not backend.configuration().simulator: - max_qubits = backend.configuration().n_qubits - else: - max_qubits = backend.num_qubits - - if max_qubits is not None and (num_qubits > max_qubits): - raise TranspilerError( - f"Number of qubits ({num_qubits}) in {circuit.name} " - f"is greater than maximum ({max_qubits}) in the coupling_map" - ) - - -def _log_transpile_time(start_time, end_time): - log_msg = "Total Transpile Time - %.5f (ms)" % ((end_time - start_time) * 1000) - logger.info(log_msg) - - -def _combine_args(shared_transpiler_args, unique_config): - # Pop optimization_level to exclude it from the kwargs when building a - # PassManagerConfig - level = shared_transpiler_args.pop("optimization_level") - pass_manager_config = shared_transpiler_args - pass_manager_config.update(unique_config.pop("pass_manager_config")) - pass_manager_config = PassManagerConfig(**pass_manager_config) - # restore optimization_level in the input shared dict in case it's used again - # in the same process - shared_transpiler_args["optimization_level"] = level - - transpile_config = unique_config - transpile_config["pass_manager_config"] = pass_manager_config - - # we choose an appropriate one based on desired optimization level - if level == 0: - pass_manager = level_0_pass_manager(pass_manager_config) - elif level == 1: - pass_manager = level_1_pass_manager(pass_manager_config) - elif level == 2: - pass_manager = level_2_pass_manager(pass_manager_config) - elif level == 3: - pass_manager = level_3_pass_manager(pass_manager_config) - else: - raise TranspilerError("optimization_level can range from 0 to 3.") - return transpile_config, pass_manager - - -def _serial_transpile_circuit( - circuit, - pass_manager, - callback, - output_name, -): - result = pass_manager.run(circuit, callback=callback, output_name=output_name) - return result - - -def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, str, Dict]) -> QuantumCircuit: - """Select a PassManager and run a single circuit through it. - Args: - circuit_config_tuple (tuple): - circuit (QuantumCircuit): circuit to transpile - name (str): The name of the shared memory object containing a pickled dict of shared - arguments between parallel works - unique_config (dict): configuration dictating unique arguments for transpile. - Returns: - The transpiled circuit - Raises: - TranspilerError: if transpile_config is not valid or transpilation incurs error - """ - circuit, name, unique_config = circuit_config_tuple - existing_shm = SharedMemory(name=name) - try: - with io.BytesIO(existing_shm.buf) as buf: - shared_transpiler_args = pickle.load(buf) - finally: - existing_shm.close() - - transpile_config, pass_manager = _combine_args(shared_transpiler_args, unique_config) - - result = pass_manager.run( - circuit, callback=transpile_config["callback"], output_name=transpile_config["output_name"] - ) - - return result - - -def _parse_transpile_args( - circuits, - backend, - basis_gates, - inst_map, - coupling_map, - backend_properties, - initial_layout, - layout_method, - routing_method, - translation_method, - scheduling_method, - instruction_durations, - dt, - approximation_degree, - seed_transpiler, - optimization_level, - callback, - output_name, - timing_constraints, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - target, - hls_config, - init_method, - optimization_method, - ignore_backend_supplied_default_methods, -) -> Tuple[List[Dict], Dict]: - """Resolve the various types of args allowed to the transpile() function through - duck typing, overriding args, etc. Refer to the transpile() docstring for details on - what types of inputs are allowed. - - Here the args are resolved by converting them to standard instances, and prioritizing - them in case a transpile option is passed through multiple args (explicitly setting an - arg has more priority than the arg set by backend). - - Returns: - Tuple[list[dict], dict]: a tuple contain a list of unique transpile parameter dicts and - the second element contains a dict of shared transpiler argument across all circuits. - - Raises: - TranspilerError: If instruction_durations are required but not supplied or found. - """ - if initial_layout is not None and layout_method is not None: - warnings.warn("initial_layout provided; layout_method is ignored.", UserWarning) - # Each arg could be single or a list. If list, it must be the same size as - # number of circuits. If single, duplicate to create a list of that size. - num_circuits = len(circuits) - user_input_durations = instruction_durations - user_input_timing_constraints = timing_constraints - user_input_initial_layout = initial_layout + _skip_target = False # If a target is specified have it override any implicit selections from a backend - # but if an argument is explicitly passed use that instead of the target version if target is not None: if coupling_map is None: coupling_map = target.build_coupling_map() @@ -561,128 +319,141 @@ def _parse_transpile_args( # If target is not specified and any hardware constraint object is # manually specified then do not use the target from the backend as # it is invalidated by a custom basis gate list or a custom coupling map - elif basis_gates is None and coupling_map is None: - target = _parse_target(backend, target) + elif basis_gates is not None or coupling_map is not None: + _skip_target = True + else: + target = getattr(backend, "target", None) - basis_gates = _parse_basis_gates(basis_gates, backend) - initial_layout = _parse_initial_layout(initial_layout, circuits) - inst_map = _parse_inst_map(inst_map, backend) + initial_layout = _parse_initial_layout(initial_layout) coupling_map = _parse_coupling_map(coupling_map, backend) - backend_properties = _parse_backend_properties(backend_properties, backend) - backend_num_qubits = _parse_backend_num_qubits(backend, num_circuits) approximation_degree = _parse_approximation_degree(approximation_degree) + output_name = _parse_output_name(output_name, circuits) - callback = _parse_callback(callback, num_circuits) - durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits) - timing_constraints = _parse_timing_constraints(backend, timing_constraints, num_circuits) + inst_map = _parse_inst_map(inst_map, backend) + + _check_circuits_coupling_map(circuits, coupling_map, backend) + + timing_constraints = _parse_timing_constraints(backend, timing_constraints) + if inst_map is not None and inst_map.has_custom_gate() and target is not None: # Do not mutate backend target target = copy.deepcopy(target) target.update_from_instruction_schedule_map(inst_map) - if scheduling_method and any(d is None for d in durations): - raise TranspilerError( - "Transpiling a circuit with a scheduling method" - "requires a backend or instruction_durations." - ) - unique_dict = { - "callback": callback, - "output_name": output_name, - "backend_num_qubits": backend_num_qubits, - } - shared_dict = { - "optimization_level": optimization_level, - "basis_gates": basis_gates, - "init_method": init_method, - "optimization_method": optimization_method, - } - - list_transpile_args = [] + if not ignore_backend_supplied_default_methods: if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"): scheduling_method = backend.get_scheduling_stage_plugin() if translation_method is None and hasattr(backend, "get_translation_stage_plugin"): translation_method = backend.get_translation_stage_plugin() - for key, value in { - "inst_map": inst_map, - "coupling_map": coupling_map, - "backend_properties": backend_properties, - "approximation_degree": approximation_degree, - "initial_layout": initial_layout, - "layout_method": layout_method, - "routing_method": routing_method, - "translation_method": translation_method, - "scheduling_method": scheduling_method, - "instruction_durations": durations, - "timing_constraints": timing_constraints, - "seed_transpiler": seed_transpiler, - "unitary_synthesis_method": unitary_synthesis_method, - "unitary_synthesis_plugin_config": unitary_synthesis_plugin_config, - "target": target, - "hls_config": hls_config, - }.items(): - if isinstance(value, list): - # This giant if-statement detects deprecated use of argument - # broadcasting. For arguments that previously supported broadcast - # but were not themselves of type list (the majority), we simply warn - # when the user provides a list. For the others, special handling is - # required to disambiguate an expected value of type list from - # an attempt to provide multiple values for broadcast. This path is - # super buggy in general (outside of the warning) and since we're - # deprecating this it's better to just remove it than try to clean it up. - # pylint: disable=too-many-boolean-expressions - if ( - key not in {"instruction_durations", "timing_constraints", "initial_layout"} - or ( - key == "initial_layout" - and user_input_initial_layout - and isinstance(user_input_initial_layout, list) - and isinstance(user_input_initial_layout[0], (Layout, dict, list)) - ) - or ( - key == "instruction_durations" - and user_input_durations - and isinstance(user_input_durations, list) - and isinstance(user_input_durations[0], (list, InstructionDurations)) - ) - or ( - key == "timing_constraints" - and user_input_timing_constraints - and isinstance(user_input_timing_constraints, list) + if instruction_durations or dt: + # If durations are provided and there is more than one circuit + # we need to serialize the execution because the full durations + # is dependent on the circuit calibrations which are per circuit + if len(circuits) > 1: + out_circuits = [] + for circuit in circuits: + instruction_durations = _parse_instruction_durations( + backend, instruction_durations, dt, circuit ) - ): - warnings.warn( - f"Passing in a list of arguments for {key} is deprecated and will no longer work " - "starting in the 0.25.0 release.", - DeprecationWarning, - stacklevel=3, + pm = generate_preset_pass_manager( + optimization_level, + backend=backend, + target=target, + basis_gates=basis_gates, + inst_map=inst_map, + coupling_map=coupling_map, + instruction_durations=instruction_durations, + backend_properties=backend_properties, + timing_constraints=timing_constraints, + initial_layout=initial_layout, + layout_method=layout_method, + routing_method=routing_method, + translation_method=translation_method, + scheduling_method=scheduling_method, + approximation_degree=approximation_degree, + seed_transpiler=seed_transpiler, + unitary_synthesis_method=unitary_synthesis_method, + unitary_synthesis_plugin_config=unitary_synthesis_plugin_config, + hls_config=hls_config, + init_method=init_method, + optimization_method=optimization_method, + _skip_target=_skip_target, ) - unique_dict[key] = value + out_circuits.append(pm.run(circuit, callback=callback)) + for name, circ in zip(output_name, out_circuits): + circ.name = name + end_time = time() + _log_transpile_time(start_time, end_time) + return out_circuits else: - shared_dict[key] = value + instruction_durations = _parse_instruction_durations( + backend, instruction_durations, dt, circuits[0] + ) - for kwargs in _zip_dict(unique_dict): - transpile_args = { - "output_name": kwargs.pop("output_name"), - "callback": kwargs.pop("callback"), - "backend_num_qubits": kwargs.pop("backend_num_qubits"), - "pass_manager_config": kwargs, - } - list_transpile_args.append(transpile_args) + pm = generate_preset_pass_manager( + optimization_level, + backend=backend, + target=target, + basis_gates=basis_gates, + inst_map=inst_map, + coupling_map=coupling_map, + instruction_durations=instruction_durations, + backend_properties=backend_properties, + timing_constraints=timing_constraints, + initial_layout=initial_layout, + layout_method=layout_method, + routing_method=routing_method, + translation_method=translation_method, + scheduling_method=scheduling_method, + approximation_degree=approximation_degree, + seed_transpiler=seed_transpiler, + unitary_synthesis_method=unitary_synthesis_method, + unitary_synthesis_plugin_config=unitary_synthesis_plugin_config, + hls_config=hls_config, + init_method=init_method, + optimization_method=optimization_method, + _skip_target=_skip_target, + ) + out_circuits = pm.run(circuits, callback=callback) + for name, circ in zip(output_name, out_circuits): + circ.name = name + end_time = time() + _log_transpile_time(start_time, end_time) - return list_transpile_args, shared_dict + if arg_circuits_list: + return out_circuits + else: + return out_circuits[0] -def _parse_basis_gates(basis_gates, backend): - # try getting basis_gates from user, else backend - if basis_gates is None: +def _check_circuits_coupling_map(circuits, cmap, backend): + # Check circuit width against number of qubits in coupling_map(s) + max_qubits = None + if cmap is not None: + max_qubits = cmap.size() + elif backend is not None: backend_version = getattr(backend, "version", 0) if backend_version <= 1: - if getattr(backend, "configuration", None): - basis_gates = getattr(backend.configuration(), "basis_gates", None) + if not backend.configuration().simulator: + max_qubits = backend.configuration().n_qubits + else: + max_qubits = None else: - basis_gates = backend.operation_names - return basis_gates + max_qubits = backend.num_qubits + for circuit in circuits: + # If coupling_map is not None or num_qubits == 1 + num_qubits = len(circuit.qubits) + if max_qubits is not None and (num_qubits > max_qubits): + raise TranspilerError( + f"Number of qubits ({num_qubits}) in {circuit.name} " + f"is greater than maximum ({max_qubits}) in the coupling_map" + ) + + +def _log_transpile_time(start_time, end_time): + log_msg = "Total Transpile Time - %.5f (ms)" % ((end_time - start_time) * 1000) + logger.info(log_msg) def _parse_inst_map(inst_map, backend): @@ -716,86 +487,28 @@ def _parse_coupling_map(coupling_map, backend): isinstance(i, list) and len(i) == 2 for i in coupling_map ): return CouplingMap(coupling_map) - - coupling_map = [CouplingMap(cm) if isinstance(cm, list) else cm for cm in coupling_map] - return coupling_map - - -def _parse_backend_properties(backend_properties, backend): - # try getting backend_properties from user, else backend - if backend_properties is None: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - if getattr(backend, "properties", None): - backend_properties = backend.properties() - else: - backend_properties = target_to_backend_properties(backend.target) - return backend_properties - - -def _parse_backend_num_qubits(backend, num_circuits): - if backend is None: - return [None] * num_circuits - if not isinstance(backend, list): - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - return [backend.configuration().n_qubits] * num_circuits - else: - return [backend.num_qubits] * num_circuits - backend_num_qubits = [] - for a_backend in backend: - backend_version = getattr(backend, "version", 0) - if backend_version <= 1: - backend_num_qubits.append(a_backend.configuration().n_qubits) - else: - backend_num_qubits.append(a_backend.num_qubits) - return backend_num_qubits + else: + raise TranspilerError( + "Only a single input coupling map can be used with transpile() if you need to " + "target different coupling maps for different circuits you must call transpile() " + "multiple times" + ) -def _parse_initial_layout(initial_layout, circuits): +def _parse_initial_layout(initial_layout): # initial_layout could be None, or a list of ints, e.g. [0, 5, 14] # or a list of tuples/None e.g. [qr[0], None, qr[1]] or a dict e.g. {qr[0]: 0} - def _layout_from_raw(initial_layout, circuit): - if initial_layout is None or isinstance(initial_layout, Layout): - return initial_layout - if isinstance(initial_layout, dict): - return Layout(initial_layout) - # Should be an iterable either of ints or bits/None. - specifier = tuple(initial_layout) - if all(phys is None or isinstance(phys, Qubit) for phys in specifier): - mapping = {phys: virt for phys, virt in enumerate(specifier) if virt is not None} - if len(mapping) != circuit.num_qubits: - raise TranspilerError( - f"'initial_layout' ({len(mapping)}) and circuit ({circuit.num_qubits}) had" - " different numbers of qubits" - ) - else: - if len(specifier) != circuit.num_qubits: - raise TranspilerError( - f"'initial_layout' ({len(specifier)}) and circuit ({circuit.num_qubits}) had" - " different numbers of qubits" - ) - if len(specifier) != len(set(specifier)): - raise TranspilerError(f"'initial_layout' contained duplicate entries: {specifier}") - mapping = {int(phys): virt for phys, virt in zip(specifier, circuit.qubits)} - return Layout(mapping) - - # multiple layouts? - if isinstance(initial_layout, list) and any( - isinstance(i, (list, dict)) for i in initial_layout - ): - initial_layout = [ - _layout_from_raw(lo, circ) if isinstance(lo, (list, dict)) else lo - for lo, circ in zip(initial_layout, circuits) - ] + if initial_layout is None or isinstance(initial_layout, Layout): + return initial_layout + if isinstance(initial_layout, dict): + return Layout(initial_layout) + if all(phys is None or isinstance(phys, Qubit) for phys in initial_layout): + return Layout.from_qubit_list(initial_layout) else: - # even if one layout, but multiple circuits, the layout needs to be adapted for each - initial_layout = [_layout_from_raw(initial_layout, circ) for circ in circuits] - - return initial_layout + return initial_layout -def _parse_instruction_durations(backend, inst_durations, dt, circuits): +def _parse_instruction_durations(backend, inst_durations, dt, circuit): """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided, the backend will be ignored, otherwise, the durations will be populated from the backend. If any circuits have gate calibrations, those calibration durations would @@ -812,51 +525,31 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuits): else: backend_durations = backend.instruction_durations - durations = [] - for circ in circuits: - circ_durations = InstructionDurations() - if not inst_durations: - circ_durations.update(backend_durations, dt or backend_durations.dt) + circ_durations = InstructionDurations() + if not inst_durations: + circ_durations.update(backend_durations, dt or backend_durations.dt) - if circ.calibrations: - cal_durations = [] - for gate, gate_cals in circ.calibrations.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) - circ_durations.update(cal_durations, circ_durations.dt) + if circuit.calibrations: + cal_durations = [] + for gate, gate_cals in circuit.calibrations.items(): + for (qubits, parameters), schedule in gate_cals.items(): + cal_durations.append((gate, qubits, parameters, schedule.duration)) + circ_durations.update(cal_durations, circ_durations.dt) - if inst_durations: - circ_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None)) + if inst_durations: + circ_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None)) - durations.append(circ_durations) - return durations + return circ_durations def _parse_approximation_degree(approximation_degree): if approximation_degree is None: return None - if not isinstance(approximation_degree, list): - if approximation_degree < 0.0 or approximation_degree > 1.0: - raise TranspilerError("Approximation degree must be in [0.0, 1.0]") - else: - if not all(0.0 <= d <= 1.0 for d in approximation_degree if d): - raise TranspilerError("Approximation degree must be in [0.0, 1.0]") + if approximation_degree < 0.0 or approximation_degree > 1.0: + raise TranspilerError("Approximation degree must be in [0.0, 1.0]") return approximation_degree -def _parse_target(backend, target): - backend_target = getattr(backend, "target", None) - if target is None: - target = backend_target - return target - - -def _parse_callback(callback, num_circuits): - if not isinstance(callback, list): - callback = [callback] * num_circuits - return callback - - def _parse_output_name(output_name, circuits): # naming and returning circuits # output_name could be either a string or a list @@ -893,9 +586,9 @@ def _parse_output_name(output_name, circuits): return [circuit.name for circuit in circuits] -def _parse_timing_constraints(backend, timing_constraints, num_circuits): +def _parse_timing_constraints(backend, timing_constraints): if isinstance(timing_constraints, TimingConstraints): - return [timing_constraints] * num_circuits + return timing_constraints if backend is None and timing_constraints is None: timing_constraints = TimingConstraints() else: @@ -907,12 +600,4 @@ def _parse_timing_constraints(backend, timing_constraints, num_circuits): timing_constraints = TimingConstraints(**timing_constraints) else: timing_constraints = backend.target.timing_constraints() - return [timing_constraints] * num_circuits - - -def _zip_dict(mapping: Dict[Any, Iterable]) -> Iterable[Dict]: - """Zip a dictionary where all the values are iterables of the same length into an iterable of - dictionaries with the same keys. This has the same semantics as zip with regard to laziness - (over the iterables; there must be a finite number of keys!) and unequal lengths.""" - keys, iterables = zip(*mapping.items()) - return (dict(zip(keys, values)) for values in zip(*iterables)) + return timing_constraints diff --git a/qiskit/providers/fake_provider/fake_1q.py b/qiskit/providers/fake_provider/fake_1q.py index cf1d7354ab73..07589476149e 100644 --- a/qiskit/providers/fake_provider/fake_1q.py +++ b/qiskit/providers/fake_provider/fake_1q.py @@ -89,11 +89,3 @@ def __init__(self): general=[], ) super().__init__(configuration) - - def defaults(self): - """defaults == configuration""" - return self._configuration - - def properties(self): - """properties == configuration""" - return self._configuration diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py index 54aa272c8f4b..923196f58b9f 100644 --- a/qiskit/providers/fake_provider/fake_backend.py +++ b/qiskit/providers/fake_provider/fake_backend.py @@ -474,6 +474,8 @@ def _setup_sim(self): def properties(self): """Return backend properties""" coupling_map = self.configuration().coupling_map + if coupling_map is None: + return None unique_qubits = list(set().union(*coupling_map)) properties = { diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index ae1f43b3c243..d3ba1e60a8eb 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -106,7 +106,7 @@ def __init__( self.hls_config = hls_config @classmethod - def from_backend(cls, backend, **pass_manager_options): + def from_backend(cls, backend, _skip_target=False, **pass_manager_options): """Construct a configuration based on a backend and user input. This method automatically gererates a PassManagerConfig object based on the backend's @@ -128,7 +128,6 @@ def from_backend(cls, backend, **pass_manager_options): backend_version = 0 if backend_version < 2: config = backend.configuration() - if res.basis_gates is None: if backend_version < 2: res.basis_gates = getattr(config, "basis_gates", None) @@ -156,7 +155,7 @@ def from_backend(cls, backend, **pass_manager_options): res.instruction_durations = backend.instruction_durations if res.backend_properties is None and backend_version < 2: res.backend_properties = backend.properties() - if res.target is None: + if res.target is None and not _skip_target: if backend_version >= 2: res.target = backend.target if res.scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"): diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index 2e18e8659d38..e38b1effd57c 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -92,6 +92,8 @@ def generate_preset_pass_manager( hls_config=None, init_method=None, optimization_method=None, + *, + _skip_target=False, ): """Generate a preset :class:`~.PassManager` @@ -241,10 +243,10 @@ def generate_preset_pass_manager( } if backend is not None: + pm_options["_skip_target"] = _skip_target pm_config = PassManagerConfig.from_backend(backend, **pm_options) else: pm_config = PassManagerConfig(**pm_options) - if optimization_level == 0: pm = level_0_pass_manager(pm_config) elif optimization_level == 1: diff --git a/releasenotes/notes/remove-transpile-broadcast-1dfde28d508efa0d.yaml b/releasenotes/notes/remove-transpile-broadcast-1dfde28d508efa0d.yaml new file mode 100644 index 000000000000..f054b8fdb99c --- /dev/null +++ b/releasenotes/notes/remove-transpile-broadcast-1dfde28d508efa0d.yaml @@ -0,0 +1,42 @@ +--- +upgrade: + - | + Support for passing in lists of argument values to the :func:`~.transpile` + function is removed. This functionality was deprecated as part of the + 0.23.0 release and is now being removed. You are still able to pass in a + list of :class:`~.QuantumCircuit` objects for the first positional argument, + what has been removed is the broadcasting lists of the other arguments to + each circuit in that input list. Removing this functionality was necessary + to greatly reduce the overhead for parallel execution for transpiling + multiple circuits at once. If you’re using this functionality + currently you can call :func:`~.transpile` multiple times instead. For + example if you were previously doing something like:: + + from qiskit.transpiler import CouplingMap + from qiskit import QuantumCircuit + from qiskit import transpile + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + cmaps = [CouplingMap.from_heavy_hex(d) for d in range(3, 15, 2)] + results = transpile([qc] * 6, coupling_map=cmaps) + + instead you should now run something like:: + + from qiskit.transpiler import CouplingMap + from qiskit import QuantumCircuit + from qiskit import transpile + + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.measure_all() + + cmaps = [CouplingMap.from_heavy_hex(d) for d in range(3, 15, 2)] + results = [transpile(qc, coupling_map=cm) for cm in cmap] + + You can also leverage :func:`~.parallel_map` or ``multiprocessing`` from + the Python standard library if you want to run this in parallel. diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index ebfc79af9b04..bb2c07e5ebce 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -635,7 +635,7 @@ def test_wrong_initial_layout(self): QuantumRegister(3, "q")[2], ] - with self.assertRaisesRegex(TranspilerError, "different numbers of qubits"): + with self.assertRaises(TranspilerError): transpile(qc, backend, initial_layout=bad_initial_layout) def test_parameterized_circuit_for_simulator(self): @@ -2085,24 +2085,16 @@ def test_transpile_with_multiple_coupling_maps(self): cmap = CouplingMap.from_line(7) cmap.add_edge(0, 2) - with self.assertWarnsRegex( - DeprecationWarning, "Passing in a list of arguments for coupling_map is deprecated" - ): + with self.assertRaisesRegex(TranspilerError, "Only a single input coupling"): # Initial layout needed to prevent transpiler from relabeling # qubits to avoid doing the swap - tqc = transpile( + transpile( [qc] * 2, backend, coupling_map=[backend.coupling_map, cmap], initial_layout=(0, 1, 2), ) - # Check that the two coupling maps were used. The default should - # require swapping (extra cx's) and the second one should not (just the - # original cx). - self.assertEqual(tqc[0].count_ops()["cx"], 4) - self.assertEqual(tqc[1].count_ops()["cx"], 1) - @data(0, 1, 2, 3) def test_backend_and_custom_gate(self, opt_level): """Test transpile() with BackendV2, custom basis pulse gate.""" diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index b3a45d7e1f53..970224c55dbc 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -21,8 +21,6 @@ from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister from qiskit.compiler import transpile -from qiskit.compiler.transpiler import _parse_inst_map -from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.test.base import QiskitTestCase from qiskit.providers.fake_provider import FakeMumbaiFractionalCX from qiskit.providers.fake_provider.fake_backend_v2 import ( @@ -178,11 +176,6 @@ def test_transpile_mumbai_target(self): expected.measure(qr[1], cr[1]) self.assertEqual(expected, tqc) - def test_transpile_parse_inst_map(self): - """Test that transpiler._parse_inst_map() supports BackendV2.""" - inst_map = _parse_inst_map(inst_map=None, backend=self.backend) - self.assertIsInstance(inst_map, InstructionScheduleMap) - @data(0, 1, 2, 3, 4) def test_drive_channel(self, qubit): """Test getting drive channel with qubit index."""