flamapy-sdd 2.6.0.dev6__tar.gz → 2.6.0.dev12__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 (24) hide show
  1. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/PKG-INFO +3 -4
  2. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/transformations/fm_to_sdd.py +6 -13
  3. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy_sdd.egg-info/PKG-INFO +3 -4
  4. flamapy_sdd-2.6.0.dev12/flamapy_sdd.egg-info/requires.txt +10 -0
  5. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/pyproject.toml +3 -4
  6. flamapy_sdd-2.6.0.dev12/tests/test_sdd.py +108 -0
  7. flamapy_sdd-2.6.0.dev6/flamapy_sdd.egg-info/requires.txt +0 -11
  8. flamapy_sdd-2.6.0.dev6/tests/test_sdd.py +0 -82
  9. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/LICENSE +0 -0
  10. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/README.md +0 -0
  11. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/__init__.py +0 -0
  12. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/models/__init__.py +0 -0
  13. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/models/sdd_model.py +0 -0
  14. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/operations/__init__.py +0 -0
  15. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/operations/sdd_configurations_number.py +0 -0
  16. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/operations/sdd_core_features.py +0 -0
  17. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/operations/sdd_dead_features.py +0 -0
  18. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/operations/sdd_satisfiable.py +0 -0
  19. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy/metamodels/sdd_metamodel/transformations/__init__.py +0 -0
  20. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy_sdd.egg-info/SOURCES.txt +0 -0
  21. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy_sdd.egg-info/dependency_links.txt +0 -0
  22. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/flamapy_sdd.egg-info/top_level.txt +0 -0
  23. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/setup.cfg +0 -0
  24. {flamapy_sdd-2.6.0.dev6 → flamapy_sdd-2.6.0.dev12}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-sdd
3
- Version: 2.6.0.dev6
3
+ Version: 2.6.0.dev12
4
4
  Summary: flamapy-sdd is a plugin to flamapy providing Sentential Decision Diagram (SDD) knowledge compilation for exact analysis of feature models
5
5
  Author-email: Flamapy <flamapy@us.es>
6
6
  License-Expression: GPL-3.0-or-later
@@ -8,9 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/sdd_metamodel
8
8
  Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: flamapy-fw~=2.6.0.dev5
12
- Requires-Dist: flamapy-fm~=2.6.0.dev5
13
- Requires-Dist: flamapy-sat~=2.6.0.dev5
11
+ Requires-Dist: flamapy-fw~=2.6.0.dev12
12
+ Requires-Dist: flamapy-fm~=2.6.0.dev12
14
13
  Requires-Dist: PySDD~=1.0
15
14
  Provides-Extra: dev
16
15
  Requires-Dist: pytest; extra == "dev"
@@ -1,15 +1,14 @@
1
1
  from pysdd.sdd import SddManager, Vtree
2
2
 
3
3
  from flamapy.core.transformations import ModelToModel
4
- from flamapy.metamodels.fm_metamodel.models import FeatureModel
5
- from flamapy.metamodels.pysat_metamodel.transformations import FmToPysat
4
+ from flamapy.metamodels.fm_metamodel.models import FeatureModel, ClauseSet
6
5
  from flamapy.metamodels.sdd_metamodel.models import SDDModel
7
6
 
8
7
 
9
8
  class FmToSDD(ModelToModel):
10
9
  """Transform a feature model into an SDD by compiling its CNF with PySDD.
11
10
 
12
- The CNF is produced by reusing the SAT metamodel's ``FmToPysat`` transformation, then
11
+ The CNF is produced by the feature-model ``ClauseSet`` (no SAT-solver dependency), then
13
12
  conjoined clause by clause into an SDD over a balanced vtree.
14
13
  """
15
14
 
@@ -27,14 +26,8 @@ class FmToSDD(ModelToModel):
27
26
  self.destination_model = SDDModel()
28
27
 
29
28
  def transform(self) -> SDDModel:
30
- # Only pass cnf_method for a non-default encoding, so the plugin also works against
31
- # releases of flamapy-sat that predate the Tseytin cnf_method option.
32
- if self.cnf_method == 'distributive':
33
- sat_model = FmToPysat(self.source_model).transform()
34
- else:
35
- sat_model = FmToPysat(self.source_model, cnf_method=self.cnf_method).transform()
36
-
37
- clauses = [list(clause) for clause in sat_model.get_all_clauses().clauses]
29
+ clause_set = ClauseSet.from_feature_model(self.source_model, cnf_method=self.cnf_method)
30
+ clauses = [list(clause) for clause in clause_set.clauses]
38
31
  var_count = max((abs(literal) for clause in clauses for literal in clause), default=1)
39
32
 
40
33
  vtree = Vtree(var_count=var_count, vtree_type='balanced')
@@ -49,7 +42,7 @@ class FmToSDD(ModelToModel):
49
42
  model = self.destination_model
50
43
  model.manager = manager
51
44
  model.root = root
52
- model.variables = dict(sat_model.variables)
53
- model.features = dict(sat_model.features)
45
+ model.variables = dict(clause_set.variables)
46
+ model.features = dict(clause_set.features)
54
47
  model.original_model = self.source_model
