Passed
Push — linear-mapping ( 52ee5f )
by Konstantinos
10:20 queued 01:25
created

MapOnLinearSpace.transform()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
import attr
2
3
@attr.s
4
class LinearScale:
5
    lower_bound = attr.ib(init=True, type=int)
6
    upper_bound = attr.ib(init=True, type=int)
7
8
    @upper_bound.validator
9
    def __validate_scale(self, attribute, upper_bound):
10
        if upper_bound <= self.lower_bound:
11
            raise ValueError(
12
                f'The linear scale, should have lower_bound < upper_bound. Instead lower_bound={self.lower_bound}, upper_bound={upper_bound}')
13
14
    def __len__(self):
15
        return 2
16
17
    def __getitem__(self, item):
18
        if item == 0:
19
            return self.lower_bound
20
        if item == 1:
21
            return self.upper_bound
22
        raise IndexError(
23
            f'You can only request the 0 (lower bound) or 1 (upper bound) indexed item on the linear scale. You requested {item}')
24
25
    @classmethod
26
    def create(cls, two_element_list_like):
27
        return LinearScale(*[_ for _ in two_element_list_like])
28
29
30
@attr.s
31
class MapOnLinearSpace:
32
    _from_scale = attr.ib(init=True, type=LinearScale)
33
    _target_scale = attr.ib(init=True, type=LinearScale)
34
    _reverse = attr.ib(init=True, default=False, type=bool)
35
    _transform_callback = attr.ib(init=False,
36
                                  default=attr.Factory(lambda self: self._get_transform_callback(), takes_self=True))
37
38
    @classmethod
39
    def universal_constructor(cls, from_scale, target_scale, reverse=False):
40
        return MapOnLinearSpace(LinearScale.create(from_scale),
41
                                LinearScale.create(target_scale),
42
                                reverse)
43
44
    def __transform_inverted(self, number):
45
        # F(x) = { ( to_scale_min - to_scale_max ) * x + to_scale_max * from_scale_max - to_scale_min * from_scale_min }  / ( from_scale_max - from_scale_min )
46
        return ( (self._target_scale.lower_bound - self._target_scale.upper_bound) * number + self._target_scale.upper_bound * self._from_scale.upper_bound - self._target_scale.lower_bound * self._from_scale.lower_bound ) / (
47
               self._from_scale.upper_bound - self._from_scale.lower_bound)
48
49
    def __transform(self, number):
50
        return ((self._target_scale.upper_bound - self._target_scale.lower_bound) * number + self._target_scale.lower_bound * self._from_scale.upper_bound - self._target_scale.lower_bound * self._from_scale.lower_bound) / (self._from_scale.upper_bound - self._from_scale.lower_bound)
51
52
    def _get_transform_callback(self):
53
        if self._reverse:
54
            return self.__transform_inverted
55
        return self.__transform
56
57
    def transform(self, number):
58
        """Transform the input number to a different linear scale
59
        """
60
        return self._transform_callback(number)
61
62
    @property
63
    def from_scale(self):
64
        return self._from_scale
65
66
    @from_scale.setter
67
    def from_scale(self, from_scale):
68
        self._from_scale = LinearScale.create(from_scale)
69
        self._transform_callback = self._get_transform_callback()
70
71
    @property
72
    def target_scale(self):
73
        return self._target_scale
74
75
    @target_scale.setter
76
    def target_scale(self, target_scale):
77
        self._target_scale = LinearScale.create(target_scale)
78
        self._transform_callback = self._get_transform_callback()
79
80
    @property
81
    def reverse(self):
82
        return self._reverse
83
84
    @reverse.setter
85
    def reverse(self, reverse):
86
        self._reverse = reverse
87
        self._transform_callback = self._get_transform_callback()
88