easyscience 1.3.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {easyscience-1.3.0 → easyscience-2.1.0}/.gitignore +3 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/LICENSE +1 -1
- {easyscience-1.3.0 → easyscience-2.1.0}/PKG-INFO +12 -6
- {easyscience-1.3.0 → easyscience-2.1.0}/README.md +4 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/pyproject.toml +11 -7
- easyscience-2.1.0/src/easyscience/__init__.py +24 -0
- easyscience-2.1.0/src/easyscience/__version__.py +1 -0
- easyscience-2.1.0/src/easyscience/base_classes/__init__.py +13 -0
- easyscience-2.1.0/src/easyscience/base_classes/based_base.py +213 -0
- easyscience-1.3.0/src/easyscience/Objects/Groups.py → easyscience-2.1.0/src/easyscience/base_classes/collection_base.py +17 -22
- easyscience-2.1.0/src/easyscience/base_classes/model_base.py +119 -0
- easyscience-2.1.0/src/easyscience/base_classes/new_base.py +145 -0
- easyscience-2.1.0/src/easyscience/base_classes/obj_base.py +154 -0
- easyscience-2.1.0/src/easyscience/fitting/calculators/__init__.py +7 -0
- easyscience-1.3.0/src/easyscience/Objects/Inferface.py → easyscience-2.1.0/src/easyscience/fitting/calculators/interface_factory.py +12 -21
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/fitter.py +4 -19
- easyscience-2.1.0/src/easyscience/fitting/minimizers/__init__.py +12 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_base.py +10 -31
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_bumps.py +31 -20
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_dfo.py +23 -13
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_lmfit.py +25 -14
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/utils.py +1 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/multi_fitter.py +4 -5
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/__init__.py +1 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/global_object.py +3 -7
- easyscience-2.1.0/src/easyscience/global_object/hugger/__init__.py +4 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/hugger/hugger.py +21 -28
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/hugger/property.py +70 -97
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/logger.py +3 -8
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/map.py +6 -9
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/undo_redo.py +12 -9
- easyscience-2.1.0/src/easyscience/io/__init__.py +12 -0
- easyscience-1.3.0/src/easyscience/Utils/io/template.py → easyscience-2.1.0/src/easyscience/io/serializer_base.py +105 -53
- easyscience-2.1.0/src/easyscience/io/serializer_component.py +79 -0
- easyscience-2.1.0/src/easyscience/io/serializer_dict.py +54 -0
- easyscience-2.1.0/src/easyscience/job/__init__.py +8 -0
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/analysis.py +11 -20
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/experiment.py +6 -7
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/job.py +16 -15
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/theoreticalmodel.py +7 -7
- {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/dict.py +18 -23
- {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/json.py +8 -60
- easyscience-1.3.0/src/easyscience/Objects/core.py → easyscience-2.1.0/src/easyscience/legacy/legacy_core.py +9 -12
- {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/xml.py +8 -11
- easyscience-2.1.0/src/easyscience/models/__init__.py +5 -0
- easyscience-2.1.0/src/easyscience/models/polynomial.py +65 -0
- easyscience-2.1.0/src/easyscience/utils/__init__.py +3 -0
- {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/classTools.py +8 -11
- {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/classUtils.py +3 -6
- {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/decorators.py +6 -11
- {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/string.py +2 -6
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/__init__.py +2 -0
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_any_type.py +7 -8
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_array.py +110 -106
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_base.py +8 -8
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_bool.py +3 -2
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_number.py +101 -17
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_str.py +2 -2
- {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/parameter.py +471 -188
- easyscience-2.1.0/src/easyscience/variable/parameter_dependency_resolver.py +147 -0
- easyscience-1.3.0/src/easyscience/Constraints.py +0 -498
- easyscience-1.3.0/src/easyscience/Datasets/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/Datasets/xarray.py +0 -823
- easyscience-1.3.0/src/easyscience/Objects/ObjectClasses.py +0 -367
- easyscience-1.3.0/src/easyscience/Objects/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/Objects/job/__init__.py +0 -0
- easyscience-1.3.0/src/easyscience/REDIRECT.py +0 -9
- easyscience-1.3.0/src/easyscience/Utils/Exceptions.py +0 -15
- easyscience-1.3.0/src/easyscience/Utils/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/Utils/io/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/__init__.py +0 -31
- easyscience-1.3.0/src/easyscience/__version__.py +0 -1
- easyscience-1.3.0/src/easyscience/fitting/minimizers/__init__.py +0 -9
- easyscience-1.3.0/src/easyscience/global_object/hugger/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/models/__init__.py +0 -6
- easyscience-1.3.0/src/easyscience/models/polynomial.py +0 -102
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/__init__.py +0 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/available_minimizers.py +0 -0
- {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/factory.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
BSD 3-Clause License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025, Easyscience contributors (https://github.com/EasyScience)
|
|
4
4
|
All rights reserved.
|
|
5
5
|
|
|
6
6
|
Redistribution and use in source and binary forms, with or without
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: easyscience
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Generic logic for easyScience libraries
|
|
5
5
|
Project-URL: homepage, https://github.com/EasyScience/EasyScience
|
|
6
6
|
Project-URL: documentation, https://easyscience.github.io/EasyScience/
|
|
7
7
|
Author: EasyScience contributors
|
|
8
8
|
License: BSD 3-Clause License
|
|
9
9
|
|
|
10
|
-
Copyright (c)
|
|
10
|
+
Copyright (c) 2025, Easyscience contributors (https://github.com/EasyScience)
|
|
11
11
|
All rights reserved.
|
|
12
12
|
|
|
13
13
|
Redistribution and use in source and binary forms, with or without
|
|
@@ -40,25 +40,27 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
40
40
|
Classifier: Operating System :: OS Independent
|
|
41
41
|
Classifier: Programming Language :: Python :: 3
|
|
42
42
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
43
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
44
43
|
Classifier: Programming Language :: Python :: 3.11
|
|
45
44
|
Classifier: Programming Language :: Python :: 3.12
|
|
46
45
|
Classifier: Programming Language :: Python :: 3.13
|
|
47
46
|
Classifier: Topic :: Scientific/Engineering
|
|
48
|
-
Requires-Python: >=3.
|
|
47
|
+
Requires-Python: >=3.11
|
|
49
48
|
Requires-Dist: asteval
|
|
50
49
|
Requires-Dist: bumps
|
|
51
50
|
Requires-Dist: dfo-ls
|
|
52
51
|
Requires-Dist: lmfit
|
|
53
52
|
Requires-Dist: numpy
|
|
54
|
-
Requires-Dist: pint
|
|
55
53
|
Requires-Dist: scipp
|
|
56
54
|
Requires-Dist: uncertainties
|
|
57
|
-
|
|
55
|
+
Provides-Extra: build
|
|
56
|
+
Requires-Dist: build; extra == 'build'
|
|
57
|
+
Requires-Dist: hatchling<=1.21.0; extra == 'build'
|
|
58
|
+
Requires-Dist: setuptools-git-versioning; extra == 'build'
|
|
58
59
|
Provides-Extra: dev
|
|
59
60
|
Requires-Dist: build; extra == 'dev'
|
|
60
61
|
Requires-Dist: codecov; extra == 'dev'
|
|
61
62
|
Requires-Dist: flake8; extra == 'dev'
|
|
63
|
+
Requires-Dist: jupyterlab; extra == 'dev'
|
|
62
64
|
Requires-Dist: matplotlib; extra == 'dev'
|
|
63
65
|
Requires-Dist: pytest; extra == 'dev'
|
|
64
66
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
@@ -94,6 +96,10 @@ Or direct from the repository:
|
|
|
94
96
|
|
|
95
97
|
```pip install https://github.com/easyScience/EasyScience```
|
|
96
98
|
|
|
99
|
+
### Development
|
|
100
|
+
|
|
101
|
+
For development setup and workflow instructions, please see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
102
|
+
|
|
97
103
|
## Test
|
|
98
104
|
|
|
99
105
|
After installation, launch the test suite:
|
|
@@ -19,6 +19,10 @@ Or direct from the repository:
|
|
|
19
19
|
|
|
20
20
|
```pip install https://github.com/easyScience/EasyScience```
|
|
21
21
|
|
|
22
|
+
### Development
|
|
23
|
+
|
|
24
|
+
For development setup and workflow instructions, please see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
25
|
+
|
|
22
26
|
## Test
|
|
23
27
|
|
|
24
28
|
After installation, launch the test suite:
|
|
@@ -21,13 +21,12 @@ classifiers = [
|
|
|
21
21
|
"Topic :: Scientific/Engineering",
|
|
22
22
|
"Programming Language :: Python :: 3",
|
|
23
23
|
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
-
"Programming Language :: Python :: 3.10",
|
|
25
24
|
"Programming Language :: Python :: 3.11",
|
|
26
25
|
"Programming Language :: Python :: 3.12",
|
|
27
26
|
"Programming Language :: Python :: 3.13",
|
|
28
27
|
"Development Status :: 3 - Alpha"
|
|
29
28
|
]
|
|
30
|
-
requires-python = ">=3.
|
|
29
|
+
requires-python = ">=3.11"
|
|
31
30
|
dependencies = [
|
|
32
31
|
"asteval",
|
|
33
32
|
"bumps",
|
|
@@ -35,12 +34,15 @@ dependencies = [
|
|
|
35
34
|
"lmfit",
|
|
36
35
|
"numpy",
|
|
37
36
|
"uncertainties",
|
|
38
|
-
"xarray",
|
|
39
|
-
"pint", # Only to ensure that unit is reported as dimensionless rather than empty string
|
|
40
37
|
"scipp"
|
|
41
38
|
]
|
|
42
39
|
|
|
43
40
|
[project.optional-dependencies]
|
|
41
|
+
build = [
|
|
42
|
+
"hatchling <= 1.21.0",
|
|
43
|
+
"setuptools-git-versioning",
|
|
44
|
+
"build"
|
|
45
|
+
]
|
|
44
46
|
dev = [
|
|
45
47
|
"build",
|
|
46
48
|
"codecov",
|
|
@@ -49,7 +51,8 @@ dev = [
|
|
|
49
51
|
"pytest",
|
|
50
52
|
"pytest-cov",
|
|
51
53
|
"ruff",
|
|
52
|
-
"tox-gh-actions"
|
|
54
|
+
"tox-gh-actions",
|
|
55
|
+
"jupyterlab"
|
|
53
56
|
]
|
|
54
57
|
docs = [
|
|
55
58
|
"doc8",
|
|
@@ -75,6 +78,7 @@ packages = ["src"]
|
|
|
75
78
|
|
|
76
79
|
[tool.hatch.build.targets.wheel]
|
|
77
80
|
packages = ["src/easyscience"]
|
|
81
|
+
exclude = ["src/easyscience/legacy"]
|
|
78
82
|
|
|
79
83
|
[tool.coverage.run]
|
|
80
84
|
source = ["src/easyscience"]
|
|
@@ -128,10 +132,9 @@ force-single-line = true
|
|
|
128
132
|
legacy_tox_ini = """
|
|
129
133
|
[tox]
|
|
130
134
|
isolated_build = True
|
|
131
|
-
envlist = py{3.
|
|
135
|
+
envlist = py{3.11,3.12,3.13}
|
|
132
136
|
[gh-actions]
|
|
133
137
|
python =
|
|
134
|
-
3.10: py310
|
|
135
138
|
3.11: py311
|
|
136
139
|
3.12: py312
|
|
137
140
|
3.13: py313
|
|
@@ -139,6 +142,7 @@ python =
|
|
|
139
142
|
PLATFORM =
|
|
140
143
|
ubuntu-latest: linux
|
|
141
144
|
macos-latest: macos
|
|
145
|
+
macos-15-intel: macos-intel
|
|
142
146
|
windows-latest: windows
|
|
143
147
|
[testenv]
|
|
144
148
|
passenv =
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .global_object import GlobalObject
|
|
2
|
+
|
|
3
|
+
# Must be executed before any other imports
|
|
4
|
+
global_object = GlobalObject()
|
|
5
|
+
global_object.instantiate_stack()
|
|
6
|
+
global_object.stack.enabled = False
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from .__version__ import __version__ as __version__ # noqa: E402
|
|
10
|
+
from .base_classes import ObjBase # noqa: E402
|
|
11
|
+
from .fitting import AvailableMinimizers # noqa: E402
|
|
12
|
+
from .fitting import Fitter # noqa: E402
|
|
13
|
+
from .variable import DescriptorNumber # noqa: E402
|
|
14
|
+
from .variable import Parameter # noqa: E402
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
__version__,
|
|
18
|
+
global_object,
|
|
19
|
+
ObjBase,
|
|
20
|
+
AvailableMinimizers,
|
|
21
|
+
Fitter,
|
|
22
|
+
DescriptorNumber,
|
|
23
|
+
Parameter,
|
|
24
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.1.0'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .based_base import BasedBase
|
|
2
|
+
from .collection_base import CollectionBase
|
|
3
|
+
from .model_base import ModelBase
|
|
4
|
+
from .new_base import NewBase
|
|
5
|
+
from .obj_base import ObjBase
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
BasedBase,
|
|
9
|
+
CollectionBase,
|
|
10
|
+
ObjBase,
|
|
11
|
+
ModelBase,
|
|
12
|
+
NewBase,
|
|
13
|
+
]
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
|
|
4
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
# © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
|
|
6
|
+
from inspect import signature
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import Any
|
|
9
|
+
from typing import Dict
|
|
10
|
+
from typing import Iterable
|
|
11
|
+
from typing import List
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from typing import Set
|
|
14
|
+
|
|
15
|
+
from easyscience import global_object
|
|
16
|
+
|
|
17
|
+
from ..io import SerializerComponent
|
|
18
|
+
from ..variable import Parameter
|
|
19
|
+
from ..variable.descriptor_base import DescriptorBase
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from ..fitting.calculators import InterfaceFactoryTemplate
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BasedBase(SerializerComponent):
|
|
26
|
+
__slots__ = ['_name', '_global_object', 'user_data', '_kwargs']
|
|
27
|
+
|
|
28
|
+
_REDIRECT = {}
|
|
29
|
+
|
|
30
|
+
def __init__(self, name: str, interface: Optional[InterfaceFactoryTemplate] = None, unique_name: Optional[str] = None):
|
|
31
|
+
self._global_object = global_object
|
|
32
|
+
if unique_name is None:
|
|
33
|
+
unique_name = self._global_object.generate_unique_name(self.__class__.__name__)
|
|
34
|
+
self._unique_name = unique_name
|
|
35
|
+
self._name = name
|
|
36
|
+
self._global_object.map.add_vertex(self, obj_type='created')
|
|
37
|
+
self.interface = interface
|
|
38
|
+
self.user_data: dict = {}
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def _arg_spec(self) -> Set[str]:
|
|
42
|
+
base_cls = getattr(self, '__old_class__', self.__class__)
|
|
43
|
+
sign = signature(base_cls.__init__)
|
|
44
|
+
names = [param.name for param in sign.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD]
|
|
45
|
+
return set(names[1:])
|
|
46
|
+
|
|
47
|
+
def __reduce__(self):
|
|
48
|
+
"""
|
|
49
|
+
Make the class picklable.
|
|
50
|
+
Due to the nature of the dynamic class definitions special measures need to be taken.
|
|
51
|
+
|
|
52
|
+
:return: Tuple consisting of how to make the object
|
|
53
|
+
:rtype: tuple
|
|
54
|
+
"""
|
|
55
|
+
state = self.encode()
|
|
56
|
+
cls = getattr(self, '__old_class__', self.__class__)
|
|
57
|
+
return cls.from_dict, (state,)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def unique_name(self) -> str:
|
|
61
|
+
"""Get the unique name of the object."""
|
|
62
|
+
return self._unique_name
|
|
63
|
+
|
|
64
|
+
@unique_name.setter
|
|
65
|
+
def unique_name(self, new_unique_name: str):
|
|
66
|
+
"""Set a new unique name for the object. The old name is still kept in the map.
|
|
67
|
+
|
|
68
|
+
:param new_unique_name: New unique name for the object"""
|
|
69
|
+
if not isinstance(new_unique_name, str):
|
|
70
|
+
raise TypeError('Unique name has to be a string.')
|
|
71
|
+
self._unique_name = new_unique_name
|
|
72
|
+
self._global_object.map.add_vertex(self)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def name(self) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Get the common name of the object.
|
|
78
|
+
|
|
79
|
+
:return: Common name of the object
|
|
80
|
+
"""
|
|
81
|
+
return self._name
|
|
82
|
+
|
|
83
|
+
@name.setter
|
|
84
|
+
def name(self, new_name: str):
|
|
85
|
+
"""
|
|
86
|
+
Set a new common name for the object.
|
|
87
|
+
|
|
88
|
+
:param new_name: New name for the object
|
|
89
|
+
:return: None
|
|
90
|
+
"""
|
|
91
|
+
self._name = new_name
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def interface(self) -> InterfaceFactoryTemplate:
|
|
95
|
+
"""
|
|
96
|
+
Get the current interface of the object
|
|
97
|
+
"""
|
|
98
|
+
return self._interface
|
|
99
|
+
|
|
100
|
+
@interface.setter
|
|
101
|
+
def interface(self, new_interface: InterfaceFactoryTemplate):
|
|
102
|
+
"""
|
|
103
|
+
Set the current interface to the object and generate bindings if possible. iF.e.
|
|
104
|
+
```
|
|
105
|
+
def __init__(self, bar, interface=None, **kwargs):
|
|
106
|
+
super().__init__(self, **kwargs)
|
|
107
|
+
self.foo = bar
|
|
108
|
+
self.interface = interface # As final step after initialization to set correct bindings.
|
|
109
|
+
```
|
|
110
|
+
"""
|
|
111
|
+
self._interface = new_interface
|
|
112
|
+
if new_interface is not None:
|
|
113
|
+
self.generate_bindings()
|
|
114
|
+
|
|
115
|
+
def generate_bindings(self):
|
|
116
|
+
"""
|
|
117
|
+
Generate or re-generate bindings to an interface (if exists)
|
|
118
|
+
|
|
119
|
+
:raises: AttributeError
|
|
120
|
+
"""
|
|
121
|
+
if self.interface is None:
|
|
122
|
+
raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
|
|
123
|
+
interfaceable_children = [
|
|
124
|
+
key
|
|
125
|
+
for key in self._global_object.map.get_edges(self)
|
|
126
|
+
if issubclass(type(self._global_object.map.get_item_by_key(key)), BasedBase)
|
|
127
|
+
]
|
|
128
|
+
for child_key in interfaceable_children:
|
|
129
|
+
child = self._global_object.map.get_item_by_key(child_key)
|
|
130
|
+
child.interface = self.interface
|
|
131
|
+
self.interface.generate_bindings(self)
|
|
132
|
+
|
|
133
|
+
def switch_interface(self, new_interface_name: str):
|
|
134
|
+
"""
|
|
135
|
+
Switch or create a new interface.
|
|
136
|
+
"""
|
|
137
|
+
if self.interface is None:
|
|
138
|
+
raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
|
|
139
|
+
self.interface.switch(new_interface_name)
|
|
140
|
+
self.generate_bindings()
|
|
141
|
+
|
|
142
|
+
def get_parameters(self) -> List[Parameter]:
|
|
143
|
+
"""
|
|
144
|
+
Get all parameter objects as a list.
|
|
145
|
+
|
|
146
|
+
:return: List of `Parameter` objects.
|
|
147
|
+
"""
|
|
148
|
+
par_list = []
|
|
149
|
+
for key, item in self._kwargs.items():
|
|
150
|
+
if hasattr(item, 'get_parameters'):
|
|
151
|
+
par_list = [*par_list, *item.get_parameters()]
|
|
152
|
+
elif isinstance(item, Parameter):
|
|
153
|
+
par_list.append(item)
|
|
154
|
+
return par_list
|
|
155
|
+
|
|
156
|
+
def _get_linkable_attributes(self) -> List[DescriptorBase]:
|
|
157
|
+
"""
|
|
158
|
+
Get all objects which can be linked against as a list.
|
|
159
|
+
|
|
160
|
+
:return: List of `Descriptor`/`Parameter` objects.
|
|
161
|
+
"""
|
|
162
|
+
item_list = []
|
|
163
|
+
for key, item in self._kwargs.items():
|
|
164
|
+
if hasattr(item, '_get_linkable_attributes'):
|
|
165
|
+
item_list = [*item_list, *item._get_linkable_attributes()]
|
|
166
|
+
elif issubclass(type(item), (DescriptorBase)):
|
|
167
|
+
item_list.append(item)
|
|
168
|
+
return item_list
|
|
169
|
+
|
|
170
|
+
def get_fit_parameters(self) -> List[Parameter]:
|
|
171
|
+
"""
|
|
172
|
+
Get all objects which can be fitted (and are not fixed) as a list.
|
|
173
|
+
|
|
174
|
+
:return: List of `Parameter` objects which can be used in fitting.
|
|
175
|
+
"""
|
|
176
|
+
fit_list = []
|
|
177
|
+
for key, item in self._kwargs.items():
|
|
178
|
+
if hasattr(item, 'get_fit_parameters'):
|
|
179
|
+
fit_list = [*fit_list, *item.get_fit_parameters()]
|
|
180
|
+
elif isinstance(item, Parameter):
|
|
181
|
+
if item.independent and not item.fixed:
|
|
182
|
+
fit_list.append(item)
|
|
183
|
+
return fit_list
|
|
184
|
+
|
|
185
|
+
def __dir__(self) -> Iterable[str]:
|
|
186
|
+
"""
|
|
187
|
+
This creates auto-completion and helps out in iPython notebooks.
|
|
188
|
+
|
|
189
|
+
:return: list of function and parameter names for auto-completion
|
|
190
|
+
"""
|
|
191
|
+
new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
|
|
192
|
+
return sorted(new_class_objs)
|
|
193
|
+
|
|
194
|
+
def __copy__(self) -> BasedBase:
|
|
195
|
+
"""Return a copy of the object."""
|
|
196
|
+
temp = self.as_dict(skip=['unique_name'])
|
|
197
|
+
new_obj = self.__class__.from_dict(temp)
|
|
198
|
+
return new_obj
|
|
199
|
+
|
|
200
|
+
def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
201
|
+
"""
|
|
202
|
+
Convert an object into a full dictionary using `SerializerDict`.
|
|
203
|
+
This is a shortcut for ```obj.encode(encoder=SerializerDict)```
|
|
204
|
+
|
|
205
|
+
:param skip: List of field names as strings to skip when forming the dictionary
|
|
206
|
+
:return: encoded object containing all information to reform an EasyScience object.
|
|
207
|
+
"""
|
|
208
|
+
# extend skip to include unique_name by default
|
|
209
|
+
if skip is None:
|
|
210
|
+
skip = []
|
|
211
|
+
if 'unique_name' not in skip:
|
|
212
|
+
skip.append('unique_name')
|
|
213
|
+
return super().as_dict(skip=skip)
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText:
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
|
|
2
2
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
-
# © 2021-
|
|
3
|
+
# © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
__author__ = 'github.com/wardsimon'
|
|
8
|
-
__version__ = '0.1.0'
|
|
9
|
-
|
|
10
7
|
from collections.abc import MutableSequence
|
|
11
8
|
from numbers import Number
|
|
12
9
|
from typing import TYPE_CHECKING
|
|
@@ -15,32 +12,30 @@ from typing import Callable
|
|
|
15
12
|
from typing import List
|
|
16
13
|
from typing import Optional
|
|
17
14
|
from typing import Tuple
|
|
18
|
-
from typing import TypeVar
|
|
19
15
|
from typing import Union
|
|
20
16
|
|
|
21
17
|
from easyscience.global_object.undo_redo import NotarizedDict
|
|
22
|
-
|
|
23
|
-
from
|
|
18
|
+
|
|
19
|
+
from ..variable.descriptor_base import DescriptorBase
|
|
20
|
+
from .based_base import BasedBase
|
|
24
21
|
|
|
25
22
|
if TYPE_CHECKING:
|
|
26
|
-
from
|
|
27
|
-
from easyscience.Objects.ObjectClasses import B
|
|
28
|
-
V = TypeVar('V', bound=DescriptorBase)
|
|
23
|
+
from ..fitting.calculators import InterfaceFactoryTemplate
|
|
29
24
|
|
|
30
25
|
|
|
31
|
-
class
|
|
26
|
+
class CollectionBase(BasedBase, MutableSequence):
|
|
32
27
|
"""
|
|
33
28
|
This is the base class for which all higher level classes are built off of.
|
|
34
29
|
NOTE: This object is serializable only if parameters are supplied as:
|
|
35
|
-
`
|
|
36
|
-
cheat with `
|
|
30
|
+
`ObjBase(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
|
|
31
|
+
cheat with `ObjBase(*[Descriptor(...), Parameter(...), ...])`.
|
|
37
32
|
"""
|
|
38
33
|
|
|
39
34
|
def __init__(
|
|
40
35
|
self,
|
|
41
36
|
name: str,
|
|
42
|
-
*args: Union[
|
|
43
|
-
interface: Optional[
|
|
37
|
+
*args: Union[BasedBase, DescriptorBase],
|
|
38
|
+
interface: Optional[InterfaceFactoryTemplate] = None,
|
|
44
39
|
unique_name: Optional[str] = None,
|
|
45
40
|
**kwargs,
|
|
46
41
|
):
|
|
@@ -95,7 +90,7 @@ class BaseCollection(BasedBase, MutableSequence):
|
|
|
95
90
|
self.interface = interface
|
|
96
91
|
self._kwargs._stack_enabled = True
|
|
97
92
|
|
|
98
|
-
def insert(self, index: int, value: Union[
|
|
93
|
+
def insert(self, index: int, value: Union[DescriptorBase, BasedBase]) -> None:
|
|
99
94
|
"""
|
|
100
95
|
Insert an object into the collection at an index.
|
|
101
96
|
|
|
@@ -122,14 +117,14 @@ class BaseCollection(BasedBase, MutableSequence):
|
|
|
122
117
|
else:
|
|
123
118
|
raise AttributeError('Only EasyScience objects can be put into an EasyScience group')
|
|
124
119
|
|
|
125
|
-
def __getitem__(self, idx: Union[int, slice]) -> Union[
|
|
120
|
+
def __getitem__(self, idx: Union[int, slice]) -> Union[DescriptorBase, BasedBase]:
|
|
126
121
|
"""
|
|
127
122
|
Get an item in the collection based on its index.
|
|
128
123
|
|
|
129
124
|
:param idx: index or slice of the collection.
|
|
130
125
|
:type idx: Union[int, slice]
|
|
131
126
|
:return: Object at index `idx`
|
|
132
|
-
:rtype: Union[Parameter, Descriptor,
|
|
127
|
+
:rtype: Union[Parameter, Descriptor, ObjBase, 'CollectionBase']
|
|
133
128
|
"""
|
|
134
129
|
if isinstance(idx, slice):
|
|
135
130
|
start, stop, step = idx.indices(len(self))
|
|
@@ -156,7 +151,7 @@ class BaseCollection(BasedBase, MutableSequence):
|
|
|
156
151
|
keys = list(self._kwargs.keys())
|
|
157
152
|
return self._kwargs[keys[idx]]
|
|
158
153
|
|
|
159
|
-
def __setitem__(self, key: int, value: Union[
|
|
154
|
+
def __setitem__(self, key: int, value: Union[BasedBase, DescriptorBase]) -> None:
|
|
160
155
|
"""
|
|
161
156
|
Set an item via it's index.
|
|
162
157
|
|
|
@@ -236,9 +231,9 @@ class BaseCollection(BasedBase, MutableSequence):
|
|
|
236
231
|
return tuple(self._kwargs.values())
|
|
237
232
|
|
|
238
233
|
def __repr__(self) -> str:
|
|
239
|
-
return f
|
|
234
|
+
return f'{self.__class__.__name__} `{getattr(self, "name")}` of length {len(self)}'
|
|
240
235
|
|
|
241
|
-
def sort(self, mapping: Callable[[Union[
|
|
236
|
+
def sort(self, mapping: Callable[[Union[BasedBase, DescriptorBase]], Any], reverse: bool = False) -> None:
|
|
242
237
|
"""
|
|
243
238
|
Sort the collection according to the given mapping.
|
|
244
239
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
|
|
4
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
# © 2021-2025 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from easyscience.variable.descriptor_number import DescriptorNumber
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Dict
|
|
13
|
+
from typing import List
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from ..io import SerializerBase
|
|
17
|
+
from ..variable import Parameter
|
|
18
|
+
from ..variable.descriptor_base import DescriptorBase
|
|
19
|
+
from .new_base import NewBase
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ModelBase(NewBase):
|
|
23
|
+
"""
|
|
24
|
+
This is the base class for all model classes in EasyScience.
|
|
25
|
+
It provides methods to get parameters for fitting and analysis as well as proper serialization/deserialization for
|
|
26
|
+
DescriptorNumber/Parameter attributes.
|
|
27
|
+
|
|
28
|
+
It assumes that Parameters/DescriptorNumbers are assigned as properties with the getters returning the parameter
|
|
29
|
+
but the setter only setting the value of the parameter.
|
|
30
|
+
e.g.
|
|
31
|
+
```python
|
|
32
|
+
@property
|
|
33
|
+
def my_param(self) -> Parameter:
|
|
34
|
+
return self._my_param
|
|
35
|
+
|
|
36
|
+
@my_param.setter
|
|
37
|
+
def my_param(self, new_value: float) -> None:
|
|
38
|
+
self._my_param.value = new_value
|
|
39
|
+
```
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None):
|
|
43
|
+
super().__init__(unique_name=unique_name, display_name=display_name)
|
|
44
|
+
|
|
45
|
+
def get_all_variables(self) -> List[DescriptorBase]:
|
|
46
|
+
"""
|
|
47
|
+
Get all `Descriptor` and `Parameter` objects as a list.
|
|
48
|
+
|
|
49
|
+
:return: List of `Descriptor` and `Parameter` objects.
|
|
50
|
+
"""
|
|
51
|
+
vars = []
|
|
52
|
+
for attr_name in dir(self):
|
|
53
|
+
attr = getattr(self, attr_name)
|
|
54
|
+
if isinstance(attr, DescriptorBase):
|
|
55
|
+
vars.append(attr)
|
|
56
|
+
elif hasattr(attr, 'get_all_variables'):
|
|
57
|
+
vars += attr.get_all_variables()
|
|
58
|
+
return vars
|
|
59
|
+
|
|
60
|
+
def get_all_parameters(self) -> List[Parameter]:
|
|
61
|
+
"""
|
|
62
|
+
Get all `Parameter` objects as a list.
|
|
63
|
+
|
|
64
|
+
:return: List of `Parameter` objects.
|
|
65
|
+
"""
|
|
66
|
+
return [param for param in self.get_all_variables() if isinstance(param, Parameter)]
|
|
67
|
+
|
|
68
|
+
def get_fittable_parameters(self) -> List[Parameter]:
|
|
69
|
+
"""
|
|
70
|
+
Get all parameters which can be fitted as a list.
|
|
71
|
+
|
|
72
|
+
:return: List of `Parameter` objects.
|
|
73
|
+
"""
|
|
74
|
+
return [param for param in self.get_all_parameters() if param.independent]
|
|
75
|
+
|
|
76
|
+
def get_free_parameters(self) -> List[Parameter]:
|
|
77
|
+
"""
|
|
78
|
+
Get all parameters which are currently free to be fitted as a list.
|
|
79
|
+
|
|
80
|
+
:return: List of `Parameter` objects.
|
|
81
|
+
"""
|
|
82
|
+
return [param for param in self.get_fittable_parameters() if not param.fixed]
|
|
83
|
+
|
|
84
|
+
def get_fit_parameters(self) -> List[Parameter]:
|
|
85
|
+
"""
|
|
86
|
+
This is an alias for `get_free_parameters`.
|
|
87
|
+
To be removed when fully moved to new base classes and minimizer can be changed.
|
|
88
|
+
"""
|
|
89
|
+
return self.get_free_parameters()
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(cls, obj_dict: Dict[str, Any]) -> ModelBase:
|
|
93
|
+
"""
|
|
94
|
+
Re-create an EasyScience object with DescriptorNumber attributes from a full encoded dictionary.
|
|
95
|
+
|
|
96
|
+
:param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
|
|
97
|
+
:return: Reformed EasyScience object
|
|
98
|
+
"""
|
|
99
|
+
if not SerializerBase._is_serialized_easyscience_object(obj_dict):
|
|
100
|
+
raise ValueError('Input must be a dictionary representing an EasyScience object.')
|
|
101
|
+
if obj_dict['@class'] == cls.__name__:
|
|
102
|
+
kwargs = SerializerBase.deserialize_dict(obj_dict)
|
|
103
|
+
parameter_placeholder = {}
|
|
104
|
+
for key, value in kwargs.items():
|
|
105
|
+
if isinstance(value, DescriptorNumber):
|
|
106
|
+
parameter_placeholder[key] = value
|
|
107
|
+
kwargs[key] = value.value
|
|
108
|
+
cls_instance = cls(**kwargs)
|
|
109
|
+
for key, value in parameter_placeholder.items():
|
|
110
|
+
try:
|
|
111
|
+
temp_param = getattr(cls_instance, key)
|
|
112
|
+
setattr(cls_instance, '_' + key, value)
|
|
113
|
+
cls_instance._global_object.map.prune(temp_param.unique_name)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
raise SyntaxError(f"""Could not set parameter {key} during `from_dict` with full deserialized variable. \n'
|
|
116
|
+
This should be fixed in the class definition. Error: {e}""") from e
|
|
117
|
+
return cls_instance
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.')
|