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 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.copy())
47
- cube2 = self._find_cube(neighbor.copy())
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.copy())
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
- outputs = []
79
- different_prediction_reasons = []
79
+
80
+ counter_conditions = []
81
+
80
82
  for _, _, _, c in cubes:
81
- if c.output not in outputs:
82
- outputs.append(c.output)
83
- output += f"\nThe output may be {c.output} if"
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
- return prediction, different_prediction_reasons
97
-
98
- def __get_local_conditions(self, data: dict[str, float], cube: GenericCube) -> dict[list[Value]]:
99
- conditions = {d: [Between(*cube.dimensions[d])] for d in cube.dimensions}
100
- subcubes = cube.subcubes(self._hypercubes)
101
- for c in [c for c in subcubes if sum(c in sc and c != sc for sc in subcubes) == 0]:
102
- for d in [d for d in c.dimensions if d in data]:
103
- if c.dimensions[d][0] > data[d] or c.dimensions[d][1] < data[d]:
104
- conditions[d].append(Outside(*c.dimensions[d]))
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.copy())
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
- print("The extracted knowledge is not exhaustive; impossible to predict this instance")
111
- else:
112
- output = self._predict_from_cubes(data)
113
- print(f"The output is {output} because")
114
- conditions = self.__get_local_conditions(data, cube)
115
- for d in data.keys():
116
- simplified = HyperCubeExtractor.__simplify(conditions[d])
117
- for i, condition in enumerate(simplified):
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
- @staticmethod
129
- def __simplify(conditions):
130
- simplified = []
131
- for condition in conditions:
132
- to_add = True
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
- return [c for c in cubes if c in self and c != self]
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
- return HyperCube(self.dimensions.copy(), self._limits.copy(), self.output)
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 _interval_to_value(self, dimension, unscale):
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._interval_to_value(dim, unscale)) for dim in self.dimensions if dim not in ignore]
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
- return RegressionCube(self.dimensions.copy(), output=output)
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
- return ClassificationCube(self.dimensions.copy(), self._limits.copy(), self._output)
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, output: str | LinearRegression | float = 0.0):
495
- super().__init__(dimension=dimension, output=output)
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
- return ClosedCube(self.dimensions.copy(), output=self._output)
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
- return ClosedRegressionCube(self.dimensions.copy(), output=output)
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
- return ClosedClassificationCube(self.dimensions.copy(), output=self._output)
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,
@@ -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
- del data[dimension]
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: " + str(x) + ' and ' + str(y))
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.is_in(first_value.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 <= first_value.lower:
104
- return second_value
105
- elif first_value.is_in(second_value.value):
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
- raise _NOT_IMPLEMENTED_INTERSECTION(first_value, second_value)
123
+ return second_value
109
124
  elif isinstance(second_value, GreaterThan):
110
- if second_value.value >= first_value.lower:
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 in first_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
- else:
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 and second_value.upper >= first_value.upper:
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 and first_value.upper >= second_value.upper:
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: Between) -> bool:
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 (self.upper == other.upper) and (self.value == other.value) and (self.standard == other.standard)
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 (self.lower == other.lower) and (self.value == other.value) and (self.standard == other.standard)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: psyke
3
- Version: 0.8.3.dev2
3
+ Version: 0.8.5
4
4
  Summary: Python-based implementation of PSyKE, i.e. a Platform for Symbolic Knowledge Extraction
5
5
  Home-page: https://github.com/psykei/psyke-python
6
6
  Author: Matteo Magnini
@@ -1,5 +1,5 @@
1
- psyke/__init__.py,sha256=weEnLws23jzCu_wqZVfq-jVlikiGle14coSMrjUnt7w,18155
2
- psyke/hypercubepredictor.py,sha256=XceZtTy70QI0PaoCuowvnJWZWcF3NoWF4Khd3qXHCgk,4507
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=34ZB7zwLs3NKaJBKlpxytnyfGtOl9LVdZX3ppxZrN8k,12042
11
- psyke/extraction/hypercubic/hypercube.py,sha256=Ws0tky0CMvCVuIwBJNxmY97xsG7VapwLQ2uSwpWlb1g,24510
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=8bT0zVsJs9odlVuMIzQUzMwUpfcFwbULzm7dlbhyJkg,1730
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=gOUWx3gYSkRehlJ5opK0Q16-Tv5fwSTl19k7kzIHALU,15760
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.3.dev2.dist-info/LICENSE,sha256=KP9K6Hgezf_xdMFW7ORyKz9uA8Y8k52YJn292wcP-_E,11354
37
- psyke-0.8.3.dev2.dist-info/METADATA,sha256=yEgkd3-2d4UZVtOi0DLoEmiN8wytmsmxEH62tL0i-ZI,8107
38
- psyke-0.8.3.dev2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
39
- psyke-0.8.3.dev2.dist-info/top_level.txt,sha256=q1HglxOqqoIRukFtyis_ZNHczZg4gANRUPWkD7HAUTU,6
40
- psyke-0.8.3.dev2.dist-info/RECORD,,
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,,