flamapy-z3 2.6.0.dev4__tar.gz → 2.6.0.dev5__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_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/PKG-INFO +3 -3
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/models/z3_model.py +3 -0
- flamapy_z3-2.6.0.dev5/flamapy/metamodels/z3_metamodel/operations/interfaces/attribute_optimization.py +12 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_false_optional_features.py +6 -6
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/transformations/fm_to_z3.py +52 -1
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy_z3.egg-info/PKG-INFO +3 -3
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy_z3.egg-info/SOURCES.txt +1 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy_z3.egg-info/requires.txt +2 -2
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/pyproject.toml +3 -3
- flamapy_z3-2.6.0.dev5/tests/test_tseytin.py +69 -0
- flamapy_z3-2.6.0.dev4/flamapy/metamodels/z3_metamodel/operations/interfaces/attribute_optimization.py +0 -27
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/README.md +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/__init__.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/models/__init__.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/__init__.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/interfaces/__init__.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_all_feature_bounds.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_attribute_optimization.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_backbone.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_configurations.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_configurations_number.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_core_features.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_dead_features.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_feature_bounds.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_satisfiable.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_satisfiable_configuration.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/operations/z3_variable_bounds_complex.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/transformations/__init__.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy_z3.egg-info/dependency_links.txt +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy_z3.egg-info/top_level.txt +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/setup.cfg +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/setup.py +0 -0
- {flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/tests/test_z3_metamodel.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-z3
|
|
3
|
-
Version: 2.6.0.
|
|
3
|
+
Version: 2.6.0.dev5
|
|
4
4
|
Summary: z3-plugin for the automated analysis of feature models
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/flamapy/z3_metamodel
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: flamapy-fw~=2.6.0.
|
|
11
|
-
Requires-Dist: flamapy-fm~=2.6.0.
|
|
10
|
+
Requires-Dist: flamapy-fw~=2.6.0.dev5
|
|
11
|
+
Requires-Dist: flamapy-fm~=2.6.0.dev5
|
|
12
12
|
Requires-Dist: z3-solver~=4.14.1.0
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest; extra == "dev"
|
{flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/models/z3_model.py
RENAMED
|
@@ -35,6 +35,9 @@ class Z3Model(VariabilityModel):
|
|
|
35
35
|
self.attributes: dict[str, list[Any]] = {} # attr_name -> [z3var]
|
|
36
36
|
self.attributes_types: dict[str, AttributeType] = {} # attr_name -> AttributeType
|
|
37
37
|
self.constraints: list[Any] = [] # list of z3 expressions
|
|
38
|
+
# Auxiliary (Tseytin) boolean variables. Kept out of ``features`` so that feature
|
|
39
|
+
# enumeration/counting/bounds operations ignore them.
|
|
40
|
+
self.auxiliary_variables: list[Any] = []
|
|
38
41
|
self.original_model: Optional[VariabilityModel] = None
|
|
39
42
|
|
|
40
43
|
def create_const(self, ftype: FeatureType | AttributeType, value: Any) -> Any:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Backward-compatible re-export.
|
|
2
|
+
|
|
3
|
+
``AttributeOptimization`` and ``OptimizationGoal`` now live in the core framework so
|
|
4
|
+
that every backend shares one interface. This module keeps the historical import path
|
|
5
|
+
``flamapy.metamodels.z3_metamodel.operations.interfaces.attribute_optimization`` working.
|
|
6
|
+
"""
|
|
7
|
+
from flamapy.core.operations.attribute_optimization import (
|
|
8
|
+
AttributeOptimization,
|
|
9
|
+
OptimizationGoal,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = ["AttributeOptimization", "OptimizationGoal"]
|
|
@@ -10,7 +10,7 @@ from flamapy.metamodels.fm_metamodel.models import FeatureModel
|
|
|
10
10
|
from flamapy.metamodels.z3_metamodel.models import Z3Model
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
LOGGER = logging.getLogger('
|
|
13
|
+
LOGGER = logging.getLogger('Z3FalseOptionalFeatures')
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Z3FalseOptionalFeatures(FalseOptionalFeatures):
|
|
@@ -26,11 +26,11 @@ class Z3FalseOptionalFeatures(FalseOptionalFeatures):
|
|
|
26
26
|
|
|
27
27
|
def execute(self, model: VariabilityModel) -> 'Z3FalseOptionalFeatures':
|
|
28
28
|
z3_model = cast(Z3Model, model)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
original_model = getattr(model, 'original_model', None)
|
|
30
|
+
if original_model is None:
|
|
31
|
+
raise FlamaException("The transformation didn't attach the source model, "
|
|
32
|
+
"which is required for this operation.")
|
|
33
|
+
feature_model = cast(FeatureModel, original_model)
|
|
34
34
|
self._result = get_false_optional_features(z3_model, feature_model)
|
|
35
35
|
return self
|
|
36
36
|
|
|
@@ -61,10 +61,16 @@ class FmToZ3(ModelToModel):
|
|
|
61
61
|
def get_destination_extension() -> str:
|
|
62
62
|
return "z3"
|
|
63
63
|
|
|
64
|
-
def __init__(self, source_model: FeatureModel) -> None:
|
|
64
|
+
def __init__(self, source_model: FeatureModel, cnf_method: str = 'direct') -> None:
|
|
65
65
|
self.source_model = source_model
|
|
66
66
|
self.destination_model: Z3Model = Z3Model()
|
|
67
67
|
self._counter: int = 0
|
|
68
|
+
# 'direct' (default) builds Z3 boolean expressions straight from the AST. 'tseytin'
|
|
69
|
+
# applies the Tseytin CNF encoding to purely propositional boolean constraints
|
|
70
|
+
# (others fall back to 'direct'). Framework-only opt-in; Z3 already clausifies
|
|
71
|
+
# internally, so this exists for cross-backend uniformity/experimentation, not speed.
|
|
72
|
+
self.cnf_method = cnf_method
|
|
73
|
+
self._aux_counter: int = 0
|
|
68
74
|
|
|
69
75
|
def transform(self) -> Z3Model:
|
|
70
76
|
self.destination_model = Z3Model()
|
|
@@ -258,9 +264,54 @@ class FmToZ3(ModelToModel):
|
|
|
258
264
|
self._add_inactive_parent_constraints(parent, children)
|
|
259
265
|
|
|
260
266
|
def _add_constraint_formula(self, ctc: Constraint) -> None:
|
|
267
|
+
if self.cnf_method == 'tseytin' and self._is_propositional_boolean(ctc):
|
|
268
|
+
self._add_tseytin_constraint(ctc)
|
|
269
|
+
return
|
|
261
270
|
expr = self._get_expression(ctc.ast.root, None)
|
|
262
271
|
self.destination_model.add_constraint(expr)
|
|
263
272
|
|
|
273
|
+
def _is_propositional_boolean(self, ctc: Constraint) -> bool:
|
|
274
|
+
"""Whether a constraint is purely propositional over boolean features, i.e. safe
|
|
275
|
+
for the Tseytin CNF encoding. Arithmetic/aggregation/typed constraints are not."""
|
|
276
|
+
if any(op not in LOGICAL_OPERATORS for op in ctc.ast.get_operators()):
|
|
277
|
+
return False
|
|
278
|
+
for operand in ctc.ast.get_operands():
|
|
279
|
+
if not isinstance(operand, str):
|
|
280
|
+
return False
|
|
281
|
+
info = self.destination_model.get_variable(operand)
|
|
282
|
+
if info is None or info.ftype != FeatureType.BOOLEAN:
|
|
283
|
+
return False
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
def _add_tseytin_constraint(self, ctc: Constraint) -> None:
|
|
287
|
+
clauses, aux_names = ctc.ast.get_clauses_with_aux(method='tseytin')
|
|
288
|
+
ctx = self.destination_model.ctx
|
|
289
|
+
# Fresh, globally-unique Z3 Bool per auxiliary name (same-named Bools in one Z3
|
|
290
|
+
# context are the same variable, so per-constraint names must not collide).
|
|
291
|
+
local: dict[str, Any] = {}
|
|
292
|
+
for name in aux_names:
|
|
293
|
+
self._aux_counter += 1
|
|
294
|
+
aux_var = z3.Bool(f'__tseytin_aux_{self._aux_counter}', ctx=ctx)
|
|
295
|
+
local[name] = aux_var
|
|
296
|
+
self.destination_model.auxiliary_variables.append(aux_var)
|
|
297
|
+
|
|
298
|
+
def literal_expr(term: str) -> Any:
|
|
299
|
+
negated = term.startswith('-')
|
|
300
|
+
name = term[1:] if negated else term
|
|
301
|
+
if name in local:
|
|
302
|
+
expr = local[name]
|
|
303
|
+
else:
|
|
304
|
+
info = self.destination_model.get_variable(name)
|
|
305
|
+
if info is None:
|
|
306
|
+
raise FlamaException(f'Unsupported feature: {name}')
|
|
307
|
+
expr = info.sel
|
|
308
|
+
return z3.Not(expr) if negated else expr
|
|
309
|
+
|
|
310
|
+
for clause in clauses:
|
|
311
|
+
literals = [literal_expr(term) for term in clause]
|
|
312
|
+
formula = literals[0] if len(literals) == 1 else z3.Or(*literals)
|
|
313
|
+
self.destination_model.add_constraint(formula)
|
|
314
|
+
|
|
264
315
|
def _get_expression(self, node: Node, parent: Optional[Node]) -> z3.ExprRef:
|
|
265
316
|
if node.is_term():
|
|
266
317
|
return self._get_term_expression(node, parent)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-z3
|
|
3
|
-
Version: 2.6.0.
|
|
3
|
+
Version: 2.6.0.dev5
|
|
4
4
|
Summary: z3-plugin for the automated analysis of feature models
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/flamapy/z3_metamodel
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: flamapy-fw~=2.6.0.
|
|
11
|
-
Requires-Dist: flamapy-fm~=2.6.0.
|
|
10
|
+
Requires-Dist: flamapy-fw~=2.6.0.dev5
|
|
11
|
+
Requires-Dist: flamapy-fm~=2.6.0.dev5
|
|
12
12
|
Requires-Dist: z3-solver~=4.14.1.0
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flamapy-z3"
|
|
7
|
-
version = "2.6.0.
|
|
7
|
+
version = "2.6.0.dev5"
|
|
8
8
|
description = "z3-plugin for the automated analysis of feature models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
11
11
|
authors = [{ name = "Flamapy", email = "flamapy@us.es" }]
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"flamapy-fw~=2.6.0.
|
|
15
|
-
"flamapy-fm~=2.6.0.
|
|
14
|
+
"flamapy-fw~=2.6.0.dev5",
|
|
15
|
+
"flamapy-fm~=2.6.0.dev5",
|
|
16
16
|
"z3-solver~=4.14.1.0",
|
|
17
17
|
]
|
|
18
18
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""The opt-in Tseytin CNF encoding must yield the same Z3 analysis results as the
|
|
2
|
+
default direct translation, and must fall back to the direct translation for any
|
|
3
|
+
constraint that is not purely propositional over boolean features."""
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from flamapy.metamodels.fm_metamodel.transformations import UVLReader
|
|
8
|
+
from flamapy.metamodels.z3_metamodel.transformations import FmToZ3
|
|
9
|
+
from flamapy.metamodels.z3_metamodel.operations.z3_satisfiable import Z3Satisfiable
|
|
10
|
+
from flamapy.metamodels.z3_metamodel.operations.z3_configurations import Z3Configurations
|
|
11
|
+
from flamapy.metamodels.z3_metamodel.operations.z3_configurations_number import (
|
|
12
|
+
Z3ConfigurationsNumber,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Purely propositional model: every cross-tree constraint is boolean.
|
|
17
|
+
_PROP_UVL = """features
|
|
18
|
+
Root {abstract}
|
|
19
|
+
optional
|
|
20
|
+
A
|
|
21
|
+
B
|
|
22
|
+
C
|
|
23
|
+
D
|
|
24
|
+
constraints
|
|
25
|
+
(A | B) => (C <=> D)
|
|
26
|
+
A => !B
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# All-arithmetic constraints; the Tseytin path must fall back to direct translation.
|
|
30
|
+
_ARITH_MODEL = 'resources/models/uvl_models/fm03_integer_conditional_bounded.uvl'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_from_uvl_text(text, cnf_method):
|
|
34
|
+
handle, path = tempfile.mkstemp(suffix='.uvl')
|
|
35
|
+
try:
|
|
36
|
+
with os.fdopen(handle, 'w') as file:
|
|
37
|
+
file.write(text)
|
|
38
|
+
return FmToZ3(UVLReader(path).transform(), cnf_method=cnf_method).transform()
|
|
39
|
+
finally:
|
|
40
|
+
os.remove(path)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _projected(model):
|
|
44
|
+
configs = Z3Configurations().execute(model).get_result()
|
|
45
|
+
return {frozenset(k for k, v in c.elements.items() if v is True) for c in configs}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_tseytin_matches_direct_on_propositional_model() -> None:
|
|
49
|
+
direct = _build_from_uvl_text(_PROP_UVL, 'direct')
|
|
50
|
+
tseytin = _build_from_uvl_text(_PROP_UVL, 'tseytin')
|
|
51
|
+
|
|
52
|
+
assert Z3ConfigurationsNumber().execute(direct).get_result() == \
|
|
53
|
+
Z3ConfigurationsNumber().execute(tseytin).get_result()
|
|
54
|
+
assert _projected(direct) == _projected(tseytin)
|
|
55
|
+
assert tseytin.auxiliary_variables # gates were introduced
|
|
56
|
+
# Auxiliary variables must not have been registered as features.
|
|
57
|
+
assert not set(str(a) for a in tseytin.auxiliary_variables) & set(tseytin.features.keys())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_tseytin_falls_back_for_arithmetic_constraints() -> None:
|
|
61
|
+
direct = FmToZ3(UVLReader(_ARITH_MODEL).transform(), cnf_method='direct').transform()
|
|
62
|
+
tseytin = FmToZ3(UVLReader(_ARITH_MODEL).transform(), cnf_method='tseytin').transform()
|
|
63
|
+
|
|
64
|
+
# Nothing was propositional, so no auxiliary variables were created.
|
|
65
|
+
assert tseytin.auxiliary_variables == []
|
|
66
|
+
assert Z3Satisfiable().execute(direct).get_result() == \
|
|
67
|
+
Z3Satisfiable().execute(tseytin).get_result()
|
|
68
|
+
assert Z3ConfigurationsNumber().execute(direct).get_result() == \
|
|
69
|
+
Z3ConfigurationsNumber().execute(tseytin).get_result()
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
from enum import Enum
|
|
3
|
-
|
|
4
|
-
from flamapy.core.operations import Operation
|
|
5
|
-
from flamapy.metamodels.configuration_metamodel.models import Configuration
|
|
6
|
-
from flamapy.metamodels.fm_metamodel.models import Attribute
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class OptimizationGoal(Enum):
|
|
10
|
-
MAXIMIZE = 'Maximize'
|
|
11
|
-
MINIMIZE = 'Minimize'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class AttributeOptimization(Operation):
|
|
15
|
-
"""This operation returns the configurations that optimize the given attribute(s)."""
|
|
16
|
-
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def __init__(self) -> None:
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
@abstractmethod
|
|
22
|
-
def set_attributes(self, attributes: dict[Attribute, OptimizationGoal]) -> None:
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
def optimize(self) -> list[Configuration]:
|
|
27
|
-
pass
|
|
File without changes
|
|
File without changes
|
{flamapy_z3-2.6.0.dev4 → flamapy_z3-2.6.0.dev5}/flamapy/metamodels/z3_metamodel/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|