Skip to content

Commit

Permalink
♻️ Renamed module and cleaned up docstrings
Browse files Browse the repository at this point in the history
♻️ Rename simple_generator to model_generators; its purpose is to generate models (together with the parameters specification) based on parameter input values the user assigns to the instance of the model generator

📚 Added darglint compatible docstrings everywhere
  • Loading branch information
jsnel committed Sep 15, 2021
1 parent ffc4f85 commit c18f791
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from glotaran.model import Model
from glotaran.parameter import ParameterGroup
from glotaran.project import Scheme
from glotaran.testing.simple_generator import SimpleGenerator
from glotaran.testing.model_generators import SimpleModelGenerator


def _create_gaussian_clp(labels, amplitudes, centers, widths, axis):
Expand Down Expand Up @@ -124,7 +124,7 @@ class OneComponentOneChannelGaussianIrf:


class ThreeComponentParallel:
generator = SimpleGenerator(
generator = SimpleModelGenerator(
rates=[300e-3, 500e-4, 700e-5],
irf={"center": 1.3, "width": 7.8},
k_matrix="parallel",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Simple generators for model and parameters."""
"""Model generators used to generate simple models from a set of inputs."""

from __future__ import annotations

Expand All @@ -14,29 +14,53 @@
from glotaran.utils.ipython import MarkdownStr


def _split_iterable_in_values_and_dicts(
input_list: list,
def _split_iterable_in_non_dict_and_dict_items(
input_list: list[float, dict[str, bool | float]],
) -> tuple[list[float], list[dict[str, bool | float]]]:
"""Split an iterable (list) into non-dict and dict items.
Parameters
----------
input_list : list[float, dict[str, bool | float]]
A list of values of type `float` and a dict with parameter options, e.g.
`[1, 2, 3, {"vary": False, "non-negative": True}]`
Returns
-------
tuple[list[float], list[dict[str, bool | float]]]
Split a list into non-dict (`values`) and dict items (`defaults`),
return a tuple (`values`, `defaults`)
"""
values: list = [val for val in input_list if not isinstance(val, dict)]
defaults: list = [val for val in input_list if isinstance(val, dict)]
return values, defaults


@dataclass
class SimpleGenerator:
"""A minimal boilerplate model and parameters generator."""
class SimpleModelGenerator:
"""A minimal boilerplate model and parameters generator.
Generates a model (together with the parameters specification) based on
parameter input values assigned to the generator's attributes
"""

rates: list[float] = field(default_factory=list)
"""A list of values representing decay rates"""
k_matrix: Literal["parallel", "sequential"] | dict[tuple[str, str], str] = "parallel"
""""A `dict` with a k_matrix specification or `Literal["parallel", "sequential"]`"""
compartments: list[str] | None = None
"""A list of compartment names"""
irf: dict[str, float] = field(default_factory=dict)
"""A dict of items specifying an irf"""
initial_concentration: list[float] = field(default_factory=list)
"""A list values representing the initial concentration"""
dispersion_coefficients: list[float] = field(default_factory=list)
"""A list of values representing the dispersion coefficients"""
dispersion_center: float | None = None
"""A value representing the dispersion center"""
default_megacomplex: str = "decay"
# TODO: add:
"""The default_megacomplex identifier"""
# TODO: add support for a spectral model:
# shapes: list[float] = field(default_factory=list, init=False)

@property
Expand Down Expand Up @@ -122,15 +146,34 @@ def model_and_parameters(self) -> tuple[Model, ParameterGroup]:

@property
def _rates(self) -> tuple[list[float], list[dict[str, bool | float]]]:
"""Validate input to rates, return a tuple of rates and parameter defaults.
Returns
-------
tuple[list[float], list[dict[str, bool | float]]]
A tuple of a list of rates and a dict containing parameter defaults
Raises
------
ValueError
Raised if rates is not a list of at least one number.
"""
if not isinstance(self.rates, list):
raise ValueError(f"generator.rates: must be a `list`, got: {self.rates}")
if len(self.rates) == 0:
raise ValueError("generator.rates: must be a `list` with 1 or more rates")
if not isinstance(self.rates[0], (int, float)):
raise ValueError(f"generator.rates: 1st element must be numeric, got: {self.rates[0]}")
return _split_iterable_in_values_and_dicts(self.rates)
return _split_iterable_in_non_dict_and_dict_items(self.rates)

def _parameters_dict_items(self) -> dict:
"""Return a dict with items used in constructing the parameters.
Returns
-------
dict
A dict with items used in constructing a parameters dict.
"""
rates, rates_defaults = self._rates
items = {"rates": rates}
if rates_defaults:
Expand Down Expand Up @@ -160,6 +203,13 @@ def _parameters_dict_items(self) -> dict:
return items

def _model_dict_items(self) -> dict:
"""Return a dict with items used in constructing the model.
Returns
-------
dict
A dict with items used in constructing a model dict.
"""
rates, _ = self._rates
nr = len(rates)
indices = list(range(1, 1 + nr))
Expand Down Expand Up @@ -192,6 +242,13 @@ def _model_dict_items(self) -> dict:
return items

def _parameters_dict(self) -> dict:
"""Return a parameters dict.
Returns
-------
dict
A dict that can be passed to the `ParameterGroup` `from_dict` method.
"""
items = self._parameters_dict_items()
rates = items["rates"]
if "rates_defaults" in items:
Expand All @@ -203,6 +260,13 @@ def _parameters_dict(self) -> dict:
return result

def _model_dict(self) -> dict:
"""Return a model dict.
Returns
-------
dict
A dict that can be passed to the `Model` `from_dict` method.
"""
items = self._model_dict_items()
result = {"default-megacomplex": items["default-megacomplex"]}
result.update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from glotaran.model import Model
from glotaran.parameter import ParameterGroup
from glotaran.testing.simple_generator import SimpleGenerator
from glotaran.testing.model_generators import SimpleModelGenerator

pretty.install()

Expand Down Expand Up @@ -69,10 +69,10 @@ def simple_diff_between_string(string1, string2):
return "".join(c2 for c1, c2 in zip(string1, string2) if c1 != c2)


def test_simple_model_3comp_seq():
def test_three_component_sequential_model():
ref_model = Model.from_dict(deepcopy(REF_MODEL_DICT))
ref_parameters = ParameterGroup.from_dict(deepcopy(REF_PARAMETER_DICT))
generator = SimpleGenerator(
generator = SimpleModelGenerator(
rates=[501e-3, 202e-4, 105e-5, {"non-negative": True}],
irf={"center": 1.3, "width": 7.8},
k_matrix="sequential",
Expand All @@ -91,29 +91,29 @@ def test_simple_model_3comp_seq():


def test_only_rates_no_irf():
generator = SimpleGenerator(rates=[0.1, 0.02, 0.003])
generator = SimpleModelGenerator(rates=[0.1, 0.02, 0.003])
assert "irf" not in generator.model_dict.keys()


def test_no_rates():
generator = SimpleGenerator()
generator = SimpleModelGenerator()
assert generator.valid is False


def test_one_rate():
generator = SimpleGenerator([1])
generator = SimpleModelGenerator([1])
assert generator.valid is True
assert "is valid" in generator.validate()


def test_rates_not_a_list():
generator = SimpleGenerator(1)
generator = SimpleModelGenerator(1)
assert generator.valid is False
with pytest.raises(ValueError):
print(generator.validate())


def test_set_rates_delayed():
generator = SimpleGenerator()
generator = SimpleModelGenerator()
generator.rates = [1, 2, 3]
assert generator.valid is True

0 comments on commit c18f791

Please sign in to comment.