Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
ca46067
Implement OutOfBoundsError
zzril Jul 7, 2023
9a1ecf4
Fix formatting
zzril Jul 7, 2023
cbc8019
Fix typos
zzril Jul 7, 2023
56aa484
Add test for OutOfBoundsError
zzril Jul 7, 2023
63c8640
moved test_out_of_bounds_error.py
Marsmaennchen221 Jul 7, 2023
69ec60b
test: Added tests for `OutOfBoundsError`
Marsmaennchen221 Jul 7, 2023
3a40c6a
Fix type hints
zzril Jul 7, 2023
0805bb8
feat: Added `NotImplementedError` if lower bound is greater than uppe…
Marsmaennchen221 Jul 7, 2023
293ac8c
Merge branch '262-custom-error-if-number-is-out-of-bounds' of https:/…
Marsmaennchen221 Jul 7, 2023
bb3a235
Merge branch 'main' into 262-custom-error-if-number-is-out-of-bounds
zzril Jul 7, 2023
19d8569
Add None return type in __init__ methods
zzril Jul 7, 2023
4575b58
style: apply automated linter fixes
megalinter-bot Jul 7, 2023
d23718b
style: apply automated linter fixes
megalinter-bot Jul 7, 2023
d529a4a
feat: Added check for compare to lower and upper bounds
Marsmaennchen221 Jul 7, 2023
b724bbd
Merge branch '262-custom-error-if-number-is-out-of-bounds' of https:/…
Marsmaennchen221 Jul 7, 2023
f089975
style: apply automated linter fixes
megalinter-bot Jul 7, 2023
83f7952
style: apply automated linter fixes
megalinter-bot Jul 7, 2023
a560c59
Merge branch 'main' into 262-custom-error-if-number-is-out-of-bounds
zzril Jul 8, 2023
6dc82b1
Add docstrings to public methods in _generic.py
zzril Jul 8, 2023
97fdca4
Remove unused _is_float method from Bound classes
zzril Jul 8, 2023
1d49617
Fix typo in OutOfBoundsError test
zzril Jul 8, 2023
9f3ef11
Make str functions public
zzril Jul 8, 2023
0f0132d
Rename compare functions.
zzril Jul 8, 2023
97096c4
Fix bounds checking logic
zzril Jul 8, 2023
1a09b5f
Add missing testcase
zzril Jul 8, 2023
2ce2380
Improve exception messages and fix testcases
zzril Jul 8, 2023
7a96093
Let the linter do the work
zzril Jul 8, 2023
c4d4fdc
style: apply automated linter fixes
megalinter-bot Jul 8, 2023
72e3eae
style: apply automated linter fixes
megalinter-bot Jul 8, 2023
6b73948
Add docstring to __init__ method
zzril Jul 8, 2023
e7e1f9f
Remove parameters section in class docstring
zzril Jul 8, 2023
90079e0
Use OutOfBoundsError in image tests
zzril Jul 9, 2023
eab7354
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
0c5a23d
Use ValueError instead of NotImplementedError
zzril Jul 9, 2023
c41eb07
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
cb8de4b
Make Infinity classed private
zzril Jul 9, 2023
7a0792e
Merge branch '262-custom-error-if-number-is-out-of-bounds' of github.…
zzril Jul 9, 2023
076558a
Create property for value to silence warnings
zzril Jul 9, 2023
bd54cbc
Add type hint for value property
zzril Jul 9, 2023
1928ce2
Add additional type hints inside __init__ function
zzril Jul 9, 2023
1a79d70
Fix unintended class call instead of constructor
zzril Jul 9, 2023
6a2c516
Use local variable with different name
zzril Jul 9, 2023
7407ee2
Remove private classes from __init__.py
zzril Jul 9, 2023
4ab61c2
Improve warning messages for incorrect bounds
zzril Jul 9, 2023
14474e0
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
42ef8b5
Document ValueError in docstring
zzril Jul 9, 2023
58dd3d6
Merge branch 'main' into 262-custom-error-if-number-is-out-of-bounds
zzril Jul 9, 2023
ed983b8
Remove Infinity classes
zzril Jul 9, 2023
9798190
Make string and check methods private
zzril Jul 9, 2023
5ef248a
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
d616bcc
Check for nan in Bound constructor
zzril Jul 9, 2023
6f69b16
Apply suggestions from code review
zzril Jul 9, 2023
915d652
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
f2b82e9
Use OutOfBoundsError in Discretizer
zzril Jul 9, 2023
4cf2547
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
79be629
Update Images to use name parameter
zzril Jul 9, 2023
8649b98
Merge branch '262-custom-error-if-number-is-out-of-bounds' of github.…
zzril Jul 9, 2023
5c37bb5
Add tests for name parameter
zzril Jul 9, 2023
aff945b
style: apply automated linter fixes
megalinter-bot Jul 9, 2023
3885a43
Update class docstring to include name parameter
zzril Jul 9, 2023
fada030
Add nan and inf checks
zzril Jul 10, 2023
ff44d26
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
abf1d55
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
6173601
Remove unnecessary checks
zzril Jul 10, 2023
4a069ce
Fix equality comparisons
zzril Jul 10, 2023
1361f49
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
61f78c2
Simplify infinity checks using isinf
zzril Jul 10, 2023
6c62093
Use OutOfBoundsError in AdaBoost classifier
zzril Jul 10, 2023
a23062f
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
0a53227
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
c2e0f10
Use new error in gradient boosting classifier
zzril Jul 10, 2023
eac8329
Merge branch 'main' into 262-custom-error-if-number-is-out-of-bounds
zzril Jul 10, 2023
aa3df4a
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
babda19
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
74ea122
Use new error for nearest_neighbors classifier
zzril Jul 10, 2023
053a289
Merge branch '262-custom-error-if-number-is-out-of-bounds' of github.…
zzril Jul 10, 2023
6a42065
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
acd0d85
Use OutOfBoundsError for RandomForest classifier
zzril Jul 10, 2023
de897c9
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
3b3eba1
Use OutOfBoundsError for SVM and inner classes
zzril Jul 10, 2023
d901936
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
2dc3797
Use OutOfBoundsError in regression models
zzril Jul 10, 2023
347f2c5
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
0616612
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
6b5b15c
Use ClosedBounds for integers
zzril Jul 10, 2023
5bf1dd6
style: apply automated linter fixes
megalinter-bot Jul 10, 2023
dd6f4d1
Merge branch 'main' into 262-custom-error-if-number-is-out-of-bounds
lars-reimann Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/safeds/data/image/containers/_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from PIL.Image import open as open_image

