From a296410091116b081db177785c1805bc65819924 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 11:11:18 +0200 Subject: [PATCH 01/68] __add__ method in Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ src/easyscience/Objects/new_variable/parameter.py | 15 +++++++++++++++ .../fitting/minimizers/minimizer_base.py | 7 ++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 1b0a3e03..cf177f50 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -225,3 +225,13 @@ def as_dict(self) -> Dict[str, Any]: raw_dict['unit'] = str(self._scalar.unit) raw_dict['variance'] = self._scalar.variance return raw_dict + + def __add__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value + other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' + ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 8b3c39a6..ce34ae88 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -420,3 +420,18 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) + + def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value + other.full_value + except Exception as message: + raise ValueError(message) + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = self.name+" + "+other.name if not radd else other.name+" + "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__add__(other, radd=True) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 35cf597f..34662ca9 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -13,7 +13,8 @@ import numpy as np -from easyscience.Objects.ObjectClasses import BaseObj +#causes circular import when Parameter is imported +#from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.Variable import Parameter from ..Constraints import ObjConstraint @@ -30,7 +31,7 @@ class MinimizerBase(metaclass=ABCMeta): wrapping: str = None - def __init__(self, obj: BaseObj, fit_function: Callable, method: Optional[str] = None): + def __init__(self, obj, fit_function: Callable, method: Optional[str] = None): # todo after constraint changes, add type hint: obj: BaseObj if method not in self.available_methods(): raise FitError(f'Method {method} not available in {self.__class__}') self._object = obj @@ -169,7 +170,7 @@ def available_methods(self) -> List[str]: @staticmethod @abstractmethod - def convert_to_par_object(obj: BaseObj): + def convert_to_par_object(obj): # todo after constraint changes, add type hint: obj: BaseObj """ Convert an `EasyScience.Objects.Base.Parameter` object to an engine Parameter object. """ From 5f0c8a4b9bc73f1e8ce36c0dcda0e3d0f10dbe22 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 12:03:12 +0200 Subject: [PATCH 02/68] __sub__ methods added to Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 +++++++ .../Objects/new_variable/parameter.py | 29 +++++++++++++++++-- .../fitting/minimizers/minimizer_base.py | 2 +- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index cf177f50..0fdd5aec 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -235,3 +235,13 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: raise ValueError(message) name = self._name + ' + ' + other._name return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value - other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' - ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index ce34ae88..5a832543 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -421,7 +421,7 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) -> Parameter: + def __add__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') try: @@ -430,8 +430,31 @@ def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) raise ValueError(message) min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name if not radd else other.name+" + "+self.name + name = self.name+" + "+other.name if not inverse else other.name+" + "+self.name return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__add__(other, radd=True) + return self.__add__(other, inverse=True) + + def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value - other.full_value if not inverse else other.full_value - self.full_value + except Exception as message: + raise ValueError(message) + if isinstance(other, Parameter): + if not inverse: + min_value = self.min - other.max + max_value = self.max - other.min + else: + min_value = other.min - self.max + max_value = other.max - self.min + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" - "+other.name if not inverse else other.name+" - "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__sub__(other, inverse=True) \ No newline at end of file diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 34662ca9..696587c2 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -31,7 +31,7 @@ class MinimizerBase(metaclass=ABCMeta): wrapping: str = None - def __init__(self, obj, fit_function: Callable, method: Optional[str] = None): # todo after constraint changes, add type hint: obj: BaseObj + def __init__(self, obj, fit_function: Callable, method: Optional[str] = None): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 if method not in self.available_methods(): raise FitError(f'Method {method} not available in {self.__class__}') self._object = obj From 0245578c990a7aeb75e75af8651ceb6c3510672c Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 13:24:20 +0200 Subject: [PATCH 03/68] __mul__ added to Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ .../Objects/new_variable/parameter.py | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 0fdd5aec..bd1d6193 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -244,4 +244,14 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: except Exception as message: raise ValueError(message) name = self._name + ' - ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value * other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' * ' + other._name return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 5a832543..de6d8634 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -457,4 +457,19 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = Fal return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__sub__(other, inverse=True) \ No newline at end of file + return self.__sub__(other, inverse=True) + + def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value * other.full_value + except Exception as message: + raise ValueError(message) + min_value = min(self.min * other.min, self.min * other.max, self.max * other.min) if isinstance(other, Parameter) else -np.Inf # noqa: E501 + max_value = max(self.max * other.max, self.min * other.min) if isinstance(other, Parameter) else np.Inf + name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__mul__(other, inverse=True) \ No newline at end of file From 076c8006e482b2ba7dc55136a0a2097d3b6889f5 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 22 Jul 2024 13:51:14 +0200 Subject: [PATCH 04/68] Allow additions between similar dimensions --- .../Objects/new_variable/descriptor_number.py | 29 +++++---- .../Objects/new_variable/parameter.py | 62 +++++++++++++++---- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index bd1d6193..5107f724 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -8,6 +8,7 @@ import numpy as np import scipp as sc +from scipp import UnitError from scipp import Variable from easyscience.global_object.undo_redo import property_stack_deco @@ -58,7 +59,7 @@ def __init__( try: self._scalar = sc.scalar(float(value), unit=unit, variance=variance) except Exception as message: - raise ValueError(message) + raise UnitError(message) super().__init__( name=name, unique_name=unique_name, @@ -193,8 +194,8 @@ def convert_unit(self, unit_str: str): raise TypeError(f'{unit_str=} must be a string representing a valid scipp unit') try: new_unit = sc.Unit(unit_str) - except Exception as message: - raise ValueError(message) + except UnitError as message: + raise UnitError(message) from None self._scalar = self._scalar.to(unit=new_unit) # Just to get return type right @@ -227,18 +228,21 @@ def as_dict(self) -> Dict[str, Any]: return raw_dict def __add__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented + original_unit = other.unit try: - new_value = self.full_value + other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + new_value = self.full_value + other.full_value name = self._name + ' + ' + other._name + other.convert_unit(original_unit) return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented try: new_value = self.full_value - other.full_value except Exception as message: @@ -247,9 +251,10 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented try: + other.convert_unit(self.unit) new_value = self.full_value * other.full_value except Exception as message: raise ValueError(message) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index de6d8634..82d51ed8 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -17,6 +17,7 @@ import numpy as np import scipp as sc +from scipp import UnitError from scipp import Variable from easyscience import global_object @@ -421,20 +422,35 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + def __add__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if not isinstance(other, DescriptorNumber): + return NotImplemented + original_unit = other.unit try: - new_value = self.full_value + other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + new_value = self.full_value + other.full_value min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name if not inverse else other.name+" + "+self.name + name = self.name+" + "+other.name + other.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__add__(other, inverse=True) + if not isinstance(other, DescriptorNumber): + return NotImplemented + 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = other.name+" + "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): @@ -463,13 +479,35 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = Fal if not issubclass(other.__class__, DescriptorNumber): raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') try: + other.convert_unit(self.unit) new_value = self.full_value * other.full_value except Exception as message: raise ValueError(message) - min_value = min(self.min * other.min, self.min * other.max, self.max * other.min) if isinstance(other, Parameter) else -np.Inf # noqa: E501 - max_value = max(self.max * other.max, self.min * other.min) if isinstance(other, Parameter) else np.Inf + combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] + min_value = min(combinations) if isinstance(other, Parameter) else -np.Inf + max_value = max(combinations) if isinstance(other, Parameter) else np.Inf name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__mul__(other, inverse=True) \ No newline at end of file + return self.__mul__(other, inverse=True) + + def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value / other.full_value if not inverse else other.full_value / self.full_value + except Exception as message: + raise ValueError(message) + if isinstance(other, Parameter): + if not inverse: + combinations = [self.min / other.max, self.max / other.min, self.min / other.min, self.max / other.max] + else: + combinations = [other.min / self.max, other.max / self.min, other.min / self.min, other.max / self.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" / "+other.name if not inverse else other.name+" / "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From f26644d95ada611dd740202e4e6e726795e6b57e Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 22 Jul 2024 16:29:40 +0200 Subject: [PATCH 05/68] Allow addition with scalars --- .../Objects/new_variable/descriptor_number.py | 35 ++++--- .../Objects/new_variable/parameter.py | 91 +++++++++++-------- .../Objects/new_variable/test_parameter.py | 30 +++++- 3 files changed, 106 insertions(+), 50 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 5107f724..972d99a6 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -228,26 +228,35 @@ def as_dict(self) -> Dict[str, Any]: return raw_dict def __add__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be added to dimensionless values") + self.value += other + return self + elif type(other) == 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 + new_value = self.full_value + other.full_value + name = self._name + ' + ' + other._name + other.convert_unit(original_unit) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: return NotImplemented - 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 - new_value = self.full_value + other.full_value - name = self._name + ' + ' + other._name - other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: return NotImplemented + original_unit = other.unit try: - new_value = self.full_value - other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + new_value = self.full_value - other.full_value name = self._name + ' - ' + other._name + other.convert_unit(original_unit) return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 82d51ed8..3db8a33a 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -422,58 +422,77 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + 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") + self.value += other + return self + elif isinstance(other, 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = self.name+" + "+other.name + other.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: + return NotImplemented + + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be added to dimensionless values") + self.value += other + return self + elif isinstance(other, DescriptorNumber): + 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = other.name+" + "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: + return NotImplemented + + def __sub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if not isinstance(other, DescriptorNumber): return NotImplemented 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 - new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + new_value = self.full_value - other.full_value + min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf + max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + name = self.name+" - "+other.name other.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + + def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if not isinstance(other, DescriptorNumber): return NotImplemented 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 - new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = other.name+" + "+self.name + raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None + new_value = other.full_value - self.full_value + min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf + max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + name = other.name+" - "+self.name self.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - new_value = self.full_value - other.full_value if not inverse else other.full_value - self.full_value - except Exception as message: - raise ValueError(message) - if isinstance(other, Parameter): - if not inverse: - min_value = self.min - other.max - max_value = self.max - other.min - else: - min_value = other.min - self.max - max_value = other.max - self.min - else: - min_value = -np.Inf - max_value = np.Inf - name = self.name+" - "+other.name if not inverse else other.name+" - "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__sub__(other, inverse=True) def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 1fb776f8..1cd2d3c3 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock import scipp as sc +import numpy as np from easyscience.Objects.new_variable.parameter import Parameter from easyscience import global_object @@ -334,4 +335,31 @@ def test_as_data_dict(self, clear, parameter: Parameter): "display_name": "display_name", "enabled": "enabled", "unique_name": "Parameter_0", - } \ No newline at end of file + } + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name + test", 3, "m", 0.02, -10, 30), Parameter("test + name", 3, "m", 0.02, -10, 30)), + (Parameter("test", 2, "m", 0.01), Parameter("name + test", 3, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test + name", 3, "m", 0.02, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))]) + def test_parameter_parameter_addition(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter + test + result_reverse = test + parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max \ No newline at end of file From ac3c93560dd75514c4a8735da64d836243512cd7 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 12:59:44 +0200 Subject: [PATCH 06/68] add tests for additions --- .../Objects/new_variable/descriptor_number.py | 15 +++- .../Objects/new_variable/parameter.py | 14 +++- .../new_variable/test_descriptor_number.py | 52 ++++++++++++- .../Objects/new_variable/test_parameter.py | 75 ++++++++++++++++++- 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 972d99a6..a15e9e30 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -231,8 +231,9 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + name = self.name + ' + ' + str(other) + return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -246,6 +247,16 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: else: return NotImplemented + 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") + new_value = other + self.value + name = str(other) + ' + ' + self.name + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + else: + return NotImplemented + def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: return NotImplemented diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 3db8a33a..fe60b9fa 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,8 +426,11 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + min_value = self.min + other + max_value = self.max + other + name = f"{self.name} + {other}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -447,8 +450,11 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + min_value = self.min + other + max_value = self.max + other + name = f"{other} + {self.name}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index c5f8a09a..17263465 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock import scipp as sc +from scipp import UnitError + from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber from easyscience import global_object @@ -198,4 +200,52 @@ def test_as_data_dict(self, clear, descriptor: DescriptorNumber): "url": "url", "display_name": "display_name", "unique_name": "DescriptorNumber_0", - } \ No newline at end of file + } + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test + name", 3, "m", 0.11)), + (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test + name", 102, "cm", 1000.01))], + ids=["regular", "unit_conversion"]) + def test_addition(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test + descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + assert descriptor.unit == 'm' + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_addition_with_scalar(self, scalar): + # When + descriptor = DescriptorNumber(name="name", value=1, variance=0.1) + + # Then + result = descriptor + scalar + result_reverse = scalar + descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name + " + str(scalar) + assert result.value == 2.0 + assert result.unit == "dimensionless" + assert result.variance == 0.1 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.1 + + @pytest.mark.parametrize("test", [1.0, DescriptorNumber("test", 2, "s",)], ids=["add_scalar_to_unit", "incompatible_units"]) + def test_addition_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(UnitError): + result = descriptor + test + with pytest.raises(UnitError): + result_reverse = test + descriptor + \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 1cd2d3c3..0d7e402b 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -3,7 +3,11 @@ import scipp as sc import numpy as np +from scipp import UnitError + from easyscience.Objects.new_variable.parameter import Parameter +from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber +from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object class TestParameter: @@ -340,8 +344,9 @@ def test_as_data_dict(self, clear, parameter: Parameter): @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name + test", 3, "m", 0.02, -10, 30), Parameter("test + name", 3, "m", 0.02, -10, 30)), (Parameter("test", 2, "m", 0.01), Parameter("name + test", 3, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test + name", 3, "m", 0.02, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))]) - def test_parameter_parameter_addition(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))], + ids=["regular", "no_bounds", "unit_conversion"]) + def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() @@ -362,4 +367,68 @@ def test_parameter_parameter_addition(self, parameter : Parameter, test : Parame assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max \ No newline at end of file + assert result_reverse.max == expected_reverse.max + + assert parameter.unit == "m" + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_addition_with_scalar(self, scalar): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=0, max=10) + + # Then + result = parameter + scalar + result_reverse = scalar + parameter + + # Expect + assert result.name == "name + " + str(scalar) + assert result.value == 2.0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == 1.0 + assert result.max == 11.0 + + assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == 1.0 + assert result_reverse.max == 11.0 + + def test_addition_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=1, variance=0.1, unit="cm") + + # Then + result = parameter + descriptor_number + result_reverse = descriptor_number + parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name + test" + assert result.value == 1.01 + assert result.unit == "m" + assert result.variance == 0.01001 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test + name" + assert result_reverse.value == 101.0 + assert result_reverse.unit == "cm" + assert result_reverse.variance == 100.1 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + assert parameter.unit == "m" + assert descriptor_number.unit == "cm" + + @pytest.mark.parametrize("test", [1.0, Parameter("test", 2, "s",)], ids=["add_scalar_to_unit", "incompatible_units"]) + def test_addition_exception(self, parameter : Parameter, test): + # When Then Expect + with pytest.raises(UnitError): + result = parameter + test + with pytest.raises(UnitError): + result_reverse = test + parameter + \ No newline at end of file From 67d6011e80cab92406a50cf60aff741804415426 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 13:06:36 +0200 Subject: [PATCH 07/68] subtraction with scalars --- .../Objects/new_variable/descriptor_number.py | 37 +++++++--- .../Objects/new_variable/parameter.py | 68 ++++++++++++------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index a15e9e30..68c74f7f 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -258,17 +258,34 @@ def __radd__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be subtracted from dimensionless values") + new_value = self.value - other + name = self.name + ' - ' + str(other) + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + elif type(other) == 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 + new_value = self.full_value - other.full_value + name = self._name + ' - ' + other._name + other.convert_unit(original_unit) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: + return NotImplemented + + 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") + new_value = other - self.value + name = str(other) + ' - ' + self.name + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + else: return NotImplemented - 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 - new_value = self.full_value - other.full_value - name = self._name + ' - ' + other._name - other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index fe60b9fa..7a8d723b 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -470,35 +470,53 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: else: return NotImplemented - def __sub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - if not isinstance(other, DescriptorNumber): + 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") + new_value = self.value - other + min_value = self.min - other + max_value = self.max - other + name = f"{self.name} - {other}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) + elif isinstance(other, 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 + new_value = self.full_value - other.full_value + min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf + max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + name = self.name+" - "+other.name + other.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: return NotImplemented - 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 - new_value = self.full_value - other.full_value - min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf - max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf - name = self.name+" - "+other.name - other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - if not isinstance(other, DescriptorNumber): + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be subtracted from dimensionless values") + new_value = other - self.value + min_value = other - self.min + max_value = other - self.max + name = f"{other} - {self.name}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): + 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 + new_value = other.full_value - self.full_value + min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf + max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + name = other.name+" - "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: return NotImplemented - 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 - new_value = other.full_value - self.full_value - min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf - max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf - name = other.name+" - "+self.name - self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): From 85449b47bc9194a8bded7b339771d120a0c788d2 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 15:19:13 +0200 Subject: [PATCH 08/68] tests for subtraction --- .../Objects/new_variable/parameter.py | 4 +- .../new_variable/test_descriptor_number.py | 48 +++++++++- .../Objects/new_variable/test_parameter.py | 92 ++++++++++++++++++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 7a8d723b..a38188d5 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -499,8 +499,8 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if self.unit != 'dimensionless': raise UnitError("Numbers can only be subtracted from dimensionless values") new_value = other - self.value - min_value = other - self.min - max_value = other - self.max + min_value = other - self.max + max_value = other - self.min name = f"{other} - {self.name}" return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 17263465..1fa22eae 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -248,4 +248,50 @@ def test_addition_exception(self, descriptor: DescriptorNumber, test): result = descriptor + test with pytest.raises(UnitError): result_reverse = test + descriptor - \ No newline at end of file + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test - name", 1, "m", 0.11)), + (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test - name", -98, "cm", 1000.01))], + ids=["regular", "unit_conversion"]) + def test_subtraction(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test - descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + assert descriptor.unit == 'm' + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_subtraction_with_scalar(self, scalar): + # When + descriptor = DescriptorNumber(name="name", value=2, variance=0.1) + + # Then + result = descriptor - scalar + result_reverse = scalar - descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name - " + str(scalar) + assert result.value == 1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.1 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.value == -1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.1 + + @pytest.mark.parametrize("test", [1.0, DescriptorNumber("test", 2, "s",)], ids=["sub_scalar_to_unit", "incompatible_units"]) + def test_subtraction_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(UnitError): + result = test - descriptor + with pytest.raises(UnitError): + result_reverse = descriptor - test \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 0d7e402b..6c712f47 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -431,4 +431,94 @@ def test_addition_exception(self, parameter : Parameter, test): result = parameter + test with pytest.raises(UnitError): result_reverse = test + parameter - \ No newline at end of file + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -20, 20), Parameter("name - test", -1, "m", 0.02, -20, 30), Parameter("test - name", 1, "m", 0.02, -30, 20)), + (Parameter("test", 2, "m", 0.01), Parameter("name - test", -1, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test - name", 1, "m", 0.02, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name - test", 0.98, "m", 0.010001, -0.1, 10.1), Parameter("test - name", -98, "cm", 100.01, -1010, 10))], + ids=["regular", "no_bounds", "unit_conversion"]) + def test_subtraction_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter - test + result_reverse = test - parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + assert parameter.unit == "m" + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_subtraction_with_scalar(self, scalar): + # When + parameter = Parameter(name="name", value=2, variance=0.01, min=0, max=10) + + # Then + result = parameter - scalar + result_reverse = scalar - parameter + + # Expect + assert result.name == "name - " + str(scalar) + assert result.value == 1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == -1.0 + assert result.max == 9.0 + + assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.value == -1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == -9.0 + assert result_reverse.max == 1.0 + + def test_subtraction_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=1, variance=0.1, unit="cm") + + # Then + result = parameter - descriptor_number + result_reverse = descriptor_number - parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name - test" + assert result.value == 0.99 + assert result.unit == "m" + assert result.variance == 0.01001 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test - name" + assert result_reverse.value == -99.0 + assert result_reverse.unit == "cm" + assert result_reverse.variance == 100.1 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + assert parameter.unit == "m" + assert descriptor_number.unit == "cm" + + @pytest.mark.parametrize("test", [1.0, Parameter("test", 2, "s",)], ids=["sub_scalar_to_unit", "incompatible_units"]) + def test_subtraction_exception(self, parameter : Parameter, test): + # When Then Expect + with pytest.raises(UnitError): + result = parameter - test + with pytest.raises(UnitError): + result_reverse = test - parameter \ No newline at end of file From f6763efbd784a87d38ea4e3355d874ccb42fdf69 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 24 Jul 2024 11:10:44 +0200 Subject: [PATCH 09/68] multiplication with scalars --- .../Objects/new_variable/descriptor_number.py | 34 ++++++++--- .../Objects/new_variable/parameter.py | 59 ++++++++++++++----- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 68c74f7f..79109c46 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -288,12 +288,30 @@ def __rsub__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: - return NotImplemented - try: - other.convert_unit(self.unit) + if isinstance(other, numbers.Number): + new_value = self.full_value * other + name = self.name + ' * ' + str(other) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + elif type(other) == DescriptorNumber: new_value = self.full_value * other.full_value - except Exception as message: - raise ValueError(message) - name = self._name + ' * ' + other._name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file + name = self._name + ' * ' + other._name + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(self._base_unit()) + return descriptor_number + else: + return NotImplemented + + def __rmul__(self, other: numbers.Number) -> DescriptorNumber: + if isinstance(other, numbers.Number): + new_value = other * self.full_value + name = str(other) + ' * ' + self.name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: + return NotImplemented + + def _base_unit(self) -> str: + string = str(self._scalar.unit) + for i in range(len(string)): + if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: + return string[i+1:-1] + raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index a38188d5..41a92ade 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -518,22 +518,53 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: else: return NotImplemented - def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - other.convert_unit(self.unit) + def __mul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if isinstance(other, numbers.Number): + new_value = self.full_value * other + combinations = [self.min * other, self.max * other] + min_value = min(combinations) + max_value = max(combinations) + name = f"{self.name} * {other}" + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value - except Exception as message: - raise ValueError(message) - combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] - min_value = min(combinations) if isinstance(other, Parameter) else -np.Inf - max_value = max(combinations) if isinstance(other, Parameter) else np.Inf - name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - + if isinstance(other, Parameter): + combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" * "+other.name + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(self._base_unit()) + return parameter + else: + return NotImplemented + def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__mul__(other, inverse=True) + if isinstance(other, numbers.Number): + new_value = other * self.full_value + combinations = [other * self.min, other * self.max] + min_value = min(combinations) + max_value = max(combinations) + name = f"{other} * {self.name}" + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): + new_value = other.full_value * self.full_value + if isinstance(other, Parameter): + combinations = [other.min * self.max, other.max * self.min, other.min * self.min, other.max * self.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = other.name+" * "+self.name + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(self._base_unit()) + return parameter + else: + return NotImplemented def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): From f543f717b163bcfbe96aaeaae331a3a422135388 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 25 Jul 2024 13:17:04 +0200 Subject: [PATCH 10/68] tests for multiplication plus cleanup --- .../Objects/new_variable/descriptor_number.py | 35 ++-- .../Objects/new_variable/parameter.py | 92 +++++---- .../new_variable/test_descriptor_number.py | 57 ++++-- .../Objects/new_variable/test_parameter.py | 179 ++++++++++++++++-- 4 files changed, 262 insertions(+), 101 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 79109c46..03526643 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -227,13 +227,12 @@ def as_dict(self) -> Dict[str, Any]: raw_dict['variance'] = self._scalar.variance return raw_dict - def __add__(self, other: DescriptorNumber) -> DescriptorNumber: + 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.value + other + new_value = self.full_value + other name = self.name + ' + ' + str(other) - return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -243,27 +242,26 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: new_value = self.full_value + other.full_value name = self._name + ' + ' + other._name other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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") - new_value = other + self.value + new_value = other + self.full_value name = str(other) + ' + ' + self.name - return DescriptorNumber(name=name, value=new_value, variance=self.variance) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) - def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: + 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") - new_value = self.value - other + new_value = self.full_value - other name = self.name + ' - ' + str(other) - return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -273,45 +271,44 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: new_value = self.full_value - other.full_value name = self._name + ' - ' + other._name other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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") - new_value = other - self.value + new_value = other - self.full_value name = str(other) + ' - ' + self.name - return DescriptorNumber(name=name, value=new_value, variance=self.variance) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) - def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: + def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = self.full_value * other name = self.name + ' * ' + str(other) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif type(other) == DescriptorNumber: new_value = self.full_value * other.full_value name = self._name + ' * ' + other._name - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) - descriptor_number.convert_unit(self._base_unit()) - return descriptor_number else: return NotImplemented + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(descriptor_number._base_unit()) + return descriptor_number def __rmul__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = other * self.full_value name = str(other) + ' * ' + self.name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: - return string[i+1:-1] + return string[i:] raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 41a92ade..7f75eab7 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,11 +426,10 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - new_value = self.value + other + new_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -438,23 +437,22 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + 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 name = self.name+" + "+other.name other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> 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") - new_value = self.value + other + new_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: @@ -462,23 +460,22 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be added") from None new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + min_value = self.min + other.value + max_value = self.max + other.value name = other.name+" + "+self.name self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) 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") - new_value = self.value - other + new_value = self.full_value - other min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -486,23 +483,26 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_value = self.full_value - other.full_value - min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf - max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + if isinstance(other, Parameter): + min_value = self.min - other.max if other.max != np.Inf else -np.Inf + max_value = self.max - other.min if other.min != -np.Inf else np.Inf + else: + min_value = self.min - other.value + max_value = self.max - other.value name = self.name+" - "+other.name other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> 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") - new_value = other - self.value + new_value = other - self.full_value min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: @@ -510,61 +510,57 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None new_value = other.full_value - self.full_value - min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf - max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + min_value = other.value - self.max + max_value = other.value - self.min name = other.name+" - "+self.name self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __mul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = self.full_value * other combinations = [self.min * other, self.max * other] - min_value = min(combinations) - max_value = max(combinations) name = f"{self.name} * {other}" - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value if isinstance(other, Parameter): - combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] - min_value = min(combinations) - max_value = max(combinations) + combinations = [] + for first, second in [(self.min, other.min), (self.min, other.max), (self.max, other.min), (self.max, other.max)]: # noqa: E501 + if (first == np.Inf and second == 0) or (first == 0 and second == np.Inf): + combinations.append(np.Inf) + elif (first == -np.Inf and second == 0) or (first == 0 and second == -np.Inf): + combinations.append(-np.Inf) + else: + combinations.append(first * second) else: - min_value = -np.Inf - max_value = np.Inf + combinations = [self.min * other.value, self.max * other.value] name = self.name+" * "+other.name - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - parameter.convert_unit(self._base_unit()) - return parameter else: return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter - def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = other * self.full_value combinations = [other * self.min, other * self.max] - min_value = min(combinations) - max_value = max(combinations) name = f"{other} * {self.name}" - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - if isinstance(other, Parameter): - combinations = [other.min * self.max, other.max * self.min, other.min * self.min, other.max * self.max] - min_value = min(combinations) - max_value = max(combinations) - else: - min_value = -np.Inf - max_value = np.Inf + combinations = [self.min * other.value, self.max * other.value] name = other.name+" * "+self.name - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - parameter.convert_unit(self._base_unit()) - return parameter else: return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 1fa22eae..5b07f405 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -219,24 +219,23 @@ def test_addition(self, descriptor: DescriptorNumber, test, expected): assert descriptor.unit == 'm' - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_addition_with_scalar(self, scalar): + def test_addition_with_scalar(self): # When descriptor = DescriptorNumber(name="name", value=1, variance=0.1) # Then - result = descriptor + scalar - result_reverse = scalar + descriptor + result = descriptor + 1.0 + result_reverse = 1.0 + descriptor # Expect assert type(result) == DescriptorNumber - assert result.name == "name + " + str(scalar) + assert result.name == "name + 1.0" assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.name == "1.0 + name" assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -266,24 +265,23 @@ def test_subtraction(self, descriptor: DescriptorNumber, test, expected): assert descriptor.unit == 'm' - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_subtraction_with_scalar(self, scalar): + def test_subtraction_with_scalar(self): # When descriptor = DescriptorNumber(name="name", value=2, variance=0.1) # Then - result = descriptor - scalar - result_reverse = scalar - descriptor + result = descriptor - 1.0 + result_reverse = 1.0 - descriptor # Expect assert type(result) == DescriptorNumber - assert result.name == "name - " + str(scalar) + assert result.name == "name - 1.0" assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.name == "1.0 - name" assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -294,4 +292,37 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): with pytest.raises(UnitError): result = test - descriptor with pytest.raises(UnitError): - result_reverse = descriptor - test \ No newline at end of file + result_reverse = descriptor - test + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], + ids=["regular", "base_unit_conversion"]) + def test_multiplication(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test * descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): + # When Then + result = descriptor * 2.0 + result_reverse = 2.0 * descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name * 2.0" + assert result.value == 2.0 + assert result.unit == "m" + assert result.variance == 0.4 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == "2.0 * name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "m" + assert result_reverse.variance == 0.4 \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 6c712f47..f85ecf46 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -371,24 +371,23 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, assert parameter.unit == "m" - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_addition_with_scalar(self, scalar): + def test_addition_with_scalar(self): # When parameter = Parameter(name="name", value=1, variance=0.01, min=0, max=10) # Then - result = parameter + scalar - result_reverse = scalar + parameter + result = parameter + 1.0 + result_reverse = 1.0 + parameter # Expect - assert result.name == "name + " + str(scalar) + assert result.name == "name + 1.0" assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == 1.0 assert result.max == 11.0 - assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.name == "1.0 + name" assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -410,16 +409,16 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): assert result.value == 1.01 assert result.unit == "m" assert result.variance == 0.01001 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.min == 0.01 + assert result.max == 10.01 assert type(result_reverse) == Parameter assert result_reverse.name == "test + name" assert result_reverse.value == 101.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.min == 1 + assert result_reverse.max == 1001 assert parameter.unit == "m" assert descriptor_number.unit == "cm" @@ -462,24 +461,47 @@ def test_subtraction_with_parameter(self, parameter : Parameter, test : Paramete assert parameter.unit == "m" - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_subtraction_with_scalar(self, scalar): + def test_subtraction_with_parameter_nan_cases(self): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) + test = Parameter(name="test", value=2, variance=0.01, min=-np.Inf, max=np.Inf) + + # Then + result = parameter - test + result_reverse = test - parameter + + # Expect + assert result.name == "name - test" + assert result.value == -1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.02 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert result_reverse.name == "test - name" + assert result_reverse.value == 1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.02 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + def test_subtraction_with_scalar(self): # When parameter = Parameter(name="name", value=2, variance=0.01, min=0, max=10) # Then - result = parameter - scalar - result_reverse = scalar - parameter + result = parameter - 1.0 + result_reverse = 1.0 - parameter # Expect - assert result.name == "name - " + str(scalar) + assert result.name == "name - 1.0" assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == -1.0 assert result.max == 9.0 - assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.name == "1.0 - name" assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -501,16 +523,16 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): assert result.value == 0.99 assert result.unit == "m" assert result.variance == 0.01001 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.min == -0.01 + assert result.max == 9.99 assert type(result_reverse) == Parameter assert result_reverse.name == "test - name" assert result_reverse.value == -99.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.min == -999 + assert result_reverse.max == 1 assert parameter.unit == "m" assert descriptor_number.unit == "cm" @@ -521,4 +543,119 @@ def test_subtraction_exception(self, parameter : Parameter, test): with pytest.raises(UnitError): result = parameter - test with pytest.raises(UnitError): - result_reverse = test - parameter \ No newline at end of file + result_reverse = test - parameter + + # parameter = Parameter( + # name="name", + # value=1, + # unit="m", + # variance=0.01, + # min=0, + # max=10, + # description="description", + # url="url", + # display_name="display_name", + # callback=self.mock_callback, + # enabled="enabled", + # parent=None, + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], + ids=["regular", "no_bounds", "base_unit_conversion"]) + def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter * test + result_reverse = test * parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + def test_multiplication_with_parameter_nan_cases(self): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) + test = Parameter(name="test", value=0, variance=0.01, min=0, max=0) + + # Then + result = parameter * test + result_reverse = test * parameter + + # Expect + assert result.name == "name * test" + assert result.value == 0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert result_reverse.name == "test * name" + assert result_reverse.value == 0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + def test_multiplication_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=2, variance=0.1, unit="cm") + + # Then + result = parameter * descriptor_number + result_reverse = descriptor_number * parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name * test" + assert result.value == 2 + assert result.unit == "dm^2" + assert result.variance == 0.14 + assert result.min == 0 + assert result.max == 20 + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test * name" + assert result_reverse.value == 2 + assert result_reverse.unit == "dm^2" + assert result_reverse.variance == 0.14 + assert result_reverse.min == 0 + assert result_reverse.max == 20 + + def test_multiplication_with_scalar(self, parameter : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter * 2 + result_reverse = 2 * parameter + + # Expect + assert result.name == "name * 2" + assert result.value == 2.0 + assert result.unit == "m" + assert result.variance == 0.04 + assert result.min == 0.0 + assert result.max == 20.0 + + assert result_reverse.name == "2 * name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "m" + assert result_reverse.variance == 0.04 + assert result_reverse.min == 0.0 + assert result_reverse.max == 20.0 \ No newline at end of file From 4b8c0af2812e84607c29fc37d1543b00c8464f7b Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 26 Jul 2024 15:46:07 +0200 Subject: [PATCH 11/68] Divion methods added --- .../Objects/new_variable/descriptor_number.py | 31 ++++++ .../Objects/new_variable/parameter.py | 104 +++++++++++++++--- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 03526643..3f62e30a 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -15,6 +15,7 @@ from .descriptor_base import DescriptorBase +INFINITESIMAL = 1e-9 class DescriptorNumber(DescriptorBase): """ @@ -306,6 +307,36 @@ def __rmul__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: + if isinstance(other, numbers.Number): + original_other = other + if other == 0: + other = INFINITESIMAL + new_value = self.full_value / other + name = self.name + ' / ' + str(original_other) + elif type(other) == DescriptorNumber: + original_other = other.value + if original_other == 0: + other.value = INFINITESIMAL + new_value = self.full_value / other.full_value + other.value = original_other + name = self._name + ' / ' + other._name + else: + return NotImplemented + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(descriptor_number._base_unit()) + return descriptor_number + + def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: + if isinstance(other, numbers.Number): + if self.value == 0: + self.value = INFINITESIMAL + new_value = other / self.full_value + name = str(other) + ' / ' + self.name + else: + return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 7f75eab7..e04d62e0 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -26,6 +26,7 @@ from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Utils.Exceptions import CoreSetException +from .descriptor_number import INFINITESIMAL from .descriptor_number import DescriptorNumber Constraints = namedtuple('Constraints', ['user', 'builtin', 'virtual']) @@ -86,6 +87,8 @@ def __init__( raise ValueError(f'{value=} can not be less than {min=}') if value > max: raise ValueError(f'{value=} can not be greater than {max=}') + if min == max: + 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') @@ -247,6 +250,8 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') + if min_value == self._max.value: + raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: self._min.value = min_value else: @@ -273,6 +278,8 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') + if max_value == self._min.value: + raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: self._max.value = max_value else: @@ -562,22 +569,85 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: parameter.convert_unit(parameter._base_unit()) return parameter - def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - new_value = self.full_value / other.full_value if not inverse else other.full_value / self.full_value - except Exception as message: - raise ValueError(message) - if isinstance(other, Parameter): - if not inverse: - combinations = [self.min / other.max, self.max / other.min, self.min / other.min, self.max / other.max] + def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: + if isinstance(other, numbers.Number): + original_other = other + if other == 0: + other = INFINITESIMAL + new_value = self.full_value / other + combinations = [self.min / other, self.max / other] + name = f"{self.name} / {original_other}" + elif isinstance(other, DescriptorNumber): + original_value = other.value + if original_value == 0: + other.value = INFINITESIMAL + new_value = self.full_value / other.full_value + if isinstance(other, Parameter): + 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): + combinations = [-np.Inf, np.Inf] + elif self.min >= 0: + combinations = [self.min/other.max, np.Inf] + elif self.max <= 0: + combinations = [-np.Inf, self.max/other.max] + elif other.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] + elif self.max <= 0: + 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] else: - combinations = [other.min / self.max, other.max / self.min, other.min / self.min, other.max / self.max] - min_value = min(combinations) - max_value = max(combinations) + combinations = [self.min / other.value, self.max / other.value] + name = self.name+" / "+other.name + other.value = original_value + else: + return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter + + def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: + original_self = self.value + if original_self == 0: + self.value = INFINITESIMAL + if isinstance(other, numbers.Number): + new_value = other / self.full_value + other_value = other + name = f"{other} / {self.name}" + if other_value == 0: + return DescriptorNumber.from_scipp(name=name, value=new_value) + elif isinstance(other, DescriptorNumber): + new_value = other.full_value / self.full_value + other_value = other.value + name = other.name+" / "+self.name + if other_value == 0: + return DescriptorNumber.from_scipp(name=name, value=new_value) + else: + return NotImplemented + 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] + elif other_value < 0: + combinations = [-np.Inf, other_value/self.max] + elif self.max == 0: + if other_value > 0: + combinations = [-np.Inf, other_value/self.min] + elif other_value < 0: + combinations = [other_value/self.min, np.Inf] else: - min_value = -np.Inf - max_value = np.Inf - name = self.name+" / "+other.name if not inverse else other.name+" / "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file + combinations = [other_value / self.min, other_value / self.max] + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + self.value = original_self + return parameter \ No newline at end of file From 587f653a0cb703953d85405d82f60c1497a389f1 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 26 Jul 2024 15:55:05 +0200 Subject: [PATCH 12/68] account for unit cancellation in base_unit method --- .../Objects/new_variable/descriptor_number.py | 2 +- .../Objects/new_variable/test_descriptor_number.py | 5 +++-- tests/unit_tests/Objects/new_variable/test_parameter.py | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 3f62e30a..4567b153 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -342,4 +342,4 @@ def _base_unit(self) -> str: for i in range(len(string)): if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: return string[i:] - raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file + return "" \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 5b07f405..f6a3578e 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -296,8 +296,9 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), - (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], - ids=["regular", "base_unit_conversion"]) + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041)), + (DescriptorNumber("test", 2, "1/dm", 0.01), DescriptorNumber("test * name", 20.0, "dimensionless", 41))], + ids=["regular", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # When Then result = test * descriptor diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index f85ecf46..17f318bf 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -560,10 +560,11 @@ def test_subtraction_exception(self, parameter : Parameter, test): # parent=None, @pytest.mark.parametrize("test, expected, expected_reverse", [ - (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), - (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], - ids=["regular", "no_bounds", "base_unit_conversion"]) + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), + (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], + ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() From ffae7df409bf35d17a3588279b276b068bcb05bb Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 29 Jul 2024 11:41:29 +0200 Subject: [PATCH 13/68] account for multiplication with 0 --- .../Objects/new_variable/parameter.py | 24 ++-- .../Objects/new_variable/test_parameter.py | 121 ++++++++++-------- 2 files changed, 84 insertions(+), 61 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index e04d62e0..38449572 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -528,22 +528,26 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = self.full_value * other - combinations = [self.min * other, self.max * other] name = f"{self.name} * {other}" + if other == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value + name = self.name+" * "+other.name + if other.value == 0 and type(other) == DescriptorNumber: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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 - if (first == np.Inf and second == 0) or (first == 0 and second == np.Inf): - combinations.append(np.Inf) - elif (first == -np.Inf and second == 0) or (first == 0 and second == -np.Inf): - combinations.append(-np.Inf) + if first == 0 and np.isinf(second): + combinations.append(0) + elif second == 0 and np.isinf(first): + combinations.append(0) else: combinations.append(first * second) else: combinations = [self.min * other.value, self.max * other.value] - name = self.name+" * "+other.name else: return NotImplemented min_value = min(combinations) @@ -555,12 +559,16 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = other * self.full_value - combinations = [other * self.min, other * self.max] name = f"{other} * {self.name}" + if other == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - combinations = [self.min * other.value, self.max * other.value] name = other.name+" * "+self.name + if other.value == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 17f318bf..73dbf24c 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -560,10 +560,10 @@ def test_subtraction_exception(self, parameter : Parameter, test): # parent=None, @pytest.mark.parametrize("test, expected, expected_reverse", [ - (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), - (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), - (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), + (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When @@ -588,75 +588,90 @@ def test_multiplication_with_parameter(self, parameter : Parameter, test : Param assert result_reverse.min == expected_reverse.min assert result_reverse.max == expected_reverse.max - def test_multiplication_with_parameter_nan_cases(self): + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 0, "", 0.01, -10, 0), Parameter("name * test", 0.0, "dimensionless", 0.01, -np.Inf, 0), Parameter("test * name", 0, "dimensionless", 0.01, -np.Inf, 0)), + (Parameter("test", 0, "", 0.01, 0, 10), Parameter("name * test", 0.0, "dimensionless", 0.01, 0, np.Inf), Parameter("test * name", 0, "dimensionless", 0.01, 0, np.Inf))], + ids=["zero_min", "zero_max"]) + def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_reverse): # When - parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) - test = Parameter(name="test", value=0, variance=0.01, min=0, max=0) + parameter = Parameter(name="name", value=1, variance=0.01, min=1, max=np.Inf) # Then result = parameter * test result_reverse = test * parameter # Expect - assert result.name == "name * test" - assert result.value == 0 - assert result.unit == "dimensionless" - assert result.variance == 0.01 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max - assert result_reverse.name == "test * name" - assert result_reverse.value == 0 - assert result_reverse.unit == "dimensionless" - assert result_reverse.variance == 0.01 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max - def test_multiplication_with_descriptor_number(self, parameter : Parameter): + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), + (DescriptorNumber(name="test", value=0, variance=0.1, unit="cm"), DescriptorNumber("name * test", 0, "dm^2", 0.1), DescriptorNumber("test * name", 0, "dm^2", 0.1))], + ids=["regular", "zero_value"]) + def test_multiplication_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() - descriptor_number = DescriptorNumber(name="test", value=2, variance=0.1, unit="cm") # Then - result = parameter * descriptor_number - result_reverse = descriptor_number * parameter + result = parameter * test + result_reverse = test * parameter # Expect - assert type(result) == Parameter - assert result.name == "name * test" - assert result.value == 2 - assert result.unit == "dm^2" - assert result.variance == 0.14 - assert result.min == 0 - assert result.max == 20 + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max - assert type(result_reverse) == Parameter - assert result_reverse.name == "test * name" - assert result_reverse.value == 2 - assert result_reverse.unit == "dm^2" - assert result_reverse.variance == 0.14 - assert result_reverse.min == 0 - assert result_reverse.max == 20 - - def test_multiplication_with_scalar(self, parameter : Parameter): + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, Parameter("name * 2", 2, "m", 0.04, 0, 20), Parameter("2 * name", 2, "m", 0.04, 0, 20)), + (0, DescriptorNumber("name * 0", 0, "m", 0), DescriptorNumber("0 * name", 0, "m", 0))], + ids=["regular", "zero_value"]) + def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() # Then - result = parameter * 2 - result_reverse = 2 * parameter + result = parameter * test + result_reverse = test * parameter # Expect - assert result.name == "name * 2" - assert result.value == 2.0 - assert result.unit == "m" - assert result.variance == 0.04 - assert result.min == 0.0 - assert result.max == 20.0 + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max - assert result_reverse.name == "2 * name" - assert result_reverse.value == 2.0 - assert result_reverse.unit == "m" - assert result_reverse.variance == 0.04 - assert result_reverse.min == 0.0 - assert result_reverse.max == 20.0 \ No newline at end of file + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max \ No newline at end of file From 0a96a87ee11266a31b875754a7dfc807ad3bb0e6 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 30 Jul 2024 14:27:50 +0200 Subject: [PATCH 14/68] Add unittests for divisions --- .../Objects/new_variable/parameter.py | 13 +- src/easyscience/unit_aliases.py | 12 ++ .../new_variable/test_descriptor_number.py | 42 ++++- .../Objects/new_variable/test_parameter.py | 154 ++++++++++++++++-- 4 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 src/easyscience/unit_aliases.py diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 38449572..ee7b55f6 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,8 +426,9 @@ def __repr__(self) -> str: s.append('bounds=[%s:%s]' % (repr(self.min), repr(self.max))) return '%s>' % ', '.join(s) - def __float__(self) -> float: - return float(self._scalar.value) + # 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): @@ -591,7 +592,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) other.value = INFINITESIMAL new_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): @@ -630,16 +631,16 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame other_value = other name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif isinstance(other, DescriptorNumber): new_value = other.full_value / self.full_value other_value = other.value name = other.name+" / "+self.name if other_value == 0: - return DescriptorNumber.from_scipp(name=name, value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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: diff --git a/src/easyscience/unit_aliases.py b/src/easyscience/unit_aliases.py new file mode 100644 index 00000000..297dd0e4 --- /dev/null +++ b/src/easyscience/unit_aliases.py @@ -0,0 +1,12 @@ +import scipp as sc + +# This document shows how to define aliases for units in scipp, +# and how to overwrite existing aliases. + +sc.units.aliases.clear() # Clear existing aliases +sc.units.aliases['funny_unit'] = sc.scalar(42.0, unit='m') +# ^ 'funny_unit' is now an alias for 42 m and can be used as a unit in scipp and be converted to units of dimension 'm' + +# The representation of a unit can be changed by defining an alias for it: +sc.units.aliases['m/ms'] = 'm/ms' +# Setting this alias ensures that 'm/ms' is displayed as 'm/ms' instead of the default 'km/s' \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index f6a3578e..d58ca799 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock import scipp as sc +from scipp import UnitError from scipp import UnitError @@ -58,7 +59,7 @@ def test_init_sc_unit(self): def test_init_sc_unit_unknown(self): # When Then Expect - with pytest.raises(ValueError): + with pytest.raises(UnitError): DescriptorNumber( name="name", value=1, @@ -326,4 +327,41 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): assert result_reverse.name == "2.0 * name" assert result_reverse.value == 2.0 assert result_reverse.unit == "m" - assert result_reverse.variance == 0.4 \ No newline at end of file + assert result_reverse.variance == 0.4 + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625)), + (DescriptorNumber("test", 0, "m^2", 0.01), DescriptorNumber("name / test", 1e9, "1/m", 1e34))], + ids=["regular", "zero_value"]) + def test_division(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = descriptor / test + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4)), + (0, DescriptorNumber("name / 0", 1e9, "m", 1e17), DescriptorNumber("0 / name", 0, "1/m", 0))], + ids=["regular", "zero_value"]) + def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected, expected_reverse): + # When Then + result = descriptor / test + result_reverse = test / descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 73dbf24c..63322fd6 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,6 +7,7 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber +from easyscience.Objects.new_variable.descriptor_number import INFINITESIMAL from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object @@ -168,9 +169,10 @@ def test_set_error_exception(self, parameter: Parameter): with pytest.raises(ValueError): parameter.error = -0.1 - def test_float(self, parameter: Parameter): - # When Then Expect - assert float(parameter) == 1.0 + # Commented out because __float__ method might be removed + # def test_float(self, parameter: Parameter): + # # When Then Expect + # assert float(parameter) == 1.0 def test_repr(self, parameter: Parameter): # When Then Expect @@ -545,20 +547,6 @@ def test_subtraction_exception(self, parameter : Parameter, test): with pytest.raises(UnitError): result_reverse = test - parameter - # parameter = Parameter( - # name="name", - # value=1, - # unit="m", - # variance=0.01, - # min=0, - # max=10, - # description="description", - # url="url", - # display_name="display_name", - # callback=self.mock_callback, - # enabled="enabled", - # parent=None, - @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), @@ -674,4 +662,134 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, assert result_reverse.variance == expected_reverse.variance if isinstance(result_reverse, Parameter): assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max \ No newline at end of file + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), + (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), + (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0)), + (Parameter("test", 0, "s", 0.01, -10, 20), Parameter("name / test", 1e9, "m/s", 1e34, -np.Inf, np.Inf), Parameter("test / name", 0, "s/m", 0.01, -np.Inf, np.Inf))], + ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative", "zero_value"]) + def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == Parameter + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + assert result.min == expected.min + assert result.max == expected.max + + assert type(result) == Parameter + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("first, second, expected", [ + (Parameter("name", 1, "m", 0.01, -10, 20), Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, np.Inf)), + (Parameter("name", -10, "m", 0.01, -20, -10), Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", 5.0, "m/s", 0.065, 1, np.Inf)), + (Parameter("name", 10, "m", 0.01, 10, 20), Parameter("test", -20, "s", 0.01, -20, -10), Parameter("name / test", -0.5, "m/s", 3.125e-5, -2, -0.5))], + ids=["first_crossing_zero_second_negative_0", "both_negative_second_negative_0", "finite_limits"]) + def test_division_with_parameter_remaining_cases(self, first, second, expected): + # When Then + result = first / second + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (DescriptorNumber(name="test", value=2, variance=0.1, unit="dm^2"), Parameter("name / test", 0.5, "cm**-1", 0.00875, 0, 5), Parameter("test / name", 2.0, "cm", 0.14, 0.2, np.Inf)), + (DescriptorNumber(name="test", value=0, variance=0.1, unit="dm^2"), Parameter("name / test", 1e9, "cm**-1,", 1e35, 0, 1e10), DescriptorNumber("test / name", 0.0, "cm", 0.1))], + ids=["regular", "zero_value"]) + def test_division_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("first, second, expected", [ + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, -10, 10), Parameter("name / test", 0.5, "m/s", 0.00875, -np.Inf, np.Inf)), + (DescriptorNumber("name", -1, "m", 0.01), Parameter("test", 2, "s", 0.1, 0, 10), Parameter("name / test", -0.5, "m/s", 0.00875, -np.Inf, -0.1)), + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", -2, "s", 0.1, -10, 0), Parameter("name / test", -0.5, "m/s", 0.00875, -np.Inf, -0.1)), + (DescriptorNumber("name", -1, "m", 0.01), Parameter("test", -2, "s", 0.1, -10, 0), Parameter("name / test", 0.5, "m/s", 0.00875, 0.1, np.Inf)), + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, 1, 10), Parameter("name / test", 0.5, "m/s", 0.00875, 0.1, 1))], + ids=["crossing_zero", "positive_0_with_negative", "negative_0_with_positive", "negative_0_with_negative", "finite_limits"]) + def test_division_with_descriptor_number_missing_cases(self, first, second, expected): + # When Then + result = first / second + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf)), + (0, Parameter("name / 0", 1/INFINITESIMAL, "m", (1/(INFINITESIMAL*10))**2, 0, 1/INFINITESIMAL*10), DescriptorNumber("0 / name", 0.0, "m**-1", 0.0))], + ids=["regular", "zero_value"]) + def test_division_with_numbers(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == pytest.approx(expected.max) + + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == pytest.approx(expected_reverse.max) \ No newline at end of file From dc617bfe1e71ed786e0c17f4fb24f871f303370d Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 30 Jul 2024 15:30:28 +0200 Subject: [PATCH 15/68] raise ZeroDivisionError --- .../Objects/new_variable/descriptor_number.py | 7 +- .../Objects/new_variable/parameter.py | 7 +- .../new_variable/test_descriptor_number.py | 43 +++++---- .../Objects/new_variable/test_parameter.py | 91 ++++++++++--------- 4 files changed, 73 insertions(+), 75 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 4567b153..316edace 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -15,7 +15,6 @@ from .descriptor_base import DescriptorBase -INFINITESIMAL = 1e-9 class DescriptorNumber(DescriptorBase): """ @@ -311,13 +310,13 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip if isinstance(other, numbers.Number): original_other = other if other == 0: - other = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other name = self.name + ' / ' + str(original_other) elif type(other) == DescriptorNumber: original_other = other.value if original_other == 0: - other.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value other.value = original_other name = self._name + ' / ' + other._name @@ -330,7 +329,7 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.value == 0: - self.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = other / self.full_value name = str(other) + ' / ' + self.name else: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index ee7b55f6..63c93a16 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -26,7 +26,6 @@ from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Utils.Exceptions import CoreSetException -from .descriptor_number import INFINITESIMAL from .descriptor_number import DescriptorNumber Constraints = namedtuple('Constraints', ['user', 'builtin', 'virtual']) @@ -582,14 +581,14 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) if isinstance(other, numbers.Number): original_other = other if other == 0: - other = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" elif isinstance(other, DescriptorNumber): original_value = other.value if original_value == 0: - other.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): @@ -625,7 +624,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: original_self = self.value if original_self == 0: - self.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") if isinstance(other, numbers.Number): new_value = other / self.full_value other_value = other diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index d58ca799..930b4132 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -329,26 +329,11 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): assert result_reverse.unit == "m" assert result_reverse.variance == 0.4 - @pytest.mark.parametrize("test, expected", [ - (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625)), - (DescriptorNumber("test", 0, "m^2", 0.01), DescriptorNumber("name / test", 1e9, "1/m", 1e34))], - ids=["regular", "zero_value"]) - def test_division(self, descriptor: DescriptorNumber, test, expected): - # When Then - result = descriptor / test - - # Expect - assert type(result) == DescriptorNumber - assert result.name == expected.name - assert result.value == pytest.approx(expected.value) - assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - @pytest.mark.parametrize("test, expected, expected_reverse", [ - (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4)), - (0, DescriptorNumber("name / 0", 1e9, "m", 1e17), DescriptorNumber("0 / name", 0, "1/m", 0))], - ids=["regular", "zero_value"]) - def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected, expected_reverse): + (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625), DescriptorNumber("test / name", 2, "m", 0.41)), + (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4))], + ids=["descriptorNumber", "scalar"]) + def test_division(self, descriptor: DescriptorNumber, test, expected, expected_reverse): # When Then result = descriptor / test result_reverse = test / descriptor @@ -356,12 +341,26 @@ def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected # Expect assert type(result) == DescriptorNumber assert result.name == expected.name - assert result.value == pytest.approx(expected.value) + assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert type(result_reverse) == DescriptorNumber assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) \ No newline at end of file + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + + @pytest.mark.parametrize("test", [0, DescriptorNumber("test", 0, "m", 0.01)], ids=["zero", "zero_descriptor"]) + def test_division_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(ZeroDivisionError): + result = descriptor / test + + def test_division_exception_reverse(self): + # When + descriptor = DescriptorNumber(name="name", value=0, variance=0.1) + + # Then Expect + with pytest.raises(ZeroDivisionError): + result = 2 / descriptor \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 63322fd6..7adfcaf3 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,7 +7,6 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber -from easyscience.Objects.new_variable.descriptor_number import INFINITESIMAL from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object @@ -668,9 +667,8 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), - (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0)), - (Parameter("test", 0, "s", 0.01, -10, 20), Parameter("name / test", 1e9, "m/s", 1e34, -np.Inf, np.Inf), Parameter("test / name", 0, "s/m", 0.01, -np.Inf, np.Inf))], - ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative", "zero_value"]) + (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0))], + ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative"]) def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() @@ -714,10 +712,10 @@ def test_division_with_parameter_remaining_cases(self, first, second, expected): assert result.max == expected.max @pytest.mark.parametrize("test, expected, expected_reverse", [ - (DescriptorNumber(name="test", value=2, variance=0.1, unit="dm^2"), Parameter("name / test", 0.5, "cm**-1", 0.00875, 0, 5), Parameter("test / name", 2.0, "cm", 0.14, 0.2, np.Inf)), - (DescriptorNumber(name="test", value=0, variance=0.1, unit="dm^2"), Parameter("name / test", 1e9, "cm**-1,", 1e35, 0, 1e10), DescriptorNumber("test / name", 0.0, "cm", 0.1))], - ids=["regular", "zero_value"]) - def test_division_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): + (DescriptorNumber(name="test", value=2, variance=0.1, unit="s"), Parameter("name / test", 0.5, "m/s", 0.00875, 0, 5), Parameter("test / name", 2, "s/m", 0.14, 0.2, np.Inf)), + (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf))], + ids=["descriptor_number", "number"]) + def test_division_with_descriptor_number_and_number(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() @@ -726,23 +724,39 @@ def test_division_with_descriptor_number(self, parameter : Parameter, test, expe result_reverse = test / parameter # Expect - assert type(result) == type(expected) + assert type(result) == Parameter assert result.name == expected.name - assert result.value == pytest.approx(expected.value) + assert result.value == expected.value assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == expected.max + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max - assert type(result_reverse) == type(expected_reverse) + assert type(result_reverse) == Parameter assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber(name="test", value=0, variance=0.1, unit="s"), DescriptorNumber("test / name", 0.0, "s/m", 0.1)), + (0, DescriptorNumber("0 / name", 0.0, "1/m", 0.0))], + ids=["descriptor_number", "number"]) + def test_zero_value_dividided_by_parameter(self, parameter : Parameter, test, expected): + # When + parameter._callback = property() + + # Then + result = test / parameter + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance @pytest.mark.parametrize("first, second, expected", [ (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, -10, 10), Parameter("name / test", 0.5, "m/s", 0.00875, -np.Inf, np.Inf)), @@ -763,33 +777,20 @@ def test_division_with_descriptor_number_missing_cases(self, first, second, expe assert result.min == expected.min assert result.max == expected.max - @pytest.mark.parametrize("test, expected, expected_reverse", [ - (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf)), - (0, Parameter("name / 0", 1/INFINITESIMAL, "m", (1/(INFINITESIMAL*10))**2, 0, 1/INFINITESIMAL*10), DescriptorNumber("0 / name", 0.0, "m**-1", 0.0))], - ids=["regular", "zero_value"]) - def test_division_with_numbers(self, parameter : Parameter, test, expected, expected_reverse): + @pytest.mark.parametrize("test", [0, DescriptorNumber("test", 0, "s", 0.1)], ids=["number", "descriptor_number"]) + def test_divide_parameter_by_zero(self, parameter : Parameter, test): # When parameter._callback = property() - # Then - result = parameter / test - result_reverse = test / parameter + # Then Expect + with pytest.raises(ZeroDivisionError): + result = parameter / test - # Expect - assert type(result) == type(expected) - assert result.name == expected.name - assert result.value == pytest.approx(expected.value) - assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == pytest.approx(expected.max) + def test_divide_by_zero_value_parameter(self): + # When + descriptor = DescriptorNumber("test", 1, "s", 0.1) + parameter = Parameter("name", 0, "m", 0.01) - assert type(result_reverse) == type(expected_reverse) - assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) - assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == pytest.approx(expected_reverse.max) \ No newline at end of file + # Then Expect + with pytest.raises(ZeroDivisionError): + result = descriptor / parameter \ No newline at end of file From f553ce7c1728e40698cf4ca6036a54178cac1b96 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 31 Jul 2024 09:46:08 +0200 Subject: [PATCH 16/68] add __pow__ methods --- .../Objects/new_variable/descriptor_number.py | 33 +++++++++++++++++++ .../Objects/new_variable/parameter.py | 27 ++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 316edace..3fc0b2b6 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -336,6 +336,39 @@ def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: + if isinstance(other, numbers.Number): + exponent = other + name = f"{self.name} ** {other}" + elif type(other) == DescriptorNumber: + if other.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if other.variance is not None: + raise ValueError("Exponents must not have variance") + exponent = other.value + name = self.name+" ** "+other.name + else: + return NotImplemented + try: + new_value = self.full_value ** exponent + except Exception as message: + raise message from None + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __rpow__(self, other: numbers.Number) -> numbers.Number: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if self.variance is not None: + raise ValueError("Exponents must not have variance") + try: + new_value = other ** self.full_value + except Exception as message: + raise message from None + else: + return NotImplemented + return new_value + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 63c93a16..e24cdd87 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -658,4 +658,29 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self - return parameter \ No newline at end of file + return parameter + + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: + if isinstance(other, numbers.Number): + exponent = other + name = f"{self.name} ** {other}" + elif type(other) == DescriptorNumber: + if other.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if other.variance is not None: + raise ValueError("Exponents must not have variance") + exponent = other.value + name = self.name+" ** "+other.name + else: + return NotImplemented + try: + new_value = self.full_value ** exponent + except Exception as message: + raise message from None + if exponent % 2 == 0 and self.min <= 0 and self.max >= 0: + combinations = [0, self.max ** exponent] + else: + combinations = [self.min ** exponent, self.max ** exponent] + min_value = min(combinations) + max_value = max(combinations) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From 8e1a111cfe4234383f4a55f99fad03d8089ecc0c Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:04:11 +0200 Subject: [PATCH 17/68] Test __pow__ methods --- .../Objects/new_variable/descriptor_number.py | 7 +- .../Objects/new_variable/parameter.py | 23 ++++++- .../fitting/minimizers/minimizer_lmfit.py | 2 +- .../new_variable/test_descriptor_number.py | 66 ++++++++++++++++++- .../Objects/new_variable/test_parameter.py | 59 ++++++++++++++++- 5 files changed, 148 insertions(+), 9 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 3fc0b2b6..b8b9f2ae 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -353,6 +353,8 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN 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") return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __rpow__(self, other: numbers.Number) -> numbers.Number: @@ -361,10 +363,7 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: raise UnitError("Exponents must be dimensionless") if self.variance is not None: raise ValueError("Exponents must not have variance") - try: - new_value = other ** self.full_value - except Exception as message: - raise message from None + new_value = other ** self.value else: return NotImplemented return new_value diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index e24cdd87..5545dfd8 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -677,10 +677,29 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = self.full_value ** exponent except Exception as message: raise message from None - if exponent % 2 == 0 and self.min <= 0 and self.max >= 0: - combinations = [0, self.max ** exponent] + if np.isnan(new_value.value): + raise ValueError("The result of the exponentiation is not a number") + if exponent == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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] + elif self.max == 0: + combinations = [-np.Inf, self.min ** exponent] + else: + combinations = [self.min ** exponent, self.max ** exponent] else: combinations = [self.min ** exponent, self.max ** exponent] + if exponent % 2 == 0: + if self.min < 0 and self.max > 0: + combinations.append(0) + combinations = [abs(combination) for combination in combinations] + elif exponent % 1 != 0: + if self.min < 0: + combinations.append(0) + combinations = [combination for combination in combinations if combination >= 0] min_value = min(combinations) max_value = max(combinations) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index 126ef814..b9982a77 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -35,7 +35,7 @@ class LMFit(MinimizerBase): # noqa: S101 wrapping = 'lmfit' - def __init__(self, obj: BaseObj, fit_function: Callable, method: Optional[str] = None): + def __init__(self, obj, fit_function: Callable, method: Optional[str] = None): """ Initialize the minimizer with the `BaseObj` and the `fit_function` to be used. diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 930b4132..0b6f17a8 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -363,4 +363,68 @@ def test_division_exception_reverse(self): # Then Expect with pytest.raises(ZeroDivisionError): - result = 2 / descriptor \ No newline at end of file + result = 2 / descriptor + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2), DescriptorNumber("name ** test", 4, unit="m^2", variance=1.6)), + (2, DescriptorNumber("name ** 2", 4, unit="m^2", variance=1.6)), + (-2, DescriptorNumber("name ** -2", 0.25, unit="1/m^2", variance=0.00625))], + ids=["descriptorNumber", "scalar", "negative_scalar"]) + def test_power_of_descriptor(self, test, expected): + # When + descriptor = DescriptorNumber(name="name", value=2, unit="m", variance=0.1) + + # Then + result = descriptor ** test + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + def test_power_of_dimensionless_descriptor(self): + # When + descriptor = DescriptorNumber(name="name", value=2, unit="dimensionless", variance=0.1) + + # Then + result = descriptor ** 0.5 + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name ** 0.5" + assert result.value == 1.4142135623730951 + assert result.unit == "dimensionless" + assert result.variance == pytest.approx(0.0125) + + @pytest.mark.parametrize("descriptor, exponent, exception", [ + (DescriptorNumber("name", 2), DescriptorNumber("test", 2, unit="m"), UnitError), + (DescriptorNumber("name", 2), DescriptorNumber("test", 2, variance=0.1), ValueError), + (DescriptorNumber("name", 2, unit="m"), 0.5, UnitError), + (DescriptorNumber("name", -2), 0.5, ValueError)], + ids=["descriptor_unit", "descriptor_variance", "fractional_of_unit", "fractonal_of_negative"]) + def test_power_of_descriptor_exceptions(self, descriptor, exponent, exception): + # When Then Expect + with pytest.raises(exception): + result = descriptor ** exponent + + + def test_descriptor_as_exponentiation(self): + # When + descriptor = DescriptorNumber(name="name", value=2) + + # Then + result = 2 ** descriptor + + # Expect + assert result == 4 + + @pytest.mark.parametrize("exponent, exception", [ + (DescriptorNumber("test", 2, unit="m"), UnitError), + (DescriptorNumber("test", 2, variance=0.1), ValueError)], + ids=["descriptor_unit", "descriptor_variance"]) + def test_descriptor_as_exponentiation_exception(self, exponent, exception): + # When Then Expect + with pytest.raises(exception): + result = 2 ** exponent \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 7adfcaf3..fa54ba3d 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -793,4 +793,61 @@ def test_divide_by_zero_value_parameter(self): # Then Expect with pytest.raises(ZeroDivisionError): - result = descriptor / parameter \ No newline at end of file + result = descriptor / parameter + + @pytest.mark.parametrize("test, expected", [ + (3, Parameter("name ** 3", 125, "m^3", 281.25, -125, 1000)), + (2, Parameter("name ** 2", 25, "m^2", 5.0, 0, 100)), + (-1, Parameter("name ** -1", 0.2, "1/m", 8e-5, -np.Inf, np.Inf)), + (-2, Parameter("name ** -2", 0.04, "1/m^2", 1.28e-5, 0, np.Inf)), + (0, DescriptorNumber("name ** 0", 1, "dimensionless", 0)), + (DescriptorNumber("test", 2), Parameter("name ** test", 25, "m^2", 5.0, 0, 100))], + ids=["power_3", "power_2", "power_-1", "power_-2", "power_0", "power_descriptor_number"]) + def test_power_of_parameter(self, test, expected): + # When + parameter = Parameter("name", 5, "m", 0.05, -5, 10) + + # Then + result = parameter ** test + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, exponent, expected", [ + (Parameter("name", 5, "m", 0.05, 0, 10), -1, Parameter("name ** -1", 0.2, "1/m", 8e-5, 0.1, np.Inf)), + (Parameter("name", -5, "m", 0.05, -5, 0), -1, Parameter("name ** -1", -0.2, "1/m", 8e-5, -np.Inf, -0.2)), + (Parameter("name", 5, "m", 0.05, 5, 10), -1, Parameter("name ** -1", 0.2, "1/m", 8e-5, 0.1, 0.2)), + (Parameter("name", -5, "m", 0.05, -10, -5), -1, Parameter("name ** -1", -0.2, "1/m", 8e-5, -0.2, -0.1)), + (Parameter("name", -5, "m", 0.05, -10, -5), -2, Parameter("name ** -2", 0.04, "1/m^2", 1.28e-5, 0.01, 0.04)), + (Parameter("name", 5, "", 0.1, 1, 10), 0.3, Parameter("name ** 0.3", 1.6206565966927624, "", 0.0009455500095853564, 1, 1.9952623149688795)), + (Parameter("name", 5, "", 0.1), 0.5, Parameter("name ** 0.5", 2.23606797749979, "", 0.005, 0, np.Inf))], + ids=["0_positive", "negative_0", "both_positive", "both_negative_invert", "both_negative_invert_square", "fractional", "fractional_negative_limit"]) + def test_power_of_diffent_parameters(self, test, exponent, expected): + # When Then + result = test ** exponent + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("parameter, exponent, expected", [ + (Parameter("name", 5, "m"), DescriptorNumber("test", 2, unit="s"), UnitError), + (Parameter("name", 5, "m"), DescriptorNumber("test", 2, variance=0.01), ValueError), + (Parameter("name", 5, "m"), 0.5, UnitError), + (Parameter("name", -5, ""), 0.5, ValueError),], + ids=["exponent_unit", "exponent_variance", "exponent_fractional", "negative_base_fractional"]) + def test_power_exceptions(self, parameter, exponent, expected): + # When Then Expect + with pytest.raises(expected): + result = parameter ** exponent \ No newline at end of file From 76bd9566522004eb1bd3bf1fffc0a97587dd95d9 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:08:21 +0200 Subject: [PATCH 18/68] add __neg__ and __abs__ methods --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ src/easyscience/Objects/new_variable/parameter.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index b8b9f2ae..ded91168 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -368,6 +368,16 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: return NotImplemented return new_value + def __neg__(self) -> DescriptorNumber: + new_value = -self.full_value + name = '-'+self.name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __abs__(self) -> DescriptorNumber: + new_value = abs(self.full_value) + name = 'abs('+self.name+')' + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 5545dfd8..461c7dd6 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -702,4 +702,19 @@ 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) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __neg__(self) -> Parameter: + new_value = -self.full_value + name = f"-{self.name}" + min_value = -self.max + max_value = -self.min + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __abs__(self) -> Parameter: + new_value = abs(self.full_value) + name = f"abs({self.name})" + if self.min < 0 and self.max > 0: + min_value = 0 + max_value = max(abs(self.min), abs(self.max)) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From 9394974edc551e80a518f6ae07773aa01628405a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:16:49 +0200 Subject: [PATCH 19/68] Add tests for __neg__ and __abs__ --- .../Objects/new_variable/parameter.py | 6 ++-- .../new_variable/test_descriptor_number.py | 30 ++++++++++++++++- .../Objects/new_variable/test_parameter.py | 33 ++++++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 461c7dd6..78ca0587 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -714,7 +714,9 @@ def __neg__(self) -> Parameter: def __abs__(self) -> Parameter: new_value = abs(self.full_value) name = f"abs({self.name})" + combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: - min_value = 0 - max_value = max(abs(self.min), abs(self.max)) + combinations.append(0) + min_value = min(combinations) + max_value = max(combinations) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 0b6f17a8..678198da 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -427,4 +427,32 @@ def test_descriptor_as_exponentiation(self): def test_descriptor_as_exponentiation_exception(self, exponent, exception): # When Then Expect with pytest.raises(exception): - result = 2 ** exponent \ No newline at end of file + result = 2 ** exponent + + def test_negation(self): + # When + descriptor = DescriptorNumber(name="name", unit="m", value=2, variance=0.1) + + # Then + result = -descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "-name" + assert result.value == -2 + assert result.unit == "m" + assert result.variance == 0.1 + + def test_abs(self): + # When + descriptor = DescriptorNumber(name="name", unit="m", value=-2, variance=0.1) + + # Then + result = abs(descriptor) + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "abs(name)" + assert result.value == 2 + assert result.unit == "m" + assert result.variance == 0.1 \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index fa54ba3d..22008408 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -850,4 +850,35 @@ def test_power_of_diffent_parameters(self, test, exponent, expected): def test_power_exceptions(self, parameter, exponent, expected): # When Then Expect with pytest.raises(expected): - result = parameter ** exponent \ No newline at end of file + result = parameter ** exponent + + def test_negation(self): + # When + parameter = Parameter("name", 5, "m", 0.05, -5, 10) + + # Then + result = -parameter + + # Expect + assert result.name == "-name" + assert result.value == -5 + assert result.unit == "m" + assert result.variance == 0.05 + assert result.min == -10 + assert result.max == 5 + + @pytest.mark.parametrize("test, expected", [ + (Parameter("name", -5, "m", 0.05, -10, -5), Parameter("abs(name)", 5, "m", 0.05, 5, 10)), + (Parameter("name", 5, "m", 0.05, -10, 10), Parameter("abs(name)", 5, "m", 0.05, 0, 10))], + ids=["pure_negative", "crossing_zero"]) + def test_abs(self, test, expected): + # When Then + result = abs(test) + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max \ No newline at end of file From 10e8ab47ee4c2fcb40de0d669b4fca99afbbd9a2 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:28:02 +0200 Subject: [PATCH 20/68] ruff --- src/easyscience/fitting/minimizers/minimizer_lmfit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index b9982a77..5e6452d2 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -18,7 +18,8 @@ from lmfit.model import ModelResult from easyscience.Objects.new_variable import Parameter as NewParameter -from easyscience.Objects.ObjectClasses import BaseObj + +# from easyscience.Objects.ObjectClasses import BaseObj ruff complained about this was unused from easyscience.Objects.Variable import Parameter from .minimizer_base import MINIMIZER_PARAMETER_PREFIX From 32e47c8654bef32f1d2edd3e3c28a6c677555316 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 2 Aug 2024 09:58:11 +0200 Subject: [PATCH 21/68] Updated ruff fix --- .../Objects/new_variable/descriptor_number.py | 10 +++++----- src/easyscience/Objects/new_variable/parameter.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index ded91168..fe34fcd3 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -233,7 +233,7 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise UnitError("Numbers can only be added to dimensionless values") new_value = self.full_value + other name = self.name + ' + ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) @@ -262,7 +262,7 @@ def __sub__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise UnitError("Numbers can only be subtracted from dimensionless values") new_value = self.full_value - other name = self.name + ' - ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) @@ -289,7 +289,7 @@ def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if isinstance(other, numbers.Number): new_value = self.full_value * other name = self.name + ' * ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: new_value = self.full_value * other.full_value name = self._name + ' * ' + other._name else: @@ -313,7 +313,7 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other name = self.name + ' / ' + str(original_other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_other = other.value if original_other == 0: raise ZeroDivisionError("Cannot divide by zero") @@ -340,7 +340,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 78ca0587..3144f4b4 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -535,7 +535,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value name = self.name+" * "+other.name - if other.value == 0 and type(other) == DescriptorNumber: + if other.value == 0 and type(other) is DescriptorNumber: return DescriptorNumber.from_scipp(name=name, full_value=new_value) if isinstance(other, Parameter): combinations = [] @@ -664,7 +664,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: From a0ff7dc116777a545a8dba57ff8d7cd7afe26466 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 2 Aug 2024 12:07:13 +0200 Subject: [PATCH 22/68] Fix unit prefixes with "e" numbers --- src/easyscience/Objects/new_variable/descriptor_number.py | 7 +++++-- tests/unit_tests/Objects/new_variable/test_parameter.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index fe34fcd3..f2c91b00 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -380,7 +380,10 @@ def __abs__(self) -> DescriptorNumber: def _base_unit(self) -> str: string = str(self._scalar.unit) - for i in range(len(string)): - if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: + for i, letter in enumerate(string): + 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", ".", "+", "-"]: return string[i:] return "" \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 22008408..d0c19da6 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,7 +7,6 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber -from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object class TestParameter: @@ -744,7 +743,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, (DescriptorNumber(name="test", value=0, variance=0.1, unit="s"), DescriptorNumber("test / name", 0.0, "s/m", 0.1)), (0, DescriptorNumber("0 / name", 0.0, "1/m", 0.0))], ids=["descriptor_number", "number"]) - def test_zero_value_dividided_by_parameter(self, parameter : Parameter, test, expected): + def test_zero_value_divided_by_parameter(self, parameter : Parameter, test, expected): # When parameter._callback = property() From 6f3ee8ea9e389581f9a51b6a81e086bbc8df3f38 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 11:49:15 +0200 Subject: [PATCH 23/68] minor code consistency, updated test --- .../Objects/new_variable/parameter.py | 58 ++++++++++--------- .../Objects/new_variable/test_parameter.py | 12 ++-- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 3144f4b4..c0b34b4a 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -46,8 +46,8 @@ def __init__( value: numbers.Number, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = 0.0, - min: Optional[numbers.Number] = -np.Inf, - max: Optional[numbers.Number] = np.Inf, + min: Optional[numbers.Number] = -np.inf, + max: Optional[numbers.Number] = np.inf, fixed: Optional[bool] = False, unique_name: Optional[str] = None, description: Optional[str] = None, @@ -446,7 +446,7 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_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 - name = self.name+" + "+other.name + name = f"{self.name} + {other.name}" other.convert_unit(original_unit) else: return NotImplemented @@ -469,7 +469,7 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value - name = other.name+" + "+self.name + name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented @@ -491,8 +491,8 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_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 - max_value = self.max - other.min if other.min != -np.Inf else np.Inf + min_value = self.min - other.max if other.max != np.inf else -np.inf + max_value = self.max - other.min if other.min != -np.inf else np.inf else: min_value = self.min - other.value max_value = self.max - other.value @@ -519,7 +519,7 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min - name = other.name+" - "+self.name + name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented @@ -534,8 +534,8 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value - name = self.name+" * "+other.name - if other.value == 0 and type(other) is DescriptorNumber: + name = f"{self.name} * {other.name}" + if other.value == 0 and isinstance(other, DescriptorNumber): return DescriptorNumber.from_scipp(name=name, full_value=new_value) if isinstance(other, Parameter): combinations = [] @@ -565,7 +565,7 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - name = other.name+" * "+self.name + name = f"{other.name} * {self.name}" if other.value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_value) combinations = [self.min * other.value, self.max * other.value] @@ -592,26 +592,26 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) new_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): - combinations = [-np.Inf, np.Inf] + combinations = [-np.inf, np.inf] elif other.min == 0: if (self.min < 0 and self.max > 0): - combinations = [-np.Inf, np.Inf] + 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): - combinations = [-np.Inf, np.Inf] + 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] else: combinations = [self.min / other.value, self.max / other.value] - name = self.name+" / "+other.name + name = f"{self.name} / {other.name}" other.value = original_value else: return NotImplemented @@ -640,17 +640,17 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame else: return NotImplemented if (self.min < 0 and self.max > 0): - combinations = [-np.Inf, np.Inf] + 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) @@ -664,30 +664,32 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) is DescriptorNumber: + elif isinstance(other, DescriptorNumber): if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = self.name+" ** "+other.name + name = f"{self.name} ** {other.name}" else: return NotImplemented + try: 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") if exponent == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif exponent < 0: if self.min < 0 and self.max > 0: - combinations = [-np.Inf, np.Inf] + 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] else: diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index d0c19da6..76783544 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -591,15 +591,19 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance - assert result.min == expected.min - assert result.max == expected.max assert result_reverse.name == expected_reverse.name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max @pytest.mark.parametrize("test, expected, expected_reverse", [ (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), From 180a6ee8c17faacc2602dadb304fc1dfb21ab18b Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:45:55 +0200 Subject: [PATCH 24/68] Change name of new_value --- .../Objects/new_variable/parameter.py | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index c0b34b4a..c8a571f7 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -433,30 +433,30 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> 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 + new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" elif isinstance(other, DescriptorNumber): - original_unit = other.unit + 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 - new_value = self.full_value + other.full_value + 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 name = f"{self.name} + {other.name}" - other.convert_unit(original_unit) + other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = self.full_value + other + new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" @@ -466,30 +466,30 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(other.unit) except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be added") from None - new_value = self.full_value + other.full_value + new_full_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = self.full_value - other + new_full_value = self.full_value - other min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" elif isinstance(other, DescriptorNumber): - original_unit = other.unit + 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 - new_value = self.full_value - other.full_value + 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 max_value = self.max - other.min if other.min != -np.inf else np.inf @@ -497,16 +497,16 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min - other.value max_value = self.max - other.value name = self.name+" - "+other.name - other.convert_unit(original_unit) + other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = other - self.full_value + new_full_value = other - self.full_value min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" @@ -516,27 +516,27 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(other.unit) except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None - new_value = other.full_value - self.full_value + new_full_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - new_value = self.full_value * other + new_full_value = self.full_value * other name = f"{self.name} * {other}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): - new_value = self.full_value * other.full_value + new_full_value = self.full_value * other.full_value name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) 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 @@ -552,28 +552,28 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - new_value = other * self.full_value + new_full_value = other * self.full_value name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): - new_value = other.full_value * self.full_value + new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -582,14 +582,14 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) original_other = other if other == 0: raise ZeroDivisionError("Cannot divide by zero") - new_value = self.full_value / other + new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" elif isinstance(other, DescriptorNumber): original_value = other.value if original_value == 0: raise ZeroDivisionError("Cannot divide by zero") - new_value = self.full_value / other.full_value + new_full_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): combinations = [-np.inf, np.inf] @@ -617,7 +617,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -626,17 +626,17 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame if original_self == 0: raise ZeroDivisionError("Cannot divide by zero") if isinstance(other, numbers.Number): - new_value = other / self.full_value + new_full_value = other / self.full_value other_value = other name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) elif isinstance(other, DescriptorNumber): - new_value = other.full_value / self.full_value + new_full_value = other.full_value / self.full_value other_value = other.value name = other.name+" / "+self.name if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) else: return NotImplemented if (self.min < 0 and self.max > 0): @@ -655,7 +655,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self return parameter @@ -675,14 +675,14 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: return NotImplemented try: - new_value = self.full_value ** exponent + new_full_value = self.full_value ** exponent except Exception as message: raise message from None - if np.isnan(new_value.value): + if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) elif exponent < 0: if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] @@ -704,21 +704,21 @@ 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) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __neg__(self) -> Parameter: - new_value = -self.full_value + new_full_value = -self.full_value name = f"-{self.name}" min_value = -self.max max_value = -self.min - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __abs__(self) -> Parameter: - new_value = abs(self.full_value) + new_full_value = abs(self.full_value) name = f"abs({self.name})" combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file From ccfaa2c7ee6b38b0982a0df97d56a8ae6ba182d8 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:48:55 +0200 Subject: [PATCH 25/68] Add comments about DescriptorNumber --- .../Objects/new_variable/parameter.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index c8a571f7..b98bdd30 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -437,7 +437,7 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) @@ -460,7 +460,7 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) @@ -483,7 +483,7 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) @@ -510,7 +510,7 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) @@ -532,7 +532,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if other == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other, self.max * other] - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = self.full_value * other.full_value name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): @@ -563,7 +563,7 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if other == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [other * self.min, other * self.max] - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: @@ -585,7 +585,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_value = other.value if original_value == 0: raise ZeroDivisionError("Cannot divide by zero") @@ -631,7 +631,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame name = f"{other} / {self.name}" if other_value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) - elif isinstance(other, DescriptorNumber): + 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 name = other.name+" / "+self.name @@ -664,7 +664,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: From 817fae2235bcc0b40439e3965b4f0f20b2360e94 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:57:35 +0200 Subject: [PATCH 26/68] Changed == to np.isclose --- src/easyscience/Objects/new_variable/parameter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index b98bdd30..273288df 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -86,7 +86,8 @@ def __init__( raise ValueError(f'{value=} can not be less than {min=}') if value > max: raise ValueError(f'{value=} can not be greater than {max=}') - if min == max: + + if np.isclose(min, max, rtol=1e-9, atol=0.0): 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') @@ -249,7 +250,8 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') - if min_value == self._max.value: + # if min_value == self._max.value: + if np.isclose(min_value, self._max.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: self._min.value = min_value @@ -277,7 +279,8 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') - if max_value == self._min.value: + # if max_value == self._min.value: + if np.isclose(max_value, self._min.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: self._max.value = max_value From 0cd009a389a7ed49e24c208793491402943dd3a8 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 14:08:05 +0200 Subject: [PATCH 27/68] Added calls to convert_unit in initialize to ensure unit consistency --- .../Objects/new_variable/descriptor_number.py | 5 +++++ src/easyscience/Objects/new_variable/parameter.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index f2c91b00..4605644f 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -69,6 +69,11 @@ def __init__( parent=parent, ) + # Call convert_unit during initialization to ensure that the unit has no numbers in it, and to ensure unit consistency. + # For some reason, converting to the same unit often changes the representation. This breaks almost every test. + if self.unit is not None: + self.convert_unit(self._base_unit()) + @classmethod def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorNumber: """ diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 273288df..93289178 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -91,6 +91,9 @@ 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) super().__init__( name=name, @@ -109,8 +112,6 @@ def __init__( weakref.finalize(self, self._callback.fdel) # Create additional fitting elements - self._min = sc.scalar(float(min), unit=unit) - self._max = sc.scalar(float(max), unit=unit) self._fixed = fixed self._enabled = enabled self._initial_scalar = copy.deepcopy(self._scalar) @@ -121,6 +122,10 @@ def __init__( } self._constraints = Constraints(builtin=builtin_constraint, user={}, virtual={}) + if self.unit is not None: + self.convert_unit(self._base_unit()) + + @property def value_no_call_back(self) -> numbers.Number: """ @@ -181,7 +186,7 @@ def value(self) -> numbers.Number: @property_stack_deco def value(self, value: numbers.Number) -> None: """ - Set the value of self. This only update the value of the scipp scalar. + Set the value of self. This only updates the value of the scipp scalar. :param value: New value of self """ @@ -582,12 +587,11 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - original_other = other if other == 0: raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] - name = f"{self.name} / {original_other}" + name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_value = other.value if original_value == 0: From 219d93ec8aee5616daa0c56d28982853d6512df1 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 11:44:47 +0200 Subject: [PATCH 28/68] Minor consistency fixes --- src/easyscience/Objects/new_variable/parameter.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 93289178..601fb4de 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -122,9 +122,6 @@ def __init__( } self._constraints = Constraints(builtin=builtin_constraint, user={}, virtual={}) - if self.unit is not None: - self.convert_unit(self._base_unit()) - @property def value_no_call_back(self) -> numbers.Number: @@ -255,7 +252,6 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') - # if min_value == self._max.value: if np.isclose(min_value, self._max.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: @@ -284,7 +280,6 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') - # if max_value == self._min.value: if np.isclose(max_value, self._min.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: @@ -504,7 +499,7 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> else: min_value = self.min - other.value max_value = self.max - other.value - name = self.name+" - "+other.name + name = f"{self.name} - {other.name}" other.convert_unit(other_unit) else: return NotImplemented @@ -593,8 +588,8 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) combinations = [self.min / other, self.max / other] name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here - original_value = other.value - if original_value == 0: + other_value = other.value + if other_value == 0: raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other.full_value if isinstance(other, Parameter): @@ -619,7 +614,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) else: combinations = [self.min / other.value, self.max / other.value] name = f"{self.name} / {other.name}" - other.value = original_value + other.value = other_value else: return NotImplemented min_value = min(combinations) @@ -641,7 +636,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame 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 - name = other.name+" / "+self.name + name = f"{other.name} / {self.name}" if other_value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) else: From 3c972f720fc423d8ed66917a473e47c210037f5c Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 13:29:05 +0200 Subject: [PATCH 29/68] Changed names to be unique_name --- .../Objects/new_variable/descriptor_base.py | 5 +- .../Objects/new_variable/descriptor_bool.py | 2 +- .../Objects/new_variable/descriptor_number.py | 2 +- .../Objects/new_variable/descriptor_str.py | 2 +- .../Objects/new_variable/parameter.py | 54 +++++++------------ 5 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_base.py b/src/easyscience/Objects/new_variable/descriptor_base.py index c05cef85..79d87db3 100644 --- a/src/easyscience/Objects/new_variable/descriptor_base.py +++ b/src/easyscience/Objects/new_variable/descriptor_base.py @@ -31,7 +31,7 @@ class DescriptorBase(ComponentSerializer, metaclass=abc.ABCMeta): def __init__( self, - name: str, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, @@ -58,6 +58,9 @@ def __init__( unique_name = self._unique_name_generator() self._unique_name = unique_name + if name is None: + name=unique_name + if not isinstance(name, str): raise TypeError('Name must be a string') self._name: str = name diff --git a/src/easyscience/Objects/new_variable/descriptor_bool.py b/src/easyscience/Objects/new_variable/descriptor_bool.py index 768b35b1..6509a378 100644 --- a/src/easyscience/Objects/new_variable/descriptor_bool.py +++ b/src/easyscience/Objects/new_variable/descriptor_bool.py @@ -14,8 +14,8 @@ class DescriptorBool(DescriptorBase): """ def __init__( self, - name: str, value: bool, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 4605644f..ad3b959b 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -23,8 +23,8 @@ class DescriptorNumber(DescriptorBase): def __init__( self, - name: str, value: numbers.Number, + name: Optional[str] = None, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = None, unique_name: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_str.py b/src/easyscience/Objects/new_variable/descriptor_str.py index 1abe4e4e..80c0f6c9 100644 --- a/src/easyscience/Objects/new_variable/descriptor_str.py +++ b/src/easyscience/Objects/new_variable/descriptor_str.py @@ -15,8 +15,8 @@ class DescriptorStr(DescriptorBase): def __init__( self, - name: str, value: str, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 601fb4de..22246ada 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -439,7 +439,6 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - name = f"{self.name} + {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: @@ -449,11 +448,10 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> 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 - name = f"{self.name} + {other.name}" other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -462,7 +460,6 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - name = f"{other} + {self.name}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: @@ -472,11 +469,10 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value - name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -485,7 +481,6 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_full_value = self.full_value - other min_value = self.min - other max_value = self.max - other - name = f"{self.name} - {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: @@ -499,11 +494,10 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> else: min_value = self.min - other.value max_value = self.max - other.value - name = f"{self.name} - {other.name}" other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -512,7 +506,6 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other - self.full_value min_value = other - self.max max_value = other - self.min - name = f"{other} - {self.name}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: @@ -522,24 +515,21 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min - name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = self.full_value * other - name = f"{self.name} * {other}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = self.full_value * other.full_value - name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) 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 @@ -555,7 +545,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -564,19 +554,19 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other * self.full_value name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -586,7 +576,6 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] - name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_value = other.value if other_value == 0: @@ -613,13 +602,12 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) 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] - name = f"{self.name} / {other.name}" other.value = other_value else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -630,15 +618,13 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame if isinstance(other, numbers.Number): new_full_value = other / self.full_value other_value = other - name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) 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 - name = f"{other.name} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) else: return NotImplemented if (self.min < 0 and self.max > 0): @@ -657,7 +643,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self return parameter @@ -665,14 +651,12 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other - name = f"{self.name} ** {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = f"{self.name} ** {other.name}" else: return NotImplemented @@ -684,7 +668,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) elif exponent < 0: if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] @@ -706,21 +690,19 @@ 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) - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __neg__(self) -> Parameter: new_full_value = -self.full_value - name = f"-{self.name}" min_value = -self.max max_value = -self.min - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __abs__(self) -> Parameter: new_full_value = abs(self.full_value) - name = f"abs({self.name})" combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file From 7edbadbacdf19d8351a6ffdb18e5f5c41e1d10f7 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 14:54:17 +0200 Subject: [PATCH 30/68] Auto generate names as unique_name for arithmetic operations --- .../Objects/new_variable/descriptor_base.py | 5 +- .../Objects/new_variable/descriptor_bool.py | 2 +- .../Objects/new_variable/descriptor_number.py | 62 ++++++++-------- .../Objects/new_variable/descriptor_str.py | 2 +- .../Objects/new_variable/parameter.py | 70 +++++++++++++------ .../Objects/new_variable/test_parameter.py | 68 +++++++++--------- 6 files changed, 121 insertions(+), 88 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_base.py b/src/easyscience/Objects/new_variable/descriptor_base.py index 79d87db3..c05cef85 100644 --- a/src/easyscience/Objects/new_variable/descriptor_base.py +++ b/src/easyscience/Objects/new_variable/descriptor_base.py @@ -31,7 +31,7 @@ class DescriptorBase(ComponentSerializer, metaclass=abc.ABCMeta): def __init__( self, - name: Optional[str] = None, + name: str, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, @@ -58,9 +58,6 @@ def __init__( unique_name = self._unique_name_generator() self._unique_name = unique_name - if name is None: - name=unique_name - if not isinstance(name, str): raise TypeError('Name must be a string') self._name: str = name diff --git a/src/easyscience/Objects/new_variable/descriptor_bool.py b/src/easyscience/Objects/new_variable/descriptor_bool.py index 6509a378..768b35b1 100644 --- a/src/easyscience/Objects/new_variable/descriptor_bool.py +++ b/src/easyscience/Objects/new_variable/descriptor_bool.py @@ -14,8 +14,8 @@ class DescriptorBool(DescriptorBase): """ def __init__( self, + name: str, value: bool, - name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index ad3b959b..2dfc14dd 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -23,8 +23,8 @@ class DescriptorNumber(DescriptorBase): def __init__( self, + name: str, value: numbers.Number, - name: Optional[str] = None, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = None, unique_name: Optional[str] = None, @@ -237,7 +237,6 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") new_value = self.full_value + other - name = self.name + ' + ' + str(other) elif type(other) is DescriptorNumber: original_unit = other.unit try: @@ -245,28 +244,31 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None new_value = self.full_value + other.full_value - name = self._name + ' + ' + other._name other.convert_unit(original_unit) else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = other + self.full_value - name = str(other) + ' + ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = self.full_value - other - name = self.name + ' - ' + str(other) elif type(other) is DescriptorNumber: original_unit = other.unit try: @@ -274,42 +276,44 @@ def __sub__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_value = self.full_value - other.full_value - name = self._name + ' - ' + other._name other.convert_unit(original_unit) else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = other - self.full_value - name = str(other) + ' - ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 - name = self.name + ' * ' + str(other) elif type(other) is DescriptorNumber: new_value = self.full_value * other.full_value - name = self._name + ' * ' + other._name else: return NotImplemented - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 return descriptor_number def __rmul__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = other * self.full_value - name = str(other) + ' * ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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): @@ -317,18 +321,17 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip if other == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other - name = self.name + ' / ' + str(original_other) elif type(other) is DescriptorNumber: original_other = other.value if original_other == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value other.value = original_other - name = self._name + ' / ' + other._name else: return NotImplemented - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 return descriptor_number def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: @@ -336,22 +339,21 @@ def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: if self.value == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = other / self.full_value - name = str(other) + ' / ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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: if isinstance(other, numbers.Number): exponent = other - name = f"{self.name} ** {other}" elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = self.name+" ** "+other.name else: return NotImplemented try: @@ -360,7 +362,9 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise message from None if np.isnan(new_value.value): raise ValueError("The result of the exponentiation is not a number") - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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): @@ -375,13 +379,15 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: def __neg__(self) -> DescriptorNumber: new_value = -self.full_value - name = '-'+self.name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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) - name = 'abs('+self.name+')' - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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) diff --git a/src/easyscience/Objects/new_variable/descriptor_str.py b/src/easyscience/Objects/new_variable/descriptor_str.py index 80c0f6c9..1abe4e4e 100644 --- a/src/easyscience/Objects/new_variable/descriptor_str.py +++ b/src/easyscience/Objects/new_variable/descriptor_str.py @@ -15,8 +15,8 @@ class DescriptorStr(DescriptorBase): def __init__( self, + name: str, value: str, - name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 22246ada..cf7c6e8e 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -451,7 +451,9 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -472,7 +474,9 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -497,7 +501,9 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -518,18 +524,24 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = self.full_value * other.full_value if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 @@ -545,29 +557,33 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = other * self.full_value - name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = other.full_value * self.full_value - name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: @@ -607,8 +623,9 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: @@ -619,12 +636,16 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame new_full_value = other / self.full_value other_value = other if other_value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = other.full_value / self.full_value other_value = other.value if other_value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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): @@ -643,8 +664,9 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 self.value = original_self return parameter @@ -668,7 +690,9 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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] @@ -690,13 +714,17 @@ 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) - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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) @@ -705,4 +733,6 @@ def __abs__(self) -> Parameter: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file + 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/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 76783544..115baf0f 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -355,15 +355,15 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, result_reverse = test + parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name - assert result_reverse.value == expected_reverse.value + assert result_reverse.name == result_reverse.unique_name + assert result_reverse.value == result_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min @@ -380,14 +380,14 @@ def test_addition_with_scalar(self): result_reverse = 1.0 + parameter # Expect - assert result.name == "name + 1.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == 1.0 assert result.max == 11.0 - assert result_reverse.name == "1.0 + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -405,7 +405,7 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): # Expect assert type(result) == Parameter - assert result.name == "name + test" + assert result.name == result.unique_name assert result.value == 1.01 assert result.unit == "m" assert result.variance == 0.01001 @@ -413,7 +413,7 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): assert result.max == 10.01 assert type(result_reverse) == Parameter - assert result_reverse.name == "test + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 101.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 @@ -445,14 +445,14 @@ def test_subtraction_with_parameter(self, parameter : Parameter, test : Paramete result_reverse = test - parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -471,14 +471,14 @@ def test_subtraction_with_parameter_nan_cases(self): result_reverse = test - parameter # Expect - assert result.name == "name - test" + assert result.name == result.unique_name assert result.value == -1.0 assert result.unit == "dimensionless" assert result.variance == 0.02 assert result.min == -np.Inf assert result.max == np.Inf - assert result_reverse.name == "test - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.02 @@ -494,14 +494,14 @@ def test_subtraction_with_scalar(self): result_reverse = 1.0 - parameter # Expect - assert result.name == "name - 1.0" + assert result.name == result.unique_name assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == -1.0 assert result.max == 9.0 - assert result_reverse.name == "1.0 - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -519,7 +519,7 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): # Expect assert type(result) == Parameter - assert result.name == "name - test" + assert result.name == result.unique_name assert result.value == 0.99 assert result.unit == "m" assert result.variance == 0.01001 @@ -527,7 +527,7 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): assert result.max == 9.99 assert type(result_reverse) == Parameter - assert result_reverse.name == "test - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -99.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 @@ -560,14 +560,14 @@ def test_multiplication_with_parameter(self, parameter : Parameter, test : Param result_reverse = test * parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -587,12 +587,12 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ result_reverse = test * parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -619,7 +619,7 @@ def test_multiplication_with_descriptor_number(self, parameter : Parameter, test # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -628,7 +628,7 @@ def test_multiplication_with_descriptor_number(self, parameter : Parameter, test assert result.max == expected.max assert type(result_reverse) == type(expected_reverse) - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -650,7 +650,7 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -658,7 +658,7 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -682,7 +682,7 @@ def test_division_with_parameter(self, parameter : Parameter, test, expected, ex # Expect assert type(result) == Parameter - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == pytest.approx(expected.value) assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) @@ -690,7 +690,7 @@ def test_division_with_parameter(self, parameter : Parameter, test, expected, ex assert result.max == expected.max assert type(result) == Parameter - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == pytest.approx(expected_reverse.value) assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -707,7 +707,7 @@ def test_division_with_parameter_remaining_cases(self, first, second, expected): result = first / second # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -728,7 +728,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, # Expect assert type(result) == Parameter - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -736,7 +736,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, assert result.max == expected.max assert type(result_reverse) == Parameter - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -756,7 +756,7 @@ def test_zero_value_divided_by_parameter(self, parameter : Parameter, test, expe # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -773,7 +773,7 @@ def test_division_with_descriptor_number_missing_cases(self, first, second, expe result = first / second # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -815,7 +815,7 @@ def test_power_of_parameter(self, test, expected): # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -837,7 +837,7 @@ def test_power_of_diffent_parameters(self, test, exponent, expected): result = test ** exponent # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -863,7 +863,7 @@ def test_negation(self): result = -parameter # Expect - assert result.name == "-name" + assert result.name == result.unique_name assert result.value == -5 assert result.unit == "m" assert result.variance == 0.05 @@ -879,7 +879,7 @@ def test_abs(self, test, expected): result = abs(test) # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance From 182983ec6f9b1cf8706961513ed8308355de7ab2 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 15:09:28 +0200 Subject: [PATCH 31/68] Updated tests --- .../new_variable/test_descriptor_number.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 678198da..f984d346 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -213,7 +213,7 @@ def test_addition(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -230,13 +230,13 @@ def test_addition_with_scalar(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name + 1.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "1.0 + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -259,7 +259,7 @@ def test_subtraction(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -276,13 +276,13 @@ def test_subtraction_with_scalar(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name - 1.0" + assert result.name == result.unique_name assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "1.0 - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -306,7 +306,7 @@ def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) @@ -318,13 +318,13 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): # Expect assert type(result) == DescriptorNumber - assert result.name == "name * 2.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "m" assert result.variance == 0.4 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "2.0 * name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "m" assert result_reverse.variance == 0.4 @@ -340,13 +340,13 @@ def test_division(self, descriptor: DescriptorNumber, test, expected, expected_r # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -379,7 +379,7 @@ def test_power_of_descriptor(self, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -393,7 +393,7 @@ def test_power_of_dimensionless_descriptor(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name ** 0.5" + assert result.name == result.unique_name assert result.value == 1.4142135623730951 assert result.unit == "dimensionless" assert result.variance == pytest.approx(0.0125) @@ -438,7 +438,7 @@ def test_negation(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "-name" + assert result.name == result.unique_name assert result.value == -2 assert result.unit == "m" assert result.variance == 0.1 @@ -452,7 +452,7 @@ def test_abs(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "abs(name)" + assert result.name == result.unique_name assert result.value == 2 assert result.unit == "m" assert result.variance == 0.1 \ No newline at end of file From 6a7a69fbf9f3a5687d2d91ff9e61647fa7a5d111 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Thu, 8 Aug 2024 10:20:28 +0200 Subject: [PATCH 32/68] remove unused import --- tests/unit_tests/Objects/new_variable/test_descriptor_number.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index f984d346..9438f300 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -3,8 +3,6 @@ import scipp as sc from scipp import UnitError -from scipp import UnitError - from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber from easyscience import global_object From e050e0e99a2f40cbfef4ace93a747f2d8999739e Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 12 Aug 2024 14:17:32 +0200 Subject: [PATCH 33/68] Fix commit mistakes and clean tests --- .../Objects/new_variable/parameter.py | 4 ++-- .../new_variable/test_descriptor_number.py | 20 ++++++++++++++++--- .../Objects/new_variable/test_parameter.py | 20 +++++++------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index cf7c6e8e..b48f6a91 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -538,7 +538,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> combinations = [self.min * other, self.max * other] 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 isinstance(other, DescriptorNumber): + 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 @@ -673,7 +673,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + 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") if other.variance is not None: diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 9438f300..75edc6dd 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -201,6 +201,21 @@ def test_as_data_dict(self, clear, descriptor: DescriptorNumber): "unique_name": "DescriptorNumber_0", } + @pytest.mark.parametrize("unit_string, expected", [ + ("1e+9", "dimensionless"), + ("1000", "dimensionless"), + ("10dm^2", "m^2")], + ids=["scientific_notation", "numbers", "unit_prefix"]) + def test_base_unit(self, unit_string, expected): + # When + descriptor = DescriptorNumber(name="name", value=1, unit=unit_string) + + # Then + base_unit = descriptor._base_unit() + + # Expect + assert base_unit == expected + @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test + name", 3, "m", 0.11)), (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test + name", 102, "cm", 1000.01))], @@ -295,9 +310,8 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), - (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041)), - (DescriptorNumber("test", 2, "1/dm", 0.01), DescriptorNumber("test * name", 20.0, "dimensionless", 41))], - ids=["regular", "base_unit_conversion", "base_unit_conversion_dimensionless"]) + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], + ids=["regular", "base_unit_conversion"]) def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # When Then result = test * descriptor diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 115baf0f..3d81e6ad 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -548,9 +548,8 @@ def test_subtraction_exception(self, parameter : Parameter, test): @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), - (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], - ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], + ids=["regular", "no_bounds", "base_unit_conversion"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() @@ -591,19 +590,15 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance - - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == expected.max - - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max @pytest.mark.parametrize("test, expected, expected_reverse", [ (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), @@ -668,10 +663,9 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0))], - ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative"]) + ids=["crossing_zero", "only_positive", "only_negative"]) def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() From ffbe57575118ccff8559af2ea2787e2961e118bf Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 14 Aug 2024 12:08:03 +0200 Subject: [PATCH 34/68] Fix mistakes from Henrik --- src/easyscience/Objects/new_variable/descriptor_number.py | 1 - tests/unit_tests/Objects/new_variable/test_parameter.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 2dfc14dd..4fd25709 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -70,7 +70,6 @@ def __init__( ) # Call convert_unit during initialization to ensure that the unit has no numbers in it, and to ensure unit consistency. - # For some reason, converting to the same unit often changes the representation. This breaks almost every test. if self.unit is not None: self.convert_unit(self._base_unit()) diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 3d81e6ad..017e76bd 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -363,7 +363,7 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, assert result.max == expected.max assert result_reverse.name == result_reverse.unique_name - assert result_reverse.value == result_reverse.value + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min From b3ba8ae7e7dcdf0724415f7ad7fc590301e67c23 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 11:11:18 +0200 Subject: [PATCH 35/68] __add__ method in Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ src/easyscience/Objects/new_variable/parameter.py | 15 +++++++++++++++ .../fitting/minimizers/minimizer_base.py | 4 ++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 1b0a3e03..cf177f50 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -225,3 +225,13 @@ def as_dict(self) -> Dict[str, Any]: raw_dict['unit'] = str(self._scalar.unit) raw_dict['variance'] = self._scalar.variance return raw_dict + + def __add__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value + other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' + ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 8b3c39a6..ce34ae88 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -420,3 +420,18 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) + + def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value + other.full_value + except Exception as message: + raise ValueError(message) + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = self.name+" + "+other.name if not radd else other.name+" + "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__add__(other, radd=True) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 9836bdfb..59ef4133 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -16,8 +16,8 @@ import numpy as np -# causes circular import when Parameter is imported -# from easyscience.Objects.ObjectClasses import BaseObj +#causes circular import when Parameter is imported +#from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.Variable import Parameter from ..Constraints import ObjConstraint From e6ce024260cf6f808e2dd153ae48bad4a903460d Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 12:03:12 +0200 Subject: [PATCH 36/68] __sub__ methods added to Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 +++++++ .../Objects/new_variable/parameter.py | 29 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index cf177f50..0fdd5aec 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -235,3 +235,13 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: raise ValueError(message) name = self._name + ' + ' + other._name return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value - other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' - ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index ce34ae88..5a832543 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -421,7 +421,7 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) -> Parameter: + def __add__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') try: @@ -430,8 +430,31 @@ def __add__(self, other: Union[DescriptorNumber, Parameter], radd: bool = False) raise ValueError(message) min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name if not radd else other.name+" + "+self.name + name = self.name+" + "+other.name if not inverse else other.name+" + "+self.name return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__add__(other, radd=True) + return self.__add__(other, inverse=True) + + def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value - other.full_value if not inverse else other.full_value - self.full_value + except Exception as message: + raise ValueError(message) + if isinstance(other, Parameter): + if not inverse: + min_value = self.min - other.max + max_value = self.max - other.min + else: + min_value = other.min - self.max + max_value = other.max - self.min + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" - "+other.name if not inverse else other.name+" - "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__sub__(other, inverse=True) \ No newline at end of file From 723cf41bf17ece9ce14a0aee19289f80cefefb1a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 18 Jul 2024 13:24:20 +0200 Subject: [PATCH 37/68] __mul__ added to Parameter and DescriptorNumber --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ .../Objects/new_variable/parameter.py | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 0fdd5aec..bd1d6193 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -244,4 +244,14 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: except Exception as message: raise ValueError(message) name = self._name + ' - ' + other._name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: + if not isinstance(other, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber') + try: + new_value = self.full_value * other.full_value + except Exception as message: + raise ValueError(message) + name = self._name + ' * ' + other._name return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 5a832543..de6d8634 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -457,4 +457,19 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = Fal return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__sub__(other, inverse=True) \ No newline at end of file + return self.__sub__(other, inverse=True) + + def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value * other.full_value + except Exception as message: + raise ValueError(message) + min_value = min(self.min * other.min, self.min * other.max, self.max * other.min) if isinstance(other, Parameter) else -np.Inf # noqa: E501 + max_value = max(self.max * other.max, self.min * other.min) if isinstance(other, Parameter) else np.Inf + name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + return self.__mul__(other, inverse=True) \ No newline at end of file From 781fd1dd76c8984ad3f1c8bd4dcab594d521da06 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 22 Jul 2024 13:51:14 +0200 Subject: [PATCH 38/68] Allow additions between similar dimensions --- .../Objects/new_variable/descriptor_number.py | 29 +++++---- .../Objects/new_variable/parameter.py | 62 +++++++++++++++---- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index bd1d6193..5107f724 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -8,6 +8,7 @@ import numpy as np import scipp as sc +from scipp import UnitError from scipp import Variable from easyscience.global_object.undo_redo import property_stack_deco @@ -58,7 +59,7 @@ def __init__( try: self._scalar = sc.scalar(float(value), unit=unit, variance=variance) except Exception as message: - raise ValueError(message) + raise UnitError(message) super().__init__( name=name, unique_name=unique_name, @@ -193,8 +194,8 @@ def convert_unit(self, unit_str: str): raise TypeError(f'{unit_str=} must be a string representing a valid scipp unit') try: new_unit = sc.Unit(unit_str) - except Exception as message: - raise ValueError(message) + except UnitError as message: + raise UnitError(message) from None self._scalar = self._scalar.to(unit=new_unit) # Just to get return type right @@ -227,18 +228,21 @@ def as_dict(self) -> Dict[str, Any]: return raw_dict def __add__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented + original_unit = other.unit try: - new_value = self.full_value + other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + new_value = self.full_value + other.full_value name = self._name + ' + ' + other._name + other.convert_unit(original_unit) return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented try: new_value = self.full_value - other.full_value except Exception as message: @@ -247,9 +251,10 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: - if not isinstance(other, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber') + if not type(other) == DescriptorNumber: + return NotImplemented try: + other.convert_unit(self.unit) new_value = self.full_value * other.full_value except Exception as message: raise ValueError(message) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index de6d8634..82d51ed8 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -17,6 +17,7 @@ import numpy as np import scipp as sc +from scipp import UnitError from scipp import Variable from easyscience import global_object @@ -421,20 +422,35 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + def __add__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if not isinstance(other, DescriptorNumber): + return NotImplemented + original_unit = other.unit try: - new_value = self.full_value + other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None + new_value = self.full_value + other.full_value min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name if not inverse else other.name+" + "+self.name + name = self.name+" + "+other.name + other.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__add__(other, inverse=True) + if not isinstance(other, DescriptorNumber): + return NotImplemented + 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = other.name+" + "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): @@ -463,13 +479,35 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = Fal if not issubclass(other.__class__, DescriptorNumber): raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') try: + other.convert_unit(self.unit) new_value = self.full_value * other.full_value except Exception as message: raise ValueError(message) - min_value = min(self.min * other.min, self.min * other.max, self.max * other.min) if isinstance(other, Parameter) else -np.Inf # noqa: E501 - max_value = max(self.max * other.max, self.min * other.min) if isinstance(other, Parameter) else np.Inf + combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] + min_value = min(combinations) if isinstance(other, Parameter) else -np.Inf + max_value = max(combinations) if isinstance(other, Parameter) else np.Inf name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__mul__(other, inverse=True) \ No newline at end of file + return self.__mul__(other, inverse=True) + + def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: + if not issubclass(other.__class__, DescriptorNumber): + raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') + try: + new_value = self.full_value / other.full_value if not inverse else other.full_value / self.full_value + except Exception as message: + raise ValueError(message) + if isinstance(other, Parameter): + if not inverse: + combinations = [self.min / other.max, self.max / other.min, self.min / other.min, self.max / other.max] + else: + combinations = [other.min / self.max, other.max / self.min, other.min / self.min, other.max / self.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" / "+other.name if not inverse else other.name+" / "+self.name + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From 3c29e7be5011510001272e0257312b5ce0996291 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 22 Jul 2024 16:29:40 +0200 Subject: [PATCH 39/68] Allow addition with scalars --- .../Objects/new_variable/descriptor_number.py | 35 ++++--- .../Objects/new_variable/parameter.py | 91 +++++++++++-------- .../Objects/new_variable/test_parameter.py | 30 +++++- 3 files changed, 106 insertions(+), 50 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 5107f724..972d99a6 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -228,26 +228,35 @@ def as_dict(self) -> Dict[str, Any]: return raw_dict def __add__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be added to dimensionless values") + self.value += other + return self + elif type(other) == 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 + new_value = self.full_value + other.full_value + name = self._name + ' + ' + other._name + other.convert_unit(original_unit) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: return NotImplemented - 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 - new_value = self.full_value + other.full_value - name = self._name + ' + ' + other._name - other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: return NotImplemented + original_unit = other.unit try: - new_value = self.full_value - other.full_value - except Exception as message: - raise ValueError(message) + other.convert_unit(self.unit) + except UnitError: + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + new_value = self.full_value - other.full_value name = self._name + ' - ' + other._name + other.convert_unit(original_unit) return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 82d51ed8..3db8a33a 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -422,58 +422,77 @@ def __repr__(self) -> str: def __float__(self) -> float: return float(self._scalar.value) - def __add__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + 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") + self.value += other + return self + elif isinstance(other, 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = self.name+" + "+other.name + other.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: + return NotImplemented + + def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be added to dimensionless values") + self.value += other + return self + elif isinstance(other, DescriptorNumber): + 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 + new_value = self.full_value + other.full_value + min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf + max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + name = other.name+" + "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: + return NotImplemented + + def __sub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if not isinstance(other, DescriptorNumber): return NotImplemented 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 - new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = self.name+" + "+other.name + raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None + new_value = self.full_value - other.full_value + min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf + max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + name = self.name+" - "+other.name other.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + + def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if not isinstance(other, DescriptorNumber): return NotImplemented 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 - new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf - name = other.name+" + "+self.name + raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None + new_value = other.full_value - self.full_value + min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf + max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + name = other.name+" - "+self.name self.convert_unit(original_unit) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __sub__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - new_value = self.full_value - other.full_value if not inverse else other.full_value - self.full_value - except Exception as message: - raise ValueError(message) - if isinstance(other, Parameter): - if not inverse: - min_value = self.min - other.max - max_value = self.max - other.min - else: - min_value = other.min - self.max - max_value = other.max - self.min - else: - min_value = -np.Inf - max_value = np.Inf - name = self.name+" - "+other.name if not inverse else other.name+" - "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - - def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__sub__(other, inverse=True) def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 1fb776f8..1cd2d3c3 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock import scipp as sc +import numpy as np from easyscience.Objects.new_variable.parameter import Parameter from easyscience import global_object @@ -334,4 +335,31 @@ def test_as_data_dict(self, clear, parameter: Parameter): "display_name": "display_name", "enabled": "enabled", "unique_name": "Parameter_0", - } \ No newline at end of file + } + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name + test", 3, "m", 0.02, -10, 30), Parameter("test + name", 3, "m", 0.02, -10, 30)), + (Parameter("test", 2, "m", 0.01), Parameter("name + test", 3, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test + name", 3, "m", 0.02, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))]) + def test_parameter_parameter_addition(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter + test + result_reverse = test + parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max \ No newline at end of file From 480343255015e2667eacfb5b21df92dd69f9287a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 12:59:44 +0200 Subject: [PATCH 40/68] add tests for additions --- .../Objects/new_variable/descriptor_number.py | 15 +++- .../Objects/new_variable/parameter.py | 14 +++- .../new_variable/test_descriptor_number.py | 52 ++++++++++++- .../Objects/new_variable/test_parameter.py | 75 ++++++++++++++++++- 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 972d99a6..a15e9e30 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -231,8 +231,9 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + name = self.name + ' + ' + str(other) + return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -246,6 +247,16 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: else: return NotImplemented + 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") + new_value = other + self.value + name = str(other) + ' + ' + self.name + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + else: + return NotImplemented + def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: return NotImplemented diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 3db8a33a..fe60b9fa 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,8 +426,11 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + min_value = self.min + other + max_value = self.max + other + name = f"{self.name} + {other}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -447,8 +450,11 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - self.value += other - return self + new_value = self.value + other + min_value = self.min + other + max_value = self.max + other + name = f"{other} + {self.name}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index c5f8a09a..17263465 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock import scipp as sc +from scipp import UnitError + from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber from easyscience import global_object @@ -198,4 +200,52 @@ def test_as_data_dict(self, clear, descriptor: DescriptorNumber): "url": "url", "display_name": "display_name", "unique_name": "DescriptorNumber_0", - } \ No newline at end of file + } + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test + name", 3, "m", 0.11)), + (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test + name", 102, "cm", 1000.01))], + ids=["regular", "unit_conversion"]) + def test_addition(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test + descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + assert descriptor.unit == 'm' + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_addition_with_scalar(self, scalar): + # When + descriptor = DescriptorNumber(name="name", value=1, variance=0.1) + + # Then + result = descriptor + scalar + result_reverse = scalar + descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name + " + str(scalar) + assert result.value == 2.0 + assert result.unit == "dimensionless" + assert result.variance == 0.1 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.1 + + @pytest.mark.parametrize("test", [1.0, DescriptorNumber("test", 2, "s",)], ids=["add_scalar_to_unit", "incompatible_units"]) + def test_addition_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(UnitError): + result = descriptor + test + with pytest.raises(UnitError): + result_reverse = test + descriptor + \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 1cd2d3c3..0d7e402b 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -3,7 +3,11 @@ import scipp as sc import numpy as np +from scipp import UnitError + from easyscience.Objects.new_variable.parameter import Parameter +from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber +from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object class TestParameter: @@ -340,8 +344,9 @@ def test_as_data_dict(self, clear, parameter: Parameter): @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name + test", 3, "m", 0.02, -10, 30), Parameter("test + name", 3, "m", 0.02, -10, 30)), (Parameter("test", 2, "m", 0.01), Parameter("name + test", 3, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test + name", 3, "m", 0.02, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))]) - def test_parameter_parameter_addition(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name + test", 1.02, "m", 0.010001, -0.1, 10.1), Parameter("test + name", 102, "cm", 100.01, -10, 1010))], + ids=["regular", "no_bounds", "unit_conversion"]) + def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() @@ -362,4 +367,68 @@ def test_parameter_parameter_addition(self, parameter : Parameter, test : Parame assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max \ No newline at end of file + assert result_reverse.max == expected_reverse.max + + assert parameter.unit == "m" + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_addition_with_scalar(self, scalar): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=0, max=10) + + # Then + result = parameter + scalar + result_reverse = scalar + parameter + + # Expect + assert result.name == "name + " + str(scalar) + assert result.value == 2.0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == 1.0 + assert result.max == 11.0 + + assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == 1.0 + assert result_reverse.max == 11.0 + + def test_addition_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=1, variance=0.1, unit="cm") + + # Then + result = parameter + descriptor_number + result_reverse = descriptor_number + parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name + test" + assert result.value == 1.01 + assert result.unit == "m" + assert result.variance == 0.01001 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test + name" + assert result_reverse.value == 101.0 + assert result_reverse.unit == "cm" + assert result_reverse.variance == 100.1 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + assert parameter.unit == "m" + assert descriptor_number.unit == "cm" + + @pytest.mark.parametrize("test", [1.0, Parameter("test", 2, "s",)], ids=["add_scalar_to_unit", "incompatible_units"]) + def test_addition_exception(self, parameter : Parameter, test): + # When Then Expect + with pytest.raises(UnitError): + result = parameter + test + with pytest.raises(UnitError): + result_reverse = test + parameter + \ No newline at end of file From c91732bf48c6370f35aa2d4f2fa93d288ee8d618 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 13:06:36 +0200 Subject: [PATCH 41/68] subtraction with scalars --- .../Objects/new_variable/descriptor_number.py | 37 +++++++--- .../Objects/new_variable/parameter.py | 68 ++++++++++++------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index a15e9e30..68c74f7f 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -258,17 +258,34 @@ def __radd__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be subtracted from dimensionless values") + new_value = self.value - other + name = self.name + ' - ' + str(other) + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + elif type(other) == 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 + new_value = self.full_value - other.full_value + name = self._name + ' - ' + other._name + other.convert_unit(original_unit) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: + return NotImplemented + + 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") + new_value = other - self.value + name = str(other) + ' - ' + self.name + return DescriptorNumber(name=name, value=new_value, variance=self.variance) + else: return NotImplemented - 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 - new_value = self.full_value - other.full_value - name = self._name + ' - ' + other._name - other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: if not type(other) == DescriptorNumber: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index fe60b9fa..7a8d723b 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -470,35 +470,53 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: else: return NotImplemented - def __sub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - if not isinstance(other, DescriptorNumber): + 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") + new_value = self.value - other + min_value = self.min - other + max_value = self.max - other + name = f"{self.name} - {other}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) + elif isinstance(other, 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 + new_value = self.full_value - other.full_value + min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf + max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + name = self.name+" - "+other.name + other.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: return NotImplemented - 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 - new_value = self.full_value - other.full_value - min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf - max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf - name = self.name+" - "+other.name - other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - if not isinstance(other, DescriptorNumber): + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Numbers can only be subtracted from dimensionless values") + new_value = other - self.value + min_value = other - self.min + max_value = other - self.max + name = f"{other} - {self.name}" + return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): + 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 + new_value = other.full_value - self.full_value + min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf + max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + name = other.name+" - "+self.name + self.convert_unit(original_unit) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + else: return NotImplemented - 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 - new_value = other.full_value - self.full_value - min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf - max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf - name = other.name+" - "+self.name - self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): From 549b9a2590e53b3da78f2c9d7accda53d3fb3e78 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 23 Jul 2024 15:19:13 +0200 Subject: [PATCH 42/68] tests for subtraction --- .../Objects/new_variable/parameter.py | 4 +- .../new_variable/test_descriptor_number.py | 48 +++++++++- .../Objects/new_variable/test_parameter.py | 92 ++++++++++++++++++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 7a8d723b..a38188d5 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -499,8 +499,8 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: if self.unit != 'dimensionless': raise UnitError("Numbers can only be subtracted from dimensionless values") new_value = other - self.value - min_value = other - self.min - max_value = other - self.max + min_value = other - self.max + max_value = other - self.min name = f"{other} - {self.name}" return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 17263465..1fa22eae 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -248,4 +248,50 @@ def test_addition_exception(self, descriptor: DescriptorNumber, test): result = descriptor + test with pytest.raises(UnitError): result_reverse = test + descriptor - \ No newline at end of file + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test - name", 1, "m", 0.11)), + (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test - name", -98, "cm", 1000.01))], + ids=["regular", "unit_conversion"]) + def test_subtraction(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test - descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + assert descriptor.unit == 'm' + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_subtraction_with_scalar(self, scalar): + # When + descriptor = DescriptorNumber(name="name", value=2, variance=0.1) + + # Then + result = descriptor - scalar + result_reverse = scalar - descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name - " + str(scalar) + assert result.value == 1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.1 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.value == -1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.1 + + @pytest.mark.parametrize("test", [1.0, DescriptorNumber("test", 2, "s",)], ids=["sub_scalar_to_unit", "incompatible_units"]) + def test_subtraction_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(UnitError): + result = test - descriptor + with pytest.raises(UnitError): + result_reverse = descriptor - test \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 0d7e402b..6c712f47 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -431,4 +431,94 @@ def test_addition_exception(self, parameter : Parameter, test): result = parameter + test with pytest.raises(UnitError): result_reverse = test + parameter - \ No newline at end of file + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -20, 20), Parameter("name - test", -1, "m", 0.02, -20, 30), Parameter("test - name", 1, "m", 0.02, -30, 20)), + (Parameter("test", 2, "m", 0.01), Parameter("name - test", -1, "m", 0.02, min=-np.Inf, max=np.Inf),Parameter("test - name", 1, "m", 0.02, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "cm", 0.01, -10, 10), Parameter("name - test", 0.98, "m", 0.010001, -0.1, 10.1), Parameter("test - name", -98, "cm", 100.01, -1010, 10))], + ids=["regular", "no_bounds", "unit_conversion"]) + def test_subtraction_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter - test + result_reverse = test - parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + assert parameter.unit == "m" + + @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) + def test_subtraction_with_scalar(self, scalar): + # When + parameter = Parameter(name="name", value=2, variance=0.01, min=0, max=10) + + # Then + result = parameter - scalar + result_reverse = scalar - parameter + + # Expect + assert result.name == "name - " + str(scalar) + assert result.value == 1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == -1.0 + assert result.max == 9.0 + + assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.value == -1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == -9.0 + assert result_reverse.max == 1.0 + + def test_subtraction_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=1, variance=0.1, unit="cm") + + # Then + result = parameter - descriptor_number + result_reverse = descriptor_number - parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name - test" + assert result.value == 0.99 + assert result.unit == "m" + assert result.variance == 0.01001 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test - name" + assert result_reverse.value == -99.0 + assert result_reverse.unit == "cm" + assert result_reverse.variance == 100.1 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + assert parameter.unit == "m" + assert descriptor_number.unit == "cm" + + @pytest.mark.parametrize("test", [1.0, Parameter("test", 2, "s",)], ids=["sub_scalar_to_unit", "incompatible_units"]) + def test_subtraction_exception(self, parameter : Parameter, test): + # When Then Expect + with pytest.raises(UnitError): + result = parameter - test + with pytest.raises(UnitError): + result_reverse = test - parameter \ No newline at end of file From 23b275bf175cebee6a4f2823e12986ac98355559 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 24 Jul 2024 11:10:44 +0200 Subject: [PATCH 43/68] multiplication with scalars --- .../Objects/new_variable/descriptor_number.py | 34 ++++++++--- .../Objects/new_variable/parameter.py | 59 ++++++++++++++----- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 68c74f7f..79109c46 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -288,12 +288,30 @@ def __rsub__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: - if not type(other) == DescriptorNumber: - return NotImplemented - try: - other.convert_unit(self.unit) + if isinstance(other, numbers.Number): + new_value = self.full_value * other + name = self.name + ' * ' + str(other) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + elif type(other) == DescriptorNumber: new_value = self.full_value * other.full_value - except Exception as message: - raise ValueError(message) - name = self._name + ' * ' + other._name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) \ No newline at end of file + name = self._name + ' * ' + other._name + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(self._base_unit()) + return descriptor_number + else: + return NotImplemented + + def __rmul__(self, other: numbers.Number) -> DescriptorNumber: + if isinstance(other, numbers.Number): + new_value = other * self.full_value + name = str(other) + ' * ' + self.name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + else: + return NotImplemented + + def _base_unit(self) -> str: + string = str(self._scalar.unit) + for i in range(len(string)): + if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: + return string[i+1:-1] + raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index a38188d5..41a92ade 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -518,22 +518,53 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: else: return NotImplemented - def __mul__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - other.convert_unit(self.unit) + def __mul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + if isinstance(other, numbers.Number): + new_value = self.full_value * other + combinations = [self.min * other, self.max * other] + min_value = min(combinations) + max_value = max(combinations) + name = f"{self.name} * {other}" + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value - except Exception as message: - raise ValueError(message) - combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] - min_value = min(combinations) if isinstance(other, Parameter) else -np.Inf - max_value = max(combinations) if isinstance(other, Parameter) else np.Inf - name = self.name+" * "+other.name if not inverse else other.name+" * "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - + if isinstance(other, Parameter): + combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = self.name+" * "+other.name + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(self._base_unit()) + return parameter + else: + return NotImplemented + def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: - return self.__mul__(other, inverse=True) + if isinstance(other, numbers.Number): + new_value = other * self.full_value + combinations = [other * self.min, other * self.max] + min_value = min(combinations) + max_value = max(combinations) + name = f"{other} * {self.name}" + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + elif isinstance(other, DescriptorNumber): + new_value = other.full_value * self.full_value + if isinstance(other, Parameter): + combinations = [other.min * self.max, other.max * self.min, other.min * self.min, other.max * self.max] + min_value = min(combinations) + max_value = max(combinations) + else: + min_value = -np.Inf + max_value = np.Inf + name = other.name+" * "+self.name + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(self._base_unit()) + return parameter + else: + return NotImplemented def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): From d6bb54cd055d8c43c48a5107cac9335d2459bbb5 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 25 Jul 2024 13:17:04 +0200 Subject: [PATCH 44/68] tests for multiplication plus cleanup --- .../Objects/new_variable/descriptor_number.py | 35 ++-- .../Objects/new_variable/parameter.py | 92 +++++---- .../new_variable/test_descriptor_number.py | 57 ++++-- .../Objects/new_variable/test_parameter.py | 179 ++++++++++++++++-- 4 files changed, 262 insertions(+), 101 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 79109c46..03526643 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -227,13 +227,12 @@ def as_dict(self) -> Dict[str, Any]: raw_dict['variance'] = self._scalar.variance return raw_dict - def __add__(self, other: DescriptorNumber) -> DescriptorNumber: + 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.value + other + new_value = self.full_value + other name = self.name + ' + ' + str(other) - return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -243,27 +242,26 @@ def __add__(self, other: DescriptorNumber) -> DescriptorNumber: new_value = self.full_value + other.full_value name = self._name + ' + ' + other._name other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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") - new_value = other + self.value + new_value = other + self.full_value name = str(other) + ' + ' + self.name - return DescriptorNumber(name=name, value=new_value, variance=self.variance) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) - def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: + 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") - new_value = self.value - other + new_value = self.full_value - other name = self.name + ' - ' + str(other) - return DescriptorNumber(name=name, value=new_value, variance=self.variance) elif type(other) == DescriptorNumber: original_unit = other.unit try: @@ -273,45 +271,44 @@ def __sub__(self, other: DescriptorNumber) -> DescriptorNumber: new_value = self.full_value - other.full_value name = self._name + ' - ' + other._name other.convert_unit(original_unit) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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") - new_value = other - self.value + new_value = other - self.full_value name = str(other) + ' - ' + self.name - return DescriptorNumber(name=name, value=new_value, variance=self.variance) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) - def __mul__(self, other: DescriptorNumber) -> DescriptorNumber: + def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = self.full_value * other name = self.name + ' * ' + str(other) - return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif type(other) == DescriptorNumber: new_value = self.full_value * other.full_value name = self._name + ' * ' + other._name - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) - descriptor_number.convert_unit(self._base_unit()) - return descriptor_number else: return NotImplemented + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(descriptor_number._base_unit()) + return descriptor_number def __rmul__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = other * self.full_value name = str(other) + ' * ' + self.name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) else: return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: - return string[i+1:-1] + return string[i:] raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 41a92ade..7f75eab7 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,11 +426,10 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if isinstance(other, numbers.Number): if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") - new_value = self.value + other + new_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -438,23 +437,22 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + 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 name = self.name+" + "+other.name other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> 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") - new_value = self.value + other + new_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: @@ -462,23 +460,22 @@ def __radd__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be added") from None new_value = self.full_value + other.full_value - min_value = self.min + other.min if isinstance(other, Parameter) else -np.Inf - max_value = self.max + other.max if isinstance(other, Parameter) else np.Inf + min_value = self.min + other.value + max_value = self.max + other.value name = other.name+" + "+self.name self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) 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") - new_value = self.value - other + new_value = self.full_value - other min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = other.unit try: @@ -486,23 +483,26 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_value = self.full_value - other.full_value - min_value = self.min - other.max if isinstance(other, Parameter) else -np.Inf - max_value = self.max - other.min if isinstance(other, Parameter) else np.Inf + if isinstance(other, Parameter): + min_value = self.min - other.max if other.max != np.Inf else -np.Inf + max_value = self.max - other.min if other.min != -np.Inf else np.Inf + else: + min_value = self.min - other.value + max_value = self.max - other.value name = self.name+" - "+other.name other.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> 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") - new_value = other - self.value + new_value = other - self.full_value min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" - return Parameter(name=name, value=new_value, variance=self.variance, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): original_unit = self.unit try: @@ -510,61 +510,57 @@ def __rsub__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None new_value = other.full_value - self.full_value - min_value = other.min - self.max if isinstance(other, Parameter) else -np.Inf - max_value = other.max - self.min if isinstance(other, Parameter) else np.Inf + min_value = other.value - self.max + max_value = other.value - self.min name = other.name+" - "+self.name self.convert_unit(original_unit) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) else: return NotImplemented + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - def __mul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = self.full_value * other combinations = [self.min * other, self.max * other] - min_value = min(combinations) - max_value = max(combinations) name = f"{self.name} * {other}" - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value if isinstance(other, Parameter): - combinations = [self.min * other.max, self.max * other.min, self.min * other.min, self.max * other.max] - min_value = min(combinations) - max_value = max(combinations) + combinations = [] + for first, second in [(self.min, other.min), (self.min, other.max), (self.max, other.min), (self.max, other.max)]: # noqa: E501 + if (first == np.Inf and second == 0) or (first == 0 and second == np.Inf): + combinations.append(np.Inf) + elif (first == -np.Inf and second == 0) or (first == 0 and second == -np.Inf): + combinations.append(-np.Inf) + else: + combinations.append(first * second) else: - min_value = -np.Inf - max_value = np.Inf + combinations = [self.min * other.value, self.max * other.value] name = self.name+" * "+other.name - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - parameter.convert_unit(self._base_unit()) - return parameter else: return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter - def __rmul__(self, other: Union[DescriptorNumber, Parameter]) -> Parameter: + def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = other * self.full_value combinations = [other * self.min, other * self.max] - min_value = min(combinations) - max_value = max(combinations) name = f"{other} * {self.name}" - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - if isinstance(other, Parameter): - combinations = [other.min * self.max, other.max * self.min, other.min * self.min, other.max * self.max] - min_value = min(combinations) - max_value = max(combinations) - else: - min_value = -np.Inf - max_value = np.Inf + combinations = [self.min * other.value, self.max * other.value] name = other.name+" * "+self.name - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) - parameter.convert_unit(self._base_unit()) - return parameter else: return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: if not issubclass(other.__class__, DescriptorNumber): diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 1fa22eae..5b07f405 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -219,24 +219,23 @@ def test_addition(self, descriptor: DescriptorNumber, test, expected): assert descriptor.unit == 'm' - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_addition_with_scalar(self, scalar): + def test_addition_with_scalar(self): # When descriptor = DescriptorNumber(name="name", value=1, variance=0.1) # Then - result = descriptor + scalar - result_reverse = scalar + descriptor + result = descriptor + 1.0 + result_reverse = 1.0 + descriptor # Expect assert type(result) == DescriptorNumber - assert result.name == "name + " + str(scalar) + assert result.name == "name + 1.0" assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.name == "1.0 + name" assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -266,24 +265,23 @@ def test_subtraction(self, descriptor: DescriptorNumber, test, expected): assert descriptor.unit == 'm' - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_subtraction_with_scalar(self, scalar): + def test_subtraction_with_scalar(self): # When descriptor = DescriptorNumber(name="name", value=2, variance=0.1) # Then - result = descriptor - scalar - result_reverse = scalar - descriptor + result = descriptor - 1.0 + result_reverse = 1.0 - descriptor # Expect assert type(result) == DescriptorNumber - assert result.name == "name - " + str(scalar) + assert result.name == "name - 1.0" assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.name == "1.0 - name" assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -294,4 +292,37 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): with pytest.raises(UnitError): result = test - descriptor with pytest.raises(UnitError): - result_reverse = descriptor - test \ No newline at end of file + result_reverse = descriptor - test + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], + ids=["regular", "base_unit_conversion"]) + def test_multiplication(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = test * descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): + # When Then + result = descriptor * 2.0 + result_reverse = 2.0 * descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name * 2.0" + assert result.value == 2.0 + assert result.unit == "m" + assert result.variance == 0.4 + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == "2.0 * name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "m" + assert result_reverse.variance == 0.4 \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 6c712f47..f85ecf46 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -371,24 +371,23 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, assert parameter.unit == "m" - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_addition_with_scalar(self, scalar): + def test_addition_with_scalar(self): # When parameter = Parameter(name="name", value=1, variance=0.01, min=0, max=10) # Then - result = parameter + scalar - result_reverse = scalar + parameter + result = parameter + 1.0 + result_reverse = 1.0 + parameter # Expect - assert result.name == "name + " + str(scalar) + assert result.name == "name + 1.0" assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == 1.0 assert result.max == 11.0 - assert result_reverse.name == str(scalar) + " + name" + assert result_reverse.name == "1.0 + name" assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -410,16 +409,16 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): assert result.value == 1.01 assert result.unit == "m" assert result.variance == 0.01001 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.min == 0.01 + assert result.max == 10.01 assert type(result_reverse) == Parameter assert result_reverse.name == "test + name" assert result_reverse.value == 101.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.min == 1 + assert result_reverse.max == 1001 assert parameter.unit == "m" assert descriptor_number.unit == "cm" @@ -462,24 +461,47 @@ def test_subtraction_with_parameter(self, parameter : Parameter, test : Paramete assert parameter.unit == "m" - @pytest.mark.parametrize("scalar", [1, 1.0], ids=["int", "float"]) - def test_subtraction_with_scalar(self, scalar): + def test_subtraction_with_parameter_nan_cases(self): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) + test = Parameter(name="test", value=2, variance=0.01, min=-np.Inf, max=np.Inf) + + # Then + result = parameter - test + result_reverse = test - parameter + + # Expect + assert result.name == "name - test" + assert result.value == -1.0 + assert result.unit == "dimensionless" + assert result.variance == 0.02 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert result_reverse.name == "test - name" + assert result_reverse.value == 1.0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.02 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + def test_subtraction_with_scalar(self): # When parameter = Parameter(name="name", value=2, variance=0.01, min=0, max=10) # Then - result = parameter - scalar - result_reverse = scalar - parameter + result = parameter - 1.0 + result_reverse = 1.0 - parameter # Expect - assert result.name == "name - " + str(scalar) + assert result.name == "name - 1.0" assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == -1.0 assert result.max == 9.0 - assert result_reverse.name == str(scalar) + " - name" + assert result_reverse.name == "1.0 - name" assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -501,16 +523,16 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): assert result.value == 0.99 assert result.unit == "m" assert result.variance == 0.01001 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.min == -0.01 + assert result.max == 9.99 assert type(result_reverse) == Parameter assert result_reverse.name == "test - name" assert result_reverse.value == -99.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.min == -999 + assert result_reverse.max == 1 assert parameter.unit == "m" assert descriptor_number.unit == "cm" @@ -521,4 +543,119 @@ def test_subtraction_exception(self, parameter : Parameter, test): with pytest.raises(UnitError): result = parameter - test with pytest.raises(UnitError): - result_reverse = test - parameter \ No newline at end of file + result_reverse = test - parameter + + # parameter = Parameter( + # name="name", + # value=1, + # unit="m", + # variance=0.01, + # min=0, + # max=10, + # description="description", + # url="url", + # display_name="display_name", + # callback=self.mock_callback, + # enabled="enabled", + # parent=None, + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], + ids=["regular", "no_bounds", "base_unit_conversion"]) + def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter * test + result_reverse = test * parameter + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + assert result.min == expected.min + assert result.max == expected.max + + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + def test_multiplication_with_parameter_nan_cases(self): + # When + parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) + test = Parameter(name="test", value=0, variance=0.01, min=0, max=0) + + # Then + result = parameter * test + result_reverse = test * parameter + + # Expect + assert result.name == "name * test" + assert result.value == 0 + assert result.unit == "dimensionless" + assert result.variance == 0.01 + assert result.min == -np.Inf + assert result.max == np.Inf + + assert result_reverse.name == "test * name" + assert result_reverse.value == 0 + assert result_reverse.unit == "dimensionless" + assert result_reverse.variance == 0.01 + assert result_reverse.min == -np.Inf + assert result_reverse.max == np.Inf + + def test_multiplication_with_descriptor_number(self, parameter : Parameter): + # When + parameter._callback = property() + descriptor_number = DescriptorNumber(name="test", value=2, variance=0.1, unit="cm") + + # Then + result = parameter * descriptor_number + result_reverse = descriptor_number * parameter + + # Expect + assert type(result) == Parameter + assert result.name == "name * test" + assert result.value == 2 + assert result.unit == "dm^2" + assert result.variance == 0.14 + assert result.min == 0 + assert result.max == 20 + + assert type(result_reverse) == Parameter + assert result_reverse.name == "test * name" + assert result_reverse.value == 2 + assert result_reverse.unit == "dm^2" + assert result_reverse.variance == 0.14 + assert result_reverse.min == 0 + assert result_reverse.max == 20 + + def test_multiplication_with_scalar(self, parameter : Parameter): + # When + parameter._callback = property() + + # Then + result = parameter * 2 + result_reverse = 2 * parameter + + # Expect + assert result.name == "name * 2" + assert result.value == 2.0 + assert result.unit == "m" + assert result.variance == 0.04 + assert result.min == 0.0 + assert result.max == 20.0 + + assert result_reverse.name == "2 * name" + assert result_reverse.value == 2.0 + assert result_reverse.unit == "m" + assert result_reverse.variance == 0.04 + assert result_reverse.min == 0.0 + assert result_reverse.max == 20.0 \ No newline at end of file From e2d59eb5d47b242f44099e75094f76ce5fc1ca24 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 26 Jul 2024 15:46:07 +0200 Subject: [PATCH 45/68] Divion methods added --- .../Objects/new_variable/descriptor_number.py | 31 ++++++ .../Objects/new_variable/parameter.py | 104 +++++++++++++++--- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 03526643..3f62e30a 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -15,6 +15,7 @@ from .descriptor_base import DescriptorBase +INFINITESIMAL = 1e-9 class DescriptorNumber(DescriptorBase): """ @@ -306,6 +307,36 @@ def __rmul__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: + if isinstance(other, numbers.Number): + original_other = other + if other == 0: + other = INFINITESIMAL + new_value = self.full_value / other + name = self.name + ' / ' + str(original_other) + elif type(other) == DescriptorNumber: + original_other = other.value + if original_other == 0: + other.value = INFINITESIMAL + new_value = self.full_value / other.full_value + other.value = original_other + name = self._name + ' / ' + other._name + else: + return NotImplemented + descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + descriptor_number.convert_unit(descriptor_number._base_unit()) + return descriptor_number + + def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: + if isinstance(other, numbers.Number): + if self.value == 0: + self.value = INFINITESIMAL + new_value = other / self.full_value + name = str(other) + ' / ' + self.name + else: + return NotImplemented + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 7f75eab7..e04d62e0 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -26,6 +26,7 @@ from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Utils.Exceptions import CoreSetException +from .descriptor_number import INFINITESIMAL from .descriptor_number import DescriptorNumber Constraints = namedtuple('Constraints', ['user', 'builtin', 'virtual']) @@ -86,6 +87,8 @@ def __init__( raise ValueError(f'{value=} can not be less than {min=}') if value > max: raise ValueError(f'{value=} can not be greater than {max=}') + if min == max: + 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') @@ -247,6 +250,8 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') + if min_value == self._max.value: + raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: self._min.value = min_value else: @@ -273,6 +278,8 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') + if max_value == self._min.value: + raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: self._max.value = max_value else: @@ -562,22 +569,85 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: parameter.convert_unit(parameter._base_unit()) return parameter - def __truediv__(self, other: Union[DescriptorNumber, Parameter], inverse: bool = False) -> Parameter: - if not issubclass(other.__class__, DescriptorNumber): - raise TypeError(f'{other=} must be a DescriptorNumber or Parameter') - try: - new_value = self.full_value / other.full_value if not inverse else other.full_value / self.full_value - except Exception as message: - raise ValueError(message) - if isinstance(other, Parameter): - if not inverse: - combinations = [self.min / other.max, self.max / other.min, self.min / other.min, self.max / other.max] + def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: + if isinstance(other, numbers.Number): + original_other = other + if other == 0: + other = INFINITESIMAL + new_value = self.full_value / other + combinations = [self.min / other, self.max / other] + name = f"{self.name} / {original_other}" + elif isinstance(other, DescriptorNumber): + original_value = other.value + if original_value == 0: + other.value = INFINITESIMAL + new_value = self.full_value / other.full_value + if isinstance(other, Parameter): + 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): + combinations = [-np.Inf, np.Inf] + elif self.min >= 0: + combinations = [self.min/other.max, np.Inf] + elif self.max <= 0: + combinations = [-np.Inf, self.max/other.max] + elif other.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] + elif self.max <= 0: + 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] else: - combinations = [other.min / self.max, other.max / self.min, other.min / self.min, other.max / self.max] - min_value = min(combinations) - max_value = max(combinations) + combinations = [self.min / other.value, self.max / other.value] + name = self.name+" / "+other.name + other.value = original_value + else: + return NotImplemented + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + return parameter + + def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: + original_self = self.value + if original_self == 0: + self.value = INFINITESIMAL + if isinstance(other, numbers.Number): + new_value = other / self.full_value + other_value = other + name = f"{other} / {self.name}" + if other_value == 0: + return DescriptorNumber.from_scipp(name=name, value=new_value) + elif isinstance(other, DescriptorNumber): + new_value = other.full_value / self.full_value + other_value = other.value + name = other.name+" / "+self.name + if other_value == 0: + return DescriptorNumber.from_scipp(name=name, value=new_value) + else: + return NotImplemented + 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] + elif other_value < 0: + combinations = [-np.Inf, other_value/self.max] + elif self.max == 0: + if other_value > 0: + combinations = [-np.Inf, other_value/self.min] + elif other_value < 0: + combinations = [other_value/self.min, np.Inf] else: - min_value = -np.Inf - max_value = np.Inf - name = self.name+" / "+other.name if not inverse else other.name+" / "+self.name - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file + combinations = [other_value / self.min, other_value / self.max] + min_value = min(combinations) + max_value = max(combinations) + parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter.convert_unit(parameter._base_unit()) + self.value = original_self + return parameter \ No newline at end of file From 9bb730a0ec3264caf3b64e6976e25913944baa78 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 26 Jul 2024 15:55:05 +0200 Subject: [PATCH 46/68] account for unit cancellation in base_unit method --- .../Objects/new_variable/descriptor_number.py | 2 +- .../Objects/new_variable/test_descriptor_number.py | 5 +++-- tests/unit_tests/Objects/new_variable/test_parameter.py | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 3f62e30a..4567b153 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -342,4 +342,4 @@ def _base_unit(self) -> str: for i in range(len(string)): if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: return string[i:] - raise ValueError(f"Could not find base unit for {string}") \ No newline at end of file + return "" \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 5b07f405..f6a3578e 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -296,8 +296,9 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), - (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], - ids=["regular", "base_unit_conversion"]) + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041)), + (DescriptorNumber("test", 2, "1/dm", 0.01), DescriptorNumber("test * name", 20.0, "dimensionless", 41))], + ids=["regular", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # When Then result = test * descriptor diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index f85ecf46..17f318bf 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -560,10 +560,11 @@ def test_subtraction_exception(self, parameter : Parameter, test): # parent=None, @pytest.mark.parametrize("test, expected, expected_reverse", [ - (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), - (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], - ids=["regular", "no_bounds", "base_unit_conversion"]) + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), + (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], + ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() From e0c4017905c3be3f04f362f9ed6964de53394afc Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 29 Jul 2024 11:41:29 +0200 Subject: [PATCH 47/68] account for multiplication with 0 --- .../Objects/new_variable/parameter.py | 24 ++-- .../Objects/new_variable/test_parameter.py | 121 ++++++++++-------- 2 files changed, 84 insertions(+), 61 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index e04d62e0..38449572 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -528,22 +528,26 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = self.full_value * other - combinations = [self.min * other, self.max * other] name = f"{self.name} * {other}" + if other == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value + name = self.name+" * "+other.name + if other.value == 0 and type(other) == DescriptorNumber: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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 - if (first == np.Inf and second == 0) or (first == 0 and second == np.Inf): - combinations.append(np.Inf) - elif (first == -np.Inf and second == 0) or (first == 0 and second == -np.Inf): - combinations.append(-np.Inf) + if first == 0 and np.isinf(second): + combinations.append(0) + elif second == 0 and np.isinf(first): + combinations.append(0) else: combinations.append(first * second) else: combinations = [self.min * other.value, self.max * other.value] - name = self.name+" * "+other.name else: return NotImplemented min_value = min(combinations) @@ -555,12 +559,16 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_value = other * self.full_value - combinations = [other * self.min, other * self.max] name = f"{other} * {self.name}" + if other == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - combinations = [self.min * other.value, self.max * other.value] name = other.name+" * "+self.name + if other.value == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 17f318bf..73dbf24c 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -560,10 +560,10 @@ def test_subtraction_exception(self, parameter : Parameter, test): # parent=None, @pytest.mark.parametrize("test, expected, expected_reverse", [ - (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), - (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), - (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], + (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), + (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), + (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When @@ -588,75 +588,90 @@ def test_multiplication_with_parameter(self, parameter : Parameter, test : Param assert result_reverse.min == expected_reverse.min assert result_reverse.max == expected_reverse.max - def test_multiplication_with_parameter_nan_cases(self): + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 0, "", 0.01, -10, 0), Parameter("name * test", 0.0, "dimensionless", 0.01, -np.Inf, 0), Parameter("test * name", 0, "dimensionless", 0.01, -np.Inf, 0)), + (Parameter("test", 0, "", 0.01, 0, 10), Parameter("name * test", 0.0, "dimensionless", 0.01, 0, np.Inf), Parameter("test * name", 0, "dimensionless", 0.01, 0, np.Inf))], + ids=["zero_min", "zero_max"]) + def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_reverse): # When - parameter = Parameter(name="name", value=1, variance=0.01, min=-np.Inf, max=np.Inf) - test = Parameter(name="test", value=0, variance=0.01, min=0, max=0) + parameter = Parameter(name="name", value=1, variance=0.01, min=1, max=np.Inf) # Then result = parameter * test result_reverse = test * parameter # Expect - assert result.name == "name * test" - assert result.value == 0 - assert result.unit == "dimensionless" - assert result.variance == 0.01 - assert result.min == -np.Inf - assert result.max == np.Inf + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max - assert result_reverse.name == "test * name" - assert result_reverse.value == 0 - assert result_reverse.unit == "dimensionless" - assert result_reverse.variance == 0.01 - assert result_reverse.min == -np.Inf - assert result_reverse.max == np.Inf + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max - def test_multiplication_with_descriptor_number(self, parameter : Parameter): + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), + (DescriptorNumber(name="test", value=0, variance=0.1, unit="cm"), DescriptorNumber("name * test", 0, "dm^2", 0.1), DescriptorNumber("test * name", 0, "dm^2", 0.1))], + ids=["regular", "zero_value"]) + def test_multiplication_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() - descriptor_number = DescriptorNumber(name="test", value=2, variance=0.1, unit="cm") # Then - result = parameter * descriptor_number - result_reverse = descriptor_number * parameter + result = parameter * test + result_reverse = test * parameter # Expect - assert type(result) == Parameter - assert result.name == "name * test" - assert result.value == 2 - assert result.unit == "dm^2" - assert result.variance == 0.14 - assert result.min == 0 - assert result.max == 20 + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max - assert type(result_reverse) == Parameter - assert result_reverse.name == "test * name" - assert result_reverse.value == 2 - assert result_reverse.unit == "dm^2" - assert result_reverse.variance == 0.14 - assert result_reverse.min == 0 - assert result_reverse.max == 20 - - def test_multiplication_with_scalar(self, parameter : Parameter): + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, Parameter("name * 2", 2, "m", 0.04, 0, 20), Parameter("2 * name", 2, "m", 0.04, 0, 20)), + (0, DescriptorNumber("name * 0", 0, "m", 0), DescriptorNumber("0 * name", 0, "m", 0))], + ids=["regular", "zero_value"]) + def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() # Then - result = parameter * 2 - result_reverse = 2 * parameter + result = parameter * test + result_reverse = test * parameter # Expect - assert result.name == "name * 2" - assert result.value == 2.0 - assert result.unit == "m" - assert result.variance == 0.04 - assert result.min == 0.0 - assert result.max == 20.0 + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max - assert result_reverse.name == "2 * name" - assert result_reverse.value == 2.0 - assert result_reverse.unit == "m" - assert result_reverse.variance == 0.04 - assert result_reverse.min == 0.0 - assert result_reverse.max == 20.0 \ No newline at end of file + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == expected_reverse.value + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == expected_reverse.variance + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max \ No newline at end of file From e3751db1cdac795c3fc657b960c720cae28f1516 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 30 Jul 2024 14:27:50 +0200 Subject: [PATCH 48/68] Add unittests for divisions --- .../Objects/new_variable/parameter.py | 13 +- src/easyscience/unit_aliases.py | 12 ++ .../new_variable/test_descriptor_number.py | 42 ++++- .../Objects/new_variable/test_parameter.py | 154 ++++++++++++++++-- 4 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 src/easyscience/unit_aliases.py diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 38449572..ee7b55f6 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -426,8 +426,9 @@ def __repr__(self) -> str: s.append('bounds=[%s:%s]' % (repr(self.min), repr(self.max))) return '%s>' % ', '.join(s) - def __float__(self) -> float: - return float(self._scalar.value) + # 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): @@ -591,7 +592,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) other.value = INFINITESIMAL new_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): @@ -630,16 +631,16 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame other_value = other name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif isinstance(other, DescriptorNumber): new_value = other.full_value / self.full_value other_value = other.value name = other.name+" / "+self.name if other_value == 0: - return DescriptorNumber.from_scipp(name=name, value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_value) 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: diff --git a/src/easyscience/unit_aliases.py b/src/easyscience/unit_aliases.py new file mode 100644 index 00000000..297dd0e4 --- /dev/null +++ b/src/easyscience/unit_aliases.py @@ -0,0 +1,12 @@ +import scipp as sc + +# This document shows how to define aliases for units in scipp, +# and how to overwrite existing aliases. + +sc.units.aliases.clear() # Clear existing aliases +sc.units.aliases['funny_unit'] = sc.scalar(42.0, unit='m') +# ^ 'funny_unit' is now an alias for 42 m and can be used as a unit in scipp and be converted to units of dimension 'm' + +# The representation of a unit can be changed by defining an alias for it: +sc.units.aliases['m/ms'] = 'm/ms' +# Setting this alias ensures that 'm/ms' is displayed as 'm/ms' instead of the default 'km/s' \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index f6a3578e..d58ca799 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock import scipp as sc +from scipp import UnitError from scipp import UnitError @@ -58,7 +59,7 @@ def test_init_sc_unit(self): def test_init_sc_unit_unknown(self): # When Then Expect - with pytest.raises(ValueError): + with pytest.raises(UnitError): DescriptorNumber( name="name", value=1, @@ -326,4 +327,41 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): assert result_reverse.name == "2.0 * name" assert result_reverse.value == 2.0 assert result_reverse.unit == "m" - assert result_reverse.variance == 0.4 \ No newline at end of file + assert result_reverse.variance == 0.4 + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625)), + (DescriptorNumber("test", 0, "m^2", 0.01), DescriptorNumber("name / test", 1e9, "1/m", 1e34))], + ids=["regular", "zero_value"]) + def test_division(self, descriptor: DescriptorNumber, test, expected): + # When Then + result = descriptor / test + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4)), + (0, DescriptorNumber("name / 0", 1e9, "m", 1e17), DescriptorNumber("0 / name", 0, "1/m", 0))], + ids=["regular", "zero_value"]) + def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected, expected_reverse): + # When Then + result = descriptor / test + result_reverse = test / descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + + assert type(result_reverse) == DescriptorNumber + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 73dbf24c..63322fd6 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,6 +7,7 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber +from easyscience.Objects.new_variable.descriptor_number import INFINITESIMAL from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object @@ -168,9 +169,10 @@ def test_set_error_exception(self, parameter: Parameter): with pytest.raises(ValueError): parameter.error = -0.1 - def test_float(self, parameter: Parameter): - # When Then Expect - assert float(parameter) == 1.0 + # Commented out because __float__ method might be removed + # def test_float(self, parameter: Parameter): + # # When Then Expect + # assert float(parameter) == 1.0 def test_repr(self, parameter: Parameter): # When Then Expect @@ -545,20 +547,6 @@ def test_subtraction_exception(self, parameter : Parameter, test): with pytest.raises(UnitError): result_reverse = test - parameter - # parameter = Parameter( - # name="name", - # value=1, - # unit="m", - # variance=0.01, - # min=0, - # max=10, - # description="description", - # url="url", - # display_name="display_name", - # callback=self.mock_callback, - # enabled="enabled", - # parent=None, - @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), @@ -674,4 +662,134 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, assert result_reverse.variance == expected_reverse.variance if isinstance(result_reverse, Parameter): assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max \ No newline at end of file + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), + (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), + (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0)), + (Parameter("test", 0, "s", 0.01, -10, 20), Parameter("name / test", 1e9, "m/s", 1e34, -np.Inf, np.Inf), Parameter("test / name", 0, "s/m", 0.01, -np.Inf, np.Inf))], + ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative", "zero_value"]) + def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == Parameter + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + assert result.min == expected.min + assert result.max == expected.max + + assert type(result) == Parameter + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("first, second, expected", [ + (Parameter("name", 1, "m", 0.01, -10, 20), Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, np.Inf)), + (Parameter("name", -10, "m", 0.01, -20, -10), Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", 5.0, "m/s", 0.065, 1, np.Inf)), + (Parameter("name", 10, "m", 0.01, 10, 20), Parameter("test", -20, "s", 0.01, -20, -10), Parameter("name / test", -0.5, "m/s", 3.125e-5, -2, -0.5))], + ids=["first_crossing_zero_second_negative_0", "both_negative_second_negative_0", "finite_limits"]) + def test_division_with_parameter_remaining_cases(self, first, second, expected): + # When Then + result = first / second + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (DescriptorNumber(name="test", value=2, variance=0.1, unit="dm^2"), Parameter("name / test", 0.5, "cm**-1", 0.00875, 0, 5), Parameter("test / name", 2.0, "cm", 0.14, 0.2, np.Inf)), + (DescriptorNumber(name="test", value=0, variance=0.1, unit="dm^2"), Parameter("name / test", 1e9, "cm**-1,", 1e35, 0, 1e10), DescriptorNumber("test / name", 0.0, "cm", 0.1))], + ids=["regular", "zero_value"]) + def test_division_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("first, second, expected", [ + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, -10, 10), Parameter("name / test", 0.5, "m/s", 0.00875, -np.Inf, np.Inf)), + (DescriptorNumber("name", -1, "m", 0.01), Parameter("test", 2, "s", 0.1, 0, 10), Parameter("name / test", -0.5, "m/s", 0.00875, -np.Inf, -0.1)), + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", -2, "s", 0.1, -10, 0), Parameter("name / test", -0.5, "m/s", 0.00875, -np.Inf, -0.1)), + (DescriptorNumber("name", -1, "m", 0.01), Parameter("test", -2, "s", 0.1, -10, 0), Parameter("name / test", 0.5, "m/s", 0.00875, 0.1, np.Inf)), + (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, 1, 10), Parameter("name / test", 0.5, "m/s", 0.00875, 0.1, 1))], + ids=["crossing_zero", "positive_0_with_negative", "negative_0_with_positive", "negative_0_with_negative", "finite_limits"]) + def test_division_with_descriptor_number_missing_cases(self, first, second, expected): + # When Then + result = first / second + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, expected, expected_reverse", [ + (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf)), + (0, Parameter("name / 0", 1/INFINITESIMAL, "m", (1/(INFINITESIMAL*10))**2, 0, 1/INFINITESIMAL*10), DescriptorNumber("0 / name", 0.0, "m**-1", 0.0))], + ids=["regular", "zero_value"]) + def test_division_with_numbers(self, parameter : Parameter, test, expected, expected_reverse): + # When + parameter._callback = property() + + # Then + result = parameter / test + result_reverse = test / parameter + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == pytest.approx(expected.value) + assert result.unit == expected.unit + assert result.variance == pytest.approx(expected.variance) + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == pytest.approx(expected.max) + + assert type(result_reverse) == type(expected_reverse) + assert result_reverse.name == expected_reverse.name + assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.unit == expected_reverse.unit + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == pytest.approx(expected_reverse.max) \ No newline at end of file From 0b73c24b2b0bcd45641ffbc148b87e939f195300 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 30 Jul 2024 15:30:28 +0200 Subject: [PATCH 49/68] raise ZeroDivisionError --- .../Objects/new_variable/descriptor_number.py | 7 +- .../Objects/new_variable/parameter.py | 7 +- .../new_variable/test_descriptor_number.py | 43 +++++---- .../Objects/new_variable/test_parameter.py | 91 ++++++++++--------- 4 files changed, 73 insertions(+), 75 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 4567b153..316edace 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -15,7 +15,6 @@ from .descriptor_base import DescriptorBase -INFINITESIMAL = 1e-9 class DescriptorNumber(DescriptorBase): """ @@ -311,13 +310,13 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip if isinstance(other, numbers.Number): original_other = other if other == 0: - other = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other name = self.name + ' / ' + str(original_other) elif type(other) == DescriptorNumber: original_other = other.value if original_other == 0: - other.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value other.value = original_other name = self._name + ' / ' + other._name @@ -330,7 +329,7 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): if self.value == 0: - self.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = other / self.full_value name = str(other) + ' / ' + self.name else: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index ee7b55f6..63c93a16 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -26,7 +26,6 @@ from easyscience.global_object.undo_redo import property_stack_deco from easyscience.Utils.Exceptions import CoreSetException -from .descriptor_number import INFINITESIMAL from .descriptor_number import DescriptorNumber Constraints = namedtuple('Constraints', ['user', 'builtin', 'virtual']) @@ -582,14 +581,14 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) if isinstance(other, numbers.Number): original_other = other if other == 0: - other = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" elif isinstance(other, DescriptorNumber): original_value = other.value if original_value == 0: - other.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): @@ -625,7 +624,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: original_self = self.value if original_self == 0: - self.value = INFINITESIMAL + raise ZeroDivisionError("Cannot divide by zero") if isinstance(other, numbers.Number): new_value = other / self.full_value other_value = other diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index d58ca799..930b4132 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -329,26 +329,11 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): assert result_reverse.unit == "m" assert result_reverse.variance == 0.4 - @pytest.mark.parametrize("test, expected", [ - (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625)), - (DescriptorNumber("test", 0, "m^2", 0.01), DescriptorNumber("name / test", 1e9, "1/m", 1e34))], - ids=["regular", "zero_value"]) - def test_division(self, descriptor: DescriptorNumber, test, expected): - # When Then - result = descriptor / test - - # Expect - assert type(result) == DescriptorNumber - assert result.name == expected.name - assert result.value == pytest.approx(expected.value) - assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - @pytest.mark.parametrize("test, expected, expected_reverse", [ - (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4)), - (0, DescriptorNumber("name / 0", 1e9, "m", 1e17), DescriptorNumber("0 / name", 0, "1/m", 0))], - ids=["regular", "zero_value"]) - def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected, expected_reverse): + (DescriptorNumber("test", 2, "m^2", 0.01,), DescriptorNumber("name / test", 0.5, "1/m", 0.025625), DescriptorNumber("test / name", 2, "m", 0.41)), + (2, DescriptorNumber("name / 2", 0.5, "m", 0.025), DescriptorNumber("2 / name", 2, "1/m", 0.4))], + ids=["descriptorNumber", "scalar"]) + def test_division(self, descriptor: DescriptorNumber, test, expected, expected_reverse): # When Then result = descriptor / test result_reverse = test / descriptor @@ -356,12 +341,26 @@ def test_division_with_scalar(self, descriptor: DescriptorNumber, test, expected # Expect assert type(result) == DescriptorNumber assert result.name == expected.name - assert result.value == pytest.approx(expected.value) + assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert type(result_reverse) == DescriptorNumber assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) \ No newline at end of file + assert result_reverse.variance == pytest.approx(expected_reverse.variance) + + @pytest.mark.parametrize("test", [0, DescriptorNumber("test", 0, "m", 0.01)], ids=["zero", "zero_descriptor"]) + def test_division_exception(self, descriptor: DescriptorNumber, test): + # When Then Expect + with pytest.raises(ZeroDivisionError): + result = descriptor / test + + def test_division_exception_reverse(self): + # When + descriptor = DescriptorNumber(name="name", value=0, variance=0.1) + + # Then Expect + with pytest.raises(ZeroDivisionError): + result = 2 / descriptor \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 63322fd6..7adfcaf3 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,7 +7,6 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber -from easyscience.Objects.new_variable.descriptor_number import INFINITESIMAL from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object @@ -668,9 +667,8 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), - (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0)), - (Parameter("test", 0, "s", 0.01, -10, 20), Parameter("name / test", 1e9, "m/s", 1e34, -np.Inf, np.Inf), Parameter("test / name", 0, "s/m", 0.01, -np.Inf, np.Inf))], - ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative", "zero_value"]) + (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0))], + ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative"]) def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() @@ -714,10 +712,10 @@ def test_division_with_parameter_remaining_cases(self, first, second, expected): assert result.max == expected.max @pytest.mark.parametrize("test, expected, expected_reverse", [ - (DescriptorNumber(name="test", value=2, variance=0.1, unit="dm^2"), Parameter("name / test", 0.5, "cm**-1", 0.00875, 0, 5), Parameter("test / name", 2.0, "cm", 0.14, 0.2, np.Inf)), - (DescriptorNumber(name="test", value=0, variance=0.1, unit="dm^2"), Parameter("name / test", 1e9, "cm**-1,", 1e35, 0, 1e10), DescriptorNumber("test / name", 0.0, "cm", 0.1))], - ids=["regular", "zero_value"]) - def test_division_with_descriptor_number(self, parameter : Parameter, test, expected, expected_reverse): + (DescriptorNumber(name="test", value=2, variance=0.1, unit="s"), Parameter("name / test", 0.5, "m/s", 0.00875, 0, 5), Parameter("test / name", 2, "s/m", 0.14, 0.2, np.Inf)), + (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf))], + ids=["descriptor_number", "number"]) + def test_division_with_descriptor_number_and_number(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() @@ -726,23 +724,39 @@ def test_division_with_descriptor_number(self, parameter : Parameter, test, expe result_reverse = test / parameter # Expect - assert type(result) == type(expected) + assert type(result) == Parameter assert result.name == expected.name - assert result.value == pytest.approx(expected.value) + assert result.value == expected.value assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == expected.max + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max - assert type(result_reverse) == type(expected_reverse) + assert type(result_reverse) == Parameter assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + assert result_reverse.variance == expected_reverse.variance + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber(name="test", value=0, variance=0.1, unit="s"), DescriptorNumber("test / name", 0.0, "s/m", 0.1)), + (0, DescriptorNumber("0 / name", 0.0, "1/m", 0.0))], + ids=["descriptor_number", "number"]) + def test_zero_value_dividided_by_parameter(self, parameter : Parameter, test, expected): + # When + parameter._callback = property() + + # Then + result = test / parameter + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance @pytest.mark.parametrize("first, second, expected", [ (DescriptorNumber("name", 1, "m", 0.01), Parameter("test", 2, "s", 0.1, -10, 10), Parameter("name / test", 0.5, "m/s", 0.00875, -np.Inf, np.Inf)), @@ -763,33 +777,20 @@ def test_division_with_descriptor_number_missing_cases(self, first, second, expe assert result.min == expected.min assert result.max == expected.max - @pytest.mark.parametrize("test, expected, expected_reverse", [ - (2, Parameter("name / 2", 0.5, "m", 0.0025, 0, 5), Parameter("2 / name", 2, "m**-1", 0.04, 0.2, np.Inf)), - (0, Parameter("name / 0", 1/INFINITESIMAL, "m", (1/(INFINITESIMAL*10))**2, 0, 1/INFINITESIMAL*10), DescriptorNumber("0 / name", 0.0, "m**-1", 0.0))], - ids=["regular", "zero_value"]) - def test_division_with_numbers(self, parameter : Parameter, test, expected, expected_reverse): + @pytest.mark.parametrize("test", [0, DescriptorNumber("test", 0, "s", 0.1)], ids=["number", "descriptor_number"]) + def test_divide_parameter_by_zero(self, parameter : Parameter, test): # When parameter._callback = property() - # Then - result = parameter / test - result_reverse = test / parameter + # Then Expect + with pytest.raises(ZeroDivisionError): + result = parameter / test - # Expect - assert type(result) == type(expected) - assert result.name == expected.name - assert result.value == pytest.approx(expected.value) - assert result.unit == expected.unit - assert result.variance == pytest.approx(expected.variance) - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == pytest.approx(expected.max) + def test_divide_by_zero_value_parameter(self): + # When + descriptor = DescriptorNumber("test", 1, "s", 0.1) + parameter = Parameter("name", 0, "m", 0.01) - assert type(result_reverse) == type(expected_reverse) - assert result_reverse.name == expected_reverse.name - assert result_reverse.value == pytest.approx(expected_reverse.value) - assert result_reverse.unit == expected_reverse.unit - assert result_reverse.variance == pytest.approx(expected_reverse.variance) - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == pytest.approx(expected_reverse.max) \ No newline at end of file + # Then Expect + with pytest.raises(ZeroDivisionError): + result = descriptor / parameter \ No newline at end of file From e28732b8f5f21ee3bedf45c79a3b9fdd495fb9f6 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 31 Jul 2024 09:46:08 +0200 Subject: [PATCH 50/68] add __pow__ methods --- .../Objects/new_variable/descriptor_number.py | 33 +++++++++++++++++++ .../Objects/new_variable/parameter.py | 27 ++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 316edace..3fc0b2b6 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -336,6 +336,39 @@ def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: return NotImplemented return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: + if isinstance(other, numbers.Number): + exponent = other + name = f"{self.name} ** {other}" + elif type(other) == DescriptorNumber: + if other.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if other.variance is not None: + raise ValueError("Exponents must not have variance") + exponent = other.value + name = self.name+" ** "+other.name + else: + return NotImplemented + try: + new_value = self.full_value ** exponent + except Exception as message: + raise message from None + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __rpow__(self, other: numbers.Number) -> numbers.Number: + if isinstance(other, numbers.Number): + if self.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if self.variance is not None: + raise ValueError("Exponents must not have variance") + try: + new_value = other ** self.full_value + except Exception as message: + raise message from None + else: + return NotImplemented + return new_value + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 63c93a16..e24cdd87 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -658,4 +658,29 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self - return parameter \ No newline at end of file + return parameter + + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: + if isinstance(other, numbers.Number): + exponent = other + name = f"{self.name} ** {other}" + elif type(other) == DescriptorNumber: + if other.unit != 'dimensionless': + raise UnitError("Exponents must be dimensionless") + if other.variance is not None: + raise ValueError("Exponents must not have variance") + exponent = other.value + name = self.name+" ** "+other.name + else: + return NotImplemented + try: + new_value = self.full_value ** exponent + except Exception as message: + raise message from None + if exponent % 2 == 0 and self.min <= 0 and self.max >= 0: + combinations = [0, self.max ** exponent] + else: + combinations = [self.min ** exponent, self.max ** exponent] + min_value = min(combinations) + max_value = max(combinations) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From 128e6c04989942297dafdc1cf7654adb6cb65194 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:04:11 +0200 Subject: [PATCH 51/68] Test __pow__ methods --- .../Objects/new_variable/descriptor_number.py | 7 +- .../Objects/new_variable/parameter.py | 23 ++++++- .../new_variable/test_descriptor_number.py | 66 ++++++++++++++++++- .../Objects/new_variable/test_parameter.py | 59 ++++++++++++++++- 4 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 3fc0b2b6..b8b9f2ae 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -353,6 +353,8 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN 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") return DescriptorNumber.from_scipp(name=name, full_value=new_value) def __rpow__(self, other: numbers.Number) -> numbers.Number: @@ -361,10 +363,7 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: raise UnitError("Exponents must be dimensionless") if self.variance is not None: raise ValueError("Exponents must not have variance") - try: - new_value = other ** self.full_value - except Exception as message: - raise message from None + new_value = other ** self.value else: return NotImplemented return new_value diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index e24cdd87..5545dfd8 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -677,10 +677,29 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = self.full_value ** exponent except Exception as message: raise message from None - if exponent % 2 == 0 and self.min <= 0 and self.max >= 0: - combinations = [0, self.max ** exponent] + if np.isnan(new_value.value): + raise ValueError("The result of the exponentiation is not a number") + if exponent == 0: + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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] + elif self.max == 0: + combinations = [-np.Inf, self.min ** exponent] + else: + combinations = [self.min ** exponent, self.max ** exponent] else: combinations = [self.min ** exponent, self.max ** exponent] + if exponent % 2 == 0: + if self.min < 0 and self.max > 0: + combinations.append(0) + combinations = [abs(combination) for combination in combinations] + elif exponent % 1 != 0: + if self.min < 0: + combinations.append(0) + combinations = [combination for combination in combinations if combination >= 0] min_value = min(combinations) max_value = max(combinations) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 930b4132..0b6f17a8 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -363,4 +363,68 @@ def test_division_exception_reverse(self): # Then Expect with pytest.raises(ZeroDivisionError): - result = 2 / descriptor \ No newline at end of file + result = 2 / descriptor + + @pytest.mark.parametrize("test, expected", [ + (DescriptorNumber("test", 2), DescriptorNumber("name ** test", 4, unit="m^2", variance=1.6)), + (2, DescriptorNumber("name ** 2", 4, unit="m^2", variance=1.6)), + (-2, DescriptorNumber("name ** -2", 0.25, unit="1/m^2", variance=0.00625))], + ids=["descriptorNumber", "scalar", "negative_scalar"]) + def test_power_of_descriptor(self, test, expected): + # When + descriptor = DescriptorNumber(name="name", value=2, unit="m", variance=0.1) + + # Then + result = descriptor ** test + + # Expect + assert type(result) == DescriptorNumber + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + + def test_power_of_dimensionless_descriptor(self): + # When + descriptor = DescriptorNumber(name="name", value=2, unit="dimensionless", variance=0.1) + + # Then + result = descriptor ** 0.5 + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "name ** 0.5" + assert result.value == 1.4142135623730951 + assert result.unit == "dimensionless" + assert result.variance == pytest.approx(0.0125) + + @pytest.mark.parametrize("descriptor, exponent, exception", [ + (DescriptorNumber("name", 2), DescriptorNumber("test", 2, unit="m"), UnitError), + (DescriptorNumber("name", 2), DescriptorNumber("test", 2, variance=0.1), ValueError), + (DescriptorNumber("name", 2, unit="m"), 0.5, UnitError), + (DescriptorNumber("name", -2), 0.5, ValueError)], + ids=["descriptor_unit", "descriptor_variance", "fractional_of_unit", "fractonal_of_negative"]) + def test_power_of_descriptor_exceptions(self, descriptor, exponent, exception): + # When Then Expect + with pytest.raises(exception): + result = descriptor ** exponent + + + def test_descriptor_as_exponentiation(self): + # When + descriptor = DescriptorNumber(name="name", value=2) + + # Then + result = 2 ** descriptor + + # Expect + assert result == 4 + + @pytest.mark.parametrize("exponent, exception", [ + (DescriptorNumber("test", 2, unit="m"), UnitError), + (DescriptorNumber("test", 2, variance=0.1), ValueError)], + ids=["descriptor_unit", "descriptor_variance"]) + def test_descriptor_as_exponentiation_exception(self, exponent, exception): + # When Then Expect + with pytest.raises(exception): + result = 2 ** exponent \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 7adfcaf3..fa54ba3d 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -793,4 +793,61 @@ def test_divide_by_zero_value_parameter(self): # Then Expect with pytest.raises(ZeroDivisionError): - result = descriptor / parameter \ No newline at end of file + result = descriptor / parameter + + @pytest.mark.parametrize("test, expected", [ + (3, Parameter("name ** 3", 125, "m^3", 281.25, -125, 1000)), + (2, Parameter("name ** 2", 25, "m^2", 5.0, 0, 100)), + (-1, Parameter("name ** -1", 0.2, "1/m", 8e-5, -np.Inf, np.Inf)), + (-2, Parameter("name ** -2", 0.04, "1/m^2", 1.28e-5, 0, np.Inf)), + (0, DescriptorNumber("name ** 0", 1, "dimensionless", 0)), + (DescriptorNumber("test", 2), Parameter("name ** test", 25, "m^2", 5.0, 0, 100))], + ids=["power_3", "power_2", "power_-1", "power_-2", "power_0", "power_descriptor_number"]) + def test_power_of_parameter(self, test, expected): + # When + parameter = Parameter("name", 5, "m", 0.05, -5, 10) + + # Then + result = parameter ** test + + # Expect + assert type(result) == type(expected) + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("test, exponent, expected", [ + (Parameter("name", 5, "m", 0.05, 0, 10), -1, Parameter("name ** -1", 0.2, "1/m", 8e-5, 0.1, np.Inf)), + (Parameter("name", -5, "m", 0.05, -5, 0), -1, Parameter("name ** -1", -0.2, "1/m", 8e-5, -np.Inf, -0.2)), + (Parameter("name", 5, "m", 0.05, 5, 10), -1, Parameter("name ** -1", 0.2, "1/m", 8e-5, 0.1, 0.2)), + (Parameter("name", -5, "m", 0.05, -10, -5), -1, Parameter("name ** -1", -0.2, "1/m", 8e-5, -0.2, -0.1)), + (Parameter("name", -5, "m", 0.05, -10, -5), -2, Parameter("name ** -2", 0.04, "1/m^2", 1.28e-5, 0.01, 0.04)), + (Parameter("name", 5, "", 0.1, 1, 10), 0.3, Parameter("name ** 0.3", 1.6206565966927624, "", 0.0009455500095853564, 1, 1.9952623149688795)), + (Parameter("name", 5, "", 0.1), 0.5, Parameter("name ** 0.5", 2.23606797749979, "", 0.005, 0, np.Inf))], + ids=["0_positive", "negative_0", "both_positive", "both_negative_invert", "both_negative_invert_square", "fractional", "fractional_negative_limit"]) + def test_power_of_diffent_parameters(self, test, exponent, expected): + # When Then + result = test ** exponent + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max + + @pytest.mark.parametrize("parameter, exponent, expected", [ + (Parameter("name", 5, "m"), DescriptorNumber("test", 2, unit="s"), UnitError), + (Parameter("name", 5, "m"), DescriptorNumber("test", 2, variance=0.01), ValueError), + (Parameter("name", 5, "m"), 0.5, UnitError), + (Parameter("name", -5, ""), 0.5, ValueError),], + ids=["exponent_unit", "exponent_variance", "exponent_fractional", "negative_base_fractional"]) + def test_power_exceptions(self, parameter, exponent, expected): + # When Then Expect + with pytest.raises(expected): + result = parameter ** exponent \ No newline at end of file From 871d1532042a625ae43a516957ad70a8283778d6 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:08:21 +0200 Subject: [PATCH 52/68] add __neg__ and __abs__ methods --- .../Objects/new_variable/descriptor_number.py | 10 ++++++++++ src/easyscience/Objects/new_variable/parameter.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index b8b9f2ae..ded91168 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -368,6 +368,16 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: return NotImplemented return new_value + def __neg__(self) -> DescriptorNumber: + new_value = -self.full_value + name = '-'+self.name + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + + def __abs__(self) -> DescriptorNumber: + new_value = abs(self.full_value) + name = 'abs('+self.name+')' + return DescriptorNumber.from_scipp(name=name, full_value=new_value) + def _base_unit(self) -> str: string = str(self._scalar.unit) for i in range(len(string)): diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 5545dfd8..461c7dd6 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -702,4 +702,19 @@ 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) + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __neg__(self) -> Parameter: + new_value = -self.full_value + name = f"-{self.name}" + min_value = -self.max + max_value = -self.min + return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + + def __abs__(self) -> Parameter: + new_value = abs(self.full_value) + name = f"abs({self.name})" + if self.min < 0 and self.max > 0: + min_value = 0 + max_value = max(abs(self.min), abs(self.max)) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file From 4b5d6d58f17f939089d232d6070a81958b8c6e6a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Thu, 1 Aug 2024 16:16:49 +0200 Subject: [PATCH 53/68] Add tests for __neg__ and __abs__ --- .../Objects/new_variable/parameter.py | 6 ++-- .../new_variable/test_descriptor_number.py | 30 ++++++++++++++++- .../Objects/new_variable/test_parameter.py | 33 ++++++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 461c7dd6..78ca0587 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -714,7 +714,9 @@ def __neg__(self) -> Parameter: def __abs__(self) -> Parameter: new_value = abs(self.full_value) name = f"abs({self.name})" + combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: - min_value = 0 - max_value = max(abs(self.min), abs(self.max)) + combinations.append(0) + min_value = min(combinations) + max_value = max(combinations) return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 0b6f17a8..678198da 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -427,4 +427,32 @@ def test_descriptor_as_exponentiation(self): def test_descriptor_as_exponentiation_exception(self, exponent, exception): # When Then Expect with pytest.raises(exception): - result = 2 ** exponent \ No newline at end of file + result = 2 ** exponent + + def test_negation(self): + # When + descriptor = DescriptorNumber(name="name", unit="m", value=2, variance=0.1) + + # Then + result = -descriptor + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "-name" + assert result.value == -2 + assert result.unit == "m" + assert result.variance == 0.1 + + def test_abs(self): + # When + descriptor = DescriptorNumber(name="name", unit="m", value=-2, variance=0.1) + + # Then + result = abs(descriptor) + + # Expect + assert type(result) == DescriptorNumber + assert result.name == "abs(name)" + assert result.value == 2 + assert result.unit == "m" + assert result.variance == 0.1 \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index fa54ba3d..22008408 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -850,4 +850,35 @@ def test_power_of_diffent_parameters(self, test, exponent, expected): def test_power_exceptions(self, parameter, exponent, expected): # When Then Expect with pytest.raises(expected): - result = parameter ** exponent \ No newline at end of file + result = parameter ** exponent + + def test_negation(self): + # When + parameter = Parameter("name", 5, "m", 0.05, -5, 10) + + # Then + result = -parameter + + # Expect + assert result.name == "-name" + assert result.value == -5 + assert result.unit == "m" + assert result.variance == 0.05 + assert result.min == -10 + assert result.max == 5 + + @pytest.mark.parametrize("test, expected", [ + (Parameter("name", -5, "m", 0.05, -10, -5), Parameter("abs(name)", 5, "m", 0.05, 5, 10)), + (Parameter("name", 5, "m", 0.05, -10, 10), Parameter("abs(name)", 5, "m", 0.05, 0, 10))], + ids=["pure_negative", "crossing_zero"]) + def test_abs(self, test, expected): + # When Then + result = abs(test) + + # Expect + assert result.name == expected.name + assert result.value == expected.value + assert result.unit == expected.unit + assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max \ No newline at end of file From 1fe562576959f553619883d01f62622d38d2535a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 2 Aug 2024 09:58:11 +0200 Subject: [PATCH 54/68] Updated ruff fix --- .../Objects/new_variable/descriptor_number.py | 10 +++++----- src/easyscience/Objects/new_variable/parameter.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index ded91168..fe34fcd3 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -233,7 +233,7 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise UnitError("Numbers can only be added to dimensionless values") new_value = self.full_value + other name = self.name + ' + ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) @@ -262,7 +262,7 @@ def __sub__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise UnitError("Numbers can only be subtracted from dimensionless values") new_value = self.full_value - other name = self.name + ' - ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_unit = other.unit try: other.convert_unit(self.unit) @@ -289,7 +289,7 @@ def __mul__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if isinstance(other, numbers.Number): new_value = self.full_value * other name = self.name + ' * ' + str(other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: new_value = self.full_value * other.full_value name = self._name + ' * ' + other._name else: @@ -313,7 +313,7 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other name = self.name + ' / ' + str(original_other) - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: original_other = other.value if original_other == 0: raise ZeroDivisionError("Cannot divide by zero") @@ -340,7 +340,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 78ca0587..3144f4b4 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -535,7 +535,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value name = self.name+" * "+other.name - if other.value == 0 and type(other) == DescriptorNumber: + if other.value == 0 and type(other) is DescriptorNumber: return DescriptorNumber.from_scipp(name=name, full_value=new_value) if isinstance(other, Parameter): combinations = [] @@ -664,7 +664,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) == DescriptorNumber: + elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: From d5aab7c737a20538711fdce2a4034401a3396fd7 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 2 Aug 2024 12:07:13 +0200 Subject: [PATCH 55/68] Fix unit prefixes with "e" numbers --- src/easyscience/Objects/new_variable/descriptor_number.py | 7 +++++-- tests/unit_tests/Objects/new_variable/test_parameter.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index fe34fcd3..f2c91b00 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -380,7 +380,10 @@ def __abs__(self) -> DescriptorNumber: def _base_unit(self) -> str: string = str(self._scalar.unit) - for i in range(len(string)): - if string[i] not in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]: + for i, letter in enumerate(string): + 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", ".", "+", "-"]: return string[i:] return "" \ No newline at end of file diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 22008408..d0c19da6 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -7,7 +7,6 @@ from easyscience.Objects.new_variable.parameter import Parameter from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber -from easyscience.Objects.new_variable.descriptor_str import DescriptorStr from easyscience import global_object class TestParameter: @@ -744,7 +743,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, (DescriptorNumber(name="test", value=0, variance=0.1, unit="s"), DescriptorNumber("test / name", 0.0, "s/m", 0.1)), (0, DescriptorNumber("0 / name", 0.0, "1/m", 0.0))], ids=["descriptor_number", "number"]) - def test_zero_value_dividided_by_parameter(self, parameter : Parameter, test, expected): + def test_zero_value_divided_by_parameter(self, parameter : Parameter, test, expected): # When parameter._callback = property() From 8f88319995c97801d14ef37fb6f66bb67af93f55 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 11:49:15 +0200 Subject: [PATCH 56/68] minor code consistency, updated test --- .../Objects/new_variable/parameter.py | 58 ++++++++++--------- .../Objects/new_variable/test_parameter.py | 12 ++-- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 3144f4b4..c0b34b4a 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -46,8 +46,8 @@ def __init__( value: numbers.Number, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = 0.0, - min: Optional[numbers.Number] = -np.Inf, - max: Optional[numbers.Number] = np.Inf, + min: Optional[numbers.Number] = -np.inf, + max: Optional[numbers.Number] = np.inf, fixed: Optional[bool] = False, unique_name: Optional[str] = None, description: Optional[str] = None, @@ -446,7 +446,7 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_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 - name = self.name+" + "+other.name + name = f"{self.name} + {other.name}" other.convert_unit(original_unit) else: return NotImplemented @@ -469,7 +469,7 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value - name = other.name+" + "+self.name + name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented @@ -491,8 +491,8 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_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 - max_value = self.max - other.min if other.min != -np.Inf else np.Inf + min_value = self.min - other.max if other.max != np.inf else -np.inf + max_value = self.max - other.min if other.min != -np.inf else np.inf else: min_value = self.min - other.value max_value = self.max - other.value @@ -519,7 +519,7 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min - name = other.name+" - "+self.name + name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented @@ -534,8 +534,8 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): new_value = self.full_value * other.full_value - name = self.name+" * "+other.name - if other.value == 0 and type(other) is DescriptorNumber: + name = f"{self.name} * {other.name}" + if other.value == 0 and isinstance(other, DescriptorNumber): return DescriptorNumber.from_scipp(name=name, full_value=new_value) if isinstance(other, Parameter): combinations = [] @@ -565,7 +565,7 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): new_value = other.full_value * self.full_value - name = other.name+" * "+self.name + name = f"{other.name} * {self.name}" if other.value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_value) combinations = [self.min * other.value, self.max * other.value] @@ -592,26 +592,26 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) new_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): - combinations = [-np.Inf, np.Inf] + combinations = [-np.inf, np.inf] elif other.min == 0: if (self.min < 0 and self.max > 0): - combinations = [-np.Inf, np.Inf] + 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): - combinations = [-np.Inf, np.Inf] + 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] else: combinations = [self.min / other.value, self.max / other.value] - name = self.name+" / "+other.name + name = f"{self.name} / {other.name}" other.value = original_value else: return NotImplemented @@ -640,17 +640,17 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame else: return NotImplemented if (self.min < 0 and self.max > 0): - combinations = [-np.Inf, np.Inf] + 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) @@ -664,30 +664,32 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif type(other) is DescriptorNumber: + elif isinstance(other, DescriptorNumber): if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = self.name+" ** "+other.name + name = f"{self.name} ** {other.name}" else: return NotImplemented + try: 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") if exponent == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_value) elif exponent < 0: if self.min < 0 and self.max > 0: - combinations = [-np.Inf, np.Inf] + 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] else: diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index d0c19da6..76783544 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -591,15 +591,19 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance - assert result.min == expected.min - assert result.max == expected.max assert result_reverse.name == expected_reverse.name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + + if isinstance(result, Parameter): + assert result.min == expected.min + assert result.max == expected.max + + if isinstance(result_reverse, Parameter): + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max @pytest.mark.parametrize("test, expected, expected_reverse", [ (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), From 8eebe7486bc587191ad3160f54b76d478a1cd1bb Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:45:55 +0200 Subject: [PATCH 57/68] Change name of new_value --- .../Objects/new_variable/parameter.py | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index c0b34b4a..c8a571f7 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -433,30 +433,30 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> 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 + new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" elif isinstance(other, DescriptorNumber): - original_unit = other.unit + 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 - new_value = self.full_value + other.full_value + 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 name = f"{self.name} + {other.name}" - other.convert_unit(original_unit) + other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = self.full_value + other + new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" @@ -466,30 +466,30 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(other.unit) except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be added") from None - new_value = self.full_value + other.full_value + new_full_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = self.full_value - other + new_full_value = self.full_value - other min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" elif isinstance(other, DescriptorNumber): - original_unit = other.unit + 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 - new_value = self.full_value - other.full_value + 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 max_value = self.max - other.min if other.min != -np.inf else np.inf @@ -497,16 +497,16 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min - other.value max_value = self.max - other.value name = self.name+" - "+other.name - other.convert_unit(original_unit) + other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) 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") - new_value = other - self.full_value + new_full_value = other - self.full_value min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" @@ -516,27 +516,27 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(other.unit) except UnitError: raise UnitError(f"Values with units {other.unit} and {self.unit} cannot be subtracted") from None - new_value = other.full_value - self.full_value + new_full_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - new_value = self.full_value * other + new_full_value = self.full_value * other name = f"{self.name} * {other}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): - new_value = self.full_value * other.full_value + new_full_value = self.full_value * other.full_value name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) 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 @@ -552,28 +552,28 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - new_value = other * self.full_value + new_full_value = other * self.full_value name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): - new_value = other.full_value * self.full_value + new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -582,14 +582,14 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) original_other = other if other == 0: raise ZeroDivisionError("Cannot divide by zero") - new_value = self.full_value / other + new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" elif isinstance(other, DescriptorNumber): original_value = other.value if original_value == 0: raise ZeroDivisionError("Cannot divide by zero") - new_value = self.full_value / other.full_value + new_full_value = self.full_value / other.full_value if isinstance(other, Parameter): if (other.min < 0 and other.max > 0): combinations = [-np.inf, np.inf] @@ -617,7 +617,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -626,17 +626,17 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame if original_self == 0: raise ZeroDivisionError("Cannot divide by zero") if isinstance(other, numbers.Number): - new_value = other / self.full_value + new_full_value = other / self.full_value other_value = other name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) elif isinstance(other, DescriptorNumber): - new_value = other.full_value / self.full_value + new_full_value = other.full_value / self.full_value other_value = other.value name = other.name+" / "+self.name if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) else: return NotImplemented if (self.min < 0 and self.max > 0): @@ -655,7 +655,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self return parameter @@ -675,14 +675,14 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: return NotImplemented try: - new_value = self.full_value ** exponent + new_full_value = self.full_value ** exponent except Exception as message: raise message from None - if np.isnan(new_value.value): + if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) elif exponent < 0: if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] @@ -704,21 +704,21 @@ 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) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __neg__(self) -> Parameter: - new_value = -self.full_value + new_full_value = -self.full_value name = f"-{self.name}" min_value = -self.max max_value = -self.min - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) def __abs__(self) -> Parameter: - new_value = abs(self.full_value) + new_full_value = abs(self.full_value) name = f"abs({self.name})" combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=name, full_value=new_value, min=min_value, max=max_value) \ No newline at end of file + return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file From 02b0b3f21ce8793436684e6914f6c056bcf5beeb Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:48:55 +0200 Subject: [PATCH 58/68] Add comments about DescriptorNumber --- .../Objects/new_variable/parameter.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index c8a571f7..b98bdd30 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -437,7 +437,7 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min + other max_value = self.max + other name = f"{self.name} + {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) @@ -460,7 +460,7 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: min_value = self.min + other max_value = self.max + other name = f"{other} + {self.name}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) @@ -483,7 +483,7 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> min_value = self.min - other max_value = self.max - other name = f"{self.name} - {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: other.convert_unit(self.unit) @@ -510,7 +510,7 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: min_value = other - self.max max_value = other - self.min name = f"{other} - {self.name}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: self.convert_unit(other.unit) @@ -532,7 +532,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> if other == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [self.min * other, self.max * other] - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = self.full_value * other.full_value name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): @@ -563,7 +563,7 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if other == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) combinations = [other * self.min, other * self.max] - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: @@ -585,7 +585,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] name = f"{self.name} / {original_other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_value = other.value if original_value == 0: raise ZeroDivisionError("Cannot divide by zero") @@ -631,7 +631,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame name = f"{other} / {self.name}" if other_value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) - elif isinstance(other, DescriptorNumber): + 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 name = other.name+" / "+self.name @@ -664,7 +664,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other name = f"{self.name} ** {other}" - elif isinstance(other, DescriptorNumber): + elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: From 9c88ed7dc17015e0afb68d772aea4908374e953e Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 12:57:35 +0200 Subject: [PATCH 59/68] Changed == to np.isclose --- src/easyscience/Objects/new_variable/parameter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index b98bdd30..273288df 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -86,7 +86,8 @@ def __init__( raise ValueError(f'{value=} can not be less than {min=}') if value > max: raise ValueError(f'{value=} can not be greater than {max=}') - if min == max: + + if np.isclose(min, max, rtol=1e-9, atol=0.0): 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') @@ -249,7 +250,8 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') - if min_value == self._max.value: + # if min_value == self._max.value: + if np.isclose(min_value, self._max.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: self._min.value = min_value @@ -277,7 +279,8 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') - if max_value == self._min.value: + # if max_value == self._min.value: + if np.isclose(max_value, self._min.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: self._max.value = max_value From 0e3dba35c48172f675f58e0d828ff0904ce29f0a Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 6 Aug 2024 14:08:05 +0200 Subject: [PATCH 60/68] Added calls to convert_unit in initialize to ensure unit consistency --- .../Objects/new_variable/descriptor_number.py | 5 +++++ src/easyscience/Objects/new_variable/parameter.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index f2c91b00..4605644f 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -69,6 +69,11 @@ def __init__( parent=parent, ) + # Call convert_unit during initialization to ensure that the unit has no numbers in it, and to ensure unit consistency. + # For some reason, converting to the same unit often changes the representation. This breaks almost every test. + if self.unit is not None: + self.convert_unit(self._base_unit()) + @classmethod def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorNumber: """ diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 273288df..93289178 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -91,6 +91,9 @@ 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) super().__init__( name=name, @@ -109,8 +112,6 @@ def __init__( weakref.finalize(self, self._callback.fdel) # Create additional fitting elements - self._min = sc.scalar(float(min), unit=unit) - self._max = sc.scalar(float(max), unit=unit) self._fixed = fixed self._enabled = enabled self._initial_scalar = copy.deepcopy(self._scalar) @@ -121,6 +122,10 @@ def __init__( } self._constraints = Constraints(builtin=builtin_constraint, user={}, virtual={}) + if self.unit is not None: + self.convert_unit(self._base_unit()) + + @property def value_no_call_back(self) -> numbers.Number: """ @@ -181,7 +186,7 @@ def value(self) -> numbers.Number: @property_stack_deco def value(self, value: numbers.Number) -> None: """ - Set the value of self. This only update the value of the scipp scalar. + Set the value of self. This only updates the value of the scipp scalar. :param value: New value of self """ @@ -582,12 +587,11 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): - original_other = other if other == 0: raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] - name = f"{self.name} / {original_other}" + name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_value = other.value if original_value == 0: From 872aea3f17db00f1464cb29de15cbac4b9ad2a26 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 11:44:47 +0200 Subject: [PATCH 61/68] Minor consistency fixes --- src/easyscience/Objects/new_variable/parameter.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 93289178..601fb4de 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -122,9 +122,6 @@ def __init__( } self._constraints = Constraints(builtin=builtin_constraint, user={}, virtual={}) - if self.unit is not None: - self.convert_unit(self._base_unit()) - @property def value_no_call_back(self) -> numbers.Number: @@ -255,7 +252,6 @@ def min(self, min_value: numbers.Number) -> None: """ if not isinstance(min_value, numbers.Number): raise TypeError('`min` must be a number') - # if min_value == self._max.value: if np.isclose(min_value, self._max.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if min_value <= self.value: @@ -284,7 +280,6 @@ def max(self, max_value: numbers.Number) -> None: """ if not isinstance(max_value, numbers.Number): raise TypeError('`max` must be a number') - # if max_value == self._min.value: if np.isclose(max_value, self._min.value, rtol=1e-9, atol=0.0): raise ValueError('The min and max bounds cannot be identical. Please use fixed=True instead to fix the value.') if max_value >= self.value: @@ -504,7 +499,7 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> else: min_value = self.min - other.value max_value = self.max - other.value - name = self.name+" - "+other.name + name = f"{self.name} - {other.name}" other.convert_unit(other_unit) else: return NotImplemented @@ -593,8 +588,8 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) combinations = [self.min / other, self.max / other] name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here - original_value = other.value - if original_value == 0: + other_value = other.value + if other_value == 0: raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other.full_value if isinstance(other, Parameter): @@ -619,7 +614,7 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) else: combinations = [self.min / other.value, self.max / other.value] name = f"{self.name} / {other.name}" - other.value = original_value + other.value = other_value else: return NotImplemented min_value = min(combinations) @@ -641,7 +636,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame 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 - name = other.name+" / "+self.name + name = f"{other.name} / {self.name}" if other_value == 0: return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) else: From a99be72d173e6c6d74f5ee1a61ca6674145ccc29 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 13:29:05 +0200 Subject: [PATCH 62/68] Changed names to be unique_name --- .../Objects/new_variable/descriptor_base.py | 5 +- .../Objects/new_variable/descriptor_bool.py | 2 +- .../Objects/new_variable/descriptor_number.py | 2 +- .../Objects/new_variable/descriptor_str.py | 2 +- .../Objects/new_variable/parameter.py | 54 +++++++------------ 5 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_base.py b/src/easyscience/Objects/new_variable/descriptor_base.py index 79004770..ffe76cff 100644 --- a/src/easyscience/Objects/new_variable/descriptor_base.py +++ b/src/easyscience/Objects/new_variable/descriptor_base.py @@ -31,7 +31,7 @@ class DescriptorBase(ComponentSerializer, metaclass=abc.ABCMeta): def __init__( self, - name: str, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, @@ -58,6 +58,9 @@ def __init__( unique_name = self._unique_name_generator() self._unique_name = unique_name + if name is None: + name=unique_name + if not isinstance(name, str): raise TypeError('Name must be a string') self._name: str = name diff --git a/src/easyscience/Objects/new_variable/descriptor_bool.py b/src/easyscience/Objects/new_variable/descriptor_bool.py index 768b35b1..6509a378 100644 --- a/src/easyscience/Objects/new_variable/descriptor_bool.py +++ b/src/easyscience/Objects/new_variable/descriptor_bool.py @@ -14,8 +14,8 @@ class DescriptorBool(DescriptorBase): """ def __init__( self, - name: str, value: bool, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 4605644f..ad3b959b 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -23,8 +23,8 @@ class DescriptorNumber(DescriptorBase): def __init__( self, - name: str, value: numbers.Number, + name: Optional[str] = None, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = None, unique_name: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_str.py b/src/easyscience/Objects/new_variable/descriptor_str.py index 1abe4e4e..80c0f6c9 100644 --- a/src/easyscience/Objects/new_variable/descriptor_str.py +++ b/src/easyscience/Objects/new_variable/descriptor_str.py @@ -15,8 +15,8 @@ class DescriptorStr(DescriptorBase): def __init__( self, - name: str, value: str, + name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 601fb4de..22246ada 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -439,7 +439,6 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - name = f"{self.name} + {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: @@ -449,11 +448,10 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> 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 - name = f"{self.name} + {other.name}" other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -462,7 +460,6 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = self.full_value + other min_value = self.min + other max_value = self.max + other - name = f"{other} + {self.name}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: @@ -472,11 +469,10 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = self.full_value + other.full_value min_value = self.min + other.value max_value = self.max + other.value - name = f"{other.name} + {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -485,7 +481,6 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> new_full_value = self.full_value - other min_value = self.min - other max_value = self.max - other - name = f"{self.name} - {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_unit = other.unit try: @@ -499,11 +494,10 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> else: min_value = self.min - other.value max_value = self.max - other.value - name = f"{self.name} - {other.name}" other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): @@ -512,7 +506,6 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other - self.full_value min_value = other - self.max max_value = other - self.min - name = f"{other} - {self.name}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here original_unit = self.unit try: @@ -522,24 +515,21 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other.full_value - self.full_value min_value = other.value - self.max max_value = other.value - self.min - name = f"{other.name} - {self.name}" self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = self.full_value * other - name = f"{self.name} * {other}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [self.min * other, self.max * other] elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = self.full_value * other.full_value - name = f"{self.name} * {other.name}" if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) 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 @@ -555,7 +545,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -564,19 +554,19 @@ def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: new_full_value = other * self.full_value name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [other * self.min, other * self.max] elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here new_full_value = other.full_value * self.full_value name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) combinations = [self.min * other.value, self.max * other.value] else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -586,7 +576,6 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) raise ZeroDivisionError("Cannot divide by zero") new_full_value = self.full_value / other combinations = [self.min / other, self.max / other] - name = f"{self.name} / {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here other_value = other.value if other_value == 0: @@ -613,13 +602,12 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) 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] - name = f"{self.name} / {other.name}" other.value = other_value else: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) return parameter @@ -630,15 +618,13 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame if isinstance(other, numbers.Number): new_full_value = other / self.full_value other_value = other - name = f"{other} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) 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 - name = f"{other.name} / {self.name}" if other_value == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) else: return NotImplemented if (self.min < 0 and self.max > 0): @@ -657,7 +643,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) parameter.convert_unit(parameter._base_unit()) self.value = original_self return parameter @@ -665,14 +651,12 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other - name = f"{self.name} ** {other}" elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = f"{self.name} ** {other.name}" else: return NotImplemented @@ -684,7 +668,7 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=name, full_value=new_full_value) + return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) elif exponent < 0: if self.min < 0 and self.max > 0: combinations = [-np.inf, np.inf] @@ -706,21 +690,19 @@ 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) - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __neg__(self) -> Parameter: new_full_value = -self.full_value - name = f"-{self.name}" min_value = -self.max max_value = -self.min - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) def __abs__(self) -> Parameter: new_full_value = abs(self.full_value) - name = f"abs({self.name})" combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=name, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file + return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file From a093af09b3f302dfa47b4fdaf6de104162f1128a Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 14:54:17 +0200 Subject: [PATCH 63/68] Auto generate names as unique_name for arithmetic operations --- .../Objects/new_variable/descriptor_base.py | 5 +- .../Objects/new_variable/descriptor_bool.py | 2 +- .../Objects/new_variable/descriptor_number.py | 62 ++++++++-------- .../Objects/new_variable/descriptor_str.py | 2 +- .../Objects/new_variable/parameter.py | 70 +++++++++++++------ .../Objects/new_variable/test_parameter.py | 68 +++++++++--------- 6 files changed, 121 insertions(+), 88 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_base.py b/src/easyscience/Objects/new_variable/descriptor_base.py index ffe76cff..79004770 100644 --- a/src/easyscience/Objects/new_variable/descriptor_base.py +++ b/src/easyscience/Objects/new_variable/descriptor_base.py @@ -31,7 +31,7 @@ class DescriptorBase(ComponentSerializer, metaclass=abc.ABCMeta): def __init__( self, - name: Optional[str] = None, + name: str, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, @@ -58,9 +58,6 @@ def __init__( unique_name = self._unique_name_generator() self._unique_name = unique_name - if name is None: - name=unique_name - if not isinstance(name, str): raise TypeError('Name must be a string') self._name: str = name diff --git a/src/easyscience/Objects/new_variable/descriptor_bool.py b/src/easyscience/Objects/new_variable/descriptor_bool.py index 6509a378..768b35b1 100644 --- a/src/easyscience/Objects/new_variable/descriptor_bool.py +++ b/src/easyscience/Objects/new_variable/descriptor_bool.py @@ -14,8 +14,8 @@ class DescriptorBool(DescriptorBase): """ def __init__( self, + name: str, value: bool, - name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index ad3b959b..2dfc14dd 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -23,8 +23,8 @@ class DescriptorNumber(DescriptorBase): def __init__( self, + name: str, value: numbers.Number, - name: Optional[str] = None, unit: Optional[Union[str, sc.Unit]] = '', variance: Optional[numbers.Number] = None, unique_name: Optional[str] = None, @@ -237,7 +237,6 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN if self.unit != 'dimensionless': raise UnitError("Numbers can only be added to dimensionless values") new_value = self.full_value + other - name = self.name + ' + ' + str(other) elif type(other) is DescriptorNumber: original_unit = other.unit try: @@ -245,28 +244,31 @@ def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be added") from None new_value = self.full_value + other.full_value - name = self._name + ' + ' + other._name other.convert_unit(original_unit) else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = other + self.full_value - name = str(other) + ' + ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = self.full_value - other - name = self.name + ' - ' + str(other) elif type(other) is DescriptorNumber: original_unit = other.unit try: @@ -274,42 +276,44 @@ def __sub__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN except UnitError: raise UnitError(f"Values with units {self.unit} and {other.unit} cannot be subtracted") from None new_value = self.full_value - other.full_value - name = self._name + ' - ' + other._name other.convert_unit(original_unit) else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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") new_value = other - self.full_value - name = str(other) + ' - ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 - name = self.name + ' * ' + str(other) elif type(other) is DescriptorNumber: new_value = self.full_value * other.full_value - name = self._name + ' * ' + other._name else: return NotImplemented - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 return descriptor_number def __rmul__(self, other: numbers.Number) -> DescriptorNumber: if isinstance(other, numbers.Number): new_value = other * self.full_value - name = str(other) + ' * ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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): @@ -317,18 +321,17 @@ def __truediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Descrip if other == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other - name = self.name + ' / ' + str(original_other) elif type(other) is DescriptorNumber: original_other = other.value if original_other == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = self.full_value / other.full_value other.value = original_other - name = self._name + ' / ' + other._name else: return NotImplemented - descriptor_number = DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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 return descriptor_number def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: @@ -336,22 +339,21 @@ def __rtruediv__(self, other: numbers.Number) -> DescriptorNumber: if self.value == 0: raise ZeroDivisionError("Cannot divide by zero") new_value = other / self.full_value - name = str(other) + ' / ' + self.name else: return NotImplemented - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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: if isinstance(other, numbers.Number): exponent = other - name = f"{self.name} ** {other}" elif type(other) is DescriptorNumber: if other.unit != 'dimensionless': raise UnitError("Exponents must be dimensionless") if other.variance is not None: raise ValueError("Exponents must not have variance") exponent = other.value - name = self.name+" ** "+other.name else: return NotImplemented try: @@ -360,7 +362,9 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorN raise message from None if np.isnan(new_value.value): raise ValueError("The result of the exponentiation is not a number") - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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): @@ -375,13 +379,15 @@ def __rpow__(self, other: numbers.Number) -> numbers.Number: def __neg__(self) -> DescriptorNumber: new_value = -self.full_value - name = '-'+self.name - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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) - name = 'abs('+self.name+')' - return DescriptorNumber.from_scipp(name=name, full_value=new_value) + 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) diff --git a/src/easyscience/Objects/new_variable/descriptor_str.py b/src/easyscience/Objects/new_variable/descriptor_str.py index 80c0f6c9..1abe4e4e 100644 --- a/src/easyscience/Objects/new_variable/descriptor_str.py +++ b/src/easyscience/Objects/new_variable/descriptor_str.py @@ -15,8 +15,8 @@ class DescriptorStr(DescriptorBase): def __init__( self, + name: str, value: str, - name: Optional[str] = None, unique_name: Optional[str] = None, description: Optional[str] = None, url: Optional[str] = None, diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index 22246ada..cf7c6e8e 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -451,7 +451,9 @@ def __add__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -472,7 +474,9 @@ def __radd__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -497,7 +501,9 @@ def __sub__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> other.convert_unit(other_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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): @@ -518,18 +524,24 @@ def __rsub__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: self.convert_unit(original_unit) else: return NotImplemented - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = self.full_value * other.full_value if other.value == 0 and isinstance(other, DescriptorNumber): - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 @@ -545,29 +557,33 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __rmul__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): new_full_value = other * self.full_value - name = f"{other} * {self.name}" if other == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = other.full_value * self.full_value - name = f"{other.name} * {self.name}" if other.value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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: return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> Parameter: @@ -607,8 +623,9 @@ def __truediv__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) return NotImplemented min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 return parameter def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: @@ -619,12 +636,16 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame new_full_value = other / self.full_value other_value = other if other_value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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 new_full_value = other.full_value / self.full_value other_value = other.value if other_value == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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): @@ -643,8 +664,9 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame combinations = [other_value / self.min, other_value / self.max] min_value = min(combinations) max_value = max(combinations) - parameter = Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 self.value = original_self return parameter @@ -668,7 +690,9 @@ def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if np.isnan(new_full_value.value): raise ValueError("The result of the exponentiation is not a number") if exponent == 0: - return DescriptorNumber.from_scipp(name=None, full_value=new_full_value) + 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] @@ -690,13 +714,17 @@ 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) - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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 - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) + 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) @@ -705,4 +733,6 @@ def __abs__(self) -> Parameter: combinations.append(0) min_value = min(combinations) max_value = max(combinations) - return Parameter.from_scipp(name=None, full_value=new_full_value, min=min_value, max=max_value) \ No newline at end of file + 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/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 76783544..115baf0f 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -355,15 +355,15 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, result_reverse = test + parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name - assert result_reverse.value == expected_reverse.value + assert result_reverse.name == result_reverse.unique_name + assert result_reverse.value == result_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min @@ -380,14 +380,14 @@ def test_addition_with_scalar(self): result_reverse = 1.0 + parameter # Expect - assert result.name == "name + 1.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == 1.0 assert result.max == 11.0 - assert result_reverse.name == "1.0 + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -405,7 +405,7 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): # Expect assert type(result) == Parameter - assert result.name == "name + test" + assert result.name == result.unique_name assert result.value == 1.01 assert result.unit == "m" assert result.variance == 0.01001 @@ -413,7 +413,7 @@ def test_addition_with_descriptor_number(self, parameter : Parameter): assert result.max == 10.01 assert type(result_reverse) == Parameter - assert result_reverse.name == "test + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 101.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 @@ -445,14 +445,14 @@ def test_subtraction_with_parameter(self, parameter : Parameter, test : Paramete result_reverse = test - parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -471,14 +471,14 @@ def test_subtraction_with_parameter_nan_cases(self): result_reverse = test - parameter # Expect - assert result.name == "name - test" + assert result.name == result.unique_name assert result.value == -1.0 assert result.unit == "dimensionless" assert result.variance == 0.02 assert result.min == -np.Inf assert result.max == np.Inf - assert result_reverse.name == "test - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.02 @@ -494,14 +494,14 @@ def test_subtraction_with_scalar(self): result_reverse = 1.0 - parameter # Expect - assert result.name == "name - 1.0" + assert result.name == result.unique_name assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.01 assert result.min == -1.0 assert result.max == 9.0 - assert result_reverse.name == "1.0 - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.01 @@ -519,7 +519,7 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): # Expect assert type(result) == Parameter - assert result.name == "name - test" + assert result.name == result.unique_name assert result.value == 0.99 assert result.unit == "m" assert result.variance == 0.01001 @@ -527,7 +527,7 @@ def test_subtraction_with_descriptor_number(self, parameter : Parameter): assert result.max == 9.99 assert type(result_reverse) == Parameter - assert result_reverse.name == "test - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -99.0 assert result_reverse.unit == "cm" assert result_reverse.variance == 100.1 @@ -560,14 +560,14 @@ def test_multiplication_with_parameter(self, parameter : Parameter, test : Param result_reverse = test * parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -587,12 +587,12 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ result_reverse = test * parameter # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -619,7 +619,7 @@ def test_multiplication_with_descriptor_number(self, parameter : Parameter, test # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -628,7 +628,7 @@ def test_multiplication_with_descriptor_number(self, parameter : Parameter, test assert result.max == expected.max assert type(result_reverse) == type(expected_reverse) - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -650,7 +650,7 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -658,7 +658,7 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, assert result.min == expected.min assert result.max == expected.max - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -682,7 +682,7 @@ def test_division_with_parameter(self, parameter : Parameter, test, expected, ex # Expect assert type(result) == Parameter - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == pytest.approx(expected.value) assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) @@ -690,7 +690,7 @@ def test_division_with_parameter(self, parameter : Parameter, test, expected, ex assert result.max == expected.max assert type(result) == Parameter - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == pytest.approx(expected_reverse.value) assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -707,7 +707,7 @@ def test_division_with_parameter_remaining_cases(self, first, second, expected): result = first / second # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -728,7 +728,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, # Expect assert type(result) == Parameter - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -736,7 +736,7 @@ def test_division_with_descriptor_number_and_number(self, parameter : Parameter, assert result.max == expected.max assert type(result_reverse) == Parameter - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance @@ -756,7 +756,7 @@ def test_zero_value_divided_by_parameter(self, parameter : Parameter, test, expe # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -773,7 +773,7 @@ def test_division_with_descriptor_number_missing_cases(self, first, second, expe result = first / second # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -815,7 +815,7 @@ def test_power_of_parameter(self, test, expected): # Expect assert type(result) == type(expected) - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -837,7 +837,7 @@ def test_power_of_diffent_parameters(self, test, exponent, expected): result = test ** exponent # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -863,7 +863,7 @@ def test_negation(self): result = -parameter # Expect - assert result.name == "-name" + assert result.name == result.unique_name assert result.value == -5 assert result.unit == "m" assert result.variance == 0.05 @@ -879,7 +879,7 @@ def test_abs(self, test, expected): result = abs(test) # Expect - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance From b701d45f56cdc3419dbd6725438a4a5a8a8fd45d Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 7 Aug 2024 15:09:28 +0200 Subject: [PATCH 64/68] Updated tests --- .../new_variable/test_descriptor_number.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 678198da..f984d346 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -213,7 +213,7 @@ def test_addition(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -230,13 +230,13 @@ def test_addition_with_scalar(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name + 1.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "1.0 + name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -259,7 +259,7 @@ def test_subtraction(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -276,13 +276,13 @@ def test_subtraction_with_scalar(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name - 1.0" + assert result.name == result.unique_name assert result.value == 1.0 assert result.unit == "dimensionless" assert result.variance == 0.1 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "1.0 - name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == -1.0 assert result_reverse.unit == "dimensionless" assert result_reverse.variance == 0.1 @@ -306,7 +306,7 @@ def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) @@ -318,13 +318,13 @@ def test_multiplication_with_scalar(self, descriptor: DescriptorNumber): # Expect assert type(result) == DescriptorNumber - assert result.name == "name * 2.0" + assert result.name == result.unique_name assert result.value == 2.0 assert result.unit == "m" assert result.variance == 0.4 assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == "2.0 * name" + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == 2.0 assert result_reverse.unit == "m" assert result_reverse.variance == 0.4 @@ -340,13 +340,13 @@ def test_division(self, descriptor: DescriptorNumber, test, expected, expected_r # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == pytest.approx(expected.variance) assert type(result_reverse) == DescriptorNumber - assert result_reverse.name == expected_reverse.name + assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == pytest.approx(expected_reverse.variance) @@ -379,7 +379,7 @@ def test_power_of_descriptor(self, test, expected): # Expect assert type(result) == DescriptorNumber - assert result.name == expected.name + assert result.name == result.unique_name assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance @@ -393,7 +393,7 @@ def test_power_of_dimensionless_descriptor(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "name ** 0.5" + assert result.name == result.unique_name assert result.value == 1.4142135623730951 assert result.unit == "dimensionless" assert result.variance == pytest.approx(0.0125) @@ -438,7 +438,7 @@ def test_negation(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "-name" + assert result.name == result.unique_name assert result.value == -2 assert result.unit == "m" assert result.variance == 0.1 @@ -452,7 +452,7 @@ def test_abs(self): # Expect assert type(result) == DescriptorNumber - assert result.name == "abs(name)" + assert result.name == result.unique_name assert result.value == 2 assert result.unit == "m" assert result.variance == 0.1 \ No newline at end of file From 5a110677f2fb717fc4685be077eceaa4eb20db47 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Thu, 8 Aug 2024 10:20:28 +0200 Subject: [PATCH 65/68] remove unused import --- tests/unit_tests/Objects/new_variable/test_descriptor_number.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index f984d346..9438f300 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -3,8 +3,6 @@ import scipp as sc from scipp import UnitError -from scipp import UnitError - from easyscience.Objects.new_variable.descriptor_number import DescriptorNumber from easyscience import global_object From 212da63cbd7195b860cd4a3ecb1927e01275e802 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Mon, 12 Aug 2024 14:17:32 +0200 Subject: [PATCH 66/68] Fix commit mistakes and clean tests --- .../Objects/new_variable/parameter.py | 4 ++-- .../new_variable/test_descriptor_number.py | 20 ++++++++++++++++--- .../Objects/new_variable/test_parameter.py | 20 +++++++------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/easyscience/Objects/new_variable/parameter.py b/src/easyscience/Objects/new_variable/parameter.py index cf7c6e8e..b48f6a91 100644 --- a/src/easyscience/Objects/new_variable/parameter.py +++ b/src/easyscience/Objects/new_variable/parameter.py @@ -538,7 +538,7 @@ def __mul__(self, other: Union[DescriptorNumber, Parameter, numbers.Number]) -> combinations = [self.min * other, self.max * other] 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 isinstance(other, DescriptorNumber): + 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 @@ -673,7 +673,7 @@ def __rtruediv__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parame def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> Parameter: if isinstance(other, numbers.Number): exponent = other - elif isinstance(other, DescriptorNumber): # Parameter inherits from DescriptorNumber and is also handled here + 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") if other.variance is not None: diff --git a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py index 9438f300..75edc6dd 100644 --- a/tests/unit_tests/Objects/new_variable/test_descriptor_number.py +++ b/tests/unit_tests/Objects/new_variable/test_descriptor_number.py @@ -201,6 +201,21 @@ def test_as_data_dict(self, clear, descriptor: DescriptorNumber): "unique_name": "DescriptorNumber_0", } + @pytest.mark.parametrize("unit_string, expected", [ + ("1e+9", "dimensionless"), + ("1000", "dimensionless"), + ("10dm^2", "m^2")], + ids=["scientific_notation", "numbers", "unit_prefix"]) + def test_base_unit(self, unit_string, expected): + # When + descriptor = DescriptorNumber(name="name", value=1, unit=unit_string) + + # Then + base_unit = descriptor._base_unit() + + # Expect + assert base_unit == expected + @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test + name", 3, "m", 0.11)), (DescriptorNumber("test", 2, "cm", 0.01), DescriptorNumber("test + name", 102, "cm", 1000.01))], @@ -295,9 +310,8 @@ def test_subtraction_exception(self, descriptor: DescriptorNumber, test): @pytest.mark.parametrize("test, expected", [ (DescriptorNumber("test", 2, "m", 0.01,), DescriptorNumber("test * name", 2, "m^2", 0.41)), - (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041)), - (DescriptorNumber("test", 2, "1/dm", 0.01), DescriptorNumber("test * name", 20.0, "dimensionless", 41))], - ids=["regular", "base_unit_conversion", "base_unit_conversion_dimensionless"]) + (DescriptorNumber("test", 2, "dm", 0.01), DescriptorNumber("test * name", 0.2, "m^2", 0.0041))], + ids=["regular", "base_unit_conversion"]) def test_multiplication(self, descriptor: DescriptorNumber, test, expected): # When Then result = test * descriptor diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 115baf0f..3d81e6ad 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -548,9 +548,8 @@ def test_subtraction_exception(self, parameter : Parameter, test): @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "m", 0.01, -10, 20), Parameter("name * test", 2, "m^2", 0.05, -100, 200), Parameter("test * name", 2, "m^2", 0.05, -100, 200)), (Parameter("test", 2, "m", 0.01), Parameter("name * test", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf), Parameter("test * name", 2, "m^2", 0.05, min=-np.Inf, max=np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20)), - (Parameter("test", 2, "1/dm", 0.01, -10, 20), Parameter("name * test", 20.0, "dimensionless", 5, -1000, 2000), Parameter("test * name", 20.0, "dimensionless", 5, -1000, 2000))], - ids=["regular", "no_bounds", "base_unit_conversion", "base_unit_conversion_dimensionless"]) + (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name * test", 0.2, "m^2", 0.0005, -10, 20), Parameter("test * name", 0.2, "m^2", 0.0005, -10, 20))], + ids=["regular", "no_bounds", "base_unit_conversion"]) def test_multiplication_with_parameter(self, parameter : Parameter, test : Parameter, expected : Parameter, expected_reverse : Parameter): # When parameter._callback = property() @@ -591,19 +590,15 @@ def test_multiplication_with_parameter_nan_cases(self, test, expected, expected_ assert result.value == expected.value assert result.unit == expected.unit assert result.variance == expected.variance + assert result.min == expected.min + assert result.max == expected.max assert result_reverse.name == result_reverse.unique_name assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance - - if isinstance(result, Parameter): - assert result.min == expected.min - assert result.max == expected.max - - if isinstance(result_reverse, Parameter): - assert result_reverse.min == expected_reverse.min - assert result_reverse.max == expected_reverse.max + assert result_reverse.min == expected_reverse.min + assert result_reverse.max == expected_reverse.max @pytest.mark.parametrize("test, expected, expected_reverse", [ (DescriptorNumber(name="test", value=2, variance=0.1, unit="cm"), Parameter("name * test", 2, "dm^2", 0.14, 0, 20), Parameter("test * name", 2, "dm^2", 0.14, 0, 20)), @@ -668,10 +663,9 @@ def test_multiplication_with_scalar(self, parameter : Parameter, test, expected, @pytest.mark.parametrize("test, expected, expected_reverse", [ (Parameter("test", 2, "s", 0.01, -10, 20), Parameter("name / test", 0.5, "m/s", 0.003125, -np.Inf, np.Inf), Parameter("test / name", 2, "s/m", 0.05, -np.Inf, np.Inf)), - (Parameter("test", 2, "dm", 0.01, -10, 20), Parameter("name / test", 5, "dimensionless", 0.3125, -np.Inf, np.Inf), Parameter("test / name", 0.2, "dimensionless", 0.0005, -np.Inf, np.Inf)), (Parameter("test", 2, "s", 0.01, 0, 20), Parameter("name / test", 0.5, "m/s", 0.003125, 0.0, np.Inf), Parameter("test / name", 2, "s/m", 0.05, 0.0, np.Inf)), (Parameter("test", -2, "s", 0.01, -10, 0), Parameter("name / test", -0.5, "m/s", 0.003125, -np.Inf, 0.0), Parameter("test / name", -2, "s/m", 0.05, -np.Inf, 0.0))], - ids=["crossing_zero", "base_unit_conversion_dimensionless", "only_positive", "only_negative"]) + ids=["crossing_zero", "only_positive", "only_negative"]) def test_division_with_parameter(self, parameter : Parameter, test, expected, expected_reverse): # When parameter._callback = property() From 7571f7f28e36c47b77a459c796e41d6cef7afd10 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 14 Aug 2024 12:08:03 +0200 Subject: [PATCH 67/68] Fix mistakes from Henrik --- src/easyscience/Objects/new_variable/descriptor_number.py | 1 - tests/unit_tests/Objects/new_variable/test_parameter.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/easyscience/Objects/new_variable/descriptor_number.py b/src/easyscience/Objects/new_variable/descriptor_number.py index 2dfc14dd..4fd25709 100644 --- a/src/easyscience/Objects/new_variable/descriptor_number.py +++ b/src/easyscience/Objects/new_variable/descriptor_number.py @@ -70,7 +70,6 @@ def __init__( ) # Call convert_unit during initialization to ensure that the unit has no numbers in it, and to ensure unit consistency. - # For some reason, converting to the same unit often changes the representation. This breaks almost every test. if self.unit is not None: self.convert_unit(self._base_unit()) diff --git a/tests/unit_tests/Objects/new_variable/test_parameter.py b/tests/unit_tests/Objects/new_variable/test_parameter.py index 3d81e6ad..017e76bd 100644 --- a/tests/unit_tests/Objects/new_variable/test_parameter.py +++ b/tests/unit_tests/Objects/new_variable/test_parameter.py @@ -363,7 +363,7 @@ def test_addition_with_parameter(self, parameter : Parameter, test : Parameter, assert result.max == expected.max assert result_reverse.name == result_reverse.unique_name - assert result_reverse.value == result_reverse.value + assert result_reverse.value == expected_reverse.value assert result_reverse.unit == expected_reverse.unit assert result_reverse.variance == expected_reverse.variance assert result_reverse.min == expected_reverse.min From 512b03a1eb275bfa62e714858b7b6e258532a7bb Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 14 Aug 2024 15:44:48 +0200 Subject: [PATCH 68/68] Fix rebase error --- src/easyscience/fitting/minimizers/minimizer_base.py | 2 +- src/easyscience/unit_aliases.py | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 src/easyscience/unit_aliases.py diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 48769081..59ef4133 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -40,7 +40,7 @@ def __init__( fit_function: Callable, method: Optional[str] = None, ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501 - if method not in self.available_methods(): + if method not in self.supported_methods(): raise FitError(f'Method {method} not available in {self.__class__}') self._object = obj self._original_fit_function = fit_function diff --git a/src/easyscience/unit_aliases.py b/src/easyscience/unit_aliases.py deleted file mode 100644 index 297dd0e4..00000000 --- a/src/easyscience/unit_aliases.py +++ /dev/null @@ -1,12 +0,0 @@ -import scipp as sc - -# This document shows how to define aliases for units in scipp, -# and how to overwrite existing aliases. - -sc.units.aliases.clear() # Clear existing aliases -sc.units.aliases['funny_unit'] = sc.scalar(42.0, unit='m') -# ^ 'funny_unit' is now an alias for 42 m and can be used as a unit in scipp and be converted to units of dimension 'm' - -# The representation of a unit can be changed by defining an alias for it: -sc.units.aliases['m/ms'] = 'm/ms' -# Setting this alias ensures that 'm/ms' is displayed as 'm/ms' instead of the default 'km/s' \ No newline at end of file