Source code for mrmustard.physics.symplectics

# Copyright 2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.

"""This module contains the symplectic matrices for the Fock-Bargmann representation of
various states and transformations.
"""

from __future__ import annotations

from collections.abc import Sequence

from mrmustard import math
from mrmustard.utils.typing import Matrix

__all__ = [
    "cxgate_symplectic",
    "czgate_symplectic",
    "interferometer_symplectic",
    "mzgate_symplectic",
    "pgate_symplectic",
    "realinterferometer_symplectic",
]


[docs] def cxgate_symplectic(s: float | Sequence[float]) -> Matrix: r"""The symplectic matrix of a controlled X gate. Args: s: The control parameter. Returns: The symplectic matrix of a CX gate. """ s = math.astensor(s, dtype=math.complex128) batch_shape = s.shape batch_dim = len(batch_shape) O_matrix = math.zeros(batch_shape, math.complex128) I_matrix = math.ones(batch_shape, math.complex128) return math.stack( [ math.stack([I_matrix, O_matrix, O_matrix, O_matrix], batch_dim), math.stack([s, I_matrix, O_matrix, O_matrix], batch_dim), math.stack([O_matrix, O_matrix, I_matrix, -s], batch_dim), math.stack([O_matrix, O_matrix, O_matrix, I_matrix], batch_dim), ], batch_dim, )
[docs] def czgate_symplectic(s: float | Sequence[float]) -> Matrix: r"""The symplectic matrix of a controlled Z gate. Args: s: The control parameter. Returns: The symplectic matrix of a CZ gate. """ s = math.astensor(s, dtype=math.complex128) batch_shape = s.shape batch_dim = len(batch_shape) O_matrix = math.zeros(batch_shape, math.complex128) I_matrix = math.ones(batch_shape, math.complex128) return math.stack( [ math.stack([I_matrix, O_matrix, O_matrix, O_matrix], batch_dim), math.stack([O_matrix, I_matrix, O_matrix, O_matrix], batch_dim), math.stack([O_matrix, s, I_matrix, O_matrix], batch_dim), math.stack([s, O_matrix, O_matrix, I_matrix], batch_dim), ], batch_dim, )
[docs] def interferometer_symplectic(unitary: Matrix) -> Matrix: r"""The symplectic matrix of an N-mode interferometer parametrized by an NxN unitary matrix. Args: unitary : A unitary matrix. For N modes it must have shape `(N,N)`. Returns: The symplectic matrix of an N-mode interferometer. """ return math.block( [[math.real(unitary), -math.imag(unitary)], [math.imag(unitary), math.real(unitary)]], )
[docs] def mzgate_symplectic( phi_a: float | Sequence[float], phi_b: float | Sequence[float], internal: bool, ) -> Matrix: r"""The symplectic matrix of a Mach-Zehnder gate. It supports two conventions: 1. if ``internal=True``, both phases act inside the interferometer: ``phi_a`` on the upper arm, ``phi_b`` on the lower arm; 2. if ``internal = False``, both phases act on the upper arm: ``phi_a`` before the first BS, ``phi_b`` after the first BS. Args: phi_a: The phase in the upper arm of the MZ interferometer phi_b: The phase in the lower arm or external of the MZ interferometer internal: Whether phases are both in the internal arms. Returns: The symplectic matrix of a Mach-Zehnder gate. """ phi_a, phi_b = math.broadcast_arrays( math.astensor(phi_a, dtype=math.complex128), math.astensor(phi_b, dtype=math.complex128), ) batch_shape = phi_a.shape batch_dim = len(batch_shape) ca = math.cos(phi_a) sa = math.sin(phi_a) cb = math.cos(phi_b) sb = math.sin(phi_b) cp = math.cos(phi_a + phi_b) sp = math.sin(phi_a + phi_b) if internal: symplectic = math.stack( [ math.stack([ca - cb, -sa - sb, sb - sa, -ca - cb], batch_dim), math.stack([-sa - sb, cb - ca, -ca - cb, sa - sb], batch_dim), math.stack([sa - sb, ca + cb, ca - cb, -sa - sb], batch_dim), math.stack([ca + cb, sb - sa, -sa - sb, cb - ca], batch_dim), ], batch_dim, ) else: symplectic = math.stack( [ math.stack([cp - ca, -sb, sa - sp, -1 - cb], batch_dim), math.stack([-sa - sp, 1 - cb, -ca - cp, sb], batch_dim), math.stack([sp - sa, 1 + cb, cp - ca, -sb], batch_dim), math.stack([cp + ca, -sb, -sa - sp, 1 - cb], batch_dim), ], batch_dim, ) return 0.5 * symplectic
[docs] def pgate_symplectic(n_modes: int, shearing: float | Sequence[float]) -> Matrix: r"""The symplectic matrix of a quadratic phase gate. Args: n_modes: The number of modes. shearing: The shearing parameter. Returns: The symplectic matrix of a phase gate. """ shearing = math.astensor(shearing, dtype=math.complex128) batch_shape = shearing.shape I_matrix = math.broadcast_to( math.eye(n_modes, dtype=math.complex128), (*batch_shape, n_modes, n_modes), ) O_matrix = math.zeros((*batch_shape, n_modes, n_modes), dtype=math.complex128) return math.block([[I_matrix, O_matrix], [I_matrix * shearing[..., None, None], I_matrix]])
[docs] def realinterferometer_symplectic(orthogonal: Matrix) -> Matrix: r"""The symplectic matrix of an N-mode interferometer parametrized by an NxN orthogonal matrix. Args: orthogonal : A real orthogonal matrix. For N modes it must have shape `(N,N)`. Returns: The symplectic matrix of an N-mode interferometer. """ return math.block( [[orthogonal, -math.zeros_like(orthogonal)], [math.zeros_like(orthogonal), orthogonal]], )