Skip to content

Commit

Permalink
Merge pull request #78 from zapatacomputing/zqs-1256-extend-str-to-wr…
Browse files Browse the repository at this point in the history
…apped-gates

feat: extend str to wrapped gates
  • Loading branch information
Athena Caesura authored Jan 12, 2023
2 parents 2b90f95 + 0b339aa commit abd8ac6
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 20 deletions.
7 changes: 7 additions & 0 deletions list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"list": [
0.1,
0.3,
-0.3
]
}
59 changes: 41 additions & 18 deletions src/orquestra/quantum/circuits/_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from ._operations import Parameter, get_free_symbols, sub_symbols
from ._unitary_tools import _lift_matrix_numpy, _lift_matrix_sympy

DAGGER_GATE_NAME = "Dagger"
CONTROLLED_GATE_NAME = "Control"
EXPONENTIAL_GATE_NAME = "Exponential"
POWER_GATE_SYMBOL = "^"


@runtime_checkable
class Gate(Protocol):
Expand Down Expand Up @@ -262,9 +267,6 @@ def free_symbols(self) -> Iterable[sympy.Symbol]:
__call__ = Gate.__call__


CONTROLLED_GATE_NAME = "Control"


@dataclass(frozen=True)
class ControlledGate(Gate):
wrapped_gate: Gate
Expand Down Expand Up @@ -310,10 +312,7 @@ def dagger(self) -> "ControlledGate":

@property
def exp(self) -> "Gate":
return ControlledGate(
wrapped_gate=self.wrapped_gate.exp,
num_control_qubits=self.num_control_qubits,
)
return Exponential(self)

def power(self, exponent: float) -> "Gate":
return ControlledGate(
Expand All @@ -329,8 +328,8 @@ def replace_params(self, new_params: Tuple[Parameter, ...]) -> "Gate":
self.num_control_qubits
)


DAGGER_GATE_NAME = "Dagger"
def __str__(self):
return self.num_control_qubits * "c-" + str(self.wrapped_gate)


@dataclass(frozen=True)
Expand Down Expand Up @@ -373,8 +372,11 @@ def exp(self) -> "Gate":
def power(self, exponent: float) -> "Gate":
return Power(self, exponent)


EXPONENTIAL_GATE_NAME = "Exponential"
def __str__(self):
wrapped_string = str(self.wrapped_gate)
before_and_after_params = wrapped_string.split("(")
before_and_after_params[0] += "†"
return "(".join(before_and_after_params)


@dataclass(frozen=True)
Expand All @@ -383,9 +385,7 @@ class Exponential(Gate):

def __post_init__(self):
if len(self.wrapped_gate.free_symbols) > 0:
raise ValueError(
"On gates with free symbols the exponential cannot be performed"
)
raise ValueError("Cannot be perform exponential on gates with free symbols")

@property
def matrix(self) -> sympy.Matrix:
Expand All @@ -404,7 +404,7 @@ def name(self):
return EXPONENTIAL_GATE_NAME

def controlled(self, num_control_qubits: int) -> Gate:
return self.wrapped_gate.controlled(num_control_qubits).exp
return ControlledGate(self, num_control_qubits)

def bind(self, symbols_map) -> "Gate":
raise NotImplementedError(
Expand All @@ -425,8 +425,10 @@ def exp(self) -> "Gate":
def power(self, exponent: float) -> "Gate":
return Power(self, exponent)


POWER_GATE_SYMBOL = "^"
def __str__(self):
if check_has_non_commuting_gate_type(self, [ControlledGate, Power]):
return "exp" + POWER_GATE_SYMBOL + "{" + str(self.wrapped_gate) + "}"
return "exp" + POWER_GATE_SYMBOL + str(self.wrapped_gate)


@dataclass(frozen=True)
Expand All @@ -436,7 +438,7 @@ class Power(Gate):

def __post_init__(self):
if len(self.wrapped_gate.free_symbols) > 0:
raise ValueError("Gates with free symbols cannot be exponentiated")
raise ValueError("Cannot return power of gates with free symbols")

@property
def name(self) -> str:
Expand Down Expand Up @@ -480,6 +482,14 @@ def bind(self, symbols_map: Dict[sympy.Symbol, Parameter]) -> "Gate":
def replace_params(self, new_params: Tuple[Parameter, ...]) -> "Gate":
return self.wrapped_gate.replace_params(new_params).power(self.exponent)

def __str__(self):
if check_has_non_commuting_gate_type(self, [Exponential]):
inner_string = "{" + str(self.wrapped_gate) + "}"
else:
inner_string = str(self.wrapped_gate)

return inner_string + POWER_GATE_SYMBOL + str(self.exponent)


def _n_qubits(matrix):
n_qubits = math.floor(math.log2(matrix.shape[0]))
Expand Down Expand Up @@ -596,3 +606,16 @@ def _are_matrices_equal(matrix, another_matrix):
_are_matrix_elements_equal(element, another_element)
for element, another_element in zip(matrix, another_matrix)
)


def check_has_non_commuting_gate_type(self, non_commuting_gate_types):
wrapped_gate = self.wrapped_gate
has_non_commuting_gate_type = False
while True:
if type(self.wrapped_gate) in non_commuting_gate_types:
has_non_commuting_gate_type = True
if hasattr(wrapped_gate, "wrapped_gate"):
wrapped_gate = wrapped_gate.wrapped_gate
else:
break
return has_non_commuting_gate_type
66 changes: 64 additions & 2 deletions tests/orquestra/quantum/circuits/_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,44 @@ def test_applying_gate_returns_operation_with_correct_gate_and_indices(self):
assert operation.gate == gate
assert operation.qubit_indices == (4, 1)

