Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy gates2 #31

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions qiskit/algorithms/state_fidelities/compute_uncompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def create_fidelity_circuit(
circuit_1.remove_final_measurements()
if len(circuit_2.clbits) > 0:
circuit_2.remove_final_measurements()

# THE LINE BELOW CREATES A CIRCUIT WITH LAZY GATES!
# WHICH LEADS TO ERRORS DOWNSTREAM.
# THIS MIGHT NOT NEED BE THE BEST PLACE TO FIX.
circuit = circuit_1.compose(circuit_2.inverse())
circuit.measure_all()
return circuit
Expand Down Expand Up @@ -141,7 +143,6 @@ def _run(
ValueError: At least one pair of circuits must be defined.
AlgorithmError: If the sampler job is not completed successfully.
"""

circuits = self._construct_circuits(circuits_1, circuits_2)
if len(circuits) == 0:
raise ValueError(
Expand Down
1 change: 1 addition & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@

# pylint: disable=cyclic-import
from .controlledgate import ControlledGate
from .lazy_op import LazyOp
from .instruction import Instruction
from .instructionset import InstructionSet
from .operation import Operation
Expand Down
6 changes: 3 additions & 3 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

from qiskit.circuit.exceptions import CircuitError
from qiskit.extensions import UnitaryGate
from . import ControlledGate, Gate, QuantumRegister, QuantumCircuit
from . import ControlledGate, Gate, QuantumRegister, QuantumCircuit, Operation


def add_control(
operation: Union[Gate, ControlledGate],
operation: Union[Gate, ControlledGate, Operation],
num_ctrl_qubits: int,
label: Union[str, None],
ctrl_state: Union[int, str, None],
Expand Down Expand Up @@ -62,7 +62,7 @@ def add_control(


def control(
operation: Union[Gate, ControlledGate],
operation: Union[Gate, ControlledGate, Operation],
num_ctrl_qubits: Optional[int] = 1,
label: Optional[Union[None, str]] = None,
ctrl_state: Optional[Union[None, int, str]] = None,
Expand Down
8 changes: 8 additions & 0 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ def control(
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[int, str]] = None,
):
return self.lazy_control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state)

def real_control(
self,
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[int, str]] = None,
):
"""Return controlled version of gate. See :class:`.ControlledGate` for usage.

Expand Down
37 changes: 36 additions & 1 deletion qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,16 @@ def reverse_ops(self):
reverse_inst.definition = reversed_definition
return reverse_inst

def print_rec(self, offset=0, depth=100, header=""):
"""Temporary debugging function."""
line = " " * offset + header + " Instruction " + self.name + " "
print(line)
if depth == 0:
return
if getattr(self, "definition", None) is not None:
def_header = "DefCircuit"
self.definition.print_rec(offset + 2, depth - 1, header=def_header)

def inverse(self):
"""Invert this instruction.

Expand All @@ -358,6 +368,25 @@ def inverse(self):
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.
"""
return self.lazy_inverse()
# return self.real_inverse()

def real_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

Expand Down Expand Up @@ -385,10 +414,16 @@ def inverse(self):
else:
inverse_gate = Gate(name=name, num_qubits=self.num_qubits, params=self.params.copy())

from qiskit.circuit import LazyOp

inverse_definition = self._definition.copy_empty_like()
inverse_definition.global_phase = -inverse_definition.global_phase
for inst in reversed(self._definition):
inverse_definition._append(inst.operation.inverse(), inst.qubits, inst.clbits)
inverse_op = inst.operation.inverse()
if isinstance(inverse_op, LazyOp):
inverse_op = inst.operation.real_inverse()
assert not isinstance(inverse_op, LazyOp)
inverse_definition._append(inverse_op, inst.qubits, inst.clbits)
inverse_gate.definition = inverse_definition
return inverse_gate

Expand Down
115 changes: 115 additions & 0 deletions qiskit/circuit/inverse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from qiskit.circuit import Operation, LazyOp

# ToDo: this is absolutely horrible!
# But in order to support gate.control().inverse() == gate.inverse().control(),
# we need to consider cases where both gates are lazy, both gates are not lazy,
# one gate is lazy and one is not.


def are_inverse_ops(op1: Operation, op2: Operation) -> bool:

if (op1.num_qubits != op2.num_qubits) or (op1.num_clbits != op2.num_clbits):
return False

# Case 1: both ops are not lazy
if not isinstance(op1, LazyOp) and not isinstance(op2, LazyOp):

if getattr(op1, "inverse", None) is not None:
op1_inverse = op1.inverse()
if isinstance(op1_inverse, LazyOp):
op1_inverse = op1.real_inverse()
assert not isinstance(op1_inverse, LazyOp)
return op1_inverse == op2

if getattr(op2, "inverse", None) is not None:
op2_inverse = op2.inverse()
if isinstance(op2_inverse, LazyOp):
op2_inverse = op2.real_inverse()
assert not isinstance(op2_inverse, LazyOp)
return op2_inverse == op1

return False

# Case 2: both ops are lazy
if isinstance(op1, LazyOp) and isinstance(op2, LazyOp):

if op1.num_ctrl_qubits != op2.num_ctrl_qubits:
return False
if op1.ctrl_state != op2.ctrl_state:
return False

if (op1.inverted == op2.inverted) and are_inverse_ops(op1.base_op, op2.base_op):
return True

if (op1.inverted != op2.inverted) and are_equal_ops(op1.base_op, op2.base_op):
return True

return False

# Case 3: op1 is lazy, op2 is not
if isinstance(op1, LazyOp) and not isinstance(op2, LazyOp):
if op1.num_ctrl_qubits != 0:
return False
if not op1.inverted:
return are_inverse_ops(op1.base_op, op2)
else:
return are_equal_ops(op1.base_op, op2)

# Case 4: op2 is lazy, op1 is not
if isinstance(op2, LazyOp) and not isinstance(op1, LazyOp):
if op2.num_ctrl_qubits != 0:
return False
if not op2.inverted:
return are_inverse_ops(op2.base_op, op1)
else:
return are_equal_ops(op2.base_op, op1)

# Actually, we should not be here
return False


def are_equal_ops(op1: Operation, op2: Operation) -> bool:

if (op1.num_qubits != op2.num_qubits) or (op1.num_clbits != op2.num_clbits):
return False

# Case 1: both ops are not lazy, we default to the standard equality between such gates
if not isinstance(op1, LazyOp) and not isinstance(op2, LazyOp):
return op1 == op2

# Case 2: both ops are lazy
if isinstance(op1, LazyOp) and isinstance(op2, LazyOp):

if op1.num_ctrl_qubits != op2.num_ctrl_qubits:
return False
if op1.ctrl_state != op2.ctrl_state:
return False

if (op1.inverted == op2.inverted) and are_equal_ops(op1.base_op, op2.base_op):
return True

if (op1.inverted != op2.inverted) and are_inverse_ops(op1.base_op, op2.base_op):
return True

return False

# Case 3: op1 is lazy, op2 is not
if isinstance(op1, LazyOp) and not isinstance(op2, LazyOp):
if op1.num_ctrl_qubits != 0:
return False
if not op1.inverted:
return are_equal_ops(op1.base_op, op2)
else:
return are_inverse_ops(op1.base_op, op2)

# Case 4: op2 is lazy, op1 is not
if isinstance(op2, LazyOp) and not isinstance(op1, LazyOp):
if op2.num_ctrl_qubits != 0:
return False
if not op2.inverted:
return are_equal_ops(op2.base_op, op1)
else:
return are_inverse_ops(op2.base_op, op1)

# Actually, we should not be here
return False
131 changes: 131 additions & 0 deletions qiskit/circuit/lazy_op.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from typing import Optional, Union

from qiskit.circuit.operation import Operation
from qiskit.circuit._utils import _compute_control_matrix, _ctrl_state_to_int


class LazyOp(Operation):
"""Gate and modifiers inside."""

def __init__(
self,
base_op,
num_ctrl_qubits=0,
ctrl_state: Optional[Union[int, str]] = None,
inverted=False,
label: Optional[str] = None,
):
self.base_op = base_op
self.num_ctrl_qubits = num_ctrl_qubits
self.ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits)
self.inverted = inverted
self._name = "lazy"
self.label = label

@property
def name(self):
"""Unique string identifier for operation type."""
return self._name

@name.setter
def name(self, new_name):
self._name = new_name

@property
def num_qubits(self):
"""Number of qubits."""
return self.num_ctrl_qubits + self.base_op.num_qubits

@property
def num_clbits(self):
"""Number of classical bits."""
return self.base_op.num_clbits

def lazy_inverse(self):
"""Returns lazy inverse
Maybe does not belong here
"""

# ToDo: Should we copy base_op?
return LazyOp(
self.base_op,
num_ctrl_qubits=self.num_ctrl_qubits,
ctrl_state=self.ctrl_state,
inverted=not self.inverted,
)

def inverse(self):
return self.lazy_inverse()

def lazy_control(
self,
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[int, str]] = None,
):
"""Maybe does not belong here"""

ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits)
new_num_ctrl_qubits = self.num_ctrl_qubits + num_ctrl_qubits
new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state