from safeds.data.image.typing import ImageFormat
from safeds.exceptions import ClosedBound, OutOfBoundsError


class Image:
Expand Down Expand Up @@ -293,7 +294,7 @@ def adjust_brightness(self, factor: float) -> Image:
The Image with adjusted brightness.
"""
if factor < 0:
raise ValueError("Brightness factor has to be 0 or bigger")
raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0))
elif factor == 1:
warnings.warn(
"Brightness adjustment factor is 1.0, this will not make changes to the image.",
Expand Down Expand Up @@ -322,7 +323,7 @@ def adjust_contrast(self, factor: float) -> Image:
New image with adjusted contrast.
"""
if factor < 0:
raise ValueError("Contrast factor has to be 0 or bigger")
raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0))
elif factor == 1:
warnings.warn(
"Contrast adjustment factor is 1.0, this will not make changes to the image.",
Expand Down Expand Up @@ -352,7 +353,7 @@ def adjust_color_balance(self, factor: float) -> Image:
The new, adjusted image.
"""
if factor < 0:
raise ValueError("Color factor has to be 0 or bigger.")
raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0))
elif factor == 1:
warnings.warn(
"Color adjustment factor is 1.0, this will not make changes to the image.",
Expand Down
12 changes: 9 additions & 3 deletions src/safeds/data/tabular/transformation/_discretizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

from safeds.data.tabular.containers import Table
from safeds.data.tabular.transformation._table_transformer import TableTransformer
from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError
from safeds.exceptions import (
ClosedBound,
NonNumericColumnError,
OutOfBoundsError,
TransformerNotFittedError,
UnknownColumnNameError,
)


class Discretizer(TableTransformer):
Expand All @@ -18,7 +24,7 @@ class Discretizer(TableTransformer):

Raises
------
ValueError
OutOfBoundsError
If the given number_of_bins is less than 2.
"""

Expand All @@ -27,7 +33,7 @@ def __init__(self, number_of_bins: float = 5):
self._wrapped_transformer: sk_KBinsDiscretizer | None = None

if number_of_bins < 2:
raise ValueError("Parameter 'number_of_bins' must be >= 2.")
raise OutOfBoundsError(number_of_bins, name="number_of_bins", lower_bound=ClosedBound(2))
self._number_of_bins = number_of_bins

def fit(self, table: Table, column_names: list[str] | None) -> Discretizer:
Expand Down
12 changes: 12 additions & 0 deletions src/safeds/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
ValueNotPresentWhenFittedError,
WrongFileExtensionError,
)
from safeds.exceptions._generic import (
Bound,
ClosedBound,
OpenBound,
OutOfBoundsError,
)
from safeds.exceptions._ml import (
DatasetContainsTargetError,
DatasetMissesDataError,
Expand All @@ -26,6 +32,8 @@
)

