flamapy-z3 2.1.0.dev1__tar.gz → 2.5.0.dev0__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 (35) hide show
  1. flamapy_z3-2.5.0.dev0/PKG-INFO +202 -0
  2. flamapy_z3-2.5.0.dev0/README.md +183 -0
  3. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/models/__init__.py +1 -1
  4. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/models/z3_model.py +35 -38
  5. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/__init__.py +9 -4
  6. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/interfaces/attribute_optimization.py +1 -1
  7. flamapy_z3-2.5.0.dev0/flamapy/metamodels/z3_metamodel/operations/z3_all_feature_bounds.py +86 -0
  8. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_attribute_optimization.py +126 -136
  9. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_configurations.py +35 -32
  10. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_configurations_number.py +1 -1
  11. flamapy_z3-2.5.0.dev0/flamapy/metamodels/z3_metamodel/operations/z3_feature_bounds.py +213 -0
  12. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_satisfiable_configuration.py +6 -6
  13. flamapy_z3-2.5.0.dev0/flamapy/metamodels/z3_metamodel/operations/z3_variable_bounds_complex.py +216 -0
  14. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/transformations/__init__.py +1 -1
  15. flamapy_z3-2.5.0.dev0/flamapy/metamodels/z3_metamodel/transformations/fm_to_z3.py +465 -0
  16. flamapy_z3-2.5.0.dev0/flamapy_z3.egg-info/PKG-INFO +202 -0
  17. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy_z3.egg-info/SOURCES.txt +4 -0
  18. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy_z3.egg-info/requires.txt +2 -2
  19. flamapy_z3-2.5.0.dev0/pyproject.toml +76 -0
  20. flamapy_z3-2.5.0.dev0/setup.py +3 -0
  21. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/tests/test_z3_metamodel.py +4 -2
  22. flamapy_z3-2.1.0.dev1/PKG-INFO +0 -221
  23. flamapy_z3-2.1.0.dev1/README.md +0 -189
  24. flamapy_z3-2.1.0.dev1/flamapy/metamodels/z3_metamodel/transformations/fm_to_z3.py +0 -451
  25. flamapy_z3-2.1.0.dev1/flamapy_z3.egg-info/PKG-INFO +0 -221
  26. flamapy_z3-2.1.0.dev1/setup.py +0 -37
  27. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/__init__.py +0 -0
  28. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/interfaces/__init__.py +0 -0
  29. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_core_features.py +0 -0
  30. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_dead_features.py +0 -0
  31. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_false_optional_features.py +0 -0
  32. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy/metamodels/z3_metamodel/operations/z3_satisfiable.py +0 -0
  33. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy_z3.egg-info/dependency_links.txt +0 -0
  34. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/flamapy_z3.egg-info/top_level.txt +0 -0
  35. {flamapy_z3-2.1.0.dev1 → flamapy_z3-2.5.0.dev0}/setup.cfg +0 -0
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: flamapy-z3
3
+ Version: 2.5.0.dev0
4
+ Summary: z3-plugin for the automated analysis of feature models
5
+ Author-email: Flamapy <flamapy@us.es>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/flamapy/z3_metamodel
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: flamapy-fw~=2.5.0.dev0
11
+ Requires-Dist: flamapy-fm~=2.5.0.dev0
12
+ Requires-Dist: z3-solver~=4.14.1.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pytest-mock; extra == "dev"
16
+ Requires-Dist: prospector; extra == "dev"
17
+ Requires-Dist: mypy; extra == "dev"
18
+ Requires-Dist: coverage; extra == "dev"
19
+
20
+ # Automated Analysis of UVL using Satisfiability Modulo Theories
21
+
22
+
23
+ ## Description
24
+ This repository contains the plugin that supports z3 representations for feature models.
25
+
26
+ The plugin is based on [flamapy](https://flamapy.github.io/), and relies on the [Z3 solver](https://github.com/Z3Prover/z3?tab=readme-ov-file) library. The architecture is as follows:
27
+
28
+ <p align="center">
29
+ <img width="750" src="resources/images/z3metamodel.png">
30
+ </p>
31
+
32
+
33
+ ## Requirements and Installation
34
+ - [Python 3.11+](https://www.python.org/)
35
+ - [Flamapy](https://www.flamapy.org/)
36
+
37
+ The framework has been tested in Linux and Windows 11 with Python 3.12. Python 3.13+ may not be still supported.
38
+
39
+ ### Download and installation
40
+ 1. Install [Python 3.11+](https://www.python.org/).
41
+ 2. Download/Clone this repository and enter into the main directory.
42
+ 3. Create a virtual environment: `python -m venv env`
43
+ 4. Activate the environment:
44
+
45
+ In Linux: `source env/bin/activate`
46
+
47
+ In Windows: `.\env\Scripts\Activate`
48
+
49
+ 5. Install dependencies (flamapy): `pip install -r requirements.txt`
50
+
51
+ ** In case that you are running Ubuntu and get an error installing flamapy, please install the package python3-dev with the command `sudo apt update && sudo apt install python3-dev` and update wheel and setuptools with the command `pip install --upgrade pip wheel setuptools` before step 5.
52
+
53
+
54
+ ## Functionality and usage
55
+ The executable script [test.py](/test.py) serves as an entry point to show the plugin in action.
56
+
57
+ Simply run: `python test.py` to see it in action over the running feature model presented in the paper.
58
+
59
+ The following functionality is provided:
60
+
61
+
62
+ ### Load a feature model in UVL and translate to SMT
63
+ ```python
64
+ from flamapy.metamodels.fm_metamodel.transformations import UVLReader
65
+ from flamapy.metamodels.z3_metamodel.transformations import FmToZ3
66
+
67
+ # Load the feature model from UVL
68
+ fm_model = UVLReader('resources/models/uvl_models/Pizza_z3.uvl').transform()
69
+ # Transform the feature model to SMT
70
+ z3_model = FmToZ3(fm_model).transform()
71
+ ```
72
+
73
+ ### Analysis operations
74
+ The following operations are available:
75
+ ```python
76
+ from flamapy.metamodels.z3_metamodel.operations import (
77
+ Z3Satisfiable,
78
+ Z3Configurations,
79
+ Z3ConfigurationsNumber,
80
+ Z3CoreFeatures,
81
+ Z3DeadFeatures,
82
+ Z3FalseOptionalFeatures,
83
+ Z3AttributeOptimization,
84
+ Z3SatisfiableConfiguration,
85
+ Z3FeatureBounds,
86
+ Z3AllFeatureBounds,
87
+ )
88
+ ```
89
+
90
+ - **Satisfiable**
91
+
92
+ Return whether the model is satisfiable (valid):
93
+ ```python
94
+ satisfiable = Z3Satisfiable().execute(z3_model).get_result()
95
+ print(f'Satisfiable? (valid?): {satisfiable}')
96
+ ```
97
+
98
+ - **Core features**
99
+
100
+ Return the core features of the model:
101
+ ```python
102
+ core_features = Z3CoreFeatures().execute(z3_model).get_result()
103
+ print(f'Core features: {core_features}')
104
+ ```
105
+
106
+ - **Dead features**
107
+
108
+ Return the dead features of the model:
109
+ ```python
110
+ dead_features = Z3DeadFeatures().execute(z3_model).get_result()
111
+ print(f'Dead features: {dead_features}')
112
+ ```
113
+
114
+ - **False-Optional features**
115
+
116
+ Return the false-optional features of the model:
117
+ ```python
118
+ false_optional_features = Z3FalseOptionalFeatures().execute(z3_model).get_result()
119
+ print(f'False-optional features: {false_optional_features}')
120
+ ```
121
+
122
+ - **Configurations**
123
+
124
+ Enumerate the configurations of the model:
125
+ ```python
126
+ configurations = Z3Configurations().execute(z3_model).get_result()
127
+ print(f'Configurations: {len(configurations)}')
128
+ for i, config in enumerate(configurations, 1):
129
+ config_str = ', '.join(f'{f}={v}' if not isinstance(v, bool) else f'{f}' for f,v in config.elements.items() if config.is_selected(f))
130
+ print(f'Config. {i}: {config_str}')
131
+ ```
132
+
133
+ - **Configurations number**
134
+
135
+ Return the number of configurations:
136
+ ```python
137
+ n_configs = Z3ConfigurationsNumber().execute(z3_model).get_result()
138
+ print(f'Configurations number: {n_configs}')
139
+ ```
140
+
141
+ - **Boundaries analysis of typed features**
142
+
143
+ Return the boundaries of the numerical features (Integer, Real, String) of the model:
144
+ ```python
145
+ attributes = fm_model.get_attributes()
146
+ print('Attributes in the model')
147
+ for attr in attributes:
148
+ print(f' - {attr.name} ({attr.attribute_type})')
149
+
150
+ variable_bounds = Z3AllFeatureBounds().execute(z3_model).get_result()
151
+ print('Variable bounds for all typed variables:')
152
+ for var_name, bounds in variable_bounds.items():
153
+ print(f' - {var_name}: {bounds}')
154
+ ```
155
+
156
+ - **Configuration optimization based on feature attributes:**
157
+
158
+ Return the set of configurations that optimize the given goals (i.e., the pareto front):
159
+ ```python
160
+ attribute_optimization_op = Z3AttributeOptimization()
161
+ attributes = {'Price': OptimizationGoal.MAXIMIZE,
162
+ 'Kcal': OptimizationGoal.MINIMIZE}
163
+ attribute_optimization_op.set_attributes(attributes)
164
+ configurations_with_values = attribute_optimization_op.execute(z3_model).get_result()
165
+ print(f'Optimum configurations: {len(configurations_with_values)} configs.')
166
+ for i, config_value in enumerate(configurations_with_values, 1):
167
+ config, values = config_value
168
+ config_str = ', '.join(f'{f}={v}' if not isinstance(v, bool) else f'{f}' for f,v in config.elements.items() if config.is_selected(f))
169
+ values_str = ', '.join(f'{k}={v}' for k,v in values.items())
170
+ print(f'Config. {i}: {config_str} | Values: {values_str}')
171
+ ```
172
+
173
+ - Configuration validation:
174
+
175
+ Return whether a given partial or full configuration is valid:
176
+ ```python
177
+ from flamapy.metamodels.configuration_metamodel.transformations import ConfigurationJSONReader
178
+ configuration = ConfigurationJSONReader('resources/configs/pizza_z3_config1.json').transform()
179
+ configuration.set_full(False)
180
+ print(f'Configuration: {configuration.elements}')
181
+ satisfiable_configuration_op = Z3SatisfiableConfiguration()
182
+ satisfiable_configuration_op.set_configuration(configuration)
183
+ is_satisfiable = satisfiable_configuration_op.execute(z3_model).get_result()
184
+ print(f'Is the configuration satisfiable? {is_satisfiable}')
185
+ ```
186
+
187
+ **Note:** The Z3Configurations and Z3ConfigurationsNumber operations may takes longer if the number of configuration is huge, or even not finish if the model is unbounded.
188
+
189
+ **Note:** The Z3Configurations and Z3ConfigurationsNumber operations support also a partial configuration as an additional argument, so the operation will return the result taking into account the given partial configuration.
190
+ For example:
191
+
192
+ ```python
193
+ from flamapy.core.models import Configuration
194
+ # Create a partial configuration
195
+ elements = {'Pizza': True, 'SpicyLvl': 5}
196
+ partial_config = Configuration(elements)
197
+ # Calculate the number of configuration from the partial configuration
198
+ configs_number_op = Z3ConfigurationsNumber()
199
+ configs_number_op.set_partial_configuration(partial_config)
200
+ n_configs = configs_number_op.execute(z3_model).get_result()
201
+ print(f'#Configurations: {n_configs}')
202
+ ```
@@ -0,0 +1,183 @@
1
+ # Automated Analysis of UVL using Satisfiability Modulo Theories
2
+
3
+
4
+ ## Description
5
+ This repository contains the plugin that supports z3 representations for feature models.
6
+
7
+ The plugin is based on [flamapy](https://flamapy.github.io/), and relies on the [Z3 solver](https://github.com/Z3Prover/z3?tab=readme-ov-file) library. The architecture is as follows:
8
+
9
+ <p align="center">
10
+ <img width="750" src="resources/images/z3metamodel.png">
11
+ </p>
12
+
13
+
14
+ ## Requirements and Installation
15
+ - [Python 3.11+](https://www.python.org/)
16
+ - [Flamapy](https://www.flamapy.org/)
17
+
18
+ The framework has been tested in Linux and Windows 11 with Python 3.12. Python 3.13+ may not be still supported.
19
+
20
+ ### Download and installation
21
+ 1. Install [Python 3.11+](https://www.python.org/).
22
+ 2. Download/Clone this repository and enter into the main directory.
23
+ 3. Create a virtual environment: `python -m venv env`
24
+ 4. Activate the environment:
25
+
26
+ In Linux: `source env/bin/activate`
27
+
28
+ In Windows: `.\env\Scripts\Activate`
29
+
30
+ 5. Install dependencies (flamapy): `pip install -r requirements.txt`
31
+
32
+ ** In case that you are running Ubuntu and get an error installing flamapy, please install the package python3-dev with the command `sudo apt update && sudo apt install python3-dev` and update wheel and setuptools with the command `pip install --upgrade pip wheel setuptools` before step 5.
33
+
34
+
35
+ ## Functionality and usage
36
+ The executable script [test.py](/test.py) serves as an entry point to show the plugin in action.
37
+
38
+ Simply run: `python test.py` to see it in action over the running feature model presented in the paper.
39
+
40
+ The following functionality is provided:
41
+
42
+
43
+ ### Load a feature model in UVL and translate to SMT
44
+ ```python
45
+ from flamapy.metamodels.fm_metamodel.transformations import UVLReader
46
+ from flamapy.metamodels.z3_metamodel.transformations import FmToZ3
47
+
48
+ # Load the feature model from UVL
49
+ fm_model = UVLReader('resources/models/uvl_models/Pizza_z3.uvl').transform()
50
+ # Transform the feature model to SMT
51
+ z3_model = FmToZ3(fm_model).transform()
52
+ ```
53
+
54
+ ### Analysis operations
55
+ The following operations are available:
56
+ ```python
57
+ from flamapy.metamodels.z3_metamodel.operations import (
58
+ Z3Satisfiable,
59
+ Z3Configurations,
60
+ Z3ConfigurationsNumber,
61
+ Z3CoreFeatures,
62
+ Z3DeadFeatures,
63
+ Z3FalseOptionalFeatures,
64
+ Z3AttributeOptimization,
65
+ Z3SatisfiableConfiguration,
66
+ Z3FeatureBounds,
67
+ Z3AllFeatureBounds,
68
+ )
69
+ ```
70
+
71
+ - **Satisfiable**
72
+
73
+ Return whether the model is satisfiable (valid):
74
+ ```python
75
+ satisfiable = Z3Satisfiable().execute(z3_model).get_result()
76
+ print(f'Satisfiable? (valid?): {satisfiable}')
77
+ ```
78
+
79
+ - **Core features**
80
+
81
+ Return the core features of the model:
82
+ ```python
83
+ core_features = Z3CoreFeatures().execute(z3_model).get_result()
84
+ print(f'Core features: {core_features}')
85
+ ```
86
+
87
+ - **Dead features**
88
+
89
+ Return the dead features of the model:
90
+ ```python
91
+ dead_features = Z3DeadFeatures().execute(z3_model).get_result()
92
+ print(f'Dead features: {dead_features}')
93
+ ```
94
+
95
+ - **False-Optional features**
96
+
97
+ Return the false-optional features of the model:
98
+ ```python
99
+ false_optional_features = Z3FalseOptionalFeatures().execute(z3_model).get_result()
100
+ print(f'False-optional features: {false_optional_features}')
101
+ ```
102
+
103
+ - **Configurations**
104
+
105
+ Enumerate the configurations of the model:
106
+ ```python
107
+ configurations = Z3Configurations().execute(z3_model).get_result()
108
+ print(f'Configurations: {len(configurations)}')
109
+ for i, config in enumerate(configurations, 1):
110
+ config_str = ', '.join(f'{f}={v}' if not isinstance(v, bool) else f'{f}' for f,v in config.elements.items() if config.is_selected(f))
111
+ print(f'Config. {i}: {config_str}')
112
+ ```
113
+
114
+ - **Configurations number**
115
+
116
+ Return the number of configurations:
117
+ ```python
118
+ n_configs = Z3ConfigurationsNumber().execute(z3_model).get_result()
119
+ print(f'Configurations number: {n_configs}')
120
+ ```
121
+
122
+ - **Boundaries analysis of typed features**
123
+
124
+ Return the boundaries of the numerical features (Integer, Real, String) of the model:
125
+ ```python
126
+ attributes = fm_model.get_attributes()
127
+ print('Attributes in the model')
128
+ for attr in attributes:
129
+ print(f' - {attr.name} ({attr.attribute_type})')
130
+
131
+ variable_bounds = Z3AllFeatureBounds().execute(z3_model).get_result()
132
+ print('Variable bounds for all typed variables:')
133
+ for var_name, bounds in variable_bounds.items():
134
+ print(f' - {var_name}: {bounds}')
135
+ ```
136
+
137
+ - **Configuration optimization based on feature attributes:**
138
+
139
+ Return the set of configurations that optimize the given goals (i.e., the pareto front):
140
+ ```python
141
+ attribute_optimization_op = Z3AttributeOptimization()
142
+ attributes = {'Price': OptimizationGoal.MAXIMIZE,
143
+ 'Kcal': OptimizationGoal.MINIMIZE}
144
+ attribute_optimization_op.set_attributes(attributes)
145
+ configurations_with_values = attribute_optimization_op.execute(z3_model).get_result()
146
+ print(f'Optimum configurations: {len(configurations_with_values)} configs.')
147
+ for i, config_value in enumerate(configurations_with_values, 1):
148
+ config, values = config_value
149
+ config_str = ', '.join(f'{f}={v}' if not isinstance(v, bool) else f'{f}' for f,v in config.elements.items() if config.is_selected(f))
150
+ values_str = ', '.join(f'{k}={v}' for k,v in values.items())
151
+ print(f'Config. {i}: {config_str} | Values: {values_str}')
152
+ ```
153
+
154
+ - Configuration validation:
155
+
156
+ Return whether a given partial or full configuration is valid:
157
+ ```python
158
+ from flamapy.metamodels.configuration_metamodel.transformations import ConfigurationJSONReader
159
+ configuration = ConfigurationJSONReader('resources/configs/pizza_z3_config1.json').transform()
160
+ configuration.set_full(False)
161
+ print(f'Configuration: {configuration.elements}')
162
+ satisfiable_configuration_op = Z3SatisfiableConfiguration()
163
+ satisfiable_configuration_op.set_configuration(configuration)
164
+ is_satisfiable = satisfiable_configuration_op.execute(z3_model).get_result()
165
+ print(f'Is the configuration satisfiable? {is_satisfiable}')
166
+ ```
167
+
168
+ **Note:** The Z3Configurations and Z3ConfigurationsNumber operations may takes longer if the number of configuration is huge, or even not finish if the model is unbounded.
169
+
170
+ **Note:** The Z3Configurations and Z3ConfigurationsNumber operations support also a partial configuration as an additional argument, so the operation will return the result taking into account the given partial configuration.
171
+ For example:
172
+
173
+ ```python
174
+ from flamapy.core.models import Configuration
175
+ # Create a partial configuration
176
+ elements = {'Pizza': True, 'SpicyLvl': 5}
177
+ partial_config = Configuration(elements)
178
+ # Calculate the number of configuration from the partial configuration
179
+ configs_number_op = Z3ConfigurationsNumber()
180
+ configs_number_op.set_partial_configuration(partial_config)
181
+ n_configs = configs_number_op.execute(z3_model).get_result()
182
+ print(f'#Configurations: {n_configs}')
183
+ ```
@@ -1,4 +1,4 @@
1
1
  from .z3_model import Z3Model, FeatureInfo
2
2
 
3
3
 
4
- __all__ = ['Z3Model', 'FeatureInfo']
4
+ __all__ = ['FeatureInfo', 'Z3Model']
@@ -22,7 +22,7 @@ class FeatureInfo:
22
22
 
23
23
 
24
24
  class Z3Model(VariabilityModel):
25
-
25
+
26
26
  DEFAULT_PRECISION = 4
27
27
 
28
28
  @staticmethod
@@ -52,15 +52,15 @@ class Z3Model(VariabilityModel):
52
52
  def add_boolean_feature(self, name: str) -> Any:
53
53
  """Add a boolean feature with the given name."""
54
54
  sel = z3.Bool(name, ctx=self.ctx)
55
- self.features[name] = FeatureInfo(name=name,
56
- sel=sel,
57
- val=None,
58
- ftype=FeatureType.BOOLEAN,
55
+ self.features[name] = FeatureInfo(name=name,
56
+ sel=sel,
57
+ val=None,
58
+ ftype=FeatureType.BOOLEAN,
59
59
  attributes={})
60
60
  return sel
61
61
 
62
- def add_typed_feature(self,
63
- name: str,
62
+ def add_typed_feature(self,
63
+ name: str,
64
64
  ftype: FeatureType,
65
65
  const_value: Optional[Any]=None,
66
66
  neutral_when_unselected: Optional[Any]=None) -> tuple[Any, Any]:
@@ -110,39 +110,36 @@ class Z3Model(VariabilityModel):
110
110
  def get_variable(self, name: str) -> Optional[FeatureInfo]:
111
111
  """Get the FeatureInfo of a feature by name."""
112
112
  return self.features.get(name, None)
113
-
114
- def add_attribute(self,
115
- feature_name: str,
116
- attr_name: str,
117
- attr_type: AttributeType,
113
+
114
+ def _create_attribute_var(self,
115
+ attr_type: AttributeType,
116
+ var_name: str) -> tuple[Optional[Any], Optional[Any]]:
117
+ """Create a Z3 variable and default value for the given attribute type."""
118
+ if attr_type == AttributeType.INTEGER:
119
+ return z3.Int(var_name, ctx=self.ctx), self.create_const(AttributeType.INTEGER, 0)
120
+ if attr_type == AttributeType.REAL:
121
+ return z3.Real(var_name, ctx=self.ctx), self.create_const(AttributeType.REAL, 0.0)
122
+ if attr_type == AttributeType.STRING:
123
+ return z3.String(var_name, ctx=self.ctx), self.create_const(AttributeType.STRING, "")
124
+ if attr_type == AttributeType.BOOLEAN:
125
+ return z3.Bool(var_name, ctx=self.ctx), self.create_const(AttributeType.BOOLEAN, False)
126
+ if attr_type in (AttributeType.NESTED, AttributeType.VECTOR):
127
+ LOGGER.warning(f"Warning: Attribute {var_name} has {attr_type.name} type, "
128
+ "which is not currently supported in Z3Model. Ignored.")
129
+ return None, None
130
+ raise ValueError("Unsupported attribute type")
131
+
132
+ def add_attribute(self,
133
+ feature_name: str,
134
+ attr_name: str,
135
+ attr_type: AttributeType,
118
136
  const_value: Optional[Any]=None) -> Optional[Any]:
119
137
  """Add an attribute to a feature (attributes are typed variables)."""
120
138
  if feature_name not in self.features:
121
139
  raise KeyError(feature_name)
122
140
  info = self.features[feature_name]
123
141
  var_name = f"{feature_name}.{attr_name}"
124
- if attr_type == AttributeType.INTEGER:
125
- var = z3.Int(var_name, ctx=self.ctx)
126
- default_value = self.create_const(AttributeType.INTEGER, 0)
127
- elif attr_type == AttributeType.REAL:
128
- var = z3.Real(var_name, ctx=self.ctx)
129
- default_value = self.create_const(AttributeType.REAL, 0.0)
130
- elif attr_type == AttributeType.STRING:
131
- var = z3.String(var_name, ctx=self.ctx)
132
- default_value = self.create_const(AttributeType.STRING, "")
133
- elif attr_type == AttributeType.BOOLEAN:
134
- var = z3.Bool(var_name, ctx=self.ctx)
135
- default_value = self.create_const(AttributeType.BOOLEAN, False)
136
- elif attr_type == AttributeType.NESTED:
137
- LOGGER.warning(f"Warning: Attribute {var_name} has NESTED type, " \
138
- "which is not currently supported in Z3Model. Ignored.")
139
- var = None
140
- elif attr_type == AttributeType.VECTOR:
141
- LOGGER.warning(f"Warning: Attribute {var_name} has VECTOR type, " \
142
- "which is not currently supported in Z3Model. Ignored.")
143
- var = None
144
- else:
145
- raise ValueError("Unsupported attribute type")
142
+ var, default_value = self._create_attribute_var(attr_type, var_name)
146
143
 
147
144
  if var is not None:
148
145
  info.attributes[attr_name] = {"var": var, "type": attr_type}
@@ -214,9 +211,9 @@ class Z3Model(VariabilityModel):
214
211
  raise ValueError(f'Feature {feature_info.name} has no value expression.')
215
212
  val_var = feature_info.val
216
213
  z3_value = Z3Model.get_z3_value(feature_value, feature_info.ftype, context)
217
- constraints.append(val_var == z3_value)
214
+ constraints.append(val_var == z3_value)
218
215
  return constraints
219
-
216
+
220
217
  @staticmethod
221
218
  def get_z3_value(value: Any, ftype: FeatureType, context: z3.Context) -> z3.ExprRef:
222
219
  """Return a Z3 expression for a given Python configuration value."""
@@ -237,7 +234,7 @@ class Z3Model(VariabilityModel):
237
234
 
238
235
  @staticmethod
239
236
  def sum_attribute(model: 'Z3Model', attr_name: str) -> z3.ArithRef:
240
- """Return a Z3 expression representing the sum of the given attribute across
237
+ """Return a Z3 expression representing the sum of the given attribute across
241
238
  all features."""
242
239
  exprs = []
243
240
  for _, feature_info in model.features.items():
@@ -249,4 +246,4 @@ class Z3Model(VariabilityModel):
249
246
  zero_val = z3.RealVal(0.0, ctx=model.ctx)
250
247
  expr = z3.If(feature_info.sel, attr['var'], zero_val)
251
248
  exprs.append(expr)
252
- return z3.Sum(exprs) if exprs else z3.RealVal(0.0, ctx=model.ctx)
249
+ return z3.Sum(exprs) if exprs else z3.RealVal(0.0, ctx=model.ctx)
@@ -6,13 +6,18 @@ from .z3_dead_features import Z3DeadFeatures
6
6
  from .z3_false_optional_features import Z3FalseOptionalFeatures
7
7
  from .z3_attribute_optimization import Z3AttributeOptimization
8
8
  from .z3_satisfiable_configuration import Z3SatisfiableConfiguration
9
+ from .z3_feature_bounds import Z3FeatureBounds
10
+ from .z3_all_feature_bounds import Z3AllFeatureBounds
9
11
 
10
-
11
- __all__ = ['Z3Satisfiable',
12
+ __all__ = [
13
+ 'Z3AllFeatureBounds',
14
+ 'Z3AttributeOptimization',
12
15
  'Z3Configurations',
13
16
  'Z3ConfigurationsNumber',
14
17
  'Z3CoreFeatures',
15
18
  'Z3DeadFeatures',
16
19
  'Z3FalseOptionalFeatures',
17
- 'Z3AttributeOptimization',
18
- 'Z3SatisfiableConfiguration']
20
+ 'Z3FeatureBounds',
21
+ 'Z3Satisfiable',
22
+ 'Z3SatisfiableConfiguration',
23
+ ]
@@ -21,7 +21,7 @@ class AttributeOptimization(Operation):
21
21
  @abstractmethod
22
22
  def set_attributes(self, attributes: dict[Attribute, OptimizationGoal]) -> None:
23
23
  pass
24
-
24
+
25
25
  @abstractmethod
26
26
  def optimize(self) -> list[Configuration]:
27
27
  pass
@@ -0,0 +1,86 @@
1
+ import logging
2
+ from typing import Any, cast
3
+
4
+ from flamapy.core.models import VariabilityModel
5
+ from flamapy.core.operations import Operation
6
+ from flamapy.metamodels.z3_metamodel.models import Z3Model
7
+ from flamapy.metamodels.z3_metamodel.operations import Z3FeatureBounds
8
+ from flamapy.metamodels.fm_metamodel.models import FeatureType
9
+
10
+
11
+ LOGGER = logging.getLogger(__name__)
12
+
13
+
14
+ class Z3AllFeatureBounds(Operation):
15
+ """Computes the effective bounds (min/max) for ALL typed feature
16
+ (Integer, Real, String length) in the Z3 model.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ # El resultado será un diccionario donde la clave es el nombre de la variable
21
+ # y el valor es el diccionario de bounds (min, max, bounded).
22
+ self._result: dict[str, dict[str, Any]] = {}
23
+
24
+ def get_result(self) -> dict[str, dict[str, Any]]:
25
+ """
26
+ Devuelve un diccionario con los bounds de todas las variables tipadas.
27
+ Formato: {'var_name': {'min': ..., 'max': ..., 'bounded': True/False}, ...}
28
+ """
29
+ return self._result
30
+
31
+ def execute(self, model: VariabilityModel) -> 'Z3AllFeatureBounds':
32
+ z3_model = cast(Z3Model, model)
33
+ all_bounds_result: dict[str, dict[str, Any]] = {}
34
+
35
+ # Instanciamos la operación de bajo nivel que ya definiste
36
+ bounds_op = Z3FeatureBounds()
37
+
38
+ # Iterar sobre todas las features del modelo Z3
39
+ for var_name, feature_info in z3_model.features.items():
40
+ ftype = feature_info.ftype
41
+
42
+ # Solo procesar variables tipadas (Integer, Real, String)
43
+ if ftype in (FeatureType.INTEGER, FeatureType.REAL, FeatureType.STRING):
44
+
45
+ try:
46
+ # 1. Configurar la operación con el nombre de la variable
47
+ bounds_op.set_variable_name(var_name)
48
+
49
+ # 2. Ejecutar la operación Z3VariableBounds para la variable actual
50
+ bounds_op.execute(z3_model)
51
+
52
+ # 3. Obtener el resultado unificado
53
+ bounds = bounds_op.get_result()
54
+
55
+ # 4. Almacenar el resultado en el diccionario final
56
+ if 'error' not in bounds:
57
+ # Aseguramos que solo guardamos los campos unificados y esenciales
58
+ all_bounds_result[var_name] = {
59
+ 'feature_type': bounds.get('feature_type', ftype.name),
60
+ 'min': bounds.get('min', 'N/A'),
61
+ 'max': bounds.get('max', 'N/A'),
62
+ 'bounded': bounds.get('bounded', False)
63
+ }
64
+ else:
65
+ LOGGER.warning(
66
+ f"Error calculating bounds for {var_name}: {bounds['error']}"
67
+ )
68
+ all_bounds_result[var_name] = {
69
+ 'feature_type': ftype.name,
70
+ 'min': 'ERROR',
71
+ 'max': 'ERROR',
72
+ 'bounded': False
73
+ }
74
+
75
+ except Exception as e:
76
+ LOGGER.error(f"Execution error for variable {var_name}: {e}")
77
+ all_bounds_result[var_name] = {
78
+ 'feature_type': ftype.name,
79
+ 'min': 'RUNTIME_ERROR',
80
+ 'max': 'RUNTIME_ERROR',
81
+ 'bounded': False
82
+ }
83
+
84
+
85
+ self._result = all_bounds_result
86
+ return self