diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 6d02c1cf..ac9967bd 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -30,7 +30,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} diff --git a/Examples/fitting/plot_constraints.py b/Examples/fitting/plot_constraints.py index 70d2db5e..b150bc82 100644 --- a/Examples/fitting/plot_constraints.py +++ b/Examples/fitting/plot_constraints.py @@ -4,7 +4,7 @@ This example shows the usages of the different constraints. """ -from easyscience.fitting import Constraints +from easyscience import Constraints from easyscience.Objects.ObjectClasses import Parameter p1 = Parameter('p1', 1) diff --git a/pyproject.toml b/pyproject.toml index 53379663..5984f52f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Development Status :: 3 - Alpha" ] -requires-python = ">=3.9,<3.12" +requires-python = ">=3.9,<3.13" dependencies = [ "asteval", "bumps", diff --git a/src/easyscience/fitting/Constraints.py b/src/easyscience/Constraints.py similarity index 100% rename from src/easyscience/fitting/Constraints.py rename to src/easyscience/Constraints.py diff --git a/src/easyscience/Objects/Groups.py b/src/easyscience/Objects/Groups.py index eaec36b5..cbbecb55 100644 --- a/src/easyscience/Objects/Groups.py +++ b/src/easyscience/Objects/Groups.py @@ -18,6 +18,7 @@ from typing import Union from easyscience.global_object.undo_redo import NotarizedDict +from easyscience.Objects.new_variable.descriptor_base import DescriptorBase from easyscience.Objects.ObjectClasses import BasedBase from easyscience.Objects.ObjectClasses import Descriptor @@ -40,6 +41,7 @@ def __init__( name: str, *args: Union[B, V], interface: Optional[iF] = None, + unique_name: Optional[str] = None, **kwargs, ): """ @@ -51,7 +53,7 @@ def __init__( :param _kwargs: Fields which this class should contain :type _kwargs: dict """ - BasedBase.__init__(self, name) + BasedBase.__init__(self, name, unique_name=unique_name) kwargs = {key: kwargs[key] for key in kwargs.keys() if kwargs[key] is not None} _args = [] for item in args: @@ -67,7 +69,7 @@ def __init__( _kwargs[key] = item kwargs = _kwargs for item in list(kwargs.values()) + _args: - if not issubclass(type(item), (Descriptor, BasedBase)): + if not issubclass(type(item), (Descriptor, DescriptorBase, BasedBase)): raise AttributeError('A collection can only be formed from easyscience objects.') args = _args _kwargs = {} @@ -83,10 +85,11 @@ def __init__( for key in kwargs.keys(): if key in self.__dict__.keys() or key in self.__slots__: raise AttributeError(f'Given kwarg: `{key}`, is an internal attribute. Please rename.') - self._global_object.map.add_edge(self, kwargs[key]) - self._global_object.map.reset_type(kwargs[key], 'created_internal') - if interface is not None: - kwargs[key].interface = interface + if kwargs[key]: # Might be None (empty tuple or list) + self._global_object.map.add_edge(self, kwargs[key]) + self._global_object.map.reset_type(kwargs[key], 'created_internal') + if interface is not None: + kwargs[key].interface = interface # TODO wrap getter and setter in Logger if interface is not None: self.interface = interface @@ -104,7 +107,7 @@ def insert(self, index: int, value: Union[V, B]) -> None: :rtype: None """ t_ = type(value) - if issubclass(t_, (BasedBase, Descriptor)): + if issubclass(t_, (BasedBase, Descriptor, DescriptorBase)): update_key = list(self._kwargs.keys()) values = list(self._kwargs.values()) # Update the internal dict @@ -165,7 +168,7 @@ def __setitem__(self, key: int, value: Union[B, V]) -> None: if isinstance(value, Number): # noqa: S3827 item = self.__getitem__(key) item.value = value - elif issubclass(type(value), BasedBase) or issubclass(type(value), Descriptor): + elif issubclass(type(value), (BasedBase, Descriptor, DescriptorBase)): update_key = list(self._kwargs.keys()) values = list(self._kwargs.values()) old_item = values[key] diff --git a/src/easyscience/Objects/ObjectClasses.py b/src/easyscience/Objects/ObjectClasses.py index 52ebdc90..4956c72c 100644 --- a/src/easyscience/Objects/ObjectClasses.py +++ b/src/easyscience/Objects/ObjectClasses.py @@ -28,7 +28,7 @@ from .Variable import Parameter if TYPE_CHECKING: - from easyscience.fitting.Constraints import C + from easyscience.Constraints import C from easyscience.Objects.Inferface import iF from easyscience.Objects.Variable import V @@ -186,7 +186,7 @@ def _get_linkable_attributes(self) -> List[V]: for key, item in self._kwargs.items(): if hasattr(item, '_get_linkable_attributes'): item_list = [*item_list, *item._get_linkable_attributes()] - elif issubclass(type(item), Descriptor) or issubclass(type(item), DescriptorBase): + elif issubclass(type(item), (Descriptor, DescriptorBase)): item_list.append(item) return item_list @@ -215,6 +215,12 @@ def __dir__(self) -> Iterable[str]: new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_')) return sorted(new_class_objs) + def __copy__(self) -> BasedBase: + """Return a copy of the object.""" + temp = self.as_dict(skip=['unique_name']) + new_obj = self.__class__.from_dict(temp) + return new_obj + if TYPE_CHECKING: B = TypeVar('B', bound=BasedBase) @@ -346,7 +352,9 @@ def getter(obj: BV) -> BV: @staticmethod def __setter(key: str) -> Callable[[BV], None]: def setter(obj: BV, value: float) -> None: - if issubclass(obj._kwargs[key].__class__, Descriptor) and not issubclass(value.__class__, Descriptor): + if issubclass(obj._kwargs[key].__class__, (Descriptor, DescriptorBase)) and not issubclass( + value.__class__, (Descriptor, DescriptorBase) + ): obj._kwargs[key].value = value else: obj._kwargs[key] = value diff --git a/src/easyscience/Objects/Variable.py b/src/easyscience/Objects/Variable.py index 2cf0ed6d..10362ba9 100644 --- a/src/easyscience/Objects/Variable.py +++ b/src/easyscience/Objects/Variable.py @@ -30,14 +30,14 @@ from easyscience import global_object from easyscience import pint from easyscience import ureg -from easyscience.fitting.Constraints import SelfConstraint +from easyscience.Constraints import SelfConstraint from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Objects.core import ComponentSerializer from easyscience.Utils.classTools import addProp from easyscience.Utils.Exceptions import CoreSetException if TYPE_CHECKING: - from easyscience.fitting.Constraints import C + from easyscience.Constraints import C Q_ = ureg.Quantity M_ = ureg.Measurement diff --git a/src/easyscience/Objects/new_variable/__init__.py b/src/easyscience/Objects/new_variable/__init__.py index b46d4b95..37b95714 100644 --- a/src/easyscience/Objects/new_variable/__init__.py +++ b/src/easyscience/Objects/new_variable/__init__.py @@ -1,4 +1,11 @@ -from .descriptor_bool import DescriptorBool # noqa: F401 -from .descriptor_number import DescriptorNumber # noqa: F401 -from .descriptor_str import DescriptorStr # noqa: F401 -from .parameter import Parameter # noqa: F401 +from .descriptor_bool import DescriptorBool +from .descriptor_number import DescriptorNumber +from .descriptor_str import DescriptorStr +from .parameter import Parameter + +__all__ = [ + DescriptorBool, + DescriptorNumber, + DescriptorStr, + Parameter, +] diff --git a/src/easyscience/Objects/new_variable/descriptor_base.py b/src/easyscience/Objects/new_variable/descriptor_base.py index ae5572ce..9f144f8f 100644 --- a/src/easyscience/Objects/new_variable/descriptor_base.py +++ b/src/easyscience/Objects/new_variable/descriptor_base.py @@ -27,7 +27,6 @@ class DescriptorBase(ComponentSerializer, metaclass=abc.ABCMeta): # Used by serializer _REDIRECT = {'parent': None} - _global_object = global_object def __init__( self, @@ -55,7 +54,7 @@ def __init__( """ if unique_name is None: - unique_name = self._global_object.generate_unique_name(self.__class__.__name__) + unique_name = global_object.generate_unique_name(self.__class__.__name__) self._unique_name = unique_name if not isinstance(name, str): @@ -80,10 +79,10 @@ def __init__( # Let the collective know we've been assimilated self._parent = parent - self._global_object.map.add_vertex(self, obj_type='created') + global_object.map.add_vertex(self, obj_type='created') # Make the connection between self and parent if parent is not None: - self._global_object.map.add_edge(parent, self) + global_object.map.add_edge(parent, self) @property def name(self) -> str: @@ -187,7 +186,7 @@ def unique_name(self, new_unique_name: str): if not isinstance(new_unique_name, str): raise TypeError('Unique name has to be a string.') self._unique_name = new_unique_name - self._global_object.map.add_vertex(self) + global_object.map.add_vertex(self) @property @abc.abstractmethod @@ -205,7 +204,6 @@ def __repr__(self) -> str: def __copy__(self) -> DescriptorBase: """Return a copy of the object.""" - temp = self.as_dict() - temp['unique_name'] = None + temp = self.as_dict(skip=['unique_name']) new_obj = self.__class__.from_dict(temp) return new_obj diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 95c6fc5e..c21c52e2 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -3,6 +3,7 @@ import numbers from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Union @@ -89,7 +90,6 @@ def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorNumb raise TypeError(f'{full_value=} must be a scipp scalar') return cls(name=name, value=full_value.value, unit=full_value.unit, variance=full_value.variance, **kwargs) - @property def full_value(self) -> Variable: """ @@ -101,8 +101,10 @@ def full_value(self) -> Variable: @full_value.setter def full_value(self, full_value: Variable) -> None: - raise AttributeError(f'Full_value is read-only. Change the value and variance seperately. Or create a new {self.__class__.__name__}.') # noqa: E501 - + raise AttributeError( + f'Full_value is read-only. Change the value and variance seperately. Or create a new {self.__class__.__name__}.' + ) + @property def value(self) -> numbers.Number: """ @@ -135,7 +137,12 @@ def unit(self) -> str: @unit.setter def unit(self, unit_str: str) -> None: - raise AttributeError(f'Unit is read-only. Use convert_unit to change the unit between allowed types or create a new {self.__class__.__name__} with the desired unit.') # noqa: E501 + raise AttributeError( + ( + f'Unit is read-only. Use convert_unit to change the unit between allowed types ' + f'or create a new {self.__class__.__name__} with the desired unit.' + ) + ) # noqa: E501 @property def variance(self) -> float: @@ -162,7 +169,6 @@ def variance(self, variance_float: float) -> None: variance_float = float(variance_float) self._scalar.variance = variance_float - @property def error(self) -> float: """ @@ -205,94 +211,92 @@ def convert_unit(self, unit_str: str): # Just to get return type right def __copy__(self) -> DescriptorNumber: return super().__copy__() - + def __repr__(self) -> str: """Return printable representation.""" - string='<' - string+= self.__class__.__name__+' ' - string+=f"'{self._name}': " - string+= f'{self._scalar.value:.4f}' + string = '<' + string += self.__class__.__name__ + ' ' + string += f"'{self._name}': " + string += f'{self._scalar.value:.4f}' if self.variance: - string += f' \u00B1 {self.error:.4f}' + string += f' \u00b1 {self.error:.4f}' obj_unit = self._scalar.unit if obj_unit == 'dimensionless': obj_unit = '' else: obj_unit = f' {obj_unit}' - string+= obj_unit - string+='>' + string += obj_unit + string += '>' return string # return f"<{class_name} '{obj_name}': {obj_value:0.04f}{obj_unit}>" - def as_dict(self) -> Dict[str, Any]: - raw_dict = super().as_dict() + def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + raw_dict = super().as_dict(skip=skip) raw_dict['value'] = self._scalar.value raw_dict['unit'] = str(self._scalar.unit) raw_dict['variance'] = self._scalar.variance return raw_dict - + def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be added to dimensionless values") - new_value = self.full_value + other + raise UnitError('Numbers can only be added to dimensionless values') + new_value = self.full_value + other elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) except UnitError: - raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} cannot be added') from None new_value = self.full_value + other.full_value other.convert_unit(original_unit) else: return NotImplemented - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - - def __radd__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be added to dimensionless values") + raise UnitError('Numbers can only be added to dimensionless values') new_value = other + self.full_value else: return NotImplemented - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number def __sub__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be subtracted from dimensionless values") + raise UnitError('Numbers can only be subtracted from dimensionless values') new_value = self.full_value - other elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) except UnitError: - raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} cannot be subtracted') from None new_value = self.full_value - other.full_value other.convert_unit(original_unit) else: return NotImplemented - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __rsub__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be subtracted from dimensionless values") + raise UnitError('Numbers can only be subtracted from dimensionless values') new_value = other - self.full_value else: return NotImplemented - descriptor= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor.name=descriptor.unique_name + descriptor = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor.name = descriptor.unique_name return descriptor - + def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = self.full_value * other @@ -302,46 +306,46 @@ def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN return NotImplemented descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) descriptor_number.convert_unit(descriptor_number._base_unit()) - descriptor_number.name=descriptor_number.unique_name + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __rmul__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = other * self.full_value else: return NotImplemented - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): original_other = other if other == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') new_value = self.full_value / other elif type(other) is DescriptorNumber: original_other = other.value if original_other == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') new_value = self.full_value / other.full_value other.value = original_other else: return NotImplemented descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) descriptor_number.convert_unit(descriptor_number._base_unit()) - descriptor_number.name=descriptor_number.unique_name + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.value == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') new_value = other / self.full_value else: return NotImplemented - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: @@ -349,51 +353,51 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN exponent = other elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': - raise UnitError("Exponents must be dimensionless") + raise UnitError('Exponents must be dimensionless') if other.variance is not None: - raise ValueError("Exponents must not have variance") + raise ValueError('Exponents must not have variance') exponent = other.value else: return NotImplemented try: - new_value = self.full_value ** exponent + new_value = self.full_value**exponent except Exception as message: raise message from None if np.isnan(new_value.value): - raise ValueError("The result of the exponentiation is not a number") - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + raise ValueError('The result of the exponentiation is not a number') + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __rpow__(self, other: numbers.Number) -> numbers.Number: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Exponents must be dimensionless") + raise UnitError('Exponents must be dimensionless') if self.variance is not None: - raise ValueError("Exponents must not have variance") - new_value = other ** self.value + raise ValueError('Exponents must not have variance') + new_value = other**self.value else: return NotImplemented return new_value def __neg__(self) -> DescriptorNumber: new_value = -self.full_value - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - + def __abs__(self) -> DescriptorNumber: new_value = abs(self.full_value) - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number def _base_unit(self) -> str: string = str(self._scalar.unit) for i, letter in enumerate(string): - if letter == "e": - if string[i:i+2] not in ["e+", "e-"]: + if letter == 'e': + if string[i : i + 2] not in ['e+', 'e-']: return string[i:] - elif letter not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "+", "-"]: + elif letter not in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '-']: return string[i:] - return "" \ No newline at end of file + return '' diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 66626cdc..26992c04 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -21,8 +21,8 @@ from scipp import Variable from easyscience import global_object -from easyscience.fitting.Constraints import ConstraintBase -from easyscience.fitting.Constraints import SelfConstraint +from easyscience.Constraints import ConstraintBase +from easyscience.Constraints import SelfConstraint from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Utils.Exceptions import CoreSetException @@ -91,7 +91,7 @@ def __init__( raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if not isinstance(fixed, bool): raise TypeError('`fixed` must be either True or False') - + self._min = sc.scalar(float(min), unit=unit) self._max = sc.scalar(float(max), unit=unit) @@ -122,7 +122,6 @@ def __init__( } self._constraints = Constraints(builtin=builtin_constraint, user={}, virtual={}) - @property def value_no_call_back(self) -> numbers.Number: """ @@ -148,7 +147,9 @@ def full_value(self) -> Variable: @full_value.setter def full_value(self, scalar: Variable) -> None: - raise AttributeError(f'Full_value is read-only. Change the value and variance seperately. Or create a new {self.__class__.__name__}.') # noqa: E501 + raise AttributeError( + f'Full_value is read-only. Change the value and variance seperately. Or create a new {self.__class__.__name__}.' + ) # noqa: E501 @property def value(self) -> numbers.Number: @@ -189,13 +190,13 @@ def value(self, value: numbers.Number) -> None: # Deals with user constraints # Changes should not be registrered in the undo/redo stack - stack_state = self._global_object.stack.enabled + stack_state = global_object.stack.enabled if stack_state: - self._global_object.stack.force_state(False) + global_object.stack.force_state(False) try: value = self._constraint_runner(self.user_constraints, value) finally: - self._global_object.stack.force_state(stack_state) + global_object.stack.force_state(stack_state) value = self._constraint_runner(self._constraints.virtual, value) @@ -290,9 +291,9 @@ def fixed(self, fixed: bool) -> None: :param fixed: True = fixed, False = can vary """ if not self.enabled: - if self._global_object.stack.enabled: + if global_object.stack.enabled: # Remove the recorded change from the stack - self._global_object.stack.pop() + global_object.stack.pop() if global_object.debug: raise CoreSetException(f'{str(self)} is not enabled.') return @@ -415,66 +416,66 @@ def __repr__(self) -> str: # Seems redundant # def __float__(self) -> float: # return float(self._scalar.value) - + def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be added to dimensionless values") + raise UnitError('Numbers can only be added to dimensionless values') new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) except UnitError: - raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} cannot be added') from None new_full_value = self.full_value + other.full_value min_value = self.min + other.min if isinstance(other, Parameter) else self.min + other.value max_value = self.max + other.max if isinstance(other, Parameter) else self.max + other.value other.convert_unit(other_unit) - else: + else: return NotImplemented - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be added to dimensionless values") + raise UnitError('Numbers can only be added to dimensionless values') new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) except UnitError: - raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be added") from None + raise UnitError(f'Values with units {other.unit} and {self.unit} cannot be added') from None new_full_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value self.convert_unit(original_unit) else: return NotImplemented - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be subtracted from dimensionless values") + raise UnitError('Numbers can only be subtracted from dimensionless values') new_full_value = self.full_value - other min_value = self.min - other max_value = self.max - other - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) except UnitError: - raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} cannot be subtracted') from None new_full_value = self.full_value - other.full_value if isinstance(other, Parameter): min_value = self.min - other.max if other.max != np.inf else -np.inf @@ -483,52 +484,59 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min - other.value max_value = self.max - other.value other.convert_unit(other_unit) - else: + else: return NotImplemented - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter - + def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': - raise UnitError("Numbers can only be subtracted from dimensionless values") + raise UnitError('Numbers can only be subtracted from dimensionless values') new_full_value = other - self.full_value min_value = other - self.max max_value = other - self.min - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) except UnitError: - raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None + raise UnitError(f'Values with units {other.unit} and {self.unit} cannot be subtracted') from None new_full_value = other.full_value - self.full_value - min_value = other.value - self.max - max_value = other.value - self.min + min_value = other.value - self.max + max_value = other.value - self.min self.convert_unit(original_unit) else: return NotImplemented - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter - + def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = self.full_value * other if other == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number combinations = [self.min * other, self.max * other] - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = self.full_value * other.full_value - if other.value == 0 and type(other) is DescriptorNumber: # Only return DescriptorNumber if other is strictly 0, i.e. not a parameter # noqa: E501 - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + if ( + other.value == 0 and type(other) is DescriptorNumber + ): # Only return DescriptorNumber if other is strictly 0, i.e. not a parameter # noqa: E501 + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number if isinstance(other, Parameter): combinations = [] - for first, second in [(self.min, other.min), (self.min, other.max), (self.max, other.min), (self.max, other.max)]: # noqa: E501 + for first, second in [ + (self.min, other.min), + (self.min, other.max), + (self.max, other.min), + (self.max, other.max), + ]: # noqa: E501 if first == 0 and np.isinf(second): combinations.append(0) elif second == 0 and np.isinf(first): @@ -537,28 +545,28 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> combinations.append(first * second) else: combinations = [self.min * other.value, self.max * other.value] - else: + else: return NotImplemented min_value = min(combinations) max_value = max(combinations) parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) - parameter.name=parameter.unique_name + parameter.name = parameter.unique_name return parameter - + def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = other * self.full_value if other == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number combinations = [other * self.min, other * self.max] - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value * self.full_value if other.value == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number combinations = [self.min * other.value, self.max * other.value] else: @@ -567,39 +575,39 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: max_value = max(combinations) parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) - parameter.name=parameter.unique_name + parameter.name = parameter.unique_name return parameter - + def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): if other == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_value = other.value if other_value == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') new_full_value = self.full_value / other.full_value if isinstance(other, Parameter): - if (other.min < 0 and other.max > 0): + if other.min < 0 and other.max > 0: combinations = [-np.inf, np.inf] elif other.min == 0: - if (self.min < 0 and self.max > 0): + if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] elif self.min >= 0: - combinations = [self.min/other.max, np.inf] + combinations = [self.min / other.max, np.inf] elif self.max <= 0: - combinations = [-np.inf, self.max/other.max] + combinations = [-np.inf, self.max / other.max] elif other.max == 0: - if (self.min < 0 and self.max > 0): + if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] elif self.min >= 0: - combinations = [-np.inf, self.min/other.min] + combinations = [-np.inf, self.min / other.min] elif self.max <= 0: - combinations = [self.max/other.min, np.inf] + combinations = [self.max / other.min, np.inf] else: - combinations = [self.min/other.min, self.max/other.max, self.min/other.max, self.max/other.min] + combinations = [self.min / other.min, self.max / other.max, self.min / other.max, self.max / other.min] else: combinations = [self.min / other.value, self.max / other.value] other.value = other_value @@ -609,85 +617,85 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) max_value = max(combinations) parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) - parameter.name=parameter.unique_name + parameter.name = parameter.unique_name return parameter - + def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: original_self = self.value if original_self == 0: - raise ZeroDivisionError("Cannot divide by zero") + raise ZeroDivisionError('Cannot divide by zero') if isinstance(other, numbers.Number): new_full_value = other / self.full_value other_value = other if other_value == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value / self.full_value other_value = other.value if other_value == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name return descriptor_number else: return NotImplemented - if (self.min < 0 and self.max > 0): + if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] elif self.min == 0: if other_value > 0: - combinations = [other_value/self.max, np.inf] + combinations = [other_value / self.max, np.inf] elif other_value < 0: - combinations = [-np.inf, other_value/self.max] + combinations = [-np.inf, other_value / self.max] elif self.max == 0: if other_value > 0: - combinations = [-np.inf, other_value/self.min] + combinations = [-np.inf, other_value / self.min] elif other_value < 0: - combinations = [other_value/self.min, np.inf] + combinations = [other_value / self.min, np.inf] else: combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) - parameter.name=parameter.unique_name + parameter.name = parameter.unique_name self.value = original_self return parameter - + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other - elif type(other) is DescriptorNumber: # Strictly a DescriptorNumber, We can't raise to the power of a Parameter + elif type(other) is DescriptorNumber: # Strictly a DescriptorNumber, We can't raise to the power of a Parameter if other.unit != 'dimensionless': - raise UnitError("Exponents must be dimensionless") + raise UnitError('Exponents must be dimensionless') if other.variance is not None: - raise ValueError("Exponents must not have variance") + raise ValueError('Exponents must not have variance') exponent = other.value else: return NotImplemented - + try: - new_full_value = self.full_value ** exponent + new_full_value = self.full_value**exponent except Exception as message: raise message from None - + if np.isnan(new_full_value.value): - raise ValueError("The result of the exponentiation is not a number") + raise ValueError('The result of the exponentiation is not a number') if exponent == 0: - descriptor_number= DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) - descriptor_number.name=descriptor_number.unique_name - return descriptor_number + descriptor_number = DescriptorNumber.from_scipp(name=self.name, full_value=new_full_value) + descriptor_number.name = descriptor_number.unique_name + return descriptor_number elif exponent < 0: if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] elif self.min == 0: - combinations = [self.max ** exponent, np.inf] + combinations = [self.max**exponent, np.inf] elif self.max == 0: - combinations = [-np.inf, self.min ** exponent] + combinations = [-np.inf, self.min**exponent] else: - combinations = [self.min ** exponent, self.max ** exponent] + combinations = [self.min**exponent, self.max**exponent] else: - combinations = [self.min ** exponent, self.max ** exponent] + combinations = [self.min**exponent, self.max**exponent] if exponent % 2 == 0: if self.min < 0 and self.max > 0: combinations.append(0) @@ -698,18 +706,18 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: combinations = [combination for combination in combinations if combination >= 0] min_value = min(combinations) max_value = max(combinations) - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter - + def __neg__(self) -> Parameter: new_full_value = -self.full_value min_value = -self.max max_value = -self.min - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter - + def __abs__(self) -> Parameter: new_full_value = abs(self.full_value) combinations = [abs(self.min), abs(self.max)] @@ -717,6 +725,6 @@ def __abs__(self) -> Parameter: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - parameter=Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) - parameter.name=parameter.unique_name + parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) + parameter.name = parameter.unique_name return parameter diff --git a/src/easyscience/Objects/virtual.py b/src/easyscience/Objects/virtual.py index b33d8406..eb4e7623 100644 --- a/src/easyscience/Objects/virtual.py +++ b/src/easyscience/Objects/virtual.py @@ -15,7 +15,7 @@ from typing import MutableSequence from easyscience import global_object -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import ObjConstraint if TYPE_CHECKING: from easyscience.Objects.ObjectClasses import BV diff --git a/src/easyscience/Utils/classTools.py b/src/easyscience/Utils/classTools.py index 5c09711f..382abe4d 100644 --- a/src/easyscience/Utils/classTools.py +++ b/src/easyscience/Utils/classTools.py @@ -59,14 +59,12 @@ def removeProp(inst: BV, name: str) -> None: def generatePath(model_obj: B, skip_first: bool = False) -> Tuple[List[int], List[str]]: pars = model_obj.get_parameters() start_idx = 0 + int(skip_first) - ids = [] + unique_names = [] names = [] - model_id = model_obj.unique_name for par in pars: - elem = par.unique_name - route = global_object.map.reverse_route(elem, model_id) + route = global_object.map.reverse_route(par.unique_name, model_obj.unique_name) objs = [getattr(global_object.map.get_item_by_key(r), 'name') for r in route] objs.reverse() names.append('.'.join(objs[start_idx:])) - ids.append(elem.int) - return ids, names + unique_names.append(par.unique_name) + return unique_names, names diff --git a/src/easyscience/Utils/io/template.py b/src/easyscience/Utils/io/template.py index e5e6bbb0..3424ddf7 100644 --- a/src/easyscience/Utils/io/template.py +++ b/src/easyscience/Utils/io/template.py @@ -222,8 +222,8 @@ def runner(o): d.update({'value': runner(obj.value)}) # pylint: disable=E1101 if hasattr(obj, '_convert_to_dict'): d = obj._convert_to_dict(d, self, skip=skip, **kwargs) - if hasattr(obj, '_global_object') and '@id' not in d: - d['@id'] = obj.unique_name + if hasattr(obj, '_global_object') and 'unique_name' not in d and 'unique_name' not in skip: + d['unique_name'] = obj.unique_name return d @staticmethod @@ -257,7 +257,11 @@ def _convert_from_dict(d): mod = __import__(modname, globals(), locals(), [classname], 0) if hasattr(mod, classname): cls_ = getattr(mod, classname) - data = {k: BaseEncoderDecoder._convert_from_dict(v) for k, v in d.items() if not k.startswith('@')} + data = { + k: BaseEncoderDecoder._convert_from_dict(v) + for k, v in d.items() + if not (k.startswith('@') or k == 'unique_name') + } return cls_(**data) elif np is not None and modname == 'numpy' and classname == 'array': if d['dtype'].startswith('complex'): diff --git a/src/easyscience/__init__.py b/src/easyscience/__init__.py index b658a714..4913e0a9 100644 --- a/src/easyscience/__init__.py +++ b/src/easyscience/__init__.py @@ -1,23 +1,26 @@ -# SPDX-FileCopyrightText: 2023 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause -# © 2021-2023 Contributors to the EasyScience project MinimizerBase: - if minimizer_enum == AvailableMinimizers.LMFit: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='leastsq') - elif minimizer_enum == AvailableMinimizers.LMFit_leastsq: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='leastsq') - elif minimizer_enum == AvailableMinimizers.LMFit_powell: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='powell') - elif minimizer_enum == AvailableMinimizers.LMFit_cobyla: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='cobyla') - elif minimizer_enum == AvailableMinimizers.LMFit_differential_evolution: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='differential_evolution') - elif minimizer_enum == AvailableMinimizers.LMFit_scipy_least_squares: - minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='least_squares') + if minimizer_enum.package == 'lm': + minimizer = LMFit(obj=fit_object, fit_function=fit_function, minimizer_enum=minimizer_enum) - elif minimizer_enum == AvailableMinimizers.Bumps: - minimizer = Bumps(obj=fit_object, fit_function=fit_function, method='amoeba') - elif minimizer_enum == AvailableMinimizers.Bumps_simplex: - minimizer = Bumps(obj=fit_object, fit_function=fit_function, method='amoeba') - elif minimizer_enum == AvailableMinimizers.Bumps_newton: - minimizer = Bumps(obj=fit_object, fit_function=fit_function, method='newton') - elif minimizer_enum == AvailableMinimizers.Bumps_lm: - minimizer = Bumps(obj=fit_object, fit_function=fit_function, method='lm') + elif minimizer_enum.package == 'bumps': + minimizer = Bumps(obj=fit_object, fit_function=fit_function, minimizer_enum=minimizer_enum) - elif minimizer_enum == AvailableMinimizers.DFO: - minimizer = DFO(obj=fit_object, fit_function=fit_function, method='leastsq') - elif minimizer_enum == AvailableMinimizers.DFO_leastsq: - minimizer = DFO(obj=fit_object, fit_function=fit_function, method='leastsq') + elif minimizer_enum.package == 'dfo': + minimizer = DFO(obj=fit_object, fit_function=fit_function, minimizer_enum=minimizer_enum) return minimizer diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 124c377b..5435b39c 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -16,11 +16,13 @@ import numpy as np +from easyscience.Constraints import ObjConstraint + # causes circular import when Parameter is imported # from easyscience.Objects.ObjectClasses import BaseObj -from easyscience.Objects.Variable import Parameter +from easyscience.Objects.new_variable import Parameter -from ..Constraints import ObjConstraint +from ..available_minimizers import AvailableMinimizers from .utils import FitError from .utils import FitResults @@ -32,19 +34,20 @@ class MinimizerBase(metaclass=ABCMeta): This template class is the basis for all minimizer engines in `EasyScience`. """ - wrapping: str = None + package: str = None def __init__( self, obj, #: BaseObj, fit_function: Callable, - method: Optional[str] = None, + minimizer_enum: Optional[AvailableMinimizers] = None, ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 - if method not in self.supported_methods(): - raise FitError(f'Method {method} not available in {self.__class__}') + if minimizer_enum.method not in self.supported_methods(): + raise FitError(f'Method {minimizer_enum.method} not available in {self.__class__}') self._object = obj self._original_fit_function = fit_function - self._method = method + self._minimizer_enum = minimizer_enum + self._method = minimizer_enum.method self._cached_pars: Dict[str, Parameter] = {} self._cached_pars_vals: Dict[str, Tuple[float]] = {} self._cached_model = None @@ -55,6 +58,10 @@ def __init__( def all_constraints(self) -> List[ObjConstraint]: return [*self._constraints, *self._object._constraints] + @property + def name(self) -> str: + return self._minimizer_enum.name + def fit_constraints(self) -> List[ObjConstraint]: return self._constraints @@ -178,14 +185,11 @@ def _prepare_parameters(self, parameters: dict[str, float]) -> dict[str, float]: """ pars = self._cached_pars - # TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter as NewParameter - for name, item in pars.items(): parameter_name = MINIMIZER_PARAMETER_PREFIX + str(name) if parameter_name not in parameters.keys(): # TODO clean when full move to new_variable - if isinstance(item, NewParameter): + if isinstance(item, Parameter): parameters[parameter_name] = item.value else: parameters[parameter_name] = item.raw_value @@ -221,8 +225,6 @@ def _fit_function(x: np.ndarray, **kwargs): """ # Update the `Parameter` values and the callback if needed # TODO THIS IS NOT THREAD SAFE :-( - # TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter for name, value in kwargs.items(): par_name = name[1:] @@ -259,12 +261,9 @@ def _create_signature(parameters: Dict[int, Parameter]) -> Signature: wrapped_parameters = [] wrapped_parameters.append(InspectParameter('x', InspectParameter.POSITIONAL_OR_KEYWORD, annotation=_empty)) - ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter as NewParameter - for name, parameter in parameters.items(): ## TODO clean when full move to new_variable - if isinstance(parameter, NewParameter): + if isinstance(parameter, Parameter): default_value = parameter.value else: default_value = parameter.raw_value diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index f571d717..a2142584 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -16,8 +16,9 @@ # causes circular import when Parameter is imported # from easyscience.Objects.ObjectClasses import BaseObj -from easyscience.Objects.Variable import Parameter +from easyscience.Objects.new_variable import Parameter +from ..available_minimizers import AvailableMinimizers from .minimizer_base import MINIMIZER_PARAMETER_PREFIX from .minimizer_base import MinimizerBase from .utils import FitError @@ -34,13 +35,13 @@ class Bumps(MinimizerBase): It allows for the Bumps fitting engine to use parameters declared in an `EasyScience.Objects.Base.BaseObj`. """ - wrapping = 'bumps' + package = 'bumps' def __init__( self, obj, #: BaseObj, fit_function: Callable, - method: Optional[str] = None, + minimizer_enum: Optional[AvailableMinimizers] = None, ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 """ Initialize the fitting engine with a `BaseObj` and an arbitrary fitting function. @@ -52,7 +53,7 @@ def __init__( keyword/value pairs :type fit_function: Callable """ - super().__init__(obj=obj, fit_function=fit_function, method=method) + super().__init__(obj=obj, fit_function=fit_function, minimizer_enum=minimizer_enum) self._p_0 = {} @staticmethod @@ -116,8 +117,6 @@ def fit( self._cached_model = model ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(self._cached_pars[list(self._cached_pars.keys())[0]], Parameter): self._p_0 = {f'p{key}': self._cached_pars[key].value for key in self._cached_pars.keys()} else: @@ -166,8 +165,6 @@ def convert_to_par_object(obj) -> BumpsParameter: """ ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(obj, Parameter): value = obj.value else: @@ -251,8 +248,6 @@ def _gen_fit_results(self, fit_results, **kwargs) -> FitResults: dict_name = name[len(MINIMIZER_PARAMETER_PREFIX) :] ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(pars[dict_name], Parameter): item[name] = pars[dict_name].value else: diff --git a/src/easyscience/fitting/minimizers/minimizer_dfo.py b/src/easyscience/fitting/minimizers/minimizer_dfo.py index 042674ff..7534e2fc 100644 --- a/src/easyscience/fitting/minimizers/minimizer_dfo.py +++ b/src/easyscience/fitting/minimizers/minimizer_dfo.py @@ -12,8 +12,9 @@ # causes circular import when Parameter is imported # from easyscience.Objects.ObjectClasses import BaseObj -from easyscience.Objects.Variable import Parameter +from easyscience.Objects.new_variable import Parameter +from ..available_minimizers import AvailableMinimizers from .minimizer_base import MINIMIZER_PARAMETER_PREFIX from .minimizer_base import MinimizerBase from .utils import FitError @@ -25,13 +26,13 @@ class DFO(MinimizerBase): This is a wrapper to Derivative Free Optimisation for Least Square: https://numericalalgorithmsgroup.github.io/dfols/ """ - wrapping = 'dfo' + package = 'dfo' def __init__( self, obj, #: BaseObj, fit_function: Callable, - method: Optional[str] = None, + minimizer_enum: Optional[AvailableMinimizers] = None, ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 """ Initialize the fitting engine with a `BaseObj` and an arbitrary fitting function. @@ -43,7 +44,7 @@ def __init__( keyword/value pairs :type fit_function: Callable """ - super().__init__(obj=obj, fit_function=fit_function, method=method) + super().__init__(obj=obj, fit_function=fit_function, minimizer_enum=minimizer_enum) self._p_0 = {} @staticmethod @@ -98,8 +99,6 @@ def fit( self._cached_model.y = y ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(self._cached_pars[list(self._cached_pars.keys())[0]], Parameter): self._p_0 = {f'p{key}': self._cached_pars[key].value for key in self._cached_pars.keys()} else: @@ -147,19 +146,18 @@ def _make_model(self, parameters: Optional[List[Parameter]] = None) -> Callable: def _outer(obj: DFO): def _make_func(x, y, weights): ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter as NewParameter dfo_pars = {} if not parameters: for name, par in obj._cached_pars.items(): - if isinstance(par, NewParameter): + if isinstance(par, Parameter): dfo_pars[MINIMIZER_PARAMETER_PREFIX + str(name)] = par.value else: dfo_pars[MINIMIZER_PARAMETER_PREFIX + str(name)] = par.raw_value else: for par in parameters: - if isinstance(par, NewParameter): + if isinstance(par, Parameter): dfo_pars[MINIMIZER_PARAMETER_PREFIX + par.unique_name] = par.value else: dfo_pars[MINIMIZER_PARAMETER_PREFIX + par.unique_name] = par.raw_value @@ -220,8 +218,6 @@ def _gen_fit_results(self, fit_results, weights, **kwargs) -> FitResults: pars = {} for p_name, par in self._cached_pars.items(): ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(par, Parameter): pars[f'p{p_name}'] = par.value else: @@ -255,9 +251,7 @@ def _dfo_fit(pars: Dict[str, Parameter], model: Callable, **kwargs): """ ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter as NewParameter - - if isinstance(list(pars.values())[0], NewParameter): + if isinstance(list(pars.values())[0], Parameter): pars_values = np.array([par.value for par in pars.values()]) else: pars_values = np.array([par.raw_value for par in pars.values()]) diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index 514a9c23..8550ffc0 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -15,8 +15,9 @@ # causes circular import when Parameter is imported # from easyscience.Objects.ObjectClasses import BaseObj -from easyscience.Objects.Variable import Parameter +from easyscience.Objects.new_variable import Parameter +from ..available_minimizers import AvailableMinimizers from .minimizer_base import MINIMIZER_PARAMETER_PREFIX from .minimizer_base import MinimizerBase from .utils import FitError @@ -29,13 +30,13 @@ class LMFit(MinimizerBase): # noqa: S101 It allows for the lmfit fitting engine to use parameters declared in an `EasyScience.Objects.Base.BaseObj`. """ - wrapping = 'lmfit' + package = 'lmfit' def __init__( self, obj, #: BaseObj, fit_function: Callable, - method: Optional[str] = None, + minimizer_enum: Optional[AvailableMinimizers] = None, ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 """ Initialize the minimizer with the `BaseObj` and the `fit_function` to be used. @@ -47,7 +48,7 @@ def __init__( :param method: Method to be used by the minimizer :type method: str """ - super().__init__(obj=obj, fit_function=fit_function, method=method) + super().__init__(obj=obj, fit_function=fit_function, minimizer_enum=minimizer_enum) @staticmethod def all_methods() -> List[str]: @@ -164,9 +165,7 @@ def convert_to_par_object(parameter: Parameter) -> LMParameter: :rtype: LMParameter """ ## TODO clean when full move to - from easyscience.Objects.new_variable import Parameter as NewParameter - - if isinstance(parameter, NewParameter): + if isinstance(parameter, Parameter): value = parameter.value else: value = parameter.raw_value @@ -207,8 +206,6 @@ def _make_model(self, pars: Optional[LMParameters] = None) -> LMModel: value = item.value else: ## TODO clean when full move to new_variable - from easyscience.Objects.new_variable import Parameter - if isinstance(item, Parameter): value = item.value else: diff --git a/src/easyscience/global_object/map.py b/src/easyscience/global_object/map.py index 2e9d6857..7eb25ff3 100644 --- a/src/easyscience/global_object/map.py +++ b/src/easyscience/global_object/map.py @@ -9,6 +9,7 @@ import sys import weakref from typing import List +from typing import Optional class _EntryList(list): @@ -183,17 +184,10 @@ def find_isolated_vertices(self) -> list: isolated += [vertex] return isolated - def find_path(self, start_obj, end_obj, path=[]) -> list: + def find_path(self, start_vertex: str, end_vertex: str, path=[]) -> list: """find a path from start_vertex to end_vertex in map""" - try: - start_vertex = start_obj.unique_name - end_vertex = end_obj.unique_name - except AttributeError: - start_vertex = start_obj - end_vertex = end_obj - graph = self.__type_dict path = path + [start_vertex] if start_vertex == end_vertex: @@ -207,13 +201,10 @@ def find_path(self, start_obj, end_obj, path=[]) -> list: return extended_path return [] - def find_all_paths(self, start_obj, end_obj, path=[]) -> list: + def find_all_paths(self, start_vertex: str, end_vertex: str, path=[]) -> list: """find all paths from start_vertex to end_vertex in map""" - start_vertex = start_obj.unique_name - end_vertex = end_obj.unique_name - graph = self.__type_dict path = path + [start_vertex] if start_vertex == end_vertex: @@ -228,7 +219,7 @@ def find_all_paths(self, start_obj, end_obj, path=[]) -> list: paths.append(p) return paths - def reverse_route(self, end_obj, start_obj=None) -> List: + def reverse_route(self, end_vertex: str, start_vertex: Optional[str] = None) -> List: """ In this case we have an object and want to know the connections to get to another in reverse. We might not know the start_object. In which case we follow the shortest path to a base vertex. @@ -239,11 +230,9 @@ def reverse_route(self, end_obj, start_obj=None) -> List: :return: :rtype: """ - end_vertex = end_obj.unique_name - path_length = sys.maxsize optimum_path = [] - if start_obj is None: + if start_vertex is None: # We now have to find where to begin..... for possible_start, vertices in self.__type_dict.items(): if end_vertex in vertices: @@ -252,7 +241,7 @@ def reverse_route(self, end_obj, start_obj=None) -> List: path_length = len(temp_path) optimum_path = temp_path else: - optimum_path = self.find_path(start_obj, end_obj) + optimum_path = self.find_path(start_vertex, end_vertex) optimum_path.reverse() return optimum_path diff --git a/tests/integration_tests/Fitting/test_fitter.py b/tests/integration_tests/Fitting/test_fitter.py index 757c3424..a3e12519 100644 --- a/tests/integration_tests/Fitting/test_fitter.py +++ b/tests/integration_tests/Fitting/test_fitter.py @@ -8,7 +8,7 @@ import pytest import numpy as np -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import ObjConstraint from easyscience.fitting.fitter import Fitter from easyscience.fitting.minimizers import FitError from easyscience.fitting.available_minimizers import AvailableMinimizers @@ -103,7 +103,7 @@ def test_basic_fit(fit_engine: AvailableMinimizers, with_errors): result = f.fit(*args, **kwargs) if fit_engine is not None: - assert result.minimizer_engine.wrapping == fit_engine.name.lower() # Special case where minimizer matches wrapping + assert result.minimizer_engine.package == fit_engine.name.lower() # Special case where minimizer matches package assert sp_sin.phase.value == pytest.approx(ref_sin.phase.value, rel=1e-3) assert sp_sin.offset.value == pytest.approx(ref_sin.offset.value, rel=1e-3) diff --git a/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py b/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py index 1f43d530..f9554062 100644 --- a/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py +++ b/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py @@ -8,7 +8,7 @@ import pytest import numpy as np -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import ObjConstraint from easyscience.fitting.fitter import Fitter from easyscience.fitting.minimizers import FitError from easyscience.fitting.available_minimizers import AvailableMinimizers @@ -103,7 +103,7 @@ def test_basic_fit(fit_engine, with_errors): result = f.fit(*args, **kwargs) if fit_engine is not None: - assert result.minimizer_engine.wrapping == fit_engine.name.lower() + assert result.minimizer_engine.package == fit_engine.name.lower() assert sp_sin.phase.raw_value == pytest.approx(ref_sin.phase.raw_value, rel=1e-3) assert sp_sin.offset.raw_value == pytest.approx(ref_sin.offset.raw_value, rel=1e-3) diff --git a/tests/integration_tests/Fitting/test_multi_fitter.py b/tests/integration_tests/Fitting/test_multi_fitter.py index 66092dce..ba59f953 100644 --- a/tests/integration_tests/Fitting/test_multi_fitter.py +++ b/tests/integration_tests/Fitting/test_multi_fitter.py @@ -8,7 +8,7 @@ import pytest import numpy as np -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import ObjConstraint from easyscience.fitting.multi_fitter import MultiFitter from easyscience.fitting.minimizers import FitError from easyscience.Objects.ObjectClasses import BaseObj diff --git a/tests/integration_tests/Fitting/test_multi_fitter_legacy_parameter.py b/tests/integration_tests/Fitting/test_multi_fitter_legacy_parameter.py index ed869302..0f9d70a2 100644 --- a/tests/integration_tests/Fitting/test_multi_fitter_legacy_parameter.py +++ b/tests/integration_tests/Fitting/test_multi_fitter_legacy_parameter.py @@ -8,7 +8,7 @@ import pytest import numpy as np -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import ObjConstraint from easyscience.fitting.multi_fitter import MultiFitter from easyscience.fitting.minimizers import FitError from easyscience.Objects.ObjectClasses import BaseObj diff --git a/tests/unit_tests/Fitting/minimizers/test_factory.py b/tests/unit_tests/Fitting/minimizers/test_factory.py index db749c4e..9a860955 100644 --- a/tests/unit_tests/Fitting/minimizers/test_factory.py +++ b/tests/unit_tests/Fitting/minimizers/test_factory.py @@ -16,19 +16,19 @@ def pull_minminizer(self, minimizer: AvailableMinimizers) -> MinimizerBase: def test_factory_lm_fit(self, minimizer_method, minimizer_enum): minimizer = self.pull_minminizer(minimizer_enum) assert minimizer._method == minimizer_method - assert minimizer.wrapping == 'lmfit' + assert minimizer.package == 'lmfit' @pytest.mark.parametrize('minimizer_method,minimizer_enum', [('amoeba', AvailableMinimizers.Bumps), ('amoeba', AvailableMinimizers.Bumps_simplex), ('newton', AvailableMinimizers.Bumps_newton), ('lm', AvailableMinimizers.Bumps_lm)]) def test_factory_bumps_fit(self, minimizer_method, minimizer_enum): minimizer = self.pull_minminizer(minimizer_enum) assert minimizer._method == minimizer_method - assert minimizer.wrapping == 'bumps' + assert minimizer.package == 'bumps' @pytest.mark.parametrize('minimizer_method,minimizer_enum', [('leastsq', AvailableMinimizers.DFO), ('leastsq', AvailableMinimizers.DFO_leastsq)]) def test_factory_dfo_fit(self, minimizer_method, minimizer_enum): minimizer = self.pull_minminizer(minimizer_enum) assert minimizer._method == minimizer_method - assert minimizer.wrapping == 'dfo' + assert minimizer.package == 'dfo' @pytest.mark.parametrize('minimizer_name,expected', [('LMFit', AvailableMinimizers.LMFit), ('LMFit_leastsq', AvailableMinimizers.LMFit_leastsq), ('LMFit_powell', AvailableMinimizers.LMFit_powell), ('LMFit_cobyla', AvailableMinimizers.LMFit_cobyla), ('LMFit_differential_evolution', AvailableMinimizers.LMFit_differential_evolution), ('LMFit_scipy_least_squares', AvailableMinimizers.LMFit_scipy_least_squares) ]) diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py index 3618489f..7109468b 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py @@ -19,8 +19,8 @@ def minimizer(self): minimizer = MinimizerBase( obj='obj', - fit_function='fit_function', - method='method' + fit_function='fit_function', + minimizer_enum=MagicMock(package='package', method='method') ) return minimizer @@ -34,7 +34,7 @@ def test_init_exception(self): MinimizerBase( obj='obj', fit_function='fit_function', - method='not_a_method' + minimizer_enum=MagicMock(package='package', method='not_a_method') ) def test_init(self, minimizer: MinimizerBase): diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_bumps.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_bumps.py index bed4f6bb..42050a86 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_bumps.py @@ -4,7 +4,6 @@ import numpy as np import easyscience.fitting.minimizers.minimizer_bumps -from easyscience.Objects.new_variable import Parameter from easyscience.fitting.minimizers.minimizer_bumps import Bumps from easyscience.fitting.minimizers.utils import FitError @@ -16,20 +15,20 @@ def minimizer(self) -> Bumps: minimizer = Bumps( obj='obj', fit_function='fit_function', - method='scipy.leastsq' + minimizer_enum=MagicMock(package='bumps', method='amoeba') ) return minimizer def test_init(self, minimizer: Bumps) -> None: assert minimizer._p_0 == {} - assert minimizer.wrapping == 'bumps' + assert minimizer.package == 'bumps' def test_init_exception(self) -> None: with pytest.raises(FitError): Bumps( obj='obj', fit_function='fit_function', - method='not_leastsq' + minimizer_enum=MagicMock(package='bumps', method='not_amoeba') ) def test_all_methods(self, minimizer: Bumps) -> None: @@ -67,7 +66,7 @@ def test_fit(self, minimizer: Bumps, monkeypatch) -> None: # Expect assert result == 'gen_fit_results' - mock_bumps_fit.assert_called_once_with('fit_problem', method='scipy.leastsq') + mock_bumps_fit.assert_called_once_with('fit_problem', method='amoeba') minimizer._make_model.assert_called_once_with(parameters=None) minimizer._set_parameter_fit_result.assert_called_once_with('fit', False) minimizer._gen_fit_results.assert_called_once_with('fit') diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_dfo.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_dfo.py index 1be679c5..ae620f11 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_dfo.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_dfo.py @@ -16,20 +16,20 @@ def minimizer(self) -> DFO: minimizer = DFO( obj='obj', fit_function='fit_function', - method='leastsq' + minimizer_enum=MagicMock(package='dfo', method='leastsq') ) return minimizer def test_init(self, minimizer: DFO) -> None: assert minimizer._p_0 == {} - assert minimizer.wrapping == 'dfo' + assert minimizer.package == 'dfo' def test_init_exception(self) -> None: with pytest.raises(FitError): DFO( obj='obj', fit_function='fit_function', - method='not_leastsq' + minimizer_enum=MagicMock(package='dfo', method='not_leastsq') ) def test_supported_methods(self, minimizer: DFO) -> None: diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py index 9c055aa4..b2f7b08e 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py @@ -15,19 +15,19 @@ def minimizer(self) -> LMFit: minimizer = LMFit( obj='obj', fit_function='fit_function', - method='leastsq' + minimizer_enum=MagicMock(package='lm', method='leastsq') ) return minimizer def test_init(self, minimizer: LMFit) -> None: - assert minimizer.wrapping == 'lmfit' + assert minimizer.package == 'lmfit' def test_init_exception(self) -> None: with pytest.raises(FitError): LMFit( obj='obj', fit_function='fit_function', - method='method' + minimizer_enum=MagicMock(package='dfo', method='not_leastsq') ) def test_make_model(self, minimizer: LMFit, monkeypatch) -> None: diff --git a/tests/unit_tests/Fitting/test_constraints.py b/tests/unit_tests/Fitting/test_constraints.py index b20247e5..566da085 100644 --- a/tests/unit_tests/Fitting/test_constraints.py +++ b/tests/unit_tests/Fitting/test_constraints.py @@ -11,8 +11,8 @@ import pytest from unittest.mock import MagicMock -from easyscience.fitting.Constraints import NumericConstraint -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import NumericConstraint +from easyscience.Constraints import ObjConstraint from easyscience.Objects.new_variable.parameter import Parameter diff --git a/tests/unit_tests/Fitting/test_constraints_legacy_parameter.py b/tests/unit_tests/Fitting/test_constraints_legacy_parameter.py index b33f4dfb..b20cd0f2 100644 --- a/tests/unit_tests/Fitting/test_constraints_legacy_parameter.py +++ b/tests/unit_tests/Fitting/test_constraints_legacy_parameter.py @@ -10,8 +10,8 @@ import pytest -from easyscience.fitting.Constraints import NumericConstraint -from easyscience.fitting.Constraints import ObjConstraint +from easyscience.Constraints import NumericConstraint +from easyscience.Constraints import ObjConstraint from easyscience.Objects.Variable import Parameter diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_base.py b/tests/unit_tests/Objects/new_variable/test_descriptor_base.py index 346a383d..7a93b4f2 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_base.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_base.py @@ -53,7 +53,7 @@ def test_init(self, descriptor: DescriptorBase): assert descriptor._description == "description" assert descriptor._url == "url" assert descriptor._display_name == "display_name" - assert len(descriptor._global_object.map.created_objs) - self.objs_before_new_descriptor == 1 + assert len(global_object.map.created_objs) - self.objs_before_new_descriptor == 1 def test_display_name(self, descriptor: DescriptorBase): @@ -119,15 +119,15 @@ def test_url_setter_type_error(self, descriptor: DescriptorBase, url): def test_set_display_name_without_global_object_stack(self, descriptor: DescriptorBase, stack_enabled, stack_elements): # When descriptor.__repr__ = lambda x: "DescriptorBase" - descriptor._global_object.stack.clear() - descriptor._global_object.stack._enabled = stack_enabled + global_object.stack.clear() + global_object.stack._enabled = stack_enabled # Then descriptor.display_name = "new_display_name" # Expect assert descriptor.display_name == "new_display_name" - assert len(descriptor._global_object.stack.history) == stack_elements + assert len(global_object.stack.history) == stack_elements def test_copy(self, descriptor: DescriptorBase): # When Then diff --git a/tests/unit_tests/Objects/test_BaseObj.py b/tests/unit_tests/Objects/test_BaseObj.py index 5fdb087e..15d12fdf 100644 --- a/tests/unit_tests/Objects/test_BaseObj.py +++ b/tests/unit_tests/Objects/test_BaseObj.py @@ -6,6 +6,8 @@ # © 2021-2023 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2023 Contributors to the EasyScience project dict: ######################################################################################################################## @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip): +def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -69,7 +69,7 @@ def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip): +def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -97,7 +97,7 @@ def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], ) @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_encode_data(dp_kwargs: dict, dp_cls: Type[Descriptor], skip, encoder): +def test_variable_encode_data(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip, encoder): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -123,7 +123,7 @@ def test_variable_encode_data(dp_kwargs: dict, dp_cls: Type[Descriptor], skip, e @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) def test_custom_class_DictSerializer_encode( - dp_kwargs: dict, dp_cls: Type[Descriptor], skip + dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip ): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} @@ -158,7 +158,7 @@ def test_custom_class_DictSerializer_encode( @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) def test_custom_class_DataDictSerializer( - dp_kwargs: dict, dp_cls: Type[Descriptor], skip + dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip ): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} @@ -185,7 +185,7 @@ def test_custom_class_DataDictSerializer( "encoder", [None, DataDictSerializer], ids=["Default", "DataDictSerializer"] ) @pytest.mark.parametrize(**dp_param_dict) -def test_custom_class_encode_data(dp_kwargs: dict, dp_cls: Type[Descriptor], encoder): +def test_custom_class_encode_data(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], encoder): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} a_kw = {data_dict["name"]: dp_cls(**data_dict)} @@ -216,12 +216,13 @@ def __init__(self, a, b): except metadata.PackageNotFoundError: version = '0.0.0' - obj = B(Descriptor("a", 1.0, unique_name="a"), np.array([1.0, 2.0, 3.0])) + obj = B(DescriptorNumber("a", 1.0, unique_name="a"), np.array([1.0, 2.0, 3.0])) full_enc = obj.encode(encoder=DictSerializer, full_encode=True) expected = { "@module": "tests.unit_tests.utils.io_tests.test_dict", "@class": "B", "@version": None, + "unique_name": "B_0", "b": { "@module": "numpy", "@class": "array", @@ -229,15 +230,15 @@ def __init__(self, a, b): "data": [1.0, 2.0, 3.0], }, "a": { - "@module": "easyscience.Objects.Variable", - "@class": "Descriptor", + "@module": "easyscience.Objects.new_variable.descriptor_number", + "@class": "DescriptorNumber", "@version": version, "description": "", - "units": "dimensionless", + "unit": "dimensionless", "display_name": "a", "name": "a", - "enabled": True, "value": 1.0, + "variance": None, "unique_name": "a", "url": "", }, @@ -246,13 +247,14 @@ def __init__(self, a, b): def test_custom_class_full_decode_with_numpy(): - - obj = B(Descriptor("a", 1.0), np.array([1.0, 2.0, 3.0])) + global_object.map._clear() + obj = B(DescriptorNumber("a", 1.0), np.array([1.0, 2.0, 3.0])) full_enc = obj.encode(encoder=DictSerializer, full_encode=True) global_object.map._clear() obj2 = B.decode(full_enc, decoder=DictSerializer) assert obj.name == obj2.name - assert obj.a.raw_value == obj2.a.raw_value + assert obj.unique_name == obj2.unique_name + assert obj.a.value == obj2.a.value assert np.all(obj.b == obj2.b) @@ -260,14 +262,10 @@ def test_custom_class_full_decode_with_numpy(): # TESTING DECODING ######################################################################################################################## @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=DictSerializer) global_object.map._clear() @@ -281,14 +279,10 @@ def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DictSerializer_from_dict(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_DictSerializer_from_dict(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=DictSerializer) global_object.map._clear() @@ -302,14 +296,10 @@ def test_variable_DictSerializer_from_dict(dp_kwargs: dict, dp_cls: Type[Descrip @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DataDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_DataDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=DataDictSerializer) with pytest.raises(NotImplementedError): @@ -317,8 +307,8 @@ def test_variable_DataDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descri def test_group_encode(): - d0 = Descriptor("a", 0) - d1 = Descriptor("b", 1) + d0 = DescriptorNumber("a", 0) + d1 = DescriptorNumber("b", 1) from easyscience.Objects.Groups import BaseCollection @@ -328,8 +318,8 @@ def test_group_encode(): def test_group_encode2(): - d0 = Descriptor("a", 0) - d1 = Descriptor("b", 1) + d0 = DescriptorNumber("a", 0) + d1 = DescriptorNumber("b", 1) from easyscience.Objects.Groups import BaseCollection diff --git a/tests/unit_tests/utils/io_tests/test_json.py b/tests/unit_tests/utils/io_tests/test_json.py index 17442c4a..e5d25c03 100644 --- a/tests/unit_tests/utils/io_tests/test_json.py +++ b/tests/unit_tests/utils/io_tests/test_json.py @@ -9,9 +9,9 @@ from easyscience.Utils.io.json import JsonDataSerializer from easyscience.Utils.io.json import JsonSerializer +from easyscience.Objects.new_variable import DescriptorNumber from .test_core import A -from .test_core import Descriptor from .test_core import check_dict from .test_core import dp_param_dict from .test_core import skip_dict @@ -39,7 +39,7 @@ def recursive_remove(d, remove_keys: list) -> dict: ######################################################################################################################## @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip): +def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -69,7 +69,7 @@ def test_variable_DictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip): +def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -97,7 +97,7 @@ def test_variable_DataDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) def test_custom_class_DictSerializer_encode( - dp_kwargs: dict, dp_cls: Type[Descriptor], skip + dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip ): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} @@ -137,7 +137,7 @@ def test_custom_class_DictSerializer_encode( @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) def test_custom_class_DataDictSerializer( - dp_kwargs: dict, dp_cls: Type[Descriptor], skip + dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip ): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} @@ -169,14 +169,10 @@ def test_custom_class_DataDictSerializer( # # TESTING DECODING # ######################################################################################################################## @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=JsonSerializer) global_object.map._clear() @@ -191,14 +187,10 @@ def test_variable_DictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor @pytest.mark.parametrize(**dp_param_dict) -def test_variable_DataDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_DataDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=JsonDataSerializer) global_object.map._clear() diff --git a/tests/unit_tests/utils/io_tests/test_xml.py b/tests/unit_tests/utils/io_tests/test_xml.py index 562ceae1..9f908a99 100644 --- a/tests/unit_tests/utils/io_tests/test_xml.py +++ b/tests/unit_tests/utils/io_tests/test_xml.py @@ -9,9 +9,9 @@ import pytest from easyscience.Utils.io.xml import XMLSerializer +from easyscience.Objects.new_variable import DescriptorNumber from .test_core import A -from .test_core import Descriptor from .test_core import dp_param_dict from .test_core import skip_dict from easyscience import global_object @@ -45,7 +45,7 @@ def recursive_test(testing_obj, reference_obj): ######################################################################################################################## @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) -def test_variable_XMLDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], skip): +def test_variable_XMLDictSerializer(dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) @@ -69,7 +69,7 @@ def test_variable_XMLDictSerializer(dp_kwargs: dict, dp_cls: Type[Descriptor], s @pytest.mark.parametrize(**skip_dict) @pytest.mark.parametrize(**dp_param_dict) def test_custom_class_XMLDictSerializer_encode( - dp_kwargs: dict, dp_cls: Type[Descriptor], skip + dp_kwargs: dict, dp_cls: Type[DescriptorNumber], skip ): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} @@ -102,14 +102,10 @@ def test_custom_class_XMLDictSerializer_encode( # # TESTING DECODING # ######################################################################################################################## @pytest.mark.parametrize(**dp_param_dict) -def test_variable_XMLDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[Descriptor]): +def test_variable_XMLDictSerializer_decode(dp_kwargs: dict, dp_cls: Type[DescriptorNumber]): data_dict = {k: v for k, v in dp_kwargs.items() if k[0] != "@"} obj = dp_cls(**data_dict) - if "units" in data_dict.keys(): - data_dict["unit"] = data_dict.pop("units") - if "value" in data_dict.keys(): - data_dict["raw_value"] = data_dict.pop("value") enc = obj.encode(encoder=XMLSerializer) assert isinstance(enc, str)