two_qubit_model/hiro_models/model_base.py

511 lines
16 KiB
Python
Raw Normal View History

2022-03-18 11:25:31 +01:00
"""A base class for model HOPS configs."""
2022-04-06 18:28:53 +02:00
from dataclasses import dataclass, field
2022-07-11 14:32:50 +02:00
from typing import Any, Optional, Union, ClassVar
2022-04-06 18:28:53 +02:00
from hops.util.dynamic_matrix import DynamicMatrix
2022-03-23 14:55:26 +01:00
from .utility import JSONEncoder, object_hook
2022-03-18 11:25:31 +01:00
import numpy as np
from numpy.typing import NDArray
2022-03-18 11:25:31 +01:00
import json
import copy
import hashlib
from abc import ABC, abstractmethod
import qutip as qt
from hops.core.hierarchy_data import HIData
import hopsflow
2022-04-06 18:28:53 +02:00
from hopsflow.util import EnsembleValue
import hashlib
2022-03-21 14:55:52 +01:00
import hops.core.hierarchy_parameters as params
2022-07-11 14:32:50 +02:00
from collections.abc import Callable
2022-03-18 11:25:31 +01:00
2022-04-06 18:28:53 +02:00
@dataclass
class Model(ABC):
2022-03-18 11:25:31 +01:00
"""
A base class with some data management functionality.
"""
2022-03-23 12:55:37 +01:00
ψ_0: qt.Qobj
"""The initial state."""
2022-03-24 18:11:42 +01:00
description: str = ""
"""A free-form description of the model instance."""
2022-04-12 18:22:54 +02:00
t: NDArray[np.float64] = np.linspace(0, 10, 1000)
"""The simulation time points."""
2022-04-11 14:49:21 +02:00
2022-03-21 14:55:52 +01:00
__base_version__: int = 1
"""The version of the model base."""
2022-03-18 11:25:31 +01:00
__version__: int = 1
"""
The version of the model implementation. It is increased for
breaking changes.
"""
2022-04-06 18:28:53 +02:00
_ignored_keys: list[str] = field(default_factory=lambda: ["_sigmas", "description"])
2022-03-24 18:11:42 +01:00
"""Keys that are ignored when comparing or hashing models."""
2022-03-18 11:25:31 +01:00
###########################################################################
# Utility #
###########################################################################
2022-07-11 14:32:50 +02:00
def to_dict(self, extra_fields: "dict[str, Callable[[Model], str]]" = {}):
"""Returns a dictionary representation of the model
configuration.
2022-07-11 14:32:50 +02:00
:param extra_fields: A dictionary whose keys will be added to
the final dict and whose values are callables that take
the model instance as an argument and return the value
that will be assigned the addyd key.
"""
return (
{key: self.__dict__[key] for key in self.__dict__ if key[0] != "_"}
| {
"__version__": self.__version__,
"__base_version__": self.__base_version__,
"__model__": self.__class__.__name__,
}
| {key: value(self) for key, value in extra_fields.items()}
)
2022-03-24 18:11:42 +01:00
def to_hashable_dict(self):
"""
Returns a dictionary representation of the model configuration
without unecessary keys.
"""
return {
key: self.__dict__[key]
for key in self.__dict__
if key[0] != "_" and key not in self._ignored_keys
} | {
"__version__": self.__version__,
"__base_version__": self.__base_version__,
"__model__": self.__class__.__name__,
}
2022-03-18 11:25:31 +01:00
def to_json(self):
"""Returns a json representation of the model configuration."""
2022-03-22 10:25:58 +01:00
return JSONEncoder.dumps(self.to_dict())
2022-03-18 11:25:31 +01:00
def __hash__(self):
2022-03-24 18:11:42 +01:00
return JSONEncoder.hash(self.to_hashable_dict()).__hash__()
2022-03-22 15:43:01 +01:00
@property
def hexhash(self):
"""A hexadecimal representation of the model hash."""
2022-04-07 14:29:49 +02:00
2022-03-24 18:11:42 +01:00
return JSONEncoder.hexhash(self.to_hashable_dict())
2022-03-18 11:25:31 +01:00
@classmethod
def from_dict(cls, model_dict: dict[str, Any]):
2022-03-18 11:25:31 +01:00
"""
Tries to instantiate a model config from the dictionary ``dictionary``.
2022-03-18 11:25:31 +01:00
"""
assert (
2022-03-21 14:55:52 +01:00
model_dict["__model__"] == cls.__name__
), f"You are trying to instantiate the wrong model '{model_dict['__model__']}'."
assert (
model_dict["__version__"] == cls.__version__
and model_dict["__base_version__"] == cls.__base_version__
2022-03-18 11:25:31 +01:00
), "Incompatible version detected."
del model_dict["__version__"]
2022-03-21 14:55:52 +01:00
del model_dict["__base_version__"]
del model_dict["__model__"]
2022-03-18 11:25:31 +01:00
return cls(**model_dict)
@classmethod
def from_json(cls, json_str: str):
"""
Tries to instantiate a model config from the json string
``json_str``.
"""
model_dict = JSONEncoder.loads(json_str)
return cls.from_dict(model_dict)
2022-03-18 11:25:31 +01:00
def __eq__(self, other):
this_keys = list(self.__dict__.keys())
for key in this_keys:
2022-03-24 18:11:42 +01:00
if key not in self._ignored_keys:
2022-03-18 11:25:31 +01:00
this_val, other_val = self.__dict__[key], other.__dict__[key]
same = this_val == other_val
if isinstance(this_val, np.ndarray):
same = same.all()
if not same:
return False
return self.__hash__() == other.__hash__()
def copy(self):
"""Return a deep copy of the model."""
return copy.deepcopy(self)
@property
@abstractmethod
2022-04-12 18:22:54 +02:00
def system(self) -> DynamicMatrix:
"""The system hamiltonian."""
pass
@property
@abstractmethod
2022-04-06 18:28:53 +02:00
def coupling_operators(self) -> list[Union[np.ndarray, DynamicMatrix]]:
"""The bath coupling operators :math:`L`."""
2022-04-06 18:28:53 +02:00
pass
2022-04-20 11:41:21 +02:00
@property
def num_baths(self) -> int:
"""The number of baths attached to the system."""
return len(self.coupling_operators)
@abstractmethod
def bcf_coefficients(
self, n: Optional[int] = None
) -> tuple[list[NDArray[np.complex128]], list[NDArray[np.complex128]]]:
"""
The normalized zero temperature BCF fit coefficients
:math:`[G_i], [W_i]` with ``n`` terms.
"""
pass
@property
@abstractmethod
def thermal_processes(self) -> list[Optional[hopsflow.hopsflow.StocProc]]:
"""
The thermal noise stochastic processes for each bath.
:any:`None` means zero temperature.
"""
pass
@property
@abstractmethod
def bcf_scales(self) -> list[float]:
"""The scaling factors for the bath correlation functions."""
pass
2022-03-21 14:55:52 +01:00
@property
@abstractmethod
def hops_config(self) -> params.HIParams:
"""
The hops :any:`hops.core.hierarchy_params.HIParams` parameter object
for this system.
"""
@property
def hopsflow_system(self) -> hopsflow.hopsflow.SystemParams:
"""The :any:`hopsflow` system config for the system."""
g, w = self.bcf_coefficients()
return hopsflow.hopsflow.SystemParams(
L=self.coupling_operators,
2022-03-31 12:07:42 +02:00
G=g,
W=w,
2022-04-06 18:28:53 +02:00
t=self.t,
2022-03-31 12:07:42 +02:00
bcf_scale=self.bcf_scales,
fock_hops=True,
2022-03-23 12:55:37 +01:00
nonlinear=True,
)
def hopsflow_therm(
self, τ: NDArray[np.float64]
) -> Optional[hopsflow.hopsflow.ThermalParams]:
"""The :any:`hopsflow` thermal config for the system."""
processes = self.thermal_processes
scales = self.bcf_scales
for process, scale in zip(processes, scales):
if process:
process.set_scale(scale)
process.calc_deriv = True
return hopsflow.hopsflow.ThermalParams(processes, τ)
###########################################################################
# Derived Quantities #
###########################################################################
def system_expectation(
2022-04-12 18:22:54 +02:00
self, data: HIData, operator: DynamicMatrix, **kwargs
2022-03-31 12:07:42 +02:00
) -> EnsembleValue:
"""Calculates the expectation value of ``operator`` from the
hierarchy data ``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.ensemble_mean`.
:returns: See :any:`hopsflow.util.ensemble_mean`.
"""
2022-03-23 12:55:37 +01:00
operator_hash = JSONEncoder.hexhash(operator)
2022-03-24 18:11:42 +01:00
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.util.operator_expectation_ensemble(
2022-03-31 12:07:42 +02:00
data.valid_sample_iterator(data.stoc_traj), # type: ignore
2022-04-12 18:22:54 +02:00
operator,
self.t,
2022-03-23 12:55:37 +01:00
normalize=True, # always nonlinear
save=f"{operator_hash}_{self.hexhash}",
2022-03-31 12:07:42 +02:00
N=N,
2022-03-23 12:55:37 +01:00
**kwargs,
)
2022-03-31 12:07:42 +02:00
def system_energy(self, data: HIData, **kwargs) -> EnsembleValue:
"""Calculates the system energy from the hierarchy data
``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.ensemble_mean`.
:returns: See :any:`hopsflow.util.ensemble_mean`.
"""
2022-03-23 12:55:37 +01:00
operator = self.system
return self.system_expectation(data, operator, real=True, **kwargs)
2022-07-11 14:32:50 +02:00
def system_power(self, data: HIData, **kwargs) -> Optional[EnsembleValue]:
"""Calculates the power based on the time dependency of the
system hamiltonian from ``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.ensemble_mean`.
:returns: See :any:`hopsflow.util.ensemble_mean`. Returns
:any:`None` if the system is static.
"""
operator = self.system.derivative()
if (abs(operator(self.t)).sum() == 0).all():
return None
return self.system_expectation(data, operator, real=True, **kwargs)
def energy_change_from_system_power(
self, data: HIData, **kwargs
) -> Optional[EnsembleValue]:
"""Calculates the integrated system power from the hierarchy
data ``data``.
The ``kwargs`` are passed on to :any:`system_power`.
:returns: See :any:`system_power`. Returns :any:`None` if the
system is static.
"""
power = self.system_power(data, **kwargs)
if power is not None:
return power.integrate(self.t)
return None
2022-03-31 12:07:42 +02:00
def bath_energy_flow(self, data: HIData, **kwargs) -> EnsembleValue:
"""Calculates the bath energy flow from the hierarchy data
``data``.
The ``kwargs`` are passed on to
2022-03-23 12:55:37 +01:00
:any:`hopsflow.util.heat_flow_ensemble`.
2022-03-23 12:55:37 +01:00
:returns: See :any:`hopsflow.util.heat_flow_ensemble`.
"""
2022-03-23 12:55:37 +01:00
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.hopsflow.heat_flow_ensemble(
2022-03-31 12:07:42 +02:00
data.valid_sample_iterator(data.stoc_traj), # type: ignore
data.valid_sample_iterator(data.aux_states), # type: ignore
self.hopsflow_system,
2022-03-24 16:34:17 +01:00
(data.valid_sample_iterator(data.rng_seed), self.hopsflow_therm(data.time[:])), # type: ignore
2022-03-23 12:55:37 +01:00
save=f"flow_{self.hexhash}",
2022-03-31 12:07:42 +02:00
N=N,
2022-03-23 12:55:37 +01:00
**kwargs,
)
2022-03-31 12:07:42 +02:00
def interaction_energy(self, data: HIData, **kwargs) -> EnsembleValue:
2022-03-23 12:55:37 +01:00
"""Calculates interaction energy from the hierarchy data
``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.interaction_energy_ensemble`.
:returns: See :any:`hopsflow.util.interaction_energy_ensemble`.
"""
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.hopsflow.interaction_energy_ensemble(
2022-03-31 12:07:42 +02:00
data.valid_sample_iterator(data.stoc_traj), # type: ignore
data.valid_sample_iterator(data.aux_states), # type: ignore
2022-03-23 12:55:37 +01:00
self.hopsflow_system,
2022-03-24 16:34:17 +01:00
(data.valid_sample_iterator(data.rng_seed), self.hopsflow_therm(data.time[:])), # type: ignore
2022-03-31 12:07:42 +02:00
N=N,
2022-03-23 12:55:37 +01:00
save=f"interaction_{self.hexhash}",
**kwargs,
)
2022-07-11 14:32:50 +02:00
def interaction_power(self, data: HIData, **kwargs) -> EnsembleValue:
"""Calculates interaction power from the hierarchy data
``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.interaction_energy_ensemble`.
:returns: See :any:`hopsflow.util.interaction_energy_ensemble`.
"""
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.hopsflow.interaction_energy_ensemble(
data.valid_sample_iterator(data.stoc_traj), # type: ignore
data.valid_sample_iterator(data.aux_states), # type: ignore
self.hopsflow_system,
(data.valid_sample_iterator(data.rng_seed), self.hopsflow_therm(data.time[:])), # type: ignore
N=N,
save=f"interaction_power_{self.hexhash}",
power=True,
**kwargs,
)
def energy_change_from_interaction_power(
self, data: HIData, **kwargs
) -> EnsembleValue:
"""Calculates the integrated interaction power from the hierarchy data
``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.util.energy_change_from_interaction_power`.
:returns: See :any:`hopsflow.util.energy_change_from_interaction_power`.
"""
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.hopsflow.energy_change_from_interaction_power(
np.array(data.time),
data.valid_sample_iterator(data.stoc_traj), # type: ignore
data.valid_sample_iterator(data.aux_states), # type: ignore
self.hopsflow_system,
(data.valid_sample_iterator(data.rng_seed), self.hopsflow_therm(data.time[:])), # type: ignore
N=N,
save=f"interaction_power_{self.hexhash}", # under the hood the power is used
**kwargs,
)
2022-03-31 12:07:42 +02:00
def bath_energy(self, data: HIData, **kwargs) -> EnsembleValue:
2022-03-23 12:55:37 +01:00
"""Calculates bath energy by integrating the bath energy flow
calculated from the ``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.bath_energy_from_flow`.
:returns: See :any:`hopsflow.bath_energy_from_flow`.
"""
N, kwargs = _get_N_kwargs(kwargs, data)
return hopsflow.hopsflow.bath_energy_from_flow(
np.array(data.time),
2022-03-31 12:07:42 +02:00
data.valid_sample_iterator(data.stoc_traj), # type: ignore
data.valid_sample_iterator(data.aux_states), # type: ignore
2022-03-23 12:55:37 +01:00
self.hopsflow_system,
2022-03-24 16:34:17 +01:00
(data.valid_sample_iterator(data.rng_seed), self.hopsflow_therm(data.time[:])), # type: ignore
2022-03-23 12:55:37 +01:00
save=f"flow_{self.hexhash}", # under the hood the flow is used
2022-03-31 12:07:42 +02:00
N=N,
**kwargs,
)
2022-03-23 12:55:37 +01:00
def interaction_energy_from_conservation(
self, data: HIData, **kwargs
2022-03-31 12:07:42 +02:00
) -> EnsembleValue:
2022-03-23 12:55:37 +01:00
"""Calculates the interaction energy from energy conservations
calculated from the ``data``.
The ``kwargs`` are passed on to
:any:`hopsflow.bath_energy_from_flow`.
:returns: See :any:`hopsflow.bath_energy_from_flow`.
"""
2022-03-31 12:07:42 +02:00
system = self.system_energy(data, **kwargs)
bath = self.bath_energy(data, **kwargs)
2022-04-13 13:48:20 +02:00
total = float(qt.expect(qt.Qobj(self.system(0)), self.ψ_0))
2022-03-31 12:07:42 +02:00
return total - (system + bath)
2022-03-23 12:55:37 +01:00
2022-06-09 16:10:41 +02:00
def total_energy(self, data: HIData, **kwargs) -> EnsembleValue:
2022-07-11 14:32:50 +02:00
"""Calculates the total energy from the trajectories in
``data`` using energy bilance.
2022-06-09 16:10:41 +02:00
The ``kwargs`` are passed on to :any:`bath_energy`,
:any:`system_energy` and :any:`interaction_energy`.
:returns: The total energy.
"""
system = self.system_energy(data, **kwargs)
bath = self.bath_energy(data, **kwargs)
interaction = self.interaction_energy(data, **kwargs)
total = system + bath.sum_baths() + interaction.sum_baths()
return total
2022-07-11 14:32:50 +02:00
def total_power(self, data: HIData, **kwargs) -> EnsembleValue:
"""Calculates the total power from the trajectories in
``data``.
The ``kwargs`` are passed on to :any:`system_power` and
:any:`interaction_power`.
:returns: The total power.
"""
power = self.interaction_power(data, **kwargs).sum_baths()
system_power = self.system_power(data, **kwargs)
if system_power is not None:
power = power + system_power
return power
def total_energy_from_power(self, data: HIData, **kwargs) -> EnsembleValue:
"""Calculates the total energy from the trajectories in
``data`` using the integrated power.
The ``kwargs`` are passed on to :any:`total_power`.
:returns: The total energy.
"""
return self.total_power(data, **kwargs).integrate(self.t)
std_extra_fields: ClassVar = {"BCF scaling": lambda model: model.bcf_scale}
2022-03-23 12:55:37 +01:00
def _get_N_kwargs(kwargs: dict, data: HIData) -> tuple[int, dict]:
N = kwargs.get("N", data.samples)
if "N" in kwargs:
del kwargs["N"]
return N, kwargs