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.
Files changed (79) hide show
  1. {easyscience-1.3.0 → easyscience-2.1.0}/.gitignore +3 -0
  2. {easyscience-1.3.0 → easyscience-2.1.0}/LICENSE +1 -1
  3. {easyscience-1.3.0 → easyscience-2.1.0}/PKG-INFO +12 -6
  4. {easyscience-1.3.0 → easyscience-2.1.0}/README.md +4 -0
  5. {easyscience-1.3.0 → easyscience-2.1.0}/pyproject.toml +11 -7
  6. easyscience-2.1.0/src/easyscience/__init__.py +24 -0
  7. easyscience-2.1.0/src/easyscience/__version__.py +1 -0
  8. easyscience-2.1.0/src/easyscience/base_classes/__init__.py +13 -0
  9. easyscience-2.1.0/src/easyscience/base_classes/based_base.py +213 -0
  10. easyscience-1.3.0/src/easyscience/Objects/Groups.py → easyscience-2.1.0/src/easyscience/base_classes/collection_base.py +17 -22
  11. easyscience-2.1.0/src/easyscience/base_classes/model_base.py +119 -0
  12. easyscience-2.1.0/src/easyscience/base_classes/new_base.py +145 -0
  13. easyscience-2.1.0/src/easyscience/base_classes/obj_base.py +154 -0
  14. easyscience-2.1.0/src/easyscience/fitting/calculators/__init__.py +7 -0
  15. easyscience-1.3.0/src/easyscience/Objects/Inferface.py → easyscience-2.1.0/src/easyscience/fitting/calculators/interface_factory.py +12 -21
  16. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/fitter.py +4 -19
  17. easyscience-2.1.0/src/easyscience/fitting/minimizers/__init__.py +12 -0
  18. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_base.py +10 -31
  19. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_bumps.py +31 -20
  20. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_dfo.py +23 -13
  21. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/minimizer_lmfit.py +25 -14
  22. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/utils.py +1 -0
  23. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/multi_fitter.py +4 -5
  24. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/__init__.py +1 -0
  25. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/global_object.py +3 -7
  26. easyscience-2.1.0/src/easyscience/global_object/hugger/__init__.py +4 -0
  27. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/hugger/hugger.py +21 -28
  28. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/hugger/property.py +70 -97
  29. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/logger.py +3 -8
  30. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/map.py +6 -9
  31. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/global_object/undo_redo.py +12 -9
  32. easyscience-2.1.0/src/easyscience/io/__init__.py +12 -0
  33. easyscience-1.3.0/src/easyscience/Utils/io/template.py → easyscience-2.1.0/src/easyscience/io/serializer_base.py +105 -53
  34. easyscience-2.1.0/src/easyscience/io/serializer_component.py +79 -0
  35. easyscience-2.1.0/src/easyscience/io/serializer_dict.py +54 -0
  36. easyscience-2.1.0/src/easyscience/job/__init__.py +8 -0
  37. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/analysis.py +11 -20
  38. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/experiment.py +6 -7
  39. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/job.py +16 -15
  40. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/job/theoreticalmodel.py +7 -7
  41. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/dict.py +18 -23
  42. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/json.py +8 -60
  43. easyscience-1.3.0/src/easyscience/Objects/core.py → easyscience-2.1.0/src/easyscience/legacy/legacy_core.py +9 -12
  44. {easyscience-1.3.0/src/easyscience/Utils/io → easyscience-2.1.0/src/easyscience/legacy}/xml.py +8 -11
  45. easyscience-2.1.0/src/easyscience/models/__init__.py +5 -0
  46. easyscience-2.1.0/src/easyscience/models/polynomial.py +65 -0
  47. easyscience-2.1.0/src/easyscience/utils/__init__.py +3 -0
  48. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/classTools.py +8 -11
  49. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/classUtils.py +3 -6
  50. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/decorators.py +6 -11
  51. {easyscience-1.3.0/src/easyscience/Utils → easyscience-2.1.0/src/easyscience/utils}/string.py +2 -6
  52. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/__init__.py +2 -0
  53. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_any_type.py +7 -8
  54. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_array.py +110 -106
  55. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_base.py +8 -8
  56. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_bool.py +3 -2
  57. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_number.py +101 -17
  58. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/descriptor_str.py +2 -2
  59. {easyscience-1.3.0/src/easyscience/Objects → easyscience-2.1.0/src/easyscience}/variable/parameter.py +471 -188
  60. easyscience-2.1.0/src/easyscience/variable/parameter_dependency_resolver.py +147 -0
  61. easyscience-1.3.0/src/easyscience/Constraints.py +0 -498
  62. easyscience-1.3.0/src/easyscience/Datasets/__init__.py +0 -6
  63. easyscience-1.3.0/src/easyscience/Datasets/xarray.py +0 -823
  64. easyscience-1.3.0/src/easyscience/Objects/ObjectClasses.py +0 -367
  65. easyscience-1.3.0/src/easyscience/Objects/__init__.py +0 -6
  66. easyscience-1.3.0/src/easyscience/Objects/job/__init__.py +0 -0
  67. easyscience-1.3.0/src/easyscience/REDIRECT.py +0 -9
  68. easyscience-1.3.0/src/easyscience/Utils/Exceptions.py +0 -15
  69. easyscience-1.3.0/src/easyscience/Utils/__init__.py +0 -6
  70. easyscience-1.3.0/src/easyscience/Utils/io/__init__.py +0 -6
  71. easyscience-1.3.0/src/easyscience/__init__.py +0 -31
  72. easyscience-1.3.0/src/easyscience/__version__.py +0 -1
  73. easyscience-1.3.0/src/easyscience/fitting/minimizers/__init__.py +0 -9
  74. easyscience-1.3.0/src/easyscience/global_object/hugger/__init__.py +0 -6
  75. easyscience-1.3.0/src/easyscience/models/__init__.py +0 -6
  76. easyscience-1.3.0/src/easyscience/models/polynomial.py +0 -102
  77. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/__init__.py +0 -0
  78. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/available_minimizers.py +0 -0
  79. {easyscience-1.3.0 → easyscience-2.1.0}/src/easyscience/fitting/minimizers/factory.py +0 -0
