Skip to content
Merged
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
1 change: 1 addition & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from .classicalregister import ClassicalRegister, Clbit
from .quantumregister import QuantumRegister, Qubit
from .gate import Gate
# pylint: disable=cyclic-import
from .controlledgate import ControlledGate
from .instruction import Instruction
from .instructionset import InstructionSet
Expand Down
72 changes: 46 additions & 26 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,39 @@
"""
Add control to operation if supported.
"""
from qiskit import QiskitError
from typing import Union, Optional

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


def add_control(operation: Union[Gate, ControlledGate],
num_ctrl_qubits: int,
label: Union[str, None],
ctrl_state: Union[int, str, None]) -> ControlledGate:
"""For standard gates, if the controlled version already exists in the
library, it will be returned (e.g. XGate.control() = CnotGate().

For more generic gates, this method implements the controlled
version by first decomposing into the ['u1', 'u3', 'cx'] basis, then
controlling each gate in the decomposition.

def add_control(operation, num_ctrl_qubits, label):
"""Add num_ctrl_qubits controls to operation
Open controls are implemented by conjugating the control line with
X gates. Adds num_ctrl_qubits controls to operation.

Args:
operation (Gate or ControlledGate): operation to add control to.
num_ctrl_qubits (int): number of controls to add to gate (default=1)
label (str): optional gate label
operation: Operation for which control will be added.
num_ctrl_qubits: The number of controls to add to gate (default=1).
label: Optional gate label.
ctrl_state (int or str or None): The control state in decimal or as
a bitstring (e.g. '111'). If specified as a bitstring the length
must equal num_ctrl_qubits, MSB on left. If None, use
2**num_ctrl_qubits-1.

Returns:
ControlledGate: controlled version of gate. This default algorithm
uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size
num_qubits + 2*num_ctrl_qubits - 1.
Controlled version of gate.

"""
import qiskit.extensions.standard as standard
if isinstance(operation, standard.RZGate) or operation.name == 'rz':
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from before, but why is Rz a special case here, and all other gates are further down?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is due to the global phase issue. When num_ctrl_qubits=1 CrzGate is properly retrieved. However when num_ctrl_qubits > 1, the algorithm computes the control gate from the definition of the base gate. Since RzGate is defined by U1Gate, this leads to the wrong phase. By reducing the number of controls by 1 and using instead the definition from CrzGate, which is phase correct, the proper controlled version is generated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so maybe a comment here so someone reading the code would know why this is the way it is

Expand All @@ -42,29 +59,34 @@ def add_control(operation, num_ctrl_qubits, label):
if isinstance(operation, UnitaryGate):
# attempt decomposition
operation._define()
return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label)
return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)


def control(operation, num_ctrl_qubits=1, label=None):
def control(operation: Union[Gate, ControlledGate],
num_ctrl_qubits: Optional[int] = 1,
label: Optional[Union[None, str]] = None,
ctrl_state: Optional[Union[None, int, str]] = None) -> ControlledGate:
"""Return controlled version of gate using controlled rotations

Args:
operation (Gate or Controlledgate): gate to create ControlledGate from
num_ctrl_qubits (int): number of controls to add to gate (default=1)
label (str): optional gate label
operation: gate to create ControlledGate from
num_ctrl_qubits: number of controls to add to gate (default=1)
label: optional gate label
ctrl_state: The control state in decimal or as
a bitstring (e.g. '111'). If specified as a bitstring the length
must equal num_ctrl_qubits, MSB on left. If None, use
2**num_ctrl_qubits-1.

Returns:
ControlledGate: controlled version of gate. This default algorithm
uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size
num_qubits + 2*num_ctrl_qubits - 1.
Controlled version of gate.

