diff --git a/makefile b/makefile index abf2ef90..6fb84071 100644 --- a/makefile +++ b/makefile @@ -8,7 +8,7 @@ format: isort tests black src black tests - flake8 src + flake8 src --max-complexity 12 pylint src html: diff --git a/pyproject.toml b/pyproject.toml index b4c77b6c..0115f1da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ packages = [{ include = "shapepy", from = "src" }] [tool.poetry.dependencies] numpy = "^1.0.0" -rbool = "^0.0.4" matplotlib = "^3.4.0" pynurbs = "^1.0.7" python = "^3.9" diff --git a/src/shapepy/analytic/polynomial.py b/src/shapepy/analytic/polynomial.py index e1a30e52..261e1f8c 100644 --- a/src/shapepy/analytic/polynomial.py +++ b/src/shapepy/analytic/polynomial.py @@ -8,7 +8,7 @@ from typing import Iterable, List, Union from ..loggers import debug -from ..rbool import scale, shift +from ..rbool import move, scale from ..scalar.reals import Math from ..tools import Is, To, vectorize from .base import BaseAnalytic, IAnalytic @@ -176,7 +176,7 @@ def shift(self, amount: Real) -> Polynomial: if (i + j) % 2: value *= -1 newcoefs[j] += coef * value - return Polynomial(newcoefs, shift(self.domain, amount)) + return Polynomial(newcoefs, move(self.domain, amount)) @debug("shapepy.analytic.polynomial") def integrate(self, times: int = 1) -> Polynomial: diff --git a/src/shapepy/rbool.py b/src/shapepy/rbool.py deleted file mode 100644 index 4cbc89a8..00000000 --- a/src/shapepy/rbool.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Wraps the rbool library and add some useful functions for this package""" - -from typing import Any, Callable, Iterator, Type - -import rbool - -from .loggers import debug -from .scalar.reals import Real -from .tools import Is - -EmptyR1: Type = rbool.Empty -IntervalR1: Type = rbool.Interval -SingleR1: Type = rbool.SingleValue -SubSetR1: Type = rbool.SubSetR1 -DisjointR1: Type = rbool.Disjoint -WholeR1: Type = rbool.Whole -extract_knots: Callable[[Any], Iterator[Any]] = rbool.extract_knots -from_any: Callable[[Any], object] = rbool.from_any -shift = rbool.move -scale = rbool.scale -unite = rbool.unite -infimum = rbool.infimum -supremum = rbool.supremum - - -def create_single(knot: Real) -> SingleR1: - """Creates a Subset on real-line that contains only one value""" - if not Is.finite(knot): - raise ValueError(f"Invalid {knot}") - return SingleR1(knot) - - -def create_interval(knota: Real, knotb: Real) -> IntervalR1: - """Creates a closed interval [a, b] in the real line""" - if not Is.real(knota) or not Is.real(knotb): - raise TypeError(f"Invalid typos: {type(knota)}, {type(knotb)}") - if knotb <= knota: - raise ValueError(f"{knotb} <= {knota}") - return IntervalR1(knota, knotb) - - -@debug("shapepy.rbool") -def subset_length(subset: SubSetR1) -> Real: - """Computes the length of the subset - - Example - ------- - >>> subset_length([0, 1]) - 1 - >>> subset_length([-3, 2]) - 5 - >>> subset_length({}) - 0 - """ - subset = from_any(subset) - if Is.instance(subset, IntervalR1): - return subset[1] - subset[0] - if Is.instance(subset, DisjointR1): - return sum(map(subset_length, subset.intervals)) - return 0 diff --git a/src/shapepy/rbool/__init__.py b/src/shapepy/rbool/__init__.py new file mode 100644 index 00000000..23e74700 --- /dev/null +++ b/src/shapepy/rbool/__init__.py @@ -0,0 +1,26 @@ +""" +Init file that includes the most used classes and functions of the module +""" + +from .base import EmptyR1, Future, SubSetR1, WholeR1 +from .bool1d import contains, extract_knots, intersect, invert, unite +from .converter import from_any +from .singles import DisjointR1, IntervalR1, SingleR1, bigger, lower +from .tools import ( + create_interval, + create_single, + infimum, + maximum, + minimum, + subset_length, + supremum, +) +from .transform import move, scale + +Future.convert = from_any +Future.unite = unite +Future.intersect = intersect +Future.invert = invert +Future.contains = contains +Future.scale = scale +Future.move = move diff --git a/src/shapepy/rbool/base.py b/src/shapepy/rbool/base.py new file mode 100644 index 00000000..89892c62 --- /dev/null +++ b/src/shapepy/rbool/base.py @@ -0,0 +1,316 @@ +""" +Defines the basis structure to store subsets of real line. +The main purpose of this module is to be returned from analytics roots +or when concatenating piecewise curves + +Here we define 5 classes: +* EmptyR1 : Represents an empty set {} of the real line R1 +* WholeR1 : Represents the entire real line R1 +* SingleR1 : Represents a subset of R1 with only one finite element +* IntervalR1 : Represents a subset of R1 with continuous points +* DisjointR1 : Represents the union of some SingleR1 and IntervalR1 + +With them, we can verify if one subset contains another subset +It's possible to make the standard boolean operations, like union, +intersection, inversion, xor and so on +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Tuple, Union + +from ..scalar.reals import Math, Real +from ..tools import Is, NotExpectedError, To + + +class Future: + """ + Class that stores methods that are further defined. + They are overrided by other methods in __init__.py file + """ + + @staticmethod + def convert(obj: object) -> SubSetR1: + """ + Converts an object to a SubSetR1 instance. + + This function is overrided by a function defined + in the `converter.py` file + + Example + ------- + >>> type(convert("{}")) + + """ + raise NotExpectedError + + @staticmethod + def unite(*subsets: SubSetR1) -> SubSetR1: + """ + Computes the union of some SubSetR1 instances + + This function is overrided by a function defined + in the `shapepy.rbool.bool1d.py` file + """ + raise NotExpectedError + + @staticmethod + def intersect(*subsets: SubSetR1) -> SubSetR1: + """ + Computes the intersection of some SubSetR1 instances + + This function is overrided by a function defined + in the `shapepy.rbool.bool1d.py` file + """ + raise NotExpectedError + + @staticmethod + def invert(subset: SubSetR1) -> SubSetR1: + """ + Computes the inversion of a SubSetR1 instance + + This function is overrided by a function defined + in the `shapepy.rbool.bool1d.py` file + """ + raise NotExpectedError + + @staticmethod + def contains(subseta: SubSetR1, subsetb: SubSetR1) -> bool: + """ + Checks if the subsetb is contained by subseta + + This function is overrided by a function defined + in the `shapepy.rbool.bool1d.py` file + """ + raise NotExpectedError + + @staticmethod + def move(subset: SubSetR1, vector: Tuple[Real, Real]) -> SubSetR1: + """ + Moves the SubSetR1 instance by given vector + + This function is overrided by a function defined + in the `shapepy.rbool.transform.py` file + """ + raise NotExpectedError + + @staticmethod + def scale( + subset: SubSetR1, amount: Union[Real, Tuple[Real, Real]] + ) -> SubSetR1: + """ + Scales the SubSetR1 instance by given amount + + This function is overrided by a function defined + in the `shapepy.rbool.transform.py` file + """ + raise NotExpectedError + + +class SubSetR1(ABC): + """ + General class to be parent of some others. + + It represents an arbitrary subset of R1. + """ + + @abstractmethod + def __hash__(self): + raise NotImplementedError + + @abstractmethod + def __eq__(self, other): + raise NotImplementedError + + @abstractmethod + def __contains__(self, other): + raise NotImplementedError + + def move(self, amount: Real) -> SubSetR1: + """ + Translates the entire subset of R1 to the right by given amount + + new_set = {x + a for x in old_set} + + Parameters + ---------- + amount : Real + The quantity to be translated + + Return + ------ + SubSetR1 + The translated subset + + """ + return Future.move(self, amount) + + def scale(self, amount: Real) -> SubSetR1: + """ + Scales the entire subset of R1 to the right by given amount + + new_set = {a * x for x in old_set} + + Negative values are valid. + + Parameters + ---------- + amount : Real + The quantity to be scaled. Cannot be zero. + + Return + ------ + SubSetR1 + The scaled subset + """ + return Future.scale(self, amount) + + def __invert__(self): + return Future.invert(self) + + def __or__(self, other): + return Future.unite(self, Future.convert(other)) + + def __and__(self, other): + return Future.intersect(self, Future.convert(other)) + + def __ror__(self, other): + return self.__or__(Future.convert(other)) + + def __rand__(self, other): + return self.__and__(Future.convert(other)) + + def __xor__(self, other): + other = Future.convert(other) + return (self & (~other)) | (other & (~self)) + + def __sub__(self, other): + return self & (~Future.convert(other)) + + def __rsub__(self, other): + return (~self) & Future.convert(other) + + def __rxor__(self, other): + return self ^ Future.convert(other) + + def __repr__(self): + return self.__str__() + + def __ne__(self, other): + return not self == other + + +class EmptyR1(SubSetR1): + """ + EmptyR1 class is a singleton that represents an empty set + + It's equivalent to: EmptyR1 = {} + """ + + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + + def __contains__(self, other) -> bool: + if Is.real(other): + return False + return Future.convert(other) is self + + def move(self, amount: Real) -> SubSetR1: + To.finite(amount) # Checks if it's finite + return self + + def scale(self, amount: Real) -> SubSetR1: + To.finite(amount) # Checks if it's finite + return self + + def __invert__(self): + return WholeR1() + + def __and__(self, other): + Future.convert(other) + return self + + def __or__(self, other): + return Future.convert(other) + + def __rand__(self, other): + Future.convert(other) + return self + + def __ror__(self, other): + return Future.convert(other) + + def __str__(self): + return r"{}" + + def __repr__(self): + return "EmptyR1" + + def __eq__(self, other): + return self is Future.convert(other) + + def __hash__(self): + return 0 + + +class WholeR1(SubSetR1): + """ + WholeR1 class is a singleton that represents the entire real line + + It's equivalent to: WholeR1 = (-inf, +inf) + """ + + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + + def __contains__(self, other) -> bool: + if Is.real(other): + return True + Future.convert(other) + return True + + def move(self, amount: Real) -> SubSetR1: + To.finite(amount) # Checks if it's finite + return self + + def scale(self, amount: Real) -> SubSetR1: + To.finite(amount) # Checks if it's finite + return self + + def __invert__(self): + return EmptyR1() + + def __and__(self, other): + return Future.convert(other) + + def __or__(self, other): + Future.convert(other) + return self + + def __rand__(self, other): + return Future.convert(other) + + def __ror__(self, other): + Future.convert(other) + return self + + def __str__(self): + return "(" + str(Math.NEGINF) + ", " + str(Math.POSINF) + ")" + + def __repr__(self): + return "WholeR1" + + def __eq__(self, other): + return self is Future.convert(other) + + def __hash__(self): + return 1 diff --git a/src/shapepy/rbool/bool1d.py b/src/shapepy/rbool/bool1d.py new file mode 100644 index 00000000..37f0f39d --- /dev/null +++ b/src/shapepy/rbool/bool1d.py @@ -0,0 +1,222 @@ +""" +This file contains functions to perform boolean operation on 1D subsets +""" + +from numbers import Real +from typing import Callable, Iterable, List, Set, Union + +from ..tools import Is +from .base import EmptyR1, Future, SubSetR1, WholeR1 +from .singles import DisjointR1, IntervalR1, SingleR1, bigger, lower + + +def extract_knots(obj: SubSetR1) -> Iterable[Real]: + """ + Extract all the knots from the SubSetR1. + + If it's a SingleR1, gives the internal value + If it's a IntervalR1, gives the extremities + If it's a DisjointR1, use recursion + """ + if isinstance(obj, SingleR1): + yield obj.internal + if isinstance(obj, IntervalR1): + if Is.finite(obj[0]): + yield obj[0] + if Is.finite(obj[1]): + yield obj[1] + if isinstance(obj, DisjointR1): + for sub in obj: + yield from extract_knots(sub) + + +def general_doer( + subsets: Iterable[SubSetR1], function: Callable[[Real], bool] +) -> SubSetR1: + """ + Receives a group of SubSetR1 and makes the union, the intersection, + or the inversion depending on the given function. + + This is an internal function and should not be used careless + """ + subsets = tuple(map(Future.convert, subsets)) + if not all(isinstance(subset, SubSetR1) for subset in subsets): + raise TypeError + set_all_knots: Set[Real] = set() + for subset in subsets: + set_all_knots |= set(extract_knots(subset)) + all_knots: List[Real] = sorted(set_all_knots) + eval_knots: List[Real] = [0] * (2 * len(all_knots) + 1) + eval_knots[0] = all_knots[0] - 1 + eval_knots[-1] = all_knots[-1] + 1 + for i, knot in enumerate(all_knots): + eval_knots[2 * i + 1] = knot + for i, (knota, knotb) in enumerate(zip(all_knots, all_knots[1:])): + eval_knots[2 * i + 2] = (knota + knotb) / 2 + return general_subset(all_knots, map(function, eval_knots)) + + +def general_subset(knots: Iterable[Real], insides: Iterable[bool]) -> SubSetR1: + """ + Transforms the knots real values and the vector of insides into a SubSetR1 + + Basically it gets all the knots from a group of subsets: + * internal value of SingleR1 + * start and end of an IntervalR1 + * knots of the internals for case DisjointR1 + and then mark the middle points from the + + Then, this function walks from left to right, deciding which SingleR1 + or IntervalR1 should be gotten to make the return SubSetR1. + + This is an internal function and should not be used careless + """ + insides = tuple(insides) + knots = tuple(knots) + if len(insides) != 2 * len(knots) + 1: + raise ValueError(f"Invalid: knots = {knots}, insides = {insides}") + if all(insides): + return WholeR1() + if not any(insides): + return EmptyR1() + + items: List[Union[SingleR1, IntervalR1]] = [] + start: Union[None, Real] = None + close: bool = False + for i, knot in enumerate(knots): + left = insides[2 * i] + midd = insides[2 * i + 1] + righ = insides[2 * i + 2] + if left == midd == righ: + continue + if not left and not righ: + items.append(SingleR1(knot)) + continue + if left: # Finish interval + if start is None: + newinterv = lower(knot, midd) + else: + newinterv = IntervalR1(start, knot, close, midd) + items.append(newinterv) + start = None + if righ: + start = knot + close = midd + if start is not None: + newinterv = bigger(start, close) + items.append(newinterv) + + if len(items) == 1: + return items[0] + + return DisjointR1(items) + + +def unite(*subsets: SubSetR1) -> SubSetR1: + """ + Unites a group of subsets + + Parameters + ---------- + subsets : Iterable[SubSetR1] + The subsets of R1 to be united + + Return + ------ + SubSetR1 + The result of the union of the given subsets + + Example + ------- + >>> unite({10}, [-10, 5]) + [-10, 5] U {10} + >>> unite((-10, 5), 5) + (-10, 5] + >>> unite(("-inf", 10), (-10, "inf")) + (-inf, +inf) + """ + + def or_func(x): + return any(x in sub for sub in subsets) + + return general_doer(subsets, or_func) + + +def intersect(*subsets: SubSetR1) -> SubSetR1: + """ + Intersects a group of subsets + + Parameters + ---------- + subsets : Iterable[SubSetR1] + The subsets of R1 to be intersected + + Return + ------ + SubSetR1 + The result of the union of the given subsets + + Example + ------- + >>> intersect({-10}, [-10, 5]) + {-10} + >>> intersect((-10, 5), [3, 10]) + [3, 5) + >>> intersect((-10, 0), (0, 10)) + {} + """ + + def and_func(x): + return all(x in sub for sub in subsets) + + return general_doer(subsets, and_func) + + +def invert(subset: SubSetR1) -> SubSetR1: + """ + Computes the complementar / inversion of the given subset + + Parameters + ---------- + subset : SubSetR1 + The subset of R1 to be inverted + + Return + ------ + SubSetR1 + The inverted subset + + Example + ------- + >>> invert({-10}) + (-inf, -10) U (-10, +inf) + >>> invert((-10, 10)) + (-inf, -10] U [10, +inf) + >>> invert(("-inf", 0)) + [0, +inf) + """ + + def inv_func(x): + return x not in subset + + return general_doer((subset,), inv_func) + + +def contains(subseta: SubSetR1, subsetb: SubSetR1) -> bool: + """ + Tells if B in A + + Parameters + ---------- + subseta : SubSetR1 + The A subset + subsetb : SubSetR1 + The B subset + + Return + ------ + bool + The result + + """ + return Future.convert(subsetb) in Future.convert(subseta) diff --git a/src/shapepy/rbool/converter.py b/src/shapepy/rbool/converter.py new file mode 100644 index 00000000..d075f1d4 --- /dev/null +++ b/src/shapepy/rbool/converter.py @@ -0,0 +1,206 @@ +""" +Module that contains functions to convert some basic types into Bool1D types + +The easier example is from string: +* "{}" represents a empty set, so returns the EmptyR1 instance +* "(-inf, +inf)" represents the entire real line, returns WholeR1 instance +""" + +from numbers import Real +from typing import Any, Dict, List, Set, Tuple + +from ..scalar.reals import Math +from ..tools import NotExpectedError, To +from .base import EmptyR1, Future, SubSetR1, WholeR1 +from .singles import IntervalR1, SingleR1, bigger, lower + + +# pylint: disable=too-many-return-statements +def from_any(obj: Any) -> SubSetR1: + """ + Converts an arbitrary object into a SubSetR1 instance. + If it's already a SubSetR1 instance, returns the object + + Example + ------- + >>> Future.convert("{}") + {} + >>> Future.convert("(-inf, +inf)") + (-inf, +inf) + """ + if isinstance(obj, SubSetR1): + return obj + if isinstance(obj, Real): + number = To.finite(obj) + return SingleR1(number) + if isinstance(obj, str): + return from_str(obj) + if isinstance(obj, dict): + return from_dict(obj) + if isinstance(obj, set): + return from_set(obj) + if isinstance(obj, tuple): + return from_tuple(obj) + if isinstance(obj, list): + return from_list(obj) + raise NotExpectedError(f"Received object {type(obj)} = {obj}") + + +def from_str(string: str) -> SubSetR1: + """ + Converts a string into a SubSetR1 instance. + + Example + ------- + >>> from_str("{}") # EmptyR1 + {} + >>> from_str("(-inf, +inf)") # WholeR1 + (-inf, +inf) + >>> from_str("{10}") # SingleR1 + {10} + >>> from_str("[-10, 0] U {5, 10}") # DisjointR1 + [-10, 0] U {5, 10} + """ + string = string.strip() + if "U" in string: + return Future.unite(*map(from_str, string.split("U"))) + if string[0] == "{" and string[-1] == "}": + result = EmptyR1() + for substr in string[1:-1].split(","): + if not substr: # EmptyR1 string + continue + finite = To.finite(substr) + result |= SingleR1(finite) + return result + if string[0] in "([" and string[-1] in ")]": + stastr, endstr = string[1:-1].split(",") + start = To.real(stastr) + end = To.real(endstr) + if start == Math.NEGINF and end == Math.POSINF: + return WholeR1() + left = string[0] == "[" + right = string[-1] == "]" + return IntervalR1(start, end, left, right) + raise ValueError(f"Cannot parse '{string}' into a SubSetR1 instance") + + +def from_dict(dic: Dict) -> SubSetR1: + """ + Converts a dictonary into a SubSetR1 instance + + Only accepts an empty dict, since it's the standard type of {}: + + Example + ------- + >>> variable = {} + >>> type(variable) + + >>> subset = from_dict(variable) + >>> subset + {} + >>> type(subset) + + """ + if not isinstance(dic, dict): + raise TypeError + result = EmptyR1() + if len(dic) != 0: + raise NotExpectedError + return result + + +def from_set(items: Set[object]) -> SubSetR1: + """ + Converts a set into a SubSetR1 instance + + Example + ------- + >>> variable = {-10, 5} + >>> type(variable) + + >>> subset = from_set(variable) + >>> subset + {-10, 5} + >>> type(subset) + + """ + if not isinstance(items, set): + raise TypeError + result = EmptyR1() + for item in items: + result |= To.finite(item) + return result + + +def from_tuple(pair: Tuple[object]) -> SubSetR1: + """ + Converts a tuple of two values into a SubSetR1 instance + + It's the standard open interval, or the WholeR1 + + Example + ------- + >>> variable = (-10, 10) + >>> type(variable) + + >>> subset = from_tuple(variable) + >>> subset + (-10, 10) + >>> type(subset) + + >>> variable = ("-inf", "inf") + >>> subset = from_tuple(variable) + >>> type(subset) + + """ + if not isinstance(pair, tuple): + raise TypeError + if len(pair) != 2: + raise ValueError + sta = To.real(pair[0]) + end = To.real(pair[1]) + if sta == Math.NEGINF and end == Math.POSINF: + return WholeR1() + if sta == Math.NEGINF: + return lower(end, False) + if end == Math.POSINF: + return bigger(sta, False) + return IntervalR1(sta, end, False, False) + + +def from_list(pair: List[object]) -> SubSetR1: + """ + Converts a list of two values into a SubSetR1 instance + + It's the standard closed interval, or the WholeR1 + + Example + ------- + >>> variable = [-10, 10] + >>> type(variable) + + >>> subset = from_list(variable) + >>> subset + [-10, 10] + >>> type(subset) + + >>> variable = ["-inf", "inf"] + >>> subset = from_list(variable) + >>> subset + (-inf, +inf) + >>> type(subset) + + """ + if not isinstance(pair, list): + raise TypeError + if len(pair) != 2: + raise ValueError + sta = To.real(pair[0]) + end = To.real(pair[1]) + if sta == Math.NEGINF and end == Math.POSINF: + return WholeR1() + if sta == Math.NEGINF: + return lower(end, True) + if end == Math.POSINF: + return bigger(sta, True) + return IntervalR1(sta, end, True, True) diff --git a/src/shapepy/rbool/singles.py b/src/shapepy/rbool/singles.py new file mode 100644 index 00000000..d0d29c0f --- /dev/null +++ b/src/shapepy/rbool/singles.py @@ -0,0 +1,364 @@ +""" +Defines some basic types of boolean 1D +""" + +from __future__ import annotations + +from numbers import Real +from typing import Iterable, List, Set, Tuple, Union + +from ..scalar.reals import Math +from ..tools import Is, To +from .base import EmptyR1, Future, SubSetR1 + + +def lower(number: Real, closed: bool = True) -> IntervalR1: + """ + Gives the interval such points are lower than given number + + Parameters + ---------- + number : Real + The finite number + closed : bool, default = True + If the interval is closed on the right end + + Return + ------ + IntervalR1 + The IntervalR1 such is lower than the given `number` + + Example + ------- + >>> lower(0) + (-inf, 0] + >>> lower(0, False) + (-inf, 0) + >>> lower(10, True) + (-inf, 10] + """ + return IntervalR1(Math.NEGINF, To.finite(number), False, closed) + + +def bigger(finite: Real, closed: bool = True) -> IntervalR1: + """ + Gives the interval such points are bigger than given number + + Parameters + ---------- + number : Real + The finite number + closed : bool, default = True + If the interval is closed on the left end + + Return + ------ + IntervalR1 + The IntervalR1 such is bigger than the given `number` + + Example + ------- + >>> bigger(0) + [0, +inf) + >>> bigger(0, False) + (0, +inf) + >>> bigger(10, True) + [10, +inf) + """ + return IntervalR1(finite, Math.POSINF, closed, False) + + +def middle_point(subset: Union[SingleR1, IntervalR1]) -> Real: + """Gets the middle value of the subset + + Example + ------- + >>> middle_point(SingleR1(1.0)) + 1.0 + >>> middle_point(lower(1.0)) + 0.0 + >>> middle_point(bigger(1.0)) + 2.0 + >>> middle_point(IntervalR1(0, 1)) + 0.5 + """ + if isinstance(subset, SingleR1): + return subset.internal + if subset[0] == Math.NEGINF: + return subset[1] - 1 + if subset[1] == Math.POSINF: + return subset[0] + 1 + return (subset[0] + subset[1]) / 2 + + +class SingleR1(SubSetR1): + """ + SingleR1 stores only one value, being a subset of the real line + + Only finite values are acceptable + """ + + def __init__(self, value: Real): + self.__internal = To.finite(value) + + @property + def internal(self) -> Real: + """ + Gives the internal real value of the SingleR1 + + :getter: Returns the internal value of the SubSetR1 + :type: Real + """ + return self.__internal + + def __str__(self) -> str: + return "{" + str(self.__internal) + "}" + + def __repr__(self): + return f"SingleR1({self.__internal})" + + def __contains__(self, other): + if Is.infinity(other): + return False + other = Future.convert(other) + return isinstance(other, EmptyR1) or self == other + + def __eq__(self, other): + other = Future.convert(other) + return ( + isinstance(other, self.__class__) + and self.internal == other.internal + ) + + def __invert__(self): + return DisjointR1( + [ + lower(self.internal, False), + bigger(self.internal, False), + ] + ) + + def __and__(self, other: SubSetR1): + other = Future.convert(other) + return self if other.__contains__(self) else EmptyR1() + + def __hash__(self): + return hash(self.internal) + + +class IntervalR1(SubSetR1): + """ + IntervalR1 stores a continuous set of points on R1 + + + """ + + def __init__( + self, start: Real, end: Real, left: bool = True, right: bool = True + ): + start = To.real(start) + end = To.real(end) + if end <= start: + raise ValueError( + "Received interval [{start}, {end}], but {end} <= {start}" + ) + if Is.infinity(start) and Is.infinity(end): + raise ValueError("Received interval (-inf, +inf), use WholeR1") + if start == Math.NEGINF: + left = False + if end == Math.POSINF: + right = False + self.__start = start + self.__end = end + self.__left = left + self.__right = right + + # pylint: disable=too-many-return-statements + def __contains__(self, other): + if Is.infinity(other): + return other in (self[0], self[1]) + other = Future.convert(other) + if isinstance(other, SingleR1): + other = other.internal + if other < self[0] or self[1] < other: + return False + if self[0] < other < self[1]: + return True + return self.closed_left if self[0] == other else self.closed_right + if isinstance(other, IntervalR1): + if other[0] < self[0] or self[1] < other[1]: + return False + if self[0] < other[0] and other[1] < self[1]: + return True + if self[0] == other[0] and (self.closed_left ^ other.closed_left): + return False + if self[1] == other[1] and ( + self.closed_right ^ other.closed_right + ): + return False + return True + if isinstance(other, DisjointR1): + return all(map(self.__contains__, other)) + return isinstance(other, EmptyR1) + + def __invert__(self): + if self[0] == Math.NEGINF: + return bigger(self[1], not self.closed_right) + if self[1] == Math.POSINF: + return lower(self[0], not self.closed_left) + return DisjointR1( + [ + lower(self[0], not self.closed_left), + bigger(self[1], not self.closed_right), + ] + ) + + def __getitem__(self, index): + return self.__end if index else self.__start + + def __eq__(self, other): + other = Future.convert(other) + return ( + isinstance(other, IntervalR1) + and self[0] == other[0] + and self[1] == other[1] + and self.closed_left == other.closed_left + and self.closed_right == other.closed_right + ) + + def __str__(self): + msg = "[" if self.closed_left else "(" + msg += str(self[0]) + ", " + str(self[1]) + msg += "]" if self.closed_right else ")" + return msg + + @property + def closed_left(self) -> bool: + """ + Tells if the interval is closed on the left side + + :getter: Returns a boolean that tells if interval is bounded on bot + :type: bool + """ + return self.__left + + @property + def closed_right(self) -> bool: + """ + Tells if the interval is closed on the right side + + :getter: Returns a boolean that tells if interval is bounded on top + :type: bool + """ + return self.__right + + def __hash__(self): + return hash((self[0], self[1])) + + +class DisjointR1(SubSetR1): + """ + Stores the union of SingleR1 and IntervalR1 which are not connected + + The direct constructor should not be used. + This object should be constructed by the standard boolean operations + of the some SingleR1 and IntervalR1 + """ + + def __init__(self, items: Iterable[Union[SingleR1, IntervalR1]]): + items = tuple(items) + if len(items) < 2: + raise ValueError("Less than 2 items!") + + knots: Set[Real] = set() + singles: List[SingleR1] = [] + intervs: List[IntervalR1] = [] + for item in items: + if isinstance(item, SingleR1): + singles.append(item) + knots.add(item.internal) + elif isinstance(item, IntervalR1): + intervs.append(item) + if isinstance(item[0], Real): + knots.add(item[0]) + if isinstance(item[1], Real): + knots.add(item[1]) + else: + raise TypeError("Received wrong type!") + + weights = tuple(single.internal for single in singles) + self.__singles = tuple( + s for _, s in sorted(zip(weights, singles), key=lambda x: x[0]) + ) + weights = tuple((interv[0] + interv[1]) / 2 for interv in intervs) + self.__intervs = tuple( + i for _, i in sorted(zip(weights, intervs), key=lambda x: x[0]) + ) + + @property + def singles(self) -> Tuple[SingleR1, ...]: + """ + Gives all the isolated nodes that are inside the DisjointR1 + + :getter: Returns all the isolated points + :type: Tuple[SingleR1, ...] + """ + return self.__singles + + @property + def intervals(self) -> Tuple[IntervalR1, ...]: + """ + Gives all the non-connected intervals that are inside the DisjointR1 + + :getter: Returns all the non-connected intervals + :type: Tuple[SingleR1, ...] + """ + return self.__intervs + + def __iter__(self): + yield from self.__singles + yield from self.__intervs + + def __contains__(self, other): + if Is.infinity(other): + return any(other in sub for sub in self) + other = Future.convert(other) + if isinstance(other, DisjointR1): + return all(sub in self for sub in other) + return any(other in sub for sub in self) + + # pylint: disable=too-many-branches, too-many-statements + def __str__(self) -> str: + + def divide_disjoint_for_print(disjoint: DisjointR1) -> Iterable[str]: + """Divides the DisjointR1 object into small pieces""" + stack: List[SingleR1] = [] + items = list(disjoint.singles) + list(disjoint.intervals) + items = sorted(items, key=middle_point) + for item in items: + if not isinstance(item, IntervalR1): + stack.append(item) + else: + if len(stack) != 0: + yield "{" + ", ".join( + str(s.internal) for s in stack + ) + "}" + stack = [] + yield str(item) + if len(stack) != 0: + yield "{" + ", ".join(str(s.internal) for s in stack) + "}" + + return " U ".join(divide_disjoint_for_print(self)) + + def __eq__(self, other): + other = Future.convert(other) + if not isinstance(other, DisjointR1): + return False + if len(self.singles) != len(other.singles) or len( + self.intervals + ) != len(other.intervals): + return False + return all(subs == subo for subs, subo in zip(self, other)) + + def __hash__(self): + return hash(tuple(map(hash, self))) diff --git a/src/shapepy/rbool/tools.py b/src/shapepy/rbool/tools.py new file mode 100644 index 00000000..df46dcc4 --- /dev/null +++ b/src/shapepy/rbool/tools.py @@ -0,0 +1,237 @@ +"""Defines useful functions""" + +from numbers import Real +from typing import Union + +from ..loggers import debug +from ..scalar.reals import Math +from ..tools import Is, NotExpectedError +from .base import EmptyR1, Future, SubSetR1, WholeR1 +from .singles import DisjointR1, IntervalR1, SingleR1 + + +def infimum(subset: SubSetR1) -> Union[Real, None]: + """ + Computes the infimum of the SubSetR1 + + Parameters + ---------- + subset: SubSetR1 + The subset to get the infimum + + Return + ------ + Real | None + The infimum value, or None if receives EmptyR1 + + Example + ------- + >>> infimum("{}") # EmptyR1 + None + >>> infimum("(-inf, +inf)") # WholeR1 + -inf + >>> infimum("{-10}") # SingleR1 + -10 + >>> infimum("[-10, 10]") # IntervalR1 + -10 + >>> infimum("(-10, 10)") # IntervalR1 + -10 + >>> infimum("{0, 10, 20}") # DisjointR1 + 0 + """ + subset = Future.convert(subset) + if isinstance(subset, EmptyR1): + return None + if isinstance(subset, WholeR1): + return Math.NEGINF + if isinstance(subset, SingleR1): + return subset.internal + if isinstance(subset, IntervalR1): + return subset[0] + if isinstance(subset, DisjointR1): + return min(map(infimum, subset)) + raise NotExpectedError(f"Received {type(subset)}: {subset}") + + +def minimum(subset: SubSetR1) -> Union[Real, None]: + """ + Computes the minimum of the SubSetR1 + + Parameters + ---------- + subset: SubSetR1 + The subset to get the minimum + + Return + ------ + Real | None + The minimum value or None + + Example + ------- + >>> minimum("{}") # EmptyR1 + None + >>> minimum("(-inf, +inf)") # WholeR1 + None + >>> minimum("{-10}") # SingleR1 + -10 + >>> minimum("[-10, 10]") # IntervalR1 + -10 + >>> minimum("(-10, 10)") # IntervalR1 + None + >>> minimum("{0, 10, 20}") # DisjointR1 + 0 + """ + subset = Future.convert(subset) + if isinstance(subset, (EmptyR1, WholeR1)): + return None + if isinstance(subset, SingleR1): + return subset.internal + if isinstance(subset, IntervalR1): + return ( + subset[0] + if (Is.finite(subset[0]) and subset.closed_left) + else None + ) + if isinstance(subset, DisjointR1): + infval = Math.POSINF + global_minval = Math.POSINF + for sub in subset: + infval = min(infval, infimum(sub)) + minval = minimum(sub) + if minval is not None: + global_minval = min(global_minval, minval) + return infval if (global_minval == infval) else None + raise NotExpectedError(f"Received {type(subset)}: {subset}") + + +def maximum(subset: SubSetR1) -> Union[Real, None]: + """ + Computes the maximum of the SubSetR1 + + Parameters + ---------- + subset: SubSetR1 + The subset to get the maximum + + Return + ------ + Real | None + The maximum value of the subset + + Example + ------- + >>> maximum("{}") # EmptyR1 + None + >>> maximum("(-inf, +inf)") # WholeR1 + None + >>> maximum("{-10}") # SingleR1 + -10 + >>> maximum("[-10, 10]") # IntervalR1 + 10 + >>> maximum("(-10, 10)") # IntervalR1 + None + >>> maximum("{0, 10, 20}") # DisjointR1 + 20 + """ + subset = Future.convert(subset) + if isinstance(subset, (EmptyR1, WholeR1)): + return None + if isinstance(subset, SingleR1): + return subset.internal + if isinstance(subset, IntervalR1): + return ( + subset[1] + if (Is.finite(subset[1]) and subset.closed_right) + else None + ) + if isinstance(subset, DisjointR1): + supval = Math.NEGINF + global_maxval = Math.NEGINF + for sub in subset: + supval = max(supval, supremum(sub)) + maxval = maximum(sub) + if maxval is not None: + global_maxval = max(global_maxval, maxval) + return maxval if (global_maxval == supval) else None + raise NotExpectedError(f"Received {type(subset)}: {subset}") + + +def supremum(subset: SubSetR1) -> Union[Real, None]: + """ + Computes the supremum of the SubSetR1 + + Parameters + ---------- + subset: SubSetR1 + The subset to get the supremum + + Return + ------ + Real | None + The supremum value, or None if receives EmptyR1 + + Example + ------- + >>> supremum("{}") # EmptyR1 + None + >>> supremum("(-inf, +inf)") # WholeR1 + +inf + >>> supremum("{-10}") # SingleR1 + -10 + >>> supremum("[-10, 10]") # IntervalR1 + 10 + >>> supremum("(-10, 10)") # IntervalR1 + 10 + >>> supremum("{0, 10, 20}") # DisjointR1 + 20 + """ + subset = Future.convert(subset) + if isinstance(subset, EmptyR1): + return None + if isinstance(subset, WholeR1): + return Math.POSINF + if isinstance(subset, SingleR1): + return subset.internal + if isinstance(subset, IntervalR1): + return subset[1] + if isinstance(subset, DisjointR1): + return max(map(supremum, subset)) + raise NotExpectedError(f"Received {type(subset)}: {subset}") + + +def create_single(knot: Real) -> SingleR1: + """Creates a Subset on real-line that contains only one value""" + if not Is.finite(knot): + raise ValueError(f"Invalid {knot}") + return SingleR1(knot) + + +def create_interval(knota: Real, knotb: Real) -> IntervalR1: + """Creates a closed interval [a, b] in the real line""" + if not Is.real(knota) or not Is.real(knotb): + raise TypeError(f"Invalid typos: {type(knota)}, {type(knotb)}") + if knotb <= knota: + raise ValueError(f"{knotb} <= {knota}") + return IntervalR1(knota, knotb) + + +@debug("shapepy.rbool") +def subset_length(subset: SubSetR1) -> Real: + """Computes the length of the subset + + Example + ------- + >>> subset_length([0, 1]) + 1 + >>> subset_length([-3, 2]) + 5 + >>> subset_length({}) + 0 + """ + subset = Future.convert(subset) + if Is.instance(subset, IntervalR1): + return subset[1] - subset[0] + if Is.instance(subset, DisjointR1): + return sum(map(subset_length, subset.intervals)) + return 0 diff --git a/src/shapepy/rbool/transform.py b/src/shapepy/rbool/transform.py new file mode 100644 index 00000000..0300325c --- /dev/null +++ b/src/shapepy/rbool/transform.py @@ -0,0 +1,91 @@ +""" +Defines the methods used to do the usual transformations, +like translating, scaling and rotating the SubSetR2 instances on the plane +""" + +from numbers import Real + +from ..tools import NotExpectedError, To +from .base import EmptyR1, Future, SubSetR1, WholeR1 +from .singles import DisjointR1, IntervalR1, SingleR1 + + +def move(subset: SubSetR1, amount: Real) -> SubSetR1: + """ + Translates the subset on the the real line by given amount + + Parameters + ---------- + subset: SubSetR1 + The subset to be displaced + amount: Real + The quantity to be displaced + + Return + ------ + SubSetR1 + The translated subset, of the same type + """ + subset = Future.convert(subset) + amount = To.finite(amount) + if isinstance(subset, (EmptyR1, WholeR1)): + return subset + if isinstance(subset, SingleR1): + return SingleR1(subset.internal + amount) + if isinstance(subset, IntervalR1): + newlef = subset[0] + amount + newrig = subset[1] + amount + return IntervalR1( + newlef, newrig, subset.closed_left, subset.closed_right + ) + if isinstance(subset, DisjointR1): + amount = To.finite(amount) + newiterable = (move(sub, amount) for sub in subset) + return DisjointR1(newiterable) + raise NotExpectedError(f"Missing typo? {type(subset)}") + + +def scale(subset: SubSetR1, amount: Real) -> SubSetR1: + """ + Scales the subset on the real line by given amount. + + Parameters + ---------- + subset: SubSetR1 + The subset to be scaled + amount: Real + The amount to be scaled + + Return + ------ + SubSetR1 + The scaled subset, of the same type + + Example + ------- + >>> subset = [-2, 3] + >>> scale(subset, 3) + [-6, 9] + """ + subset = Future.convert(subset) + amount = To.finite(amount) + if amount == 0: + raise ValueError + if isinstance(subset, (EmptyR1, WholeR1)): + return subset + if isinstance(subset, SingleR1): + return SingleR1(subset.internal * amount) + if isinstance(subset, IntervalR1): + newlef = subset[0] * amount + newrig = subset[1] * amount + clolef = subset.closed_left + clorig = subset.closed_right + if amount < 0: + newlef, newrig = newrig, newlef + clolef, clorig = clorig, clolef + return IntervalR1(newlef, newrig, clolef, clorig) + if isinstance(subset, DisjointR1): + amount = To.finite(amount) + newiterable = (scale(sub, amount) for sub in subset) + return DisjointR1(newiterable) + raise NotExpectedError(f"Missing typo? {type(subset)}") diff --git a/tests/rbool/__init__.py b/tests/rbool/__init__.py new file mode 100644 index 00000000..04ddc643 --- /dev/null +++ b/tests/rbool/__init__.py @@ -0,0 +1,3 @@ +import sys + +sys.path.append("./src") diff --git a/tests/rbool/test_boolean.py b/tests/rbool/test_boolean.py new file mode 100644 index 00000000..425c27d4 --- /dev/null +++ b/tests/rbool/test_boolean.py @@ -0,0 +1,230 @@ +import pytest + +from shapepy.rbool import ( + EmptyR1, + IntervalR1, + SingleR1, + WholeR1, + bigger, + from_any, + lower, +) + + +@pytest.mark.order(16) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + "tests/rbool/test_compare.py::test_all", + "tests/rbool/test_contains.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +class TestInversion: + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_singletion(self): + empty = EmptyR1() + whole = WholeR1() + + assert ~empty == whole + assert ~whole == empty + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single(self): + assert ~SingleR1(0) == "(-inf, 0) U (0, inf)" + assert ~SingleR1(-10) == "(-inf, -10) U (-10, inf)" + assert ~SingleR1(+10) == "(-inf, 10) U (10, inf)" + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_interval(self): + + assert ~lower(0, True) == bigger(0, False) + assert ~lower(0, False) == bigger(0, True) + assert ~bigger(0, True) == lower(0, False) + assert ~bigger(0, False) == lower(0, True) + + assert ~from_any("[-10, 10]") == "(-inf, -10) U (10, inf)" + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_disjoint(self): + assert ~from_any("(-inf, -10) U (10, inf)") == "[-10, 10]" + + assert ~from_any("(-inf, 0) U (0, inf)") == {0} + assert ~from_any("(-inf, -10) U (-10, inf)") == {-10} + assert ~from_any("(-inf, +10) U (+10, inf)") == {10} + + assert ~from_any("(-inf, -10) U (-10, 10) U (10, inf)") == {-10, 10} + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency( + depends=[ + "test_begin", + "TestInversion::test_singletion", + "TestInversion::test_single", + "TestInversion::test_interval", + "TestInversion::test_disjoint", + ] + ) + def test_all(self): + pass + + +class TestAndOr: + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_singleton(self): + empty = EmptyR1() + whole = WholeR1() + + assert empty & empty == empty + assert whole & empty == empty + assert empty & whole == empty + assert whole & whole == whole + + assert empty | empty == empty + assert whole | empty == whole + assert empty | whole == whole + assert whole | whole == whole + + assert empty - empty == empty + assert whole - empty == whole + assert empty - whole == empty + assert whole - whole == empty + + assert empty ^ empty == empty + assert whole ^ empty == whole + assert empty ^ whole == whole + assert whole ^ whole == empty + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single_singleton(self): + empty = EmptyR1() + whole = WholeR1() + + for value in (-10, 0, 10): + single = SingleR1(value) + assert single & empty == empty + assert single & whole == single + assert empty & single == empty + assert whole & single == single + + assert single | empty == single + assert single | whole == whole + assert empty | single == single + assert whole | single == whole + + assert single - empty == single + assert single - whole == empty + assert empty - single == empty + assert whole - single == ~single + + assert single ^ empty == single + assert single ^ whole == ~single + assert empty ^ single == single + assert whole ^ single == ~single + + single = {10} + assert single & empty == empty + assert single & whole == single + assert empty & single == empty + assert whole & single == single + + assert single | empty == single + assert single | whole == whole + assert empty | single == single + assert whole | single == whole + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single_single(self): + empty = EmptyR1() + + values = (-10, 0, 10) + singles = tuple(map(SingleR1, values)) + for i, (vali, singi) in enumerate(zip(values, singles)): + for j, (valj, singj) in enumerate(zip(values, singles)): + if i == j: + assert singi | singj == singi + assert singi & singj == singi + assert singi - singj == empty + assert singi ^ singj == empty + + assert singi | {valj} == singi + assert singi & {valj} == singi + assert singi - {valj} == empty + assert singi ^ {valj} == empty + + assert {vali} | singj == singi + assert {vali} & singj == singi + assert {vali} - singj == empty + assert {vali} ^ singj == empty + else: + assert singi | singj == {vali, valj} + assert singi & singj == empty + assert singi - singj == singi + assert singi ^ singj == {vali, valj} + + assert singi | {valj} == {vali, valj} + assert singi & {valj} == empty + assert singi - {valj} == singi + assert singi ^ {valj} == {vali, valj} + + assert {vali} | singj == {vali, valj} + assert {vali} & singj == empty + assert {vali} - singj == singi + assert {vali} ^ singj == {vali, valj} + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_interval_contains_disjoint(self): + string = "{-30, -25} U [-20, 20] U {25, 30, 40}" + disjoint = from_any(string) + assert disjoint in IntervalR1(-50, 50) + + @pytest.mark.order(16) + @pytest.mark.timeout(1) + @pytest.mark.dependency( + depends=[ + "TestAndOr::test_singleton", + "TestAndOr::test_single_singleton", + "TestAndOr::test_single_single", + "TestAndOr::test_interval_contains_disjoint", + ] + ) + def test_all(self): + pass + + +@pytest.mark.order(16) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "TestInversion::test_all", + "TestAndOr::test_all", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_build.py b/tests/rbool/test_build.py new file mode 100644 index 00000000..d4375dba --- /dev/null +++ b/tests/rbool/test_build.py @@ -0,0 +1,94 @@ +import pytest + +from shapepy.rbool import DisjointR1, EmptyR1, IntervalR1, SingleR1, WholeR1 + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency() +def test_begin(): + pass + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_empty(): + empty1 = EmptyR1() + empty2 = EmptyR1() + assert id(empty1) == id(empty2) + assert empty1 is empty2 + + hash(empty1) + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_whole(): + whole1 = WholeR1() + whole2 = WholeR1() + assert id(whole1) == id(whole2) + assert whole1 is whole2 + + hash(whole1) + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_single(): + SingleR1(-10) + SingleR1(0) + SingleR1(10) + + SingleR1(-10.0) + SingleR1(0.0) + SingleR1(10.0) + + with pytest.raises(ValueError): + SingleR1(float("-inf")) + with pytest.raises(ValueError): + SingleR1(float("inf")) + + hash(SingleR1(10)) + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_interval(): + IntervalR1(-10, 10) + IntervalR1(float("-inf"), 10) + IntervalR1(-10, float("inf")) + with pytest.raises(ValueError): + IntervalR1(float("-inf"), float("inf")) + + hash(IntervalR1(-10, 10)) + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_disjoint(): + interval = IntervalR1(-10, 10) + single = SingleR1(-20) + disjoint = DisjointR1([single, interval]) + + hash(disjoint) + + +@pytest.mark.order(12) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_empty", + "test_whole", + "test_single", + "test_interval", + "test_disjoint", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_compare.py b/tests/rbool/test_compare.py new file mode 100644 index 00000000..d4d95826 --- /dev/null +++ b/tests/rbool/test_compare.py @@ -0,0 +1,172 @@ +import pytest + +from shapepy.rbool import EmptyR1, IntervalR1, SingleR1, WholeR1 +from shapepy.scalar.reals import Math + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_empty(): + empty = EmptyR1() + + assert empty == empty + assert empty == {} + assert empty == r"{}" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_whole(): + whole = WholeR1() + + assert whole == whole + assert whole == (float("-inf"), float("inf")) + assert whole == (float("-inf"), Math.POSINF) + assert whole == (Math.NEGINF, float("inf")) + assert whole == (Math.NEGINF, Math.POSINF) + assert whole == [float("-inf"), float("inf")] + assert whole == [float("-inf"), Math.POSINF] + assert whole == [Math.NEGINF, float("inf")] + assert whole == [Math.NEGINF, Math.POSINF] + assert whole == r"(-inf, inf)" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_empty", "test_whole"]) +def test_singletons(): + empty = EmptyR1() + whole = WholeR1() + + assert empty == empty + assert empty != whole + assert whole != empty + assert whole == whole + + assert not empty != empty + assert not empty == whole + assert not whole == empty + assert not whole != whole + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_singletons"]) +def test_singles(): + empty = EmptyR1() + whole = WholeR1() + + for value in (-10, 0, 10): + single = SingleR1(value) + assert single == single + assert single == value + assert single == {value} + assert single == "{" + str(value) + "}" + + assert single != empty + assert empty != single + assert single != whole + assert whole != single + + assert not single == empty + assert not empty == single + assert not single == whole + assert not whole == single + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_singletons", "test_singles"]) +def test_intervals(): + empty = EmptyR1() + whole = WholeR1() + + aval, bval = -10, 10 + + n2a = IntervalR1(Math.NEGINF, aval) + n2b = IntervalR1(Math.NEGINF, bval) + a2b = IntervalR1(aval, bval) + a2p = IntervalR1(aval, Math.POSINF) + b2p = IntervalR1(bval, Math.POSINF) + + intervals = (n2a, n2b, a2b, a2p, b2p) + for i, intvi in enumerate(intervals): + for j, intvj in enumerate(intervals): + if i == j: + assert intvi == intvj + else: + assert intvi != intvj + + for interv in intervals: + assert empty != interv + assert whole != interv + assert interv != empty + assert interv != whole + + assert not empty == interv + assert not whole == interv + assert not interv == empty + assert not interv == whole + + closed_pairs = [ + (-50, 50), + (-50, -20), + (20, 50), + (Math.NEGINF, -20), + (Math.NEGINF, 0), + (Math.NEGINF, 20), + (-20, Math.POSINF), + (0, Math.POSINF), + (20, Math.POSINF), + ] + for sta, end in closed_pairs: + interv = IntervalR1(sta, end, True, True) + assert interv == [sta, end] + assert [sta, end] == interv + + open_pairs = [ + (-50, 50), + (-50, -20), + (20, 50), + (Math.NEGINF, -20), + (Math.NEGINF, 0), + (Math.NEGINF, 20), + (-20, Math.POSINF), + (0, Math.POSINF), + (20, Math.POSINF), + ] + for sta, end in open_pairs: + interv = IntervalR1(sta, end, False, False) + assert interv == (sta, end) + assert (sta, end) == interv + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_empty", + "test_whole", + "test_singletons", + "test_singles", + "test_intervals", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_contains.py b/tests/rbool/test_contains.py new file mode 100644 index 00000000..d79ca1b8 --- /dev/null +++ b/tests/rbool/test_contains.py @@ -0,0 +1,231 @@ +import pytest + +from shapepy.rbool import ( + DisjointR1, + EmptyR1, + IntervalR1, + SingleR1, + WholeR1, + bigger, + contains, + from_any, + lower, +) +from shapepy.scalar.reals import Math + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + "tests/rbool/test_compare.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_singleton_in_singleton(): + empty = EmptyR1() + whole = WholeR1() + + assert empty in empty + assert empty in whole + + assert whole not in empty + assert whole in whole + + assert contains(whole, whole) + assert contains(whole, empty) + assert not contains(empty, whole) + assert contains(empty, empty) + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_singleton_contains_object(): + empty = EmptyR1() + whole = WholeR1() + + values = (-100, -50, -20, -10, 0, 10, 20, 50, 100) + singls = tuple(map(SingleR1, values)) + for value in values: + assert value not in empty + assert value in whole + for single in singls: + assert empty in single + assert whole not in single + assert single not in empty + assert single in whole + + aval, bval = -10, 10 + + n2a = IntervalR1(Math.NEGINF, aval) + n2b = IntervalR1(Math.NEGINF, bval) + a2b = IntervalR1(aval, bval) + a2p = IntervalR1(aval, Math.POSINF) + b2p = IntervalR1(bval, Math.POSINF) + intervals = (n2a, n2b, a2b, a2p, b2p) + for interval in intervals: + assert empty in interval + assert whole not in interval + assert interval not in empty + assert interval in whole + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_interval_contains_interval(): + + aval, bval = -10, 10 + + n2a = IntervalR1(Math.NEGINF, aval) + n2b = IntervalR1(Math.NEGINF, bval) + a2b = IntervalR1(aval, bval) + a2p = IntervalR1(aval, Math.POSINF) + b2p = IntervalR1(bval, Math.POSINF) + + assert n2a in n2a + assert n2a in n2b + assert n2a not in a2b + assert n2a not in a2p + assert n2a not in b2p + + assert n2b not in n2a + assert n2b in n2b + assert n2b not in a2b + assert n2b not in a2p + assert n2b not in b2p + + assert a2b not in n2a + assert a2b in n2b + assert a2b in a2b + assert a2b in a2p + assert a2b not in b2p + + assert a2p not in n2a + assert a2p not in n2b + assert a2p not in a2b + assert a2p in a2p + assert a2p not in b2p + + assert b2p not in n2a + assert b2p not in n2b + assert b2p not in a2b + assert b2p in a2p + assert b2p in b2p + + assert IntervalR1(-50, -20) not in IntervalR1(0, 20) + assert IntervalR1(10, 50) not in IntervalR1(0, 20) + assert IntervalR1(-10, 10) in IntervalR1(-20, 20) + assert IntervalR1(-10, 10, True, True) not in IntervalR1( + -20, 10, True, False + ) + assert IntervalR1(-10, 10, True, True) not in IntervalR1( + -10, 20, False, True + ) + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_disjoint_contains_object(): + blocks = [ + "(-inf, -20)", + "[-10, -5]", + "[0, 5)", + "(10, 15]", + "(20, 25)", + "{30, 31, 33}", + ] + string = " U ".join(blocks) + disjoint = from_any(string) + assert isinstance(disjoint, DisjointR1) + + inside = {-25, -10, -7, -5, 0, 2, 12, 15, 22, 30, 31, 33} + outside = {-20, -15, -2, 5, 7, 10, 17, 20, 25, 28, 32} + for value in inside: + assert value in disjoint + for value in outside: + assert value not in disjoint + + assert disjoint == string + assert disjoint != {30, 31} + assert disjoint != [-10, -5] + assert disjoint != {30} + assert disjoint != EmptyR1() + assert disjoint != WholeR1() + + assert {30, 31} in disjoint + assert [-10, -5] in disjoint + assert {30} in disjoint + assert EmptyR1() in disjoint + assert WholeR1() not in disjoint + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_infinity(): + empty = EmptyR1() + assert Math.NEGINF not in empty + assert Math.POSINF not in empty + + whole = WholeR1() + assert Math.NEGINF in whole + assert Math.POSINF in whole + + for value in (-10, -1, 0, 1, 10): + assert Math.NEGINF not in SingleR1(value) + assert Math.POSINF not in SingleR1(value) + + interval = lower(0) + assert Math.NEGINF in interval + assert Math.POSINF not in interval + + interval = bigger(0) + assert Math.NEGINF not in interval + assert Math.POSINF in interval + + interval = IntervalR1(-10, 10) + assert Math.NEGINF not in interval + assert Math.POSINF not in interval + + blocks = [ + "(-inf, -20)", + "[-10, -5]", + "[0, 5)", + "(10, 15]", + "(20, 25)", + "{30, 31, 33}", + ] + string = " U ".join(blocks) + disjoint = from_any(string) + assert isinstance(disjoint, DisjointR1) + + assert Math.NEGINF in disjoint + assert Math.POSINF not in disjoint + + +@pytest.mark.order(15) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_singleton_in_singleton", + "test_singleton_contains_object", + "test_interval_contains_interval", + "test_disjoint_contains_object", + "test_infinity", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_convert.py b/tests/rbool/test_convert.py new file mode 100644 index 00000000..f1b03813 --- /dev/null +++ b/tests/rbool/test_convert.py @@ -0,0 +1,94 @@ +import pytest + +from shapepy.rbool import ( + EmptyR1, + IntervalR1, + SingleR1, + WholeR1, + bigger, + from_any, + lower, +) + + +@pytest.mark.order(13) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +class TestFromString: + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_empty(self): + assert from_any("{}") == EmptyR1() + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_whole(self): + assert from_any("(-inf, inf)") == WholeR1() + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single(self): + assert from_any(r"{-10}") == SingleR1(-10) + assert from_any(r"{0}") == SingleR1(0) + assert from_any(r"{10}") == SingleR1(10) + assert from_any(r"{+10}") == SingleR1(10) + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_interval(self): + assert from_any(r"[-10, 10]") == IntervalR1(-10, 10, True, True) + assert from_any(r"[-10, 10)") == IntervalR1(-10, 10, True, False) + assert from_any(r"(-10, 10]") == IntervalR1(-10, 10, False, True) + assert from_any(r"(-10, 10)") == IntervalR1(-10, 10, False, False) + + assert from_any(r"(-inf, 10]") == lower(10, True) + assert from_any(r"(-inf, 10)") == lower(10, False) + assert from_any(r"[-10, inf)") == bigger(-10, True) + assert from_any(r"(-10, inf)") == bigger(-10, False) + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_disjoint(self): + pass + + @pytest.mark.order(13) + @pytest.mark.timeout(1) + @pytest.mark.dependency( + depends=[ + "test_begin", + "TestFromString::test_empty", + "TestFromString::test_whole", + "TestFromString::test_single", + "TestFromString::test_interval", + "TestFromString::test_disjoint", + ] + ) + def test_all(self): + pass + + +@pytest.mark.order(13) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "TestFromString::test_all", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_limits.py b/tests/rbool/test_limits.py new file mode 100644 index 00000000..e8c856e2 --- /dev/null +++ b/tests/rbool/test_limits.py @@ -0,0 +1,63 @@ +import pytest + +from shapepy.rbool import infimum, maximum, minimum, supremum +from shapepy.scalar.reals import Math + + +@pytest.mark.order(17) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + ], + scope="session", +) +def test_inf_min_max_sup(): + subset = r"{}" + assert infimum(subset) is None + assert minimum(subset) is None + assert maximum(subset) is None + assert supremum(subset) is None + + subset = r"(-inf, +inf)" + assert infimum(subset) == Math.NEGINF + assert minimum(subset) is None + assert maximum(subset) is None + assert supremum(subset) == Math.POSINF + + subset = r"{1}" + assert infimum(subset) == 1 + assert minimum(subset) == 1 + assert maximum(subset) == 1 + assert supremum(subset) == 1 + + subset = r"{1, 3}" + assert infimum(subset) == 1 + assert minimum(subset) == 1 + assert maximum(subset) == 3 + assert supremum(subset) == 3 + + subset = r"[-10, 10]" + assert infimum(subset) == -10 + assert minimum(subset) == -10 + assert maximum(subset) == 10 + assert supremum(subset) == 10 + + subset = r"(-10, 10]" + assert infimum(subset) == -10 + assert minimum(subset) is None + assert maximum(subset) == 10 + assert supremum(subset) == 10 + + subset = r"[-10, 10)" + assert infimum(subset) == -10 + assert minimum(subset) == -10 + assert maximum(subset) is None + assert supremum(subset) == 10 + + subset = r"(-10, 10)" + assert infimum(subset) == -10 + assert minimum(subset) is None + assert maximum(subset) is None + assert supremum(subset) == 10 diff --git a/tests/rbool/test_print.py b/tests/rbool/test_print.py new file mode 100644 index 00000000..a28bfbf6 --- /dev/null +++ b/tests/rbool/test_print.py @@ -0,0 +1,137 @@ +import pytest + +from shapepy.rbool import ( + DisjointR1, + EmptyR1, + IntervalR1, + SingleR1, + WholeR1, + bigger, + lower, +) + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_empty(): + empty = EmptyR1() + assert str(empty) == r"{}" + assert repr(empty) == r"EmptyR1" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_whole(): + whole = WholeR1() + assert str(whole) == r"(-inf, inf)" + assert repr(whole) == r"WholeR1" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_single(): + value = SingleR1(-10) + assert str(value) == r"{-10}" + assert repr(value) == r"SingleR1(-10)" + + value = SingleR1(0) + assert str(value) == r"{0}" + assert repr(value) == r"SingleR1(0)" + + value = SingleR1(10) + assert str(value) == r"{10}" + assert repr(value) == r"SingleR1(10)" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_interval(): + interval = IntervalR1(-10, 10, True, True) + assert str(interval) == r"[-10, 10]" + + interval = IntervalR1(-10, 10, True, False) + assert str(interval) == r"[-10, 10)" + + interval = IntervalR1(-10, 10, False, True) + assert str(interval) == r"(-10, 10]" + + interval = IntervalR1(-10, 10, False, False) + assert str(interval) == r"(-10, 10)" + + interval = lower(10, True) + assert str(interval) == r"(-inf, 10]" + + interval = lower(10, False) + assert str(interval) == r"(-inf, 10)" + + interval = bigger(-10, True) + assert str(interval) == r"[-10, inf)" + + interval = bigger(-10, False) + assert str(interval) == r"(-10, inf)" + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency(depends=["test_begin"]) +def test_disjoint(): + interv0 = lower(-50) + interv1 = bigger(50) + disjoint = DisjointR1([interv0, interv1]) + assert str(disjoint) == "(-inf, -50] U [50, inf)" + repr(disjoint) + + interv2 = IntervalR1(-20, 20) + singles = list(map(SingleR1, [-30, -25, 25, 30, 40])) + disjoint = DisjointR1([interv0, interv1, interv2] + singles) + blocks = [ + "(-inf, -50]", + "{-30, -25}", + "[-20, 20]", + "{25, 30, 40}", + "[50, inf)", + ] + assert str(disjoint) == " U ".join(blocks) + repr(disjoint) + + disjoint = DisjointR1([interv0, interv2] + singles) + blocks = ["(-inf, -50]", "{-30, -25}", "[-20, 20]", "{25, 30, 40}"] + assert str(disjoint) == " U ".join(blocks) + repr(disjoint) + + disjoint = DisjointR1(singles) + assert str(disjoint) == "{-30, -25, 25, 30, 40}" + repr(disjoint) + + +@pytest.mark.order(14) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "test_empty", + "test_whole", + "test_single", + "test_interval", + "test_disjoint", + ] +) +def test_all(): + pass diff --git a/tests/rbool/test_transform.py b/tests/rbool/test_transform.py new file mode 100644 index 00000000..625215da --- /dev/null +++ b/tests/rbool/test_transform.py @@ -0,0 +1,211 @@ +import pytest + +from shapepy.rbool import ( + EmptyR1, + IntervalR1, + SingleR1, + WholeR1, + from_any, + move, + scale, +) + + +@pytest.mark.order(17) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "tests/rbool/test_build.py::test_all", + "tests/rbool/test_convert.py::test_all", + "tests/rbool/test_compare.py::test_all", + "tests/rbool/test_contains.py::test_all", + ], + scope="session", +) +def test_begin(): + pass + + +class TestShift: + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_empty(self): + empty = EmptyR1() + assert empty.move(-1) == empty + assert empty.move(0) == empty + assert empty.move(1) == empty + + assert move(empty, -1) == empty + assert move(empty, 0) == empty + assert move(empty, 1) == empty + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_whole(self): + whole = WholeR1() + assert whole.move(-1) == whole + assert whole.move(0) == whole + assert whole.move(1) == whole + + assert move(whole, -1) == whole + assert move(whole, 0) == whole + assert move(whole, 1) == whole + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single(self): + values = [-10, 0, 1] + amounts = [-20, -10, 0, 10, 20] + + for value in values: + single = SingleR1(value) + for amount in amounts: + test = single.move(amount) + good = SingleR1(value + amount) + assert test == good + + with pytest.raises(ValueError): + single.move(float("-inf")) + with pytest.raises(ValueError): + single.move(float("inf")) + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_interval(self): + base = IntervalR1(-10, 10) + test = base.move(-5) + good = IntervalR1(-15, 5) + assert test == good + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_disjoint(self): + base = from_any(r"[-10, -5) U {-4} U (3, 4]") + test = base.move(-5) + good = from_any(r"[-15, -10) U {-9} U (-2, -1]") + assert test == good + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency( + depends=[ + "TestShift::test_empty", + "TestShift::test_whole", + "TestShift::test_single", + "TestShift::test_interval", + "TestShift::test_disjoint", + ] + ) + def test_all(self): + pass + + +class TestScale: + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_empty(self): + empty = EmptyR1() + assert empty.scale(-1) == empty + assert empty.scale(1) == empty + + assert scale(empty, -1) == empty + assert scale(empty, 1) == empty + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_whole(self): + whole = WholeR1() + assert whole.scale(-1) == whole + assert whole.scale(1) == whole + + assert scale(whole, -1) == whole + assert scale(whole, 1) == whole + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_single(self): + values = [-10, 0, 1] + amounts = [-20, -10, 10, 20] + + for value in values: + single = SingleR1(value) + for amount in amounts: + test = single.scale(amount) + good = SingleR1(value * amount) + assert test == good + + with pytest.raises(ValueError): + single.scale(0) + with pytest.raises(ValueError): + single.scale(float("-inf")) + with pytest.raises(ValueError): + single.scale(float("inf")) + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_interval(self): + base = IntervalR1(-10, 10) + assert base.scale(1) == base + assert base.scale(2) == IntervalR1(-20, 20) + assert base.scale(-1) == base # symmetry + + base = IntervalR1(0, 10) + assert base.scale(1) == base + assert base.scale(2) == IntervalR1(0, 20) + assert base.scale(-1) == IntervalR1(-10, 0) + + base = from_any("[-10, 5)") + assert base.scale(1) == base + assert base.scale(2) == from_any("[-20, 10)") + assert base.scale(-1) == from_any("(-5, 10]") + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency(depends=["test_begin"]) + def test_disjoint(self): + base = from_any(r"[-10, -5) U {-4} U (3, 4]") + assert base.scale(1) == base + + good = from_any(r"[-20, -10) U {-8} U (6, 8]") + assert base.scale(2) == good + + good = from_any(r"[-4, -3) U {4} U (5, 10]") + assert base.scale(-1) == good + + @pytest.mark.order(17) + @pytest.mark.timeout(1) + @pytest.mark.dependency( + depends=[ + "TestScale::test_empty", + "TestScale::test_whole", + "TestScale::test_single", + "TestScale::test_interval", + "TestScale::test_disjoint", + ] + ) + def test_all(self): + pass + + +@pytest.mark.order(17) +@pytest.mark.timeout(1) +@pytest.mark.dependency( + depends=[ + "test_begin", + "TestShift::test_all", + "TestScale::test_all", + ] +) +def test_all(): + pass