# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from __future__ import annotations
import copy
import math
from collections.abc import Sequence
from functools import singledispatch
from typing import Optional, Union
from sympy import Float
from braket.circuits.free_parameter_expression import FreeParameterExpression
from braket.circuits.gate import Gate
from braket.circuits.parameterizable import Parameterizable
[docs]
class AngledGate(Gate, Parameterizable):
"""Class `AngledGate` represents a quantum gate that operates on N qubits and an angle."""
def __init__(
self,
angle: Union[FreeParameterExpression, float],
qubit_count: Optional[int],
ascii_symbols: Sequence[str],
):
"""Initializes an `AngledGate`.
Args:
angle (Union[FreeParameterExpression, float]): The angle of the gate in radians
or expression representation.
qubit_count (Optional[int]): The number of qubits that this gate interacts with.
ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when
printing a diagram of a circuit. The length must be the same as `qubit_count`, and
index ordering is expected to correlate with the target ordering on the instruction.
For instance, if a CNOT instruction has the control qubit on the first index and
target qubit on the second index, the ASCII symbols should have `["C", "X"]` to
correlate a symbol with that index.
Raises:
ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or
`ascii_symbols` length != `qubit_count`, or `angle` is `None`
"""
super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols)
if angle is None:
raise ValueError("angle must not be None")
if isinstance(angle, FreeParameterExpression):
self._parameters = [angle]
else:
self._parameters = [float(angle)] # explicit casting in case angle is e.g. np.float32
@property
def parameters(self) -> list[Union[FreeParameterExpression, float]]:
"""Returns the parameters associated with the object, either unbound free parameters or
bound values.
Returns:
list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
associated with the object.
"""
return self._parameters
@property
def angle(self) -> Union[FreeParameterExpression, float]:
"""Returns the angle of the gate
Returns:
Union[FreeParameterExpression, float]: The angle of the gate in radians
"""
return self._parameters[0]
[docs]
def bind_values(self, **kwargs) -> AngledGate:
"""Takes in parameters and attempts to assign them to values.
Returns:
AngledGate: A new Gate of the same type with the requested parameters bound.
Raises:
NotImplementedError: Subclasses should implement this function.
"""
raise NotImplementedError
[docs]
def adjoint(self) -> list[Gate]:
"""Returns the adjoint of this gate as a singleton list.
Returns:
list[Gate]: A list containing the gate with negated angle.
"""
gate_ascii_name_index = self.ascii_symbols[0].find("(")
gate_ascii_name = self.ascii_symbols[0][:gate_ascii_name_index]
new_ascii_symbols = [
angled_ascii_characters(gate_ascii_name, -self.angle)
] * self.qubit_count
new = copy.copy(self)
new._parameters = [-angle for angle in self._parameters]
new._ascii_symbols = new_ascii_symbols
return [new]
def __eq__(self, other: AngledGate):
return (
isinstance(other, AngledGate)
and self.name == other.name
and _angles_equal(self.angle, other.angle)
)
def __repr__(self):
return f"{self.name}('angle': {self.angle}, 'qubit_count': {self.qubit_count})"
def __hash__(self):
return hash((self.name, self.angle, self.qubit_count))
[docs]
class DoubleAngledGate(Gate, Parameterizable):
"""Class `DoubleAngledGate` represents a quantum gate that operates on N qubits and
two angles.
"""
def __init__(
self,
angle_1: Union[FreeParameterExpression, float],
angle_2: Union[FreeParameterExpression, float],
qubit_count: Optional[int],
ascii_symbols: Sequence[str],
):
"""Inits a `DoubleAngledGate`.
Args:
angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in
radians or expression representation.
angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in
radians or expression representation.
qubit_count (Optional[int]): The number of qubits that this gate interacts with.
ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when
printing a diagram of a circuit. The length must be the same as `qubit_count`, and
index ordering is expected to correlate with the target ordering on the instruction.
For instance, if a CNOT instruction has the control qubit on the first index and
target qubit on the second index, the ASCII symbols should have `["C", "X"]` to
correlate a symbol with that index.
Raises:
ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or
`ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` is `None`
"""
super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols)
if angle_1 is None or angle_2 is None:
raise ValueError("angles must not be None")
self._parameters = [
(
angle
if isinstance(angle, FreeParameterExpression)
else float(angle) # explicit casting in case angle is e.g. np.float32
)
for angle in (angle_1, angle_2)
]
@property
def parameters(self) -> list[Union[FreeParameterExpression, float]]:
"""Returns the parameters associated with the object, either unbound free parameters or
bound values.
Returns:
list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
associated with the object.
"""
return self._parameters
@property
def angle_1(self) -> Union[FreeParameterExpression, float]:
"""Returns the first angle of the gate
Returns:
Union[FreeParameterExpression, float]: The first angle of the gate in radians
"""
return self._parameters[0]
@property
def angle_2(self) -> Union[FreeParameterExpression, float]:
"""Returns the second angle of the gate
Returns:
Union[FreeParameterExpression, float]: The second angle of the gate in radians
"""
return self._parameters[1]
[docs]
def bind_values(self, **kwargs: FreeParameterExpression | str) -> AngledGate:
"""Takes in parameters and attempts to assign them to values.
Args:
**kwargs (FreeParameterExpression | str): The parameters that are being assigned.
Returns:
AngledGate: A new Gate of the same type with the requested parameters bound.
Raises:
NotImplementedError: Subclasses should implement this function.
"""
raise NotImplementedError
[docs]
def adjoint(self) -> list[Gate]:
"""Returns the adjoint of this gate as a singleton list.
Returns:
list[Gate]: A list containing the gate with negated angle.
"""
raise NotImplementedError
def __eq__(self, other: DoubleAngledGate):
return (
isinstance(other, DoubleAngledGate)
and self.name == other.name
and _angles_equal(self.angle_1, other.angle_1)
and _angles_equal(self.angle_2, other.angle_2)
)
def __repr__(self):
return (
f"{self.name}('angles': ({self.angle_1}, {self.angle_2}), "
f"'qubit_count': {self.qubit_count})"
)
def __hash__(self):
return hash((self.name, self.angle_1, self.angle_2, self.qubit_count))
[docs]
class TripleAngledGate(Gate, Parameterizable):
"""Class `TripleAngledGate` represents a quantum gate that operates on N qubits and
three angles.
"""
def __init__(
self,
angle_1: Union[FreeParameterExpression, float],
angle_2: Union[FreeParameterExpression, float],
angle_3: Union[FreeParameterExpression, float],
qubit_count: Optional[int],
ascii_symbols: Sequence[str],
):
"""Inits a `TripleAngledGate`.
Args:
angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in
radians or expression representation.
angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in
radians or expression representation.
angle_3 (Union[FreeParameterExpression, float]): The third angle of the gate in
radians or expression representation.
qubit_count (Optional[int]): The number of qubits that this gate interacts with.
ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when
printing a diagram of a circuit. The length must be the same as `qubit_count`, and
index ordering is expected to correlate with the target ordering on the instruction.
For instance, if a CNOT instruction has the control qubit on the first index and
target qubit on the second index, the ASCII symbols should have `["C", "X"]` to
correlate a symbol with that index.
Raises:
ValueError: If `qubit_count` is less than 1, `ascii_symbols` are `None`, or
`ascii_symbols` length != `qubit_count`, or `angle_1` or `angle_2` or `angle_3`
is `None`
"""
super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols)
if angle_1 is None or angle_2 is None or angle_3 is None:
raise ValueError("angles must not be None")
self._parameters = [
(
angle
if isinstance(angle, FreeParameterExpression)
else float(angle) # explicit casting in case angle is e.g. np.float32
)
for angle in (angle_1, angle_2, angle_3)
]
@property
def parameters(self) -> list[Union[FreeParameterExpression, float]]:
"""Returns the parameters associated with the object, either unbound free parameters or
bound values.
Returns:
list[Union[FreeParameterExpression, float]]: The free parameters or fixed value
associated with the object.
"""
return self._parameters
@property
def angle_1(self) -> Union[FreeParameterExpression, float]:
"""Returns the first angle of the gate
Returns:
Union[FreeParameterExpression, float]: The first angle of the gate in radians
"""
return self._parameters[0]
@property
def angle_2(self) -> Union[FreeParameterExpression, float]:
"""Returns the second angle of the gate
Returns:
Union[FreeParameterExpression, float]: The second angle of the gate in radians
"""
return self._parameters[1]
@property
def angle_3(self) -> Union[FreeParameterExpression, float]:
"""Returns the third angle of the gate
Returns:
Union[FreeParameterExpression, float]: The third angle of the gate in radians
"""
return self._parameters[2]
[docs]
def bind_values(self, **kwargs: FreeParameterExpression | str) -> AngledGate:
"""Takes in parameters and attempts to assign them to values.
Args:
**kwargs (FreeParameterExpression | str): The parameters that are being assigned.
Returns:
AngledGate: A new Gate of the same type with the requested parameters bound.
Raises:
NotImplementedError: Subclasses should implement this function.
"""
raise NotImplementedError
[docs]
def adjoint(self) -> list[Gate]:
"""Returns the adjoint of this gate as a singleton list.
Returns:
list[Gate]: A list containing the gate with negated angle.
"""
raise NotImplementedError
def __eq__(self, other: TripleAngledGate):
return (
isinstance(other, TripleAngledGate)
and self.name == other.name
and _angles_equal(self.angle_1, other.angle_1)
and _angles_equal(self.angle_2, other.angle_2)
and _angles_equal(self.angle_3, other.angle_3)
)
def __repr__(self):
return (
f"{self.name}('angles': ({self.angle_1}, {self.angle_2}, {self.angle_3}), "
f"'qubit_count': {self.qubit_count})"
)
def __hash__(self):
return hash((self.name, self.angle_1, self.angle_2, self.angle_3, self.qubit_count))
@singledispatch
def _angles_equal(
angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float]
) -> bool:
return isinstance(angle_2, float) and math.isclose(angle_1, angle_2)
@_angles_equal.register
def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression):
return angle_1 == angle_2
[docs]
def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str:
"""Generates a formatted ascii representation of an angled gate.
Args:
gate (str): The name of the gate.
angle (Union[FreeParameterExpression, float]): The angle for the gate.
Returns:
str: Returns the ascii representation for an angled gate.
"""
return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})'
def _multi_angled_ascii_characters(
gate: str,
*angles: Union[FreeParameterExpression, float],
) -> str:
"""Generates a formatted ascii representation of an angled gate.
Args:
gate (str): The name of the gate.
*angles (Union[FreeParameterExpression, float]): angles in radians.
Returns:
str: Returns the ascii representation for an angled gate.
"""
def format_string(angle: FreeParameterExpression | float) -> str:
"""Formats an angle for ASCII representation.
Args:
angle (FreeParameterExpression | float): The angle to format.
Returns:
str: The ASCII representation of the angle.
"""
return ".2f" if isinstance(angle, (float, Float)) else ""
return f"{gate}({', '.join(f'{angle:{format_string(angle)}}' for angle in angles)})"
[docs]
def get_angle(gate: AngledGate, **kwargs: FreeParameterExpression | str) -> AngledGate:
"""Gets the angle with all values substituted in that are requested.
Args:
gate (AngledGate): The subclass of AngledGate for which the angle is being obtained.
**kwargs (FreeParameterExpression | str): The named parameters that are being filled
for a particular gate.
Returns:
AngledGate: A new gate of the type of the AngledGate originally used with all
angles updated.
"""
new_angle = (
gate.angle.subs(kwargs) if isinstance(gate.angle, FreeParameterExpression) else gate.angle
)
return type(gate)(angle=new_angle)
def _get_angles(
gate: DoubleAngledGate | TripleAngledGate, **kwargs: FreeParameterExpression | str
) -> DoubleAngledGate | TripleAngledGate:
"""Gets the angle with all values substituted in that are requested.
Args:
gate (DoubleAngledGate | TripleAngledGate): The subclass of multi angle AngledGate for
which the angle is being obtained.
**kwargs (FreeParameterExpression | str): The named parameters that are being filled
for a particular gate.
Returns:
DoubleAngledGate | TripleAngledGate: A new gate of the type of the AngledGate
originally used with all angles updated.
"""
angles = [f"angle_{i + 1}" for i in range(len(gate._parameters))]
new_angles_args = {
angle: (
getattr(gate, angle).subs(kwargs)
if isinstance(getattr(gate, angle), FreeParameterExpression)
else getattr(gate, angle)
)
for angle in angles
}
return type(gate)(**new_angles_args)