Source code for fridom.framework.modules.ramper

"""A module for ramping up and down parameters during a simulation."""
from __future__ import annotations

from typing import TYPE_CHECKING, Literal

import numpy as np

import fridom.framework as fr

if TYPE_CHECKING:  # pragma: no cover
    from collections.abc import Callable


[docs] class Ramper(fr.modules.Module): """ A module for ramping up and down parameters during a simulation. Parameters ---------- start_time : float The time at which the ramping should start. ramp_period : float The period over which the ramping should occur. update_parameters : Callable[[fr.ModelState, float], None], optional A method that updates the model parameters based on the ramped value. It should take the model state and the ramped value which is between 0 and 1. ramp_function : str or Callable[[float], float], optional The ramp function. It can be one of the following strings: "exponential", "power_3", "cosine", "linear" or a custom callable. The default is "exponential". Description ----------- This module contains a list of ramp functions that can be used to ramp up and down parameters during a simulation. This can be useful for adiabatic processes or for slowly changing parameters. For example to ramp up the nonlinear term. """ name = "Ramper"
[docs] def __init__(self, start_time: float, ramp_period: float, update_parameters: Callable[[fr.ModelState, float], None] = \ lambda mz, ramped_value: None, # noqa: ARG005 ramp_function: ( Literal["exponential", "power_3", "cosine", "linear"] | Callable[[float], float] ) = "exponential", ) -> None: super().__init__() self.start_time = start_time self.ramp_period = ramp_period self.ramp_function = ramp_function self.update_parameters = update_parameters
[docs] @fr.modules.module_method def update(self, mz: fr.ModelState) -> fr.ModelState: # noqa: D102 # Check if the time is smaller than the start time if mz.clock.time < self.start_time: return mz # Check if the time is greater than the start time + ramp period if mz.clock.time > self.start_time + self.ramp_period: return mz # get the scaled time value theta = (mz.clock.time - self.start_time) / self.ramp_period # get the ramped value ramped_value = self.ramp_function(theta) # call the custom update parameters method self.update_parameters(mz, ramped_value) return mz
# ================================================================ # The different ramp functions # ================================================================
[docs] @staticmethod def exponential_ramp_func(theta: float) -> float: r""" Exponential ramp function. .. math:: f(\theta) = \frac{e^{-\frac{1}{\theta}}} {e^{-\frac{1}{\theta}} + e^{-\frac{1}{1-\theta}}} """ t1 = 1. / max(1e-32, theta) t2 = 1. / max(1e-32, 1. - theta) return np.exp(-t1) / (np.exp(-t1) + np.exp(-t2))
[docs] @staticmethod def power_3_ramp_func(theta: float) -> float: r""" Power 3 ramp function. .. math:: f(\theta) = \frac{\theta^3}{\theta^3 + (1 - \theta)^3} """ return theta**3 / (theta**3 + (1 - theta)**3)
[docs] @staticmethod def cosine_ramp_func(theta: float) -> float: r""" Cosine ramp function. .. math:: f(\theta) = \frac{1}{2} \left(1 - \cos(\pi \theta)\right) """ return 0.5 * (1 - np.cos(np.pi * theta))
[docs] @staticmethod def linear_ramp_func(theta: float) -> float: r""" Linear ramp function. .. math:: f(\theta) = \theta """ return theta
# ================================================================ # Properties # ================================================================ @property def info(self) -> dict[str, str]: # noqa: D102 res = super().info res["start_time"] = str(self.start_time) res["ramp_period"] = str(self.ramp_period) res["ramp_function"] = self._ramp_name return res @property def ramp_function(self) -> Callable[[float], float]: """ The ramp function. Parameters ---------- theta : float The scaled time value (between 0 and 1). Returns ------- float The ramped value (between 0 and 1). """ return self._ramp_func @ramp_function.setter def ramp_function(self, value: Literal["exponential", "power_3", "cosine", "linear"] | Callable[[float], float], ) -> None: # If the value is a callable, set the ramp function to that callable if callable(value): self._ramp_name = value.__name__ self._ramp_func = value return # Else, set the ramp function to the corresponding method self._ramp_name = value self._ramp_func = { "exponential": self.exponential_ramp_func, "power_3": self.power_3_ramp_func, "cosine": self.cosine_ramp_func, "linear": self.linear_ramp_func, }[value]