Raises:
QiskitError: gate contains non-gate in definitionl
CircuitError: gate contains non-gate in definition
"""
from math import pi
# pylint: disable=cyclic-import
import qiskit.circuit.controlledgate as controlledgate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.quantumcircuit import QuantumCircuit
# pylint: disable=unused-import
import qiskit.extensions.standard.multi_control_rotation_gates
import qiskit.extensions.standard.multi_control_toffoli_gate
Expand All @@ -73,7 +95,6 @@ def control(operation, num_ctrl_qubits=1, label=None):
q_control = QuantumRegister(num_ctrl_qubits, name='control')
q_target = QuantumRegister(operation.num_qubits, name='target')
q_ancillae = None # TODO: add

qc = QuantumCircuit(q_control, q_target)

if operation.name == 'x' or (
Expand Down Expand Up @@ -122,7 +143,7 @@ def control(operation, num_ctrl_qubits=1, label=None):
None,
mode='noancilla')
else:
raise QiskitError('gate contains non-controllable instructions')
raise CircuitError('gate contains non-controllable instructions')
instr = qc.to_instruction()
if isinstance(operation, controlledgate.ControlledGate):
new_num_ctrl_qubits = num_ctrl_qubits + operation.num_ctrl_qubits
Expand All @@ -146,20 +167,19 @@ def control(operation, num_ctrl_qubits=1, label=None):
operation.params,
label=label,
num_ctrl_qubits=new_num_ctrl_qubits,
definition=instr.definition)
definition=instr.definition,
ctrl_state=ctrl_state)
cgate.base_gate = base_gate
return cgate


def _gate_to_circuit(operation):
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
qr = QuantumRegister(operation.num_qubits)
qc = QuantumCircuit(qr, name=operation.name)
if hasattr(operation, 'definition') and operation.definition:
for rule in operation.definition:
if rule[0].name in {'id', 'barrier', 'measure', 'snapshot'}:
raise QiskitError('Cannot make controlled gate with {} instruction'.format(
raise CircuitError('Cannot make controlled gate with {} instruction'.format(
rule[0].name))
qc.append(rule[0], qargs=[qr[bit.index] for bit in rule[1]], cargs=[])
else:
Expand Down
81 changes: 76 additions & 5 deletions qiskit/circuit/controlledgate.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@
"""
Controlled unitary gate.
"""

from qiskit.circuit.exceptions import CircuitError
from .gate import Gate
from . import QuantumRegister


class ControlledGate(Gate):
"""Controlled unitary gate."""

def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1,
definition=None):
"""Create a new gate.
definition=None, ctrl_state=None):
"""Create a controlled gate.

Attributes:
num_ctrl_qubits (int): The number of control qubits.
ctrl_state (int): The control state in decimal notation.

Args:
name (str): The Qobj name of the gate.
Expand All @@ -34,8 +38,13 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1,
label (str or None): An optional label for the gate [Default: None]
num_ctrl_qubits (int): Number of control qubits.
definition (list): list of gate rules for implementing this gate.
ctrl_state (int or str or None): The control state in decimal or as
a bitstring (e.g. '111'). If specified as a bitstring the length
must equal num_ctrl_qubits, MSB on left. If None, use
2**num_ctrl_qubits-1.
Raises:
CircuitError: num_ctrl_qubits >= num_qubits
CircuitError: ctrl_state < 0 or ctrl_state > 2**num_ctrl_qubits.
"""
super().__init__(name, num_qubits, params, label=label)
if num_ctrl_qubits < num_qubits:
Expand All @@ -50,14 +59,76 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1,
self.base_gate = base_gate.base_gate
else:
self.base_gate = base_gate
self._ctrl_state = None
self.ctrl_state = ctrl_state

@property
def definition(self):
"""Return definition in terms of other basic gates. If the gate has
open controls, as determined from `self.ctrl_state`, the returned
definition is conjugated with X."""
if not self._definition:
self._define()
# pylint: disable=cyclic-import
from qiskit.extensions.standard import XGate, CnotGate
bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits)
# hacky way to get register assuming single register
if self._definition:
qreg = self._definition[0][1][0].register
elif isinstance(self, CnotGate):
qreg = QuantumRegister(self.num_qubits, 'q')
self._definition = [(self, [qreg[0], qreg[1]], [])]
open_rules = []
for qind, val in enumerate(bit_ctrl_state[::-1]):
if val == '0':
open_rules.append([XGate(), [qreg[qind]], []])
return open_rules + self._definition + open_rules

@definition.setter
def definition(self, excited_def):
"""Set controlled gate definition with closed controls."""
super(Gate, self.__class__).definition.fset(self, excited_def)

@property
def ctrl_state(self):
"""Return the control state of the gate as a decimal integer."""
return self._ctrl_state

