Skip to content

bsr

BlochSphereRotation

Bases: GateSemantic, IRNode

Source code in opensquirrel/ir/semantics/bsr.py
class BlochSphereRotation(GateSemantic, IRNode):
    normalize_angle_params: bool = True

    def __init__(
        self,
        axis: AxisLike,
        angle: SupportsFloat,
        phase: SupportsFloat,
    ) -> None:
        self.axis = Axis(axis)
        self.angle = normalize_angle(angle) if self.normalize_angle_params else float(angle)
        self.phase = normalize_angle(phase) if self.normalize_angle_params else float(phase)

    def accept(self, visitor: IRVisitor) -> Any:
        return visitor.visit_bloch_sphere_rotation(self)

    def is_identity(self) -> bool:
        # Angle and phase are already normalized.
        return abs(self.angle) < ATOL and abs(self.phase) < ATOL

    def __repr__(self) -> str:
        return (
            f"BlochSphereRotation(axis={repr_round(self.axis)}, angle={repr_round(self.angle)}, "
            f"phase={repr_round(self.phase)})"
        )

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, BlochSphereRotation):
            return False

        if np.allclose(self.axis.value, other.axis.value, atol=ATOL):
            return abs(self.angle - other.angle) < ATOL and abs(self.phase - other.phase) < ATOL

        if np.allclose(self.axis.value, -other.axis.value, atol=ATOL):
            return abs(self.angle + other.angle) < ATOL and abs(self.phase + other.phase) < ATOL

        return False

    def __mul__(self, other: BlochSphereRotation) -> BlochSphereRotation:
        """Computes the single qubit gate resulting from the composition of two single
        qubit gates, by composing the Bloch sphere rotations of the two gates.
        The first rotation (A) is applied and then the second (B):

        As separate gates:
            A q
            B q

        As linear operations:
            (B * A) q

        If the final single qubit gate is anonymous, we try to match it to a default gate.

        Uses Rodrigues' rotation formula (see https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula).
        """
        acos_argument = cos(self.angle / 2) * cos(other.angle / 2) - sin(self.angle / 2) * sin(
            other.angle / 2
        ) * np.dot(self.axis, other.axis)
        combined_angle = 2 * acos(acos_argument)

        if abs(sin(combined_angle / 2)) < ATOL:
            return bsr_from_matrix([[1, 0], [0, 1]])

        order_of_magnitude = abs(floor(log10(ATOL)))
        combined_axis = np.round(
            (
                1
                / sin(combined_angle / 2)
                * (
                    sin(self.angle / 2) * cos(other.angle / 2) * self.axis.value
                    + cos(self.angle / 2) * sin(other.angle / 2) * other.axis.value
                    + sin(self.angle / 2) * sin(other.angle / 2) * np.cross(other.axis, self.axis)
                )
            ),
            order_of_magnitude,
        )

        combined_phase = np.round(self.phase + other.phase, order_of_magnitude)
        return BlochSphereRotation(
            axis=combined_axis,
            angle=combined_angle,
            phase=combined_phase,
        )

__mul__(other)

Computes the single qubit gate resulting from the composition of two single qubit gates, by composing the Bloch sphere rotations of the two gates. The first rotation (A) is applied and then the second (B):

As separate gates

A q B q

As linear operations

(B * A) q

If the final single qubit gate is anonymous, we try to match it to a default gate.

Uses Rodrigues' rotation formula (see https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula).

Source code in opensquirrel/ir/semantics/bsr.py
def __mul__(self, other: BlochSphereRotation) -> BlochSphereRotation:
    """Computes the single qubit gate resulting from the composition of two single
    qubit gates, by composing the Bloch sphere rotations of the two gates.
    The first rotation (A) is applied and then the second (B):

    As separate gates:
        A q
        B q

    As linear operations:
        (B * A) q

    If the final single qubit gate is anonymous, we try to match it to a default gate.

    Uses Rodrigues' rotation formula (see https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula).
    """
    acos_argument = cos(self.angle / 2) * cos(other.angle / 2) - sin(self.angle / 2) * sin(
        other.angle / 2
    ) * np.dot(self.axis, other.axis)
    combined_angle = 2 * acos(acos_argument)

    if abs(sin(combined_angle / 2)) < ATOL:
        return bsr_from_matrix([[1, 0], [0, 1]])

    order_of_magnitude = abs(floor(log10(ATOL)))
    combined_axis = np.round(
        (
            1
            / sin(combined_angle / 2)
            * (
                sin(self.angle / 2) * cos(other.angle / 2) * self.axis.value
                + cos(self.angle / 2) * sin(other.angle / 2) * other.axis.value
                + sin(self.angle / 2) * sin(other.angle / 2) * np.cross(other.axis, self.axis)
            )
        ),
        order_of_magnitude,
    )

    combined_phase = np.round(self.phase + other.phase, order_of_magnitude)
    return BlochSphereRotation(
        axis=combined_axis,
        angle=combined_angle,
        phase=combined_phase,
    )