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.
Files changed (69) hide show
  1. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/PKG-INFO +75 -72
  2. flamapy-bdd-2.0.0.dev1/README.md +198 -0
  3. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/models/__init__.py +4 -0
  4. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +200 -0
  5. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/models/utils/__init__.py +1 -1
  6. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/models/utils/txtcnf.py +86 -61
  7. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/__init__.py +32 -0
  8. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_commonality_factor.py +36 -0
  9. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations.py +51 -0
  10. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_configurations_number.py +43 -0
  11. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_core_features.py +36 -0
  12. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_dead_features.py +36 -0
  13. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +195 -0
  14. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_homogeneity.py +35 -0
  15. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_metrics.py +239 -0
  16. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +202 -0
  17. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_pure_optional_features.py +36 -0
  18. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/bdd_sampling.py +39 -30
  19. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_satisfiable.py +33 -0
  20. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_unique_features.py +56 -0
  21. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_variability.py +37 -0
  22. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/bdd_variant_features.py +36 -0
  23. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +18 -0
  24. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/commonality_factor.py +26 -0
  25. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/interfaces/feature_inclusion_probability.py +1 -1
  26. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/homogeneity.py +24 -0
  27. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/operations/interfaces/product_distribution.py +11 -1
  28. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/pure_optional_features.py +21 -0
  29. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/unique_features.py +21 -0
  30. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/variability.py +32 -0
  31. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/operations/interfaces/variant_features.py +24 -0
  32. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +22 -0
  33. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/_bdd_writer.py +41 -0
  34. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/dddmp_reader.py +47 -0
  35. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/dddmp_writer.py +51 -0
  36. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +29 -0
  37. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_cnf.py +202 -0
  38. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd_pl.py +136 -0
  39. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/json_reader.py +35 -0
  40. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/json_writer.py +28 -0
  41. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pdf_writer.py +8 -0
  42. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pickle_reader.py +22 -0
  43. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/pickle_writer.py +23 -0
  44. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/png_writer.py +8 -0
  45. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/sat_to_bdd.py +48 -0
  46. flamapy-bdd-2.0.0.dev1/flamapy/metamodels/bdd_metamodel/transformations/svg_writer.py +8 -0
  47. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy_bdd.egg-info/PKG-INFO +75 -72
  48. flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/SOURCES.txt +52 -0
  49. flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/dependency_links.txt +1 -0
  50. flamapy-bdd-2.0.0.dev1/flamapy_bdd.egg-info/requires.txt +11 -0
  51. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/setup.py +6 -6
  52. flamapy-bdd-1.0.1.dev0/README.md +0 -195
  53. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/models/__init__.py +0 -4
  54. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/models/bdd_model.py +0 -99
  55. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/__init__.py +0 -15
  56. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_feature_inclusion_probability.py +0 -48
  57. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_product_distribution.py +0 -58
  58. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_products.py +0 -48
  59. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/bdd_products_number.py +0 -42
  60. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/operations/interfaces/__init__.py +0 -5
  61. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/__init__.py +0 -5
  62. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/bdd_writer.py +0 -79
  63. flamapy-bdd-1.0.1.dev0/flamapy/metamodels/bdd_metamodel/transformations/fm_to_bdd.py +0 -156
  64. flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/SOURCES.txt +0 -24
  65. flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/dependency_links.txt +0 -1
  66. flamapy-bdd-1.0.1.dev0/flamapy_bdd.egg-info/requires.txt +0 -10
  67. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy/metamodels/bdd_metamodel/__init__.py +0 -0
  68. {flamapy-bdd-1.0.1.dev0 → flamapy-bdd-2.0.0.dev1}/flamapy_bdd.egg-info/top_level.txt +0 -0
  69. {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: 1.0.1.dev0
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.featureide_reader import FeatureIDEReader
70
- from flamapy.metamodels.bdd_metamodel.transformations.fm_to_bdd import FmToBDD
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 FeatureIDE
73
- feature_model = FeatureIDEReader('input_fms/featureide_models/pizzas.xml').transform()
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.bdd_writer import BDDWriter, BDDDumpFormat
82
+ from flamapy.metamodels.bdd_metamodel.transformations import PNGWriter, DDDMPv3Writer
82
83
  # Save the BDD as an image in PNG
83
- BDDWriter(path='my_bdd.png',
84
- source_model=bdd_model,
85
- roots=[bdd_model.root],
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
- Formats supported: DDDMP_V3 ('dddmp'), DDDMP_V2 ('dddmp2'), PDF ('pdf'), PNG ('png'), SVG ('svg').
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
- - Products number
102
+ - Satisfiable
94
103
 
95
- Return the number of products (configurations):
104
+ Return whether the model is satisfiable (valid):
96
105
  ```python
97
- from flamapy.metamodels.bdd_metamodel.operations import BDDProductsNumber
98
- nof_products = BDDProductsNumber().execute(bdd_model).get_result()
99
- print(f'#Products: {nof_products}')
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
- - Products
111
+ - Configurations number
109
112
 
110
- Return the list of products (configurations):
113
+ Return the number of configurations:
111
114
  ```python
112
- from flamapy.metamodels.bdd_metamodel.operations import BDDProducts
113
- list_products = BDDProducts().execute(bdd_model).get_result()
114
- for i, prod in enumerate(list_products):
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
- or alternatively:
119
+
120
+ - Configurations
121
+
122
+ Enumerate the configurations of the model:
118
123
  ```python
119
- from flamapy.metamodels.bdd_metamodel.operations import products
120
- nof_products = products(bdd_model)
121
- for i, prod in enumerate(list_products):
122
- print(f'Product {i}: {[feat for feat in prod.elements if prod.elements[feat]]}')
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 products (configurations) with or without replacement:
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
- list_sample = BDDSampling(size=5, with_replacement=False).execute(bdd_model).get_result()
131
- for i, prod in enumerate(list_sample):
132
- print(f'Product {i}: {[feat for feat in prod.elements if prod.elements[feat]]}')
133
- ```
134
- or alternatively:
135
- ```python
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 product_distribution
153
- dist = product_distribution(bdd_model)
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 product:
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 BDDFeatureInclusionProbabilityBF
162
- prob = BDDFeatureInclusionProbabilityBF().execute(bdd_model).get_result()
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
- or alternatively:
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 feature_inclusion_probability
169
- prob = feature_inclusion_probability(bdd_model)
170
- for feat in prob.keys():
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
- All 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:
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 products from the partial configuration
182
- nof_products = BDDProductsNumber(partial_config).execute(bdd_model).get_result()
183
- print(f'#Products: {nof_products}')
184
- ```
185
- or alternatively:
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,4 @@
1
+ from .bdd_model import BDDModel
2
+
3
+
4
+ __all__ = ['BDDModel']
@@ -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
@@ -5,4 +5,4 @@ from .txtcnf import (
5
5
  )
6
6
 
7
7
 
8
- __all__ = ['CNFLogicConnective', 'TextCNFNotation', 'TextCNFModel']
8
+ __all__ = ['CNFLogicConnective', 'TextCNFNotation', 'TextCNFModel']