@ctrl_state.setter
def ctrl_state(self, ctrl_state):
"""Set the control state of this gate.

Args:
ctrl_state (int or str or None): The control state of the gate.

Raises:
CircuitError: ctrl_state is invalid.
"""
if isinstance(ctrl_state, str):
try:
assert len(ctrl_state) == self.num_ctrl_qubits
ctrl_state = int(ctrl_state, 2)
except ValueError:
raise CircuitError('invalid control bit string: ' + ctrl_state)
except AssertionError:
raise CircuitError('invalid control bit string: length != '
'num_ctrl_qubits')
if isinstance(ctrl_state, int):
if 0 <= ctrl_state < 2**self.num_ctrl_qubits:
self._ctrl_state = ctrl_state
else:
raise CircuitError('invalid control state specification')
elif ctrl_state is None:
self._ctrl_state = 2**self.num_ctrl_qubits - 1
else:
raise CircuitError('invalid control state specification')

def __eq__(self, other):
if not isinstance(other, ControlledGate):
return False
else:
return (other.num_ctrl_qubits == self.num_ctrl_qubits and
self.base_gate == other.base_gate and
super().__eq__(other))
self.base_gate == other.base_gate)

def inverse(self):
"""Invert this gate by calling inverse on the base gate."""
Expand Down
14 changes: 7 additions & 7 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,24 @@ def label(self, name):
else:
raise TypeError('label expects a string or None')

def control(self, num_ctrl_qubits=1, label=None):
def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
"""Return controlled version of gate

Args:
num_ctrl_qubits (int): number of controls to add to gate (default=1)
label (str): optional gate label
label (str or None): optional gate label
ctrl_state (int or str or None): The control state in decimal or as
a bitstring (e.g. '111'). If None, use 2**num_ctrl_qubits-1.

Returns:
ControlledGate: controlled version of gate. This default algorithm
uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size
num_qubits + 2*num_ctrl_qubits - 1.
ControlledGate: controlled version of gate.

Raises:
QiskitError: unrecognized mode
QiskitError: unrecognized mode or invalid ctrl_state
"""
# pylint: disable=cyclic-import
from .add_control import add_control
return add_control(self, num_ctrl_qubits, label)
return add_control(self, num_ctrl_qubits, label, ctrl_state)

@staticmethod
def _broadcast_single_argument(qarg):
Expand Down
12 changes: 8 additions & 4 deletions qiskit/extensions/standard/h.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,23 @@ def _define(self):
definition.append(inst)
self.definition = definition

def control(self, num_ctrl_qubits=1, label=None):
def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
"""Controlled version of this gate.

Args:
num_ctrl_qubits (int): number of control qubits.
label (str or None): An optional label for the gate [Default: None]
ctrl_state (int or str or None): control state expressed as integer,
string (e.g. '110'), or None. If None, use all 1s.

Returns:
ControlledGate: controlled version of this gate.
"""
if num_ctrl_qubits == 1:
return CHGate()
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return CHGate()
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

def inverse(self):
"""Invert this gate."""
Expand Down
12 changes: 8 additions & 4 deletions qiskit/extensions/standard/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,23 @@ def _define(self):
definition.append(inst)
self.definition = definition

def control(self, num_ctrl_qubits=1, label=None):
def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
"""Controlled version of this gate.

Args:
num_ctrl_qubits (int): number of control qubits.
label (str or None): An optional label for the gate [Default: None]
ctrl_state (int or str or None): control state expressed as integer,
string (e.g. '110'), or None. If None, use all 1s.

Returns:
ControlledGate: controlled version of this gate.
"""
if num_ctrl_qubits == 1:
return CrxGate(self.params[0])
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return CrxGate(self.params[0])
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

def inverse(self):
"""Invert this gate.
Expand Down
12 changes: 8 additions & 4 deletions qiskit/extensions/standard/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,23 @@ def _define(self):
definition.append(inst)
self.definition = definition

def control(self, num_ctrl_qubits=1, label=None):
def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None):
"""Controlled version of this gate.

Args:
num_ctrl_qubits (int): number of control qubits.
label (str or None): An optional label for the gate [Default: None]
ctrl_state (int or str or None): control state expressed as integer,
string (e.g. '110'), or None. If None, use all 1s.

Returns:
ControlledGate: controlled version of this gate.
"""
if num_ctrl_qubits == 1:
return CryGate(self.params[0])
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return CryGate(self.params[0])
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

def inverse(self):
"""Invert this gate.
Expand Down
Loading