diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 730b5e7369cf..e1ae442b834a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,8 +17,8 @@ extensions/ @ajavadia @kdk @ewinston @1ucian0 @mtreinish dagcircuit/ @ajavadia @kdk @1ucian0 @maddy-tod providers/ @ajavadia @mtreinish @diego-plan9 basicaer/ @chriseclectic @ajavadia -pulse/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 -scheduler/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 +pulse/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 @danpuzzuoli +scheduler/ @lcapelluto @taalexander @eggerdj @nkanazawa1989 @danpuzzuoli quantum_info/ @chriseclectic @ajavadia qi/ @chriseclectic @ajavadia result/ @diego-plan9 @jyu00 @taalexander @mtreinish @ajavadia diff --git a/docs/apidocs/qiskit.rst b/docs/apidocs/qiskit.rst index d69e770284b8..933ce5e74c16 100644 --- a/docs/apidocs/qiskit.rst +++ b/docs/apidocs/qiskit.rst @@ -1,8 +1,8 @@ .. module:: qiskit -============= -API Reference -============= +========================== +Qiskit Terra API Reference +========================== .. toctree:: :maxdepth: 1 diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 16d5810b773c..6918a0a01805 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -334,8 +334,12 @@ def extend(self, rhs): if element not in self.cregs: self.cregs.append(element) + # Copy the circuit data if rhs and self are the same, otherwise the data of rhs is + # appended to both self and rhs resulting in an infinite loop + data = rhs.data.copy() if rhs is self else rhs.data + # Add new gates - for instruction_context in rhs.data: + for instruction_context in data: self._append(*instruction_context) return self diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index e761e8127082..b10cfe331ae0 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -15,13 +15,17 @@ """Circuit transpile function""" from qiskit.transpiler import Layout, CouplingMap from qiskit.tools.parallel import parallel_map -from qiskit.transpiler.transpile_config import TranspileConfig -from qiskit.transpiler.transpile_circuit import transpile_circuit +from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.pulse import Schedule from qiskit.circuit.quantumregister import Qubit from qiskit import user_config from qiskit.transpiler.exceptions import TranspilerError from qiskit.converters import isinstanceint, isinstancelist +from qiskit.transpiler.passes.basis.ms_basis_decomposer import MSBasisDecomposer +from qiskit.transpiler.preset_passmanagers import (level_0_pass_manager, + level_1_pass_manager, + level_2_pass_manager, + level_3_pass_manager) def transpile(circuits, @@ -176,14 +180,15 @@ def callback_func(**kwargs): config = user_config.get_config() optimization_level = config.get('transpile_optimization_level', None) - # Get TranspileConfig(s) to configure the circuit transpilation job(s) + # Get transpile_args to configure the circuit transpilation job(s) circuits = circuits if isinstance(circuits, list) else [circuits] - transpile_configs = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, - backend_properties, initial_layout, - seed_transpiler, optimization_level, - pass_manager, callback, output_name) + transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, + backend_properties, initial_layout, + seed_transpiler, optimization_level, + pass_manager, callback, output_name) # Check circuit width against number of qubits in coupling_map(s) - coupling_maps_list = list(config.coupling_map for config in transpile_configs) + coupling_maps_list = list(config['pass_manager_config'].coupling_map for config in + transpile_args) for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): # If coupling_map is not None or n_qubits == 1 n_qubits = len(circuit.qubits) @@ -201,28 +206,72 @@ def callback_func(**kwargs): 'is greater than maximum ({}) '.format(max_qubits) + 'in the coupling_map') # Transpile circuits in parallel - circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_configs))) + circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_args))) if len(circuits) == 1: return circuits[0] return circuits -# FIXME: This is a helper function because of parallel tools. def _transpile_circuit(circuit_config_tuple): """Select a PassManager and run a single circuit through it. - Args: circuit_config_tuple (tuple): circuit (QuantumCircuit): circuit to transpile - transpile_config (TranspileConfig): configuration dictating how to transpile - + transpile_config (dict): configuration dictating how to transpile. The + dictionary has the following format: + {'optimization_level': int, + 'pass_manager': PassManager, + 'output_name': string, + 'callback': callable, + 'pass_manager_config': PassManagerConfig} Returns: QuantumCircuit: transpiled circuit + Raises: + TranspilerError: if transpile_config is not valid or transpilation incurs error """ circuit, transpile_config = circuit_config_tuple - return transpile_circuit(circuit, transpile_config) + pass_manager_config = transpile_config['pass_manager_config'] + + # Workaround for ion trap support: If basis gates includes + # Mølmer-Sørensen (rxx) and the circuit includes gates outside the basis, + # first unroll to u3, cx, then run MSBasisDecomposer to target basis. + basic_insts = ['measure', 'reset', 'barrier', 'snapshot'] + device_insts = set(pass_manager_config.basis_gates).union(basic_insts) + ms_basis_swap = None + if 'rxx' in pass_manager_config.basis_gates and \ + not device_insts >= circuit.count_ops().keys(): + ms_basis_swap = pass_manager_config.basis_gates + pass_manager_config.basis_gates = list( + set(['u3', 'cx']).union(pass_manager_config.basis_gates)) + + if transpile_config['pass_manager'] is not None: + # either the pass manager is already selected... + pass_manager = transpile_config['pass_manager'] + else: + # or we choose an appropriate one based on desired optimization level (default: level 1) + if transpile_config['optimization_level'] is not None: + level = transpile_config['optimization_level'] + else: + level = 1 + + 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.") + + if ms_basis_swap is not None: + pass_manager.append(MSBasisDecomposer(ms_basis_swap)) + + return pass_manager.run(circuit, callback=transpile_config['callback'], + output_name=transpile_config['output_name']) def _parse_transpile_args(circuits, backend, @@ -238,45 +287,38 @@ def _parse_transpile_args(circuits, backend, arg has more priority than the arg set by backend). Returns: - list[TranspileConfig]: a transpile config for each circuit, which is a standardized - object that configures the transpiler and determines the pass manager to use. + list[dicts]: a list of transpile parameters. """ # 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) basis_gates = _parse_basis_gates(basis_gates, backend, circuits) - coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits) - backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits) - initial_layout = _parse_initial_layout(initial_layout, circuits) - seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) - optimization_level = _parse_optimization_level(optimization_level, num_circuits) - pass_manager = _parse_pass_manager(pass_manager, num_circuits) - output_name = _parse_output_name(output_name, circuits) + callback = _parse_callback(callback, num_circuits) - transpile_configs = [] + list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, initial_layout, seed_transpiler, optimization_level, - pass_manager, output_name): - transpile_config = TranspileConfig(basis_gates=args[0], - coupling_map=args[1], - backend_properties=args[2], - initial_layout=args[3], - seed_transpiler=args[4], - optimization_level=args[5], - pass_manager=args[6], - callback=callback, - output_name=args[7]) - transpile_configs.append(transpile_config) + pass_manager, output_name, callback): + transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], + coupling_map=args[1], + backend_properties=args[2], + initial_layout=args[3], + seed_transpiler=args[4]), + 'optimization_level': args[5], + 'pass_manager': args[6], + 'output_name': args[7], + 'callback': args[8]} + list_transpile_args.append(transpile_args) - return transpile_configs + return list_transpile_args def _parse_basis_gates(basis_gates, backend, circuits): @@ -303,14 +345,19 @@ def _parse_coupling_map(coupling_map, backend, num_circuits): # try getting coupling_map from user, else backend if coupling_map is None: if getattr(backend, 'configuration', None): - coupling_map = getattr(backend.configuration(), 'coupling_map', None) + configuration = backend.configuration() + if hasattr(configuration, 'coupling_map') and configuration.coupling_map: + coupling_map = CouplingMap(configuration.coupling_map) + # coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]] if coupling_map is None or isinstance(coupling_map, CouplingMap): coupling_map = [coupling_map] * num_circuits elif isinstance(coupling_map, list) and all(isinstance(i, list) and len(i) == 2 for i in coupling_map): coupling_map = [coupling_map] * num_circuits + coupling_map = [CouplingMap(cm) if isinstance(cm, list) else cm for cm in coupling_map] + return coupling_map @@ -372,6 +419,12 @@ def _parse_pass_manager(pass_manager, num_circuits): return pass_manager +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 diff --git a/qiskit/execute.py b/qiskit/execute.py index c56d024abb2f..d272c06bfd20 100644 --- a/qiskit/execute.py +++ b/qiskit/execute.py @@ -21,8 +21,10 @@ .. autofunction:: execute """ -from qiskit.compiler import transpile, assemble +from qiskit.compiler import transpile, assemble, schedule from qiskit.qobj.utils import MeasLevel, MeasReturnType +from qiskit.pulse import Schedule +from qiskit.exceptions import QiskitError def execute(experiments, backend, @@ -35,6 +37,7 @@ def execute(experiments, backend, schedule_los=None, meas_level=MeasLevel.CLASSIFIED, meas_return=MeasReturnType.AVERAGE, memory_slots=None, memory_slot_size=100, rep_time=None, parameter_binds=None, + schedule_circuit=False, inst_map=None, meas_map=None, scheduling_method=None, **run_config): """Execute a list of :class:`qiskit.circuit.QuantumCircuit` or :class:`qiskit.pulse.Schedule` on a backend. @@ -179,6 +182,21 @@ def execute(experiments, backend, length-n list, and there are m experiments, a total of m x n experiments will be run (one for each experiment/bind pair). + schedule_circuit (bool): + If ``True``, ``experiments`` will be converted to ``Schedule``s prior to + execution. + + inst_map (InstructionScheduleMap): + Mapping of circuit operations to pulse schedules. If None, defaults to the + ``instruction_schedule_map`` of ``backend``. + + meas_map (list(list(int))): + List of sets of qubits that must be measured together. If None, defaults to + the ``meas_map`` of ``backend``. + + scheduling_method (str or list(str)): + Optionally specify a particular scheduling method. + run_config (dict): Extra arguments used to configure the run (e.g. for Aer configurable backends). Refer to the backend documentation for details on these arguments. @@ -220,6 +238,16 @@ def execute(experiments, backend, pass_manager=pass_manager, ) + if schedule_circuit: + if isinstance(experiments, Schedule) or isinstance(experiments[0], Schedule): + raise QiskitError("Must supply QuantumCircuit to schedule circuit.") + experiments = schedule(circuits=experiments, + backend=backend, + inst_map=inst_map, + meas_map=meas_map, + method=scheduling_method + ) + # assembling the circuits into a qobj to be run on the backend qobj = assemble(experiments, qobj_id=qobj_id, diff --git a/qiskit/extensions/standard/__init__.py b/qiskit/extensions/standard/__init__.py index 4d21ef78f338..863d3821ad04 100644 --- a/qiskit/extensions/standard/__init__.py +++ b/qiskit/extensions/standard/__init__.py @@ -18,6 +18,8 @@ from .iden import IdGate from .ms import MSGate from .r import RGate +from .rccx import RCCXGate +from .rcccx import RCCCXGate from .rx import RXGate, CrxGate from .rxx import RXXGate from .ry import RYGate, CryGate diff --git a/qiskit/extensions/standard/multi_control_toffoli_gate.py b/qiskit/extensions/standard/multi_control_toffoli_gate.py index c285a995fdc5..558835a5c18e 100644 --- a/qiskit/extensions/standard/multi_control_toffoli_gate.py +++ b/qiskit/extensions/standard/multi_control_toffoli_gate.py @@ -22,8 +22,6 @@ from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit from qiskit import QiskitError -# pylint: disable=unused-import -from .relative_phase_toffoli import rccx logger = logging.getLogger(__name__) diff --git a/qiskit/extensions/standard/rcccx.py b/qiskit/extensions/standard/rcccx.py new file mode 100644 index 000000000000..b91917a86639 --- /dev/null +++ b/qiskit/extensions/standard/rcccx.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""The simplified 3-controlled Toffoli gate.""" + +import numpy + +from qiskit.circuit import QuantumCircuit, Gate, QuantumRegister +from qiskit.extensions.standard.u1 import U1Gate +from qiskit.extensions.standard.u2 import U2Gate +from qiskit.extensions.standard.x import CnotGate +from qiskit.qasm import pi + + +class RCCCXGate(Gate): + """The simplified 3-controlled Toffoli gate. + + The simplified Toffoli gate implements the Toffoli gate up to relative phases. + Note, that the simplified Toffoli is not equivalent to the Toffoli. But can be used in places + where the Toffoli gate is uncomputed again. + + This concrete implementation is from https://arxiv.org/abs/1508.03273, the complete circuit + of Fig. 4. + """ + + def __init__(self): + """Create a new RCCCX gate.""" + super().__init__('rcccx', 4, []) + + def _define(self): + """ + gate rcccx a,b,c,d + { u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; + } + """ + definition = [] + q = QuantumRegister(4, 'q') + + rule = [ + (U2Gate(0, pi), [q[3]], []), # H gate + (U1Gate(pi / 4), [q[3]], []), # T gate + (CnotGate(), [q[2], q[3]], []), + (U1Gate(-pi / 4), [q[3]], []), # inverse T gate + (U2Gate(0, pi), [q[3]], []), + (CnotGate(), [q[0], q[3]], []), + (U1Gate(pi / 4), [q[3]], []), + (CnotGate(), [q[1], q[3]], []), + (U1Gate(-pi / 4), [q[3]], []), + (CnotGate(), [q[0], q[3]], []), + (U1Gate(pi / 4), [q[3]], []), + (CnotGate(), [q[1], q[3]], []), + (U1Gate(-pi / 4), [q[3]], []), + (U2Gate(0, pi), [q[3]], []), + (U1Gate(pi / 4), [q[3]], []), + (CnotGate(), [q[2], q[3]], []), + (U1Gate(-pi / 4), [q[3]], []), + (U2Gate(0, pi), [q[3]], []), + ] + for inst in rule: + definition.append(inst) + self.definition = definition + + def to_matrix(self): + """Return a numpy.array for the RCCCX gate.""" + return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1j, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1j, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=complex) + + +def rcccx(self, control_qubit1, control_qubit2, control_qubit3, target_qubit): + """Apply the simplified, relative-phase 3-control Toffoli gate.""" + return self.append(RCCCXGate(), [control_qubit1, control_qubit2, control_qubit3, target_qubit], + []) + + +QuantumCircuit.rcccx = rcccx diff --git a/qiskit/extensions/standard/rccx.py b/qiskit/extensions/standard/rccx.py new file mode 100644 index 000000000000..1ee5f58c7364 --- /dev/null +++ b/qiskit/extensions/standard/rccx.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""The simplified Toffoli gate.""" + +import numpy + +from qiskit.circuit import QuantumCircuit, Gate, QuantumRegister +from qiskit.extensions.standard.u1 import U1Gate +from qiskit.extensions.standard.u2 import U2Gate +from qiskit.extensions.standard.x import CnotGate +from qiskit.qasm import pi + + +class RCCXGate(Gate): + """The simplified Toffoli gate, also referred to as Margolus gate. + + The simplified Toffoli gate implements the Toffoli gate up to relative phases. + This implementation requires three CX gates which is the minimal amount possible, + as shown in https://arxiv.org/abs/quant-ph/0312225. + Note, that the simplified Toffoli is not equivalent to the Toffoli. But can be used in places + where the Toffoli gate is uncomputed again. + + This concrete implementation is from https://arxiv.org/abs/1508.03273, the dashed box + of Fig. 3. + """ + + def __init__(self): + """Create a new simplified CCX gate.""" + super().__init__('rccx', 3, []) + + def _define(self): + """ + gate rccx a,b,c + { u2(0,pi) c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + cx a, c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + u2(0,pi) c; + } + """ + definition = [] + q = QuantumRegister(3, 'q') + rule = [ + (U2Gate(0, pi), [q[2]], []), # H gate + (U1Gate(pi / 4), [q[2]], []), # T gate + (CnotGate(), [q[1], q[2]], []), + (U1Gate(-pi / 4), [q[2]], []), # inverse T gate + (CnotGate(), [q[0], q[2]], []), + (U1Gate(pi / 4), [q[2]], []), + (CnotGate(), [q[1], q[2]], []), + (U1Gate(-pi / 4), [q[2]], []), # inverse T gate + (U2Gate(0, pi), [q[2]], []), # H gate + ] + for inst in rule: + definition.append(inst) + self.definition = definition + + def to_matrix(self): + """Return a numpy.array for the simplified CCX gate.""" + return numpy.array([[1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, -1j], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, -1, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1j, 0, 0, 0, 0]], dtype=complex) + + +def rccx(self, control_qubit1, control_qubit2, target_qubit): + """Apply the simplified, relative-phase Toffoli gate.""" + return self.append(RCCXGate(), [control_qubit1, control_qubit2, target_qubit], []) + + +QuantumCircuit.rccx = rccx diff --git a/qiskit/extensions/standard/relative_phase_toffoli.py b/qiskit/extensions/standard/relative_phase_toffoli.py deleted file mode 100644 index 108a06eb0f39..000000000000 --- a/qiskit/extensions/standard/relative_phase_toffoli.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Relative Phase Toffoli Gates. -""" - -from qiskit.circuit import QuantumCircuit, Qubit -from qiskit.qasm import pi - -from qiskit import QiskitError - - -def _apply_rccx(circ, qba, qbb, qbc): - circ.u2(0, pi, qbc) # h - circ.u1(pi / 4, qbc) # t - circ.cx(qbb, qbc) - circ.u1(-pi / 4, qbc) # tdg - circ.cx(qba, qbc) - circ.u1(pi / 4, qbc) # t - circ.cx(qbb, qbc) - circ.u1(-pi / 4, qbc) # tdg - circ.u2(0, pi, qbc) # h - - -def _apply_rcccx(circ, qba, qbb, qbc, qbd): - circ.u2(0, pi, qbd) # h - circ.u1(pi / 4, qbd) # t - circ.cx(qbc, qbd) - circ.u1(-pi / 4, qbd) # tdg - circ.u2(0, pi, qbd) # h - circ.cx(qba, qbd) - circ.u1(pi / 4, qbd) # t - circ.cx(qbb, qbd) - circ.u1(-pi / 4, qbd) # tdg - circ.cx(qba, qbd) - circ.u1(pi / 4, qbd) # t - circ.cx(qbb, qbd) - circ.u1(-pi / 4, qbd) # tdg - circ.u2(0, pi, qbd) # h - circ.u1(pi / 4, qbd) # t - circ.cx(qbc, qbd) - circ.u1(-pi / 4, qbd) # tdg - circ.u2(0, pi, qbd) # h - - -def rccx(self, q_control_1, q_control_2, q_target): - """ - Apply 2-Control Relative-Phase Toffoli gate from q_control_1 and q_control_2 to q_target. - - The implementation is based on https://arxiv.org/pdf/1508.03273.pdf Figure 3 - - Args: - self (QuantumCircuit): The QuantumCircuit object to apply the rccx gate on. - q_control_1 (Qubit): The 1st control qubit. - q_control_2 (Qubit): The 2nd control qubit. - q_target (Qubit): The target qubit. - - Raises: - QiskitError: improper qubit specification - """ - if not isinstance(q_control_1, Qubit): - raise QiskitError('A qubit is expected for the first control.') - if not self.has_register(q_control_1.register): - raise QiskitError('The first control qubit is expected to be part of the circuit.') - - if not isinstance(q_control_2, Qubit): - raise QiskitError('A qubit is expected for the second control.') - if not self.has_register(q_control_2.register): - raise QiskitError('The second control qubit is expected to be part of the circuit.') - - if not isinstance(q_target, Qubit): - raise QiskitError('A qubit is expected for the target.') - if not self.has_register(q_target.register): - raise QiskitError('The target qubit is expected to be part of the circuit.') - self._check_dups([q_control_1, q_control_2, q_target]) - _apply_rccx(self, q_control_1, q_control_2, q_target) - - -def rcccx(self, q_control_1, q_control_2, q_control_3, q_target): - """ - Apply 3-Control Relative-Phase Toffoli gate from q_control_1, q_control_2, - and q_control_3 to q_target. - - The implementation is based on https://arxiv.org/pdf/1508.03273.pdf Figure 4 - - Args: - self (QuantumCircuit): The QuantumCircuit object to apply the rcccx gate on. - q_control_1 (Qubit): The 1st control qubit. - q_control_2 (Qubit): The 2nd control qubit. - q_control_3 (Qubit): The 3rd control qubit. - q_target (Qubit): The target qubit. - - Raises: - QiskitError: improper qubit specification - """ - if not isinstance(q_control_1, Qubit): - raise QiskitError('A qubit is expected for the first control.') - if not self.has_register(q_control_1.register): - raise QiskitError('The first control qubit is expected to be part of the circuit.') - - if not isinstance(q_control_2, Qubit): - raise QiskitError('A qubit is expected for the second control.') - if not self.has_register(q_control_2.register): - raise QiskitError('The second control qubit is expected to be part of the circuit.') - - if not isinstance(q_control_3, Qubit): - raise QiskitError('A qubit is expected for the third control.') - if not self.has_register(q_control_3.register): - raise QiskitError('The third control qubit is expected to be part of the circuit.') - - if not isinstance(q_target, Qubit): - raise QiskitError('A qubit is expected for the target.') - if not self.has_register(q_target.register): - raise QiskitError('The target qubit is expected to be part of the circuit.') - - self._check_dups([q_control_1, q_control_2, q_control_3, q_target]) - _apply_rcccx(self, q_control_1, q_control_2, q_control_3, q_target) - - -QuantumCircuit.rccx = rccx -QuantumCircuit.rcccx = rcccx diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index f00368cc8090..02d4b9169fb4 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -25,7 +25,6 @@ .. autosummary:: :toctree: ../stubs/ - PulseChannelSpec DriveChannel MeasureChannel AcquireChannel @@ -49,14 +48,12 @@ Kernel Discriminator Delay - functional_pulse ParametricPulse ParametricInstruction Gaussian GaussianSquare Drag ConstantPulse - functional_pulse Schedules ========= @@ -73,10 +70,18 @@ .. autosummary:: :toctree: ../stubs/ - CmdDef + InstructionScheduleMap LoConfig LoRange +Pulse Library +============= + +.. autosummary:: + :toctree: ../stubs/ + + ~qiskit.pulse.pulse_lib.discrete + Exceptions ========== @@ -86,8 +91,7 @@ PulseError """ -from .channels import (PulseChannelSpec, DriveChannel, - MeasureChannel, AcquireChannel, +from .channels import (DriveChannel, MeasureChannel, AcquireChannel, ControlChannel, RegisterSlot, MemorySlot) from .cmd_def import CmdDef from .commands import (Instruction, Acquire, AcquireInstruction, FrameChange, diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py new file mode 100644 index 000000000000..18381ab3bc79 --- /dev/null +++ b/qiskit/pulse/channels.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +This module defines Pulse Channels. Channels include: + + - transmit channels, which should subclass``PulseChannel`` + - receive channels, such as ``AcquireChannel`` + - non-signal "channels" such as ``SnapshotChannel``, ``MemorySlot`` and ``RegisterChannel``. + +Novel channel types can often utilize the ``ControlChannel``, but if this is not sufficient, new +channel types can be created. Then, they must be supported in the PulseQobj schema and the +assembler. +""" +from abc import ABCMeta + +from qiskit.pulse.exceptions import PulseError + + +class Channel(metaclass=ABCMeta): + """Base class of channels. Channels provide a Qiskit-side label for typical quantum control + hardware signal channels. The final label -> physical channel mapping is the responsibility + of the hardware backend. For instance,``DriveChannel(0)`` holds instructions which the backend + should map to the signal line driving gate operations on the qubit labeled (indexed) 0. + """ + + prefix = None # type: str + """A shorthand string prefix for characterizing the channel type.""" + + def __init__(self, index: int): + """Channel class. + + Args: + index: Index of channel. + + Raises: + PulseError: If ``index`` is not a nonnegative integer. + """ + if not isinstance(index, int) or index < 0: + raise PulseError('Channel index must be a nonnegative integer') + self._index = index + + @property + def index(self) -> int: + """Return the index of this channel. The index is a label for a control signal line + typically mapped trivially to a qubit index. For instance, ``DriveChannel(0)`` labels + the signal line driving the qubit labeled with index 0. + """ + return self._index + + @property + def name(self) -> str: + """Return the shorthand alias for this channel, which is based on its type and index.""" + return '%s%d' % (self.__class__.prefix, self._index) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self._index) + + def __eq__(self, other: 'Channel') -> bool: + """Return True iff self and other are equal, specifically, iff they have the same type + and the same index. + + Args: + other: The channel to compare to this channel. + + Returns: + True iff equal. + """ + return type(self) is type(other) and self._index == other._index + + def __hash__(self): + return hash((type(self), self._index)) + + +class PulseChannel(Channel, metaclass=ABCMeta): + """Base class of transmit Channels. Pulses can be played on these channels.""" + pass + + +class DriveChannel(PulseChannel): + """Drive channels transmit signals to qubits which enact gate operations.""" + prefix = 'd' + + +class MeasureChannel(PulseChannel): + """Measure channels transmit measurement stimulus pulses for readout.""" + prefix = 'm' + + +class ControlChannel(PulseChannel): + """Control channels provide supplementary control over the qubit to the drive channel. + These are often associated with multi-qubit gate operations. They may not map trivially + to a particular qubit index. + """ + prefix = 'u' + + +class AcquireChannel(Channel): + """Acquire channels are used to collect data.""" + prefix = 'a' + + +class SnapshotChannel(Channel): + """Snapshot channels are used to specify commands for simulators.""" + prefix = 's' + + def __init__(self): + """Create new snapshot channel.""" + super().__init__(0) + + +class MemorySlot(Channel): + """Memory slot channels represent classical memory storage.""" + prefix = 'm' + + +class RegisterSlot(Channel): + """Classical resister slot channels represent classical registers (low-latency classical + memory). + """ + prefix = 'c' diff --git a/qiskit/pulse/channels/__init__.py b/qiskit/pulse/channels/__init__.py deleted file mode 100644 index a089598477e2..000000000000 --- a/qiskit/pulse/channels/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Device-related classes for pulse.""" - -from .pulse_channel_spec import PulseChannelSpec -from .qubit import Qubit -from .channels import (Channel, PulseChannel, DriveChannel, ControlChannel, MeasureChannel, - AcquireChannel, MemorySlot, RegisterSlot, SnapshotChannel) diff --git a/qiskit/pulse/channels/channels.py b/qiskit/pulse/channels/channels.py deleted file mode 100644 index a04862164944..000000000000 --- a/qiskit/pulse/channels/channels.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Channels. -""" -import warnings - -from abc import ABCMeta - -from qiskit.pulse.exceptions import PulseError - - -class Channel(metaclass=ABCMeta): - """Base class of channels.""" - - prefix = None - - def __init__(self, index: int, buffer: int = 0): - """Channel class. - - Args: - index: Index of channel - buffer: Buffer that should be placed between instructions on channel - - Raises: - PulseError: If integer index or buffer not supplied - """ - if not isinstance(index, int): - raise PulseError('Channel index must be integer') - - self._index = index - - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - - @property - def index(self) -> int: - """Return the index of this channel.""" - return self._index - - @property - def buffer(self) -> int: - """Return the buffer for this channel.""" - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - return 0 - - @property - def name(self) -> str: - """Return the name of this channel.""" - return '%s%d' % (self.__class__.prefix, self._index) - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, self._index) - - def __eq__(self, other: 'Channel') -> bool: - """ - Channels are the same iff they are of the same type, and have the same index. - - Args: - other: The channel to compare to this channel. - Returns: - bool: equality - """ - - return type(self) is type(other) and self._index == other._index - - def __hash__(self): - return hash((type(self), self._index)) - - -class PulseChannel(Channel, metaclass=ABCMeta): - """Base class of Channel supporting pulse output.""" - pass - - -class DriveChannel(PulseChannel): - """Drive Channel.""" - prefix = 'd' - - -class MeasureChannel(PulseChannel): - """Measure Channel.""" - prefix = 'm' - - -class ControlChannel(PulseChannel): - """Control Channel.""" - prefix = 'u' - - -class AcquireChannel(Channel): - """Acquire channel.""" - prefix = 'a' - - -class SnapshotChannel(Channel): - """Snapshot channel.""" - prefix = 's' - - def __init__(self): - """Create new snapshot channel.""" - super().__init__(0) - - -class MemorySlot(Channel): - """Memory slot channel.""" - prefix = 'm' - - -class RegisterSlot(Channel): - """Classical resister slot channel.""" - prefix = 'c' diff --git a/qiskit/pulse/channels/pulse_channel_spec.py b/qiskit/pulse/channels/pulse_channel_spec.py deleted file mode 100644 index f999c9ca750d..000000000000 --- a/qiskit/pulse/channels/pulse_channel_spec.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name - -""" -Pulse channel wrapper object for the system. -""" -import warnings - -from typing import List - -from qiskit.pulse.exceptions import PulseError -from .channels import (AcquireChannel, MemorySlot, RegisterSlot, DriveChannel, ControlChannel, - MeasureChannel) -from .qubit import Qubit - - -class PulseChannelSpec: - """A helper class to support assembling channel objects and their mapping to qubits. - This class can be initialized with two methods shown as below. - - 1. With the `BaseBackend` object of the target pulse backend: - ```python - system = PulseChannelSpec.from_backend(backend) - ``` - - 2. By specifying the number of pulse elements constituting the target quantum computing system: - ```python - system = PulseChannelSpec(n_qubits=5, n_control=6, n_registers=1, buffer=10) - ``` - - Within Qiskit a quantum computing system at the level of pulses is abstracted as - a combination of multiple types of channels on which instructions are scheduled. - These most common channel types are the: - - `PulseChannel`: For performing stimulus of the system. - - `AcquireChannel`: For scheduling acquisition of qubit data. - - `MemorySlot`: For persistent storage of measurement results. - - `RegisterSlot`: For temporary storage of and conditional feedback on measurement results. - - There are also several special types of pulse channels which are the: - - `DriveChannel`: Used to control a single qubit. - - `MeasureChannel`: Used to perform measurement stimulus of a single qubit. - - `ControlChannel`: Used to control an arbitrary Hamiltonian term on the system. - Typically a two-qubit interaction. - - A collection of above channels is automatically assembled within `PulseChannelSpec`. - - For example, the zeroth drive channel may be accessed by - ```python - system.drives[0] - ``` - or if the channel is connected to the first qubit, - ```python - system.qubits[0].drive - ``` - In the above example, both commands refer to the same object. - """ - def __init__(self, - n_qubits: int, - n_control: int, - n_registers: int, - buffer: int = 0): - """ - Create pulse channel specification with number of channels. - - Args: - n_qubits: Number of qubits. - n_control: Number of control channels. - n_registers: Number of classical registers. - buffer: Buffer that should be placed between instructions on channel. - """ - warnings.warn("The PulseChannelSpec is deprecated. Use backend.configuration() instead. " - "The supported methods require some migrations; check out the release " - "notes for the complete details.", - DeprecationWarning) - if buffer: - warnings.warn("Buffers are no longer supported. Please use an explicit Delay.") - self._drives = [DriveChannel(idx) for idx in range(n_qubits)] - self._controls = [ControlChannel(idx) for idx in range(n_control)] - self._measures = [MeasureChannel(idx) for idx in range(n_qubits)] - self._acquires = [AcquireChannel(idx) for idx in range(n_qubits)] - self._mem_slots = [MemorySlot(idx) for idx in range(n_qubits)] - self._reg_slots = [RegisterSlot(idx) for idx in range(n_registers)] - - # create mapping information from channels - warnings.simplefilter("ignore") # Suppress Qubit deprecation warnings - self._qubits = [ - Qubit(ii, DriveChannel(ii), MeasureChannel(ii), - AcquireChannel(ii), [ControlChannel(ii)]) - for ii in range(n_qubits)] - warnings.resetwarnings() - - @classmethod - def from_backend(cls, backend): - """ - Create pulse channel specification with values from backend. - - Args: - backend (BaseBackend): Backend configuration. - - Returns: - PulseChannelSpec: New PulseSpecification configured by backend. - - Raises: - PulseError: When OpenPulse is not supported. - """ - configuration = backend.configuration() - - if not configuration.open_pulse: - raise PulseError(configuration.backend_name + ' does not support OpenPulse.') - - # TODO: allow for drives/measures which are not identical to number of qubit - n_qubits = configuration.n_qubits - n_controls = configuration.n_uchannels - n_registers = configuration.n_registers - - return PulseChannelSpec(n_qubits=n_qubits, n_control=n_controls, - n_registers=n_registers) - - @property - def drives(self) -> List[DriveChannel]: - """Return system's drive channels.""" - return self._drives - - @property - def controls(self): - """Return system's control channels.""" - return self._controls - - @property - def measures(self): - """Return system's measure channels.""" - return self._measures - - @property - def acquires(self): - """Return system's acquire channels.""" - return self._acquires - - @property - def qubits(self) -> List[Qubit]: - """Return list of qubit in this system.""" - return self._qubits - - @property - def registers(self) -> List[RegisterSlot]: - """Return system's register slots.""" - return self._reg_slots - - @property - def memoryslots(self) -> List[MemorySlot]: - """Return system's memory slots.""" - return self._mem_slots diff --git a/qiskit/pulse/channels/pulse_channels.py b/qiskit/pulse/channels/pulse_channels.py deleted file mode 100644 index 3ab81508c2cf..000000000000 --- a/qiskit/pulse/channels/pulse_channels.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -This import path is being deprecated. Please use qiskit.pulse.channels. -""" -import warnings - -# pylint: disable=unused-import - -from .channels import PulseChannel, DriveChannel, MeasureChannel, ControlChannel -warnings.warn("Channels have been migrated. Please use `from qiskit.pulse.channels import X` " - "rather than `from qiskit.pulse.channels.pulse_channels import X`.", - DeprecationWarning) diff --git a/qiskit/pulse/channels/qubit.py b/qiskit/pulse/channels/qubit.py deleted file mode 100644 index 616721633cc6..000000000000 --- a/qiskit/pulse/channels/qubit.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Physical qubit. -""" -import warnings - -from typing import Tuple, Optional - -from .channels import AcquireChannel, DriveChannel, ControlChannel, MeasureChannel - - -class Qubit: - """Physical qubit.""" - - def __init__(self, index: int, - drive_channel: DriveChannel, - measure_channel: MeasureChannel, - acquire_channel: AcquireChannel, - control_channels: Optional[Tuple[ControlChannel]] = None): - warnings.warn("Qubit is deprecated. Use backend.configuration() instead.", - DeprecationWarning) - self._index = index - self._drive = drive_channel - self._controls = tuple(control_channels) if control_channels else tuple() - self._measure = measure_channel - self._acquire = acquire_channel - - @property - def index(self) -> int: - """Return the index of this qubit.""" - return self._index - - @property - def drive(self) -> DriveChannel: - """Return the drive channel of this qubit.""" - return self._drive - - @property - def measure(self) -> MeasureChannel: - """Return the measure channel of this qubit.""" - return self._measure - - @property - def acquire(self) -> AcquireChannel: - """Return the primary acquire channel of this qubit.""" - return self._acquire - - @property - def controls(self) -> Tuple[ControlChannel]: - """Return the control channels for this qubit.""" - return self._controls - - def __eq__(self, other): - """Two physical qubits are the same if they have the same index and channels. - - Args: - other (Qubit): other Qubit - - Returns: - bool: are self and other equal. - """ - # pylint: disable=too-many-boolean-expressions - if (type(self) is type(other) and - self.index == other.index and - self.drive == other.drive and - self.measure == other.measure and - self.acquire == other.acquire and - self.controls == other.controls): - return True - return False diff --git a/qiskit/pulse/cmd_def.py b/qiskit/pulse/cmd_def.py index f1e661f92537..7152ad659954 100644 --- a/qiskit/pulse/cmd_def.py +++ b/qiskit/pulse/cmd_def.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. """ +Deprecated. Use InstructionScheduleMap instead. + Command definition module. Relates circuit gates to pulse commands. """ import warnings diff --git a/qiskit/pulse/commands/acquire.py b/qiskit/pulse/commands/acquire.py index e85cec70e6ee..fdc35f6f28d9 100644 --- a/qiskit/pulse/commands/acquire.py +++ b/qiskit/pulse/commands/acquire.py @@ -18,7 +18,7 @@ import warnings from typing import Optional, Union, List -from qiskit.pulse.channels import Qubit, MemorySlot, RegisterSlot, AcquireChannel +from qiskit.pulse.channels import MemorySlot, RegisterSlot, AcquireChannel from qiskit.pulse.exceptions import PulseError from .instruction import Instruction from .meas_opts import Discriminator, Kernel @@ -127,10 +127,6 @@ def __init__(self, if not isinstance(acquire, list): acquire = [acquire] - if isinstance(acquire[0], Qubit): - raise PulseError("AcquireInstruction can not be instantiated with Qubits, " - "which are deprecated.") - if mem_slot and not isinstance(mem_slot, list): mem_slot = [mem_slot] elif mem_slots: diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index 16247c3b4e94..7a9d1d94fbf2 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -15,12 +15,18 @@ """ A convenient way to track reusable subschedules by name and qubit. -This can be used for scheduling circuits with custom definitions, for instance: +This can be used for scheduling circuits with custom definitions, for instance:: inst_map = InstructionScheduleMap() inst_map.add('new_inst', 0, qubit_0_new_inst_schedule) sched = schedule(quantum_circuit, backend, inst_map) + +An instance of this class is instantiated by Pulse-enabled backends and populated with defaults +(if available):: + + inst_map = backend.defaults().instruction_schedule_map + """ import warnings @@ -32,13 +38,15 @@ class InstructionScheduleMap(): - """Mapping from QuantumCircuit Instruction names to Schedules. In particular: + """Mapping from :py:class:`~qiskit.circuit.QuantumCircuit` + :py:class:`qiskit.circuit.Instruction` names and qubits to + :py:class:`~qiskit.pulse.Schedule` s. In particular, the mapping is formatted as type:: Dict[str, Dict[Tuple[int], Schedule]] - where the first key is the name of a circuit instruction (e.g. 'u1', 'measure'), the second - key is a tuple of qubit indices, and the final value is a Schedule implementing the requested - instruction. + where the first key is the name of a circuit instruction (e.g. ``'u1'``, ``'measure'``), the + second key is a tuple of qubit indices, and the final value is a Schedule implementing the + requested instruction. """ def __init__(self): @@ -50,9 +58,10 @@ def __init__(self): @property def instructions(self) -> List[str]: - """ - Return all instructions which have definitions. By default, these are typically the basis - gates along with other instructions such as measure and reset. + """Return all instructions which have definitions. + + By default, these are typically the basis gates along with other instructions such as + measure and reset. Returns: The names of all the circuit instructions which have Schedule definitions in this. @@ -60,8 +69,7 @@ def instructions(self) -> List[str]: return list(self._map.keys()) def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int]]]: - """ - Return a list of the qubits for which the given instruction is defined. Single qubit + """Return a list of the qubits for which the given instruction is defined. Single qubit instructions return a flat list, and multiqubit instructions return a list of ordered tuples. @@ -71,6 +79,7 @@ def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int Returns: Qubit indices which have the given instruction defined. This is a list of tuples if the instruction has an arity greater than 1, or a flat list of ints otherwise. + Raises: PulseError: If the instruction is not found. """ @@ -80,25 +89,24 @@ def qubits_with_instruction(self, instruction: str) -> List[Union[int, Tuple[int for qubits in sorted(self._map[instruction].keys())] def qubit_instructions(self, qubits: Union[int, Iterable[int]]) -> List[str]: - """ - Return a list of the instruction names that are defined by the backend for the given qubit - or qubits. + """Return a list of the instruction names that are defined by the backend for the given + qubit or qubits. Args: qubits: A qubit index, or a list or tuple of indices. Returns: - All the instructions which are defined on the qubits. For 1 qubit, all the 1Q - instructions defined. For multiple qubits, all the instructions which apply to that - whole set of qubits (e.g. qubits=[0, 1] may return ['cx']). + All the instructions which are defined on the qubits. + + For 1 qubit, all the 1Q instructions defined. For multiple qubits, all the instructions + which apply to that whole set of qubits (e.g. ``qubits=[0, 1]`` may return ``['cx']``). """ if _to_tuple(qubits) in self._qubit_instructions: return list(self._qubit_instructions[_to_tuple(qubits)]) return [] def has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> bool: - """ - Is the instruction defined for the given qubits? + """Is the instruction defined for the given qubits? Args: instruction: The instruction for which to look. @@ -111,16 +119,12 @@ def has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> bool: _to_tuple(qubits) in self._map[instruction] def assert_has(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: - """ - Convenience method to check that the given instruction is defined, and error if it is not. + """Error if the given instruction is not defined. Args: instruction: The instruction for which to look. qubits: The specific qubits for the instruction. - Returns: - None - Raises: PulseError: If the instruction is not defined on the qubits. """ @@ -138,8 +142,8 @@ def get(self, qubits: Union[int, Iterable[int]], *params: List[Union[int, float, complex]], **kwparams: Dict[str, Union[int, float, complex]]) -> Schedule: - """ - Return the defined Schedule for the given instruction on the given qubits. + """Return the defined :py:class:`~qiskit.pulse.Schedule` for the given instruction on + the given qubits. Args: instruction: Name of the instruction. @@ -157,8 +161,7 @@ def get(self, return schedule def get_parameters(self, instruction: str, qubits: Union[int, Iterable[int]]) -> Tuple[str]: - """ - Return the list of parameters taken by the given instruction on the given qubits. + """Return the list of parameters taken by the given instruction on the given qubits. Args: instruction: Name of the instruction. @@ -173,18 +176,14 @@ def get_parameters(self, instruction: str, qubits: Union[int, Iterable[int]]) -> def add(self, instruction: str, qubits: Union[int, Iterable[int]], - schedule: [Schedule, ParameterizedSchedule]) -> None: - """ - Add a new known instruction for the given qubits and its mapping to a pulse schedule. + schedule: Union[Schedule, ParameterizedSchedule]) -> None: + """Add a new known instruction for the given qubits and its mapping to a pulse schedule. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. schedule: The Schedule that implements the given instruction. - Returns: - None - Raises: PulseError: If the qubits are provided as an empty iterable. """ @@ -197,14 +196,11 @@ def add(self, self._qubit_instructions[qubits].add(instruction) def remove(self, instruction: str, qubits: Union[int, Iterable[int]]) -> None: - """Remove the given instruction from the defined instructions. + """Remove the given instruction from the listing of instructions defined in self. Args: instruction: The name of the instruction to add. qubits: The qubits which the instruction applies to. - - Returns: - None """ qubits = _to_tuple(qubits) self.assert_has(instruction, qubits) @@ -220,8 +216,8 @@ def pop(self, qubits: Union[int, Iterable[int]], *params: List[Union[int, float, complex]], **kwparams: Dict[str, Union[int, float, complex]]) -> Schedule: - """ - Remove and return the defined Schedule for the given instruction on the given qubits. + """Remove and return the defined ``Schedule`` for the given instruction on the given + qubits. Args: instruction: Name of the instruction. @@ -240,8 +236,7 @@ def pop(self, return schedule def cmds(self) -> List[str]: - """ - Deprecated. + """Deprecated. Returns: The names of all the circuit instructions which have Schedule definitions in this. @@ -251,8 +246,7 @@ def cmds(self) -> List[str]: return self.instructions def cmd_qubits(self, cmd_name: str) -> List[Union[int, Tuple[int]]]: - """ - Deprecated. + """Deprecated. Args: cmd_name: The name of the circuit instruction. @@ -278,14 +272,14 @@ def __str__(self): "".format(name=self.__class__.__name__, insts=instructions)) -def _to_tuple(values): - """ - Return the input as a tuple, even if it is an integer. +def _to_tuple(values: Union[int, Iterable[int]]) -> Tuple[int, ...]: + """Return the input as a tuple. Args: - values (Union[int, Iterable[int]]): An integer, or iterable of integers. + values: An integer, or iterable of integers. + Returns: - tuple: The input values as a sorted tuple. + The input values as a sorted tuple. """ try: return tuple(values) diff --git a/qiskit/pulse/parser.py b/qiskit/pulse/parser.py index 8b78ff2ba432..3000903ec740 100644 --- a/qiskit/pulse/parser.py +++ b/qiskit/pulse/parser.py @@ -14,8 +14,8 @@ # pylint: disable=invalid-name -"""Helper function to parse string expression given by backends.""" - +"""Parser for mathematical string expressions returned by backends.""" +from typing import Dict, List, Union import ast import copy import operator @@ -26,9 +26,8 @@ class PulseExpression(ast.NodeTransformer): - """Expression parser to evaluate parameter values. - """ - # valid functions + """Expression parser to evaluate parameter values.""" + _math_ops = { 'acos': cmath.acos, 'acosh': cmath.acosh, @@ -49,8 +48,8 @@ class PulseExpression(ast.NodeTransformer): 'pi': cmath.pi, 'e': cmath.e } + """Valid math functions.""" - # valid binary operations _binary_ops = { ast.Add: operator.add, ast.Sub: operator.sub, @@ -58,19 +57,20 @@ class PulseExpression(ast.NodeTransformer): ast.Div: operator.truediv, ast.Pow: operator.pow } + """Valid binary operations.""" - # valid unary operations _unary_ops = { ast.UAdd: operator.pos, ast.USub: operator.neg } + """Valid unary operations.""" - def __init__(self, source, partial_binding=False): + def __init__(self, source: Union[str, ast.Expression], partial_binding: bool = False): """Create new evaluator. Args: - source (str or ast.Expression): Expression of equation to evaluate. - partial_binding (bool): Allow partial bind of parameters. + source: Expression of equation to evaluate. + partial_binding: Allow partial bind of parameters. Raises: PulseError: When invalid string is specified. @@ -91,19 +91,23 @@ def __init__(self, source, partial_binding=False): self.visit(self._tree) @property - def params(self): - """Get parameters.""" + def params(self) -> List[str]: + """Get parameters. + + Returns: + A list of parameters in sorted order. + """ return sorted(self._params.copy()) - def __call__(self, *args, **kwargs): - """Get evaluated value with given parameters. + def __call__(self, *args, **kwargs) -> Union[float, complex, ast.Expression]: + """Evaluate the expression with the given values of the expression's parameters. Args: *args: Variable length parameter list. **kwargs: Arbitrary parameters. Returns: - float or complex or ast: Evaluated value. + Evaluated value. Raises: PulseError: When parameters are not bound. @@ -137,16 +141,16 @@ def __call__(self, *args, **kwargs): return expr.body.n @staticmethod - def _match_ops(opr, opr_dict, *args): + def _match_ops(opr: ast.AST, opr_dict: Dict, *args) -> Union[float, complex]: """Helper method to apply operators. Args: - opr (ast.AST): Operator of node. - opr_dict (dict): Mapper from ast to operator. + opr: Operator of node. + opr_dict: Mapper from ast to operator. *args: Arguments supplied to operator. Returns: - float or complex: Evaluated value. + Evaluated value. Raises: PulseError: When unsupported operation is specified. @@ -156,39 +160,39 @@ def _match_ops(opr, opr_dict, *args): return op_func(*args) raise PulseError('Operator %s is not supported.' % opr.__class__.__name__) - def visit_Expression(self, node): + def visit_Expression(self, node: ast.Expression) -> ast.Expression: """Evaluate children nodes of expression. Args: - node (ast.Expression): Expression to evaluate. + node: Expression to evaluate. Returns: - ast.Expression: Evaluated value. + Evaluated value. """ tmp_node = copy.deepcopy(node) tmp_node.body = self.visit(tmp_node.body) return tmp_node - def visit_Num(self, node): + def visit_Num(self, node: ast.Num) -> ast.Num: """Return number as it is. Args: - node (ast.Num): Number. + node: Number. Returns: - ast.Num: Number to return. + Input node. """ return node - def visit_Name(self, node): + def visit_Name(self, node: ast.Name) -> Union[ast.Name, ast.Num]: """Evaluate name and return ast.Num if it is bound. Args: - node (ast.Name): Name to evaluate. + node: Name to evaluate. Returns: - ast.Name or ast.Num: Evaluated value. + Evaluated value. Raises: PulseError: When parameter value is not a number. @@ -210,14 +214,14 @@ def visit_Name(self, node): self._params.add(node.id) return node - def visit_UnaryOp(self, node): + def visit_UnaryOp(self, node: ast.UnaryOp) -> Union[ast.UnaryOp, ast.Num]: """Evaluate unary operation and return ast.Num if operand is bound. Args: - node (ast.UnaryOp): Unary operation to evaluate. + node: Unary operation to evaluate. Returns: - ast.UnaryOp or ast.Num: Evaluated value. + Evaluated value. """ node.operand = self.visit(node.operand) if isinstance(node.operand, ast.Num): @@ -226,14 +230,14 @@ def visit_UnaryOp(self, node): return ast.copy_location(val, node) return node - def visit_BinOp(self, node): + def visit_BinOp(self, node: ast.BinOp) -> Union[ast.BinOp, ast.Num]: """Evaluate binary operation and return ast.Num if operands are bound. Args: - node (ast.BinOp): Binary operation to evaluate. + node: Binary operation to evaluate. Returns: - ast.BinOp or ast.Num: Evaluated value. + Evaluated value. """ node.left = self.visit(node.left) node.right = self.visit(node.right) @@ -243,14 +247,14 @@ def visit_BinOp(self, node): return ast.copy_location(val, node) return node - def visit_Call(self, node): + def visit_Call(self, node: ast.Call) -> Union[ast.Call, ast.Num]: """Evaluate function and return ast.Num if all arguments are bound. Args: - node (ast.Call): Function to evaluate. + node: Function to evaluate. Returns: - ast.Call or ast.Num: Evaluated value. + Evaluated value. Raises: PulseError: When unsupported or unsafe function is specified. @@ -273,20 +277,31 @@ def generic_visit(self, node): raise PulseError('Unsupported node: %s' % node.__class__.__name__) -def parse_string_expr(source, partial_binding=False): +def parse_string_expr(source: str, partial_binding: bool = False): """Safe parsing of string expression. Args: - source (str): String expression to parse. - partial_binding (bool): Allow partial bind of parameters. + source: String expression to parse. + partial_binding: Allow partial bind of parameters. Returns: PulseExpression: Returns a expression object. + + Example: + + expr = 'P1 + P2 + P3' + parsed_expr = parse_string_expr(expr, partial_binding=True) + + # create new PulseExpression + bound_two = parsed_expr(P1=1, P2=2) + # evaluate expression + value1 = bound_two(P3=3) + value2 = bound_two(P3=4) + value3 = bound_two(P3=5) + """ subs = [('numpy.', ''), ('np.', ''), ('math.', ''), ('cmath.', '')] for match, sub in subs: source = source.replace(match, sub) - expression = PulseExpression(source, partial_binding) - - return expression + return PulseExpression(source, partial_binding) diff --git a/qiskit/pulse/pulse_lib/__init__.py b/qiskit/pulse/pulse_lib/__init__.py index d7a0b99b9192..dd0e35a95fa8 100644 --- a/qiskit/pulse/pulse_lib/__init__.py +++ b/qiskit/pulse/pulse_lib/__init__.py @@ -12,6 +12,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Module for builtin pulse_lib.""" +"""Module for builtin ``pulse_lib``.""" from .discrete import * diff --git a/qiskit/pulse/pulse_lib/discrete.py b/qiskit/pulse/pulse_lib/discrete.py index 06950b0ca3d6..69d6e9efa4b7 100644 --- a/qiskit/pulse/pulse_lib/discrete.py +++ b/qiskit/pulse/pulse_lib/discrete.py @@ -30,9 +30,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'SamplePulse': - """Generates constant-sampled `SamplePulse`. + r"""Generates constant-sampled :class:`~qiskit.pulse.SamplePulse`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + For :math:`A=` ``amp``, samples from the function: + + .. math:: + + f(x) = A Args: duration: Duration of pulse. Must be greater than zero. @@ -46,7 +50,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'Sample def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse': - """Generates zero-sampled `SamplePulse`. + """Generates zero-sampled :class:`~qiskit.pulse.SamplePulse`. + + Samples from the function: + + .. math:: + + f(x) = 0 Args: duration: Duration of pulse. Must be greater than zero. @@ -60,14 +70,23 @@ def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse': def square(duration: int, amp: complex, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates square wave `SamplePulse`. + r"""Generates square wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \text{sign}\left[ \sin\left(\frac{2 \pi x}{T} + 2\phi\right) \right] + + with the convention :math:`\text{sign}(0) = 1`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + period: Pulse period, units of dt. If ``None``, defaults to single cycle. phase: Pulse phase. name: Name of pulse. """ @@ -82,14 +101,35 @@ def square(duration: int, amp: complex, period: float = None, def sawtooth(duration: int, amp: complex, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates sawtooth wave `SamplePulse`. + r"""Generates sawtooth wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = 2 A \left( g(x) - \left\lfloor \frac{1}{2} + g(x) \right\rfloor\right) + + where :math:`g(x) = x/T + \phi/\pi`. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + period: Pulse period, units of dt. If ``None``, defaults to single cycle. phase: Pulse phase. name: Name of pulse. + + Example: + .. jupyter-execute:: + + import matplotlib.pyplot as plt + from qiskit.pulse.pulse_lib import sawtooth + + duration = 100 + amp = 1 + period = duration + plt.plot(range(duration), sawtooth(duration, amp, period).samples) """ if period is None: period = duration @@ -102,16 +142,35 @@ def sawtooth(duration: int, amp: complex, period: float = None, def triangle(duration: int, amp: complex, period: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates triangle wave `SamplePulse`. + r"""Generates triangle wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \left(-2\left|\text{sawtooth}(x, A, T, \phi)\right| + 1\right) - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + This a non-sinusoidal wave with linear ramping. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude. Wave range is [-amp, amp]. - period: Pulse period, units of dt. If `None` defaults to single cycle. + amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`. + period: Pulse period, units of dt. If ``None``, defaults to single cycle. phase: Pulse phase. name: Name of pulse. + + Example: + .. jupyter-execute:: + + import matplotlib.pyplot as plt + from qiskit.pulse.pulse_lib import triangle + + duration = 100 + amp = 1 + period = duration + plt.plot(range(duration), triangle(duration, amp, period).samples) """ if period is None: period = duration @@ -124,14 +183,20 @@ def triangle(duration: int, amp: complex, period: float = None, def cos(duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates cosine wave `SamplePulse`. + r"""Generates cosine wave :class:`~qiskit.pulse.SamplePulse`. - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \cos(2 \pi \omega x + \phi) Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. - freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle. + freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle. phase: Pulse phase. name: Name of pulse. """ @@ -146,12 +211,20 @@ def cos(duration: int, amp: complex, freq: float = None, def sin(duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None) -> 'SamplePulse': - """Generates sine wave `SamplePulse`. + r"""Generates sine wave :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: + + .. math:: + + f(x) = A \sin(2 \pi \omega x + \phi) Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. - freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle. + freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle. phase: Pulse phase. name: Name of pulse. """ @@ -166,18 +239,32 @@ def sin(duration: int, amp: complex, freq: float = None, def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates unnormalized gaussian `SamplePulse`. + r"""Generates unnormalized gaussian :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: - Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent large - initial/final discontinuities. + f(x) = A\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right), - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + with the center :math:`\mu=` ``duration/2``. - Integrated area under curve is $\Omega_g(amp, sigma) = amp \times np.sqrt(2\pi \sigma^2)$ + If ``zero_ends==True``, each output sample :math:`y` is modifed according to: + + .. math:: + + y \mapsto A\frac{y-y^*}{A-y^*}, + + where :math:`y^*` is the value of the endpoint samples. This sets the endpoints + to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`, + :math:`y` is set to :math:`1`. + + Integrated area under the full curve is ``amp * np.sqrt(2*np.pi*sigma**2)`` Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `duration/2`. + amp: Pulse amplitude at ``duration/2``. sigma: Width (standard deviation) of pulse. name: Name of pulse. zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. @@ -195,13 +282,20 @@ def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = No def gaussian_deriv(duration: int, amp: complex, sigma: float, name: Optional[str] = None) -> 'SamplePulse': - r"""Generates unnormalized gaussian derivative `SamplePulse`. + r"""Generates unnormalized gaussian derivative :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma`` applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + f(x) = A\frac{(x - \mu)}{\sigma^2}\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right) + + i.e. the derivative of the Gaussian function, with center :math:`\mu=` ``duration/2``. Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `center`. + amp: Pulse amplitude of corresponding Gaussian at the pulse center (``duration/2``). sigma: Width (standard deviation) of pulse. name: Name of pulse. """ @@ -214,11 +308,26 @@ def gaussian_deriv(duration: int, amp: complex, sigma: float, def sech(duration: int, amp: complex, sigma: float, name: str = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates unnormalized sech `SamplePulse`. + r"""Generates unnormalized sech :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy + to generate a discrete pulse sampled from the continuous function: + + .. math:: + + f(x) = A\text{sech}\left(\frac{x-\mu}{\sigma} \right) + + with the center :math:`\mu=` ``duration/2``. + + If ``zero_ends==True``, each output sample :math:`y` is modifed according to: - Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity. + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + y \mapsto A\frac{y-y^*}{A-y^*}, + + where :math:`y^*` is the value of the endpoint samples. This sets the endpoints + to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`, + :math:`y` is set to :math:`1`. Args: duration: Duration of pulse. Must be greater than zero. @@ -239,9 +348,16 @@ def sech(duration: int, amp: complex, sigma: float, name: str = None, def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> 'SamplePulse': - r"""Generates unnormalized sech derivative `SamplePulse`. + r"""Generates unnormalized sech derivative :class:`~qiskit.pulse.SamplePulse`. + + For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and center :math:`\mu=` ``duration/2``, + applies the `midpoint` sampling strategy to generate a discrete pulse sampled from + the continuous function: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + .. math:: + f(x) = \frac{d}{dx}\left[A\text{sech}\left(\frac{x-\mu}{\sigma} \right)\right], + + i.e. the derivative of :math:`\text{sech}`. Args: duration: Duration of pulse. Must be greater than zero. @@ -259,25 +375,40 @@ def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> ' def gaussian_square(duration: int, amp: complex, sigma: float, risefall: Optional[float] = None, width: Optional[float] = None, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - """Generates gaussian square `SamplePulse`. + r"""Generates gaussian square :class:`~qiskit.pulse.SamplePulse`. + + For :math:`d=` ``duration``, :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, + and :math:`r=` ``risefall``, applies the `midpoint` sampling strategy to + generate a discrete pulse sampled from the continuous function: - Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent - large initial/final discontinuities. + .. math:: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + f(x) = \begin{cases} + g(x - r) ) & x\leq r \\ + A & r\leq x\leq d-r \\ + g(x - (d - r)) & d-r\leq x + \end{cases} + + where :math:`g(x)` is the Gaussian function sampled from in :meth:`gaussian` + with :math:`A=` ``amp``, :math:`\mu=1`, and :math:`\sigma=` ``sigma``. I.e. + :math:`f(x)` represents a square pulse with smooth Gaussian edges. + + If ``zero_ends == True``, the samples for the Gaussian ramps are remapped as in + :meth:`gaussian`. Args: duration: Duration of pulse. Must be greater than zero. amp: Pulse amplitude. sigma: Width (standard deviation) of Gaussian rise/fall portion of the pulse. risefall: Number of samples over which pulse rise and fall happen. Width of - square portion of pulse will be `duration-2*risefall`. - width: The duration of the embedded square pulse. Only one of `width` or `risefall` - should be specified since width = duration - 2 * risefall. + square portion of pulse will be ``duration-2*risefall``. + width: The duration of the embedded square pulse. Only one of ``width`` or ``risefall`` + should be specified as the functional form requires + ``width = duration - 2 * risefall``. name: Name of pulse. - zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. + zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp. Raises: - PulseError: If risefall and width arguments are inconsistent or not enough info. + PulseError: If ``risefall`` and ``width`` arguments are inconsistent or not enough info. """ if risefall is None and width is None: raise PulseError("gaussian_square missing required argument: 'width' or 'risefall'.") @@ -299,25 +430,41 @@ def gaussian_square(duration: int, amp: complex, sigma: float, def drag(duration: int, amp: complex, sigma: float, beta: float, name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse': - r"""Generates Y-only correction DRAG `SamplePulse` for standard nonlinear oscillator (SNO) [1]. + r"""Generates Y-only correction DRAG :class:`~qiskit.pulse.SamplePulse` for standard nonlinear + oscillator (SNO) [1]. - Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity. + For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and :math:`\beta=` ``beta``, applies the + `midpoint` sampling strategy to generate a discrete pulse sampled from the + continuous function: - Applies `midpoint` sampling strategy to generate discrete pulse from continuous function. + .. math:: - [1] Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K. - Analytic control methods for high-fidelity unitary operations - in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011). + f(x) = g(x) + i \beta h(x), + + where :math:`g(x)` is the function sampled in :meth:`gaussian`, and :math:`h(x)` + is the function sampled in :meth:`gaussian_deriv`. + + If ``zero_ends == True``, the samples from :math:`g(x)` are remapped as in :meth:`gaussian`. + + References: + 1. |citation1|_ + + .. _citation1: http://dx.doi.org/10.1103/PhysRevA.83.012308 + + .. |citation1| replace:: *Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K. + "Analytic control methods for high-fidelity unitary operations + in a weakly nonlinear oscillator." Phys. Rev. A 83, 012308 (2011).* Args: duration: Duration of pulse. Must be greater than zero. - amp: Pulse amplitude at `center`. + amp: Pulse amplitude at center ``duration/2``. sigma: Width (standard deviation) of pulse. - beta: Y correction amplitude. For the SNO this is $\beta=-\frac{\lambda_1^2}{4\Delta_2}$. - Where $\lambds_1$ is the relative coupling strength between the first excited and second - excited states and $\Delta_2$ is the detuning between the respective excited states. + beta: Y correction amplitude. For the SNO this is + :math:`\beta=-\frac{\lambda_1^2}{4\Delta_2}`. Where :math:`\lambda_1` is the + relative coupling strength between the first excited and second excited states + and :math:`\Delta_2` is the detuning between the respective excited states. name: Name of pulse. - zero_ends: If True, make the first and last sample zero, but rescale to preserve amp. + zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp. """ center = duration/2 zeroed_width = duration if zero_ends else None diff --git a/qiskit/pulse/reschedule.py b/qiskit/pulse/reschedule.py index ef750cd4279d..f23c92d4390b 100644 --- a/qiskit/pulse/reschedule.py +++ b/qiskit/pulse/reschedule.py @@ -51,8 +51,10 @@ def align_measures(schedules: Iterable[ScheduleComponent], cal_gate: The name of the gate to inspect for the calibration time max_calibration_duration: If provided, cmd_def and cal_gate will be ignored align_time: If provided, this will be used as final align time. + Returns: Schedule + Raises: PulseError: if an acquire or pulse is encountered on a channel that has already been part of an acquire, or if align_time is negative @@ -62,7 +64,8 @@ def align_measures(schedules: Iterable[ScheduleComponent], def calculate_align_time(): """Return the the max between the duration of the calibration time and the absolute time - of the latest scheduled acquire.""" + of the latest scheduled acquire. + """ nonlocal max_calibration_duration if max_calibration_duration is None: max_calibration_duration = get_max_calibration_duration() @@ -129,15 +132,15 @@ def add_implicit_acquires(schedule: ScheduleComponent, meas_map: List[List[int]] """Return a new schedule with implicit acquires from the measurement mapping replaced by explicit ones. - Warning: - Since new acquires are being added, Memory Slots will be set to match the qubit index. This - may overwrite your specification. + .. warning:: Since new acquires are being added, Memory Slots will be set to match the + qubit index. This may overwrite your specification. Args: - schedule: Schedule to be aligned - meas_map: List of lists of qubits that are measured together + schedule: Schedule to be aligned. + meas_map: List of lists of qubits that are measured together. + Returns: - Schedule + A ``Schedule`` with the additional acquisition commands. """ new_schedule = Schedule(name=schedule.name) acquire_map = dict() @@ -171,19 +174,20 @@ def add_implicit_acquires(schedule: ScheduleComponent, meas_map: List[List[int]] return new_schedule -def pad(schedule: Schedule, channels: Optional[Iterable[Channel]] = None, +def pad(schedule: Schedule, + channels: Optional[Iterable[Channel]] = None, until: Optional[int] = None) -> Schedule: - """Pad the input Schedule with `Delay`s on all unoccupied timeslots until - `schedule.duration` or `until` if not `None`. + """Pad the input ``Schedule`` with ``Delay`` s on all unoccupied timeslots until ``until`` + if it is provided, otherwise until ``schedule.duration``. Args: schedule: Schedule to pad. - channels: Channels to pad. Defaults to all channels in - `schedule` if not provided. If the supplied channel is not a member - of `schedule` it will be added. - until: Time to pad until. Defaults to `schedule.duration` if not provided. + channels: Channels to pad. Defaults to all channels in ``schedule`` if not provided. + If the supplied channel is not a member of ``schedule``, it will be added. + until: Time to pad until. Defaults to ``schedule.duration`` if not provided. + Returns: - Schedule: The padded schedule + The padded schedule. """ until = until or schedule.duration diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 7136e25d9906..b0118dddccc8 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -360,7 +360,9 @@ def interval_filter(time_inst: Tuple[int, 'Instruction']) -> bool: def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, filename: Optional[str] = None, interp_method: Optional[Callable] = None, - scale: float = None, channels_to_plot: Optional[List[Channel]] = None, + scale: Optional[float] = None, + channel_scales: Optional[Dict[Channel, float]] = None, + channels_to_plot: Optional[List[Channel]] = None, plot_all: bool = False, plot_range: Optional[Tuple[float]] = None, interactive: bool = False, table: bool = True, label: bool = False, framechange: bool = True, scaling: float = None, @@ -373,7 +375,8 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, style: A style sheet to configure plot appearance filename: Name required to save pulse image interp_method: A function for interpolation - scale: Relative visual scaling of waveform amplitudes + scale: Relative visual scaling of waveform amplitudes, see Additional Information. + channel_scales: Channel independent scaling as a dictionary of `Channel` object. channels_to_plot: Deprecated, see `channels` plot_all: Plot empty channels plot_range: A tuple of time range to plot @@ -386,6 +389,19 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, channels: A list of channel names to plot show_framechange_channels: Plot channels with only framechanges + Additional Information: + If you want to manually rescale the waveform amplitude of channels one by one, + you can set `channel_scales` argument instead of `scale`. + The `channel_scales` should be given as a python dictionary:: + + channel_scales = {pulse.DriveChannels(0): 10.0, + pulse.MeasureChannels(0): 5.0} + + When the channel to plot is not included in the `channel_scales` dictionary, + scaling factor of that channel is overwritten by the value of `scale` argument. + In default, waveform amplitude is normalized by the maximum amplitude of the channel. + The scaling factor is displayed under the channel name alias. + Returns: matplotlib.figure: A matplotlib figure object of the pulse schedule. """ @@ -404,9 +420,9 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None, return visualization.pulse_drawer(self, dt=dt, style=style, filename=filename, interp_method=interp_method, - scale=scale, plot_all=plot_all, - plot_range=plot_range, interactive=interactive, - table=table, label=label, + scale=scale, channel_scales=channel_scales, + plot_all=plot_all, plot_range=plot_range, + interactive=interactive, table=table, label=label, framechange=framechange, channels=channels, show_framechange_channels=show_framechange_channels) diff --git a/qiskit/pulse/utils.py b/qiskit/pulse/utils.py deleted file mode 100644 index 0f7db15ed33e..000000000000 --- a/qiskit/pulse/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Pulse utilities. -""" diff --git a/qiskit/qasm/libs/qelib1.inc b/qiskit/qasm/libs/qelib1.inc index 4b5b99eec505..967dad4623bd 100644 --- a/qiskit/qasm/libs/qelib1.inc +++ b/qiskit/qasm/libs/qelib1.inc @@ -140,3 +140,38 @@ gate rzz(theta) a,b u1(theta) b; cx a,b; } +// relative-phase CCX +gate rccx a,b,c +{ + u2(0,pi) c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + cx a, c; + u1(pi/4) c; + cx b, c; + u1(-pi/4) c; + u2(0,pi) c; +} +// relative-phase CCCX +gate rcccx a,b,c,d +{ + u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + cx a,d; + u1(pi/4) d; + cx b,d; + u1(-pi/4) d; + u2(0,pi) d; + u1(pi/4) d; + cx c,d; + u1(-pi/4) d; + u2(0,pi) d; +} diff --git a/qiskit/scheduler/__init__.py b/qiskit/scheduler/__init__.py index 5f9a5e31080b..6f9d0de2276e 100644 --- a/qiskit/scheduler/__init__.py +++ b/qiskit/scheduler/__init__.py @@ -19,13 +19,22 @@ .. currentmodule:: qiskit.scheduler -Circuit to pulse scheduling functionality +A scheduler compiles a circuit program to a pulse program. .. autosummary:: :toctree: ../stubs/ schedule_circuit ScheduleConfig + +Scheduling utility functions + +.. autosummary:: + :toctree: ../stubs/ + + qiskit.scheduler.utils + +.. automodule:: qiskit.scheduler.methods """ from qiskit.scheduler.config import ScheduleConfig diff --git a/qiskit/scheduler/config.py b/qiskit/scheduler/config.py index 8e1c1ea1be06..9242975e5efd 100644 --- a/qiskit/scheduler/config.py +++ b/qiskit/scheduler/config.py @@ -31,8 +31,8 @@ def __init__(self, Container for information needed to schedule a QuantumCircuit into a pulse Schedule. Args: - inst_map: The schedule definition of all gates supported on a backend - meas_map: A list of groups of qubits which have to be measured together + inst_map: The schedule definition of all gates supported on a backend. + meas_map: A list of groups of qubits which have to be measured together. """ self.inst_map = inst_map self.meas_map = format_meas_map(meas_map) diff --git a/qiskit/scheduler/methods/__init__.py b/qiskit/scheduler/methods/__init__.py index e2dd68b89131..c6384c63fe67 100644 --- a/qiskit/scheduler/methods/__init__.py +++ b/qiskit/scheduler/methods/__init__.py @@ -12,4 +12,15 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Pulse scheduling methods.""" +""" +.. currentmodule:: qiskit.scheduler.methods + +Pulse scheduling methods. + +.. autosummary:: + :toctree: ../stubs/ + + basic +""" + +from .basic import * diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py index e7ae056dde39..fa5f1317984f 100644 --- a/qiskit/scheduler/methods/basic.py +++ b/qiskit/scheduler/methods/basic.py @@ -12,7 +12,9 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The most straightforward scheduling methods: scheduling as early or as late as possible.""" +""" +The most straightforward scheduling methods: scheduling **as early** or **as late** as possible. +""" from collections import defaultdict, namedtuple from typing import List @@ -38,16 +40,20 @@ def as_soon_as_possible(circuit: QuantumCircuit, schedule_config: ScheduleConfig) -> Schedule: """ Return the pulse Schedule which implements the input circuit using an "as soon as possible" - (asap) scheduling policy. Circuit instructions are first each mapped to equivalent pulse + (asap) scheduling policy. + + Circuit instructions are first each mapped to equivalent pulse Schedules according to the command definition given by the schedule_config. Then, this circuit instruction-equivalent Schedule is appended at the earliest time at which all qubits involved in the instruction are available. Args: - circuit: The quantum circuit to translate - schedule_config: Backend specific parameters used for building the Schedule + circuit: The quantum circuit to translate. + schedule_config: Backend specific parameters used for building the Schedule. + Returns: - A schedule corresponding to the input `circuit` with pulses occurring as early as possible + A schedule corresponding to the input ``circuit`` with pulses occurring as early as + possible. """ sched = Schedule(name=circuit.name) @@ -73,7 +79,9 @@ def as_late_as_possible(circuit: QuantumCircuit, schedule_config: ScheduleConfig) -> Schedule: """ Return the pulse Schedule which implements the input circuit using an "as late as possible" - (alap) scheduling policy. Circuit instructions are first each mapped to equivalent pulse + (alap) scheduling policy. + + Circuit instructions are first each mapped to equivalent pulse Schedules according to the command definition given by the schedule_config. Then, this circuit instruction-equivalent Schedule is appended at the latest time that it can be without allowing unnecessary time between instructions or allowing instructions with common qubits to overlap. @@ -82,10 +90,12 @@ def as_late_as_possible(circuit: QuantumCircuit, maximize the time that the qubit remains in the ground state. Args: - circuit: The quantum circuit to translate - schedule_config: Backend specific parameters used for building the Schedule + circuit: The quantum circuit to translate. + schedule_config: Backend specific parameters used for building the Schedule. + Returns: - A schedule corresponding to the input `circuit` with pulses occurring as late as possible + A schedule corresponding to the input ``circuit`` with pulses occurring as late as + possible. """ sched = Schedule(name=circuit.name) # Align channel end times. @@ -122,20 +132,23 @@ def update_times(inst_qubits: List[int], shift: int = 0, inst_start_time: int = def translate_gates_to_pulse_defs(circuit: QuantumCircuit, schedule_config: ScheduleConfig) -> List[CircuitPulseDef]: """ + Return a list of Schedules and the qubits they operate on, for each element encountered in th + input circuit. + Without concern for the final schedule, extract and return a list of Schedules and the qubits they operate on, for each element encountered in the input circuit. Measures are grouped when - possible, so qc.measure(q0, c0)/qc.measure(q1, c1) will generate a synchronous measurement - pulse. + possible, so ``qc.measure(q0, c0)`` or ``qc.measure(q1, c1)`` will generate a synchronous + measurement pulse. Args: - circuit: The quantum circuit to translate - schedule_config: Backend specific parameters used for building the Schedule + circuit: The quantum circuit to translate. + schedule_config: Backend specific parameters used for building the Schedule. Returns: - A list of CircuitPulseDefs: the pulse definition for each circuit element + A list of CircuitPulseDefs: the pulse definition for each circuit element. Raises: - QiskitError: If circuit uses a command that isn't defined in config.inst_map + QiskitError: If circuit uses a command that isn't defined in config.inst_map. """ circ_pulse_defs = [] diff --git a/qiskit/scheduler/schedule_circuit.py b/qiskit/scheduler/schedule_circuit.py index 367ed1740841..0eb37266bb8f 100644 --- a/qiskit/scheduler/schedule_circuit.py +++ b/qiskit/scheduler/schedule_circuit.py @@ -32,18 +32,22 @@ def schedule_circuit(circuit: QuantumCircuit, scheduled to occur as late as possible. Supported methods: - 'as_soon_as_possible': Schedule pulses greedily, as early as possible on a qubit resource - alias: 'asap' - 'as_late_as_possible': Schedule pulses late-- keep qubits in the ground state when possible - alias: 'alap' + + * ``'as_soon_as_possible'``: Schedule pulses greedily, as early as possible on a + qubit resource. alias: ``'asap'``) + * ``'as_late_as_possible'``: Schedule pulses late-- keep qubits in the ground state when + possible. (alias: ``'alap'``) + Args: - circuit: The quantum circuit to translate - schedule_config: Backend specific parameters used for building the Schedule - method: The scheduling pass method to use + circuit: The quantum circuit to translate. + schedule_config: Backend specific parameters used for building the Schedule. + method: The scheduling pass method to use. + Returns: - Schedule corresponding to the input circuit + Schedule corresponding to the input circuit. + Raises: - QiskitError: If method isn't recognized + QiskitError: If method isn't recognized. """ methods = { 'as_soon_as_possible': as_soon_as_possible, diff --git a/qiskit/test/mock/fake_openpulse_2q.py b/qiskit/test/mock/fake_openpulse_2q.py index 58bdec106c4a..a2d4bd4d60db 100644 --- a/qiskit/test/mock/fake_openpulse_2q.py +++ b/qiskit/test/mock/fake_openpulse_2q.py @@ -93,22 +93,22 @@ def __init__(self): samples=7*[0.j, 0.1j, 1j, 0.5 + 0j])], cmd_def=[Command(name='u1', qubits=[0], sequence=[PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u1', qubits=[1], sequence=[PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u2', qubits=[0], sequence=[PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P0*np.pi'), + t0=0, phase='-P1'), PulseQobjInstruction(name='test_pulse_4', ch='d0', t0=0), PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u2', qubits=[1], sequence=[PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P0*np.pi'), + t0=0, phase='-P1'), PulseQobjInstruction(name='test_pulse_4', ch='d1', t0=0), PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P0*np.pi')]), + t0=0, phase='-P0')]), Command(name='u3', qubits=[0], sequence=[PulseQobjInstruction(name='test_pulse_1', ch='d0', t0=0)]), Command(name='u3', qubits=[1], diff --git a/qiskit/test/mock/fake_openpulse_3q.py b/qiskit/test/mock/fake_openpulse_3q.py index a56d98eb3e81..d0aac380ebf7 100644 --- a/qiskit/test/mock/fake_openpulse_3q.py +++ b/qiskit/test/mock/fake_openpulse_3q.py @@ -81,29 +81,29 @@ def __init__(self): samples=7*[0.j, 0.1j, 1j, 0.5 + 0j])], cmd_def=[Command(name='u1', qubits=[0], sequence=[PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u1', qubits=[1], sequence=[PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u1', qubits=[2], sequence=[PulseQobjInstruction(name='fc', ch='d2', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u2', qubits=[0], sequence=[PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P0*np.pi'), + t0=0, phase='-P1'), PulseQobjInstruction(name='test_pulse_4', ch='d0', t0=0), PulseQobjInstruction(name='fc', ch='d0', - t0=0, phase='-P1*np.pi')]), + t0=0, phase='-P0')]), Command(name='u2', qubits=[1], sequence=[PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P0*np.pi'), + t0=0, phase='-P1'), PulseQobjInstruction(name='test_pulse_4', ch='d1', t0=0), PulseQobjInstruction(name='fc', ch='d1', - t0=0, phase='-P0*np.pi')]), + t0=0, phase='-P0')]), Command(name='u2', qubits=[2], sequence=[PulseQobjInstruction(name='test_pulse_3', ch='d2', t0=0), PulseQobjInstruction(name='fc', ch='d2', - t0=0, phase='-P0*np.pi')]), + t0=0, phase='-P0')]), Command(name='u3', qubits=[0], sequence=[PulseQobjInstruction(name='test_pulse_1', ch='d0', t0=0)]), Command(name='u3', qubits=[1], diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 9a11eb4d8aba..4eb221197dc2 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -19,14 +19,6 @@ .. currentmodule:: qiskit.transpiler -Circuit Transpilation -===================== - -.. autosummary:: - :toctree: ../stubs/ - - transpile_circuit - Pass Managment ============== @@ -73,4 +65,3 @@ from .basepasses import AnalysisPass, TransformationPass from .coupling import CouplingMap from .layout import Layout -from .transpile_circuit import transpile_circuit diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py index 2c6c76028179..d82cb3fcd111 100644 --- a/qiskit/transpiler/coupling.py +++ b/qiskit/transpiler/coupling.py @@ -319,8 +319,10 @@ def __str__(self): def draw(self): """Draws the coupling map. - This function needs `pydot `, which in turn needs - Graphviz ` to be installed. + This function needs `pydot `_, + which in turn needs `Graphviz `_ to be + installed. Additionally, `pillow `_ will + need to be installed. Returns: PIL.Image: Drawn coupling map. diff --git a/qiskit/transpiler/models.py b/qiskit/transpiler/models.py index 05121384312b..f3489f8da7be 100644 --- a/qiskit/transpiler/models.py +++ b/qiskit/transpiler/models.py @@ -12,13 +12,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Models for TranspileConfig and RunConfig.""" +"""Models for PassManagerConfig and RunConfig.""" from qiskit.validation import BaseSchema -class TranspileConfigSchema(BaseSchema): - """Schema for TranspileConfig.""" +class PassManagerConfigSchema(BaseSchema): + """Schema for PassManagerConfig.""" # Required properties. diff --git a/qiskit/transpiler/pass_manager_config.py b/qiskit/transpiler/pass_manager_config.py new file mode 100644 index 000000000000..ff4f6b3a2a00 --- /dev/null +++ b/qiskit/transpiler/pass_manager_config.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Models for PassManagerConfig and its related components.""" + +from qiskit.transpiler.models import PassManagerConfigSchema +from qiskit.validation import BaseModel, bind_schema + + +@bind_schema(PassManagerConfigSchema) +class PassManagerConfig(BaseModel): + """Model for PassManagerConfig. + + Please note that this class only describes the required fields. For the + full description of the model, please check ``PassManagerConfigSchema``. + + Attributes: + initial_layout (Layout): Initial position of virtual qubits on physical qubits. + basis_gates (list): List of basis gate names to unroll to. + coupling_map (CouplingMap): Directed graph represented a coupling map. + backend_properties (BackendProperties): Properties returned by a backend, including + information on gate errors, readout errors, qubit coherence times, etc. + seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. + """ + + def __init__(self, + initial_layout=None, + basis_gates=None, + coupling_map=None, + backend_properties=None, + seed_transpiler=None, + **kwargs): + super().__init__(initial_layout=initial_layout, + basis_gates=basis_gates, + coupling_map=coupling_map, + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, + **kwargs) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 4dbc2fb81734..e430ab2edfee 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -14,6 +14,9 @@ """Expand a gate in a circuit using its decomposition rules.""" +from typing import Type + +from qiskit.circuit.gate import Gate from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit import DAGCircuit @@ -21,23 +24,23 @@ class Decompose(TransformationPass): """Expand a gate in a circuit using its decomposition rules.""" - def __init__(self, gate=None): + def __init__(self, gate: Type[Gate] = None): """Decompose initializer. Args: - gate (qiskit.circuit.gate.Gate): Gate to decompose. + gate: gate to decompose. """ super().__init__() self.gate = gate - def run(self, dag): + def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. Args: - dag(DAGCircuit): input dag + dag: input dag. Returns: - DAGCircuit: output dag where gate was expanded. + output dag where ``gate`` was expanded. """ # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(self.gate): diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index ae924fe122d7..1abc12e0345f 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -15,7 +15,6 @@ """Map a DAGCircuit onto a `coupling_map` adding swap gates.""" from logging import getLogger -from pprint import pformat from math import inf from collections import OrderedDict import numpy as np @@ -158,8 +157,8 @@ def _layer_update(self, i, best_layout, best_depth, that the _mapper method is building. """ layout = best_layout - logger.debug("layer_update: layout = %s", pformat(layout)) - logger.debug("layer_update: self.trivial_layout = %s", pformat(self.trivial_layout)) + logger.debug("layer_update: layout = %s", layout) + logger.debug("layer_update: self.trivial_layout = %s", self.trivial_layout) dagcircuit_output = DAGCircuit() for qubit in layout.get_virtual_bits().keys(): if qubit.register not in dagcircuit_output.qregs.values(): @@ -292,10 +291,10 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # This is the final edgemap. We might use it to correctly replace # any measurements that needed to be removed earlier. - logger.debug("mapper: self.trivial_layout = %s", pformat(self.trivial_layout)) - logger.debug("mapper: layout = %s", pformat(layout)) + logger.debug("mapper: self.trivial_layout = %s", self.trivial_layout) + logger.debug("mapper: layout = %s", layout) last_edgemap = layout.combine_into_edge_map(self.trivial_layout) - logger.debug("mapper: last_edgemap = %s", pformat(last_edgemap)) + logger.debug("mapper: last_edgemap = %s", last_edgemap) return dagcircuit_output @@ -326,11 +325,11 @@ def _layer_permutation(layer_partition, layout, qubit_subset, TranspilerError: if anything went wrong. """ logger.debug("layer_permutation: layer_partition = %s", - pformat(layer_partition)) + layer_partition) logger.debug("layer_permutation: layout = %s", - pformat(layout.get_virtual_bits())) + layout.get_virtual_bits()) logger.debug("layer_permutation: qubit_subset = %s", - pformat(qubit_subset)) + qubit_subset) logger.debug("layer_permutation: trials = %s", trials) # The input dag is on a flat canonical register @@ -344,7 +343,7 @@ def _layer_permutation(layer_partition, layout, qubit_subset, raise TranspilerError("Layer contains > 2-qubit gates") if len(gate_args) == 2: gates.append(tuple(gate_args)) - logger.debug("layer_permutation: gates = %s", pformat(gates)) + logger.debug("layer_permutation: gates = %s", gates) # Can we already apply the gates? If so, there is no work to do. dist = sum([coupling.distance(layout[g[0]], layout[g[1]]) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index b6597d413433..a0d87cdc1203 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -14,6 +14,7 @@ """PassManager class for the transpiler.""" +import warnings import dill from qiskit.visualization import pass_manager_drawer @@ -35,40 +36,22 @@ def __init__(self, passes=None, max_iteration=1000, callback=None): to be added to the pass manager schedule. The default is None. max_iteration (int): The schedule looping iterates until the condition is met or until max_iteration is reached. - callback (func): A callback function that will be called after each - pass execution. The function will be called with 5 keyword - arguments:: - pass_ (Pass): the pass being run - dag (DAGCircuit): the dag output of the pass - time (float): the time to execute the pass - property_set (PropertySet): the property set - count (int): the index for the pass execution - - The exact arguments pass expose the internals of the pass - manager and are subject to change as the pass manager internals - change. If you intend to reuse a callback function over - multiple releases be sure to check that the arguments being - passed are the same. - - To use the callback feature you define a function that will - take in kwargs dict and access the variables. For example:: - - def callback_func(**kwargs): - pass_ = kwargs['pass_'] - dag = kwargs['dag'] - time = kwargs['time'] - property_set = kwargs['property_set'] - count = kwargs['count'] - ... + callback (func): DEPRECATED - A callback function that will be called after each + pass execution. + """ + self.callback = None - PassManager(callback=callback_func) + if callback: + warnings.warn("Setting a callback at construction time is being deprecated in favor of" + "PassManager.run(..., callback=callback,...)", DeprecationWarning, 2) + self.callback = callback + # the pass manager's schedule of passes, including any control-flow. + # Populated via PassManager.append(). - """ self._pass_sets = [] if passes is not None: self.append(passes) self.max_iteration = max_iteration - self.callback = callback self.property_set = None def append(self, passes, max_iteration=None, **flow_controller_conditions): @@ -141,9 +124,7 @@ def __len__(self): return len(self._pass_sets) def __getitem__(self, index): - max_iteration = self.max_iteration - call_back = self.callback - new_passmanager = PassManager(max_iteration=max_iteration, callback=call_back) + new_passmanager = PassManager(max_iteration=self.max_iteration, callback=self.callback) _pass_sets = self._pass_sets[index] if isinstance(_pass_sets, dict): _pass_sets = [_pass_sets] @@ -152,16 +133,13 @@ def __getitem__(self, index): def __add__(self, other): if isinstance(other, PassManager): - max_iteration = self.max_iteration - call_back = self.callback - new_passmanager = PassManager(max_iteration=max_iteration, callback=call_back) + new_passmanager = PassManager(max_iteration=self.max_iteration, callback=self.callback) new_passmanager._pass_sets = self._pass_sets + other._pass_sets return new_passmanager else: try: - max_iteration = self.max_iteration - call_back = self.callback - new_passmanager = PassManager(max_iteration=max_iteration, callback=call_back) + new_passmanager = PassManager(max_iteration=self.max_iteration, + callback=self.callback) new_passmanager._pass_sets += self._pass_sets new_passmanager.append(other) return new_passmanager @@ -179,23 +157,49 @@ def _normalize_passes(passes): raise TranspilerError('%s is not a pass instance' % pass_.__class__) return passes - def run(self, circuits): + def run(self, circuits, output_name=None, callback=None): """Run all the passes on circuit or circuits Args: circuits (QuantumCircuit or list[QuantumCircuit]): circuit(s) to - transform via all the registered passes. + transform via all the registered passes. + output_name (str): The output circuit name. If not given, the same as the + input circuit + callback (func): A callback function that will be called after each + pass execution. The function will be called with 5 keyword + arguments:: + pass_ (Pass): the pass being run + dag (DAGCircuit): the dag output of the pass + time (float): the time to execute the pass + property_set (PropertySet): the property set + count (int): the index for the pass execution + + The exact arguments pass expose the internals of the pass + manager and are subject to change as the pass manager internals + change. If you intend to reuse a callback function over + multiple releases be sure to check that the arguments being + passed are the same. + To use the callback feature you define a function that will + take in kwargs dict and access the variables. For example:: + + def callback_func(**kwargs): + pass_ = kwargs['pass_'] + dag = kwargs['dag'] + time = kwargs['time'] + property_set = kwargs['property_set'] + count = kwargs['count'] + ... Returns: QuantumCircuit or list[QuantumCircuit]: Transformed circuit(s). """ if isinstance(circuits, QuantumCircuit): - return self._run_single_circuit(circuits) + return self._run_single_circuit(circuits, output_name, callback) else: - return self._run_several_circuits(circuits) + return self._run_several_circuits(circuits, output_name, callback) def _create_running_passmanager(self): - running_passmanager = RunningPassManager(self.max_iteration, self.callback) + running_passmanager = RunningPassManager(self.max_iteration) for pass_set in self._pass_sets: running_passmanager.append(pass_set['passes'], **pass_set['flow_controllers']) return running_passmanager @@ -207,29 +211,61 @@ def _in_parallel(circuit, pm_dill=None): result = running_passmanager.run(circuit) return result - def _run_several_circuits(self, circuits): + def _run_several_circuits(self, circuits, output_name=None, callback=None): """Run all the passes on each of the circuits in the circuits list - - Args: - circuits (list[QuantumCircuit]): circuit to transform via all the registered passes - Returns: list[QuantumCircuit]: Transformed circuits. """ + # TODO support for List(output_name) and List(callback) + del output_name + del callback + return parallel_map(PassManager._in_parallel, circuits, task_kwargs={'pm_dill': dill.dumps(self)}) - def _run_single_circuit(self, circuit): + def _run_single_circuit(self, circuit, output_name=None, callback=None): """Run all the passes on a QuantumCircuit Args: circuit (QuantumCircuit): circuit to transform via all the registered passes + output_name (str): The output circuit name. If not given, the same as the + input circuit + callback (func): A callback function that will be called after each + pass execution. The function will be called with 5 keyword + arguments: + pass_ (Pass): the pass being run + dag (DAGCircuit): the dag output of the pass + time (float): the time to execute the pass + property_set (PropertySet): the property set + count (int): the index for the pass execution + + The exact arguments pass expose the internals of the pass + manager and are subject to change as the pass manager internals + change. If you intend to reuse a callback function over + multiple releases be sure to check that the arguments being + passed are the same. + + To use the callback feature you define a function that will + take in kwargs dict and access the variables. For example:: + + def callback_func(**kwargs): + pass_ = kwargs['pass_'] + dag = kwargs['dag'] + time = kwargs['time'] + property_set = kwargs['property_set'] + count = kwargs['count'] + ... + + PassManager(callback=callback_func) + Returns: QuantumCircuit: Transformed circuit. """ running_passmanager = self._create_running_passmanager() - result = running_passmanager.run(circuit) + if callback is None and self.callback: # TODO to remove with __init__(callback) + callback = self.callback + result = running_passmanager.run(circuit, output_name=output_name, callback=callback) self.property_set = running_passmanager.property_set return result diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 0135d88bc45c..94554851e37a 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -12,11 +12,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Level 0 pass manager: -no optimization, just conforming to basis and coupling map +"""Pass manager for optimization level 0, providing no explicit optimization. + +Level 0 pass manager: no explicit optimization other than mapping to backend. """ +from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager from qiskit.extensions.standard import SwapGate @@ -35,28 +36,30 @@ from qiskit.transpiler.passes import CheckCXDirection -def level_0_pass_manager(transpile_config): - """ - Level 0 pass manager: no explicit optimization other than mapping to backend. +def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: + """Level 0 pass manager: no explicit optimization other than mapping to backend. This pass manager applies the user-given initial layout. If none is given, a trivial layout consisting of mapping the i-th virtual qubit to the i-th physical qubit is used. Any unused physical qubit is allocated as ancilla space. + The pass manager then unrolls the circuit to the desired basis, and transforms the circuit to match the coupling map. Finally, extra resets are removed. - Note: in simulators where coupling_map=None, only the unrolling and optimization - stages are done. + + Note: + In simulators where ``coupling_map=None``, only the unrolling and + optimization stages are done. Args: - transpile_config (TranspileConfig) + pass_manager_config: configuration of the pass manager. Returns: - PassManager: a level 0 pass manager. + a level 0 pass manager. """ - basis_gates = transpile_config.basis_gates - coupling_map = transpile_config.coupling_map - initial_layout = transpile_config.initial_layout - seed_transpiler = transpile_config.seed_transpiler + basis_gates = pass_manager_config.basis_gates + coupling_map = pass_manager_config.coupling_map + initial_layout = pass_manager_config.initial_layout + seed_transpiler = pass_manager_config.seed_transpiler # 1. Use trivial layout if no layout given _given_layout = SetLayout(initial_layout) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 8eb8e7245005..401f907204c5 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -12,11 +12,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Level 1 pass manager: -mapping in addition to light optimization via adjacent gate collapse +"""Pass manager for optimization level 1, providing light optimization. + +Level 1 pass manager: light optimization by simple adjacent gate collapsing. """ +from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager from qiskit.extensions.standard import SwapGate @@ -42,9 +43,8 @@ from qiskit.transpiler.passes import DenseLayout -def level_1_pass_manager(transpile_config): - """ - Level 1 pass manager: light optimization by simple adjacent gate collapsing +def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: + """Level 1 pass manager: light optimization by simple adjacent gate collapsing. This pass manager applies the user-given initial layout. If none is given, and a trivial layout (i-th virtual -> i-th physical) makes the circuit fit the coupling map, that is used. @@ -53,20 +53,22 @@ def level_1_pass_manager(transpile_config): The pass manager then unrolls the circuit to the desired basis, and transforms the circuit to match the coupling map. Finally, optimizations in the form of adjacent gate collapse and redundant reset removal are performed. - Note: in simulators where coupling_map=None, only the unrolling and optimization - stages are done. + + Note: + In simulators where ``coupling_map=None``, only the unrolling and + optimization stages are done. Args: - transpile_config (TranspileConfig) + pass_manager_config: configuration of the pass manager. Returns: - PassManager: a level 1 pass manager. + a level 1 pass manager. """ - basis_gates = transpile_config.basis_gates - coupling_map = transpile_config.coupling_map - initial_layout = transpile_config.initial_layout - seed_transpiler = transpile_config.seed_transpiler - backend_properties = getattr(transpile_config, 'backend_properties', None) + basis_gates = pass_manager_config.basis_gates + coupling_map = pass_manager_config.coupling_map + initial_layout = pass_manager_config.initial_layout + seed_transpiler = pass_manager_config.seed_transpiler + backend_properties = pass_manager_config.backend_properties # 1. Use trivial layout if no layout given _set_initial_layout = SetLayout(initial_layout) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index a3457529298a..a25adc017781 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -12,13 +12,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=unused-variable +"""Pass manager for optimization level 2, providing medium optimization. -""" -Level 2 pass manager: -noise adaptive mapping in addition to commutation-based optimization +Level 2 pass manager: medium optimization by noise adaptive qubit mapping and +gate cancellation using commutativity rules. """ +from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager from qiskit.extensions.standard import SwapGate @@ -42,9 +42,8 @@ from qiskit.transpiler.passes import CheckCXDirection -def level_2_pass_manager(transpile_config): - """ - Level 2 pass manager: medium optimization by noise adaptive qubit mapping and +def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: + """Level 2 pass manager: medium optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules. This pass manager applies the user-given initial layout. If none is given, and @@ -55,20 +54,22 @@ def level_2_pass_manager(transpile_config): It is then unrolled to the basis, and any flipped cx directions are fixed. Finally, optimizations in the form of commutative gate cancellation and redundant reset removal are performed. - Note: in simulators where coupling_map=None, only the unrolling and optimization - stages are done. + + Note: + In simulators where ``coupling_map=None``, only the unrolling and + optimization stages are done. Args: - transpile_config (TranspileConfig) + pass_manager_config: configuration of the pass manager. Returns: - PassManager: a level 2 pass manager. + a level 2 pass manager. """ - basis_gates = transpile_config.basis_gates - coupling_map = transpile_config.coupling_map - initial_layout = transpile_config.initial_layout - seed_transpiler = transpile_config.seed_transpiler - backend_properties = transpile_config.backend_properties + basis_gates = pass_manager_config.basis_gates + coupling_map = pass_manager_config.coupling_map + initial_layout = pass_manager_config.initial_layout + seed_transpiler = pass_manager_config.seed_transpiler + backend_properties = pass_manager_config.backend_properties # 1. Unroll to the basis first, to prepare for noise-adaptive layout _unroll = Unroller(basis_gates) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 087a3d49a762..3a5848a414a5 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -12,13 +12,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=unused-variable +"""Pass manager for optimization level 3, providing heavy optimization. -""" -Level 3 pass manager: -noise adaptive mapping in addition to heavy optimization based on unitary synthesis +Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and +gate cancellation using commutativity rules and unitary synthesis. """ +from qiskit.transpiler.pass_manager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager from qiskit.extensions.standard import SwapGate from qiskit.transpiler.passes import Unroller @@ -46,33 +46,35 @@ from qiskit.transpiler.passes import CheckCXDirection -def level_3_pass_manager(transpile_config): - """ - Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and +def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: + """Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules and unitary synthesis. This pass manager applies the user-given initial layout. If none is given, and device calibration information is available, the circuit is mapped to the qubits with best readouts and to CX gates with highest fidelity. Otherwise, a layout on the most densely connected qubits is used. + The pass manager then transforms the circuit to match the coupling constraints. It is then unrolled to the basis, and any flipped cx directions are fixed. Finally, optimizations in the form of commutative gate cancellation, resynthesis of two-qubit unitary blocks, and redundant reset removal are performed. - Note: in simulators where coupling_map=None, only the unrolling and optimization - stages are done. + + Note: + In simulators where ``coupling_map=None``, only the unrolling and + optimization stages are done. Args: - transpile_config (TranspileConfig) + pass_manager_config: configuration of the pass manager. Returns: - PassManager: a level 3 pass manager. + a level 3 pass manager. """ - basis_gates = transpile_config.basis_gates - coupling_map = transpile_config.coupling_map - initial_layout = transpile_config.initial_layout - seed_transpiler = transpile_config.seed_transpiler - backend_properties = transpile_config.backend_properties + basis_gates = pass_manager_config.basis_gates + coupling_map = pass_manager_config.coupling_map + initial_layout = pass_manager_config.initial_layout + seed_transpiler = pass_manager_config.seed_transpiler + backend_properties = pass_manager_config.backend_properties # 1. Unroll to the basis first, to prepare for noise-adaptive layout _unroll = Unroller(basis_gates) diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index e80f77cc3949..6b5b3fe3876b 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -29,45 +29,17 @@ logger = logging.getLogger(__name__) -class RunningPassManager(): +class RunningPassManager: """A RunningPassManager is a running pass manager.""" - def __init__(self, max_iteration, callback): + def __init__(self, max_iteration): """Initialize an empty PassManager object (with no passes scheduled). Args: max_iteration (int): The schedule looping iterates until the condition is met or until max_iteration is reached. - callback (func): A callback function that will be called after each - pass execution. The function will be called with 5 keyword - arguments: - pass_ (Pass): the pass being run - dag (DAGCircuit): the dag output of the pass - time (float): the time to execute the pass - property_set (PropertySet): the property set - count (int): the index for the pass execution - - The exact arguments pass expose the internals of the pass - manager and are subject to change as the pass manager internals - change. If you intend to reuse a callback function over - multiple releases be sure to check that the arguments being - passed are the same. - - To use the callback feature you define a function that will - take in kwargs dict and access the variables. For example:: - - def callback_func(**kwargs): - pass_ = kwargs['pass_'] - dag = kwargs['dag'] - time = kwargs['time'] - property_set = kwargs['property_set'] - count = kwargs['count'] - ... - - PassManager(callback=callback_func) - """ - self.callback = callback + self.callback = None # the pass manager's schedule of passes, including any control-flow. # Populated via PassManager.append(). self.working_list = [] @@ -120,12 +92,14 @@ def _normalize_flow_controller(self, flow_controller): raise TranspilerError('The flow controller parameter %s is not callable' % name) return flow_controller - def run(self, circuit): + def run(self, circuit, output_name=None, callback=None): """Run all the passes on a QuantumCircuit Args: circuit (QuantumCircuit): circuit to transform via all the registered passes - + output_name (str): The output circuit name. If not given, the same as the + input circuit + callback (callable): A callback function that will be called after each pass execution. Returns: QuantumCircuit: Transformed circuit. """ @@ -133,12 +107,18 @@ def run(self, circuit): dag = circuit_to_dag(circuit) del circuit + if callback: + self.callback = callback + for passset in self.working_list: for pass_ in passset: dag = self._do_pass(pass_, dag, passset.options) circuit = dag_to_circuit(dag) - circuit.name = name + if output_name: + circuit.name = output_name + else: + circuit.name = name circuit._layout = self.property_set['layout'] return circuit diff --git a/qiskit/transpiler/transpile_circuit.py b/qiskit/transpiler/transpile_circuit.py deleted file mode 100644 index 28df2186ef4b..000000000000 --- a/qiskit/transpiler/transpile_circuit.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Circuit transpile function""" - -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.passes.basis.ms_basis_decomposer import MSBasisDecomposer -from qiskit.transpiler.exceptions import TranspilerError - - -def transpile_circuit(circuit, transpile_config): - """Select a PassManager and run a single circuit through it. - - Args: - circuit (QuantumCircuit): circuit to transpile - transpile_config (TranspileConfig): configuration dictating how to transpile - - Returns: - QuantumCircuit: transpiled circuit - - Raises: - TranspilerError: if transpile_config is not valid or transpilation incurs error - """ - # either the pass manager is already selected... - if transpile_config.pass_manager is not None: - pass_manager = transpile_config.pass_manager - - # or we choose an appropriate one based on desired optimization level (default: level 1) - else: - # Workaround for ion trap support: If basis gates includes - # Mølmer-Sørensen (rxx) and the circuit includes gates outside the basis, - # first unroll to u3, cx, then run MSBasisDecomposer to target basis. - basic_insts = ['measure', 'reset', 'barrier', 'snapshot'] - device_insts = set(transpile_config.basis_gates).union(basic_insts) - - ms_basis_swap = None - if 'rxx' in transpile_config.basis_gates and \ - not device_insts >= circuit.count_ops().keys(): - ms_basis_swap = transpile_config.basis_gates - transpile_config.basis_gates = list(set(['u3', 'cx']).union( - transpile_config.basis_gates)) - - level = transpile_config.optimization_level - if level is None: - level = 1 - - if level == 0: - pass_manager = level_0_pass_manager(transpile_config) - elif level == 1: - pass_manager = level_1_pass_manager(transpile_config) - elif level == 2: - pass_manager = level_2_pass_manager(transpile_config) - elif level == 3: - pass_manager = level_3_pass_manager(transpile_config) - else: - raise TranspilerError("optimization_level can range from 0 to 3.") - - if ms_basis_swap is not None: - pass_manager.append(MSBasisDecomposer(ms_basis_swap)) - - # Set a callback on the pass manager there is one - if getattr(transpile_config, 'callback', None): - pass_manager.callback = transpile_config.callback - - out_circuit = pass_manager.run(circuit) - out_circuit.name = transpile_config.output_name - - return out_circuit diff --git a/qiskit/transpiler/transpile_config.py b/qiskit/transpiler/transpile_config.py deleted file mode 100644 index 82d6dc4ab2c3..000000000000 --- a/qiskit/transpiler/transpile_config.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Models for TranspileConfig and its related components.""" - -from qiskit.transpiler.models import TranspileConfigSchema -from qiskit.validation import BaseModel, bind_schema - - -@bind_schema(TranspileConfigSchema) -class TranspileConfig(BaseModel): - """Model for TranspileConfig. - - Please note that this class only describes the required fields. For the - full description of the model, please check ``TranspileConfigSchema``. - - Attributes: - optimization_level (int): a non-negative integer indicating the - optimization level. 0 means no transformation on the circuit. Higher - levels may produce more optimized circuits, but may take longer. - """ - def __init__(self, optimization_level, **kwargs): - self.optimization_level = optimization_level - super().__init__(**kwargs) diff --git a/qiskit/validation/base.py b/qiskit/validation/base.py index 8696e678a8ed..7020d7c4889a 100644 --- a/qiskit/validation/base.py +++ b/qiskit/validation/base.py @@ -234,8 +234,7 @@ def _decorated(self, **kwargs): do_validation = kwargs.pop('validate', True) if do_validation: try: - _ = self.shallow_schema._do_load(kwargs, - postprocess=False) + _ = self.shallow_schema._do_load(kwargs, postprocess=False) except ValidationError as ex: raise ModelValidationError( ex.messages, ex.field_name, ex.data, ex.valid_data, **ex.kwargs) from None diff --git a/qiskit/visualization/__init__.py b/qiskit/visualization/__init__.py index 8138598cc329..a54c24b49991 100644 --- a/qiskit/visualization/__init__.py +++ b/qiskit/visualization/__init__.py @@ -80,6 +80,14 @@ pass_manager_drawer +Single Qubit State Transition Visualizations +============================================ + +.. autosummary:: + :toctree: ../stubs/ + + visualize_transition + Exceptions ========== @@ -100,6 +108,7 @@ plot_state_paulivec, plot_state_qsphere) +from qiskit.visualization.transition_visualization import visualize_transition from .pulse_visualization import pulse_drawer from .circuit_visualization import circuit_drawer, qx_color_scheme from .dag_visualization import dag_drawer diff --git a/qiskit/visualization/gate_map.py b/qiskit/visualization/gate_map.py index dc57757c7e7d..388422f928b7 100644 --- a/qiskit/visualization/gate_map.py +++ b/qiskit/visualization/gate_map.py @@ -148,6 +148,14 @@ def plot_gate_map(backend, figsize=None, mpl_data[5] = [[1, 0], [0, 1], [1, 1], [1, 2], [2, 1]] + mpl_data[28] = [[0, 2], [0, 3], [0, 4], [0, 5], [0, 6], + [1, 2], [1, 6], + [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], + [2, 5], [2, 6], [2, 7], [2, 8], + [3, 0], [3, 4], [3, 8], + [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], + [4, 5], [4, 6], [4, 7], [4, 8]] + mpl_data[53] = [[0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [1, 2], [1, 6], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py index f8d2717c7a26..d1c6aef878cc 100644 --- a/qiskit/visualization/pulse/matplotlib.py +++ b/qiskit/visualization/pulse/matplotlib.py @@ -350,16 +350,13 @@ def _build_channels(self, schedule, channels, t0, tf, show_framechange_channels= snapshot_channels[channel].add_instruction(start_time, instruction) return channels, output_channels, snapshot_channels - def _count_valid_waveforms(self, output_channels, scale=1, channels=None, - plot_all=False, scaling=None): - if scaling is not None: - warnings.warn('The parameter "scaling" is being replaced by "scale"', - DeprecationWarning, 3) - scale = scaling + def _count_valid_waveforms(self, output_channels, scale, channel_scales=None, + channels=None, plot_all=False): # count numbers of valid waveform n_valid_waveform = 0 - v_max = 0 + scale_dict = {chan: 0 for chan in output_channels.keys()} for channel, events in output_channels.items(): + v_max = 0 if channels: if channel in channels: waveform = events.waveform @@ -377,17 +374,17 @@ def _count_valid_waveforms(self, output_channels, scale=1, channels=None, n_valid_waveform += 1 events.enable = True - # when input schedule is empty or comprises only frame changes, - # we need to overwrite maximum amplitude by a value greater than zero, - # otherwise auto axis scaling will fail with zero division. - v_max = v_max or 1 - - if scale: - v_max = 0.5 * scale - else: - v_max = 0.5 / (v_max) + scale_val = channel_scales.get(channel, scale) + if not scale_val: + # when input schedule is empty or comprises only frame changes, + # we need to overwrite maximum amplitude by a value greater than zero, + # otherwise auto axis scaling will fail with zero division. + v_max = v_max or 1 + scale_dict[channel] = 1 / v_max + else: + scale_dict[channel] = scale_val - return n_valid_waveform, v_max + return n_valid_waveform, scale_dict # pylint: disable=unused-argument def _draw_table(self, figure, channels, dt, n_valid_waveform): @@ -510,12 +507,14 @@ def _draw_labels(self, ax, labels, prev_labels, dt, y0): ax.axvline(tf*dt, -1, 1, color=color, linestyle=linestyle, alpha=alpha) - def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, v_max, + def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, scale_dict, label=False, framechange=True): y0 = 0 prev_labels = [] for channel, events in output_channels.items(): if events.enable: + # scaling value of this channel + scale = 0.5 * scale_dict.get(channel, 0.5) # plot waveform waveform = events.waveform time = np.arange(t0, tf + 1, dtype=float) * dt @@ -528,10 +527,10 @@ def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, v_max, re, im = np.zeros_like(time), np.zeros_like(time) color = self._get_channel_color(channel) # Minimum amplitude scaled - amp_min = v_max * abs(min(0, np.nanmin(re), np.nanmin(im))) + amp_min = scale * abs(min(0, np.nanmin(re), np.nanmin(im))) # scaling and offset - re = v_max * re + y0 - im = v_max * im + y0 + re = scale * re + y0 + im = scale * im + y0 offset = np.zeros_like(time) + y0 # plot ax.fill_between(x=time, y1=re, y2=offset, @@ -561,6 +560,10 @@ def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, v_max, ax.text(x=0, y=y0, s=channel.name, fontsize=self.style.axis_font_size, ha='right', va='center') + # show scaling factor + ax.text(x=0, y=y0 - 0.1, s='x%.1f' % (2 * scale), + fontsize=0.7*self.style.axis_font_size, + ha='right', va='top') # change the y0 offset for removing spacing when a channel has negative values if self.style.remove_spacing: @@ -570,8 +573,8 @@ def _draw_channels(self, ax, output_channels, interp_method, t0, tf, dt, v_max, return y0 def draw(self, schedule, dt, interp_method, plot_range, - scale=None, channels_to_plot=None, plot_all=True, - table=True, label=False, framechange=True, + scale=None, channel_scales=None, channels_to_plot=None, + plot_all=True, table=True, label=False, framechange=True, scaling=None, channels=None, show_framechange_channels=True): """Draw figure. @@ -582,7 +585,9 @@ def draw(self, schedule, dt, interp_method, plot_range, interp_method (Callable): interpolation function See `qiskit.visualization.interpolation` for more information plot_range (tuple[float]): plot range - scale (float): Relative visual scaling of waveform amplitudes + scale (float): Relative visual scaling of waveform amplitudes. + channel_scales (dict[Channel, float]): Channel independent scaling as a + dictionary of `Channel` object. channels_to_plot (list[OutputChannel]): deprecated, see `channels` plot_all (bool): if plot all channels even it is empty table (bool): Draw event table @@ -612,6 +617,9 @@ def draw(self, schedule, dt, interp_method, plot_range, channels = [] interp_method = interp_method or interpolation.step_wise + if channel_scales is None: + channel_scales = {} + # setup plot range if plot_range: t0 = int(np.floor(plot_range[0]/dt)) @@ -634,10 +642,11 @@ def draw(self, schedule, dt, interp_method, plot_range, # count numbers of valid waveform - n_valid_waveform, v_max = self._count_valid_waveforms(output_channels, - scale=scale, - channels=channels, - plot_all=plot_all) + n_valid_waveform, scale_dict = self._count_valid_waveforms(output_channels, + scale=scale, + channel_scales=channel_scales, + channels=channels, + plot_all=plot_all) if table: ax = self._draw_table(figure, schedule_channels, dt, n_valid_waveform) @@ -649,13 +658,16 @@ def draw(self, schedule, dt, interp_method, plot_range, ax.set_facecolor(self.style.bg_color) y0 = self._draw_channels(ax, output_channels, interp_method, - t0, tf, dt, v_max, label=label, + t0, tf, dt, scale_dict, label=label, framechange=framechange) - self._draw_snapshots(ax, snapshot_channels, dt, y0) + y_ub = 0.5 + self.style.vertical_span + y_lb = y0 + 0.5 - self.style.vertical_span + + self._draw_snapshots(ax, snapshot_channels, dt, y_lb) ax.set_xlim(t0 * dt, tf * dt) - ax.set_ylim(y0, 1) + ax.set_ylim(y_lb, y_ub) ax.set_yticklabels([]) return figure diff --git a/qiskit/visualization/pulse/qcstyle.py b/qiskit/visualization/pulse/qcstyle.py index d4c64d1ab38d..99b6ea6cc7be 100644 --- a/qiskit/visualization/pulse/qcstyle.py +++ b/qiskit/visualization/pulse/qcstyle.py @@ -22,7 +22,7 @@ def __init__(self, figsize=(10, 12), fig_unit_h_table=0.4, label_ch_color=None, label_ch_alpha=0.3, d_ch_color=None, u_ch_color=None, m_ch_color=None, s_ch_color=None, s_ch_linestyle='-', table_color=None, bg_color=None, num_points=1000, dpi=150, remove_spacing=True, - max_table_ratio=0.5): + max_table_ratio=0.5, vertical_span=0.2): """Set style sheet for OpenPulse schedule drawer. Args: @@ -49,6 +49,7 @@ def __init__(self, figsize=(10, 12), fig_unit_h_table=0.4, remove_spacing(bool): Remove redundant spacing when the waveform has no negative values max_table_ratio (float): Maximum portion of the plot the table can take up. Limited to range between 0 and 1. + vertical_span (float): Spacing on top and bottom of plot. """ self.figsize = figsize self.fig_unit_h_table = fig_unit_h_table @@ -73,6 +74,7 @@ def __init__(self, figsize=(10, 12), fig_unit_h_table=0.4, self.dpi = dpi self.remove_spacing = remove_spacing self.max_table_ratio = max(min(max_table_ratio, 0.0), 1.0) + self.vertical_span = vertical_span class PulseStyle: diff --git a/qiskit/visualization/pulse_visualization.py b/qiskit/visualization/pulse_visualization.py index bc09106d4dc7..18c9b07a46bf 100644 --- a/qiskit/visualization/pulse_visualization.py +++ b/qiskit/visualization/pulse_visualization.py @@ -27,9 +27,9 @@ def pulse_drawer(data, dt=1, style=None, filename=None, - interp_method=None, scale=None, channels_to_plot=None, - plot_all=False, plot_range=None, interactive=False, - table=True, label=False, framechange=True, + interp_method=None, scale=None, channel_scales=None, + channels_to_plot=None, plot_all=False, plot_range=None, + interactive=False, table=True, label=False, framechange=True, channels=None, scaling=None, show_framechange_channels=True): """Plot the interpolated envelope of pulse @@ -43,6 +43,8 @@ def pulse_drawer(data, dt=1, style=None, filename=None, interp_method (Callable): interpolation function See `qiskit.visualization.interpolation` for more information scale (float): scaling of waveform amplitude + channel_scales (dict[Channel, float]): channel independent scaling as a + dictionary of `Channel` object. channels_to_plot (list): Deprecated, see `channels` plot_all (bool): Plot empty channels plot_range (tuple): A tuple of time range to plot @@ -59,7 +61,7 @@ def pulse_drawer(data, dt=1, style=None, filename=None, matplotlib.figure: A matplotlib figure object for the pulse envelope Raises: - VisualizationError: when invalid data is given or lack of information + VisualizationError: when invalid data is given ImportError: when matplotlib is not installed """ if scaling is not None: @@ -79,8 +81,9 @@ def pulse_drawer(data, dt=1, style=None, filename=None, elif isinstance(data, (Schedule, Instruction)): drawer = _matplotlib.ScheduleDrawer(style=style) image = drawer.draw(data, dt=dt, interp_method=interp_method, scale=scale, - plot_range=plot_range, plot_all=plot_all, table=table, - label=label, framechange=framechange, channels=channels, + channel_scales=channel_scales, plot_range=plot_range, + plot_all=plot_all, table=table, label=label, + framechange=framechange, channels=channels, show_framechange_channels=show_framechange_channels) else: raise VisualizationError('This data cannot be visualized.') diff --git a/qiskit/visualization/transition_visualization.py b/qiskit/visualization/transition_visualization.py new file mode 100644 index 000000000000..ce9b21f893d2 --- /dev/null +++ b/qiskit/visualization/transition_visualization.py @@ -0,0 +1,316 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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 +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Visualization function for animation of state transitions by applying gates to single qubit. +""" +import sys +from math import sin, cos, acos, sqrt +import numpy as np + +try: + import matplotlib + from matplotlib import pyplot as plt + from matplotlib import animation + from mpl_toolkits.mplot3d import Axes3D + from qiskit.visualization.bloch import Bloch + from qiskit.visualization.exceptions import VisualizationError + HAS_MATPLOTLIB = True +except ImportError: + HAS_MATPLOTLIB = False + +try: + from IPython.display import HTML + HAS_IPYTHON = True +except ImportError: + HAS_IPYTHON = False + + +def _normalize(v, tolerance=0.00001): + """Makes sure magnitude of the vector is 1 with given tolerance""" + + mag2 = sum(n * n for n in v) + if abs(mag2 - 1.0) > tolerance: + mag = sqrt(mag2) + v = tuple(n / mag for n in v) + return np.array(v) + + +class _Quaternion: + """For calculating vectors on unit sphere""" + def __init__(self): + self._val = None + + @staticmethod + def from_axisangle(theta, v): + """Create quaternion from axis""" + v = _normalize(v) + + new_quaternion = _Quaternion() + new_quaternion._axisangle_to_q(theta, v) + return new_quaternion + + @staticmethod + def from_value(value): + """Create quaternion from vector""" + new_quaternion = _Quaternion() + new_quaternion._val = value + return new_quaternion + + def _axisangle_to_q(self, theta, v): + """Convert axis and angle to quaternion""" + x = v[0] + y = v[1] + z = v[2] + + w = cos(theta/2.) + x = x * sin(theta/2.) + y = y * sin(theta/2.) + z = z * sin(theta/2.) + + self._val = np.array([w, x, y, z]) + + def __mul__(self, b): + """Multiplication of quaternion with quaternion or vector""" + + if isinstance(b, _Quaternion): + return self._multiply_with_quaternion(b) + elif isinstance(b, (list, tuple, np.ndarray)): + if len(b) != 3: + raise Exception("Input vector has invalid length {0}".format(len(b))) + return self._multiply_with_vector(b) + else: + raise Exception("Multiplication with unknown type {0}".format(type(b))) + + def _multiply_with_quaternion(self, q_2): + """Multiplication of quaternion with quaternion""" + w_1, x_1, y_1, z_1 = self._val + w_2, x_2, y_2, z_2 = q_2._val + w = w_1 * w_2 - x_1 * x_2 - y_1 * y_2 - z_1 * z_2 + x = w_1 * x_2 + x_1 * w_2 + y_1 * z_2 - z_1 * y_2 + y = w_1 * y_2 + y_1 * w_2 + z_1 * x_2 - x_1 * z_2 + z = w_1 * z_2 + z_1 * w_2 + x_1 * y_2 - y_1 * x_2 + + result = _Quaternion.from_value(np.array((w, x, y, z))) + return result + + def _multiply_with_vector(self, v): + """Multiplication of quaternion with vector""" + q_2 = _Quaternion.from_value(np.append((0.0), v)) + return (self * q_2 * self.get_conjugate())._val[1:] + + def get_conjugate(self): + """Conjugation of quaternion""" + w, x, y, z = self._val + result = _Quaternion.from_value(np.array((w, -x, -y, -z))) + return result + + def __repr__(self): + theta, v = self.get_axisangle() + return "(({0}; {1}, {2}, {3}))".format(theta, v[0], v[1], v[2]) + + def get_axisangle(self): + """Returns angle and vector of quaternion""" + w, v = self._val[0], self._val[1:] + theta = acos(w) * 2.0 + + return theta, _normalize(v) + + def tolist(self): + """Converts quaternion to a list""" + return self._val.tolist() + + def vector_norm(self): + """Calculates norm of quaternion""" + _, v = self.get_axisangle() + return np.linalg.norm(v) + + +def visualize_transition(circuit, + trace=False, + saveas=None, + fpg=100, + spg=2): + """ + Creates animation showing transitions between states of a single + qubit by applying quantum gates. + + Args: + circuit (QuantumCircuit): Qiskit single-qubit QuantumCircuit. Gates supported are + h,x, y, z, rx, ry, rz, s, sdg, t, tdg and u1. + trace (bool): Controls whether to display tracing vectors - history of 10 past vectors + at each step of the animation. + saveas (str): User can choose to save the animation as a video to their filesystem. + This argument is a string of path with filename and extension (e.g. "movie.mp4" to + save the video in current working directory). + fpg (int): Frames per gate. Finer control over animation smoothness and computiational + needs to render the animation. Works well for tkinter GUI as it is, for jupyter GUI + it might be preferable to choose fpg between 5-30. + spg (int): Seconds per gate. How many seconds should animation of individual gate + transitions take. + + Returns: + IPython.core.display.HTML: + If arg jupyter is set to True. Otherwise opens tkinter GUI and returns + after the GUI is closed. + + Raises: + ImportError: Must have Matplotlib (and/or IPython) installed. + VisualizationError: Given gate(s) are not supported. + + """ + jupyter = False + if ('ipykernel' in sys.modules) and ('spyder' not in sys.modules): + jupyter = True + + if not HAS_MATPLOTLIB: + raise ImportError("Must have Matplotlib installed.") + if not HAS_IPYTHON and jupyter is True: + raise ImportError("Must have IPython installed.") + if len(circuit.qubits) != 1: + raise VisualizationError("Only one qubit circuits are supported") + + frames_per_gate = fpg + time_between_frames = (spg*1000)/fpg + + # quaternions of gates which don't take parameters + gates = dict() + gates['x'] = ('x', _Quaternion.from_axisangle(np.pi / frames_per_gate, [1, 0, 0]), '#1abc9c') + gates['y'] = ('y', _Quaternion.from_axisangle(np.pi / frames_per_gate, [0, 1, 0]), '#2ecc71') + gates['z'] = ('z', _Quaternion.from_axisangle(np.pi / frames_per_gate, [0, 0, 1]), '#3498db') + gates['s'] = ('s', _Quaternion.from_axisangle(np.pi / 2 / frames_per_gate, + [0, 0, 1]), '#9b59b6') + gates['sdg'] = ('sdg', _Quaternion.from_axisangle(-np.pi / 2 / frames_per_gate, [0, 0, 1]), + '#8e44ad') + gates['h'] = ('h', _Quaternion.from_axisangle(np.pi / frames_per_gate, _normalize([1, 0, 1])), + '#34495e') + gates['t'] = ('t', _Quaternion.from_axisangle(np.pi / 4 / frames_per_gate, [0, 0, 1]), + '#e74c3c') + gates['tdg'] = ('tdg', _Quaternion.from_axisangle(-np.pi / 4 / frames_per_gate, [0, 0, 1]), + '#c0392b') + + implemented_gates = ['h', 'x', 'y', 'z', 'rx', 'ry', 'rz', 's', 'sdg', 't', 'tdg', 'u1'] + simple_gates = ['h', 'x', 'y', 'z', 's', 'sdg', 't', 'tdg'] + list_of_circuit_gates = [] + + for gate in circuit._data: + if gate[0].name not in implemented_gates: + raise VisualizationError("Gate {0} is not supported".format(gate[0].name)) + if gate[0].name in simple_gates: + list_of_circuit_gates.append(gates[gate[0].name]) + else: + theta = gate[0].params[0] + rad = np.deg2rad(theta) + if gate[0].name == 'rx': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [1, 0, 0]) + list_of_circuit_gates.append(('rx:'+str(theta), quaternion, '#16a085')) + elif gate[0].name == 'ry': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 1, 0]) + list_of_circuit_gates.append(('ry:'+str(theta), quaternion, '#27ae60')) + elif gate[0].name == 'rz': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 0, 1]) + list_of_circuit_gates.append(('rz:'+str(theta), quaternion, '#2980b9')) + elif gate[0].name == 'u1': + quaternion = _Quaternion.from_axisangle(rad / frames_per_gate, [0, 0, 1]) + list_of_circuit_gates.append(('u1:'+str(theta), quaternion, '#f1c40f')) + + if len(list_of_circuit_gates) == 0: + raise VisualizationError("Nothing to visualize.") + + starting_pos = _normalize(np.array([0, 0, 1])) + + fig = plt.figure(figsize=(5, 5)) + _ax = Axes3D(fig) + _ax.set_xlim(-10, 10) + _ax.set_ylim(-10, 10) + sphere = Bloch(axes=_ax) + + class Namespace: + """Helper class serving as scope container""" + def __init__(self): + self.new_vec = [] + self.last_gate = -2 + self.colors = [] + self.pnts = [] + + namespace = Namespace() + namespace.new_vec = starting_pos + + def animate(i): + sphere.clear() + + # starting gate count from -1 which is the initial vector + gate_counter = (i-1) // frames_per_gate + if gate_counter != namespace.last_gate: + namespace.pnts.append([[], [], []]) + namespace.colors.append(list_of_circuit_gates[gate_counter][2]) + + # starts with default vector [0,0,1] + if i == 0: + sphere.add_vectors(namespace.new_vec) + namespace.pnts[0][0].append(namespace.new_vec[0]) + namespace.pnts[0][1].append(namespace.new_vec[1]) + namespace.pnts[0][2].append(namespace.new_vec[2]) + namespace.colors[0] = 'r' + sphere.make_sphere() + return _ax + + namespace.new_vec = list_of_circuit_gates[gate_counter][1] * namespace.new_vec + + namespace.pnts[gate_counter+1][0].append(namespace.new_vec[0]) + namespace.pnts[gate_counter+1][1].append(namespace.new_vec[1]) + namespace.pnts[gate_counter+1][2].append(namespace.new_vec[2]) + + sphere.add_vectors(namespace.new_vec) + if trace: + # sphere.add_vectors(namespace.points) + for point_set in namespace.pnts: + sphere.add_points([point_set[0], point_set[1], point_set[2]]) + + sphere.vector_color = [list_of_circuit_gates[gate_counter][2]] + sphere.point_color = namespace.colors + sphere.point_marker = 'o' + + annotation_text = list_of_circuit_gates[gate_counter][0] + annotationvector = [1.4, -0.45, 1.7] + sphere.add_annotation(annotationvector, + annotation_text, + color=list_of_circuit_gates[gate_counter][2], + fontsize=30, + horizontalalignment='left') + + sphere.make_sphere() + + namespace.last_gate = gate_counter + return _ax + + def init(): + sphere.vector_color = ['r'] + return _ax + + ani = animation.FuncAnimation(fig, + animate, + range(frames_per_gate * len(list_of_circuit_gates)+1), + init_func=init, + blit=False, + repeat=False, + interval=time_between_frames) + + if saveas: + ani.save(saveas, fps=30) + if jupyter: + # This is necessary to overcome matplotlib memory limit + matplotlib.rcParams['animation.embed_limit'] = 50 + return HTML(ani.to_jshtml()) + plt.show() + plt.close(fig) + return None diff --git a/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml b/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml new file mode 100644 index 000000000000..eae8962c6f0e --- /dev/null +++ b/releasenotes/notes/channel-module-deprecations-in-pulse-ffc384b325b077f1.yaml @@ -0,0 +1,23 @@ +--- +upgrade: + - | + Channel ``buffer`` option was deprecated in Terra 0.11.0 and has now been + removed. To add a delay on a channel, specify it explicitly in your + Schedule with a Delay:: + + sched = Schedule() + sched += Delay(5)(DriveChannel(0)) + - | + PulseChannelSpec was deprecated in Terra 0.11.0 and has now been removed. + Use BackendConfiguration instead:: + + config = backend.configuration() + drive_chan_0 = config.drives(0) + acq_chan_0 = config.acquires(0) + + or, simply reference the channel directly, such as ``DriveChannel(index)``. + - | + An import path was deprecated in Terra 0.10.0 and has now been removed: for + ``PulseChannel``, ``DriveChannel``, ``MeasureChannel``, and + ``ControlChannel``, use ``from qiskit.pulse.channels import X`` in place of + ``from qiskit.pulse.channels.pulse_channels import X``. diff --git a/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml b/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml new file mode 100644 index 000000000000..9a822a74c2e4 --- /dev/null +++ b/releasenotes/notes/copy-rhs-on-extending-qc-9e65804b6b0ab4da.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + When extending a `QuantumCircuit` instance (extendee) with another circuit (extension), + the circuit is taken via reference. If a circuit is extended with itself that + leads to an infinite loop as extendee and extension are the same. + This bug is resolved by copying the extension if it is the same object as + the extendee. \ No newline at end of file diff --git a/releasenotes/notes/defaults_TranspileConfig-4109a90c278d46df.yaml b/releasenotes/notes/defaults_TranspileConfig-4109a90c278d46df.yaml new file mode 100644 index 000000000000..119448743bcb --- /dev/null +++ b/releasenotes/notes/defaults_TranspileConfig-4109a90c278d46df.yaml @@ -0,0 +1,18 @@ +--- +upgrade: + - | + The ``TranspileConfig`` was restructured to include only the information + needed to construct a ``PassManager`` and it was renamed to ``PassManagerConfig``. + That information is: + - ``initial_layout`` + - ``basis_gates`` + - ``coupling_map`` + - ``backend_properties`` + - ``seed_transpiler`` +deprecations: + - | + ``PassManager(..., callback=..., ...)`` parameter is deprecated. The parameter now + can be set at run-time, ``PassManager.run(..., callback=callback,...)``. + - | + The function ``transpile_circuit`` is deprecated. To transpile a circuit a + passmanager's ``run`` method should be called ``PassManager.run(cirucit, ...)``. \ No newline at end of file diff --git a/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml b/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml new file mode 100644 index 000000000000..9508b9ff53d5 --- /dev/null +++ b/releasenotes/notes/quibit-transition-visualization-a62d0d119569fa05.yaml @@ -0,0 +1,44 @@ +--- +features: + - | + Single-qubit gate transition visualization tool. Give + it a single-qubit circuit and it will return an animation + of qubit state transitions. A video codec must be installed + on the system in order to use this feature (ffmpeg). + + Gates h,x, y, z, rx, ry, rz, s, sdg, t, tdg, u1 supported. + + Argument fpg controls how many frames will be drawn per gate + and spg controls how many seconds animation will spend drawing + each gate transition. Default values make animation very smooth + but it takes longer to render in jupyter notebook because the + animation must be rendered with a video codec. + + Optional argument trace controls whether to show trailing points + or not. + + + Non-jupyter example:: + + from qiskit.visualization import visualize_transition + from qiskit import * + + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(70,0) + qc.rx(90,0) + qc.rz(120,0) + + visualize_transition(qc) + + Jupyter example with trace:: + from qiskit.visualization import visualize_transition + from qiskit import * + + qc = QuantumCircuit(1) + qc.h(0) + qc.ry(70,0) + qc.rx(90,0) + qc.rz(120,0) + + visualize_transition(qc, fpg=20, spg=1, trace=True) diff --git a/releasenotes/notes/support-scheduler-in-execute-42a1ebe9e3a67bbb.yaml b/releasenotes/notes/support-scheduler-in-execute-42a1ebe9e3a67bbb.yaml new file mode 100644 index 000000000000..3386cd5e06fe --- /dev/null +++ b/releasenotes/notes/support-scheduler-in-execute-42a1ebe9e3a67bbb.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Circuits can be scheduled during :func:`qiskit.execute.execute` by setting ``schedule_circuit=True``. + This allows users building :class:`qiskit.circuits.QuantumCircuit` objects to make use of custom scheduler + methods, such as the ``as_late_as_possible`` and ``as_soon_as_possible`` methods. + For example:: + + job = execute(qc, backend, schedule_circuit=True, scheduling_method="as_late_as_possible") diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 4afc9d59ddf2..4f00778aa492 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -25,6 +25,22 @@ class TestCircuitOperations(QiskitTestCase): """QuantumCircuit Operations tests.""" + def test_adding_self(self): + """Test that qc += qc finishes, which can be prone to infinite while-loops. + + This can occur e.g. when a user tries + >>> other_qc = qc + >>> other_qc += qc # or qc2.extend(qc) + """ + qc = QuantumCircuit(1) + qc.x(0) # must contain at least one operation to end up in a infinite while-loop + + # attempt addition, times out if qc is added via reference + qc += qc + + # finally, qc should contain two X gates + self.assertEqual(['x', 'x'], [x[0].name for x in qc.data]) + def test_combine_circuit_common(self): """Test combining two circuits with same registers. """ diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index c11ae37827aa..d0ad34219a07 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -36,7 +36,7 @@ ToffoliGate, HGate, RZGate, RXGate, RYGate, CryGate, CrxGate, FredkinGate, U3Gate, CHGate, CrzGate, Cu3Gate, - MSGate, Barrier) + MSGate, Barrier, RCCXGate, RCCCXGate) from qiskit.extensions.unitary import UnitaryGate import qiskit.extensions.standard as allGates @@ -350,7 +350,6 @@ def test_rotation_gates(self): dag = circuit_to_dag(qc) unroller = Unroller(['u3', 'cx']) uqc = dag_to_circuit(unroller.run(dag)) - print(uqc.size()) self.log.info('%s gate count: %d', uqc.name, uqc.size()) self.assertTrue(uqc.size() <= 93) # this limit could be changed @@ -483,6 +482,40 @@ def test_controlled_standard_gates(self, num_ctrl_qubits): target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) self.assertTrue(matrix_equal(Operator(cgate).data, target_mat, ignore_phase=True)) + @data(2, 3) + def test_relative_phase_toffoli_gates(self, num_ctrl_qubits): + """Test the relative phase Toffoli gates. + + This test compares the matrix representation of the relative phase gate classes + (i.e. RCCXGate().to_matrix()), the matrix obtained from the unitary simulator, + and the exact version of the gate as obtained through `_compute_control_matrix`. + """ + # get target matrix (w/o relative phase) + base_mat = XGate().to_matrix() + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) + + # build the matrix for the relative phase toffoli using the unitary simulator + circuit = QuantumCircuit(num_ctrl_qubits + 1) + if num_ctrl_qubits == 2: + circuit.rccx(0, 1, 2) + else: # num_ctrl_qubits == 3: + circuit.rcccx(0, 1, 2, 3) + simulator = BasicAer.get_backend('unitary_simulator') + simulated_mat = execute(circuit, simulator).result().get_unitary() + + # get the matrix representation from the class itself + if num_ctrl_qubits == 2: + repr_mat = RCCXGate().to_matrix() + else: # num_ctrl_qubits == 3: + repr_mat = RCCCXGate().to_matrix() + + # test up to phase + # note, that all entries may have an individual phase! (as opposed to a global phase) + self.assertTrue(matrix_equal(np.abs(simulated_mat), target_mat)) + + # compare simulated matrix with the matrix representation provided by the class + self.assertTrue(matrix_equal(simulated_mat, repr_mat)) + def _compute_control_matrix(base_mat, num_ctrl_qubits): """ diff --git a/test/python/providers/test_pulse_defaults.py b/test/python/providers/test_pulse_defaults.py index c025981d9f1f..ff8dcccd424a 100644 --- a/test/python/providers/test_pulse_defaults.py +++ b/test/python/providers/test_pulse_defaults.py @@ -49,8 +49,8 @@ def test_default_building(self): self.assertTrue(self.inst_map.has('u3', (0,))) self.assertTrue(self.inst_map.has('u3', 1)) self.assertTrue(self.inst_map.has('cx', (0, 1))) - self.assertEqual(self.inst_map.get_parameters('u1', 0), ('P1',)) - u1_minus_pi = self.inst_map.get('u1', 0, P1=1) + self.assertEqual(self.inst_map.get_parameters('u1', 0), ('P0',)) + u1_minus_pi = self.inst_map.get('u1', 0, P0=np.pi) fc_cmd = u1_minus_pi.instructions[0][-1].command self.assertEqual(fc_cmd.phase, -np.pi) diff --git a/test/python/pulse/test_channels.py b/test/python/pulse/test_channels.py index f77fab353f9f..d822f387c321 100644 --- a/test/python/pulse/test_channels.py +++ b/test/python/pulse/test_channels.py @@ -16,11 +16,9 @@ import unittest -from qiskit.pulse.channels import AcquireChannel, MemorySlot, RegisterSlot, SnapshotChannel -from qiskit.pulse.channels import PulseChannelSpec, Qubit -from qiskit.pulse.channels import DriveChannel, ControlChannel, MeasureChannel +from qiskit.pulse.channels import (AcquireChannel, MemorySlot, RegisterSlot, SnapshotChannel, + DriveChannel, ControlChannel, MeasureChannel) from qiskit.test import QiskitTestCase -from qiskit.test.mock import FakeOpenPulse2Q class TestAcquireChannel(QiskitTestCase): @@ -107,48 +105,5 @@ def test_default(self): self.assertEqual(measure_channel.name, 'm123') -class TestQubit(QiskitTestCase): - """Qubit tests.""" - - def test_default(self): - """Test default qubit. - """ - qubit = Qubit(1, DriveChannel(2), MeasureChannel(4), AcquireChannel(5), - control_channels=[ControlChannel(3)]) - - self.assertEqual(qubit.drive, DriveChannel(2)) - self.assertEqual(qubit.controls[0], ControlChannel(3)) - self.assertEqual(qubit.measure, MeasureChannel(4)) - self.assertEqual(qubit.acquire, AcquireChannel(5)) - - -class TestPulseSpecification(QiskitTestCase): - """PulseSpecification tests. (Deprecated)""" - - def test_default(self): - """Test default device specification. - """ - spec = PulseChannelSpec(n_qubits=2, n_control=0, n_registers=2) - - self.assertEqual(spec.drives[0], DriveChannel(0)) - self.assertEqual(spec.acquires[1], AcquireChannel(1)) - self.assertEqual(spec.memoryslots[0], MemorySlot(0)) - self.assertEqual(spec.registers[1], RegisterSlot(1)) - - def test_creation_from_backend_with_zero_u_channels(self): - """Test creation of device specification from backend. - """ - backend = FakeOpenPulse2Q() - - device = PulseChannelSpec.from_backend(backend) - - self.assertEqual(device.drives[0], DriveChannel(0)) - self.assertEqual(device.controls[0], ControlChannel(0)) - self.assertEqual(device.measures[0], MeasureChannel(0)) - self.assertEqual(device.acquires[0], AcquireChannel(0)) - self.assertEqual(device.registers[0], RegisterSlot(0)) - self.assertEqual(device.memoryslots[0], MemorySlot(0)) - - if __name__ == '__main__': unittest.main() diff --git a/test/python/pulse/test_cmd_def.py b/test/python/pulse/test_cmd_def.py index 825f0efb9272..1dfa59b14f20 100644 --- a/test/python/pulse/test_cmd_def.py +++ b/test/python/pulse/test_cmd_def.py @@ -21,7 +21,7 @@ from qiskit.qobj.converters import QobjToInstructionConverter from qiskit.qobj import PulseQobjInstruction from qiskit.pulse import (CmdDef, SamplePulse, Schedule, - PulseError, PersistentValue) + PulseError, PersistentValue, FrameChange) from qiskit.pulse.schedule import ParameterizedSchedule @@ -162,8 +162,28 @@ def test_build_cmd_def(self): pv_found = True self.assertTrue(pv_found) - self.assertEqual(cmd_def.get_parameters('u1', 0), ('P1',)) + self.assertEqual(cmd_def.get_parameters('u1', 0), ('P0',)) - u1_minus_pi = cmd_def.get('u1', 0, P1=1) + u1_minus_pi = cmd_def.get('u1', 0, P0=np.pi) fc_cmd = u1_minus_pi.instructions[0][-1].command self.assertEqual(fc_cmd.phase, -np.pi) + + def test_default_phases_parameters(self): + """Test parameters for phases.""" + defaults = self.backend.defaults() + cmd_def = defaults.build_cmd_def() + + for i in range(2): + u1_phases = [] + for _, instr in cmd_def.get('u1', i, P0=np.pi).instructions: + cmd = instr.command + if isinstance(cmd, FrameChange): + u1_phases.append(cmd.phase) + self.assertEqual(u1_phases, [-np.pi]) + + u2_phases = [] + for _, instr in cmd_def.get('u2', i, P0=0, P1=np.pi).instructions: + cmd = instr.command + if isinstance(cmd, FrameChange): + u2_phases.append(cmd.phase) + self.assertEqual(u2_phases, [-np.pi, 0]) diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index c660469bf70a..69fed8fd0724 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -295,26 +295,6 @@ def test_name_inherited(self): sched_snapshot = snapshot | sched1 self.assertEqual(sched_snapshot.name, 'snapshot_label') - def test_buffering(self): - """Test channel buffering.""" - buffer_chan = DriveChannel(0, buffer=5) - gp0 = pulse_lib.gaussian(duration=10, amp=0.7, sigma=3) - fc_pi_2 = FrameChange(phase=1.57) - - # no initial buffer - sched = Schedule() - sched += gp0(buffer_chan) - self.assertEqual(sched.duration, 10) - # this pulse should not be buffered - sched += gp0(buffer_chan) - self.assertEqual(sched.duration, 20) - # should not be buffered as framechange - sched += fc_pi_2(buffer_chan) - self.assertEqual(sched.duration, 20) - # use buffer with insert - sched = sched.insert(sched.duration, gp0(buffer_chan), buffer=True) - self.assertEqual(sched.duration, 30) - def test_multiple_parameters_not_returned(self): """Constructing ParameterizedSchedule object from multiple ParameterizedSchedules sharing arguments should not produce repeated parameters in resulting ParameterizedSchedule diff --git a/test/python/test_qasm_parser.py b/test/python/test_qasm_parser.py index c0f3aad3889a..8be70a1b5f8b 100644 --- a/test/python/test_qasm_parser.py +++ b/test/python/test_qasm_parser.py @@ -34,6 +34,7 @@ def parse(file_path, prec=15): class TestParser(QiskitTestCase): """QasmParser""" + def setUp(self): self.qasm_file_path = self._get_resource_path('example.qasm', Path.QASMS) self.qasm_file_path_fail = self._get_resource_path( @@ -47,10 +48,10 @@ def test_parser(self): res = parse(self.qasm_file_path) self.log.info(res) # TODO: For now only some basic checks. - self.assertEqual(len(res), 1931) + self.assertEqual(len(res), 2358) self.assertEqual(res[:12], "OPENQASM 2.0") self.assertEqual(res[14:41], "gate u3(theta,phi,lambda) q") - self.assertEqual(res[1915:1930], "measure r -> d;") + self.assertEqual(res[2342:2357], "measure r -> d;") def test_parser_fail(self): """should fail a for a not valid circuit.""" diff --git a/test/python/tools/jupyter/test_notebooks.py b/test/python/tools/jupyter/test_notebooks.py index daae2a83bfe4..070e408c34b3 100644 --- a/test/python/tools/jupyter/test_notebooks.py +++ b/test/python/tools/jupyter/test_notebooks.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=bad-docstring-quotes + """Tests for the wrapper functionality.""" import os @@ -20,6 +22,7 @@ import nbformat from nbconvert.preprocessors import ExecutePreprocessor +import qiskit from qiskit.tools.visualization import HAS_MATPLOTLIB from qiskit.test import (Path, QiskitTestCase, online_test, slow_test) @@ -30,6 +33,8 @@ JUPYTER_KERNEL = 'python3' +@unittest.skipUnless(hasattr(qiskit, 'IBMQ'), + 'qiskit-ibmq-provider is required for these tests') class TestJupyter(QiskitTestCase): """Notebooks test case.""" def setUp(self): diff --git a/test/python/transpiler/test_passmanager.py b/test/python/transpiler/test_passmanager.py index cae6c927af89..4606297e6d82 100644 --- a/test/python/transpiler/test_passmanager.py +++ b/test/python/transpiler/test_passmanager.py @@ -56,10 +56,10 @@ def callback(**kwargs): out_dict['dag'] = copy.deepcopy(kwargs['dag']) calls.append(out_dict) - passmanager = PassManager(callback=callback) + passmanager = PassManager() passmanager.append(Unroller(['u2'])) passmanager.append(Optimize1qGates()) - transpile(circuit, FakeRueschlikon(), pass_manager=passmanager) + transpile(circuit, FakeRueschlikon(), pass_manager=passmanager, callback=callback) self.assertEqual(len(calls), 2) self.assertEqual(len(calls[0]), 5) self.assertEqual(calls[0]['count'], 0) @@ -100,9 +100,9 @@ def callback(**kwargs): out_dict['dag'] = copy.deepcopy(kwargs['dag']) calls.append(out_dict) - passmanager = PassManager(callback=callback) + passmanager = PassManager() passmanager.append(CommutativeCancellation()) - passmanager.run(circuit) + passmanager.run(circuit, callback=callback) self.assertEqual(len(calls), 2) self.assertEqual(len(calls[0]), 5) self.assertEqual(calls[0]['count'], 0) diff --git a/test/python/transpiler/test_passmanager_run.py b/test/python/transpiler/test_passmanager_run.py index 99077ff9b16e..8d2c9911ef18 100644 --- a/test/python/transpiler/test_passmanager_run.py +++ b/test/python/transpiler/test_passmanager_run.py @@ -20,7 +20,7 @@ from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeMelbourne from qiskit.transpiler import Layout, CouplingMap -from qiskit.transpiler.transpile_config import TranspileConfig +from qiskit.transpiler.pass_manager_config import PassManagerConfig class TestPassManagerRun(QiskitTestCase): @@ -56,12 +56,11 @@ def test_default_pass_manager_single(self): basis_gates = FakeMelbourne().configuration().basis_gates initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] - pass_manager = level_1_pass_manager(TranspileConfig( + pass_manager = level_1_pass_manager(PassManagerConfig( basis_gates=basis_gates, coupling_map=CouplingMap(coupling_map), initial_layout=Layout.from_qubit_list(initial_layout), - seed_transpiler=42, - optimization_level=1)) + seed_transpiler=42)) new_circuit = pass_manager.run(circuit) for gate, qargs, _ in new_circuit.data: @@ -103,12 +102,11 @@ def test_default_pass_manager_two(self): basis_gates = FakeMelbourne().configuration().basis_gates initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] - pass_manager = level_1_pass_manager(TranspileConfig( + pass_manager = level_1_pass_manager(PassManagerConfig( basis_gates=basis_gates, coupling_map=CouplingMap(coupling_map), initial_layout=Layout.from_qubit_list(initial_layout), - seed_transpiler=42, - optimization_level=1)) + seed_transpiler=42)) new_circuits = pass_manager.run([circuit1, circuit2]) for new_circuit in new_circuits: diff --git a/test/python/visualization/references/instruction_matplotlib_ref.png b/test/python/visualization/references/instruction_matplotlib_ref.png index 2393366ea15e..12a8b326b4ce 100644 Binary files a/test/python/visualization/references/instruction_matplotlib_ref.png and b/test/python/visualization/references/instruction_matplotlib_ref.png differ diff --git a/test/python/visualization/references/parametric_matplotlib_ref.png b/test/python/visualization/references/parametric_matplotlib_ref.png index 4f92ac3abeb0..030a3e567144 100644 Binary files a/test/python/visualization/references/parametric_matplotlib_ref.png and b/test/python/visualization/references/parametric_matplotlib_ref.png differ diff --git a/test/python/visualization/references/schedule_matplotlib_ref.png b/test/python/visualization/references/schedule_matplotlib_ref.png index 7427182213b0..b4bf636d10c4 100644 Binary files a/test/python/visualization/references/schedule_matplotlib_ref.png and b/test/python/visualization/references/schedule_matplotlib_ref.png differ diff --git a/test/python/visualization/references/schedule_show_framechange_ref.png b/test/python/visualization/references/schedule_show_framechange_ref.png index 24b9ef693f5a..26d1097e7204 100644 Binary files a/test/python/visualization/references/schedule_show_framechange_ref.png and b/test/python/visualization/references/schedule_show_framechange_ref.png differ diff --git a/test/python/visualization/test_pulse_visualization_output.py b/test/python/visualization/test_pulse_visualization_output.py index 030fe12ea4a7..eff4706b2936 100644 --- a/test/python/visualization/test_pulse_visualization_output.py +++ b/test/python/visualization/test_pulse_visualization_output.py @@ -19,10 +19,11 @@ import os import unittest -from qiskit.tools.visualization import HAS_MATPLOTLIB, pulse_drawer +from qiskit.tools.visualization import HAS_MATPLOTLIB +from qiskit.visualization import pulse_drawer from qiskit.pulse.channels import (DriveChannel, MeasureChannel, ControlChannel, AcquireChannel, MemorySlot, RegisterSlot) -from qiskit.pulse.commands import FrameChange, Acquire, PersistentValue, Snapshot, Delay, Gaussian +from qiskit.pulse.commands import FrameChange, Acquire, ConstantPulse, Snapshot, Delay, Gaussian from qiskit.pulse.schedule import Schedule from qiskit.pulse import pulse_lib @@ -61,7 +62,7 @@ def sample_schedule(self): delay = Delay(100) sched = Schedule() sched = sched.append(gp0(DriveChannel(0))) - sched = sched.insert(0, PersistentValue(value=0.2 + 0.4j)(ControlChannel(0))) + sched = sched.insert(0, ConstantPulse(duration=60, amp=0.2 + 0.4j)(ControlChannel(0))) sched = sched.insert(60, FrameChange(phase=-1.57)(DriveChannel(0))) sched = sched.insert(30, gp1(DriveChannel(1))) sched = sched.insert(60, gp0(ControlChannel(0)))