Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
86 changes: 61 additions & 25 deletions qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,31 @@
"""
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, num_ctrl_qubits, label):
"""Add num_ctrl_qubits controls to operation
def add_control(operation: Union[Gate, ControlledGate],
num_ctrl_qubits: int,
label: Union[str, None],
ctrl_state: Union[int, str, None]) -> ControlledGate:
"""Adds num_ctrl_qubits controls to operation.
Comment thread
ewinston marked this conversation as resolved.
Outdated

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 +51,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,9 +87,19 @@ 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 ctrl_state is not None:
if isinstance(ctrl_state, str):
try:
assert len(ctrl_state) == 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')
_toggle_ctrl_state(qc, num_ctrl_qubits, ctrl_state)
if operation.name == 'x' or (
isinstance(operation, controlledgate.ControlledGate) and
operation.base_gate.name == 'x'):
Expand Down Expand Up @@ -122,7 +146,9 @@ 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')
if ctrl_state is not None:
_toggle_ctrl_state(qc, num_ctrl_qubits, ctrl_state)
instr = qc.to_instruction()
if isinstance(operation, controlledgate.ControlledGate):
new_num_ctrl_qubits = num_ctrl_qubits + operation.num_ctrl_qubits
Expand Down Expand Up @@ -151,15 +177,25 @@ def control(operation, num_ctrl_qubits=1, label=None):
return cgate


def _toggle_ctrl_state(qc: QuantumCircuit,
num_qubits: int,
ctrl_state: int) -> Gate:
if isinstance(ctrl_state, int) and 0 <= ctrl_state < 2**num_qubits:
Comment thread
ewinston marked this conversation as resolved.
Outdated
bit_ctrl_state = bin(ctrl_state)[2:].zfill(num_qubits)
else:
raise CircuitError('invalid control state specified')
for ind, val in enumerate(bit_ctrl_state):
if val == '0':
qc.x(num_qubits - ind - 1)


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
15 changes: 8 additions & 7 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,25 @@ 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 @@ -45,19 +45,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 @@ -45,19 +45,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 @@ -45,19 +45,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
12 changes: 8 additions & 4 deletions qiskit/extensions/standard/rz.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,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 CrzGate(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 CrzGate(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/swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,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 FredkinGate()
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return FredkinGate()
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/u1.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,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 Cu1Gate(*self.params)
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return Cu1Gate(*self.params)
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/u3.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,23 @@ def inverse(self):
"""
return U3Gate(-self.params[0], -self.params[2], -self.params[1])

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 Cu3Gate(*self.params)
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label)
if ctrl_state is None:
if num_ctrl_qubits == 1:
return Cu3Gate(*self.params)
return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label,
ctrl_state=ctrl_state)

def to_matrix(self):
"""Return a Numpy.array for the U3 gate."""
Expand Down
Loading