two_qubit_model/hiro_models/two_qubit_model.py

541 lines
16 KiB
Python
Raw Normal View History

2022-02-21 18:24:49 +01:00
r"""
Operators for a general model of two interacting qubits coupled to
two baths in dimensionless units normalized to the frequency of the
left qubit :math:`ω_1`.
The :math:`z` axis of the two qubits is defined by their local
hamiltonians :math:`H_s^i = \frac{ω_i}{2}σ^i_z`.
The total hamiltonian has the form
.. math::
H=\frac{1}{2}σ^1_z + \frac{ω_2}{2}σ^2_z + H_B^1 + H_B^2
+ \frac{γ}{2} _{i,j=1}^{3} J_{ij} σ^1_i σ^2_j
+ \sum_{i=1}^2 δ_i g_λ^i \vec{s}_i\cdot \vec{σ}^i (b^i_λ + b^{i,}).
The matrix :math:`J` is real and normalized so that the operator norm
of :math:`_{i,j=1}^{3} J_{ij} σ^1_i σ^2_j` is equal to one. The
:math:`\vec{s}_i` are unit vectors with zero :math:`y` componets.
The sepectral densities
.. math::
J_i(ω) = π {|g^i_λ|}^2 δ(ω-ω^i_c)
are nomalized so that their integral is equal to pi.
"""
2022-02-23 18:10:33 +01:00
import copy
2022-02-21 18:24:49 +01:00
import dataclasses
import hopsflow
2022-02-21 18:24:49 +01:00
from dataclasses import dataclass, field
2022-02-22 10:26:35 +01:00
import functools
import itertools
2022-02-21 18:24:49 +01:00
from numpy.typing import NDArray
2022-03-18 11:25:31 +01:00
from typing import Any, Optional, SupportsFloat
2022-02-21 18:24:49 +01:00
import hops.util.bcf
import hops.util.bcf_fits
import hops.core.hierarchy_parameters as params
import numpy as np
import qutip as qt
2022-02-23 18:10:33 +01:00
from hops.util.abstract_truncation_scheme import TruncationScheme_Simplex
from hops.util.truncation_schemes import (
TruncationScheme_Power_multi,
BathMemory,
)
2022-02-21 18:24:49 +01:00
import stocproc as sp
from beartype import beartype
2022-03-23 14:59:02 +01:00
from .utility import StocProcTolerances, operator_norm
2022-03-22 10:25:58 +01:00
from .model_base import Model
2022-03-21 14:55:52 +01:00
import hops.core.hierarchy_parameters as params
2022-02-21 18:24:49 +01:00
@beartype
2022-03-18 11:25:31 +01:00
@dataclass(eq=False)
class TwoQubitModel(Model):
2022-02-21 18:24:49 +01:00
"""
A class to dynamically calculate all the model parameters and
generate the HOPS configuration.
All attributes can be changed after initialization.
"""
2022-03-21 13:45:05 +01:00
__version__: int = 2
2022-02-21 18:24:49 +01:00
ω_2: SupportsFloat = 1.0
"""The second oscilator energy gap."""
γ: SupportsFloat = 1.0
"""The oveall inter-qubit coupling strength."""
δ: list[SupportsFloat] = field(default_factory=lambda: [1.0, 1.0])
"""The bath coupling factors (length 2)."""
ω_c: list[SupportsFloat] = field(default_factory=lambda: [1.0, 1.0])
"""The BCF central frequencies :math:`ω_c` (length 2)."""
s: list[SupportsFloat] = field(default_factory=lambda: [1.0, 1.0])
"""The BCF s parameters frequencies (length 2)."""
j: NDArray[np.float64] = field(
default_factory=lambda: np.array(
[[1, 0, 0], [0, 0, 0], [0, 0, 0]], dtype=np.float64
)
)
"""
The :math:`J_{ij}` coupling coefficients with shape ``(3,3)``.
They will be normalized automatically.
"""
2022-02-22 10:59:43 +01:00
s_vec: list[list[SupportsFloat]] = field(default_factory=lambda: [[1, 0], [1, 0]])
2022-02-21 18:24:49 +01:00
"""
The :math:`\vec{s}_i` unit vectors with zero y-component of shape
``(2,2)``. Two vectors of form (``[[x,z], [x, z]]``). They will
be normalized automatically.
"""
T: list[SupportsFloat] = field(default_factory=lambda: list([0, 0]))
"""The temperatures of the baths."""
###########################################################################
# HOPS Parameters #
###########################################################################
description: str = ""
"""A free-form description of the model instance."""
bcf_terms: list[int] = field(default_factory=lambda: [5, 5])
"""How many bcf terms to use in the expansions of the BCFs."""
ψ_0: qt.Qobj = qt.basis([2, 2], [1, 1])
"""The initial state."""
t_max: SupportsFloat = 10
"""The maximum simulation time."""
resolution: SupportsFloat = 0.1
"""The time resolution of the simulation."""
k_fac: list[SupportsFloat] = field(default_factory=lambda: [1.4, 1.4])
"""The k_fac parameters for the truncation scheme.
See
:any:`hops.util.truncation_schemes.TruncationScheme_Power_multi`.
"""
2022-02-23 18:10:33 +01:00
k_max: int = 10
"""The kmax parameter for the truncation scheme.
See
:any:`hops.util.abstract_truncation_scheme.TruncationScheme_Simplex`
"""
influence_tolerance: SupportsFloat = 1e-2
"""The ``influecne_tolerance`` parameter for the truncation
scheme.
See :any:`hops.util.truncation_schemes.BathMemory`.
"""
truncation_scheme: str = "power"
"""The truncation scheme to use."""
2022-02-21 18:24:49 +01:00
solver_args: dict[str, Any] = field(default_factory=dict)
"""Extra arguments for :any:`scipy.integrate.solve_ivp`."""
driving_process_tolerances: list[StocProcTolerances] = field(
default_factory=lambda: [StocProcTolerances(), StocProcTolerances()]
)
"""
The integration and interpolation tolerances for the driving
processes.
"""
thermal_process_tolerances: list[StocProcTolerances] = field(
default_factory=lambda: [StocProcTolerances(), StocProcTolerances()]
)
"""
The integration and interpolation tolerances for the thermal noise
processes.
"""
def __post_init__(self):
self._sigmas = [qt.sigmax(), qt.sigmay(), qt.sigmaz()]
2022-02-22 10:26:35 +01:00
def local_system(self, i: int) -> qt.Qobj:
"""The local system hamiltonian of the ``i``th qubit."""
2022-02-21 18:24:49 +01:00
2022-02-22 10:59:43 +01:00
if i == 0:
2022-02-21 18:24:49 +01:00
return 1 / 2 * (qt.tensor(qt.sigmaz(), qt.identity(2))) # type: ignore
return self.ω_2 / 2 * (qt.tensor(qt.identity(2), qt.sigmaz())) # type: ignore
2022-02-22 10:26:35 +01:00
@property
def system(self) -> qt.Qobj:
"""The system hamiltonian."""
return self.local_system(0) + self.local_system(1) + self.interaction
2022-02-21 18:24:49 +01:00
@property
def bare_interaction(self) -> qt.Qobj:
"""
The inter-qubit interaction hamiltonian without scaling
factors and normalization.
.. math::
_{i,j=1}^{3} J_{ij} σ^1_i σ^2_j
"""
assert self.j.shape == (3, 3)
assert (self.j.imag == 0).all()
interaction = qt.Qobj(dims=[[2, 2], [2, 2]])
for strength in (it := np.nditer(self.j, flags=["multi_index"])):
i, j = it.multi_index
2022-02-22 10:26:35 +01:00
interaction += float(strength) * qt.tensor(self._sigmas[i], self._sigmas[j]) # type: ignore
2022-02-21 18:24:49 +01:00
return interaction
@property
def j_normalized(self) -> NDArray[np.float64]:
norm = operator_norm(self.bare_interaction)
if norm > 0:
return self.j / norm
return self.j
2022-02-21 18:24:49 +01:00
@property
def interaction(self) -> qt.Qobj:
"""The inter-qubit interaction hamiltonian."""
interaction = self.bare_interaction
2022-02-22 10:27:25 +01:00
norm = operator_norm(interaction)
interaction *= float(self.γ) / (2 * norm) if norm > 0 else 0
2022-02-21 18:24:49 +01:00
return interaction
def j_vecs(
self, normalized: bool = True
) -> list[tuple[NDArray[np.float64], NDArray[np.float64]]]:
"""
This returns pairs of vectors :math:`(u_i, v_i)` so that
:math:`J=_i u_i v_i^T`. If normalized is :any:`True`
:math:`J` will be normalized as discussed in the module
docstring.
"""
norm: float = np.sqrt(operator_norm(self.bare_interaction)) if normalized else 1
2022-02-21 18:24:49 +01:00
j_vecs = []
q, r = np.linalg.qr(self.j)
test = np.zeros_like(self.j, dtype=np.float64)
for i in range(self.j.shape[0]):
c = q[:, i]
l = r[i, :]
if not ((c == 0).all() or (l == 0).all()):
j_vecs.append((c / norm, l / norm))
test += np.outer(c, l)
return j_vecs
def bath_coupling(self, i: int) -> qt.Qobj:
"""
The bath coupling operator :math:`L_i` of the ``i``th qubit.
"""
s = np.array(self.s_vec[i]) / np.linalg.norm(np.array(self.s_vec[i]))
coupling_op = qt.sigmax() * s[0] + qt.sigmaz() * s[1]
2022-02-22 10:59:43 +01:00
if i == 0:
2022-02-21 18:24:49 +01:00
return qt.tensor(coupling_op, qt.identity(2))
return qt.tensor(qt.identity(2), coupling_op)
@property
def coupling_operators(self) -> list[np.ndarray]:
"""The bath coupling operators :math:`L`."""
return [self.bath_coupling(i).full() for i in (0, 1)]
2022-02-21 18:24:49 +01:00
def bcf_scale(self, i: int) -> float:
"""
The BCF scaling factor of the ``i``th bath.
"""
return float(self.δ[i]) ** 2
@property
def bcf_scales(self) -> list[float]:
"""The scaling factors for the bath correlation functions."""
return [self.bcf_scale(i) for i in (1, 2)]
def η(self, i: int):
"""The BCF pre-factor :math:`η` of the ``i``th bath."""
ω_c = float(self.ω_c[i])
s = float(self.s[i])
T = float(self.T[i])
return (
1
/ (ω_c * s) ** s
* np.exp(s)
* (max([1, np.exp(ω_c * s * 1 / T) - 1]) if T > 0 else 1)
)
2022-02-21 18:24:49 +01:00
def bcf(self, i: int) -> hops.util.bcf.OhmicBCF_zeroTemp:
"""
The normalized zero temperature BCF of the ``i``th bath.
"""
return hops.util.bcf.OhmicBCF_zeroTemp(
s=self.s[i], eta=1, w_c=self.ω_c[i], normed=True, with_pi=False
)
def spectral_density(self, i: int) -> hops.util.bcf.OhmicSD_zeroTemp:
"""
The normalized zero temperature spectral density of the ``i``th bath.
"""
return hops.util.bcf.OhmicSD_zeroTemp(
s=float(self.s[i]),
eta=np.pi,
w_c=float(self.ω_c[i]),
normed=True,
with_pi=False,
)
def thermal_correlations(
self, i: int
) -> Optional[hops.util.bcf.Ohmic_StochasticPotentialCorrelations]:
"""
The normalized thermal noise corellation function of the ``i``th bath.
"""
if self.T[i] == 0:
return None
return hops.util.bcf.Ohmic_StochasticPotentialCorrelations(
s=self.s[i],
eta=1,
w_c=self.ω_c[i],
normed=True,
with_pi=False,
beta=1 / float(self.T[i]),
)
def thermal_spectral_density(
self, i: int
) -> Optional[hops.util.bcf.Ohmic_StochasticPotentialDensity]:
"""
The normalized thermal noise spectral density of the ``i``th bath.
"""
if self.T[i] == 0:
return None
return hops.util.bcf.Ohmic_StochasticPotentialDensity(
s=self.s[i],
eta=1,
w_c=self.ω_c[i],
normed=True,
with_pi=False,
beta=1.0 / float(self.T[i]),
)
def bcf_coefficients(
self, n: Optional[int] = None
) -> tuple[list[NDArray[np.complex128]], list[NDArray[np.complex128]]]:
2022-02-21 18:24:49 +01:00
"""
The normalizedzero temperature BCF fit coefficients
:math:`G_i,W_i` the ``i``th bath with ``n`` terms.
"""
g, w = [], []
for i in 0, 1:
n = n or self.bcf_terms[i]
g_n, w_n = self.bcf(i).exponential_coefficients(n)
g.append(g_n)
w.append(w_n)
return g, w
2022-02-21 18:24:49 +01:00
@staticmethod
def basis(n_1: int = 1, n_2: int = 1) -> qt.Qobj:
"""
A product state with the qubits in states ``n_i`` where ``1``
means down and ``0`` means up.
"""
return qt.basis([2, 2], [n_1, n_2])
def driving_process(
self,
i: int,
) -> sp.StocProc:
"""The driving stochastic process of the ``i``th bath."""
return sp.StocProc_FFT(
spectral_density=self.spectral_density(i),
alpha=self.bcf(i),
t_max=self.t_max,
intgr_tol=self.driving_process_tolerances[i].integration,
intpl_tol=self.driving_process_tolerances[i].interpolation,
negative_frequencies=False,
)
def thermal_process(
self,
i: int,
) -> Optional[sp.StocProc]:
"""The thermal noise stochastic process of the ``i``th bath."""
if self.T[i] == 0:
return None
return sp.StocProc_TanhSinh(
spectral_density=self.thermal_spectral_density(i),
alpha=self.thermal_correlations(i),
t_max=self.t_max,
intgr_tol=self.thermal_process_tolerances[i].integration,
intpl_tol=self.thermal_process_tolerances[i].interpolation,
negative_frequencies=False,
)
@property
def thermal_processes(self) -> list[Optional[sp.StocProc]]:
2022-02-23 18:10:33 +01:00
"""
The thermal noise stochastic processes for each bath.
:any:`None` means zero temperature.
2022-02-23 18:10:33 +01:00
"""
return [self.thermal_process(i) for i in (0, 1)]
2022-02-23 18:10:33 +01:00
###########################################################################
# Utility #
###########################################################################
2022-02-23 18:10:33 +01:00
2022-02-22 15:34:19 +01:00
@property
2022-03-21 14:55:52 +01:00
def hops_config(self) -> params.HIParams:
2022-02-21 18:24:49 +01:00
"""
The hops :any:`hops.core.hierarchy_params.HIParams` parameter object
for this system.
"""
g, w = self.bcf_coefficients()
2022-02-21 18:24:49 +01:00
system = params.SysP(
2022-02-22 10:26:35 +01:00
H_sys=self.system.full(),
2022-02-21 18:24:49 +01:00
L=[self.bath_coupling(0).full(), self.bath_coupling(1).full()],
g=g,
w=w,
2022-02-21 18:24:49 +01:00
bcf_scale=[self.bcf_scale(0), self.bcf_scale(1)],
T=self.T,
description=self.description,
psi0=self.ψ_0.full().flatten(),
)
2022-02-23 18:10:33 +01:00
trunc_scheme = TruncationScheme_Power_multi.from_g_w(
g=system.g,
w=system.w,
p=[1, 1],
q=[0.5, 0.5],
kfac=[float(fac) for fac in self.k_fac],
)
if self.truncation_scheme == "bath_memory":
trunc_scheme = BathMemory.from_system(
system,
nonlinear=True,
influence_tolerance=float(self.influence_tolerance),
)
if self.truncation_scheme == "simplex":
trunc_scheme = TruncationScheme_Simplex(self.k_max)
2022-02-21 18:24:49 +01:00
hierarchy = params.HiP(
seed=0,
nonlinear=True,
terminator=False,
result_type=params.ResultType.ZEROTH_AND_FIRST_ORDER,
accum_only=False,
rand_skip=None,
2022-02-23 18:10:33 +01:00
truncation_scheme=trunc_scheme,
2022-02-21 18:24:49 +01:00
save_therm_rng_seed=True,
auto_normalize=True,
)
default_solver_args = dict(rtol=1e-8, atol=1e-8)
default_solver_args.update(self.solver_args)
integration = params.IntP(
t_max=float(self.t_max),
t_steps=int(float(self.t_max) / float(self.resolution)) + 1,
2022-02-23 10:47:57 +01:00
**default_solver_args,
2022-02-21 18:24:49 +01:00
)
return params.HIParams(
SysP=system,
IntP=integration,
HiP=hierarchy,
Eta=[self.driving_process(0), self.driving_process(1)],
EtaTherm=[self.thermal_process(0), self.thermal_process(1)],
)
2022-02-22 10:26:35 +01:00
def is_close_untiary(
self,
other: "TwoQubitModel",
atol: float = 1e-5,
rtol=1e-8,
max_products: int = 6,
):
"""
A necessary criterion for the two model configuration to be
unitarily equivalient. It tests if any product of
``max_products`` system operators (system hamiltonian and bath
couplings) are unitary equivalent.
The arguments ``atol`` and ``rtol`` are passed to
:any:`numpy.allclose`.
:param max_products: The maximum number of factors considered.
"""
this_keys = list(self.__dict__.keys())
ignored_keys = ["γ", "ω_2", "j", "s_vec", "description", "_sigmas"]
for key in this_keys:
if (key not in ignored_keys) and self.__dict__[key] != other.__dict__[key]:
return False
self_ops = [self.system, self.bath_coupling(0), self.bath_coupling(1)]
other_ops = [other.system, other.bath_coupling(0), other.bath_coupling(1)]
for n in range(1, max_products):
for index in itertools.product(*((range(len(self_ops)),) * n)):
A = functools.reduce(
lambda acc, i: acc * self_ops[i],
index,
qt.identity(dims=[2, 2]),
)
B = functools.reduce(
lambda acc, i: acc * other_ops[i],
index,
qt.identity(dims=[2, 2]),
)
if not np.allclose(
A.eigenenergies(),
B.eigenenergies(),
rtol=rtol,
atol=atol,
):
return False
return True