55
48
  return model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-sdd
3
- Version: 2.6.0.dev6
3
+ Version: 2.6.0.dev12
4
4
  Summary: flamapy-sdd is a plugin to flamapy providing Sentential Decision Diagram (SDD) knowledge compilation for exact analysis of feature models
5
5
  Author-email: Flamapy <flamapy@us.es>
6
6
  License-Expression: GPL-3.0-or-later
@@ -8,9 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/sdd_metamodel
8
8
  Requires-Python: >=3.11
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
- Requires-Dist: flamapy-fw~=2.6.0.dev5
12
- Requires-Dist: flamapy-fm~=2.6.0.dev5
13
- Requires-Dist: flamapy-sat~=2.6.0.dev5
11
+ Requires-Dist: flamapy-fw~=2.6.0.dev12
12
+ Requires-Dist: flamapy-fm~=2.6.0.dev12
14
13
  Requires-Dist: PySDD~=1.0
15
14
  Provides-Extra: dev
16
15
  Requires-Dist: pytest; extra == "dev"
@@ -0,0 +1,10 @@
1
+ flamapy-fw~=2.6.0.dev12
2
+ flamapy-fm~=2.6.0.dev12
3
+ PySDD~=1.0
4
+
5
+ [dev]
6
+ pytest
7
+ pytest-mock
8
+ prospector
9
+ mypy
10
+ coverage
@@ -4,16 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flamapy-sdd"
7
- version = "2.6.0.dev6"
7
+ version = "2.6.0.dev12"
8
8
  description = "flamapy-sdd is a plugin to flamapy providing Sentential Decision Diagram (SDD) knowledge compilation for exact 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.dev5",
15
- "flamapy-fm~=2.6.0.dev5",
16
- "flamapy-sat~=2.6.0.dev5",
14
+ "flamapy-fw~=2.6.0.dev12",
15
+ "flamapy-fm~=2.6.0.dev12",
17
16
  "PySDD~=1.0",
18
17
  ]
19
18
 
