Source code for qiskit.circuit.instruction

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017.
#
# 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.

"""
A generic quantum instruction.

Instructions can be implementable on hardware (u, cx, etc.) or in simulation
(snapshot, noise, etc.).

Instructions can be unitary (a.k.a Gate) or non-unitary.

Instructions are identified by the following:

    name: A string to identify the type of instruction.
          Used to request a specific instruction on the backend, or in visualizing circuits.

    num_qubits, num_clbits: dimensions of the instruction.

    params: List of parameters to specialize a specific instruction instance.

Instructions do not have any context about where they are in a circuit (which qubits/clbits).
The circuit itself keeps this context.
"""
import copy
from itertools import zip_longest

import numpy

from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.classicalregister import ClassicalRegister
from qiskit.qobj.qasm_qobj import QasmQobjInstruction
from qiskit.circuit.parameter import ParameterExpression
from .tools import pi_check

_CUTOFF_PRECISION = 1E-10


[docs]class Instruction: """Generic quantum instruction.""" def __init__(self, name, num_qubits, num_clbits, params): """Create a new instruction. Args: name (str): instruction name num_qubits (int): instruction's qubit width num_clbits (int): instruction's clbit width params (list[int|float|complex|str|ndarray|list|ParameterExpression]): list of parameters Raises: CircuitError: when the register is not in the correct format. """ if not isinstance(num_qubits, int) or not isinstance(num_clbits, int): raise CircuitError("num_qubits and num_clbits must be integer.") if num_qubits < 0 or num_clbits < 0: raise CircuitError( "bad instruction dimensions: %d qubits, %d clbits." % num_qubits, num_clbits) self.name = name self.num_qubits = num_qubits self.num_clbits = num_clbits self._params = [] # a list of gate params stored # tuple (ClassicalRegister, int) when the instruction has a conditional ("if") self.condition = None # list of instructions (and their contexts) that this instruction is composed of # empty definition means opaque or fundamental instruction self._definition = None self.params = params def __eq__(self, other): """Two instructions are the same if they have the same name, same dimensions, and same params. Args: other (instruction): other instruction Returns: bool: are self and other equal. """ if type(self) is not type(other) or \ self.name != other.name or \ self.num_qubits != other.num_qubits or \ self.num_clbits != other.num_clbits or \ self.definition != other.definition: return False for self_param, other_param in zip_longest(self.params, other.params): try: if self_param == other_param: continue except ValueError: pass try: if numpy.shape(self_param) == numpy.shape(other_param) \ and numpy.allclose(self_param, other_param, atol=_CUTOFF_PRECISION): continue except TypeError: pass try: if numpy.isclose(float(self_param), float(other_param), atol=_CUTOFF_PRECISION): continue except TypeError: pass return False return True def _define(self): """Populates self.definition with a decomposition of this gate.""" pass @property def params(self): """return instruction params.""" return self._params @params.setter def params(self, parameters): self._params = [] for single_param in parameters: # example: u2(pi/2, sin(pi/4)) if isinstance(single_param, (ParameterExpression)): self._params.append(single_param) elif isinstance(single_param, numpy.number): self._params.append(single_param.item()) # example: u3(0.1, 0.2, 0.3) elif isinstance(single_param, (int, float)): self._params.append(single_param) # example: Initialize([complex(0,1), complex(0,0)]) elif isinstance(single_param, complex): self._params.append(single_param) # example: snapshot('label') elif isinstance(single_param, str): self._params.append(single_param) # example: Aer expectation_value_snapshot [complex, 'X'] elif isinstance(single_param, list): self._params.append(single_param) # example: numpy.array([[1, 0], [0, 1]]) elif isinstance(single_param, numpy.ndarray): self._params.append(single_param) else: raise CircuitError("invalid param type {0} in instruction " "{1}".format(type(single_param), self.name))
[docs] def is_parameterized(self): """Return True .IFF. instruction is parameterized else False""" return any(isinstance(param, ParameterExpression) and param.parameters for param in self.params)
@property def definition(self): """Return definition in terms of other basic gates.""" if self._definition is None: self._define() return self._definition @definition.setter def definition(self, array): """Set matrix representation""" self._definition = array @property def decompositions(self): """Get the decompositions of the instruction from the SessionEquivalenceLibrary.""" # pylint: disable=cyclic-import from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel return sel.get_entry(self) @decompositions.setter def decompositions(self, decompositions): """Set the decompositions of the instruction from the SessionEquivalenceLibrary.""" # pylint: disable=cyclic-import from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel sel.set_entry(self, decompositions)
[docs] def add_decomposition(self, decomposition): """Add a decomposition of the instruction to the SessionEquivalenceLibrary.""" # pylint: disable=cyclic-import from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel sel.add_equivalence(self, decomposition)
[docs] def assemble(self): """Assemble a QasmQobjInstruction""" instruction = QasmQobjInstruction(name=self.name) # Evaluate parameters if self.params: params = [ x.evalf(x) if hasattr(x, 'evalf') else x for x in self.params] instruction.params = params # Add placeholder for qarg and carg params if self.num_qubits: instruction.qubits = list(range(self.num_qubits)) if self.num_clbits: instruction.memory = list(range(self.num_clbits)) # Add condition parameters for assembler. This is needed to convert # to a qobj conditional instruction at assemble time and after # conversion will be deleted by the assembler. if self.condition: instruction._condition = self.condition return instruction
[docs] def mirror(self): """For a composite instruction, reverse the order of sub-gates. This is done by recursively mirroring all sub-instructions. It does not invert any gate. Returns: qiskit.circuit.Instruction: a fresh gate with sub-gates reversed """ if not self._definition: return self.copy() reverse_inst = self.copy(name=self.name + '_mirror') reverse_inst.definition = [] for inst, qargs, cargs in reversed(self._definition): reverse_inst._definition.append((inst.mirror(), qargs, cargs)) return reverse_inst
[docs] def inverse(self): """Invert this instruction. If the instruction is composite (i.e. has a definition), then its definition will be recursively inverted. Special instructions inheriting from Instruction can implement their own inverse (e.g. T and Tdg, Barrier, etc.) Returns: qiskit.circuit.Instruction: a fresh instruction for the inverse Raises: CircuitError: if the instruction is not composite and an inverse has not been implemented for it. """ if self.definition is None: raise CircuitError("inverse() not implemented for %s." % self.name) inverse_gate = self.copy(name=self.name + '_dg') inverse_gate._definition = [] for inst, qargs, cargs in reversed(self._definition): inverse_gate._definition.append((inst.inverse(), qargs, cargs)) return inverse_gate
[docs] def c_if(self, classical, val): """Add classical condition on register classical and value val.""" if not isinstance(classical, ClassicalRegister): raise CircuitError("c_if must be used with a classical register") if val < 0: raise CircuitError("condition value should be non-negative") self.condition = (classical, val) return self
[docs] def copy(self, name=None): """ Copy of the instruction. Args: name (str): name to be given to the copied circuit, if None then the name stays the same. Returns: qiskit.circuit.Instruction: a copy of the current instruction, with the name updated if it was provided """ cpy = self.__deepcopy__() if name: cpy.name = name return cpy
def __deepcopy__(self, _memo=None): cpy = copy.copy(self) cpy._params = copy.copy(self._params) if self._definition: cpy._definition = copy.deepcopy(self._definition, _memo) return cpy def _qasmif(self, string): """Print an if statement if needed.""" if self.condition is None: return string return "if(%s==%d) " % (self.condition[0].name, self.condition[1]) + string
[docs] def qasm(self): """Return a default OpenQASM string for the instruction. Derived instructions may override this to print in a different format (e.g. measure q[0] -> c[0];). """ name_param = self.name if self.params: name_param = "%s(%s)" % (name_param, ",".join( [pi_check(i, ndigits=8, output='qasm') for i in self.params])) return self._qasmif(name_param)
[docs] def broadcast_arguments(self, qargs, cargs): """ Validation of the arguments. Args: qargs (List): List of quantum bit arguments. cargs (List): List of classical bit arguments. Yields: Tuple(List, List): A tuple with single arguments. Raises: CircuitError: If the input is not valid. For example, the number of arguments does not match the gate expectation. """ if len(qargs) != self.num_qubits: raise CircuitError( 'The amount of qubit arguments does not match the instruction expectation.') # [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] flat_qargs = [qarg for sublist in qargs for qarg in sublist] flat_cargs = [carg for sublist in cargs for carg in sublist] yield flat_qargs, flat_cargs
def _return_repeat(self, exponent): return Instruction(name="%s*%s" % (self.name, exponent), num_qubits=self.num_qubits, num_clbits=self.num_clbits, params=self.params)
[docs] def repeat(self, n): """Creates an instruction with `gate` repeated `n` amount of times. Args: n (int): Number of times to repeat the instruction Returns: qiskit.circuit.Instruction: Containing the definition. Raises: CircuitError: If n < 1. """ if int(n) != n or n < 1: raise CircuitError("Repeat can only be called with strictly positive integer.") n = int(n) instruction = self._return_repeat(n) qargs = [] if self.num_qubits == 0 else QuantumRegister(self.num_qubits, 'q') cargs = [] if self.num_clbits == 0 else ClassicalRegister(self.num_clbits, 'c') instruction.definition = [(self, qargs[:], cargs[:])] * n return instruction