Source code for lightwin.optimisation.design_space.design_space_parameter

"""Create a base class for :class:`.Variable` and :class:`.Constraint`."""

import math
from abc import ABC
from dataclasses import dataclass
from typing import Self

import numpy as np
import pandas as pd

from lightwin.util.dicts_output import markdown


[docs] @dataclass class DesignSpaceParameter(ABC): """Hold a single variable or constraint. Parameters ---------- name : Name of the parameter. Must be compatible with the :meth:`.SimulationOutput.get` method, and be in :data:`.IMPLEMENTED_VARIABLES` or :data:`.IMPLEMENTED_CONSTRAINTS`. element_name : Name of the element concerned by the parameter. limits : Lower and upper bound for the variable. ``np.nan`` deactivates a bound. """ name: str element_name: str limits: tuple[float, float]
[docs] @classmethod def from_floats( cls, name: str, element_name: str, x_min: float, x_max: float, x_0: float = np.nan, ) -> Self: """Initialize object with ``x_min``, ``x_max`` instead of ``limits``. Parameters ---------- name : Name of the parameter. Must be compatible with the :meth:`.SimulationOutput.get` method, and be in :data:`.IMPLEMENTED_VARIABLES` or :data:`.IMPLEMENTED_CONSTRAINTS`. element_name : Name of the element concerned by the parameter. x_min : Lower limit. ``np.nan`` to deactivate lower bound. x_max : Upper limit. ``np.nan`` to deactivate lower bound. Returns ------- A DesignSpaceParameter with limits = (x_min, x_max). """ return cls(name, element_name, (x_min, x_max))
[docs] @classmethod def from_pd_series( cls, name: str, element_name: str, pd_series: pd.Series ) -> Self: """Init object from a pd series (file import).""" x_min = pd_series.loc[f"{name}: x_min"] x_max = pd_series.loc[f"{name}: x_max"] return cls.from_floats(name, element_name, x_min, x_max)
[docs] def __post_init__(self): """Convert values in deg for output if it is angle.""" self._to_deg = False self._to_numpy = False
@property def x_min(self) -> float: """Return lower variable/constraint bound.""" return self.limits[0] @property def x_max(self) -> float: """Return upper variable/constraint bound.""" return self.limits[1]
[docs] def change_limits( self, x_min: float | None = None, x_max: float | None = None ) -> None: """Change the limits after creation of the object.""" self.limits = ( x_min if x_min is not None else self.x_min, x_max if x_max is not None else self.x_max, )
@property def _fmt_x_min(self) -> float: """Lower limit in deg if it is has ``'phi'`` in it's name.""" if "phi" in self.name: return math.degrees(self.x_min) return self.x_min @property def _fmt_x_max(self) -> float: """Lower limit in deg if it is has ``'phi'`` in it's name.""" if "phi" in self.name: return math.degrees(self.x_max) return self.x_max @property def _fmt_x_0(self) -> float: """Initial value but with a better output.""" assert hasattr(self, "x_0"), ( "This design space parameter has no " "attribute x_0. Maybe you took a Contraint for a Variable?" ) x_0 = getattr(self, "x_0") if "phi" in self.name: return math.degrees(x_0) return x_0
[docs] def __str__(self) -> str: """Output parameter name and limits.""" out = f"{markdown[self.name]:25} | {self.element_name:15} | " if hasattr(self, "x_0"): out += f"{self._fmt_x_0:>8.3f} | " else: out += " | " out += f"{self._fmt_x_min:>9.3f} | {self._fmt_x_max:>9.3f}" return out
[docs] @classmethod def header_of__str__(cls) -> str: """Give information on what :func:`__str__` is about.""" header = f"{cls.__name__:<25} | {'Element':<15} | {'x_0':<8} | " header += f"{'Lower lim':<9} | {'Upper lim':<9}" return header
[docs] def to_dict( self, *to_get: str, missing_value: float | None = None, prepend_parameter_name: bool = False, ) -> dict[str, float | None | tuple[float, float] | str]: """Convert important data to dict to convert it later in pandas df.""" out = { attribute: getattr(self, attribute, missing_value) for attribute in to_get } if not prepend_parameter_name: return out return {f"{self.name}: {key}": value for key, value in out.items()}