Skip to content

Commit 32d05c8

Browse files
committed
Feature: support for import and cardinalities analysis
1 parent 34a51ca commit 32d05c8

10 files changed

Lines changed: 94 additions & 41 deletions

File tree

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
.ONESHELL:
22

33
lint:
4-
prospector
4+
echo "To lint this project, make sure that you have installed the core" >&2;
5+
ruff check .
56

67
mypy:
7-
mypy flamapy --no-namespace-packages
8+
echo "To lint this project, make sure that you have installed the core" >&2;
9+
mypy -p flamapy
810

911
test:
1012
python -m pytest -sv

flamapy/metamodels/bdd_metamodel/models/bdd_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def build_bdd(self, expression: str) -> None:
4949
self._bdd.declare(*self.variables_features.keys())
5050
self._root = self._bdd.add_expr(expression)
5151
_bdd.reorder(self._bdd)
52-
self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
52+
self._levels_variables = {level: var for var, level in self._bdd.var_levels.items()}
5353

5454
@property
5555
def formula(self) -> str:
@@ -65,7 +65,7 @@ def bdd(self, new_bdd: Union[_bdd.BDD, _dd_bdd.BDD]) -> None:
6565
self.features_variables = {var: var for var in self._bdd.vars}
6666
self.variables_features = dict(self.features_variables)
6767
self._root = next(iter(self._bdd.roots), None)
68-
self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
68+
self._levels_variables = {level: var for var, level in self._bdd.var_levels.items()}
6969

7070
@property
7171
def root(self) -> Union[_bdd.Function, int]:
@@ -76,7 +76,7 @@ def root(self, new_root: Union[_bdd.Function, int]) -> None:
7676
self._root = new_root
7777
self.features_variables = {var: var for var in self._bdd.vars}
7878
self.variables_features = dict(self.features_variables)
79-
self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
79+
self._levels_variables = {level: var for var, level in self._bdd.var_levels.items()}
8080

8181
# @classmethod
8282
# def from_logic_formula(cls,

flamapy/metamodels/bdd_metamodel/operations/bdd_metrics.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def __init__(self) -> None:
3434
self._configurations_number: int = 0
3535
self._fip: dict[Any, float] = {}
3636
self._prod_dist: list[int] = []
37-
self._prod_dist_op: bdd_operations.BDDProductDistribution = None
37+
self._prod_dist_op: bdd_operations.BDDProductDistribution | None = None
3838
self._variant_features: list[Any] = []
3939

4040
@property
@@ -233,10 +233,13 @@ def descriptive_statistics(self) -> dict[str, Any]:
233233
if self.model is None:
234234
raise FlamaException('Model not initialized.')
235235
name = "Descriptive statistics"
236-
_desc_stats = self._prod_dist_op.descriptive_statistics()
237-
return self.construct_result(name=name,
238-
doc=self.descriptive_statistics.__doc__,
239-
result=_desc_stats)
236+
if self._prod_dist_op is not None:
237+
_desc_stats = self._prod_dist_op.descriptive_statistics()
238+
return self.construct_result(name=name,
239+
doc=self.descriptive_statistics.__doc__,
240+
result=_desc_stats)
241+
else:
242+
return {}
240243

241244
@metric_method
242245
def feature_inclusion_probabilities(self) -> dict[str, Any]:

flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,14 @@ def get_prod_dist(bdd_model: BDDModel,
7676
get_prod_dist(bdd_model, low, dist, mark, complemented ^ bdd_model.negated(low))
7777

7878
# compute low_dist to account for the removed nodes through low
79-
removed_nodes = bdd_model.index(low) - bdd_model.index(node) - 1
80-
low_dist = [0] * (removed_nodes + len(dist[id_low]))
81-
for i in range(removed_nodes + 1):
82-
for j in range(len(dist[id_low])):
83-
low_dist[i + j] = low_dist[i + j] + dist[id_low][j] * math.comb(removed_nodes, i)
79+
low_index = bdd_model.index(low)
80+
node_index = bdd_model.index(node)
81+
if low_index is not None and node_index is not None:
82+
removed_nodes = low_index - node_index - 1
83+
low_dist = [0] * (removed_nodes + len(dist[id_low]))
84+
for i in range(removed_nodes + 1):
85+
for j in range(len(dist[id_low])):
86+
low_dist[i + j] = low_dist[i + j] + dist[id_low][j] * math.comb(removed_nodes, i)
8487

8588
# traverse
8689
high = bdd_model.get_high_node(node)
@@ -89,12 +92,15 @@ def get_prod_dist(bdd_model: BDDModel,
8992
get_prod_dist(bdd_model, high, dist, mark, complemented)
9093

9194
# compute high_dist to account for the removed nodes through high
92-
removed_nodes = bdd_model.index(high) - bdd_model.index(node) - 1
93-
high_dist = [0] * (removed_nodes + len(dist[id_high]))
94-
for i in range(removed_nodes + 1):
95-
for j in range(len(dist[id_high])):
96-
high_dist[i + j] = high_dist[i + j] + dist[id_high][j] * (
97-
math.comb(removed_nodes, i))
95+
high_index = bdd_model.index(high)
96+
node_index = bdd_model.index(node)
97+
if high_index is not None and node_index is not None:
98+
removed_nodes = high_index - node_index - 1
99+
high_dist = [0] * (removed_nodes + len(dist[id_high]))
100+
for i in range(removed_nodes + 1):
101+
for j in range(len(dist[id_high])):
102+
high_dist[i + j] = high_dist[i + j] + dist[id_high][j] * (
103+
math.comb(removed_nodes, i))
98104
# combine low and high distributions
99105
combine_distributions(id_node, dist, low_dist, high_dist)
100106

@@ -116,7 +122,7 @@ def combine_distributions(id_node: int,
116122
dist[id_node] = node_dist
117123

118124

119-
def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]: # noqa: MC0001
125+
def descriptive_statistics(prod_dist: list[int]) -> dict[str, Any]:
120126
total_elements = sum(prod_dist)
121127
if total_elements == 0:
122128
return {

flamapy/metamodels/bdd_metamodel/transformations/dddmp_reader.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ def dddmp_v3_to_v2(filepath: str) -> str:
3434
with open(filepath, 'r', encoding='utf8') as file:
3535
lines = file.readlines()
3636
# Change version from 3.0 to 2.0
37-
i, line = next((i, l) for i, l in enumerate(lines) if '.ver DDDMP-3.0' in l)
38-
lines[i] = line.replace('3.0', '2.0')
37+
index, line = next((index, line) for index, line in enumerate(lines)
38+
if '.ver DDDMP-3.0' in line)
39+
lines[index] = line.replace('3.0', '2.0')
3940

4041
# Add '.varnames' field
41-
i, line = next((i, l) for i, l in enumerate(lines) if '.varnames' in l)
42-
lines.pop(i)
42+
index, line = next((index, line) for index, line in enumerate(lines)
43+
if '.varnames' in line)
44+
lines.pop(index)
4345

4446
with tempfile.NamedTemporaryFile(mode='w', encoding='utf8', delete=False) as temp_file:
4547
temp_file.writelines(lines)

flamapy/metamodels/bdd_metamodel/transformations/dddmp_writer.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ def get_destination_extension() -> str:
1212
return 'dddmp'
1313

1414
def transform(self) -> str:
15-
if self.path is None: # type: ignore[has-type]
15+
if self.path is None:
1616
with tempfile.NamedTemporaryFile(mode='w', encoding='utf8') as file:
1717
self.path = file.name
1818
result = write_to_file(self)
19-
self.path = None # type: ignore[assignment]
19+
self.path = None
2020
else:
2121
result = write_to_file(self)
2222
return result
@@ -25,7 +25,10 @@ def transform(self) -> str:
2525
def write_to_file(writer: DDDMPWriter) -> str:
2626
try:
2727
super(type(writer), writer).transform()
28-
result = dddmp_v2_to_v3(writer.path)
28+
if writer.path is None:
29+
raise ValueError("Writer path is None")
30+
else:
31+
result = dddmp_v2_to_v3(writer.path)
2932
except Exception as exc:
3033
raise FlamaException('DDDMPWriter is not supported.') from exc
3134
return result
@@ -39,12 +42,14 @@ def dddmp_v2_to_v3(filepath: str) -> str:
3942
with open(filepath, 'r', encoding='utf8') as file:
4043
lines = file.readlines()
4144
# Change version from 2.0 to 3.0
42-
i, line = next((i, l) for i, l in enumerate(lines) if '.ver DDDMP-2.0' in l)
43-
lines[i] = line.replace('2.0', '3.0')
45+
index, line = next((index, line) for index, line in enumerate(lines)
46+
if '.ver DDDMP-2.0' in line)
47+
lines[index] = line.replace('2.0', '3.0')
4448

4549
# Add '.varnames' field
46-
i, line = next((i, l) for i, l in enumerate(lines) if '.orderedvarnames' in l)
47-
lines.insert(i - 1, line.replace('.orderedvarnames', '.varnames'))
50+
index, line = next((index, line) for index, line in enumerate(lines)
51+
if '.orderedvarnames' in line)
52+
lines.insert(index - 1, line.replace('.orderedvarnames', '.varnames'))
4853

4954
with open(filepath, 'w', encoding='utf8') as file:
5055
file.writelines(lines)

flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_cnf.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import itertools
2+
import copy
23
from typing import Optional, Any
34

45
from flamapy.core.transformations import ModelToModel
@@ -8,6 +9,12 @@
89
Feature,
910
Relation,
1011
)
12+
from flamapy.metamodels.fm_metamodel.transformations import (
13+
FlatFM
14+
)
15+
from flamapy.metamodels.fm_metamodel.transformations.refactorings import (
16+
FeatureCardinalityRefactoring
17+
)
1118
from flamapy.metamodels.bdd_metamodel.models import BDDModel
1219

1320

@@ -165,15 +172,24 @@ def get_term_variable(term: Any) -> int:
165172
self.clauses.append(clause_variables)
166173

167174
def transform(self) -> BDDModel:
168-
for feature in self.source_model.get_features():
175+
# FlatFM if the feature model contains imports
176+
feature_model = self.source_model
177+
if feature_model.imports:
178+
feature_model = FlatFM(feature_model).transform()
179+
# Apply the feature cardinality refactoring to the source model
180+
if FeatureCardinalityRefactoring(feature_model).is_applicable():
181+
feature_model = copy.deepcopy(feature_model)
182+
feature_model = FeatureCardinalityRefactoring(feature_model).transform()
183+
184+
for feature in feature_model.get_features():
169185
self.add_feature(feature)
170186

171-
self.add_root(self.source_model.root)
187+
self.add_root(feature_model.root)
172188

173-
for relation in self.source_model.get_relations():
189+
for relation in feature_model.get_relations():
174190
self.add_relation(relation)
175191

176-
for constraint in self.source_model.get_constraints():
192+
for constraint in feature_model.get_constraints():
177193
self.add_constraint(constraint)
178194

179195
# Transform clauses to textual CNF notation required by the BDD

flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import re
2+
import copy
23
import itertools
34
from typing import Optional
45

56
from flamapy.core.models.ast import ASTOperation
67
from flamapy.core.transformations import ModelToModel
78
from flamapy.metamodels.fm_metamodel.models import FeatureModel, Feature, Relation, Constraint
89
from flamapy.metamodels.bdd_metamodel.models import BDDModel
10+
from flamapy.metamodels.fm_metamodel.transformations import (
11+
FeatureCardinalityRefactoring
12+
)
13+
from flamapy.metamodels.fm_metamodel.transformations import FMSecureFeaturesNames, FlatFM
914

1015

1116
class FmToBDD(ModelToModel):
@@ -24,6 +29,22 @@ def __init__(self, source_model: FeatureModel) -> None:
2429
self._counter: int = 0
2530

2631
def transform(self) -> BDDModel:
32+
# FlatFM if the feature model contains imports
33+
feature_model = self.source_model
34+
if feature_model.imports:
35+
feature_model = FlatFM(feature_model).transform()
36+
# Apply the feature cardinality refactoring to the source model
37+
if FeatureCardinalityRefactoring(feature_model).is_applicable():
38+
feature_model = copy.deepcopy(feature_model)
39+
feature_model = FeatureCardinalityRefactoring(feature_model).transform()
40+
41+
# Secure the features names and create a mapping with the original names
42+
fmsfn = FMSecureFeaturesNames(feature_model)
43+
secure_fm = fmsfn.transform()
44+
mapping_names = fmsfn.mapping_names
45+
self.bdd_model.mapping_names = mapping_names
46+
self.source_model = secure_fm
47+
2748
self.destination_model = BDDModel()
2849
for feature in self.source_model.get_features():
2950
self._add_feature(feature)

flamapy/metamodels/bdd_metamodel/transformations/json_writer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ def get_destination_extension() -> str:
1111
return 'json'
1212

1313
def transform(self) -> str:
14-
if self.path is None: # type: ignore[has-type]
14+
if self.path is None:
1515
with tempfile.NamedTemporaryFile(mode='w', encoding='utf8') as file:
1616
self.path = file.name
1717
result = write_to_file(self)
18-
self.path = None # type: ignore[assignment]
18+
self.path = None
1919
else:
2020
result = write_to_file(self)
2121
return result

tests/test_bdd_metamodel.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import pytest
22
from collections import defaultdict
33

4-
from flamapy.metamodels.configuration_metamodel.models import Configuration
54
from flamapy.metamodels.fm_metamodel.transformations import UVLReader
65
from flamapy.metamodels.bdd_metamodel.models import BDDModel
76
from flamapy.metamodels.bdd_metamodel.transformations import (
@@ -15,7 +14,6 @@
1514
BDDFeatureInclusionProbability,
1615
BDDSampling,
1716
BDDConfigurationsNumber,
18-
BDDConfigurations,
1917
BDDCoreFeatures,
2018
BDDDeadFeatures,
2119
BDDVariantFeatures,

0 commit comments

Comments
 (0)