def test_str_on_dagger_gives_correct_representation(self):
gate = MatrixFactoryGate("V", example_one_qubit_matrix_factory, (1, 0), 1)
assert str(gate.dagger) == "V†(1, 0)"

def test_wrapping_two_times_gives_correct_string(self):
phi = sympy.Symbol("phi")
op = MatrixFactoryGate("U", example_one_qubit_matrix_factory, (phi, 1), 1)
T_op = _builtin_gates.T # need a gate without params for exponentiation

# assert all permutations produce correct string
assert str(op.controlled(2).dagger(0)) == "c-c-U†(phi, 1)(0)"
assert str(T_op.controlled(2).exp(0)) == "exp^{c-c-T}(0)"
assert str(T_op.controlled(2).power(3)(0)) == "c-c-T^3(0)"
assert str(op.controlled(2).controlled(2)(0)) == "c-c-c-c-U(phi, 1)(0)"

assert str(op.dagger.controlled(2)(0)) == "c-c-U†(phi, 1)(0)"
assert str(T_op.dagger.exp(0)) == "exp^T†(0)"
assert str(T_op.dagger.power(3)(0)) == "T†^3(0)"
assert str(op.dagger.dagger(0)) == "U(phi, 1)(0)"

assert str(T_op.exp.dagger(0)) == "exp^T†(0)"
assert str(T_op.exp.controlled(2)(0)) == "c-c-exp^T(0)"
assert str(T_op.exp.power(3)(0)) == "{exp^T}^3(0)"
assert str(T_op.exp.exp(0)) == "exp^exp^T(0)"

assert str(T_op.power(3).dagger(0)) == "T†^3(0)"
assert str(T_op.power(3).controlled(2)(0)) == "c-c-T^3(0)"
assert str(T_op.power(3).exp(0)) == "exp^{T^3}(0)"
assert str(T_op.power(3).power(3)(0)) == "T^3^3(0)"

def test_wrapping_three_times_gives_correct_string(self):
T_op = _builtin_gates.T # need a gate without params for exponentiation

# assert some permutations because there are too many to test exhaustively
assert str(T_op.controlled(2).dagger.exp(0)) == "exp^{c-c-T†}(0)"
assert str(T_op.dagger.controlled(2).power(3)(0)) == "c-c-T†^3(0)"
assert str(T_op.exp.controlled(2).exp(0)) == "exp^{c-c-exp^T}(0)"


@pytest.mark.parametrize("gate", GATES_REPRESENTATIVES)
class TestControlledGate:
Expand Down Expand Up @@ -212,10 +250,10 @@ def test_dagger_of_controlled_gate_is_controlled_gate_wrapping_dagger(self, gate

assert controlled_gate.dagger == gate.dagger.controlled(4)

def test_exp_of_controlled_gate_is_controlled_gate_wrapping_exp(self, gate):
def test_exp_of_controlled_gate_is_not_controlled_gate_wrapping_exp(self, gate):
if len(gate.free_symbols) == 0:
controlled_gate = gate.controlled(2)
assert controlled_gate.exp == gate.exp.controlled(2)
assert controlled_gate.exp != gate.exp.controlled(2)

def test_power_of_controlled_gate_is_controlled_gate_wrapping_power(self, gate):
if len(gate.free_symbols) == 0:
Expand All @@ -241,6 +279,20 @@ def test_constructing_controlled_gate_with_zero_control_raises_error(self, gate)
with pytest.raises(ValueError):
gate.controlled(0)

def test_str_gives_correct_string_for_one_control(self, gate):
controlled_gate = gate.controlled(1)
assert str(controlled_gate) == "c-" + str(gate)

def test_str_gives_correct_string_for_multiple_controls(self, gate):
controlled_gate_2 = gate.controlled(2)
controlled_gate_5 = gate.controlled(5)
assert str(controlled_gate_2) == "c-" * 2 + str(gate)
assert str(controlled_gate_5) == "c-" * 5 + str(gate)

def test_str_gives_correct_string_for_stacking_controls(self, gate):
double_controlled_gate = gate.controlled(1).controlled(1)
assert str(double_controlled_gate) == "c-" * 2 + str(gate)


@pytest.mark.parametrize("gate", GATES_REPRESENTATIVES)
@pytest.mark.parametrize("exponent", POWER_GATE_EXPONENTS)
Expand Down Expand Up @@ -305,6 +357,12 @@ def test_constructing_power_gate_and_replacing_parameters_commute(
new_params
).power(exponent)

def test_str_gives_correct_string(self, gate, exponent):
if len(gate.free_symbols) == 0:
power_gate = gate.power(exponent)
correct_str = str(gate) + f"{_gates.POWER_GATE_SYMBOL}{exponent}"
assert str(power_gate) == correct_str


@pytest.mark.parametrize("gate", GATES_REPRESENTATIVES[:10])
class TestGateExponential:
Expand Down Expand Up @@ -360,6 +418,10 @@ def test_constructing_gate_exponential_and_replacing_parameters_commute(self, ga
== gate.replace_params(new_params).exp
)

def test_str_gives_correct_string(self, gate):
if len(gate.free_symbols) == 0:
assert str(gate.exp) == "exp^" + str(gate)


@pytest.mark.parametrize("gate", GATES_REPRESENTATIVES)
class TestGateOperation:
Expand Down

0 comments on commit abd8ac6

Please sign in to comment.