From 5cc1cb8fb651aea46bf401041703836851397b94 Mon Sep 17 00:00:00 2001 From: Joern Weissenborn Date: Tue, 17 Oct 2023 22:39:28 +0200 Subject: [PATCH 01/17] Autoconvert to pydantic 2. --- glotaran/io/preprocessor/preprocessor.py | 6 ++---- glotaran/model/experiment_model.py | 8 ++------ glotaran/model/item.py | 8 ++------ glotaran/model/test/test_item.py | 12 ++++++------ glotaran/optimization/result.py | 8 ++------ glotaran/project/result.py | 8 ++------ glotaran/project/scheme.py | 8 ++------ setup.cfg | 2 +- 8 files changed, 19 insertions(+), 41 deletions(-) diff --git a/glotaran/io/preprocessor/preprocessor.py b/glotaran/io/preprocessor/preprocessor.py index 6d918a96e..edd9a002c 100644 --- a/glotaran/io/preprocessor/preprocessor.py +++ b/glotaran/io/preprocessor/preprocessor.py @@ -6,15 +6,13 @@ import xarray as xr from pydantic import BaseModel +from pydantic import ConfigDict class PreProcessor(BaseModel, abc.ABC): """A base class for pre=processors.""" - class Config: - """Config for BaseModel.""" - - arbitrary_types_allowed = True + model_config = ConfigDict(arbitrary_types_allowed=True) @abc.abstractmethod def apply(self, data: xr.DataArray) -> xr.DataArray: diff --git a/glotaran/model/experiment_model.py b/glotaran/model/experiment_model.py index c9073555e..c24f4bff1 100644 --- a/glotaran/model/experiment_model.py +++ b/glotaran/model/experiment_model.py @@ -5,7 +5,7 @@ from typing import Literal from pydantic import BaseModel -from pydantic import Extra +from pydantic import ConfigDict from pydantic import Field from glotaran.model.clp_constraint import ClpConstraint @@ -25,11 +25,7 @@ class ExperimentModel(BaseModel): """A dataset group for optimization.""" - class Config: - """Config for pydantic.BaseModel.""" - - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") clp_link_tolerance: float = 0.0 clp_link_method: Literal["nearest", "backward", "forward"] = "nearest" diff --git a/glotaran/model/item.py b/glotaran/model/item.py index 70899573c..e2dbea7f5 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -20,7 +20,7 @@ from typing import get_origin from pydantic import BaseModel -from pydantic import Extra +from pydantic import ConfigDict from pydantic import Field from pydantic.fields import FieldInfo from pydantic.fields import ModelField # type:ignore[attr-defined] @@ -105,11 +105,7 @@ def Attribute( class Item(BaseModel): """A baseclass for items.""" - class Config: - """Config for pydantic.BaseModel.""" - - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") class TypedItem(Item): diff --git a/glotaran/model/test/test_item.py b/glotaran/model/test/test_item.py index cc52250ef..def2e55df 100644 --- a/glotaran/model/test/test_item.py +++ b/glotaran/model/test/test_item.py @@ -15,17 +15,17 @@ class MockItem(Item): cscalar: int - cscalar_option: int | None + cscalar_option: int | None = None clist: list[int] - clist_option: list[int] | None + clist_option: list[int] | None = None cdict: dict[str, int] - cdict_option: dict[str, int] | None + cdict_option: dict[str, int] | None = None pscalar: ParameterType - pscalar_option: ParameterType | None + pscalar_option: ParameterType | None = None plist: list[ParameterType] - plist_option: list[ParameterType] | None + plist_option: list[ParameterType] | None = None pdict: dict[str, ParameterType] - pdict_option: dict[str, ParameterType] | None + pdict_option: dict[str, ParameterType] | None = None class MockTypedItem(TypedItem): diff --git a/glotaran/optimization/result.py b/glotaran/optimization/result.py index 6cefcab6e..200834470 100644 --- a/glotaran/optimization/result.py +++ b/glotaran/optimization/result.py @@ -3,7 +3,7 @@ import numpy as np from pydantic import BaseModel -from pydantic import Extra +from pydantic import ConfigDict from scipy.optimize import OptimizeResult # TODO: Fix circular import @@ -15,11 +15,7 @@ class OptimizationResult(BaseModel): - class Config: - """Config for pydantic.BaseModel.""" - - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") """The result of a global analysis.""" diff --git a/glotaran/project/result.py b/glotaran/project/result.py index 20f5564a4..948fc1cdf 100644 --- a/glotaran/project/result.py +++ b/glotaran/project/result.py @@ -4,7 +4,7 @@ import xarray as xr from pydantic import BaseModel -from pydantic import Extra +from pydantic import ConfigDict from glotaran.builtin.io.yml.utils import write_dict from glotaran.io import save_dataset @@ -27,11 +27,7 @@ class SavingOptions(BaseModel): class Result(BaseModel): - class Config: - """Config for pydantic.BaseModel.""" - - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") data: dict[str, xr.Dataset] experiments: dict[str, ExperimentModel] diff --git a/glotaran/project/scheme.py b/glotaran/project/scheme.py index f890f283a..34c432191 100644 --- a/glotaran/project/scheme.py +++ b/glotaran/project/scheme.py @@ -2,7 +2,7 @@ import xarray as xr from pydantic import BaseModel -from pydantic import Extra +from pydantic import ConfigDict from glotaran.io import load_dataset from glotaran.model import ExperimentModel @@ -15,11 +15,7 @@ class Scheme(BaseModel): - class Config: - """Config for pydantic.BaseModel.""" - - arbitrary_types_allowed = True - extra = Extra.forbid + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") experiments: dict[str, ExperimentModel] library: LibraryType diff --git a/setup.cfg b/setup.cfg index 0b4246d8c..f7b685ac8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ install_requires = odfpy>=1.4.1 openpyxl>=3.0.10 pandas>=1.3.4 - pydantic>=1.10.2 + pydantic>=2.0 ruamel.yaml>=0.17.17 scipy>=1.7.2 sdtfile>=2020.8.3 From b76c182fa7f1b300d692eb7745c4e3abdc752d2e Mon Sep 17 00:00:00 2001 From: Joern Weissenborn Date: Tue, 17 Oct 2023 22:46:11 +0200 Subject: [PATCH 02/17] Replace missing clasing with any. --- glotaran/model/item.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/glotaran/model/item.py b/glotaran/model/item.py index e2dbea7f5..d0ec5081f 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -23,20 +23,25 @@ from pydantic import ConfigDict from pydantic import Field from pydantic.fields import FieldInfo -from pydantic.fields import ModelField # type:ignore[attr-defined] -from pydantic.fields import Undefined # type:ignore[attr-defined] from glotaran.model.errors import ItemIssue from glotaran.model.errors import ParameterIssue from glotaran.parameter import Parameter from glotaran.parameter import Parameters +# from pydantic.fields import ModelField # type:ignore[attr-defined] +# from pydantic.fields import Undefined # type:ignore[attr-defined] + + ItemT = TypeVar("ItemT", bound="Item") ParameterType: TypeAlias = Parameter | float | str META_VALIDATOR = "__glotaran_validator__" +ModelField = Any +Undefined = Any + class ItemAttribute(FieldInfo): """An attribute for items. From 4ab600ae860b53f0005df47ac12d7ac7df731fa3 Mon Sep 17 00:00:00 2001 From: Joern Weissenborn Date: Tue, 17 Oct 2023 23:08:14 +0200 Subject: [PATCH 03/17] Adapted typing for pydantic 2. --- glotaran/builtin/elements/baseline/element.py | 4 ++-- glotaran/builtin/elements/clp_guide/element.py | 4 ++-- glotaran/builtin/elements/coherent_artifact/element.py | 7 ++++--- glotaran/builtin/elements/damped_oscillation/element.py | 7 +++++-- glotaran/builtin/elements/kinetic/element.py | 5 +++-- glotaran/builtin/elements/spectral/element.py | 4 ++-- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/glotaran/builtin/elements/baseline/element.py b/glotaran/builtin/elements/baseline/element.py index f09ddb484..e1f5f45d7 100644 --- a/glotaran/builtin/elements/baseline/element.py +++ b/glotaran/builtin/elements/baseline/element.py @@ -15,8 +15,8 @@ class BaselineElement(Element): type: Literal["baseline"] # type:ignore[assignment] - register_as = "baseline" # type:ignore[pydantic-field] - unique = True # type:ignore[pydantic-field] + register_as: str = "baseline" # type:ignore[misc] + unique: bool = True def clp_label(self) -> str: return f"baseline_{self.label}" diff --git a/glotaran/builtin/elements/clp_guide/element.py b/glotaran/builtin/elements/clp_guide/element.py index 260fad8a5..7bc6fda82 100644 --- a/glotaran/builtin/elements/clp_guide/element.py +++ b/glotaran/builtin/elements/clp_guide/element.py @@ -14,8 +14,8 @@ class ClpGuideElement(Element): type: Literal["clp-guide"] # type:ignore[assignment] - register_as = "clp-guide" # type:ignore[pydantic-field] - exclusive = True # type:ignore[pydantic-field] + register_as: str = "clp-guide" # type:ignore[misc] + exclusive: bool = True target: str def calculate_matrix( diff --git a/glotaran/builtin/elements/coherent_artifact/element.py b/glotaran/builtin/elements/coherent_artifact/element.py index 15dfd7a31..03a9283fc 100644 --- a/glotaran/builtin/elements/coherent_artifact/element.py +++ b/glotaran/builtin/elements/coherent_artifact/element.py @@ -13,6 +13,7 @@ from glotaran.model import Element from glotaran.model import GlotaranModelError from glotaran.model import ParameterType +from glotaran.model.data_model import DataModel if TYPE_CHECKING: from glotaran.typing.types import ArrayLike @@ -20,9 +21,9 @@ class CoherentArtifactElement(Element): type: Literal["coherent-artifact"] # type:ignore[assignment] - register_as = "coherent-artifact" # type:ignore[pydantic-field] - dimension = "time" # type:ignore[pydantic-field] - data_model_type = ActivationDataModel # type:ignore[pydantic-field] + register_as: str = "coherent-artifact" # type:ignore[misc] + dimension: str = "time" + data_model_type: type[DataModel] = ActivationDataModel # type:ignore[misc,valid-type] order: int width: ParameterType | None = None diff --git a/glotaran/builtin/elements/damped_oscillation/element.py b/glotaran/builtin/elements/damped_oscillation/element.py index 6eb62d5e7..4862b3e6b 100644 --- a/glotaran/builtin/elements/damped_oscillation/element.py +++ b/glotaran/builtin/elements/damped_oscillation/element.py @@ -20,6 +20,7 @@ from glotaran.model import Element from glotaran.model import Item from glotaran.model import ParameterType +from glotaran.model.data_model import DataModel if TYPE_CHECKING: from glotaran.typing.types import ArrayLike @@ -32,9 +33,11 @@ class Oscillation(Item): class DampedOscillationElement(Element): type: Literal["damped-oscillation"] # type:ignore[assignment] - register_as = "damped-oscillation" # type:ignore[pydantic-field] + register_as: str = "damped-oscillation" # type:ignore[misc] dimension: str = "time" - data_model_type = ActivationDataModel # type:ignore[pydantic-field] + data_model_type: type[ # type:ignore[misc,valid-type] + DataModel + ] = ActivationDataModel oscillations: dict[str, Oscillation] def calculate_matrix( # type:ignore[override] diff --git a/glotaran/builtin/elements/kinetic/element.py b/glotaran/builtin/elements/kinetic/element.py index c24e24d45..e4a6abd6f 100644 --- a/glotaran/builtin/elements/kinetic/element.py +++ b/glotaran/builtin/elements/kinetic/element.py @@ -14,6 +14,7 @@ from glotaran.builtin.items.activation import MultiGaussianActivation from glotaran.builtin.items.activation import add_activation_to_result_data from glotaran.model import ExtendableElement +from glotaran.model.data_model import DataModel from glotaran.model.data_model import is_data_model_global if TYPE_CHECKING: @@ -22,8 +23,8 @@ class KineticElement(ExtendableElement, Kinetic): type: Literal["kinetic"] = Literal["kinetic"] # type:ignore[assignment] - register_as = "kinetic" # type:ignore[pydantic-field] - data_model_type = ActivationDataModel # type:ignore[pydantic-field] + register_as: str = "kinetic" # type:ignore[misc] + data_model_type: type[DataModel] = ActivationDataModel # type:ignore[misc, valid-type] dimension: str = "time" def extend(self, other: KineticElement): # type:ignore[override] diff --git a/glotaran/builtin/elements/spectral/element.py b/glotaran/builtin/elements/spectral/element.py index 33452502e..51a714b86 100644 --- a/glotaran/builtin/elements/spectral/element.py +++ b/glotaran/builtin/elements/spectral/element.py @@ -21,9 +21,9 @@ class SpectralDataModel(DataModel): class SpectralElement(Element): type: Literal["spectral"] # type:ignore[assignment] + register_as: str = "spectral" # type:ignore[misc] dimension: str = "spectral" - register_as = "spectral" # type:ignore[pydantic-field] - data_model_type = SpectralDataModel # type:ignore[pydantic-field] + data_model_type: type[DataModel] = SpectralDataModel # type:ignore[misc,valid-type] shapes: dict[str, SpectralShape.get_annotated_type()] # type:ignore[valid-type] def calculate_matrix( # type:ignore[override] From 03f577aca118713dff2d897a555685411bb9ba35 Mon Sep 17 00:00:00 2001 From: Joris Snellenburg Date: Wed, 18 Oct 2023 00:19:23 +0200 Subject: [PATCH 04/17] Check if Factory is needed and update requirements_dev.txt so as to not get into CI trouble --- glotaran/model/item.py | 9 ++++++--- requirements_dev.txt | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/glotaran/model/item.py b/glotaran/model/item.py index d0ec5081f..85228b918 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -73,9 +73,12 @@ def __init__( metadata: dict[str, Any] = {} if validator is not None: metadata[META_VALIDATOR] = validator - super().__init__( - default=default, default_factory=factory, description=description, **metadata - ) + if factory is not None: + super().__init__( + default_factory=factory, description=description, **metadata + ) + else: + super().__init__(default=default, description=description, **metadata) def Attribute( diff --git a/requirements_dev.txt b/requirements_dev.txt index 0ca425f3a..2f8d7bddf 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -11,7 +11,7 @@ numpy==1.24.4 odfpy==1.4.1 openpyxl==3.1.2 pandas==2.1.1 -pydantic==1.10.13 +pydantic==2.4.2 ruamel.yaml==0.17.35 scipy==1.11.2 sdtfile==2023.9.28 From 072dd759b49e47a758072f023b707bdcc6605ac3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:20:24 +0000 Subject: [PATCH 05/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- glotaran/model/item.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/glotaran/model/item.py b/glotaran/model/item.py index 85228b918..1b8a25bab 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -74,9 +74,7 @@ def __init__( if validator is not None: metadata[META_VALIDATOR] = validator if factory is not None: - super().__init__( - default_factory=factory, description=description, **metadata - ) + super().__init__(default_factory=factory, description=description, **metadata) else: super().__init__(default=default, description=description, **metadata) From 1b32b311b764165e665f24d6870caa762a0cfb87 Mon Sep 17 00:00:00 2001 From: Sebastian Weigand Date: Thu, 19 Oct 2023 22:26:42 +0200 Subject: [PATCH 06/17] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20assignment=20of=20li?= =?UTF-8?q?teral=20type=20to=20type=20attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/builtin/elements/kinetic/element.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/glotaran/builtin/elements/kinetic/element.py b/glotaran/builtin/elements/kinetic/element.py index e4a6abd6f..24f0c2cd6 100644 --- a/glotaran/builtin/elements/kinetic/element.py +++ b/glotaran/builtin/elements/kinetic/element.py @@ -22,7 +22,7 @@ class KineticElement(ExtendableElement, Kinetic): - type: Literal["kinetic"] = Literal["kinetic"] # type:ignore[assignment] + type: Literal["kinetic"] # type:ignore[assignment] register_as: str = "kinetic" # type:ignore[misc] data_model_type: type[DataModel] = ActivationDataModel # type:ignore[misc, valid-type] dimension: str = "time" @@ -49,7 +49,11 @@ def combine(cls, kinetics: list[KineticElement]) -> KineticElement: # type:igno The combined KMatrix. """ - return cls(rates=reduce(lambda lhs, rhs: lhs | rhs, [k.rates for k in kinetics]), label="") + return cls( + type="kinetic", + rates=reduce(lambda lhs, rhs: lhs | rhs, [k.rates for k in kinetics]), + label="", + ) @staticmethod def combine_matrices(lhs: ArrayLike, rhs: ArrayLike) -> ArrayLike: From 38ddc3d520938a00d96e9057a32029a6fd2a4b01 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 20:03:47 +0200 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=A9=B9=20Use=20RootModel=20instead?= =?UTF-8?q?=20of=20BaseModel=20and=20=5F=5Froot=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tmp tmp --- glotaran/model/data_model.py | 8 ++++++-- glotaran/model/experiment_model.py | 9 ++++++--- glotaran/optimization/optimization.py | 10 ++++++++-- glotaran/project/library.py | 26 +++++++++++++++++--------- glotaran/project/scheme.py | 3 +-- glotaran/simulation/simulation.py | 8 ++++++-- 6 files changed, 44 insertions(+), 20 deletions(-) diff --git a/glotaran/model/data_model.py b/glotaran/model/data_model.py index 4ae3fa2fa..33dd8d611 100644 --- a/glotaran/model/data_model.py +++ b/glotaran/model/data_model.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Generator +from typing import TYPE_CHECKING from typing import Any from typing import Literal from uuid import uuid4 @@ -22,6 +23,9 @@ from glotaran.model.weight import Weight from glotaran.parameter import Parameters +if TYPE_CHECKING: + from glotaran.project.library import ModelLibrary + class ExclusiveModelIssue(ItemIssue): """Issue for exclusive elements.""" @@ -207,7 +211,7 @@ def create_class_for_elements(elements: set[type[Element]]) -> type[DataModel]: return create_model(data_model_cls_name, __base__=data_models) @classmethod - def from_dict(cls, library: dict[str, Element], model_dict: dict[str, Any]) -> DataModel: + def from_dict(cls, library: ModelLibrary, model_dict: dict[str, Any]) -> DataModel: element_labels = model_dict.get("elements", []) + model_dict.get("global_elements", []) if len(element_labels) == 0: raise GlotaranModelError("No element defined for dataset") @@ -314,7 +318,7 @@ def iterate_data_model_global_elements( def resolve_data_model( model: DataModel, - library: dict[str, Element], + library: ModelLibrary, parameters: Parameters, initial: Parameters | None = None, ) -> DataModel: diff --git a/glotaran/model/experiment_model.py b/glotaran/model/experiment_model.py index c24f4bff1..8025777d7 100644 --- a/glotaran/model/experiment_model.py +++ b/glotaran/model/experiment_model.py @@ -1,6 +1,7 @@ """This module contains the dataset group.""" from __future__ import annotations +from typing import TYPE_CHECKING from typing import Any from typing import Literal @@ -13,7 +14,6 @@ from glotaran.model.clp_relation import ClpRelation from glotaran.model.data_model import DataModel from glotaran.model.data_model import resolve_data_model -from glotaran.model.element import Element from glotaran.model.errors import ItemIssue from glotaran.model.item import ParameterType from glotaran.model.item import get_item_issues @@ -21,6 +21,9 @@ from glotaran.model.item import resolve_parameter from glotaran.parameter import Parameters +if TYPE_CHECKING: + from glotaran.project.library import ModelLibrary + class ExperimentModel(BaseModel): """A dataset group for optimization.""" @@ -45,7 +48,7 @@ class ExperimentModel(BaseModel): ) @classmethod - def from_dict(cls, library: dict[str, Element], model_dict: dict[str, Any]) -> ExperimentModel: + def from_dict(cls, library: ModelLibrary, model_dict: dict[str, Any]) -> ExperimentModel: model_dict["datasets"] = { label: DataModel.from_dict(library, dataset) for label, dataset in model_dict.get("datasets", {}).items() @@ -54,7 +57,7 @@ def from_dict(cls, library: dict[str, Element], model_dict: dict[str, Any]) -> E def resolve( self, - library: dict[str, Element], + library: ModelLibrary, parameters: Parameters, initial: Parameters | None = None, ) -> ExperimentModel: diff --git a/glotaran/optimization/optimization.py b/glotaran/optimization/optimization.py index 8217d819a..d4ece66de 100644 --- a/glotaran/optimization/optimization.py +++ b/glotaran/optimization/optimization.py @@ -1,4 +1,7 @@ +from __future__ import annotations + from collections import ChainMap +from typing import TYPE_CHECKING from typing import Literal from warnings import warn @@ -6,7 +9,6 @@ import xarray as xr from scipy.optimize import least_squares -from glotaran.model import Element from glotaran.model import ExperimentModel from glotaran.model import GlotaranModelIssues from glotaran.model import GlotaranUserError @@ -18,6 +20,10 @@ from glotaran.typing.types import ArrayLike from glotaran.utils.tee import TeeContext +if TYPE_CHECKING: + from glotaran.project.library import ModelLibrary + + SUPPORTED_OPTIMIZATION_METHODS = { "TrustRegionReflection": "trf", "Dogbox": "dogbox", @@ -47,7 +53,7 @@ def __init__( self, models: list[ExperimentModel], parameters: Parameters, - library: dict[str, Element], + library: ModelLibrary, verbose: bool = True, raise_exception: bool = False, maximum_number_function_evaluations: int | None = None, diff --git a/glotaran/project/library.py b/glotaran/project/library.py index e38a8334a..04ee9f35c 100644 --- a/glotaran/project/library.py +++ b/glotaran/project/library.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from functools import reduce from typing import TypeAlias -from pydantic import BaseModel +from pydantic import RootModel from glotaran.model import Element from glotaran.model import ExtendableElement @@ -12,8 +14,8 @@ ] -class ModelLibrary(BaseModel): - __root__: LibraryType +class ModelLibrary(RootModel[LibraryType]): + root: LibraryType def __init__(self, **data): super().__init__(**data) @@ -23,12 +25,12 @@ def __init__(self, **data): current_size = len(extended_elements) while current_size != 0: for label in extended_elements: - element = self.__root__[label] + element = self.root[label] assert element.extends is not None - extends = [self.__root__[label] for label in element.extends] + extends = [self.root[label] for label in element.extends] if all(e.label not in extended_elements for e in extends): extends += [element] - self.__root__[label] = reduce(lambda a, b: a.extend(b), extends) + self.root[label] = reduce(lambda a, b: a.extend(b), extends) extended_elements.remove(label) if current_size == len(extended_elements): raise GlotaranModelError( @@ -36,13 +38,19 @@ def __init__(self, **data): ) current_size = len(extended_elements) + def __iter__(self): + return iter(self.root) + + def __getitem__(self, item_label: str): + return self.root[item_label] + @classmethod - def from_dict(cls, spec: dict) -> LibraryType: - return cls.parse_obj({label: m | {"label": label} for label, m in spec.items()}).__root__ + def from_dict(cls, spec: dict) -> ModelLibrary: + return cls(**{label: m | {"label": label} for label, m in spec.items()}) def _get_extended_elements(self) -> list[str]: return [ label - for label, element in self.__root__.items() + for label, element in self.root.items() if isinstance(element, ExtendableElement) and element.is_extended() ] diff --git a/glotaran/project/scheme.py b/glotaran/project/scheme.py index 34c432191..8e039f940 100644 --- a/glotaran/project/scheme.py +++ b/glotaran/project/scheme.py @@ -9,7 +9,6 @@ from glotaran.model.errors import GlotaranUserError from glotaran.optimization import Optimization from glotaran.parameter import Parameters -from glotaran.project.library import LibraryType from glotaran.project.library import ModelLibrary from glotaran.project.result import Result @@ -18,7 +17,7 @@ class Scheme(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") experiments: dict[str, ExperimentModel] - library: LibraryType + library: ModelLibrary @classmethod def from_dict(cls, spec: dict): diff --git a/glotaran/simulation/simulation.py b/glotaran/simulation/simulation.py index 4546f6f3c..48f41ff4d 100644 --- a/glotaran/simulation/simulation.py +++ b/glotaran/simulation/simulation.py @@ -1,11 +1,12 @@ """Functions for simulating a dataset using a global optimization model.""" from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np import xarray as xr from glotaran.model import DataModel -from glotaran.model import Element from glotaran.model import GlotaranUserError from glotaran.model import get_data_model_dimension from glotaran.model import resolve_data_model @@ -13,10 +14,13 @@ from glotaran.parameter import Parameters from glotaran.typing.types import ArrayLike +if TYPE_CHECKING: + from glotaran.project.library import ModelLibrary + def simulate( model: DataModel, - library: dict[str, Element], + library: ModelLibrary, parameters: Parameters, coordinates: dict[str, ArrayLike], clp: xr.DataArray | None = None, From 3dfcdf646c4e4954430fbfe69c48ccafdb0d995a Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 02:34:42 +0200 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=A9=B9=20Replace=20deprecated=20par?= =?UTF-8?q?se=5Fobj=20with=20model=5Fvalidate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/io/preprocessor/test/test_preprocessor.py | 8 ++++++-- glotaran/model/experiment_model.py | 5 +++-- .../testing/simulated_data/parallel_spectral_decay.py | 2 +- .../testing/simulated_data/sequential_spectral_decay.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/glotaran/io/preprocessor/test/test_preprocessor.py b/glotaran/io/preprocessor/test/test_preprocessor.py index bc1659425..d7f7af923 100644 --- a/glotaran/io/preprocessor/test/test_preprocessor.py +++ b/glotaran/io/preprocessor/test/test_preprocessor.py @@ -39,7 +39,11 @@ def test_to_from_dict(): assert pl_dict == { "actions": [ {"action": "baseline-value", "value": 1.0}, - {"action": "baseline-average", "select": {"dim_1": slice(0, 2)}, "exclude": None}, + { + "action": "baseline-average", + "select": {"dim_1": slice(0, 2)}, + "exclude": None, + }, ] } - assert PreProcessingPipeline.parse_obj(pl_dict) == pl + assert PreProcessingPipeline.model_validate(pl_dict) == pl diff --git a/glotaran/model/experiment_model.py b/glotaran/model/experiment_model.py index 8025777d7..9d0d4c6fc 100644 --- a/glotaran/model/experiment_model.py +++ b/glotaran/model/experiment_model.py @@ -44,7 +44,8 @@ class ExperimentModel(BaseModel): "variable_projection", description="The residual function to use." ) scale: dict[str, ParameterType] = Field( - default_factory=dict, description="The scales of of the datasets in the experiment." + default_factory=dict, + description="The scales of of the datasets in the experiment.", ) @classmethod @@ -53,7 +54,7 @@ def from_dict(cls, library: ModelLibrary, model_dict: dict[str, Any]) -> Experim label: DataModel.from_dict(library, dataset) for label, dataset in model_dict.get("datasets", {}).items() } - return cls.parse_obj(model_dict) + return cls.model_validate(model_dict) def resolve( self, diff --git a/glotaran/testing/simulated_data/parallel_spectral_decay.py b/glotaran/testing/simulated_data/parallel_spectral_decay.py index f024a7ea7..326f6d665 100644 --- a/glotaran/testing/simulated_data/parallel_spectral_decay.py +++ b/glotaran/testing/simulated_data/parallel_spectral_decay.py @@ -17,7 +17,7 @@ KineticSpectrumDataModel( elements=["parallel"], global_elements=["spectral"], - activation=[GaussianActivation.parse_obj(ACTIVATION)], # type:ignore[call-arg] + activation=[GaussianActivation.model_validate(ACTIVATION)], # type:ignore[call-arg] ), ModelLibrary.from_dict(LIBRARY), SIMULATION_PARAMETERS, diff --git a/glotaran/testing/simulated_data/sequential_spectral_decay.py b/glotaran/testing/simulated_data/sequential_spectral_decay.py index 1e6c8d7aa..f36b00469 100644 --- a/glotaran/testing/simulated_data/sequential_spectral_decay.py +++ b/glotaran/testing/simulated_data/sequential_spectral_decay.py @@ -17,7 +17,7 @@ KineticSpectrumDataModel( elements=["sequential"], global_elements=["spectral"], - activation=[GaussianActivation.parse_obj(ACTIVATION)], # type:ignore[call-arg] + activation=[GaussianActivation.model_validate(ACTIVATION)], # type:ignore[call-arg] ), ModelLibrary.from_dict(LIBRARY), SIMULATION_PARAMETERS, From 28156ddf948c96d0f6fb74820ba390944854a83f Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 19:04:08 +0200 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20accessing=20of=20ele?= =?UTF-8?q?ment=20data=5Fmodel=5Ftype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/model/data_model.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/glotaran/model/data_model.py b/glotaran/model/data_model.py index 33dd8d611..b5fedb154 100644 --- a/glotaran/model/data_model.py +++ b/glotaran/model/data_model.py @@ -206,7 +206,12 @@ class DataModel(Item): def create_class_for_elements(elements: set[type[Element]]) -> type[DataModel]: data_model_cls_name = f"GlotaranDataModel_{str(uuid4()).replace('-','_')}" data_models = tuple( - {e.data_model_type for e in elements if e.data_model_type is not None} + { + e.model_fields["data_model_type"].default + for e in elements + if "data_model_type" in e.model_fields + and e.model_fields["data_model_type"].default is not None + } ) + (DataModel,) return create_model(data_model_cls_name, __base__=data_models) From 75ba6da323d6fe9ce3a5f8f3c6432a4cb2d43593 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 16:52:51 +0200 Subject: [PATCH 10/17] =?UTF-8?q?=F0=9F=A9=B9=20Undefined=20->=20PydanticU?= =?UTF-8?q?ndefined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/model/item.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/glotaran/model/item.py b/glotaran/model/item.py index 1b8a25bab..d5bba147f 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -23,6 +23,7 @@ from pydantic import ConfigDict from pydantic import Field from pydantic.fields import FieldInfo +from pydantic_core import PydanticUndefined from glotaran.model.errors import ItemIssue from glotaran.model.errors import ParameterIssue @@ -30,8 +31,6 @@ from glotaran.parameter import Parameters # from pydantic.fields import ModelField # type:ignore[attr-defined] -# from pydantic.fields import Undefined # type:ignore[attr-defined] - ItemT = TypeVar("ItemT", bound="Item") @@ -40,7 +39,6 @@ META_VALIDATOR = "__glotaran_validator__" ModelField = Any -Undefined = Any class ItemAttribute(FieldInfo): @@ -53,7 +51,7 @@ def __init__( self, *, description: str, - default: Any = Undefined, + default: Any = PydanticUndefined, factory: Callable[[], Any] | None = None, validator: Callable | None = None, ): @@ -82,7 +80,7 @@ def __init__( def Attribute( *, description: str, - default: Any = Undefined, + default: Any = PydanticUndefined, factory: Callable[[], Any] | None = None, validator: Callable | None = None, ) -> Any: From 736c4c8433488fd5ecb2c10ec7278921a8ad1f01 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 18:27:27 +0200 Subject: [PATCH 11/17] =?UTF-8?q?=F0=9F=A9=B9=20Adjust=20to=20removal=20of?= =?UTF-8?q?=20ModelField?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://docs.pydantic.dev/latest/migration/#changes-to-validators-allowed-signatures --- glotaran/model/item.py | 108 ++++++++++++++++++++----------- glotaran/model/test/test_item.py | 2 +- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/glotaran/model/item.py b/glotaran/model/item.py index d5bba147f..2979a0963 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -1,6 +1,7 @@ """This module contains the item classes and helper functions.""" import contextlib import typing +from collections import UserDict from functools import cache from inspect import getmro from inspect import isclass @@ -30,15 +31,39 @@ from glotaran.parameter import Parameter from glotaran.parameter import Parameters -# from pydantic.fields import ModelField # type:ignore[attr-defined] - ItemT = TypeVar("ItemT", bound="Item") ParameterType: TypeAlias = Parameter | float | str META_VALIDATOR = "__glotaran_validator__" -ModelField = Any + +class GlotaranFieldMetadata(UserDict): + """Container to hold glotaran field meta data.""" + + @property + def validator(self) -> Callable | None: + """Glotaran validator function if defined, else None.""" + return self[META_VALIDATOR] if META_VALIDATOR in self else None + + +def extract_glotaran_field_metadata(info: FieldInfo) -> GlotaranFieldMetadata: + """Extract glotaran metadata from field info metadata list. + + Parameters + ---------- + info : FieldInfo + Field info to for glotaran metadata in. + + Returns + ------- + GlotaranFieldMetadata + Glotaran meta data from the field info metadata or empty if not present. + """ + for item in info.metadata: + if isinstance(item, GlotaranFieldMetadata): + return item + return GlotaranFieldMetadata() class ItemAttribute(FieldInfo): @@ -68,13 +93,14 @@ def __init__( validator: Callable[[Any, Item, Model, Parameters | None], list[ItemIssue]] | None A validator function for the attribute. """ - metadata: dict[str, Any] = {} + glotaran_field_metadata = GlotaranFieldMetadata() if validator is not None: - metadata[META_VALIDATOR] = validator + glotaran_field_metadata[META_VALIDATOR] = validator if factory is not None: - super().__init__(default_factory=factory, description=description, **metadata) + super().__init__(default_factory=factory, description=description) else: - super().__init__(default=default, description=description, **metadata) + super().__init__(default=default, description=description) + self.metadata.append(glotaran_field_metadata) def Attribute( @@ -141,12 +167,12 @@ def get_annotated_type(cls) -> object: @cache -def get_structure_and_type_from_field(field: ModelField) -> tuple[None | list | dict, type]: +def get_structure_and_type_from_field(info: FieldInfo) -> tuple[None | list | dict, type]: """Get the structure and type from a field. Parameters ---------- - field: ModelField + info: FieldInfo The field. Returns @@ -154,7 +180,7 @@ def get_structure_and_type_from_field(field: ModelField) -> tuple[None | list | tuple[None | list | dict, type]: The structure and type as atuple. """ - definition = strip_option_type_from_definition(field.annotation) + definition = strip_option_type_from_definition(info.annotation) # type:ignore[arg-type] structure, definition = strip_structure_type_from_definition(definition) definition = strip_option_type_from_definition(definition, strip_type=str) return structure, definition @@ -207,7 +233,7 @@ def strip_structure_type_from_definition(definition: type) -> tuple[None | list def iterate_fields_of_type( item: type[ItemT] | ItemT, field_type: type -) -> Generator[ModelField, None, None]: +) -> Generator[tuple[str, FieldInfo], None, None]: """Iterate over all fields of the given types. Parameters @@ -219,11 +245,11 @@ def iterate_fields_of_type( Yields ------ - ModelField + tuple[str, FieldInfo] The matching attributes. """ - for field in item.__fields__.values(): # type:ignore[union-attr] - _, item_type = get_structure_and_type_from_field(field) + for name, info in item.model_fields.items(): + _, item_type = get_structure_and_type_from_field(info) with contextlib.suppress(TypeError): # issubclass does for some reason not work with e.g. tuple as item_type # and Parameter as attr_type @@ -236,10 +262,10 @@ def iterate_fields_of_type( ): item_type = typing.get_args(typing.get_args(item_type)[0])[0] if isclass(item_type) and issubclass(item_type, field_type): - yield field + yield name, info -def iterate_item_fields(item: type[ItemT] | ItemT) -> Generator[ModelField, None, None]: +def iterate_item_fields(item: type[ItemT] | ItemT) -> Generator[tuple[str, FieldInfo], None, None]: """Iterate over all item fields. Parameters @@ -249,13 +275,15 @@ def iterate_item_fields(item: type[ItemT] | ItemT) -> Generator[ModelField, None Yields ------ - ModelField + tuple[str, FieldInfo] The item fields. """ yield from iterate_fields_of_type(item, Item) -def iterate_parameter_fields(item: type[ItemT] | ItemT) -> Generator[ModelField, None, None]: +def iterate_parameter_fields( + item: type[ItemT] | ItemT, +) -> Generator[tuple[str, FieldInfo], None, None]: """Iterate over all parameter fields. Parameters @@ -265,7 +293,7 @@ def iterate_parameter_fields(item: type[ItemT] | ItemT) -> Generator[ModelField, Yields ------ - ModelField + tuple[str, FieldInfo] The parameter fields. """ yield from iterate_fields_of_type(item, Parameter) @@ -293,31 +321,31 @@ def resolve_item_parameters( resolved: dict[str, Any] = {} initial = initial or parameters - for field in iterate_parameter_fields(item): - value = getattr(item, field.name) + for name, info in iterate_parameter_fields(item): + value = getattr(item, name) if value is None: continue - structure, _ = get_structure_and_type_from_field(field) + structure, _ = get_structure_and_type_from_field(info) if structure is None: - resolved[field.name] = resolve_parameter(value, parameters, initial) + resolved[name] = resolve_parameter(value, parameters, initial) elif structure is list: - resolved[field.name] = [resolve_parameter(v, parameters, initial) for v in value] + resolved[name] = [resolve_parameter(v, parameters, initial) for v in value] elif structure is dict: - resolved[field.name] = { + resolved[name] = { k: resolve_parameter(v, parameters, initial) for k, v in value.items() } - for field in iterate_item_fields(item): - value = getattr(item, field.name) + for name, info in iterate_item_fields(item): + value = getattr(item, name) if value is None: continue - structure, item_type = get_structure_and_type_from_field(field) + structure, item_type = get_structure_and_type_from_field(info) if structure is None: - resolved[field.name] = resolve_item_parameters(value, parameters, initial) + resolved[name] = resolve_item_parameters(value, parameters, initial) elif structure is list: - resolved[field.name] = [resolve_item_parameters(v, parameters, initial) for v in value] + resolved[name] = [resolve_item_parameters(v, parameters, initial) for v in value] elif structure is dict: - resolved[field.name] = { + resolved[name] = { k: resolve_item_parameters(v, parameters, initial) for k, v in value.items() } return item.copy(update=resolved) @@ -325,13 +353,15 @@ def resolve_item_parameters( def get_item_issues(item: Item, parameters: Parameters) -> list[ItemIssue]: issues = [] - for field in iterate_item_fields(item): - value = getattr(item, field.name) + for name, info in iterate_item_fields(item): + value = getattr(item, name) if value is None: continue - if META_VALIDATOR in field.field_info.extra: - issues += field.field_info.extra[META_VALIDATOR](value, item, parameters) - structure, item_type = get_structure_and_type_from_field(field) + + glotaran_field_metadata = extract_glotaran_field_metadata(info) + if glotaran_field_metadata.validator is not None: + issues += glotaran_field_metadata.validator(value, item, parameters) + structure, _ = get_structure_and_type_from_field(info) if structure is None: issues += get_item_issues(value, parameters) else: @@ -339,11 +369,11 @@ def get_item_issues(item: Item, parameters: Parameters) -> list[ItemIssue]: for v in values: issues += get_item_issues(v, parameters) - for field in iterate_parameter_fields(item): - value = getattr(item, field.name) + for name, info in iterate_parameter_fields(item): + value = getattr(item, name) if value is None: continue - structure, _ = get_structure_and_type_from_field(field) + structure, _ = get_structure_and_type_from_field(info) if structure is None: if isinstance(value, str) and not parameters.has(value): issues += [ParameterIssue(value)] diff --git a/glotaran/model/test/test_item.py b/glotaran/model/test/test_item.py index def2e55df..988463b78 100644 --- a/glotaran/model/test/test_item.py +++ b/glotaran/model/test/test_item.py @@ -68,7 +68,7 @@ def test_item_fields_structures_and_type(): def test_iterate_parameters(): item_fields = list(iterate_parameter_fields(MockItem)) assert len(item_fields) == 6 - assert [i.name for i in item_fields] == [ + assert [name for name, _ in item_fields] == [ "pscalar", "pscalar_option", "plist", From 307d2990c8472938727bd7e35e6fa37265003281 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 19:20:58 +0200 Subject: [PATCH 12/17] =?UTF-8?q?=F0=9F=A7=AA=F0=9F=A9=B9=20Adapt=20to=20s?= =?UTF-8?q?chema=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/model/test/test_item.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/glotaran/model/test/test_item.py b/glotaran/model/test/test_item.py index 988463b78..35ef38762 100644 --- a/glotaran/model/test/test_item.py +++ b/glotaran/model/test/test_item.py @@ -29,7 +29,7 @@ class MockItem(Item): class MockTypedItem(TypedItem): - pass + """This is just a mock item for testing.""" class MockTypedItemConcrete1(MockTypedItem): @@ -43,7 +43,7 @@ class MockTypedItemConcrete2(MockTypedItem): def test_item_fields_structures_and_type(): - item_fields = MockItem.__fields__.values() + item_fields = MockItem.model_fields.values() wanted = ( (None, int), (None, int), @@ -83,12 +83,12 @@ def test_typed_item(): def test_item_schema(): - got = MockTypedItem.schema() + got = MockTypedItem.model_json_schema() wanted = { "title": "MockTypedItem", - "description": "An item with a type.", + "description": "This is just a mock item for testing.", "type": "object", - "properties": {"type": {"title": "Type", "type": "null"}}, + "properties": {"type": {"const": None, "title": "Type"}}, "required": ["type"], "additionalProperties": False, } From 41599f14918107d3258610585d4f79d232761b90 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 19:23:42 +0200 Subject: [PATCH 13/17] =?UTF-8?q?=F0=9F=A7=AA=F0=9F=A9=B9=20Fix=20pydantic?= =?UTF-8?q?=20validation=20error=20in=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/model/test/test_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glotaran/model/test/test_item.py b/glotaran/model/test/test_item.py index 35ef38762..5f1dae9af 100644 --- a/glotaran/model/test/test_item.py +++ b/glotaran/model/test/test_item.py @@ -104,7 +104,7 @@ def test_get_issues(): cdict={}, pscalar="foo", plist=["foo", "bar"], - pdict={1: "foo", 2: "bar"}, + pdict={"1": "foo", "2": "bar"}, ) issues = get_item_issues(item, Parameters({})) From cf87561b7e4513db42a738d66e0af6ee4f1adb63 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 20:11:31 +0200 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=A7=AA=F0=9F=A9=B9=20Fix=20definiti?= =?UTF-8?q?on=20MockElementWithDataModel.data=5Fmodel=5Ftype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without the type annotation it is omitted from the fields --- glotaran/model/test/test_data_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/glotaran/model/test/test_data_model.py b/glotaran/model/test/test_data_model.py index db18a8b9d..7e08b265d 100644 --- a/glotaran/model/test/test_data_model.py +++ b/glotaran/model/test/test_data_model.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Literal import numpy as np @@ -18,7 +20,7 @@ class MockDataModel(DataModel): class MockElementWithDataModel(Element): type: Literal["mock-w-datamodel"] dimension: str = "model" - data_model_type = MockDataModel + data_model_type: type[DataModel] = MockDataModel def calculate_matrix( self, From 0236689e2a8e2d450a632cbbfcac41981caaeb75 Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 21:09:40 +0200 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=A7=B9=20Replace=20.dict=20with=20.?= =?UTF-8?q?model=5Fdump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/io/preprocessor/test/test_preprocessor.py | 2 +- glotaran/project/result.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/glotaran/io/preprocessor/test/test_preprocessor.py b/glotaran/io/preprocessor/test/test_preprocessor.py index d7f7af923..7f362641b 100644 --- a/glotaran/io/preprocessor/test/test_preprocessor.py +++ b/glotaran/io/preprocessor/test/test_preprocessor.py @@ -35,7 +35,7 @@ def test_to_from_dict(): .correct_baseline_value(1) .correct_baseline_average({"dim_1": slice(0, 2)}) ) - pl_dict = pl.dict() + pl_dict = pl.model_dump() assert pl_dict == { "actions": [ {"action": "baseline-value", "value": 1.0}, diff --git a/glotaran/project/result.py b/glotaran/project/result.py index 948fc1cdf..9aeae6cb0 100644 --- a/glotaran/project/result.py +++ b/glotaran/project/result.py @@ -56,7 +56,7 @@ def save( # for label, experiment in self.experiments.items(): # experiment_path = experiment_folder / f"{label}.yml" # result_dict["experiments"][label] = experiment_path - # write_dict(experiment.dict(), experiment_path) + # write_dict(experiment.model_dump(), experiment_path) data_path = path / "data" data_path.mkdir(exist_ok=True) From 8f0cb820fd82898ebfc68aa3137756698c66e96b Mon Sep 17 00:00:00 2001 From: s-weigand Date: Fri, 20 Oct 2023 21:13:59 +0200 Subject: [PATCH 16/17] =?UTF-8?q?=F0=9F=A7=B9=20Replace=20.copy=20with=20.?= =?UTF-8?q?model=5Fcopy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/builtin/elements/kinetic/element.py | 2 +- glotaran/model/data_model.py | 2 +- glotaran/model/experiment_model.py | 2 +- glotaran/model/item.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glotaran/builtin/elements/kinetic/element.py b/glotaran/builtin/elements/kinetic/element.py index 24f0c2cd6..3fae34d77 100644 --- a/glotaran/builtin/elements/kinetic/element.py +++ b/glotaran/builtin/elements/kinetic/element.py @@ -28,7 +28,7 @@ class KineticElement(ExtendableElement, Kinetic): dimension: str = "time" def extend(self, other: KineticElement): # type:ignore[override] - return other.copy(update={"rates": self.rates | other.rates}) + return other.model_copy(update={"rates": self.rates | other.rates}) # TODO: consolidate parent method. @classmethod diff --git a/glotaran/model/data_model.py b/glotaran/model/data_model.py index b5fedb154..0f1a65a04 100644 --- a/glotaran/model/data_model.py +++ b/glotaran/model/data_model.py @@ -327,7 +327,7 @@ def resolve_data_model( parameters: Parameters, initial: Parameters | None = None, ) -> DataModel: - model = model.copy() + model = model.model_copy() model.elements = [library[m] if isinstance(m, str) else m for m in model.elements] if model.global_elements is not None: model.global_elements = [ diff --git a/glotaran/model/experiment_model.py b/glotaran/model/experiment_model.py index 9d0d4c6fc..337b87f41 100644 --- a/glotaran/model/experiment_model.py +++ b/glotaran/model/experiment_model.py @@ -62,7 +62,7 @@ def resolve( parameters: Parameters, initial: Parameters | None = None, ) -> ExperimentModel: - result = self.copy() + result = self.model_copy() result.datasets = { label: resolve_data_model(dataset, library, parameters, initial) for label, dataset in self.datasets.items() diff --git a/glotaran/model/item.py b/glotaran/model/item.py index 2979a0963..96d51fa2c 100644 --- a/glotaran/model/item.py +++ b/glotaran/model/item.py @@ -348,7 +348,7 @@ def resolve_item_parameters( resolved[name] = { k: resolve_item_parameters(v, parameters, initial) for k, v in value.items() } - return item.copy(update=resolved) + return item.model_copy(update=resolved) def get_item_issues(item: Item, parameters: Parameters) -> list[ItemIssue]: From 0d030736ec37e95fea1b2eface859f63d81cbe5a Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Fri, 20 Oct 2023 19:16:46 +0000 Subject: [PATCH 17/17] =?UTF-8?q?=F0=9F=A4=96=20Refactored=20by=20Sourcery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glotaran/optimization/result.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/glotaran/optimization/result.py b/glotaran/optimization/result.py index 200834470..a98447a38 100644 --- a/glotaran/optimization/result.py +++ b/glotaran/optimization/result.py @@ -98,7 +98,6 @@ def from_least_squares_result( result_args = { "success": success, - # "glotaran_version": glotaran_version, "free_parameter_labels": free_parameter_labels, "parameter_history": parameter_history, "termination_reason": termination_reason, @@ -106,9 +105,9 @@ def from_least_squares_result( "number_of_function_evaluations": result.nfev # type:ignore[union-attr] if success else parameter_history.number_of_records, + "cost": 0.5 * np.dot(penalty, penalty), } - result_args["cost"] = 0.5 * np.dot(penalty, penalty) if success: result_args["number_clp"] = number_clp result_args["number_of_jacobian_evaluations"] = result.njev # type:ignore[union-attr] @@ -164,5 +163,4 @@ def calculate_covariance_matrix_and_standard_errors( _, jacobian_sv, jacobian_rsv = np.linalg.svd(jacobian, full_matrices=False) jacobian_sv_square = jacobian_sv**2 mask = jacobian_sv_square > np.finfo(float).eps - covariance_matrix = (jacobian_rsv[mask].T / jacobian_sv_square[mask]) @ jacobian_rsv[mask] - return covariance_matrix + return (jacobian_rsv[mask].T / jacobian_sv_square[mask]) @ jacobian_rsv[mask]