@@ -0,0 +1,108 @@
1
+ """Tests for the SDD metamodel: exact counting, satisfiability, core/dead features.
2
+
3
+ Oracles are computed by brute-forcing the CNF from fm_metamodel's solver-agnostic ClauseSet,
4
+ so CI needs no SAT plugin — only flamapy-fm, which is already a declared dependency.
5
+ """
6
+ import os
7
+ import tempfile
8
+ from itertools import product
9
+
10
+ from flamapy.metamodels.fm_metamodel.transformations import UVLReader
11
+ from flamapy.metamodels.fm_metamodel.models import FeatureModel, ClauseSet
12
+ from flamapy.metamodels.sdd_metamodel.transformations import FmToSDD
13
+ from flamapy.metamodels.sdd_metamodel.operations import (
14
+ SDDConfigurationsNumber,
15
+ SDDSatisfiable,
16
+ SDDCoreFeatures,
17
+ SDDDeadFeatures,
18
+ )
19
+
20
+
21
+ def _valid_configs(fm: FeatureModel) -> tuple:
22
+ """(valid configurations as frozensets of selected names, all feature names)."""
23
+ clause_set = ClauseSet.from_feature_model(fm)
24
+ name_of = {vid: name for name, vid in clause_set.variables.items()}
25
+ var_ids = sorted({abs(lit) for clause in clause_set.clauses for lit in clause}
26
+ | set(clause_set.variables.values()))
27
+ configs = set()
28
+ for bits in product((False, True), repeat=len(var_ids)):
29
+ assignment = dict(zip(var_ids, bits))
30
+ if all(any((lit > 0) == assignment[abs(lit)] for lit in clause)
31
+ for clause in clause_set.clauses):
32
+ configs.add(frozenset(name_of[v] for v in var_ids if assignment[v] and v in name_of))
33
+ return configs, set(clause_set.variables)
34
+
35
+
36
+ def _exact_count(fm: FeatureModel) -> int:
37
+ return len(_valid_configs(fm)[0])
38
+
39
+
40
+ def _core_features(fm: FeatureModel) -> set:
41
+ configs, names = _valid_configs(fm)
42
+ return {name for name in names if configs and all(name in cfg for cfg in configs)}
43
+
44
+
45
+ def _dead_features(fm: FeatureModel) -> set:
46
+ configs, names = _valid_configs(fm)
47
+ return names - (set().union(*configs) if configs else set())
48
+
49
+ _UVL = """features
50
+ Root {abstract}
51
+ mandatory
52
+ Base
53
+ optional
54
+ A
55
+ B
56
+ C
57
+ constraints
58
+ A => B
59
+ A | B
60
+ """
61
+
62
+ _UNSAT_UVL = """features
63
+ Root {abstract}
64
+ mandatory
65
+ A
66
+ constraints
67
+ !A
68
+ """
69
+
70
+
71
+ def _fm(uvl):
72
+ handle, path = tempfile.mkstemp(suffix='.uvl')
73
+ try:
74
+ with os.fdopen(handle, 'w') as file:
75
+ file.write(uvl)
76
+ return UVLReader(path).transform()
77
+ finally:
78
+ os.remove(path)
79
+
80
+
81
+ def test_count_matches_sat_enumeration():
82
+ fm = _fm(_UVL)
83
+ exact = _exact_count(fm)
84
+ sdd = SDDConfigurationsNumber().execute(FmToSDD(fm).transform()).get_result()
85
+ assert sdd == exact
86
+
87
+
88
+ def test_satisfiable():
89
+ assert SDDSatisfiable().execute(FmToSDD(_fm(_UVL)).transform()).get_result() is True
90
+
91
+
92
+ def test_unsatisfiable_model():
93
+ assert SDDSatisfiable().execute(FmToSDD(_fm(_UNSAT_UVL)).transform()).get_result() is False
94
+
95
+
96
+ def test_core_features_match_sat():
97
+ fm = _fm(_UVL)
98
+ expected = _core_features(fm)
99
+ sdd = set(SDDCoreFeatures().execute(FmToSDD(fm).transform()).get_result())
100
+ assert sdd == expected
101
+ assert 'Base' in sdd and 'Root' in sdd # mandatory chain is core
102
+
103
+
104
+ def test_dead_features_match_sat():
105
+ fm = _fm(_UVL)
106
+ expected = _dead_features(fm)
107
+ sdd = set(SDDDeadFeatures().execute(FmToSDD(fm).transform()).get_result())
108
+ assert sdd == expected # no dead features in this model
@@ -1,11 +0,0 @@
1
- flamapy-fw~=2.6.0.dev5
2
- flamapy-fm~=2.6.0.dev5
3
- flamapy-sat~=2.6.0.dev5
4
- PySDD~=1.0
5
-
6
- [dev]
7
- pytest
8
- pytest-mock
9
- prospector
10
- mypy
11
- coverage
@@ -1,82 +0,0 @@
1
- """Tests for the SDD metamodel: exact counting, satisfiability, core/dead features.
2
-
3
- Oracles come from the SAT backend (a declared dependency) so CI needs no extra plugin.
4
- """
5
- import os
6
- import tempfile
7
-
8
- from flamapy.metamodels.fm_metamodel.transformations import UVLReader
9
- from flamapy.metamodels.sdd_metamodel.transformations import FmToSDD
10
- from flamapy.metamodels.sdd_metamodel.operations import (
11
- SDDConfigurationsNumber,
12
- SDDSatisfiable,
13
- SDDCoreFeatures,
14
- SDDDeadFeatures,
15
- )
16
- from flamapy.metamodels.pysat_metamodel.transformations import FmToPysat
17
- from flamapy.metamodels.pysat_metamodel.operations.pysat_configurations_number import (
18
- PySATConfigurationsNumber,
19
- )
20
- from flamapy.metamodels.pysat_metamodel.operations.pysat_core_features import PySATCoreFeatures
21
- from flamapy.metamodels.pysat_metamodel.operations.pysat_dead_features import PySATDeadFeatures
22
-
23
- _UVL = """features
24
- Root {abstract}
25
- mandatory
26
- Base
27
- optional
28
- A
29
- B
30
- C
31
- constraints
32
- A => B
33
- A | B
34
- """
35
-
36
- _UNSAT_UVL = """features
37
- Root {abstract}
38
- mandatory
39
- A
40
- constraints
41
- !A
42
- """
43
-
44
-
45
- def _fm(uvl):
46
- handle, path = tempfile.mkstemp(suffix='.uvl')
47
- try:
48
- with os.fdopen(handle, 'w') as file:
49
- file.write(uvl)
50
- return UVLReader(path).transform()
51
- finally:
52
- os.remove(path)
53
-
54
-
55
- def test_count_matches_sat_enumeration():
56
- fm = _fm(_UVL)
57
- exact = PySATConfigurationsNumber().execute(FmToPysat(fm).transform()).get_result()
58
- sdd = SDDConfigurationsNumber().execute(FmToSDD(fm).transform()).get_result()
59
- assert sdd == exact
60
-
61
-
62
- def test_satisfiable():
63
- assert SDDSatisfiable().execute(FmToSDD(_fm(_UVL)).transform()).get_result() is True
64
-
65
-
66
- def test_unsatisfiable_model():
67
- assert SDDSatisfiable().execute(FmToSDD(_fm(_UNSAT_UVL)).transform()).get_result() is False
68
-
69
-
70
- def test_core_features_match_sat():
71
- fm = _fm(_UVL)
72
- sat = set(PySATCoreFeatures().execute(FmToPysat(fm).transform()).get_result())
73
- sdd = set(SDDCoreFeatures().execute(FmToSDD(fm).transform()).get_result())
74
- assert sdd == sat
75
- assert 'Base' in sdd and 'Root' in sdd # mandatory chain is core
76
-
77
-
78
- def test_dead_features_match_sat():
79
- fm = _fm(_UVL)
80
- sat = set(PySATDeadFeatures().execute(FmToPysat(fm).transform()).get_result())
81
- sdd = set(SDDDeadFeatures().execute(FmToSDD(fm).transform()).get_result())
82
- assert sdd == sat # no dead features in this model