Skip to content

Commit

Permalink
♻️ Refactor model.from_dict to parse megacomplex_type from dict and a…
Browse files Browse the repository at this point in the history
…dd simple_generator for testing (#807)

* 👌 Refactor model.from_dict to use kwargs only for override; The keyword arguments megacomplex_types and default_megacomplex_type are only used for overwrites in testing

* ♻️ Refactor a simple DecayModel used in testing

* ✨🧪 Added plugin registry monkeypatch context managers for testing

* 🔧Add rich dependency (Amazing for debugging and printing complex objects)

*  🐛 Fix ThreeComponentParallel to be actually parallel

* 🔧🩹 Fixed  pydocstyle and darglint not picking up the testing module

* 🩹Adapted pytest benchmarks to new monkeypatch_plugin_registry

* 🩹 Ignore Codacy issue W0622 redefining built-in `print` when using `from rich import print`

Co-authored-by: Sebastian Weigand <s.weigand.phy@gmail.com>
Co-authored-by: Sourcery AI <>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 16, 2021
1 parent d74b0fb commit 2f1afe7
Show file tree
Hide file tree
Showing 13 changed files with 748 additions and 90 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ repos:
rev: 6.1.1
hooks:
- id: pydocstyle
files: "^glotaran/(plugin_system|utils|deprecation)"
exclude: "docs|tests?"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs|tests?/"
# this is needed due to the following issue:
# https://github.com/PyCQA/pydocstyle/issues/368
args: [--ignore-decorators=wrap_func_as_method]
Expand All @@ -87,14 +87,14 @@ repos:
rev: v1.8.0
hooks:
- id: darglint
files: "^glotaran/(plugin_system|utils|deprecation)"
exclude: "docs|tests?"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs|tests?/"

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910
hooks:
- id: mypy
files: "^glotaran/(plugin_system|utils|deprecation)"
files: "^glotaran/(plugin_system|utils|deprecation|testing)"
exclude: "docs"
additional_dependencies: [types-all]

Expand Down
2 changes: 2 additions & 0 deletions benchmark/pytest/analysis/test_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from glotaran.model import megacomplex
from glotaran.parameter import ParameterGroup
from glotaran.project import Scheme
from glotaran.testing.plugin_system import monkeypatch_plugin_registry

if TYPE_CHECKING:
from glotaran.model import DatasetModel
Expand Down Expand Up @@ -53,6 +54,7 @@ def finalize_data(
pass


@monkeypatch_plugin_registry(test_megacomplex={"benchmark": BenchmarkMegacomplex})
def setup_model(index_dependent):
model_dict = {
"megacomplex": {"m1": {"is_index_dependent": index_dependent}},
Expand Down
14 changes: 1 addition & 13 deletions glotaran/builtin/io/yml/yml.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from glotaran.io import save_dataset
from glotaran.io import save_parameters
from glotaran.model import Model
from glotaran.model import get_megacomplex
from glotaran.parameter import ParameterGroup
from glotaran.project import SavingOptions
from glotaran.project import Scheme
Expand Down Expand Up @@ -66,18 +65,7 @@ def load_model(self, file_name: str) -> Model:
if "megacomplex" not in spec:
raise ValueError("No megacomplex defined in model")

megacomplex_types = {
m["type"]: get_megacomplex(m["type"])
for m in spec["megacomplex"].values()
if "type" in m
}
if default_megacomplex is not None:
megacomplex_types[default_megacomplex] = get_megacomplex(default_megacomplex)
del spec["default-megacomplex"]

return Model.from_dict(
spec, megacomplex_types=megacomplex_types, default_megacomplex_type=default_megacomplex
)
return Model.from_dict(spec, megacomplex_types=None, default_megacomplex_type=None)

def load_parameters(self, file_name: str) -> ParameterGroup:

Expand Down
80 changes: 11 additions & 69 deletions glotaran/builtin/megacomplexes/decay/test/test_decay_megacomplex.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@

from glotaran.analysis.optimize import optimize
from glotaran.analysis.simulation import simulate
from glotaran.builtin.megacomplexes.decay import DecayMegacomplex
from glotaran.model import Megacomplex
from glotaran.model import Model
from glotaran.parameter import ParameterGroup
from glotaran.project import Scheme
from glotaran.testing.model_generators import SimpleModelGenerator


def _create_gaussian_clp(labels, amplitudes, centers, widths, axis):
Expand All @@ -28,20 +27,9 @@ class DecayModel(Model):
def from_dict(
cls,
model_dict,
*,
megacomplex_types: dict[str, type[Megacomplex]] | None = None,
default_megacomplex_type: str | None = None,
):
defaults: dict[str, type[Megacomplex]] = {
"decay": DecayMegacomplex,
}
if megacomplex_types is not None:
defaults.update(megacomplex_types)
return super().from_dict(
model_dict,
megacomplex_types=defaults,
default_megacomplex_type=default_megacomplex_type,
)
model_dict = {**model_dict, "default-megacomplex": "decay"}
return super().from_dict(model_dict)


class OneComponentOneChannel:
Expand Down Expand Up @@ -136,62 +124,16 @@ class OneComponentOneChannelGaussianIrf:


class ThreeComponentParallel:
model = DecayModel.from_dict(
{
"initial_concentration": {
"j1": {"compartments": ["s1", "s2", "s3"], "parameters": ["j.1", "j.1", "j.1"]},
},
"megacomplex": {
"mc1": {"k_matrix": ["k1"]},
},
"k_matrix": {
"k1": {
"matrix": {
("s2", "s1"): "kinetic.1",
("s3", "s2"): "kinetic.2",
("s3", "s3"): "kinetic.3",
}
}
},
"irf": {
"irf1": {
"type": "multi-gaussian",
"center": ["irf.center"],
"width": ["irf.width"],
},
},
"dataset": {
"dataset1": {
"initial_concentration": "j1",
"irf": "irf1",
"megacomplex": ["mc1"],
},
},
}
generator = SimpleModelGenerator(
rates=[300e-3, 500e-4, 700e-5],
irf={"center": 1.3, "width": 7.8},
k_matrix="parallel",
)
model, initial_parameters = generator.model_and_parameters

generator.rates = [301e-3, 502e-4, 705e-5]
wanted_parameters = generator.parameters

initial_parameters = ParameterGroup.from_dict(
{
"kinetic": [
["1", 300e-3],
["2", 500e-4],
["3", 700e-5],
],
"irf": [["center", 1.3], ["width", 7.8]],
"j": [["1", 1, {"vary": False, "non-negative": False}]],
}
)
wanted_parameters = ParameterGroup.from_dict(
{
"kinetic": [
["1", 301e-3],
["2", 502e-4],
["3", 705e-5],
],
"irf": [["center", 1.3], ["width", 7.8]],
"j": [["1", 1, {"vary": False, "non-negative": False}]],
}
)
time = np.arange(-10, 100, 1.5)
pixel = np.arange(600, 750, 10)

Expand Down
24 changes: 21 additions & 3 deletions glotaran/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import copy
from typing import Any
from typing import List
from warnings import warn

Expand All @@ -17,6 +18,7 @@
from glotaran.model.weight import Weight
from glotaran.parameter import Parameter
from glotaran.parameter import ParameterGroup
from glotaran.plugin_system.megacomplex_registration import get_megacomplex
from glotaran.utils.ipython import MarkdownStr

default_model_items = {
Expand Down Expand Up @@ -56,18 +58,34 @@ def __init__(
@classmethod
def from_dict(
cls,
model_dict: dict,
model_dict: dict[str, Any],
*,
megacomplex_types: dict[str, type[Megacomplex]],
megacomplex_types: dict[str, type[Megacomplex]] | None = None,
default_megacomplex_type: str | None = None,
) -> Model:
"""Creates a model from a dictionary.
Parameters
----------
model_dict :
model_dict: dict[str, Any]
Dictionary containing the model.
megacomplex_types: dict[str, type[Megacomplex]] | None
Overwrite 'megacomplex_types' in ``model_dict`` for testing.
default_megacomplex_type: str | None
Overwrite 'default-megacomplex' in ``model_dict`` for testing.
"""
if default_megacomplex_type is None:
default_megacomplex_type = model_dict.get("default-megacomplex")

if megacomplex_types is None:
megacomplex_types = {
m["type"]: get_megacomplex(m["type"])
for m in model_dict["megacomplex"].values()
if "type" in m
}
if default_megacomplex_type is not None:
megacomplex_types[default_megacomplex_type] = get_megacomplex(default_megacomplex_type)
model_dict.pop("default-megacomplex", None)

model = cls(
megacomplex_types=megacomplex_types, default_megacomplex_type=default_megacomplex_type
Expand Down
1 change: 1 addition & 0 deletions glotaran/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Testing framework package for glotaran itself and plugins."""
Loading

0 comments on commit 2f1afe7

Please sign in to comment.