flamapy-bdd 2.0.0.dev1__tar.gz → 2.0.0.dev7__tar.gz

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.
Files changed (57) hide show
  1. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/PKG-INFO +1 -1
  2. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +74 -51
  3. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_commonality_factor.py +2 -0
  4. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations.py +4 -3
  5. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations_number.py +4 -3
  6. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +39 -24
  7. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_homogeneity.py +2 -2
  8. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_metrics.py +11 -9
  9. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +14 -15
  10. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_sampling.py +13 -8
  11. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_unique_features.py +11 -10
  12. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_variability.py +1 -1
  13. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +1 -1
  14. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/_bdd_writer.py +2 -1
  15. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/dddmp_reader.py +5 -5
  16. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/dddmp_writer.py +2 -2
  17. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +2 -2
  18. flamapy-bdd-2.0.0.dev7/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +195 -0
  19. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/json_writer.py +4 -2
  20. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pickle_writer.py +3 -3
  21. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/sat_to_bdd.py +7 -1
  22. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/PKG-INFO +1 -1
  23. flamapy-bdd-2.0.0.dev7/flamapy_bdd.egg-info/dependency_links.txt +1 -0
  24. flamapy-bdd-2.0.0.dev7/flamapy_bdd.egg-info/requires.txt +11 -0
  25. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/setup.py +14 -19
  26. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +0 -136
  27. flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/dependency_links.txt +0 -1
  28. flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/requires.txt +0 -11
  29. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/README.md +0 -0
  30. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/__init__.py +0 -0
  31. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/__init__.py +0 -0
  32. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/utils/__init__.py +0 -0
  33. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/utils/txtcnf.py +0 -0
  34. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/__init__.py +0 -0
  35. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_core_features.py +0 -0
  36. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_dead_features.py +0 -0
  37. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_pure_optional_features.py +0 -0
  38. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_satisfiable.py +0 -0
  39. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_variant_features.py +0 -0
  40. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +0 -0
  41. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/commonality_factor.py +0 -0
  42. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/feature_inclusion_probability.py +0 -0
  43. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/homogeneity.py +0 -0
  44. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/product_distribution.py +0 -0
  45. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/pure_optional_features.py +0 -0
  46. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/unique_features.py +0 -0
  47. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/variability.py +0 -0
  48. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/variant_features.py +0 -0
  49. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_cnf.py +0 -0
  50. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/json_reader.py +0 -0
  51. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pdf_writer.py +0 -0
  52. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pickle_reader.py +0 -0
  53. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/png_writer.py +0 -0
  54. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/svg_writer.py +0 -0
  55. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/SOURCES.txt +0 -0
  56. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/top_level.txt +0 -0
  57. {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flamapy-bdd
3
- Version: 2.0.0.dev1
3
+ Version: 2.0.0.dev7
4
4
  Summary: bdd-plugin for the automated analysis of feature models
5
5
  Home-page: https://github.com/flamapy/bdd_metamodel
6
6
  Author: Flamapy
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Optional, Any
2
+ from typing import Optional, Any, Union
3
3
 
4
4
  # Low-level interface to pure Python implementation (wrapped by dd.autoref.BDD).
5
5
  import dd.bdd as _dd_bdd
@@ -33,57 +33,76 @@ class BDDModel(VariabilityModel):
33
33
 
34
34
  def __init__(self) -> None:
35
35
  self._bdd: _bdd.BDD = _bdd.BDD() # BDD manager
36
- self._root: Optional[_bdd.Function | int] = None
37
- self._variables: list[Any] = []
36
+ self._root: Optional[Union[_bdd.Function, int]] = None
37
+ self.features_variables: dict[Any, Any] = {}
38
+ self.variables_features: dict[Any, Any] = {}
38
39
  self._levels_variables: dict[int, Any] = {}
40
+ self._formula: Any = None
41
+
42
+ def build_bdd(self, expression: str) -> None:
43
+ """Built a BDD from an expression representing a logical formula.
44
+
45
+ It assumes that all variables have been added to
46
+ features_variables and variables_features.
47
+ """
48
+ self._formula = expression
49
+ self._bdd.declare(*self.variables_features.keys())
50
+ self._root = self._bdd.add_expr(expression)
51
+ self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
52
+
53
+ @property
54
+ def formula(self) -> str:
55
+ return self._formula
39
56
 
40
57
  @property
41
- def bdd(self) -> _bdd.BDD | _dd_bdd.BDD:
58
+ def bdd(self) -> Union[_bdd.BDD, _dd_bdd.BDD]:
42
59
  return self._bdd
43
60
 
44
61
  @bdd.setter
45
- def bdd(self, new_bdd: _bdd.BDD | _dd_bdd.BDD) -> None:
62
+ def bdd(self, new_bdd: Union[_bdd.BDD, _dd_bdd.BDD]) -> None:
46
63
  self._bdd = new_bdd
47
- self._variables = list(self._bdd.vars)
64
+ self.features_variables = {var: var for var in self._bdd.vars}
65
+ self.variables_features = dict(self.features_variables)
48
66
  self._root = next(iter(self._bdd.roots), None)
49
67
  self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
50
68
 
51
69
  @property
52
- def root(self) -> _bdd.Function | int:
70
+ def root(self) -> Union[_bdd.Function, int]:
53
71
  return self._root
54
72
 
55
73
  @root.setter
56
- def root(self, new_root: _bdd.Function | int) -> None:
74
+ def root(self, new_root: Union[_bdd.Function, int]) -> None:
57
75
  self._root = new_root
58
- self._variables = list(self._bdd.vars)
76
+ self.features_variables = {var: var for var in self._bdd.vars}
77
+ self.variables_features = dict(self.features_variables)
59
78
  self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
60
79
 
61
- @property
62
- def variables(self) -> list[Any]:
63
- return self._variables
64
-
65
- @classmethod
66
- def from_logic_formula(cls, formula: str, variables: list[Any]) -> 'BDDModel':
67
- """Build the BDD from a logic formula, and the list of variables.
68
-
69
- The logic formula can be a CNF formula or a propositional logic formula.
70
- """
71
- bdd_model = cls()
72
- # Store variables
73
- bdd_model._variables = variables
74
- # Declare variables
75
- bdd_model._bdd.declare(*variables)
76
- # Build the BDD
77
- bdd_model._root = bdd_model._bdd.add_expr(formula)
78
-
79
- # Reorder for optimization
80
- # Warning! Reordering may make the root starting to level > 0, and thus,
81
- # operations won't work correctly.
82
- # bdd_model._bdd.reorder()
83
-
84
- # Levels and variables (dict for optimization)
85
- bdd_model._levels_variables = {l: v for v, l in bdd_model._bdd.var_levels.items()}
86
- return bdd_model
80
+ # @classmethod
81
+ # def from_logic_formula(cls,
82
+ # formula: str,
83
+ # variables: list[Any]) -> 'BDDModel':
84
+ # """Build the BDD from a logic formula, and the list of variables.
85
+
86
+ # The logic formula can be a CNF formula or a propositional logic formula.
87
+ # An optional features_variables and variables_features mapping can be provided
88
+ # to track safe features' and variables' names.
89
+ # """
90
+ # bdd_model = cls()
91
+ # # Store variables
92
+ # bdd_model._variables = variables
93
+ # # Declare variables
94
+ # bdd_model._bdd.declare(*variables)
95
+ # # Build the BDD
96
+ # bdd_model._root = bdd_model._bdd.add_expr(formula)
97
+
98
+ # # Reorder for optimization
99
+ # # Warning! Reordering may make the root starting to level > 0, and thus,
100
+ # # operations won't work correctly.
101
+ # #bdd_model._bdd.reorder()
102
+
103
+ # # Levels and variables (dict for optimization)
104
+ # bdd_model._levels_variables = {l: v for v, l in bdd_model._bdd.var_levels.items()}
105
+ # return bdd_model
87
106
 
88
107
  def level_of_var(self, var: Any) -> Optional[int]:
89
108
  """Return the level of a given variable."""
@@ -93,7 +112,7 @@ class BDDModel(VariabilityModel):
93
112
  """Return the variable at the given level."""
94
113
  return self._levels_variables.get(level, None)
95
114
 
96
- def var(self, node: _bdd.Function | int) -> Optional[Any]:
115
+ def var(self, node: Union[_bdd.Function, int]) -> Optional[Any]:
97
116
  """Return the variable of the node.
98
117
 
99
118
  It returns None if the node is a terminal node.
@@ -105,7 +124,7 @@ class BDDModel(VariabilityModel):
105
124
  return self.var_at_level(level)
106
125
  return node.var
107
126
 
108
- def level(self, node: _bdd.Function | int) -> Optional[int]:
127
+ def level(self, node: Union[_bdd.Function, int]) -> Optional[int]:
109
128
  """Return the level of the node.
110
129
 
111
130
  Non-terminal nodes start at 0.
@@ -118,11 +137,11 @@ class BDDModel(VariabilityModel):
118
137
  """Return number of nodes in the BDD."""
119
138
  return len(self._bdd)
120
139
 
121
- def get_node(self, var: Any) -> _bdd.Function | int:
140
+ def get_node(self, var: Any) -> Union[_bdd.Function, int]:
122
141
  """Return the node of the variable."""
123
142
  return self._bdd.var(var)
124
143
 
125
- def index(self, node: _bdd.Function | int) -> Optional[int]:
144
+ def index(self, node: Union[_bdd.Function, int]) -> Optional[int]:
126
145
  """Position (index) of the variable that labels the node `n` in the ordering.
127
146
 
128
147
  Indexes start at 1.
@@ -133,45 +152,49 @@ class BDDModel(VariabilityModel):
133
152
  thus level(n4) = 2.
134
153
  """
135
154
  if self.is_terminal_node(node):
136
- return len(self.variables) + 1
155
+ return len(self.variables_features) + 1
137
156
  level = self.level(node)
138
157
  return level + 1 if level is not None else None
139
158
 
140
- def negated(self, node: _bdd.Function | int) -> bool:
159
+ def negated(self, node: Union[_bdd.Function, int]) -> bool:
141
160
  """Return whether the node is negated."""
142
161
  if isinstance(node, int):
143
162
  return node < 0
144
163
  return node.negated
145
164
 
146
- def get_terminal_node_n0(self) -> _bdd.Function | int:
165
+ def get_terminal_node_n0(self) -> Union[_bdd.Function, int]:
147
166
  return self._bdd.false
148
167
 
149
- def get_terminal_node_n1(self) -> _bdd.Function | int:
168
+ def get_terminal_node_n1(self) -> Union[_bdd.Function, int]:
150
169
  return self._bdd.true
151
170
 
152
- def is_terminal_node(self, node: _bdd.Function | int) -> bool:
171
+ def is_terminal_node(self, node: Union[_bdd.Function, int]) -> bool:
153
172
  """Check if the node is a terminal node."""
154
173
  return self.is_terminal_n0(node) or self.is_terminal_n1(node)
155
174
 
156
- def is_terminal_n1(self, node: _bdd.Function | int) -> bool:
175
+ def is_terminal_n1(self, node: Union[_bdd.Function, int]) -> bool:
157
176
  """Check if the node is the terminal node 1 (n1)."""
158
177
  return node == self.get_terminal_node_n1()
159
178
 
160
- def is_terminal_n0(self, node: _bdd.Function | int) -> bool:
179
+ def is_terminal_n0(self, node: Union[_bdd.Function, int]) -> bool:
161
180
  """Check if the node is the terminal node 0 (n0)."""
162
181
  return node == self.get_terminal_node_n0()
163
182
 
164
- def get_high_node(self, node: _bdd.Function | int) -> Optional[_bdd.Function | int]:
183
+ def get_high_node(self,
184
+ node: Union[_bdd.Function, int]
185
+ ) -> Optional[Union[_bdd.Function, int]]:
165
186
  """Return the high (right, solid) node."""
166
187
  _, _, high = self._bdd.succ(node)
167
188
  return high
168
189
 
169
- def get_low_node(self, node: _bdd.Function | int) -> Optional[_bdd.Function | int]:
190
+ def get_low_node(self,
191
+ node: Union[_bdd.Function, int]
192
+ ) -> Optional[Union[_bdd.Function, int]]:
170
193
  """Return the low (left, dashed) node."""
171
194
  _, low, _ = self._bdd.succ(node)
172
195
  return low
173
196
 
174
- def get_value(self, node: _bdd.Function | int, complemented: bool = False) -> int:
197
+ def get_value(self, node: Union[_bdd.Function, int], complemented: bool = False) -> int:
175
198
  """Return the value (id) of the node considering complemented arcs."""
176
199
  value = int(node)
177
200
  if self.is_terminal_n0(node):
@@ -180,7 +203,7 @@ class BDDModel(VariabilityModel):
180
203
  value = 0 if complemented else 1
181
204
  return value
182
205
 
183
- def pretty_node_str(self, node: _bdd.Function | int) -> str:
206
+ def pretty_node_str(self, node: Union[_bdd.Function, int]) -> str:
184
207
  return f'{self.var(node)} ' \
185
208
  f'(id: {self.get_value(node)}) ' \
186
209
  f'(level: {self.level(node)}) ' \
@@ -192,7 +215,7 @@ class BDDModel(VariabilityModel):
192
215
  result += f'#Nodes: {self.nof_nodes()}\n'
193
216
  result += f'Root: {self.pretty_node_str(self.root)}\n'
194
217
  levels_vars = dict(sorted(self._levels_variables.items(), key=lambda item: item[0]))
195
- for level, var in levels_vars.items():
218
+ for _level, var in levels_vars.items():
196
219
  node = self.get_node(var)
197
220
  result += f' |-{self.pretty_node_str(node)}\n'
198
221
  result += f'Terminal node (n0): {self.pretty_node_str(self.get_terminal_node_n0())}\n'
@@ -31,6 +31,8 @@ class BDDCommonalityFactor(CommonalityFactor):
31
31
  def commonality_factor(bdd_model: BDDModel, config: Configuration) -> float:
32
32
  configs_number_op = BDDConfigurationsNumber()
33
33
  total_configs = configs_number_op.execute(bdd_model).get_result()
34
+ if total_configs == 0:
35
+ return 0.0
34
36
  configs_number_op.set_partial_configuration(config)
35
37
  n_configs = configs_number_op.execute(bdd_model).get_result()
36
38
  return n_configs / total_configs
@@ -35,17 +35,18 @@ def configurations(bdd_model: BDDModel,
35
35
  partial_config: Optional[Configuration] = None) -> list[Configuration]:
36
36
  if partial_config is None:
37
37
  u_func = bdd_model.root
38
- care_vars = set(bdd_model.variables)
38
+ care_vars = set(bdd_model.variables_features)
39
39
  elements = {}
40
40
  else:
41
41
  values = dict(partial_config.elements.items())
42
42
  u_func = bdd_model.bdd.let(values, bdd_model.root)
43
- care_vars = set(bdd_model.variables) - set(values.keys())
43
+ care_vars = set(bdd_model.variables_features) - set(values.keys())
44
44
  elements = partial_config.elements
45
45
 
46
46
  configs = []
47
47
  for assignment in bdd_model.bdd.pick_iter(u_func, care_vars=care_vars):
48
- features = {f: True for f in assignment.keys() if assignment[f]}
48
+ features = {bdd_model.variables_features[f]: True
49
+ for f in assignment.keys() if assignment[f]}
49
50
  features = features | elements
50
51
  configs.append(Configuration(features))
51
52
  return configs
@@ -35,9 +35,10 @@ def configurations_number(bdd_model: BDDModel,
35
35
  partial_configuration: Optional[Configuration] = None) -> int:
36
36
  if partial_configuration is None:
37
37
  u_func = bdd_model.root
38
- n_vars = len(bdd_model.variables)
38
+ n_vars = len(bdd_model.variables_features)
39
39
  else:
40
- values = dict(partial_configuration.elements.items())
40
+ values = {bdd_model.features_variables[f]: selected
41
+ for f, selected in partial_configuration.elements.items()}
41
42
  u_func = bdd_model.bdd.let(values, bdd_model.root)
42
- n_vars = len(bdd_model.variables) - len(values)
43
+ n_vars = len(bdd_model.variables_features) - len(values)
43
44
  return int(bdd_model.bdd.count(u_func, nvars=n_vars))
@@ -42,27 +42,28 @@ def feature_inclusion_probability(bdd_model: BDDModel,
42
42
  n_configs_op.set_partial_configuration(config)
43
43
  total_configs = n_configs_op.execute(bdd_model).get_result()
44
44
  if total_configs == 0:
45
- return {feature: 0.0 for feature in bdd_model.variables}
45
+ return {feature: 0.0 for feature in bdd_model.features_variables}
46
46
 
47
47
  prob: dict[Any, float] = defaultdict(float)
48
48
  if config is None:
49
- for feature in bdd_model.variables:
50
- values = {feature: True}
49
+ for variable, feature in bdd_model.variables_features.items():
50
+ values = {variable: True}
51
51
  u_func = bdd_model.bdd.let(values, bdd_model.root)
52
- n_vars = len(bdd_model.variables) - len(values)
52
+ n_vars = len(bdd_model.variables_features) - len(values)
53
53
  prob[feature] = bdd_model.bdd.count(u_func, nvars=n_vars) / total_configs
54
54
  else:
55
- values = dict(config.elements.items())
56
- for feature in bdd_model.variables:
57
- feature_selected = values.get(feature, None)
58
- values = {feature: True}
55
+ values = {bdd_model.features_variables[f]: selected
56
+ for f, selected in config.elements.items()}
57
+ for variable, feature in bdd_model.variables_features.items():
58
+ feature_selected = values.get(variable, None)
59
+ values = {variable: True}
59
60
  u_func = bdd_model.bdd.let(values, bdd_model.root)
60
- n_vars = len(bdd_model.variables) - len(values)
61
+ n_vars = len(bdd_model.variables_features) - len(values)
61
62
  prob[feature] = bdd_model.bdd.count(u_func, nvars=n_vars) / total_configs
62
63
  if feature_selected is None:
63
- values.pop(feature)
64
+ values.pop(variable)
64
65
  else:
65
- values[feature] = feature_selected
66
+ values[variable] = feature_selected
66
67
  return prob
67
68
 
68
69
 
@@ -88,7 +89,8 @@ def feature_inclusion_probability(bdd_model: BDDModel,
88
89
  # joint_prob_nodes_phi: dict[tuple[int, Any], float] = defaultdict(float)
89
90
  # cond_prob: dict[tuple[int, Any], float] = defaultdict(float)
90
91
  # mark: dict[int, bool] = defaultdict(bool)
91
- # get_joint_pr(bdd_model, root, prob, joint_prob, joint_prob_nodes_n, joint_prob_nodes_phi, cond_prob, mark, bdd_model.negated(root))
92
+ # get_joint_pr(bdd_model, root, prob, joint_prob, joint_prob_nodes_n,
93
+ # joint_prob_nodes_phi, cond_prob, mark, bdd_model.negated(root))
92
94
  # print(f'COND PROBS: {len(cond_prob)}')
93
95
  # for v, p in cond_prob.items():
94
96
  # print(f'Value (id): {v} -> {p}')
@@ -100,8 +102,10 @@ def feature_inclusion_probability(bdd_model: BDDModel,
100
102
  # print(f'Index: {v} -> {p}')
101
103
  # #total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1())]
102
104
  # #total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1())]
103
- # #total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1(), bdd_model.negated(root))]
104
- # total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1(), bdd_model.negated(root))]
105
+ # #total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1(),
106
+ # bdd_model.negated(root))]
107
+ # total_prob = prob[bdd_model.get_value(bdd_model.get_terminal_node_n1(),
108
+ # bdd_model.negated(root))]
105
109
  # print(f'Total Prob: {total_prob}')
106
110
  # fip: dict[Any, float] = {}
107
111
  # print(bdd_model.bdd.var_levels)
@@ -131,7 +135,8 @@ def feature_inclusion_probability(bdd_model: BDDModel,
131
135
  # else:
132
136
  # prob[id_low] = prob[id_low] + (prob[id_node] / 2)
133
137
  # if mark[id_node] != mark[id_low]:
134
- # get_node_pr(bdd_model, low, prob, mark, complemented ^ bdd_model.negated(low))
138
+ # get_node_pr(bdd_model, low, prob, mark,
139
+ # complemented ^ bdd_model.negated(low))
135
140
 
136
141
  # # explore high
137
142
  # high = bdd_model.get_high_node(node)
@@ -141,7 +146,8 @@ def feature_inclusion_probability(bdd_model: BDDModel,
141
146
  # else:
142
147
  # prob[id_high] = prob[id_high] + (prob[id_node] / 2)
143
148
  # if mark[id_node] != mark[id_high]:
144
- # get_node_pr(bdd_model, high, prob, mark, complemented ^ bdd_model.negated(high))
149
+ # get_node_pr(bdd_model, high, prob, mark,
150
+ # complemented ^ bdd_model.negated(high))
145
151
 
146
152
 
147
153
  # def get_joint_pr(bdd_model: BDDModel,
@@ -167,10 +173,15 @@ def feature_inclusion_probability(bdd_model: BDDModel,
167
173
  # cond_prob[(id_node, False)] = 1.0
168
174
  # else:
169
175
  # if mark[id_node] != mark[id_low]:
170
- # get_joint_pr(bdd_model, low, prob, joint_prob, joint_prob_nodes_n, joint_prob_nodes_phi, cond_prob, mark, complemented ^ bdd_model.negated(low))
171
- # cond_prob[(id_node, False)] = joint_prob_nodes_phi[(id_low, None)] / (2 * prob[id_low])
172
- # joint_prob_nodes_n[(id_node, False)] = cond_prob[(id_node, False)] * prob[id_node]
173
-
176
+ # get_joint_pr(bdd_model, low, prob, joint_prob,
177
+ # joint_prob_nodes_n, joint_prob_nodes_phi,
178
+ # cond_prob, mark, complemented ^ bdd_model.negated(low))
179
+ # cond_prob[(id_node, False)] = joint_prob_nodes_phi[
180
+ # (id_low, None)] /
181
+ # (2 * prob[id_low])
182
+ # joint_prob_nodes_n[(id_node, False)] = cond_prob[
183
+ # (id_node, False)] *
184
+ # prob[id_node]
174
185
  # # explore high
175
186
  # high = bdd_model.get_high_node(node)
176
187
  # id_high = bdd_model.get_value(high, complemented)
@@ -180,13 +191,17 @@ def feature_inclusion_probability(bdd_model: BDDModel,
180
191
  # cond_prob[(id_node, True)] = 1.0
181
192
  # else:
182
193
  # if mark[id_node] != mark[id_high]:
183
- # get_joint_pr(bdd_model, high, prob, joint_prob, joint_prob_nodes_n, joint_prob_nodes_phi, cond_prob, mark, complemented ^ bdd_model.negated(high))
184
- # cond_prob[(id_node, True)] = joint_prob_nodes_phi[(id_high, None)] / (2 * prob[id_high])
194
+ # get_joint_pr(bdd_model, high, prob, joint_prob, joint_prob_nodes_n,
195
+ # joint_prob_nodes_phi, cond_prob, mark, complemented ^ bdd_model.negated(high))
196
+ # cond_prob[(id_node, True)] = joint_prob_nodes_phi[(id_high, None)]
197
+ # / (2 * prob[id_high])
185
198
  # joint_prob_nodes_n[(id_node, True)] = cond_prob[(id_node, True)] * prob[id_node]
186
199
 
187
200
  # # Combine both low and high
188
- # joint_prob_nodes_phi[(id_node, None)] = joint_prob_nodes_phi[(id_node, True)] + joint_prob_nodes_phi[(id_node, False)]
189
- # joint_prob[bdd_model.index(node)] = prob[bdd_model.index(node)] + joint_prob_nodes_n[(id_node, True)]
201
+ # joint_prob_nodes_phi[(id_node, None)] = joint_prob_nodes_phi[(id_node, True)] +
202
+ # joint_prob_nodes_phi[(id_node, False)]
203
+ # joint_prob[bdd_model.index(node)] = prob[bdd_model.index(node)]
204
+ # + joint_prob_nodes_n[(id_node, True)]
190
205
 
191
206
  # # Add joint probabilities of the removed nodes
192
207
  # for xj in range(bdd_model.index(node) + 1, bdd_model.index(high)):
@@ -28,8 +28,8 @@ def homogeneity(bdd_model: BDDModel) -> float:
28
28
  commonality_sum = 0.0
29
29
  commonality_op = BDDCommonalityFactor()
30
30
 
31
- for feature in bdd_model.variables:
31
+ for feature in bdd_model.features_variables:
32
32
  config = Configuration(elements={feature: True})
33
33
  commonality_op.set_configuration(config)
34
34
  commonality_sum += commonality_op.execute(bdd_model).get_result()
35
- return commonality_sum / len(bdd_model.variables)
35
+ return commonality_sum / len(bdd_model.variables_features)
@@ -7,7 +7,7 @@ from flamapy.metamodels.bdd_metamodel.models import BDDModel
7
7
  from flamapy.metamodels.bdd_metamodel import operations as bdd_operations
8
8
 
9
9
 
10
- def metric_method(func: Callable) -> Callable:
10
+ def metric_method(func: Callable[..., Any]) -> Callable[..., Any]:
11
11
  """Decorator to mark a method as a metric method.
12
12
  It has the value of the measure, it can also have a size and a ratio.
13
13
  Example:
@@ -23,8 +23,8 @@ def metric_method(func: Callable) -> Callable:
23
23
  return func
24
24
 
25
25
 
26
- class BDDMetrics(Metrics):
27
-
26
+ class BDDMetrics(Metrics):
27
+ # pylint: disable=too-many-instance-attributes
28
28
  def __init__(self) -> None:
29
29
  super().__init__()
30
30
  self.model: Optional[VariabilityModel] = None
@@ -46,11 +46,13 @@ class BDDMetrics(Metrics):
46
46
  #Do some basic calculations to speedup the rest
47
47
  self._features = self.model.variables
48
48
  self._configurations_number = bdd_operations.BDDConfigurationsNumber() \
49
- .execute(self.model).get_result()
49
+ .execute(self.model) \
50
+ .get_result()
50
51
  self._prod_dist_op = bdd_operations.BDDProductDistribution()
51
52
  self._prod_dist = self._prod_dist_op.execute(self.model).get_result()
52
53
  self._fip = bdd_operations.BDDFeatureInclusionProbability() \
53
- .execute(self.model).get_result()
54
+ .execute(self.model) \
55
+ .get_result()
54
56
  self._variant_features = [feat for feat, prob, in self._fip.items() if 0.0 < prob < 1.0]
55
57
  # Get all methods that are marked with the metric_method decorator
56
58
  metric_methods = [getattr(self, method_name) for method_name in dir(self)
@@ -94,7 +96,7 @@ class BDDMetrics(Metrics):
94
96
  result=_dead_features,
95
97
  size=len(_dead_features),
96
98
  ratio=self.get_ratio(_dead_features, self._features, 2))
97
-
99
+
98
100
  @metric_method
99
101
  def pure_optional_features(self) -> dict[str, Any]:
100
102
  """Pure optional features are those feature with 0.5 (50%) probability of being selected
@@ -139,7 +141,7 @@ class BDDMetrics(Metrics):
139
141
  size=len(_unique_features),
140
142
  ratio=self.get_ratio(_unique_features,
141
143
  self._features, 2))
142
-
144
+
143
145
  @metric_method
144
146
  def total_variability(self) -> dict[str, Any]:
145
147
  """The total variability of an SPL, considered as a measure of its flexibility, is the
@@ -165,7 +167,7 @@ class BDDMetrics(Metrics):
165
167
  return self.construct_result(name=name,
166
168
  doc=self.partial_variability.__doc__,
167
169
  result=_partial_variability)
168
-
170
+
169
171
  @metric_method
170
172
  def homogeneity(self) -> dict[str, Any]:
171
173
  """The homogeneity of an SPL is the commonality mean, i.e., the sum of the commonality
@@ -177,7 +179,7 @@ class BDDMetrics(Metrics):
177
179
  return self.construct_result(name=name,
178
180
  doc=self.homogeneity.__doc__,
179
181
  result=_homogeneity)
180
-
182
+
181
183
  @metric_method
182
184
  def configurations(self) -> dict[str, Any]:
183
185
  """Configurations represented by the feature model."""
@@ -1,6 +1,6 @@
1
1
  import math
2
2
  from collections import defaultdict
3
- from typing import cast, Any
3
+ from typing import cast, Any, Optional
4
4
 
5
5
  from flamapy.core.models import VariabilityModel
6
6
  from flamapy.metamodels.bdd_metamodel.models import BDDModel
@@ -54,7 +54,8 @@ def product_distribution(bdd_model: BDDModel) -> list[int]:
54
54
  mark: dict[int, bool] = defaultdict(bool)
55
55
  get_prod_dist(bdd_model, root, dist, mark, bdd_model.negated(root))
56
56
  # Complete distribution
57
- distribution = dist[id_root] + [0] * (len(bdd_model.variables) + 1 - len(dist[id_root]))
57
+ distribution = dist[id_root] + [0] * \
58
+ (len(bdd_model.variables_features) + 1 - len(dist[id_root]))
58
59
  return distribution
59
60
 
60
61
 
@@ -94,6 +95,7 @@ def get_prod_dist(bdd_model: BDDModel,
94
95
  for j in range(len(dist[id_high])):
95
96
  high_dist[i + j] = high_dist[i + j] + dist[id_high][j] * (
96
97
  math.comb(removed_nodes, i))
98
+ # combine low and high distributions
97
99
  combine_distributions(id_node, dist, low_dist, high_dist)
98
100
 
99
101
 
@@ -101,7 +103,6 @@ def combine_distributions(id_node: int,
101
103
  dist: dict[int, list[int]],
102
104
  low_dist: list[int],
103
105
  high_dist: list[int]) -> None:
104
- # combine low and high distributions
105
106
  if len(low_dist) > len(high_dist):
106
107
  dist_length = len(low_dist)
107
108
  else:
@@ -115,7 +116,7 @@ def combine_distributions(id_node: int,
115
116
  dist[id_node] = node_dist
116
117
 
117
118
 
118
- def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
119
+ def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]: # noqa: MC0001
119
120
  total_elements = sum(prod_dist)
120
121
  if total_elements == 0:
121
122
  return {
@@ -131,20 +132,19 @@ def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
131
132
 
132
133
  total_sum = 0
133
134
  running_total = 0
134
- median1 = None
135
- median2 = None
135
+ median1: Optional[float] = None
136
+ median2: Optional[float] = None
136
137
  median_pos1 = (total_elements + 1) // 2
137
138
  median_pos2 = (total_elements + 2) // 2
138
139
  min_val = None
139
140
  max_val = None
140
141
  mode = None
141
- mode_count = 0
142
142
 
143
- sum_squared_diff = 0
144
- abs_deviation_total = 0
143
+ sum_squared_diff = 0.0
144
+ abs_deviation_total = 0.0
145
145
  abs_deviation_running_total = 0
146
- mad1 = None
147
- mad2 = None
146
+ mad1: Optional[float] = None
147
+ mad2: Optional[float] = None
148
148
  mad_pos1 = (total_elements + 1) // 2
149
149
  mad_pos2 = (total_elements + 2) // 2
150
150
 
@@ -157,9 +157,8 @@ def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
157
157
  total_sum += i * count
158
158
  running_total += count
159
159
 
160
- if mode is None or count > mode_count:
160
+ if mode is None:
161
161
  mode = i
162
- mode_count = count
163
162
 
164
163
  if median1 is None and running_total >= median_pos1:
165
164
  median1 = i
@@ -167,7 +166,7 @@ def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
167
166
  median2 = i
168
167
 
169
168
  mean = total_sum / total_elements
170
- median = (median1 + median2) / 2
169
+ median = (median1 + median2) / 2 if median1 is not None and median2 is not None else 0
171
170
 
172
171
  running_total = 0
173
172
  for i, count in enumerate(prod_dist):
@@ -199,4 +198,4 @@ def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
199
198
  'Max': max_val,
200
199
  'Range': max_val - min_val if min_val is not None and max_val is not None else 0
201
200
  }
202
- return statistics
201
+ return statistics
@@ -75,19 +75,23 @@ def sample(model: BDDModel,
75
75
  def random_configuration(bdd_model: BDDModel,
76
76
  p_config: Optional[Configuration] = None) -> Configuration:
77
77
  # Initialize the configurations and values for BDD nodes with already known features
78
- values = {} if p_config is None else dict(p_config.elements.items())
78
+ if p_config is None:
79
+ values = {}
80
+ else:
81
+ values = {bdd_model.features_variables[f]: selected
82
+ for f, selected in p_config.elements.items()}
79
83
 
80
84
  # Set the BDD nodes with the already known features values
81
85
  u_func = bdd_model.bdd.let(values, bdd_model.root)
82
86
 
83
- care_vars = set(bdd_model.variables) - values.keys()
87
+ care_vars = set(bdd_model.variables_features.keys()) - values.keys()
84
88
  n_vars = len(care_vars)
85
- for feature in care_vars:
89
+ for variable in care_vars:
86
90
  # Number of configurations with the feature selected
87
- v_sel = bdd_model.bdd.let({feature: True}, u_func)
91
+ v_sel = bdd_model.bdd.let({variable: True}, u_func)
88
92
  nof_configs_var_selected = bdd_model.bdd.count(v_sel, nvars=n_vars - 1)
89
93
  # Number of configurations with the feature unselected
90
- v_unsel = bdd_model.bdd.let({feature: False}, u_func)
94
+ v_unsel = bdd_model.bdd.let({variable: False}, u_func)
91
95
  nof_configs_var_unselected = bdd_model.bdd.count(v_unsel, nvars=n_vars - 1)
92
96
 
93
97
  # Randomly select or not the feature
@@ -96,8 +100,9 @@ def random_configuration(bdd_model: BDDModel,
96
100
  k=1)[0]
97
101
 
98
102
  # Update configuration and BDD node for the new feature
99
- values[feature] = selected
100
- u_func = bdd_model.bdd.let({feature: selected}, u_func)
103
+ values[variable] = selected
104
+ u_func = bdd_model.bdd.let({variable: selected}, u_func)
101
105
 
102
106
  n_vars -= 1
103
- return Configuration(values)
107
+ return Configuration({bdd_model.variables_features[v]: selected
108
+ for v, selected in values.items()})