Skip to content

Commit

Permalink
♻️ Serveral refactorings in glotaran.Parameter (#910)
Browse files Browse the repository at this point in the history
* Added Parameter to and from dict methods.

* Moved markdown related parameter group tests to test_parameter_group_rendering.py

* Refactored parameter tests.

* Added parameter to and from dictionary list.

* Added more extensive test for parameter csv.

* Added `as_optimized` to parameter(group) as dict and dataframe functions.

* Fixed deprecation test.

* Added `Parameter.markdown`

* Update glotaran/parameter/parameter_group.py

Fix typo

* ♻️ Refactored by Sourcery (less if nesting)

* 🩹 Fixed renamed method get_group_for_parameter_by_label only in def

* 🧹 Removed redundant int since mypy sees int as a subclass of float

* ♻️ Changed stderr type from 'float|None' to float and default to np.nan

* 🩹🧪 Fixed tests due to changed rendering

* 👌 Improved typing where dict and list were used w/o types

* 🧹👌 Fixed up leftover tying issues in parameters

* 🧹 Fixed typo

* 🧹 Apply minor naming suggestions from code review

Co-authored-by: Sourcery AI <>
Co-authored-by: s-weigand <s.weigand.phy@gmail.com>
  • Loading branch information
joernweissenborn and s-weigand authored Nov 22, 2021
1 parent 2d44c75 commit f71a34b
Show file tree
Hide file tree
Showing 7 changed files with 669 additions and 413 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
from glotaran.examples.sequential import parameter


def test_parameter_group_to_csv(tmp_path: Path):
def test_parameter_group_to_csv_no_stderr(tmp_path: Path):
"""``ParameterGroup.to_csv`` raises deprecation warning and saves file."""
parameter_path = tmp_path / "test_parameter.csv"
deprecation_warning_on_call_test_helper(
parameter.to_csv, args=[parameter_path.as_posix()], raise_exception=True
)
expected = dedent(
"""\
label,value,minimum,maximum,vary,non-negative,expression
j.1,1.0,-inf,inf,False,False,None
j.0,0.0,-inf,inf,False,False,None
kinetic.1,0.5,-inf,inf,True,False,None
kinetic.2,0.3,-inf,inf,True,False,None
kinetic.3,0.1,-inf,inf,True,False,None
irf.center,0.3,-inf,inf,True,False,None
irf.width,0.1,-inf,inf,True,False,None
label,value,expression,minimum,maximum,non-negative,vary,standard-error
j.1,1.0,None,-inf,inf,False,False,None
j.0,0.0,None,-inf,inf,False,False,None
kinetic.1,0.5,None,-inf,inf,False,True,None
kinetic.2,0.3,None,-inf,inf,False,True,None
kinetic.3,0.1,None,-inf,inf,False,True,None
irf.center,0.3,None,-inf,inf,False,True,None
irf.width,0.1,None,-inf,inf,False,True,None
"""
)

Expand Down
10 changes: 5 additions & 5 deletions glotaran/model/test/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,9 +514,9 @@ def test_model_markdown():
* **k1**:
* *Label*: k1
* *Matrix*:
* *('s2', 's1')*: rates.1: **5.01000e-01** *(StdErr: 0e+00)*
* *('s3', 's2')*: rates.2: **2.02000e-02** *(StdErr: 0e+00)*
* *('s3', 's3')*: rates.3: **1.05000e-03** *(StdErr: 0e+00)*
* *('s2', 's1')*: rates.1: **5.01000e-01** *(StdErr: nan)*
* *('s3', 's2')*: rates.2: **2.02000e-02** *(StdErr: nan)*
* *('s3', 's3')*: rates.3: **1.05000e-03** *(StdErr: nan)*
## Initial Concentration
Expand All @@ -532,8 +532,8 @@ def test_model_markdown():
* **irf1** (multi-gaussian):
* *Label*: irf1
* *Type*: multi-gaussian
* *Center*: [irf.center: **1.30000e+00** *(StdErr: 0e+00)*]
* *Width*: [irf.width: **7.80000e+00** *(StdErr: 0e+00)*]
* *Center*: [irf.center: **1.30000e+00** *(StdErr: nan)*]
* *Width*: [irf.width: **7.80000e+00** *(StdErr: nan)*]
* *Normalize*: True
* *Backsweep*: False
Expand Down
123 changes: 111 additions & 12 deletions glotaran/parameter/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import numpy as np
from numpy.typing._array_like import _SupportsArray

from glotaran.utils.ipython import MarkdownStr
from glotaran.utils.sanitize import sanitize_parameter_list

if TYPE_CHECKING:
Expand Down Expand Up @@ -42,11 +43,12 @@ def __init__(
self,
label: str = None,
full_label: str = None,
expression: str = None,
maximum: int | float = np.inf,
minimum: int | float = -np.inf,
expression: str | None = None,
maximum: float = np.inf,
minimum: float = -np.inf,
non_negative: bool = False,
value: float | int = np.nan,
standard_error: float = np.nan,
value: float = np.nan,
vary: bool = True,
):
"""Optimization Parameter supporting numpy array operations.
Expand All @@ -58,15 +60,17 @@ def __init__(
full_label : str
The label of the parameter with its path in a parameter group prepended.
, by default None
expression : str
expression : str | None
Expression to calculate the parameters value from,
e.g. if used in relation to another parameter. , by default None
maximum : int
maximum : float
Upper boundary for the parameter to be varied to., by default np.inf
minimum : int
minimum : float
Lower boundary for the parameter to be varied to., by default -np.inf
non_negative : bool
Whether the parameter should always be bigger than zero., by default False
standard_error: float
The standard error of the parameter. , by default ``np.nan``
value : float
Value of the parameter, by default np.nan
vary : bool
Expand All @@ -79,7 +83,7 @@ def __init__(
self.maximum = maximum
self.minimum = minimum
self.non_negative = non_negative
self.standard_error = 0.0
self.standard_error = standard_error
self.value = value
self.vary = vary

Expand Down Expand Up @@ -144,6 +148,57 @@ def from_list_or_value(
param._set_options_from_dict(options)
return param

@classmethod
def from_dict(cls, parameter_dict: dict[str, Any]) -> Parameter:
"""Create a :class:`Parameter` from a dictionary.
Expects a dictionary created by :method:`Parameter.as_dict`.
Parameters
----------
parameter_dict : dict[str, Any]
The source dictionary.
Returns
-------
Parameter
The created :class:`Parameter`
"""
parameter_dict = {k.replace("-", "_"): v for k, v in parameter_dict.items()}
parameter_dict["full_label"] = parameter_dict["label"]
parameter_dict["label"] = parameter_dict["label"].split(".")[-1]
return cls(**parameter_dict)

def as_dict(self, as_optimized: bool = True) -> dict[str, Any]:
"""Create a dictionary containing the parameter properties.
Note:
-----
Intended for internal use.
Parameters
----------
as_optimized : bool
Whether to include properties which are the result of optimization.
Returns
-------
dict[str, Any]
The created dictionary.
"""
parameter_dict = {
"label": self.full_label,
"value": self.value,
"expression": self.expression,
"minimum": self.minimum,
"maximum": self.maximum,
"non-negative": self.non_negative,
"vary": self.vary,
}
if as_optimized:
parameter_dict["standard-error"] = self.standard_error
return parameter_dict

def set_from_group(self, group: ParameterGroup):
"""Set all values of the parameter to the values of the corresponding parameter in the group.
Expand All @@ -165,12 +220,12 @@ def set_from_group(self, group: ParameterGroup):
self.value = p.value
self.vary = p.vary

def _set_options_from_dict(self, options: dict):
def _set_options_from_dict(self, options: dict[str, Any]):
"""Set the parameter's options from a dictionary.
Parameters
----------
options : dict
options : dict[str, Any]
A dictionary containing parameter options.
"""
if Keys.EXPR in options:
Expand Down Expand Up @@ -220,7 +275,7 @@ def full_label(self, full_label: str):

@property
def non_negative(self) -> bool:
r"""Indicate if the parameter is non-negativ.
r"""Indicate if the parameter is non-negative.
If true, the parameter will be transformed with :math:`p' = \log{p}` and
:math:`p = \exp{p'}`.
Expand All @@ -232,7 +287,7 @@ def non_negative(self) -> bool:
Returns
-------
bool
Whether the parameter is non-negativ.
Whether the parameter is non-negative.
"""
return self._non_negative if self.expression is None else False

Expand Down Expand Up @@ -409,6 +464,50 @@ def set_value_from_optimization(self, value: float):
"""
self.value = np.exp(value) if self.non_negative else value

def markdown(
self,
all_parameter: ParameterGroup | None = None,
initial_parameter: ParameterGroup | None = None,
) -> MarkdownStr:
"""Get a markdown representation of the parameter.
Parameters
----------
all_parameter : ParameterGroup | None
A parameter group containing the whole parameter set (used for expression lookup).
initial_parameter : ParameterGroup | None
The initial parameter.
Returns
-------
MarkdownStr
The parameter as markdown string.
"""
md = f"{self.full_label}"

value = f"{self.value:.2e}"
if self.vary:
if self.standard_error is not np.nan:
value += f"±{self.standard_error}"
if initial_parameter is not None:
initial_value = initial_parameter.get(self.full_label).value
value += f", initial: {initial_value:.2e}"
md += f"({value})"
elif self.expression is not None:
expression = self.expression
if all_parameter is not None:
for match in PARAMETER_EXPRESION_REGEX.findall(expression):
label = match[0]
parameter = all_parameter.get(label)
expression = expression.replace(
"$" + label, f"_{parameter.markdown(all_parameter=all_parameter)}_"
)
md += f"({value}={expression})"
else:
md += f"({value}, fixed)"

return MarkdownStr(md)

def __getstate__(self):
"""Get state for pickle."""
return (
Expand Down
Loading

0 comments on commit f71a34b

Please sign in to comment.