flamapy-bdd 1.0.1.dev0__tar.gz → 2.0.0.dev1__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-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/PKG-INFO +75 -72
- flamapy-bdd-2.0.0.dev1/README.md +198 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/models/__init__.py +4 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +200 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/models/utils/__init__.py +1 -1
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/models/utils/txtcnf.py +86 -61
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/__init__.py +32 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_commonality_factor.py +36 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations.py +51 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations_number.py +43 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_core_features.py +36 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_dead_features.py +36 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +195 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_homogeneity.py +35 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_metrics.py +239 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +202 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_pure_optional_features.py +36 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/bdd_sampling.py +39 -30
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_satisfiable.py +33 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_unique_features.py +56 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_variability.py +37 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_variant_features.py +36 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +18 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/commonality_factor.py +26 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/interfaces/feature_inclusion_probability.py +1 -1
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/homogeneity.py +24 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/interfaces/product_distribution.py +11 -1
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/pure_optional_features.py +21 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/unique_features.py +21 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/variability.py +32 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/variant_features.py +24 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +22 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/_bdd_writer.py +41 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/dddmp_reader.py +47 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/dddmp_writer.py +51 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +29 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_cnf.py +202 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +136 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/json_reader.py +35 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/json_writer.py +28 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pdf_writer.py +8 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pickle_reader.py +22 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pickle_writer.py +23 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/png_writer.py +8 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/sat_to_bdd.py +48 -0
- flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/svg_writer.py +8 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy_bdd.egg-info/PKG-INFO +75 -72
- flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/SOURCES.txt +52 -0
- flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/dependency_links.txt +1 -0
- flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/requires.txt +11 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/setup.py +6 -6
- flamapy-bdd-1.0.1.dev0/README.md +0 -195
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/models/__init__.py +0 -4
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +0 -99
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/__init__.py +0 -15
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +0 -48
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +0 -58
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_products.py +0 -48
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_products_number.py +0 -42
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +0 -5
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +0 -5
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/bdd_writer.py +0 -79
- flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +0 -156
- flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/SOURCES.txt +0 -24
- flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/dependency_links.txt +0 -1
- flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/requires.txt +0 -10
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/__init__.py +0 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy_bdd.egg-info/top_level.txt +0 -0
- {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flamapy-bdd
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0.dev1
|
|
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
|
|
@@ -19,8 +19,9 @@ Provides-Extra: dev
|
|
|
19
19
|
- [Description](#description)
|
|
20
20
|
- [Requirements and Installation](#requirements-and-installation)
|
|
21
21
|
- [Functionality and usage](#functionality-and-usage)
|
|
22
|
-
- [Load a feature model and create the BDD](#load-a-feature-model-and-create-the-bdd)
|
|
22
|
+
- [Load a feature model in UVL and create the BDD](#load-a-feature-model-in-uvl-and-create-the-bdd)
|
|
23
23
|
- [Save the BDD in a file](#save-the-bdd-in-a-file)
|
|
24
|
+
- [Load the BDD from a file](#load-the-bdd-from-a-file)
|
|
24
25
|
- [Analysis operations](#analysis-operations)
|
|
25
26
|
- [Contributing to the BDD plugin](#contributing-to-the-bdd-plugin)
|
|
26
27
|
|
|
@@ -64,13 +65,13 @@ The executable script [test_bdd_metamodel.py](https://github.com/flamapy/bdd_met
|
|
|
64
65
|
The following functionality is provided:
|
|
65
66
|
|
|
66
67
|
|
|
67
|
-
### Load a feature model and create the BDD
|
|
68
|
+
### Load a feature model in UVL and create the BDD
|
|
68
69
|
```python
|
|
69
|
-
from flamapy.metamodels.fm_metamodel.transformations
|
|
70
|
-
from flamapy.metamodels.bdd_metamodel.transformations
|
|
70
|
+
from flamapy.metamodels.fm_metamodel.transformations import UVLReader
|
|
71
|
+
from flamapy.metamodels.bdd_metamodel.transformations import FmToBDD
|
|
71
72
|
|
|
72
|
-
# Load the feature model from
|
|
73
|
-
feature_model =
|
|
73
|
+
# Load the feature model from UVL
|
|
74
|
+
feature_model = UVLReader('models/uvl_models/pizzas.uvl').transform()
|
|
74
75
|
# Create the BDD from the feature model
|
|
75
76
|
bdd_model = FmToBDD(feature_model).transform()
|
|
76
77
|
```
|
|
@@ -78,114 +79,116 @@ bdd_model = FmToBDD(feature_model).transform()
|
|
|
78
79
|
|
|
79
80
|
### Save the BDD in a file
|
|
80
81
|
```python
|
|
81
|
-
from flamapy.metamodels.bdd_metamodel.transformations
|
|
82
|
+
from flamapy.metamodels.bdd_metamodel.transformations import PNGWriter, DDDMPv3Writer
|
|
82
83
|
# Save the BDD as an image in PNG
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
output_format=BDDDumpFormat.PNG).transform()
|
|
84
|
+
PNGWriter(path='my_bdd.png', bdd_model).transform()
|
|
85
|
+
# Save the BDD in a .dddmp file
|
|
86
|
+
DDDMPv3Writer(f'my_bdd.dddmp', bdd_model).transform()
|
|
87
87
|
```
|
|
88
|
-
|
|
88
|
+
Writers available: DDDMPv3 ('dddmp'), DDDMPv2 ('dddmp'), JSON ('json'), Pickle ('p'), PDF ('pdf'), PNG ('png'), SVG ('svg').
|
|
89
89
|
|
|
90
|
+
### Load the BDD from a file
|
|
91
|
+
```python
|
|
92
|
+
from flamapy.metamodels.bdd_metamodel.transformations import JSONReader
|
|
93
|
+
# Load the BDD from a .json file
|
|
94
|
+
bdd_model = JSONReader(path='path/to/my_bdd.json').transform()
|
|
95
|
+
```
|
|
96
|
+
Readers available: JSON ('json'), DDDMP ('dddmp'), Pickle ('p').
|
|
97
|
+
|
|
98
|
+
*NOTE:* DDDMP and Pickle readers are not fully supported yet.
|
|
90
99
|
|
|
91
100
|
### Analysis operations
|
|
92
101
|
|
|
93
|
-
-
|
|
102
|
+
- Satisfiable
|
|
94
103
|
|
|
95
|
-
Return the
|
|
104
|
+
Return whether the model is satisfiable (valid):
|
|
96
105
|
```python
|
|
97
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
98
|
-
|
|
99
|
-
print(f'
|
|
100
|
-
```
|
|
101
|
-
or alternatively:
|
|
102
|
-
```python
|
|
103
|
-
from flamapy.metamodels.bdd_metamodel.operations import products_number
|
|
104
|
-
nof_products = products_number(bdd_model)
|
|
105
|
-
print(f'#Products: {nof_products}')
|
|
106
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDSatisfiable
|
|
107
|
+
satisfiable = BDDSatisfiable().execute(bdd_model).get_result()
|
|
108
|
+
print(f'Satisfiable? (valid?): {satisfiable}')
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
-
|
|
111
|
+
- Configurations number
|
|
109
112
|
|
|
110
|
-
Return the
|
|
113
|
+
Return the number of configurations:
|
|
111
114
|
```python
|
|
112
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
print(f'Product {i}: {[feat for feat in prod.elements if prod.elements[feat]]}')
|
|
115
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDConfigurationsNumber
|
|
116
|
+
n_configs = BDDConfigurationsNumber().execute(bdd_model).get_result()
|
|
117
|
+
print(f'#Configurations: {n_configs}')
|
|
116
118
|
```
|
|
117
|
-
|
|
119
|
+
|
|
120
|
+
- Configurations
|
|
121
|
+
|
|
122
|
+
Enumerate the configurations of the model:
|
|
118
123
|
```python
|
|
119
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
120
|
-
|
|
121
|
-
for i,
|
|
122
|
-
print(f'
|
|
124
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDConfigurations
|
|
125
|
+
configurations = BDDConfigurations().execute(bdd_model).get_result()
|
|
126
|
+
for i, config in enumerate(configurations, 1):
|
|
127
|
+
print(f'Config {i}: {[feat for feat in config.elements if config.elements[feat]]}')
|
|
123
128
|
```
|
|
124
129
|
|
|
125
130
|
- Sampling
|
|
126
131
|
|
|
127
|
-
Return a sample of the given size of uniform random
|
|
132
|
+
Return a sample of the given size of uniform random configurations with or without replacement:
|
|
128
133
|
```python
|
|
129
134
|
from flamapy.metamodels.bdd_metamodel.operations import BDDSampling
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
from flamapy.metamodels.bdd_metamodel.operations import sample
|
|
137
|
-
list_sample = sample(bdd_model, size=5, with_replacement=False)
|
|
138
|
-
for i, prod in enumerate(list_sample):
|
|
139
|
-
print(f'Product {i}: {[feat for feat in prod.elements if prod.elements[feat]]}')
|
|
135
|
+
sampling_op = BDDSampling()
|
|
136
|
+
sampling_op.set_sample_size(5)
|
|
137
|
+
sampling_op.set_with_replacement(False) # Default False
|
|
138
|
+
sample = sampling_op.execute(bdd_model).get_result()
|
|
139
|
+
for i, config in enumerate(sample, 1):
|
|
140
|
+
print(f'Config {i}: {[feat for feat in config.elements if config.elements[feat]]}')
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
- Product Distribution
|
|
143
144
|
|
|
144
|
-
Return the number of products having a given number of features:
|
|
145
|
-
```python
|
|
146
|
-
from flamapy.metamodels.bdd_metamodel.operations import BDDProductDistributionBF
|
|
147
|
-
dist = BDDProductDistributionBF().execute(bdd_model).get_result()
|
|
148
|
-
print(f'Product Distribution: {dist}')
|
|
149
|
-
```
|
|
150
|
-
or alternatively:
|
|
145
|
+
Return the number of products (configurations) having a given number of features:
|
|
151
146
|
```python
|
|
152
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
153
|
-
dist =
|
|
147
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDProductDistribution
|
|
148
|
+
dist = BDDProductDistribution().execute(bdd_model).get_result()
|
|
154
149
|
print(f'Product Distribution: {dist}')
|
|
155
150
|
```
|
|
156
151
|
|
|
157
152
|
- Feature Inclusion Probability
|
|
158
153
|
|
|
159
|
-
Return the probability for a feature to be included in a valid
|
|
154
|
+
Return the probability for a feature to be included in a valid configuration:
|
|
160
155
|
```python
|
|
161
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
162
|
-
prob =
|
|
156
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDFeatureInclusionProbability
|
|
157
|
+
prob = BDDFeatureInclusionProbability().execute(bdd_model).get_result()
|
|
163
158
|
for feat in prob.keys():
|
|
164
159
|
print(f'{feat}: {prob[feat]}')
|
|
165
160
|
```
|
|
166
|
-
|
|
161
|
+
|
|
162
|
+
- Core features
|
|
163
|
+
|
|
164
|
+
Return the core features (those features that are present in all the configurations):
|
|
167
165
|
```python
|
|
168
|
-
from flamapy.metamodels.bdd_metamodel.operations import
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
print(f'{feat}: {prob[feat]}')
|
|
166
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDCoreFeatures
|
|
167
|
+
core_features = BDDCoreFeatures().execute(bdd_model).get_result()
|
|
168
|
+
print(f'Core features: {core_features}')
|
|
172
169
|
```
|
|
173
170
|
|
|
174
|
-
|
|
171
|
+
- Dead features
|
|
172
|
+
|
|
173
|
+
Return the dead features (those features that are not present in any configuration):
|
|
174
|
+
```python
|
|
175
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDDeadFeatures
|
|
176
|
+
dead_features = BDDDeadFeatures().execute(bdd_model).get_result()
|
|
177
|
+
print(f'Dead features: {dead_features}')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Most analysis operations support also a partial configuration as an additional argument, so the operation will return the result taking into account the given partial configuration. For example:
|
|
175
181
|
|
|
176
182
|
```python
|
|
177
183
|
from flamapy.core.models import Configuration
|
|
178
184
|
# Create a partial configuration
|
|
179
185
|
elements = {'Pizza': True, 'Big': True}
|
|
180
186
|
partial_config = Configuration(elements)
|
|
181
|
-
# Calculate the number of
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```python
|
|
187
|
-
nof_products = products(bdd_model, partial_config)
|
|
188
|
-
print(f'#Products: {nof_products}')
|
|
187
|
+
# Calculate the number of configuration from the partial configuration
|
|
188
|
+
configs_number_op = BDDConfigurationsNumber()
|
|
189
|
+
configs_number_op.set_partial_configuration(partial_config)
|
|
190
|
+
n_configs = configs_number_op.execute(bdd_model).get_result()
|
|
191
|
+
print(f'#Configurations: {n_configs}')
|
|
189
192
|
```
|
|
190
193
|
|
|
191
194
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# BDD plugin for flamapy
|
|
2
|
+
- [BDD plugin for flamapy](#bdd-plugin-for-flamapy)
|
|
3
|
+
- [Description](#description)
|
|
4
|
+
- [Requirements and Installation](#requirements-and-installation)
|
|
5
|
+
- [Functionality and usage](#functionality-and-usage)
|
|
6
|
+
- [Load a feature model in UVL and create the BDD](#load-a-feature-model-in-uvl-and-create-the-bdd)
|
|
7
|
+
- [Save the BDD in a file](#save-the-bdd-in-a-file)
|
|
8
|
+
- [Load the BDD from a file](#load-the-bdd-from-a-file)
|
|
9
|
+
- [Analysis operations](#analysis-operations)
|
|
10
|
+
- [Contributing to the BDD plugin](#contributing-to-the-bdd-plugin)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Description
|
|
14
|
+
This plugin supports Binary Decision Diagrams (BDDs) representations for feature models.
|
|
15
|
+
|
|
16
|
+
The plugin is based on [flamapy](https://github.com/flamapy/core) and thus, it follows the same architecture:
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img width="750" src="doc/bdd_plugin.png">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
The BDD plugin relies on the [dd](https://github.com/tulip-control/dd) library to manipulate BDDs.
|
|
23
|
+
The complete documentation of such library is available [here](https://github.com/tulip-control/dd/blob/main/doc.md).
|
|
24
|
+
|
|
25
|
+
The following is an example of feature model and its BDD using complemented arcs.
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img width="750" src="doc/fm_example.png">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img width="750" src="doc/bdd_example.svg">
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
## Requirements and Installation
|
|
36
|
+
- Python 3.9+
|
|
37
|
+
- This plugin depends on the [flamapy core](https://github.com/flamapy/core) and on the [Feature Model plugin](https://github.com/flamapy/fm_metamodel).
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
pip install flamapy flamapy-fm flamapy-bdd
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
We have tested the plugin on Linux, but Windows is also supported.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Functionality and usage
|
|
47
|
+
The executable script [test_bdd_metamodel.py](https://github.com/flamapy/bdd_metamodel/blob/master/tests/test_bdd_metamodel.py) serves as an entry point to show the plugin in action.
|
|
48
|
+
|
|
49
|
+
The following functionality is provided:
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Load a feature model in UVL and create the BDD
|
|
53
|
+
```python
|
|
54
|
+
from flamapy.metamodels.fm_metamodel.transformations import UVLReader
|
|
55
|
+
from flamapy.metamodels.bdd_metamodel.transformations import FmToBDD
|
|
56
|
+
|
|
57
|
+
# Load the feature model from UVL
|
|
58
|
+
feature_model = UVLReader('models/uvl_models/pizzas.uvl').transform()
|
|
59
|
+
# Create the BDD from the feature model
|
|
60
|
+
bdd_model = FmToBDD(feature_model).transform()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Save the BDD in a file
|
|
65
|
+
```python
|
|
66
|
+
from flamapy.metamodels.bdd_metamodel.transformations import PNGWriter, DDDMPv3Writer
|
|
67
|
+
# Save the BDD as an image in PNG
|
|
68
|
+
PNGWriter(path='my_bdd.png', bdd_model).transform()
|
|
69
|
+
# Save the BDD in a .dddmp file
|
|
70
|
+
DDDMPv3Writer(f'my_bdd.dddmp', bdd_model).transform()
|
|
71
|
+
```
|
|
72
|
+
Writers available: DDDMPv3 ('dddmp'), DDDMPv2 ('dddmp'), JSON ('json'), Pickle ('p'), PDF ('pdf'), PNG ('png'), SVG ('svg').
|
|
73
|
+
|
|
74
|
+
### Load the BDD from a file
|
|
75
|
+
```python
|
|
76
|
+
from flamapy.metamodels.bdd_metamodel.transformations import JSONReader
|
|
77
|
+
# Load the BDD from a .json file
|
|
78
|
+
bdd_model = JSONReader(path='path/to/my_bdd.json').transform()
|
|
79
|
+
```
|
|
80
|
+
Readers available: JSON ('json'), DDDMP ('dddmp'), Pickle ('p').
|
|
81
|
+
|
|
82
|
+
*NOTE:* DDDMP and Pickle readers are not fully supported yet.
|
|
83
|
+
|
|
84
|
+
### Analysis operations
|
|
85
|
+
|
|
86
|
+
- Satisfiable
|
|
87
|
+
|
|
88
|
+
Return whether the model is satisfiable (valid):
|
|
89
|
+
```python
|
|
90
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDSatisfiable
|
|
91
|
+
satisfiable = BDDSatisfiable().execute(bdd_model).get_result()
|
|
92
|
+
print(f'Satisfiable? (valid?): {satisfiable}')
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- Configurations number
|
|
96
|
+
|
|
97
|
+
Return the number of configurations:
|
|
98
|
+
```python
|
|
99
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDConfigurationsNumber
|
|
100
|
+
n_configs = BDDConfigurationsNumber().execute(bdd_model).get_result()
|
|
101
|
+
print(f'#Configurations: {n_configs}')
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- Configurations
|
|
105
|
+
|
|
106
|
+
Enumerate the configurations of the model:
|
|
107
|
+
```python
|
|
108
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDConfigurations
|
|
109
|
+
configurations = BDDConfigurations().execute(bdd_model).get_result()
|
|
110
|
+
for i, config in enumerate(configurations, 1):
|
|
111
|
+
print(f'Config {i}: {[feat for feat in config.elements if config.elements[feat]]}')
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- Sampling
|
|
115
|
+
|
|
116
|
+
Return a sample of the given size of uniform random configurations with or without replacement:
|
|
117
|
+
```python
|
|
118
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDSampling
|
|
119
|
+
sampling_op = BDDSampling()
|
|
120
|
+
sampling_op.set_sample_size(5)
|
|
121
|
+
sampling_op.set_with_replacement(False) # Default False
|
|
122
|
+
sample = sampling_op.execute(bdd_model).get_result()
|
|
123
|
+
for i, config in enumerate(sample, 1):
|
|
124
|
+
print(f'Config {i}: {[feat for feat in config.elements if config.elements[feat]]}')
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Product Distribution
|
|
128
|
+
|
|
129
|
+
Return the number of products (configurations) having a given number of features:
|
|
130
|
+
```python
|
|
131
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDProductDistribution
|
|
132
|
+
dist = BDDProductDistribution().execute(bdd_model).get_result()
|
|
133
|
+
print(f'Product Distribution: {dist}')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- Feature Inclusion Probability
|
|
137
|
+
|
|
138
|
+
Return the probability for a feature to be included in a valid configuration:
|
|
139
|
+
```python
|
|
140
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDFeatureInclusionProbability
|
|
141
|
+
prob = BDDFeatureInclusionProbability().execute(bdd_model).get_result()
|
|
142
|
+
for feat in prob.keys():
|
|
143
|
+
print(f'{feat}: {prob[feat]}')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- Core features
|
|
147
|
+
|
|
148
|
+
Return the core features (those features that are present in all the configurations):
|
|
149
|
+
```python
|
|
150
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDCoreFeatures
|
|
151
|
+
core_features = BDDCoreFeatures().execute(bdd_model).get_result()
|
|
152
|
+
print(f'Core features: {core_features}')
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- Dead features
|
|
156
|
+
|
|
157
|
+
Return the dead features (those features that are not present in any configuration):
|
|
158
|
+
```python
|
|
159
|
+
from flamapy.metamodels.bdd_metamodel.operations import BDDDeadFeatures
|
|
160
|
+
dead_features = BDDDeadFeatures().execute(bdd_model).get_result()
|
|
161
|
+
print(f'Dead features: {dead_features}')
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Most analysis operations support also a partial configuration as an additional argument, so the operation will return the result taking into account the given partial configuration. For example:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from flamapy.core.models import Configuration
|
|
168
|
+
# Create a partial configuration
|
|
169
|
+
elements = {'Pizza': True, 'Big': True}
|
|
170
|
+
partial_config = Configuration(elements)
|
|
171
|
+
# Calculate the number of configuration from the partial configuration
|
|
172
|
+
configs_number_op = BDDConfigurationsNumber()
|
|
173
|
+
configs_number_op.set_partial_configuration(partial_config)
|
|
174
|
+
n_configs = configs_number_op.execute(bdd_model).get_result()
|
|
175
|
+
print(f'#Configurations: {n_configs}')
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
## Contributing to the BDD plugin
|
|
180
|
+
To contribute in the development of this plugin:
|
|
181
|
+
|
|
182
|
+
1. Fork the repository into your GitHub account.
|
|
183
|
+
2. Clone the repository: `git@github.com:<<username>>/bdd_metamodel.git`
|
|
184
|
+
3. Create a virtual environment: `python -m venv env`
|
|
185
|
+
4. Activate the virtual environment: `source env/bin/activate`
|
|
186
|
+
5. Install the plugin dependencies: `pip install flamapy flamapy-fm`
|
|
187
|
+
6. Install the BDD plugin from the source code: `pip install -e bdd_metamodel`
|
|
188
|
+
|
|
189
|
+
Please try to follow the standards code quality to contribute to this plugin before creating a Pull Request:
|
|
190
|
+
|
|
191
|
+
- To analyze your Python code and output information about errors, potential problems, convention violations and complexity, pass the prospector with:
|
|
192
|
+
|
|
193
|
+
`make lint`
|
|
194
|
+
|
|
195
|
+
- To analyze the static type checker for Python and find bugs, pass the Mypy:
|
|
196
|
+
|
|
197
|
+
`make mypy`
|
|
198
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
|
|
4
|
+
# Low-level interface to pure Python implementation (wrapped by dd.autoref.BDD).
|
|
5
|
+
import dd.bdd as _dd_bdd
|
|
6
|
+
# Import the best available interface:
|
|
7
|
+
try:
|
|
8
|
+
import dd.cudd as _bdd # High-level interface to a C implementation.
|
|
9
|
+
except ImportError:
|
|
10
|
+
import dd.autoref as _bdd # High-level interface to pure Python implementation.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from flamapy.core.models import VariabilityModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BDDModel(VariabilityModel):
|
|
17
|
+
"""A Binary Decision Diagram (BDD) representation of the feature model.
|
|
18
|
+
|
|
19
|
+
It relies on the dd library: https://pypi.org/project/dd/
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
class LogicConnective(Enum):
|
|
23
|
+
NOT = '!'
|
|
24
|
+
OR = '|'
|
|
25
|
+
AND = '&'
|
|
26
|
+
IMPLIES = '=>'
|
|
27
|
+
EQUIVALENCE = '<=>'
|
|
28
|
+
XOR = '^'
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def get_extension() -> str:
|
|
32
|
+
return 'bdd'
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._bdd: _bdd.BDD = _bdd.BDD() # BDD manager
|
|
36
|
+
self._root: Optional[_bdd.Function | int] = None
|
|
37
|
+
self._variables: list[Any] = []
|
|
38
|
+
self._levels_variables: dict[int, Any] = {}
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def bdd(self) -> _bdd.BDD | _dd_bdd.BDD:
|
|
42
|
+
return self._bdd
|
|
43
|
+
|
|
44
|
+
@bdd.setter
|
|
45
|
+
def bdd(self, new_bdd: _bdd.BDD | _dd_bdd.BDD) -> None:
|
|
46
|
+
self._bdd = new_bdd
|
|
47
|
+
self._variables = list(self._bdd.vars)
|
|
48
|
+
self._root = next(iter(self._bdd.roots), None)
|
|
49
|
+
self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def root(self) -> _bdd.Function | int:
|
|
53
|
+
return self._root
|
|
54
|
+
|
|
55
|
+
@root.setter
|
|
56
|
+
def root(self, new_root: _bdd.Function | int) -> None:
|
|
57
|
+
self._root = new_root
|
|
58
|
+
self._variables = list(self._bdd.vars)
|
|
59
|
+
self._levels_variables = {l: v for v, l in self._bdd.var_levels.items()}
|
|
60
|
+
|
|
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
|
|
87
|
+
|
|
88
|
+
def level_of_var(self, var: Any) -> Optional[int]:
|
|
89
|
+
"""Return the level of a given variable."""
|
|
90
|
+
return self._bdd.var_levels.get(var, None)
|
|
91
|
+
|
|
92
|
+
def var_at_level(self, level: int) -> Optional[Any]:
|
|
93
|
+
"""Return the variable at the given level."""
|
|
94
|
+
return self._levels_variables.get(level, None)
|
|
95
|
+
|
|
96
|
+
def var(self, node: _bdd.Function | int) -> Optional[Any]:
|
|
97
|
+
"""Return the variable of the node.
|
|
98
|
+
|
|
99
|
+
It returns None if the node is a terminal node.
|
|
100
|
+
"""
|
|
101
|
+
if self.is_terminal_node(node):
|
|
102
|
+
return None
|
|
103
|
+
if isinstance(node, int):
|
|
104
|
+
level, _, _ = self._bdd.succ(node)
|
|
105
|
+
return self.var_at_level(level)
|
|
106
|
+
return node.var
|
|
107
|
+
|
|
108
|
+
def level(self, node: _bdd.Function | int) -> Optional[int]:
|
|
109
|
+
"""Return the level of the node.
|
|
110
|
+
|
|
111
|
+
Non-terminal nodes start at 0.
|
|
112
|
+
Terminal nodes have level `s' being the `s' the number of variables.
|
|
113
|
+
"""
|
|
114
|
+
#level, _, _ = self._bdd.succ(node)
|
|
115
|
+
return self.level_of_var(self.var(node))
|
|
116
|
+
|
|
117
|
+
def nof_nodes(self) -> int:
|
|
118
|
+
"""Return number of nodes in the BDD."""
|
|
119
|
+
return len(self._bdd)
|
|
120
|
+
|
|
121
|
+
def get_node(self, var: Any) -> _bdd.Function | int:
|
|
122
|
+
"""Return the node of the variable."""
|
|
123
|
+
return self._bdd.var(var)
|
|
124
|
+
|
|
125
|
+
def index(self, node: _bdd.Function | int) -> Optional[int]:
|
|
126
|
+
"""Position (index) of the variable that labels the node `n` in the ordering.
|
|
127
|
+
|
|
128
|
+
Indexes start at 1.
|
|
129
|
+
Terminal nodes (n0 and n1) have indexes `s + 1`, being `s' the number of variables.
|
|
130
|
+
Note that index(n) = level(n) + 1.
|
|
131
|
+
|
|
132
|
+
Example: node `n4` is labeled `B`, and `B` is in the 2nd position in ordering `[A,B,C]`,
|
|
133
|
+
thus level(n4) = 2.
|
|
134
|
+
"""
|
|
135
|
+
if self.is_terminal_node(node):
|
|
136
|
+
return len(self.variables) + 1
|
|
137
|
+
level = self.level(node)
|
|
138
|
+
return level + 1 if level is not None else None
|
|
139
|
+
|
|
140
|
+
def negated(self, node: _bdd.Function | int) -> bool:
|
|
141
|
+
"""Return whether the node is negated."""
|
|
142
|
+
if isinstance(node, int):
|
|
143
|
+
return node < 0
|
|
144
|
+
return node.negated
|
|
145
|
+
|
|
146
|
+
def get_terminal_node_n0(self) -> _bdd.Function | int:
|
|
147
|
+
return self._bdd.false
|
|
148
|
+
|
|
149
|
+
def get_terminal_node_n1(self) -> _bdd.Function | int:
|
|
150
|
+
return self._bdd.true
|
|
151
|
+
|
|
152
|
+
def is_terminal_node(self, node: _bdd.Function | int) -> bool:
|
|
153
|
+
"""Check if the node is a terminal node."""
|
|
154
|
+
return self.is_terminal_n0(node) or self.is_terminal_n1(node)
|
|
155
|
+
|
|
156
|
+
def is_terminal_n1(self, node: _bdd.Function | int) -> bool:
|
|
157
|
+
"""Check if the node is the terminal node 1 (n1)."""
|
|
158
|
+
return node == self.get_terminal_node_n1()
|
|
159
|
+
|
|
160
|
+
def is_terminal_n0(self, node: _bdd.Function | int) -> bool:
|
|
161
|
+
"""Check if the node is the terminal node 0 (n0)."""
|
|
162
|
+
return node == self.get_terminal_node_n0()
|
|
163
|
+
|
|
164
|
+
def get_high_node(self, node: _bdd.Function | int) -> Optional[_bdd.Function | int]:
|
|
165
|
+
"""Return the high (right, solid) node."""
|
|
166
|
+
_, _, high = self._bdd.succ(node)
|
|
167
|
+
return high
|
|
168
|
+
|
|
169
|
+
def get_low_node(self, node: _bdd.Function | int) -> Optional[_bdd.Function | int]:
|
|
170
|
+
"""Return the low (left, dashed) node."""
|
|
171
|
+
_, low, _ = self._bdd.succ(node)
|
|
172
|
+
return low
|
|
173
|
+
|
|
174
|
+
def get_value(self, node: _bdd.Function | int, complemented: bool = False) -> int:
|
|
175
|
+
"""Return the value (id) of the node considering complemented arcs."""
|
|
176
|
+
value = int(node)
|
|
177
|
+
if self.is_terminal_n0(node):
|
|
178
|
+
value = 1 if complemented else 0
|
|
179
|
+
elif self.is_terminal_n1(node):
|
|
180
|
+
value = 0 if complemented else 1
|
|
181
|
+
return value
|
|
182
|
+
|
|
183
|
+
def pretty_node_str(self, node: _bdd.Function | int) -> str:
|
|
184
|
+
return f'{self.var(node)} ' \
|
|
185
|
+
f'(id: {self.get_value(node)}) ' \
|
|
186
|
+
f'(level: {self.level(node)}) ' \
|
|
187
|
+
f'(index: {self.index(node)}) '
|
|
188
|
+
|
|
189
|
+
def __str__(self) -> str:
|
|
190
|
+
result = 'Binary Decision Diagram (BDD):\n'
|
|
191
|
+
result += f'Instance class: {type(self._bdd)}\n'
|
|
192
|
+
result += f'#Nodes: {self.nof_nodes()}\n'
|
|
193
|
+
result += f'Root: {self.pretty_node_str(self.root)}\n'
|
|
194
|
+
levels_vars = dict(sorted(self._levels_variables.items(), key=lambda item: item[0]))
|
|
195
|
+
for level, var in levels_vars.items():
|
|
196
|
+
node = self.get_node(var)
|
|
197
|
+
result += f' |-{self.pretty_node_str(node)}\n'
|
|
198
|
+
result += f'Terminal node (n0): {self.pretty_node_str(self.get_terminal_node_n0())}\n'
|
|
199
|
+
result += f'Terminal node (n1): {self.pretty_node_str(self.get_terminal_node_n1())}\n'
|
|
200
|
+
return result
|