Source code for seemps.tools
from __future__ import annotations
from math import cos, sin, sqrt
import numpy as np
import scipy.sparse as sp
from typing import Any
from .typing import DenseOperator, Operator
class InvalidOperation(TypeError):
"""Exception for operations with invalid or non-matching arguments."""
def __init__(self, op: str, *args: Any):
super().__init__(
f"Invalid operation {op} between arguments of types {(type(x) for x in args)}"
)
class Logger:
active: bool = False
def __call__(self, *args: Any, **kwdargs: Any):
pass
def __enter__(self) -> Logger:
return self
def __exit__(self, exc_type, exc_value, traceback): # pyright: ignore[reportMissingParameterType]
pass
def __bool__(self) -> bool:
return False
def close(self):
pass
DEBUG = 0
PREFIX = ""
NO_LOGGER = Logger()
class VerboseLogger(Logger):
old_prefix: str
level: int
active: bool
def __init__(self, level: int):
global PREFIX
self.old_prefix = PREFIX
self.level = level
if level <= DEBUG:
self.active = True
PREFIX = PREFIX + " "
else:
self.active = False
def __bool__(self) -> bool:
return self.active
def __enter__(self) -> Logger:
super().__enter__()
return self
def __call__(self, *args: Any, **kwdargs: Any):
if self.active:
txt = " ".join([str(a) for a in args])
txt = " ".join([PREFIX + a for a in txt.split("\n")])
print(txt, **kwdargs)
def __exit__(self, exc_type, exc_value, traceback): # pyright: ignore[reportMissingParameterType]
self.close()
super().__exit__(exc_type, exc_value, traceback)
def close(self):
global PREFIX
PREFIX = self.old_prefix
def make_logger(level: int = 1) -> Logger:
"""Create an object that logs debug information. This object has a property
`active` that determines whether logging is working. It also has a `__call__`
method that allows invoking the object with the information to log, working
as if it were a `print` statement."""
if level > DEBUG:
return NO_LOGGER
return VerboseLogger(level)
# TODO: Find a faster way to do logs. Currently `log` always gets called
# We should find a way to replace calls to log in the code with an if-statement
# that checks `DEBUG`
def log(*args: Any, debug_level: int = 1) -> None:
"""Optionally log informative messages to the console.
Logging is only active when :var:`~seemps.tools.DEBUG` is True or an
integer above or equal to the given `debug_level`.
Parameters
----------
*args : str
Strings to be output
debug_level : int, default = 1
Level of messages to log
"""
if DEBUG and (DEBUG is True or DEBUG >= debug_level):
print(*args)
def random_isometry(N: int, M: int | None = None) -> DenseOperator:
"""Returns a random isometry with size `(M, N)`.
Parameters
----------
N, M : int
Size of the isometry, with `N` defaulting to `M`.
Returns
-------
Operator
A dense matrix for the isometry.
"""
if M is None:
M = N
U = np.random.rand(N, M)
U, _, V = np.linalg.svd(U, full_matrices=False)
if M <= N:
return U
else:
return V
σx = np.array([[0.0, 1.0], [1.0, 0.0]])
σz = np.array([[1.0, 0.0], [0.0, -1.0]])
σy = -1j * σz @ σx
def random_Pauli():
"""Random rotation generated by Pauli matrices."""
r = np.random.rand(2)
θ = (2 * r[0] - 1) * np.pi
ϕ = r[1] * np.pi
return cos(ϕ) * (cos(θ) * σx + sin(θ) * σy) + sin(ϕ) * σz
[docs]
def creation(d: int) -> DenseOperator:
"""Bosonic creation operator for a Hilbert space with occupations 0 to `d-1`."""
return np.diag(sqrt(np.arange(1, d)), -1).astype(complex)
[docs]
def annihilation(d: int) -> DenseOperator:
"""Bosonic annihilation operator for a Hilbert space with occupations 0 to `d-1`."""
return np.diag(sqrt(np.arange(1, d)), 1).astype(complex)
def mkron(A: Operator, *other_operators: Operator) -> Operator:
for B in other_operators:
A = sp.kron(A, B)
return A