__all__ = [
# Generic exceptions
"OutOfBoundsError",
# Data exceptions
"ColumnIsTargetError",
"ColumnLengthMismatchError",
Expand All @@ -48,4 +56,8 @@
"ModelNotFittedError",
"PredictionError",
"UntaggedTableError",
# Other
"Bound",
"ClosedBound",
"OpenBound",
]
281 changes: 281 additions & 0 deletions src/safeds/exceptions/_generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
from __future__ import annotations

from abc import ABC, abstractmethod

from numpy import isinf, isnan


class OutOfBoundsError(ValueError):
"""
A generic exception that can be used to signal that a (float) value is outside its expected range.

Parameters
----------
actual: float
The actual value that is outside its expected range.
name: str | None
The name of the offending variable.
lower_bound: Bound | None
The lower bound of the expected range. Use None if there is no lower Bound.
upper_bound: Bound | None
The upper bound of the expected range. Use None if there is no upper Bound.
"""

def __init__(
self,
actual: float,
*,
name: str | None = None,
lower_bound: Bound | None = None,
upper_bound: Bound | None = None,
):
"""
Initialize an OutOfBoundsError.

Parameters
----------
actual: float
Comment thread
lars-reimann marked this conversation as resolved.
The actual value that is outside its expected range.
name: str | None
The name of the offending variable.
lower_bound: Bound | None
The lower bound of the expected range. Use None if there is no lower Bound.
upper_bound: Bound | None
The upper bound of the expected range. Use None if there is no upper Bound.

Raises
------
ValueError
* If one of the given Bounds is +/-inf. (For infinite Bounds, pass None instead.)
* If one of the given Bounds is nan.
* If upper_bound < lower_bound.
* If actual does not lie outside the given interval.
* If actual is not a real number.
"""
# Validate bound parameters:
if lower_bound is None and upper_bound is None:
raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.")
if (lower_bound is not None and isinf(lower_bound.value)) or (
upper_bound is not None and isinf(upper_bound.value)
):
raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.")
# Validate actual parameter:
if isinf(actual) or isnan(actual):
raise ValueError("Attempting to raise OutOfBoundsError with actual value not being a real number.")
# Use local variables with stricter types to help static analysis:
_lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf"))
_upper_bound: Bound = upper_bound if upper_bound is not None else OpenBound(float("inf"))
# Check bounds:
if _upper_bound.value < _lower_bound.value:
raise ValueError(
(
f"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound {_upper_bound} is "
f"actually less than given lower bound {_lower_bound}."
),
)
# Check that actual is indeed outside the interval:
elif _lower_bound._check_lower_bound(actual) and _upper_bound._check_upper_bound(actual):
raise ValueError(
(
f"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually"
f" outside given interval {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}."
),
)
# Raise the actual exception:
full_variable_name = actual if name is None else f"{name} (={actual})"
super().__init__(
f"{full_variable_name} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.",
)


