Source code for glotaran.model.base_model

"""A base class for global analysis models."""
from __future__ import annotations

import copy
import inspect

import numpy as np
import xarray as xr

from glotaran.analysis.simulation import simulate
from glotaran.parameter import ParameterGroup


[docs]class Model: """A base class for global analysis models."""
[docs] @classmethod def from_dict(cls, model_dict_ref: dict) -> Model: """Creates a model from a dictionary. Parameters ---------- model_dict : Dictionary containing the model. """ model = cls() model_dict = copy.deepcopy(model_dict_ref) # iterate over items for name, attribute in list(model_dict.items()): # we determine if we the item is known by the model by looking for # a setter with same name. if hasattr(model, f"set_{name}"): # get the set function model_set = getattr(model, f"set_{name}") # we retrieve the actual class from the signature for label, item in attribute.items(): item_cls = model_set.__func__.__annotations__["item"] is_typed = hasattr(item_cls, "_glotaran_model_attribute_typed") if isinstance(item, dict): if is_typed: if "type" not in item: raise ValueError(f"Missing type for attribute '{name}'") item_type = item["type"] if item_type not in item_cls._glotaran_model_attribute_types: raise ValueError( f"Unknown type '{item_type}' for attribute '{name}'" ) item_cls = item_cls._glotaran_model_attribute_types[item_type] item["label"] = label model_set(label, item_cls.from_dict(item)) elif isinstance(item, list): if is_typed: if len(item) < 2 and len(item) != 1: raise ValueError(f"Missing type for attribute '{name}'") item_type = ( item[1] if len(item) != 1 and hasattr(item_cls, "label") else item[0] ) if item_type not in item_cls._glotaran_model_attribute_types: raise ValueError( f"Unknown type '{item_type}' for attribute '{name}'" ) item_cls = item_cls._glotaran_model_attribute_types[item_type] item = [label] + item model_set(label, item_cls.from_list(item)) del model_dict[name] elif hasattr(model, f"add_{name}"): # get the set function add = getattr(model, f"add_{name}") # we retrieve the actual class from the signature for item in attribute: item_cls = add.__func__.__annotations__["item"] is_typed = hasattr(item_cls, "_glotaran_model_attribute_typed") if isinstance(item, dict): if is_typed: if "type" not in item: raise ValueError(f"Missing type for attribute '{name}'") item_type = item["type"] if item_type not in item_cls._glotaran_model_attribute_types: raise ValueError( f"Unknown type '{item_type}' for attribute '{name}'" ) item_cls = item_cls._glotaran_model_attribute_types[item_type] add(item_cls.from_dict(item)) elif isinstance(item, list): if is_typed: if len(item) < 2 and len(item) != 1: raise ValueError(f"Missing type for attribute '{name}'") item_type = ( item[1] if len(item) != 1 and hasattr(item_cls, "label") else item[0] ) if item_type not in item_cls._glotaran_model_attribute_types: raise ValueError( f"Unknown type '{item_type}' for attribute '{name}'" ) item_cls = item_cls._glotaran_model_attribute_types[item_type] add(item_cls.from_list(item)) del model_dict[name] return model
@property def index_dependent_matrix(self): return len(inspect.signature(self.matrix).parameters) == 3 @property def model_type(self) -> str: """The type of the model as human readable string.""" return self._model_type
[docs] def simulate( self, dataset: str, parameters: ParameterGroup, axes: dict[str, np.ndarray] = None, clp: np.ndarray | xr.DataArray = None, noise: bool = False, noise_std_dev: float = 1.0, noise_seed: int = None, ) -> xr.Dataset: """Simulates the model. Parameters ---------- dataset : Label of the dataset to simulate. parameter : The parameters for the simulation. axes : A dictionary with axes for simulation. clp : Conditionally linear parameters. Used instead of `model.global_matrix` if provided. noise : If `True` noise is added to the simulated data. noise_std_dev : The standard deviation of the noise. noise_seed : Seed for the noise. """ return simulate( self, dataset, parameters, axes=axes, clp=clp, noise=noise, noise_std_dev=noise_std_dev, noise_seed=noise_seed, )
[docs] def problem_list(self, parameters: ParameterGroup = None) -> list[str]: """ Returns a list with all problems in the model and missing parameters if specified. Parameters ---------- parameter : The parameter to validate. """ problems = [] attrs = getattr(self, "_glotaran_model_attributes") for attr in attrs: attr = getattr(self, attr) if isinstance(attr, list): for item in attr: problems += item.validate(self, parameters=parameters) else: for _, item in attr.items(): problems += item.validate(self, parameters=parameters) return problems
[docs] def validate(self, parameters: ParameterGroup = None) -> str: """ Returns a string listing all problems in the model and missing parameters if specified. Parameters ---------- parameter : The parameter to validate. """ result = "" problems = self.problem_list(parameters) if problems: result = f"Your model has {len(problems)} problems:\n" for p in problems: result += f"\n * {p}" else: result = "Your model is valid." return result
[docs] def valid(self, parameters: ParameterGroup = None) -> bool: """Returns `True` if the number problems in the model is 0, else `False` Parameters ---------- parameter : The parameter to validate. """ return len(self.problem_list(parameters)) == 0
[docs] def markdown( self, parameters: ParameterGroup = None, initial_parameters: ParameterGroup = None ) -> str: """Formats the model as Markdown string. Parameters will be included if specified. Parameters ---------- parameter : Parameter to include. initial : Initial values for the parameters. """ attrs = getattr(self, "_glotaran_model_attributes") string = "# Model\n\n" string += f"_Type_: {self.model_type}\n\n" for attr in attrs: items = getattr(self, attr) if not items: continue string += f"## {attr.replace('_', ' ').title()}\n" string += "\n" if isinstance(items, dict): items = items.values() for item in items: item_str = item.mprint( parameters=parameters, initial_parameters=initial_parameters ).split("\n") string += f"* {item_str[0]}\n" for s in item_str[1:]: string += f" {s}\n" string += "\n" return string
def __str__(self): return self.markdown()