return LazyOp(
self.base_op,
num_ctrl_qubits=new_num_ctrl_qubits,
ctrl_state=new_ctrl_state,
inverted=self.inverted,
)

def control(
self,
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[int, str]] = None,
):
return self.lazy_control(num_ctrl_qubits, label, ctrl_state)

def __eq__(self, other) -> bool:
"""Checks if two LazyOps are equal."""

from qiskit.circuit.inverse import are_equal_ops
return are_equal_ops(self, other)

def print_rec(self, offset=0, depth=100, header=""):
"""Temporary debug function."""
line = " " * offset + header + \
" LazyGate " + self.name + \
"[ c" + str(self.num_ctrl_qubits) + " inv" + str(self.inverted) + "]"
print(line)
if depth >= 0:
self.base_op.print_rec(offset + 2, depth - 1, header="base gate")

def copy(self) -> "LazyOp":
"""Return a copy of the :class:`LazyOp`."""
return LazyOp(
base_op=self.base_op.copy(),
num_ctrl_qubits=self.num_ctrl_qubits,
ctrl_state=self.ctrl_state,
inverted=self.inverted,
)

def to_matrix(self):
"""Return a matrix representation (allowing to construct Operator)."""
from qiskit.quantum_info import Operator

operator = Operator(self.base_op)

if self.inverted:
operator = operator.power(-1)

return _compute_control_matrix(operator.data, self.num_ctrl_qubits, self.ctrl_state)

@property
def definition(self):
"""
Question: do we want lazy ops to have the definition function?
"""
from qiskit.transpiler.passes.basis.unroll_lazy import UnrollLazy

unrolled_op = UnrollLazy()._unroll_op(self)
return unrolled_op.definition

Loading