psyke 0.8.3.dev2__py3-none-any.whl → 0.8.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of psyke might be problematic. Click here for more details.
- psyke/__init__.py +7 -4
- psyke/extraction/hypercubic/__init__.py +63 -79
- psyke/extraction/hypercubic/creepy/__init__.py +1 -1
- psyke/extraction/hypercubic/hypercube.py +38 -19
- psyke/hypercubepredictor.py +3 -1
- psyke/schema/__init__.py +60 -23
- {psyke-0.8.3.dev2.dist-info → psyke-0.8.5.dist-info}/METADATA +1 -1
- {psyke-0.8.3.dev2.dist-info → psyke-0.8.5.dist-info}/RECORD +11 -11
- {psyke-0.8.3.dev2.dist-info → psyke-0.8.5.dist-info}/LICENSE +0 -0
- {psyke-0.8.3.dev2.dist-info → psyke-0.8.5.dist-info}/WHEEL +0 -0
- {psyke-0.8.3.dev2.dist-info → psyke-0.8.5.dist-info}/top_level.txt +0 -0
psyke/__init__.py
CHANGED
|
@@ -61,7 +61,7 @@ class EvaluableModel(object):
|
|
|
61
61
|
raise NotImplementedError('predict')
|
|
62
62
|
|
|
63
63
|
def __convert(self, ys: Iterable) -> Iterable:
|
|
64
|
-
if self.normalization is not None:
|
|
64
|
+
if self.normalization is not None and not isinstance([p for p in ys if p is not None][0], str):
|
|
65
65
|
m, s = self.normalization[list(self.normalization.keys())[-1]]
|
|
66
66
|
ys = [prediction if prediction is None else prediction * s + m for prediction in ys]
|
|
67
67
|
return ys
|
|
@@ -73,7 +73,7 @@ class EvaluableModel(object):
|
|
|
73
73
|
raise NotImplementedError('brute_predict')
|
|
74
74
|
|
|
75
75
|
def unscale(self, values, name):
|
|
76
|
-
if self.normalization is None or isinstance(values, LinearRegression):
|
|
76
|
+
if self.normalization is None or name not in self.normalization or isinstance(values, LinearRegression):
|
|
77
77
|
return values
|
|
78
78
|
if isinstance(values, Iterable):
|
|
79
79
|
values = [None if value is None else
|
|
@@ -161,17 +161,20 @@ class Extractor(EvaluableModel, ABC):
|
|
|
161
161
|
"""
|
|
162
162
|
raise NotImplementedError('extract')
|
|
163
163
|
|
|
164
|
-
def predict_why(self, data: dict[str, float]):
|
|
164
|
+
def predict_why(self, data: dict[str, float], verbose=True):
|
|
165
165
|
"""
|
|
166
166
|
Provides a prediction and the corresponding explanation.
|
|
167
167
|
:param data: is the instance to predict.
|
|
168
|
+
:param verbose: if the explanation has to be printed.
|
|
168
169
|
"""
|
|
169
170
|
raise NotImplementedError('predict_why')
|
|
170
171
|
|
|
171
|
-
def predict_counter(self, data: dict[str, float]):
|
|
172
|
+
def predict_counter(self, data: dict[str, float], verbose=True, only_first=True):
|
|
172
173
|
"""
|
|
173
174
|
Provides a prediction and counterfactual explanations.
|
|
174
175
|
:param data: is the instance to predict.
|
|
176
|
+
:param verbose: if the counterfactual explanation has to be printed.
|
|
177
|
+
:param only_first: if only the closest counterfactual explanation is provided for each distinct class.
|
|
175
178
|
"""
|
|
176
179
|
raise NotImplementedError('predict_counter')
|
|
177
180
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import math
|
|
3
4
|
from abc import ABC
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
@@ -43,8 +44,8 @@ class HyperCubeExtractor(HyperCubePredictor, PedagogicalExtractor, ABC):
|
|
|
43
44
|
return theory
|
|
44
45
|
|
|
45
46
|
def pairwise_fairness(self, data: dict[str, float], neighbor: dict[str, float]):
|
|
46
|
-
cube1 = self._find_cube(data
|
|
47
|
-
cube2 = self._find_cube(neighbor
|
|
47
|
+
cube1 = self._find_cube(data)
|
|
48
|
+
cube2 = self._find_cube(neighbor)
|
|
48
49
|
different_prediction_reasons = []
|
|
49
50
|
|
|
50
51
|
if cube1.output == cube2.output:
|
|
@@ -62,100 +63,83 @@ class HyperCubeExtractor(HyperCubePredictor, PedagogicalExtractor, ABC):
|
|
|
62
63
|
different_prediction_reasons.append(d)
|
|
63
64
|
return different_prediction_reasons
|
|
64
65
|
|
|
65
|
-
def predict_counter(self, data: dict[str, float], verbose=True):
|
|
66
|
+
def predict_counter(self, data: dict[str, float], verbose=True, only_first=True):
|
|
66
67
|
output = ""
|
|
67
68
|
prediction = None
|
|
68
|
-
cube = self._find_cube(data
|
|
69
|
+
cube = self._find_cube(data)
|
|
69
70
|
if cube is None:
|
|
70
71
|
output += "The extracted knowledge is not exhaustive; impossible to predict this instance"
|
|
71
72
|
else:
|
|
72
73
|
prediction = self._predict_from_cubes(data)
|
|
73
|
-
output += f"The output is {prediction}"
|
|
74
|
+
output += f"The output is {prediction}\n"
|
|
74
75
|
|
|
75
76
|
point = Point(list(data.keys()), list(data.values()))
|
|
76
77
|
cubes = self._hypercubes if cube is None else [c for c in self._hypercubes if cube.output != c.output]
|
|
77
78
|
cubes = sorted([(cube.surface_distance(point), cube.volume(), i, cube) for i, cube in enumerate(cubes)])
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
|
|
80
|
+
counter_conditions = []
|
|
81
|
+
|
|
80
82
|
for _, _, _, c in cubes:
|
|
81
|
-
if c.output not in
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for d in point.dimensions.keys():
|
|
86
|
-
lower, upper = c[d]
|
|
87
|
-
p = point[d]
|
|
88
|
-
if p < lower:
|
|
89
|
-
output += f"\n {d} increases above {round(lower, 1)}"
|
|
90
|
-
different_prediction_reasons.append((d, '>=', lower))
|
|
91
|
-
elif p > upper:
|
|
92
|
-
output += f"\n {d} decreses below {round(upper, 1)}"
|
|
93
|
-
different_prediction_reasons.append((d, '<=', upper))
|
|
83
|
+
if not only_first or c.output not in [o for o, _ in counter_conditions]:
|
|
84
|
+
counter_conditions.append((c.output, {c: [val for val in v if val is not None and not val.is_in(
|
|
85
|
+
self.unscale(data[c], c))] for c, v in self.__get_conditions(data, c).items()}))
|
|
86
|
+
|
|
94
87
|
if verbose:
|
|
88
|
+
for o, conditions in counter_conditions:
|
|
89
|
+
output += f"The output may be {o} if\n" + HyperCubeExtractor.__conditions_to_string(conditions)
|
|
95
90
|
print(output)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
|
|
92
|
+
return prediction, counter_conditions
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def __conditions_to_string(conditions: dict[str, list[Value]]) -> str:
|
|
96
|
+
output = ""
|
|
97
|
+
for d in conditions:
|
|
98
|
+
for i, condition in enumerate(conditions[d]):
|
|
99
|
+
if i == 0:
|
|
100
|
+
output += f' {d} is '
|
|
101
|
+
else:
|
|
102
|
+
output += ' and '
|
|
103
|
+
output += condition.print()
|
|
104
|
+
if i + 1 == len(conditions[d]):
|
|
105
|
+
output += '\n'
|
|
106
|
+
return output
|
|
107
|
+
|
|
108
|
+
def __get_conditions(self, data: dict[str, float], cube: GenericCube) -> dict[str, list[Value]]:
|
|
109
|
+
conditions = {d: [cube.interval_to_value(d, self.unscale)] for d in data.keys()
|
|
110
|
+
if d not in self._dimensions_to_ignore}
|
|
111
|
+
for c in cube.subcubes(self._hypercubes):
|
|
112
|
+
for d in conditions:
|
|
113
|
+
condition = c.interval_to_value(d, self.unscale)
|
|
114
|
+
if condition is None:
|
|
115
|
+
continue
|
|
116
|
+
elif conditions[d][-1] is None:
|
|
117
|
+
conditions[d][-1] = -condition
|
|
118
|
+
else:
|
|
119
|
+
try:
|
|
120
|
+
conditions[d][-1] *= -condition
|
|
121
|
+
except Exception:
|
|
122
|
+
conditions[d].append(-condition)
|
|
105
123
|
return conditions
|
|
106
124
|
|
|
107
|
-
def predict_why(self, data: dict[str, float]):
|
|
108
|
-
cube = self._find_cube(data
|
|
125
|
+
def predict_why(self, data: dict[str, float], verbose=True):
|
|
126
|
+
cube = self._find_cube(data)
|
|
127
|
+
output = ""
|
|
109
128
|
if cube is None:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if i == 0:
|
|
119
|
-
print(' ', d, 'is', end=' ')
|
|
120
|
-
else:
|
|
121
|
-
print('and', end=' ')
|
|
122
|
-
if isinstance(condition, Outside):
|
|
123
|
-
print('not', end=' ')
|
|
124
|
-
print('between', round(condition.lower, 1), 'and', round(condition.upper, 1), end=' ')
|
|
125
|
-
if i + 1 == len(simplified):
|
|
126
|
-
print()
|
|
129
|
+
output += "The extracted knowledge is not exhaustive; impossible to predict this instance\n"
|
|
130
|
+
if verbose:
|
|
131
|
+
print(output)
|
|
132
|
+
return None, {}
|
|
133
|
+
prediction = self._predict_from_cubes(data)
|
|
134
|
+
output += f"The output is {prediction} because\n"
|
|
135
|
+
conditions = {c: [val for val in v if val is not None and val.is_in(self.unscale(data[c], c))]
|
|
136
|
+
for c, v in self.__get_conditions(data, cube).items()}
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for i, simple in enumerate(simplified):
|
|
134
|
-
if isinstance(condition, Outside) and isinstance(simple, Outside):
|
|
135
|
-
if simple.lower <= condition.lower <= simple.upper or \
|
|
136
|
-
simple.lower <= condition.upper <= simple.upper or \
|
|
137
|
-
condition.lower <= simple.lower <= simple.upper <= condition.upper:
|
|
138
|
-
simplified[i].upper = max(condition.upper, simple.upper)
|
|
139
|
-
simplified[i].lower = min(condition.lower, simple.lower)
|
|
140
|
-
to_add = False
|
|
141
|
-
break
|
|
142
|
-
elif isinstance(condition, Outside) and isinstance(simple, Between):
|
|
143
|
-
if simple.lower >= condition.upper or simple.upper <= condition.lower:
|
|
144
|
-
to_add = False
|
|
145
|
-
break
|
|
146
|
-
elif condition.lower <= simple.lower <= condition.upper <= simple.upper:
|
|
147
|
-
simplified[i].lower = condition.upper
|
|
148
|
-
to_add = False
|
|
149
|
-
break
|
|
150
|
-
elif simple.lower <= condition.lower <= simple.upper <= condition.upper:
|
|
151
|
-
simplified[i].upper = condition.lower
|
|
152
|
-
to_add = False
|
|
153
|
-
break
|
|
154
|
-
elif condition.lower <= simple.lower <= simple.upper <= condition.upper:
|
|
155
|
-
raise ValueError
|
|
156
|
-
if to_add:
|
|
157
|
-
simplified.append(condition)
|
|
158
|
-
return simplified
|
|
138
|
+
if verbose:
|
|
139
|
+
output += HyperCubeExtractor.__conditions_to_string(conditions)
|
|
140
|
+
print(output)
|
|
141
|
+
|
|
142
|
+
return prediction, conditions
|
|
159
143
|
|
|
160
144
|
@staticmethod
|
|
161
145
|
def _create_head(dataframe: pd.DataFrame, variables: list[Var], output: float | LinearRegression) -> Struct:
|
|
@@ -25,7 +25,7 @@ class CReEPy(HyperCubeExtractor):
|
|
|
25
25
|
self.clustering = clustering(depth, error_threshold, self._output, gauss_components, discretization,
|
|
26
26
|
normalization, seed)
|
|
27
27
|
self._default_surrounding_cube = True
|
|
28
|
-
self._dimensions_to_ignore = [dimension for dimension, relevance in ranks if relevance < ignore_threshold]
|
|
28
|
+
self._dimensions_to_ignore = set([dimension for dimension, relevance in ranks if relevance < ignore_threshold])
|
|
29
29
|
|
|
30
30
|
def _extract(self, dataframe: pd.DataFrame) -> Theory:
|
|
31
31
|
if not isinstance(self.clustering, HyperCubeClustering):
|
|
@@ -13,7 +13,6 @@ from psyke.utils import get_default_precision, get_int_precision, Target, get_de
|
|
|
13
13
|
from psyke.utils.logic import create_term, to_rounded_real, linear_function_creator
|
|
14
14
|
from sklearn.linear_model import LinearRegression
|
|
15
15
|
from tuprolog.core import Var, Struct
|
|
16
|
-
from random import Random
|
|
17
16
|
import numpy as np
|
|
18
17
|
|
|
19
18
|
|
|
@@ -149,6 +148,9 @@ class HyperCube:
|
|
|
149
148
|
else:
|
|
150
149
|
self._infinite_dimensions[dimension] = [direction]
|
|
151
150
|
|
|
151
|
+
def copy_infinite_dimensions(self, dimensions: dict[str, str]):
|
|
152
|
+
self._infinite_dimensions = dimensions.copy()
|
|
153
|
+
|
|
152
154
|
@property
|
|
153
155
|
def dimensions(self) -> Dimensions:
|
|
154
156
|
return self._dimensions
|
|
@@ -173,8 +175,12 @@ class HyperCube:
|
|
|
173
175
|
def barycenter(self) -> Point:
|
|
174
176
|
return self._barycenter
|
|
175
177
|
|
|
176
|
-
def subcubes(self, cubes: Iterable[GenericCube]) -> Iterable[GenericCube]:
|
|
177
|
-
|
|
178
|
+
def subcubes(self, cubes: Iterable[GenericCube], only_largest: bool = True) -> Iterable[GenericCube]:
|
|
179
|
+
subcubes = [c for c in cubes if c in self and c.output != self.output]
|
|
180
|
+
if only_largest:
|
|
181
|
+
subsubcubes = [c for cube_list in [c.subcubes(cubes) for c in subcubes] for c in cube_list]
|
|
182
|
+
subcubes = [c for c in subcubes if c not in subsubcubes]
|
|
183
|
+
return subcubes
|
|
178
184
|
|
|
179
185
|
def _fit_dimension(self, dimension: dict[str, tuple[float, float]]) -> dict[str, tuple[float, float]]:
|
|
180
186
|
new_dimension: dict[str, tuple[float, float]] = {}
|
|
@@ -231,12 +237,14 @@ class HyperCube:
|
|
|
231
237
|
return False
|
|
232
238
|
|
|
233
239
|
def copy(self) -> HyperCube:
|
|
234
|
-
|
|
240
|
+
new_cube = HyperCube(self.dimensions.copy(), self._limits.copy(), self.output)
|
|
241
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
242
|
+
return new_cube
|
|
235
243
|
|
|
236
244
|
def count(self, dataset: pd.DataFrame) -> int:
|
|
237
245
|
return self.filter_dataframe(dataset.iloc[:, :-1]).shape[0]
|
|
238
246
|
|
|
239
|
-
def
|
|
247
|
+
def interval_to_value(self, dimension, unscale=None):
|
|
240
248
|
if dimension not in self._infinite_dimensions:
|
|
241
249
|
return Between(unscale(self[dimension][0], dimension), unscale(self[dimension][1], dimension))
|
|
242
250
|
if len(self._infinite_dimensions[dimension]) == 2:
|
|
@@ -247,7 +255,7 @@ class HyperCube:
|
|
|
247
255
|
return LessThan(unscale(self[dimension][1], dimension))
|
|
248
256
|
|
|
249
257
|
def body(self, variables: dict[str, Var], ignore: list[str], unscale=None, normalization=None) -> Iterable[Struct]:
|
|
250
|
-
values = [(dim, self.
|
|
258
|
+
values = [(dim, self.interval_to_value(dim, unscale)) for dim in self.dimensions if dim not in ignore]
|
|
251
259
|
return [create_term(variables[name], value) for name, value in values
|
|
252
260
|
if not self.is_default and value is not None]
|
|
253
261
|
|
|
@@ -439,8 +447,8 @@ class HyperCube:
|
|
|
439
447
|
|
|
440
448
|
|
|
441
449
|
class RegressionCube(HyperCube):
|
|
442
|
-
def __init__(self, dimension: dict[str, tuple] = None, output=None):
|
|
443
|
-
super().__init__(dimension=dimension, output=LinearRegression() if output is None else output)
|
|
450
|
+
def __init__(self, dimension: dict[str, tuple] = None, limits: set[Limit] = None, output=None):
|
|
451
|
+
super().__init__(dimension=dimension, limits=limits, output=LinearRegression() if output is None else output)
|
|
444
452
|
|
|
445
453
|
def update(self, dataset: pd.DataFrame, predictor) -> None:
|
|
446
454
|
filtered = self.filter_dataframe(dataset.iloc[:, :-1])
|
|
@@ -458,7 +466,9 @@ class RegressionCube(HyperCube):
|
|
|
458
466
|
output.intercept_ = self.output.intercept_
|
|
459
467
|
except AttributeError:
|
|
460
468
|
pass
|
|
461
|
-
|
|
469
|
+
new_cube = RegressionCube(self.dimensions.copy(), self._limits.copy(), output)
|
|
470
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
471
|
+
return new_cube
|
|
462
472
|
|
|
463
473
|
def body(self, variables: dict[str, Var], ignore: list[str], unscale=None, normalization=None) -> Iterable[Struct]:
|
|
464
474
|
intercept = self.output.intercept_ if normalization is None else unscale(sum(
|
|
@@ -487,12 +497,15 @@ class ClassificationCube(HyperCube):
|
|
|
487
497
|
self._barycenter = Point(means.index.values, means.values)
|
|
488
498
|
|
|
489
499
|
def copy(self) -> ClassificationCube:
|
|
490
|
-
|
|
500
|
+
new_cube = ClassificationCube(self.dimensions.copy(), self._limits.copy(), self.output)
|
|
501
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
502
|
+
return new_cube
|
|
491
503
|
|
|
492
504
|
|
|
493
505
|
class ClosedCube(HyperCube):
|
|
494
|
-
def __init__(self, dimension: dict[str, tuple] = None,
|
|
495
|
-
|
|
506
|
+
def __init__(self, dimension: dict[str, tuple] = None, limits: set[Limit] = None,
|
|
507
|
+
output: str | LinearRegression | float = 0.0):
|
|
508
|
+
super().__init__(dimension=dimension, limits=limits, output=output)
|
|
496
509
|
|
|
497
510
|
def __contains__(self, obj: dict[str, float] | ClosedCube) -> bool:
|
|
498
511
|
"""
|
|
@@ -533,12 +546,14 @@ class ClosedCube(HyperCube):
|
|
|
533
546
|
return np.all((v[:, 0] <= ds) & (ds <= v[:, 1]), axis=1)
|
|
534
547
|
|
|
535
548
|
def copy(self) -> ClosedCube:
|
|
536
|
-
|
|
549
|
+
new_cube = ClosedCube(self.dimensions.copy(), self._limits.copy(), self.output)
|
|
550
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
551
|
+
return new_cube
|
|
537
552
|
|
|
538
553
|
|
|
539
554
|
class ClosedRegressionCube(ClosedCube, RegressionCube):
|
|
540
|
-
def __init__(self, dimension: dict[str, tuple] = None, output=None):
|
|
541
|
-
super().__init__(dimension=dimension, output=LinearRegression() if output is None else output)
|
|
555
|
+
def __init__(self, dimension: dict[str, tuple] = None, limits: set[Limit] = None, output=None):
|
|
556
|
+
super().__init__(dimension=dimension, limits=limits, output=LinearRegression() if output is None else output)
|
|
542
557
|
|
|
543
558
|
def copy(self) -> ClosedRegressionCube:
|
|
544
559
|
output = LinearRegression()
|
|
@@ -547,15 +562,19 @@ class ClosedRegressionCube(ClosedCube, RegressionCube):
|
|
|
547
562
|
output.intercept_ = self.output.intercept_
|
|
548
563
|
except AttributeError:
|
|
549
564
|
pass
|
|
550
|
-
|
|
565
|
+
new_cube = ClosedRegressionCube(self.dimensions.copy(), self._limits.copy(), output)
|
|
566
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
567
|
+
return new_cube
|
|
551
568
|
|
|
552
569
|
|
|
553
570
|
class ClosedClassificationCube(ClosedCube, ClassificationCube):
|
|
554
|
-
def __init__(self, dimension: dict[str, tuple] = None, output: str = None):
|
|
555
|
-
super().__init__(dimension=dimension, output=output)
|
|
571
|
+
def __init__(self, dimension: dict[str, tuple] = None, limits: set[Limit] = None, output: str = None):
|
|
572
|
+
super().__init__(dimension=dimension, limits=limits, output=output)
|
|
556
573
|
|
|
557
574
|
def copy(self) -> ClosedClassificationCube:
|
|
558
|
-
|
|
575
|
+
new_cube = ClosedClassificationCube(self.dimensions.copy(), self._limits.copy(), self.output)
|
|
576
|
+
new_cube.copy_infinite_dimensions(self._infinite_dimensions)
|
|
577
|
+
return new_cube
|
|
559
578
|
|
|
560
579
|
|
|
561
580
|
GenericCube = Union[HyperCube, ClassificationCube, RegressionCube,
|
psyke/hypercubepredictor.py
CHANGED
|
@@ -76,8 +76,10 @@ class HyperCubePredictor(EvaluableModel):
|
|
|
76
76
|
return round(HyperCubePredictor._get_cube_output(cube, data), get_int_precision())
|
|
77
77
|
|
|
78
78
|
def _find_cube(self, data: dict[str, float]) -> GenericCube | None:
|
|
79
|
+
data = data.copy()
|
|
79
80
|
for dimension in self._dimensions_to_ignore:
|
|
80
|
-
|
|
81
|
+
if dimension in data:
|
|
82
|
+
del data[dimension]
|
|
81
83
|
for cube in self._hypercubes:
|
|
82
84
|
if data in cube:
|
|
83
85
|
return cube.copy()
|
psyke/schema/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Callable
|
|
|
4
4
|
from psyke.utils import get_int_precision
|
|
5
5
|
|
|
6
6
|
_EMPTY_INTERSECTION_EXCEPTION: Callable = lambda x, y: \
|
|
7
|
-
Exception("Empty intersection between two Value:
|
|
7
|
+
Exception(f"Empty intersection between two Value: {str(x)} and {str(y)}")
|
|
8
8
|
|
|
9
9
|
_NOT_IMPLEMENTED_INTERSECTION: Callable = lambda x, y: \
|
|
10
10
|
Exception("Not implemented intersection between: " + str(x) + ' and ' + str(y))
|
|
@@ -85,12 +85,26 @@ class Value:
|
|
|
85
85
|
else:
|
|
86
86
|
return False
|
|
87
87
|
|
|
88
|
+
def __neg__(self) -> Value:
|
|
89
|
+
if isinstance(self, Constant):
|
|
90
|
+
return self
|
|
91
|
+
elif isinstance(self, GreaterThan):
|
|
92
|
+
return LessThan(self.value, self.standard)
|
|
93
|
+
elif isinstance(self, LessThan):
|
|
94
|
+
return GreaterThan(self.value, self.standard)
|
|
95
|
+
elif isinstance(self, Between):
|
|
96
|
+
return Outside(self.lower, self.upper, self.standard)
|
|
97
|
+
elif isinstance(self, Outside):
|
|
98
|
+
return Between(self.lower, self.upper, self.standard)
|
|
99
|
+
else:
|
|
100
|
+
raise TypeError
|
|
101
|
+
|
|
88
102
|
# TODO: handle convention (low priority).
|
|
89
103
|
def __mul__(self, other) -> Value:
|
|
90
104
|
|
|
91
105
|
def intersection_with_constant(first_value: Constant, second_value: Value) -> Value:
|
|
92
106
|
if isinstance(first_value, Constant):
|
|
93
|
-
if second_value
|
|
107
|
+
if first_value in second_value:
|
|
94
108
|
return first_value
|
|
95
109
|
else:
|
|
96
110
|
raise _EMPTY_INTERSECTION_EXCEPTION(first_value, second_value)
|
|
@@ -100,42 +114,45 @@ class Value:
|
|
|
100
114
|
def intersection_with_outside(first_value: Outside, second_value: Value) -> Value:
|
|
101
115
|
if isinstance(first_value, Outside):
|
|
102
116
|
if isinstance(second_value, LessThan):
|
|
103
|
-
if second_value.value
|
|
104
|
-
|
|
105
|
-
|
|
117
|
+
if second_value.value > first_value.upper:
|
|
118
|
+
# LessThan(first_value.lower) + Between(first_value.lower, second_value.value)
|
|
119
|
+
raise _NOT_IMPLEMENTED_INTERSECTION(first_value, second_value)
|
|
120
|
+
elif second_value.value > first_value.lower:
|
|
106
121
|
return LessThan(first_value.lower)
|
|
107
122
|
else:
|
|
108
|
-
|
|
123
|
+
return second_value
|
|
109
124
|
elif isinstance(second_value, GreaterThan):
|
|
110
|
-
if second_value.value
|
|
125
|
+
if second_value.value < first_value.lower:
|
|
126
|
+
# Between(second_value.value, first_value.lower) + GreaterThan(first_value.upper)
|
|
127
|
+
raise _NOT_IMPLEMENTED_INTERSECTION(first_value, second_value)
|
|
128
|
+
elif second_value.value < first_value.upper:
|
|
111
129
|
return GreaterThan(first_value.upper)
|
|
112
|
-
elif first_value.is_in(second_value.value):
|
|
113
|
-
return second_value
|
|
114
130
|
else:
|
|
115
|
-
raise _NOT_IMPLEMENTED_INTERSECTION(first_value, second_value)
|
|
116
|
-
elif isinstance(second_value, Constant):
|
|
117
|
-
if not first_value.is_in(second_value.value):
|
|
118
131
|
return second_value
|
|
119
|
-
else:
|
|
120
|
-
raise _EMPTY_INTERSECTION_EXCEPTION(first_value, second_value)
|
|
121
132
|
elif isinstance(second_value, Between):
|
|
122
|
-
if second_value
|
|
133
|
+
if second_value.upper <= first_value.lower or second_value.lower >= first_value.upper:
|
|
123
134
|
return second_value
|
|
124
135
|
elif second_value.lower <= first_value.lower <= second_value.upper <= first_value.upper:
|
|
125
136
|
return Between(second_value.lower, first_value.lower)
|
|
126
137
|
elif first_value.lower <= second_value.lower <= first_value.upper <= second_value.upper:
|
|
127
138
|
return Between(first_value.upper, second_value.upper)
|
|
128
|
-
|
|
139
|
+
elif second_value.lower <= first_value.lower <= first_value.upper <= second_value.upper:
|
|
129
140
|
raise _NOT_IMPLEMENTED_INTERSECTION(first_value, second_value)
|
|
141
|
+
else:
|
|
142
|
+
raise _EMPTY_INTERSECTION_EXCEPTION(first_value, second_value)
|
|
130
143
|
elif isinstance(second_value, Outside):
|
|
131
|
-
if second_value.lower <= first_value.lower
|
|
144
|
+
if second_value.lower <= first_value.lower <= first_value.upper <= second_value.upper:
|
|
132
145
|
return second_value
|
|
133
|
-
elif first_value.lower <= second_value.lower
|
|
146
|
+
elif first_value.lower <= second_value.lower <= second_value.upper <= first_value.upper:
|
|
134
147
|
return first_value
|
|
148
|
+
elif second_value.lower <= first_value.lower <= second_value.upper <= first_value.upper:
|
|
149
|
+
return Outside(second_value.lower, first_value.upper)
|
|
150
|
+
elif first_value.lower <= second_value.lower <= first_value.upper <= second_value.upper:
|
|
151
|
+
return Outside(first_value.lower, second_value.upper)
|
|
135
152
|
else:
|
|
136
153
|
raise _EMPTY_INTERSECTION_EXCEPTION(first_value, second_value)
|
|
137
154
|
elif isinstance(second_value, Constant):
|
|
138
|
-
intersection_with_constant(second_value, first_value)
|
|
155
|
+
return intersection_with_constant(second_value, first_value)
|
|
139
156
|
else:
|
|
140
157
|
raise _INTERSECTION_WITH_WRONG_TYPE(first_value, second_value)
|
|
141
158
|
else:
|
|
@@ -173,7 +190,7 @@ class Value:
|
|
|
173
190
|
else:
|
|
174
191
|
raise _EMPTY_INTERSECTION_EXCEPTION(first_value, second_value)
|
|
175
192
|
elif isinstance(second_value, Constant):
|
|
176
|
-
intersection_with_constant(second_value, first_value)
|
|
193
|
+
return intersection_with_constant(second_value, first_value)
|
|
177
194
|
elif isinstance(second_value, Outside):
|
|
178
195
|
return intersection_with_outside(second_value, first_value)
|
|
179
196
|
else:
|
|
@@ -233,6 +250,9 @@ class Value:
|
|
|
233
250
|
else:
|
|
234
251
|
raise _INTERSECTION_WITH_WRONG_TYPE(self, other)
|
|
235
252
|
|
|
253
|
+
def print(self) -> str:
|
|
254
|
+
pass
|
|
255
|
+
|
|
236
256
|
|
|
237
257
|
class Interval(Value):
|
|
238
258
|
|
|
@@ -248,7 +268,7 @@ class Interval(Value):
|
|
|
248
268
|
def __repr__(self):
|
|
249
269
|
return f"Interval({self.lower:.2f}, {self.upper:.2f})"
|
|
250
270
|
|
|
251
|
-
def __eq__(self, other:
|
|
271
|
+
def __eq__(self, other: Interval) -> bool:
|
|
252
272
|
return (self.upper == other.upper) and (self.lower == other.lower) and (self.standard == other.standard)
|
|
253
273
|
|
|
254
274
|
|
|
@@ -264,6 +284,9 @@ class LessThan(Interval):
|
|
|
264
284
|
def value(self) -> float:
|
|
265
285
|
return self.upper
|
|
266
286
|
|
|
287
|
+
def print(self) -> str:
|
|
288
|
+
return f"below {round(self.upper, 1)}"
|
|
289
|
+
|
|
267
290
|
def __str__(self):
|
|
268
291
|
return f"]-∞, {self.upper:.2f}" + ("]" if self.standard else "[")
|
|
269
292
|
|
|
@@ -271,7 +294,8 @@ class LessThan(Interval):
|
|
|
271
294
|
return f"LessThan({self.upper:.2f})"
|
|
272
295
|
|
|
273
296
|
def __eq__(self, other: LessThan) -> bool:
|
|
274
|
-
return (
|
|
297
|
+
return isinstance(other, LessThan) and (self.upper == other.upper) and \
|
|
298
|
+
(self.value == other.value) and (self.standard == other.standard)
|
|
275
299
|
|
|
276
300
|
|
|
277
301
|
class GreaterThan(Interval):
|
|
@@ -286,6 +310,9 @@ class GreaterThan(Interval):
|
|
|
286
310
|
def value(self) -> float:
|
|
287
311
|
return self.lower
|
|
288
312
|
|
|
313
|
+
def print(self) -> str:
|
|
314
|
+
return f"above {round(self.lower, 1)}"
|
|
315
|
+
|
|
289
316
|
def __str__(self):
|
|
290
317
|
return ("]" if self.standard else "[") + f"{self.lower:.2f}, ∞["
|
|
291
318
|
|
|
@@ -293,7 +320,8 @@ class GreaterThan(Interval):
|
|
|
293
320
|
return f"GreaterThan({self.lower:.2f})"
|
|
294
321
|
|
|
295
322
|
def __eq__(self, other: GreaterThan) -> bool:
|
|
296
|
-
return (
|
|
323
|
+
return isinstance(other, GreaterThan) and (self.lower == other.lower) and \
|
|
324
|
+
(self.value == other.value) and (self.standard == other.standard)
|
|
297
325
|
|
|
298
326
|
|
|
299
327
|
class Between(Interval):
|
|
@@ -304,6 +332,9 @@ class Between(Interval):
|
|
|
304
332
|
def is_in(self, other: float) -> bool:
|
|
305
333
|
return self.lower <= other < self.upper if self.standard else self.lower < other <= self.upper
|
|
306
334
|
|
|
335
|
+
def print(self) -> str:
|
|
336
|
+
return f"between {round(self.lower, 1)} and {round(self.upper, 1)}"
|
|
337
|
+
|
|
307
338
|
def __str__(self):
|
|
308
339
|
return ("[" if self.standard else "]") + f"{self.lower:.2f}, {self.upper:.2f}" + ("[" if self.standard else "]")
|
|
309
340
|
|
|
@@ -319,6 +350,9 @@ class Outside(Interval):
|
|
|
319
350
|
def is_in(self, other: float) -> bool:
|
|
320
351
|
return other < self.lower or self.upper <= other if self.standard else other <= self.lower or self.upper < other
|
|
321
352
|
|
|
353
|
+
def print(self) -> str:
|
|
354
|
+
return f"not between {round(self.lower, 1)} and {round(self.upper, 1)}"
|
|
355
|
+
|
|
322
356
|
def __str__(self):
|
|
323
357
|
return f"]-∞, {self.lower:.2f}" + ("[" if self.standard else "]") + ' U '\
|
|
324
358
|
+ ("[" if self.standard else "]") + f"{self.upper:.2f}, ∞["
|
|
@@ -336,6 +370,9 @@ class Constant(Value):
|
|
|
336
370
|
def is_in(self, other: float) -> bool:
|
|
337
371
|
return math.isclose(other, self.value)
|
|
338
372
|
|
|
373
|
+
def print(self) -> str:
|
|
374
|
+
return f"equal {round(self.value, 1)}"
|
|
375
|
+
|
|
339
376
|
def __str__(self):
|
|
340
377
|
return "{" + str(self.value) + "}"
|
|
341
378
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
psyke/__init__.py,sha256=
|
|
2
|
-
psyke/hypercubepredictor.py,sha256=
|
|
1
|
+
psyke/__init__.py,sha256=MSMiwvVth4kqmGvQt6HiYNR3J4EdINe8PBmK8-DQVSo,18545
|
|
2
|
+
psyke/hypercubepredictor.py,sha256=MlSRLky6J1I07qKcH98c9WvEjFHGyBiz_LU9W_oDnqs,4572
|
|
3
3
|
psyke/clustering/__init__.py,sha256=36MokTVwwWR_-o0mesvXHaYEYVTK2pn2m0ZY4G3Y3qU,581
|
|
4
4
|
psyke/clustering/utils.py,sha256=S0YwCKyHVYp9qUAQVzCMrTwcQFPJ5TD14Jwn10DE-Z4,1616
|
|
5
5
|
psyke/clustering/cream/__init__.py,sha256=W6k7vdjuUdA_azYA4vb5JtpWrofhDJ0DbM2jsnRKzfw,2994
|
|
@@ -7,12 +7,12 @@ psyke/clustering/exact/__init__.py,sha256=s4MPvGZ6gle3X9WH3YFHOEdinGcXIXh-7EFRcE
|
|
|
7
7
|
psyke/extraction/__init__.py,sha256=ziZ8T9eAOZjKipepE5_j1zfZgyFPONjW8MGERSk83nI,743
|
|
8
8
|
psyke/extraction/cart/__init__.py,sha256=s8tr7jPhONgF0pfetHieFax2MD8cy6ddOS7dRWzZLjc,3579
|
|
9
9
|
psyke/extraction/cart/predictor.py,sha256=2-2mv5fI0lTwwfTaEonxKh0ZUdhxuIEE6OP_rJxgmqc,3019
|
|
10
|
-
psyke/extraction/hypercubic/__init__.py,sha256=
|
|
11
|
-
psyke/extraction/hypercubic/hypercube.py,sha256=
|
|
10
|
+
psyke/extraction/hypercubic/__init__.py,sha256=w_NmfSjh8fCWLDXVXpRLiAApq697cvUSPTgju-jtZCA,10620
|
|
11
|
+
psyke/extraction/hypercubic/hypercube.py,sha256=GKjplRl34BegrA3JclvlkrL7hXftdUUMXndmRFFoJic,25697
|
|
12
12
|
psyke/extraction/hypercubic/strategy.py,sha256=X-roIsfcpJyMdo2px5JtbhP7-XE-zUNkaEK7XGXoWA8,1636
|
|
13
13
|
psyke/extraction/hypercubic/utils.py,sha256=D2FN5CCm_T3h23DmLFoTnIcFo7LvIq__ktl4hjUqkcA,1525
|
|
14
14
|
psyke/extraction/hypercubic/cosmik/__init__.py,sha256=XQUvOtMFpR0vMHYtwIVl3G626HMqN8Clt6BqNm4nvFs,1880
|
|
15
|
-
psyke/extraction/hypercubic/creepy/__init__.py,sha256=
|
|
15
|
+
psyke/extraction/hypercubic/creepy/__init__.py,sha256=js91nWXOSJtRFFjnNzv4eDKnRYthe1GA1a-CGAaHzDM,1735
|
|
16
16
|
psyke/extraction/hypercubic/divine/__init__.py,sha256=ClO8CITKKXoo7nhlBJagR1yAachsxLHYQlqggl-9eGE,3665
|
|
17
17
|
psyke/extraction/hypercubic/gridex/__init__.py,sha256=o7tNU3JH8AqA2PRj839-rPb6zhwAdpaCVGC__0DH-b0,5543
|
|
18
18
|
psyke/extraction/hypercubic/gridrex/__init__.py,sha256=h9usK5tFqd6ngBmRydsgkfQ1jlcQKj2uG72Tr1puFHk,595
|
|
@@ -22,7 +22,7 @@ psyke/extraction/real/__init__.py,sha256=fFqiwgWTpu5Jx9lz5CdSfs1QyqWYFLQDG7tc5M6
|
|
|
22
22
|
psyke/extraction/real/utils.py,sha256=eHGU-Y0inn_8jrk9lMcuRUKXpsTkI-s_myXSWz4bALQ,2190
|
|
23
23
|
psyke/extraction/trepan/__init__.py,sha256=KpZpk0btCWV4bS-DOmpgpYscSQ5FEMyP54ekm7ZedME,6583
|
|
24
24
|
psyke/extraction/trepan/utils.py,sha256=iSUJ1ooNQT_VO1KfBZuIUeUsyUbGdQf_pSEE87vMeQg,2320
|
|
25
|
-
psyke/schema/__init__.py,sha256=
|
|
25
|
+
psyke/schema/__init__.py,sha256=66Jm4hk9s2ZBdXUF7tg43_zG0X6XicMYOPsBkXyY0wE,17444
|
|
26
26
|
psyke/tuning/__init__.py,sha256=I-07lLZb02DoIm9AGXPPPOkB55ANu8RU4TMy2j30Pxg,3574
|
|
27
27
|
psyke/tuning/crash/__init__.py,sha256=zIHEF75EFy_mRIieqzP04qKLG3GLsSc_mYZHpPfkzxU,2623
|
|
28
28
|
psyke/tuning/orchid/__init__.py,sha256=s64iABbteik27CrRPHSVHNZX25JKlDu7YYjhseOizxw,3618
|
|
@@ -33,8 +33,8 @@ psyke/utils/logic.py,sha256=7bbW6qcKof5PlqoQ0n5Kt3Obcot-KqGAvpE8rMXvEPE,12419
|
|
|
33
33
|
psyke/utils/metrics.py,sha256=Oo5BOonOSfo0qYsXWT5dmypZ7jiStByFC2MKEU0uMHg,2250
|
|
34
34
|
psyke/utils/plot.py,sha256=dE8JJ6tQ0Ezosid-r2jqAisREjFe5LqExRzsVi5Ns-c,7785
|
|
35
35
|
psyke/utils/sorted.py,sha256=C3CPW2JisND30BRk5c1sAAHs3Lb_wsRB2qZrYFuRnfM,678
|
|
36
|
-
psyke-0.8.
|
|
37
|
-
psyke-0.8.
|
|
38
|
-
psyke-0.8.
|
|
39
|
-
psyke-0.8.
|
|
40
|
-
psyke-0.8.
|
|
36
|
+
psyke-0.8.5.dist-info/LICENSE,sha256=KP9K6Hgezf_xdMFW7ORyKz9uA8Y8k52YJn292wcP-_E,11354
|
|
37
|
+
psyke-0.8.5.dist-info/METADATA,sha256=5BPTGj6knIWcdwIdQZaXQTPAsl4CLMADsXHZNU9J10w,8102
|
|
38
|
+
psyke-0.8.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
39
|
+
psyke-0.8.5.dist-info/top_level.txt,sha256=q1HglxOqqoIRukFtyis_ZNHczZg4gANRUPWkD7HAUTU,6
|
|
40
|
+
psyke-0.8.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|