@@ -23,6 +23,9 @@ dist
23
23
  poetry.lock
24
24
  *.egg-info
25
25
 
26
+ # Pixi
27
+ .pixi/
28
+
26
29
  # PyInstaller
27
30
  build
28
31
  *.spec
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2024, Easyscience contributors (https://github.com/EasyScience)
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.0
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) 2024, Easyscience contributors (https://github.com/EasyScience)
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.10
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
- Requires-Dist: xarray
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.10"
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.10,3.11,3.12,3.13}
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: 2023 EasyScience contributors <core@easyscience.software>
1
+ # SPDX-FileCopyrightText: 2025 EasyScience contributors <core@easyscience.software>
2
2
  # SPDX-License-Identifier: BSD-3-Clause
3
- # © 2021-2023 Contributors to the EasyScience project <https://github.com/easyScience/EasyScience
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
- from easyscience.Objects.ObjectClasses import BasedBase
23
- from easyscience.Objects.variable.descriptor_base import DescriptorBase
18
+
19
+ from ..variable.descriptor_base import DescriptorBase
20
+ from .based_base import BasedBase
24
21
 
25
22
  if TYPE_CHECKING:
26
- from easyscience.Objects.Inferface import iF
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 BaseCollection(BasedBase, MutableSequence):
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
- `BaseObj(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
36
- cheat with `BaseObj(*[Descriptor(...), Parameter(...), ...])`.
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[B, V],
43
- interface: Optional[iF] = None,
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[V, B]) -> None:
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[V, B]:
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, BaseObj, 'BaseCollection']
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[B, V]) -> None:
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"{self.__class__.__name__} `{getattr(self, 'name')}` of length {len(self)}"
234
+ return f'{self.__class__.__name__} `{getattr(self, "name")}` of length {len(self)}'
240
235
 
241
- def sort(self, mapping: Callable[[Union[B, V]], Any], reverse: bool = False) -> None:
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__}.')