class Bound(ABC):
"""
Abstract base class for (lower or upper) Bounds on a float value.

Parameters
----------
value: float
The value of the Bound.
"""

def __init__(self, value: float):
"""
Initialize a Bound.

Parameters
----------
value: float
The value of the Bound.

Raises
------
ValueError
If value is nan or if value is +/-inf and the Bound type does not allow for infinite Bounds.
"""
if isnan(value):
raise ValueError("Bound must be a real number, not nan.")
self._value = value

def __str__(self) -> str:
"""Get a string representation of the concrete value of the Bound."""
return str(self.value)

@property
def value(self) -> float:
"""Get the concrete value of the Bound."""
return self._value

@abstractmethod
def _str_lower_bound(self) -> str:
"""Get a string representation of the Bound as the lower Bound of an interval."""

@abstractmethod
def _str_upper_bound(self) -> str:
"""Get a string representation of the Bound as the upper Bound of an interval."""

@abstractmethod
def _check_lower_bound(self, actual: float) -> bool:
"""
Check that a value does not exceed the Bound on the lower side.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""

@abstractmethod
def _check_upper_bound(self, actual: float) -> bool:
"""
Check that a value does not exceed the Bound on the upper side.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""


class ClosedBound(Bound):
"""
A closed Bound, i.e. the value on the border belongs to the range.

Parameters
----------
value: float
The value of the Bound.
"""

def __init__(self, value: float):
"""
Initialize a ClosedBound.

Parameters
----------
value: float
The value of the ClosedBound.

Raises
------
ValueError
If value is nan or if value is +/-inf.
"""
if value == float("-inf") or value == float("inf"):
raise ValueError("ClosedBound must be a real number, not +/-inf.")
super().__init__(value)

def _str_lower_bound(self) -> str:
"""Get a string representation of the ClosedBound as the lower Bound of an interval."""
return f"[{self}"

def _str_upper_bound(self) -> str:
"""Get a string representation of the ClosedBound as the upper Bound of an interval."""
return f"{self}]"

def _check_lower_bound(self, actual: float) -> bool:
"""
Check that a value is not strictly lower than the ClosedBound.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""
return actual >= self.value

def _check_upper_bound(self, actual: float) -> bool:
"""
Check that a value is not strictly higher than the ClosedBound.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""
return actual <= self.value


class OpenBound(Bound):
"""
An open Bound, i.e. the value on the border does not belong to the range.
Comment thread
lars-reimann marked this conversation as resolved.

Parameters
----------
value: float
The value of the OpenBound.
"""

def __init__(self, value: float):
"""
Initialize an OpenBound.

Parameters
----------
value: float
The value of the OpenBound.

Raises
------
ValueError
If value is nan.
"""
super().__init__(value)

def __str__(self) -> str:
"""Get a string representation of the concrete value of the OpenBound."""
if self.value == float("-inf"):
return "-\u221e"
elif self.value == float("inf"):
return "\u221e"
else:
return super().__str__()

def _str_lower_bound(self) -> str:
"""Get a string representation of the OpenBound as the lower Bound of an interval."""
return f"({self}"

def _str_upper_bound(self) -> str:
"""Get a string representation of the OpenBound as the upper Bound of an interval."""
return f"{self})"

def _check_lower_bound(self, actual: float) -> bool:
"""
Check that a value is not lower or equal to the OpenBound.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""
return actual > self.value

def _check_upper_bound(self, actual: float) -> bool:
"""
Check that a value is not higher or equal to the OpenBound.

Parameters
----------
actual: float
The actual value that should be checked for not exceeding the Bound.
"""
return actual < self.value
Loading