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.
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/PKG-INFO +1 -1
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +74 -51
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_commonality_factor.py +2 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations.py +4 -3
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations_number.py +4 -3
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +39 -24
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_homogeneity.py +2 -2
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_metrics.py +11 -9
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +14 -15
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_sampling.py +13 -8
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_unique_features.py +11 -10
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_variability.py +1 -1
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +1 -1
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/_bdd_writer.py +2 -1
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/dddmp_reader.py +5 -5
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/dddmp_writer.py +2 -2
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +2 -2
- flamapy-bdd-2.0.0.dev7/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +195 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/json_writer.py +4 -2
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pickle_writer.py +3 -3
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/sat_to_bdd.py +7 -1
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/PKG-INFO +1 -1
- flamapy-bdd-2.0.0.dev7/flamapy_bdd.egg-info/dependency_links.txt +1 -0
- flamapy-bdd-2.0.0.dev7/flamapy_bdd.egg-info/requires.txt +11 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/setup.py +14 -19
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +0 -136
- flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/dependency_links.txt +0 -1
- flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/requires.txt +0 -11
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/README.md +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/__init__.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/__init__.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/utils/__init__.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/models/utils/txtcnf.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/__init__.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_core_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_dead_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_pure_optional_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_satisfiable.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/bdd_variant_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/commonality_factor.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/feature_inclusion_probability.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/homogeneity.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/product_distribution.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/pure_optional_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/unique_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/variability.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/operations/interfaces/variant_features.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_cnf.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/json_reader.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pdf_writer.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/pickle_reader.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/png_writer.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy/metamodels/bdd_metamodel/transformations/svg_writer.py +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/SOURCES.txt +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/flamapy_bdd.egg-info/top_level.txt +0 -0
- {flamapy-bdd-2.0.0.dev1 → flamapy-bdd-2.0.0.dev7}/setup.cfg +0 -0
|
@@ -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
|
|
37
|
-
self.
|
|
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
|
|
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
|
|
62
|
+
def bdd(self, new_bdd: Union[_bdd.BDD, _dd_bdd.BDD]) -> None:
|
|
46
63
|
self._bdd = new_bdd
|
|
47
|
-
self.
|
|
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
|
|
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
|
|
74
|
+
def root(self, new_root: Union[_bdd.Function, int]) -> None:
|
|
57
75
|
self._root = new_root
|
|
58
|
-
self.
|
|
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
|
-
@
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
38
|
+
n_vars = len(bdd_model.variables_features)
|
|
39
39
|
else:
|
|
40
|
-
values =
|
|
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.
|
|
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.
|
|
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.
|
|
50
|
-
values = {
|
|
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.
|
|
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 =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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.
|
|
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(
|
|
64
|
+
values.pop(variable)
|
|
64
65
|
else:
|
|
65
|
-
values[
|
|
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,
|
|
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(),
|
|
104
|
-
#
|
|
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,
|
|
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,
|
|
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,
|
|
171
|
-
#
|
|
172
|
-
#
|
|
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,
|
|
184
|
-
#
|
|
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)] +
|
|
189
|
-
#
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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] *
|
|
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
|
|
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
|
-
|
|
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.
|
|
87
|
+
care_vars = set(bdd_model.variables_features.keys()) - values.keys()
|
|
84
88
|
n_vars = len(care_vars)
|
|
85
|
-
for
|
|
89
|
+
for variable in care_vars:
|
|
86
90
|
# Number of configurations with the feature selected
|
|
87
|
-
v_sel = bdd_model.bdd.let({
|
|
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({
|
|
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[
|
|
100
|
-
u_func = bdd_model.bdd.let({
|
|
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(
|
|
107
|
+
return Configuration({bdd_model.variables_features[v]: selected
|
|
108
|
+
for v, selected in values.items()})
|