|
1 | | -# Automated Analysis of UVL using Satisfiability Modulo Theories |
| 1 | +# flamapy-z3 |
2 | 2 |
|
| 3 | +SMT-based analysis for [flamapy](https://flamapy.org) feature models with typed |
| 4 | +attributes (Integer, Real, String), built on the [Z3](https://github.com/Z3Prover/z3) |
| 5 | +solver. |
3 | 6 |
|
4 | | -## Description |
5 | | -This repository contains the plugin that supports z3 representations for feature models. |
| 7 | +**Documentation:** https://docs.flamapy.org/framework/plugins/z3_plugin |
6 | 8 |
|
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: |
| 9 | +## Installation |
8 | 10 |
|
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() |
| 11 | +```bash |
| 12 | +pip install flamapy-z3 |
52 | 13 | ``` |
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 | | -``` |
0 commit comments