mirror of
https://github.com/vale981/two_qubit_model
synced 2025-03-05 09:41:41 -05:00
finish implementing the otto engine
This commit is contained in:
parent
904a7a18af
commit
941a637005
4 changed files with 232 additions and 74 deletions
|
@ -13,6 +13,7 @@ from filelock import FileLock
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .one_qubit_model import QubitModel, QubitModelMutliBath
|
from .one_qubit_model import QubitModel, QubitModelMutliBath
|
||||||
from .two_qubit_model import TwoQubitModel
|
from .two_qubit_model import TwoQubitModel
|
||||||
|
from .otto_cycle import OttoEngine
|
||||||
from collections.abc import Sequence, Iterator, Iterable
|
from collections.abc import Sequence, Iterator, Iterable
|
||||||
import shutil
|
import shutil
|
||||||
import logging
|
import logging
|
||||||
|
@ -72,6 +73,9 @@ def model_hook(dct: dict[str, Any]):
|
||||||
if model == "QubitModelMutliBath":
|
if model == "QubitModelMutliBath":
|
||||||
return QubitModelMutliBath.from_dict(treated_vals)
|
return QubitModelMutliBath.from_dict(treated_vals)
|
||||||
|
|
||||||
|
if model == "OttoEngine":
|
||||||
|
return OttoEngine.from_dict(treated_vals)
|
||||||
|
|
||||||
return object_hook(dct)
|
return object_hook(dct)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""A base class for model HOPS configs."""
|
"""A base class for model HOPS configs."""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Optional, Union, ClassVar
|
from typing import Any, Iterable, Optional, Union, ClassVar
|
||||||
from hops.util.dynamic_matrix import DynamicMatrix
|
from hops.util.dynamic_matrix import DynamicMatrix
|
||||||
from .utility import JSONEncoder, object_hook
|
from .utility import JSONEncoder, object_hook
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -139,10 +139,14 @@ class Model(ABC):
|
||||||
if key not in self._ignored_keys:
|
if key not in self._ignored_keys:
|
||||||
this_val, other_val = self.__dict__[key], other.__dict__[key]
|
this_val, other_val = self.__dict__[key], other.__dict__[key]
|
||||||
|
|
||||||
same = this_val == other_val
|
if isinstance(this_val, Iterable):
|
||||||
|
for val_1, val_2 in zip(this_val, other_val):
|
||||||
|
if not _compare_values(val_1, val_2):
|
||||||
|
return False
|
||||||
|
|
||||||
if isinstance(this_val, np.ndarray):
|
continue
|
||||||
same = same.all()
|
|
||||||
|
same = _compare_values(this_val, other_val)
|
||||||
|
|
||||||
if not same:
|
if not same:
|
||||||
return False
|
return False
|
||||||
|
@ -508,3 +512,12 @@ def _get_N_kwargs(kwargs: dict, data: HIData) -> tuple[int, dict]:
|
||||||
del kwargs["N"]
|
del kwargs["N"]
|
||||||
|
|
||||||
return N, kwargs
|
return N, kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _compare_values(this_val, other_val):
|
||||||
|
same = this_val == other_val
|
||||||
|
|
||||||
|
if isinstance(this_val, np.ndarray):
|
||||||
|
same = same.all()
|
||||||
|
|
||||||
|
return same
|
||||||
|
|
|
@ -2,7 +2,6 @@ r"""HOPS Configurations for a simple qubit otto cycle."""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import hopsflow
|
import hopsflow
|
||||||
from numpy.typing import NDArray
|
|
||||||
from typing import Any, Optional, SupportsFloat, Union
|
from typing import Any, Optional, SupportsFloat, Union
|
||||||
import hops.util.bcf
|
import hops.util.bcf
|
||||||
import hops.util.bcf_fits
|
import hops.util.bcf_fits
|
||||||
|
@ -19,11 +18,110 @@ import stocproc as sp
|
||||||
from beartype import beartype
|
from beartype import beartype
|
||||||
from .utility import StocProcTolerances, bcf_scale
|
from .utility import StocProcTolerances, bcf_scale
|
||||||
from .model_base import Model
|
from .model_base import Model
|
||||||
import scipy.special
|
from scipy.optimize import minimize_scalar
|
||||||
import hopsflow
|
import hopsflow
|
||||||
from hops.util.dynamic_matrix import DynamicMatrix, ConstantMatrix, SmoothStep, Periodic
|
from hops.util.dynamic_matrix import (
|
||||||
|
DynamicMatrix,
|
||||||
|
ConstantMatrix,
|
||||||
|
SmoothStep,
|
||||||
|
Periodic,
|
||||||
|
MatrixType,
|
||||||
|
)
|
||||||
from .one_qubit_model import QubitModelMutliBath
|
from .one_qubit_model import QubitModelMutliBath
|
||||||
|
|
||||||
|
from numpy.typing import ArrayLike, NDArray
|
||||||
|
from numbers import Real
|
||||||
|
|
||||||
|
|
||||||
|
Timings = tuple[Real, Real, Real, Real]
|
||||||
|
Orders = tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
|
class SmoothlyInterpolatdPeriodicMatrix(DynamicMatrix):
|
||||||
|
"""A periodic dynamic matrix that smoothly interpolates between
|
||||||
|
two matrices using :any:`SmoothStep`.
|
||||||
|
|
||||||
|
:param matrices: The two matrices ``M1`` and ``M2`` to interpolate
|
||||||
|
between.
|
||||||
|
:param timings: A tuple that contains the times (relative to the
|
||||||
|
period) when the transition from ``M1`` to ``M2`` begins, when
|
||||||
|
it ends and when the reverse transition begins and when it ends.
|
||||||
|
:param period: The period of the modulation.
|
||||||
|
:param orders: The orders of the smoothstep functions that are
|
||||||
|
being used. See also :any:`SmoothStep`.
|
||||||
|
:param amplitudes: The amplitudes of the modulation.
|
||||||
|
:param deriv: The order of derivative of the matrix.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
matrices: tuple[Union[ArrayLike, list[list]], Union[ArrayLike, list[list]]],
|
||||||
|
timings: Timings,
|
||||||
|
period: float,
|
||||||
|
orders: tuple = (2, 2),
|
||||||
|
amplitudes: tuple[float, float] = (1, 1),
|
||||||
|
deriv: int = 0,
|
||||||
|
):
|
||||||
|
self._matrices = matrices
|
||||||
|
self._timings = timings
|
||||||
|
self._period = period
|
||||||
|
self._orders = orders
|
||||||
|
self._amplitudes = amplitudes
|
||||||
|
self._deriv = deriv
|
||||||
|
|
||||||
|
M_1, M_2 = matrices
|
||||||
|
s_1, s_2 = orders
|
||||||
|
a_1, a_2 = amplitudes
|
||||||
|
|
||||||
|
one_cycle: DynamicMatrix = a_1 * (
|
||||||
|
(ConstantMatrix(M_1) if deriv == 0 else ConstantMatrix(np.zeros_like(M_1)))
|
||||||
|
)
|
||||||
|
|
||||||
|
if a_1 != 0:
|
||||||
|
one_cycle += a_1 * (
|
||||||
|
SmoothStep(
|
||||||
|
M_1, timings[2] * period, timings[3] * period, s_2, deriv=deriv
|
||||||
|
)
|
||||||
|
- SmoothStep(
|
||||||
|
M_1, timings[0] * period, timings[1] * period, s_1, deriv=deriv
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if a_2 != 0:
|
||||||
|
one_cycle += a_2 * (
|
||||||
|
SmoothStep(
|
||||||
|
M_2, timings[0] * period, timings[1] * period, s_1, deriv=deriv
|
||||||
|
)
|
||||||
|
- SmoothStep(
|
||||||
|
M_2, timings[2] * period, timings[3] * period, s_2, deriv=deriv
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._m = Periodic(one_cycle, period)
|
||||||
|
|
||||||
|
def call(self, t: NDArray[np.float64]) -> MatrixType:
|
||||||
|
return self._m.call(t)
|
||||||
|
|
||||||
|
def derivative(self):
|
||||||
|
return self.__class__(
|
||||||
|
matrices=self._matrices,
|
||||||
|
timings=self._timings,
|
||||||
|
period=self._period,
|
||||||
|
orders=self._orders,
|
||||||
|
amplitudes=self._amplitudes,
|
||||||
|
deriv=self._deriv + 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return dict(
|
||||||
|
matrices=self._matrices,
|
||||||
|
timings=self._timings,
|
||||||
|
period=self._period,
|
||||||
|
orders=self._orders,
|
||||||
|
amplitude=self._amplitudes,
|
||||||
|
deriv=self._deriv,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@beartype
|
@beartype
|
||||||
@dataclass(eq=False)
|
@dataclass(eq=False)
|
||||||
|
@ -58,6 +156,20 @@ class OttoEngine(QubitModelMutliBath):
|
||||||
is zero and its largest one is one.
|
is zero and its largest one is one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
L: tuple[np.ndarray, np.ndarray] = field(
|
||||||
|
default_factory=lambda: tuple([1 / 2 * (qt.sigmax().full())] * 2) # type: ignore
|
||||||
|
)
|
||||||
|
"""The bare coupling operators to the two baths."""
|
||||||
|
|
||||||
|
ω_s: list[Union[SupportsFloat, str]] = field(default_factory=lambda: [2] * 2)
|
||||||
|
"""
|
||||||
|
The shift frequencies :math:`ω_s`. If set to ``'auto'``, the
|
||||||
|
(thermal) spectral densities will be shifted so that the coupling
|
||||||
|
of the first bath is resonant with the hamiltonian before the
|
||||||
|
expansion of the energy gap and the second bath is resonant with
|
||||||
|
the hamiltonian after the expansion.
|
||||||
|
"""
|
||||||
|
|
||||||
###########################################################################
|
###########################################################################
|
||||||
# Cycle Settings #
|
# Cycle Settings #
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
@ -68,98 +180,92 @@ class OttoEngine(QubitModelMutliBath):
|
||||||
Δ: float = 1
|
Δ: float = 1
|
||||||
"""The expansion ratio of the modulation."""
|
"""The expansion ratio of the modulation."""
|
||||||
|
|
||||||
λ_u: float = 0.25
|
timings_H: Timings = field(default_factory=lambda: (0.25, 0.5, 0.75, 1))
|
||||||
"""
|
"""The timings for the ``H`` modulation. See :any:`SmoothlyInterpolatdPeriodicMatrix`."""
|
||||||
The portion of the cycle where the transition from ``H_0`` to
|
|
||||||
``H_1`` begins. Ranges from ``0`` to ``1``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
λ_h: float = 0.5
|
orders_H: Orders = field(default_factory=lambda: (2, 2))
|
||||||
"""
|
"""The smoothness of the modulation of ``H``."""
|
||||||
The portion of the cycle where the transition from ``H_0`` to
|
|
||||||
``H_1`` ends. Ranges from ``0`` to ``1``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
λ_d: float = 0.75
|
timings_L: tuple[Timings, Timings] = field(
|
||||||
"""
|
default_factory=lambda: ((0, 0.05, 0.15, 0.2), (0.5, 0.55, 0.65, 0.7))
|
||||||
The portion of the cycle where the transition from ``H_1`` to
|
)
|
||||||
``H_0`` begins. Ranges from ``0`` to ``1``.
|
"""The timings for the ``L`` modulation. See :any:`SmoothlyInterpolatdPeriodicMatrix`."""
|
||||||
"""
|
|
||||||
|
orders_L: tuple[Orders, Orders] = field(default_factory=lambda: ((2, 2), (2, 2)))
|
||||||
|
"""The smoothness of the modulation of ``L``."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def τ_l(self) -> float:
|
def τ_expansion_finished(self):
|
||||||
"""
|
return self.timings_H[1] * self.Θ
|
||||||
The length of the timespan the Hamiltonian matches ``H_0``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.λ_u * self.Θ
|
def __post_init__(self):
|
||||||
|
def objective(ω_s, ω_exp, i):
|
||||||
|
self.ω_s[i] = ω_s
|
||||||
|
return -self.full_thermal_spectral_density(i)(ω_exp)
|
||||||
|
|
||||||
@property
|
ω_exps = [
|
||||||
def τ_h(self) -> float:
|
get_energy_gap(self.H(0)),
|
||||||
"""
|
get_energy_gap(self.H(self.τ_expansion_finished)),
|
||||||
The length of the timespan the Hamiltonian matches ``H_1``.
|
]
|
||||||
"""
|
|
||||||
|
|
||||||
return (self.λ_d - self.λ_h) * self.Θ
|
for i, shift in enumerate(self.ω_s):
|
||||||
|
if shift == "auto":
|
||||||
|
res = minimize_scalar(
|
||||||
|
objective,
|
||||||
|
1,
|
||||||
|
method="bounded",
|
||||||
|
bounds=(0.01, ω_exps[i]),
|
||||||
|
options=dict(maxiter=100),
|
||||||
|
args=(ω_exps[i], i),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
if not res.success:
|
||||||
def τ_u(self) -> float:
|
raise RuntimeError("Cannot optimize SD shift.")
|
||||||
"""
|
|
||||||
The length of the trasition of the Hamiltonian from ``H_0`` to
|
|
||||||
``H_1``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (self.λ_h - self.λ_u) * self.Θ
|
self.ω_s[i] = res.x
|
||||||
|
|
||||||
@property
|
|
||||||
def τ_d(self) -> float:
|
|
||||||
"""
|
|
||||||
The length of the trasition of the Hamiltonian from ``H_1`` to
|
|
||||||
``H_0``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (1 - self.λ_d) * self.Θ
|
|
||||||
|
|
||||||
@property
|
|
||||||
def Ω(self) -> float:
|
|
||||||
"""The base Angular frequency of the cycle."""
|
|
||||||
|
|
||||||
return (2 * np.pi) / self.Θ
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def H(self) -> DynamicMatrix:
|
def H(self) -> DynamicMatrix:
|
||||||
r"""
|
"""
|
||||||
Returns the modulated system Hamiltonian.
|
Returns the modulated system Hamiltonian.
|
||||||
|
|
||||||
The system hamiltonian will always be :math:`ω_{\max} * H_1 +
|
The system hamiltonian will always be :math:`ω_{\max} * H_1 +
|
||||||
(ω_{\max} - ω_{\min}) * f(τ) * H_1` where ``H_0`` is a fixed
|
(ω_{\max} - ω_{\min}) * f(τ) * H_1` where ``H_0`` is a fixed
|
||||||
matrix and :math:`f(τ)` models the time dependence.
|
matrix and :math:`f(τ)` models the time dependence. The time
|
||||||
|
dependce is implemented via
|
||||||
|
:any:`SmoothlyInterpolatdPeriodicMatrix` and leads to a
|
||||||
|
modulation of the levelspacing between ``ε_min=1`` and
|
||||||
|
``ε_max`` so that ``ε_max/ε_min - 1 = Δ``.
|
||||||
|
|
||||||
The modulation :math:`f(τ)` consists of constants and smooth
|
The modulation is cyclical with period :any:`Θ`.
|
||||||
step functions. For :math:`τ < τ_l` :math:`f(τ) = 0` and for
|
|
||||||
:math:`τ_l + τ_u <= τ < τ_l + τ_u + τ_h` :math:`f(τ) =
|
|
||||||
ε_{\max}`. The transitions between those states last
|
|
||||||
:math:`τ_u` for :math:`H_0` to :math:`H_1` and :math:`τ_d` for
|
|
||||||
:math:`H_1` to :math:`H_0`.
|
|
||||||
|
|
||||||
The modulation is cyclical with period :any:`T`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
H_0 = normalize_hamiltonian(self.H_0)
|
return SmoothlyInterpolatdPeriodicMatrix(
|
||||||
H_1 = normalize_hamiltonian(self.H_1)
|
(normalize_hamiltonian(self.H_0), normalize_hamiltonian(self.H_1)),
|
||||||
|
self.timings_H,
|
||||||
one_cycle = ConstantMatrix(H_0) + self.Δ * (
|
self.Θ,
|
||||||
SmoothStep(H_1, self.λ_u * self.Θ, self.λ_h * self.Θ)
|
self.orders_H,
|
||||||
- SmoothStep(H_1, self.λ_d * self.Θ, self.Θ)
|
(1, self.Δ + 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
return Periodic(one_cycle, self.Θ)
|
|
||||||
|
|
||||||
# we black-hole the H setter in this model
|
# we black-hole the H setter in this model
|
||||||
@H.setter
|
@H.setter
|
||||||
def H(self, _):
|
def H(self, _):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def coupling_operators(self) -> list[DynamicMatrix]:
|
||||||
|
return [
|
||||||
|
SmoothlyInterpolatdPeriodicMatrix(
|
||||||
|
(np.zeros_like(L_i), L_i),
|
||||||
|
timings,
|
||||||
|
self.Θ,
|
||||||
|
orders,
|
||||||
|
(0, 1),
|
||||||
|
)
|
||||||
|
for L_i, timings, orders in zip(self.L, self.timings_L, self.orders_L)
|
||||||
|
]
|
||||||
|
|
||||||
# @property
|
# @property
|
||||||
# def qubit_model(self) -> QubitModelMutliBath:
|
# def qubit_model(self) -> QubitModelMutliBath:
|
||||||
# """Returns the underlying Qubit model."""
|
# """Returns the underlying Qubit model."""
|
||||||
|
@ -189,6 +295,12 @@ def normalize_hamiltonian(hamiltonian: np.ndarray) -> np.ndarray:
|
||||||
normalized = hamiltonian - eigvals.min() * np.eye(
|
normalized = hamiltonian - eigvals.min() * np.eye(
|
||||||
hamiltonian.shape[0], dtype=hamiltonian.dtype
|
hamiltonian.shape[0], dtype=hamiltonian.dtype
|
||||||
)
|
)
|
||||||
normalized /= eigvals.max() - eigvals.min()
|
normalized /= (eigvals.max() - eigvals.min()).real
|
||||||
|
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def get_energy_gap(hamiltonian: np.ndarray) -> float:
|
||||||
|
eigvals = np.linalg.eigvals(hamiltonian)
|
||||||
|
|
||||||
|
return (eigvals.max() - eigvals.min()).real
|
||||||
|
|
|
@ -33,6 +33,24 @@ class JSONEncoder(json.JSONEncoder):
|
||||||
:any:`TwoQubitModel`.
|
:any:`TwoQubitModel`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def encode(self, obj: Any):
|
||||||
|
def hint_tuples(item: Any):
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
return {
|
||||||
|
"type": "tuple",
|
||||||
|
"value": [
|
||||||
|
hint_tuples(i) if isinstance(i, tuple) else i for i in item
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if isinstance(item, list):
|
||||||
|
return [hint_tuples(e) for e in item]
|
||||||
|
if isinstance(item, dict):
|
||||||
|
return {key: hint_tuples(value) for key, value in item.items()}
|
||||||
|
else:
|
||||||
|
return item
|
||||||
|
|
||||||
|
return super().encode(hint_tuples(obj))
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
def default(self, obj: Any):
|
def default(self, obj: Any):
|
||||||
if hasattr(obj, "to_dict"):
|
if hasattr(obj, "to_dict"):
|
||||||
|
@ -40,6 +58,14 @@ class JSONEncoder(json.JSONEncoder):
|
||||||
|
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
|
@default.register(tuple)
|
||||||
|
def _(self, obj: tuple):
|
||||||
|
print("ho")
|
||||||
|
return {
|
||||||
|
"type": "tuple",
|
||||||
|
"value": list(*obj),
|
||||||
|
}
|
||||||
|
|
||||||
@default.register
|
@default.register
|
||||||
def _(self, arr: np.ndarray):
|
def _(self, arr: np.ndarray):
|
||||||
return {"type": "array", "value": arr.tolist()}
|
return {"type": "array", "value": arr.tolist()}
|
||||||
|
@ -134,6 +160,9 @@ def object_hook(dct: dict[str, Any]):
|
||||||
if type == "DynamicMatrix":
|
if type == "DynamicMatrix":
|
||||||
return getattr(dynamic_matrix, dct["subtype"])(**dct["value"])
|
return getattr(dynamic_matrix, dct["subtype"])(**dct["value"])
|
||||||
|
|
||||||
|
if type == "tuple":
|
||||||
|
return tuple(dct["value"])
|
||||||
|
|
||||||
return dct
|
return dct
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue