flamapy-sat 2.6.0.dev5__tar.gz → 2.6.0.dev11__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 (60) hide show
  1. {flamapy_sat-2.6.0.dev5/flamapy_sat.egg-info → flamapy_sat-2.6.0.dev11}/PKG-INFO +3 -3
  2. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/pysat_configuration_diagnosis.py +43 -0
  3. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/pysat_conflict.py +23 -0
  4. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/pysat_diagnosis.py +24 -0
  5. flamapy_sat-2.6.0.dev11/flamapy/metamodels/pysat_diagnosis_metamodel/transformations/fm_to_diag_pysat.py +37 -0
  6. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_backbone.py +10 -0
  7. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_commonality.py +15 -0
  8. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_filter.py +15 -0
  9. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_minimum_configuration.py +13 -0
  10. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_twise_sampling.py +16 -0
  11. flamapy_sat-2.6.0.dev11/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py +37 -0
  12. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11/flamapy_sat.egg-info}/PKG-INFO +3 -3
  13. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy_sat.egg-info/requires.txt +2 -2
  14. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/pyproject.toml +3 -3
  15. flamapy_sat-2.6.0.dev5/flamapy/metamodels/pysat_diagnosis_metamodel/transformations/fm_to_diag_pysat.py +0 -73
  16. flamapy_sat-2.6.0.dev5/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py +0 -192
  17. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/LICENSE +0 -0
  18. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/README.md +0 -0
  19. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/__init__.py +0 -0
  20. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/models/__init__.py +0 -0
  21. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/models/pysat_diagnosis_model.py +0 -0
  22. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/__init__.py +0 -0
  23. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/__init__.py +0 -0
  24. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/checker.py +0 -0
  25. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/fastdiag.py +0 -0
  26. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/__init__.py +0 -0
  27. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/hsdag.py +0 -0
  28. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/labeler/__init__.py +0 -0
  29. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/labeler/fastdiag_labeler.py +0 -0
  30. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/labeler/labeler.py +0 -0
  31. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/labeler/quickxplain_labeler.py +0 -0
  32. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/hsdag/node.py +0 -0
  33. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/quickxplain.py +0 -0
  34. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/diagnosis/utils.py +0 -0
  35. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/operations/pysat_abstract_identifier.py +0 -0
  36. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_diagnosis_metamodel/transformations/__init__.py +0 -0
  37. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/__init__.py +0 -0
  38. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/models/__init__.py +0 -0
  39. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/models/pysat_model.py +0 -0
  40. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/models/txtcnf_model.py +0 -0
  41. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/__init__.py +0 -0
  42. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_attribute_optimization.py +0 -0
  43. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_configurations.py +0 -0
  44. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_configurations_number.py +0 -0
  45. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_core_features.py +0 -0
  46. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_dead_features.py +0 -0
  47. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_false_optional_features.py +0 -0
  48. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_metrics.py +0 -0
  49. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_sampling.py +0 -0
  50. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_satisfiable.py +0 -0
  51. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/operations/pysat_satisfiable_configuration.py +0 -0
  52. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/transformations/__init__.py +0 -0
  53. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/transformations/cnf_to_pysat.py +0 -0
  54. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/transformations/dimacs_reader.py +0 -0
  55. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy/metamodels/pysat_metamodel/transformations/dimacs_writer.py +0 -0
  56. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy_sat.egg-info/SOURCES.txt +0 -0
  57. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy_sat.egg-info/dependency_links.txt +0 -0
  58. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/flamapy_sat.egg-info/top_level.txt +0 -0
  59. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/setup.cfg +0 -0
  60. {flamapy_sat-2.6.0.dev5 → flamapy_sat-2.6.0.dev11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-sat
3
- Version: 2.6.0.dev5
3
+ Version: 2.6.0.dev11
4
4
  Summary: flamapy-sat is a plugin to flamapy module
5
5
  Author-email: Flamapy <flamapy@us.es>
6
6
  License-Expression: GPL-3.0-or-later
@@ -8,8 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/pysat_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
11
+ Requires-Dist: flamapy-fw~=2.6.0.dev11
12
+ Requires-Dist: flamapy-fm~=2.6.0.dev11
13
13
  Requires-Dist: python-sat~=0.1.7.dev1
14
14
  Provides-Extra: dev
15
15
  Requires-Dist: pytest; extra == "dev"
@@ -17,6 +17,7 @@ from typing import Optional, cast
17
17
 
18
18
  from flamapy.core.models import VariabilityModel
19
19
  from flamapy.core.operations import Operation
20
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
20
21
  from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
21
22
  from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
22
23
 
@@ -52,6 +53,19 @@ def _decisions_from_literals(
52
53
  class PySATConfigurationConflict(Operation):
53
54
  """Minimal subset of the user's decisions that is inconsistent with the model."""
54
55
 
56
+ facade = OperationDescriptor(
57
+ doc=(
58
+ 'Returns a minimal subset of the configuration decisions that is inconsistent with\n'
59
+ 'the feature model — the conflict explaining why the (partial) configuration cannot\n'
60
+ 'be completed. Requires the pysat_diagnosis plugin.'
61
+ ),
62
+ returns='Union[None, List[Any]]',
63
+ name='configuration_conflict', operation='PySATConfigurationConflict',
64
+ default_backend='pysat_diagnosis',
65
+ inputs=(Input('configuration_path', str, required=True, kind='configuration',
66
+ setter='set_configuration'),),
67
+ )
68
+
55
69
  def __init__(self) -> None:
56
70
  self._configuration: Optional[Configuration] = None
57
71
  self._result: list[Decision] = []
@@ -78,6 +92,19 @@ class PySATConfigurationConflict(Operation):
78
92
  class PySATConfigurationRepair(Operation):
79
93
  """Minimal subset of the user's decisions to retract to restore consistency."""
80
94
 
95
+ facade = OperationDescriptor(
96
+ doc=(
97
+ 'Returns a minimal subset of the configuration decisions to retract in order to\n'
98
+ 'restore consistency with the feature model — how to repair an over-constrained\n'
99
+ 'configuration. Requires the pysat_diagnosis plugin.'
100
+ ),
101
+ returns='Union[None, List[Any]]',
102
+ name='configuration_repair', operation='PySATConfigurationRepair',
103
+ default_backend='pysat_diagnosis',
104
+ inputs=(Input('configuration_path', str, required=True, kind='configuration',
105
+ setter='set_configuration'),),
106
+ )
107
+
81
108
  def __init__(self) -> None:
82
109
  self._configuration: Optional[Configuration] = None
83
110
  self._result: list[Decision] = []
@@ -108,6 +135,22 @@ class PySATFeatureExplanation(Operation):
108
135
  forced), and ``get_result()`` is the minimal set of decisions responsible.
109
136
  """
110
137
 
138
+ facade = OperationDescriptor(
139
+ doc=(
140
+ 'Explains why a feature is forced (selected or deselected) by the current\n'
141
+ 'configuration: returns the minimal set of decisions responsible. ``feature_name``\n'
142
+ 'is the feature to explain. Requires the pysat_diagnosis plugin.'
143
+ ),
144
+ returns='Union[None, List[Any]]',
145
+ name='feature_explanation', operation='PySATFeatureExplanation',
146
+ default_backend='pysat_diagnosis',
147
+ inputs=(
148
+ Input('configuration_path', str, required=True, kind='configuration',
149
+ setter='set_configuration'),
150
+ Input('feature_name', str, required=True, setter='set_feature'),
151
+ ),
152
+ )
153
+
111
154
  def __init__(self) -> None:
112
155
  self._configuration: Optional[Configuration] = None
113
156
  self._feature: Optional[str] = None
@@ -1,6 +1,7 @@
1
1
  from typing import Tuple
2
2
 
3
3
  from flamapy.metamodels.pysat_diagnosis_metamodel.models import DiagnosisModel
4
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
4
5
  from . import PySATAbstractIdentifier
5
6
  from .diagnosis.checker import ConsistencyChecker
6
7
  from .diagnosis.hsdag.hsdag import HSDAG
@@ -18,6 +19,28 @@ class PySATConflict(PySATAbstractIdentifier):
18
19
  - max_depth - specify the maximum depth of the HSDAG to be computed
19
20
  """
20
21
 
22
+ facade = OperationDescriptor(
23
+ doc=(
24
+ 'Returns a list of conflict sets: minimal subsets of the model constraints that\n'
25
+ 'are inconsistent with the given configuration. Requires the pysat_diagnosis\n'
26
+ 'plugin.\n'
27
+ '\n'
28
+ '``configuration_path`` accepts a configuration file path, a ``{feature:\n'
29
+ 'value}`` mapping, or a Configuration object. ``test_case_path`` is optional\n'
30
+ 'and, when given, looks for conflicts against that expected outcome instead;\n'
31
+ 'same input types.'
32
+ ),
33
+ returns='Union[None, List[str]]',
34
+ name='conflict', operation='PySATConflict', default_backend='pysat_diagnosis',
35
+ inputs=(
36
+ Input('configuration_path', str, required=True, kind='configuration',
37
+ setter='set_configuration'),
38
+ Input('test_case_path', str, default=None, kind='configuration',
39
+ setter='set_test_case'),
40
+ Input('max_conflicts', int, default=None, setter='set_max_conflicts'),
41
+ ),
42
+ )
43
+
21
44
  def __init__(self) -> None:
22
45
  super().__init__()
23
46
  self.max_conflicts = -1 # -1 means no limit
@@ -1,6 +1,7 @@
1
1
  from typing import Tuple
2
2
 
3
3
  from flamapy.metamodels.pysat_diagnosis_metamodel.models import DiagnosisModel
4
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
4
5
  from . import PySATAbstractIdentifier
5
6
  from .diagnosis.checker import ConsistencyChecker
6
7
  from .diagnosis.hsdag.hsdag import HSDAG
@@ -18,6 +19,29 @@ class PySATDiagnosis(PySATAbstractIdentifier):
18
19
  - max_depth - specify the maximum depth of the HSDAG to be computed
19
20
  """
20
21
 
22
+ facade = OperationDescriptor(
23
+ doc=(
24
+ 'Returns a list of diagnoses explaining why a configuration is not valid\n'
25
+ 'against the feature model: each diagnosis is a minimal set of constraints\n'
26
+ 'whose removal would resolve the inconsistency. Requires the pysat_diagnosis\n'
27
+ 'plugin.\n'
28
+ '\n'
29
+ '``configuration_path`` accepts a configuration file path, a ``{feature:\n'
30
+ 'value}`` mapping, or a Configuration object. ``test_case_path`` is optional\n'
31
+ 'and, when given, diagnoses against that expected outcome instead; it accepts\n'
32
+ 'the same input types.'
33
+ ),
34
+ returns='Union[None, List[str]]',
35
+ name='diagnosis', operation='PySATDiagnosis', default_backend='pysat_diagnosis',
36
+ inputs=(
37
+ Input('configuration_path', str, required=True, kind='configuration',
38
+ setter='set_configuration'),
39
+ Input('test_case_path', str, default=None, kind='configuration',
40
+ setter='set_test_case'),
41
+ Input('max_diagnoses', int, default=None, setter='set_max_diagnoses'),
42
+ ),
43
+ )
44
+
21
45
  def __init__(self) -> None:
22
46
  super().__init__()
23
47
  self.max_diagnoses = -1 # -1 means no limit
@@ -0,0 +1,37 @@
1
+ from flamapy.metamodels.fm_metamodel.models import FeatureModel, ClauseSet
2
+ from flamapy.metamodels.pysat_metamodel.transformations.fm_to_pysat import FmToPysat
3
+ from ..models.pysat_diagnosis_model import DiagnosisModel
4
+
5
+
6
+ class FmToDiagPysat(FmToPysat):
7
+ """Like FmToPysat, but into a DiagnosisModel that also keeps a clause → constraint map.
8
+
9
+ The CNF and its per-source clause grouping both come from the shared feature-model encoder
10
+ (``ClauseSet``); this transformation just wraps them into the diagnosis model.
11
+ """
12
+
13
+ @staticmethod
14
+ def get_source_extension() -> str:
15
+ return 'fm'
16
+
17
+ @staticmethod
18
+ def get_destination_extension() -> str:
19
+ return 'pysat_diagnosis'
20
+
21
+ def __init__(self, source_model: FeatureModel, cnf_method: str = 'distributive') -> None:
22
+ super().__init__(source_model, cnf_method)
23
+ self.destination_model = DiagnosisModel()
24
+
25
+ def transform(self) -> DiagnosisModel:
26
+ clause_set = ClauseSet.from_feature_model(self.source_model, cnf_method=self.cnf_method)
27
+ model = self.destination_model
28
+ for clause in clause_set.clauses:
29
+ model.add_clause(clause)
30
+ model.variables = dict(clause_set.variables)
31
+ model.features = dict(clause_set.features)
32
+ model.auxiliary_variables = set(clause_set.auxiliary_variables)
33
+ model.original_model = self.source_model
34
+ # Clause → constraint mapping (root feature / relations / cross-tree constraints).
35
+ for description, clauses in clause_set.clause_groups:
36
+ model.add_clause_to_map(description, clauses)
37
+ return model
@@ -3,11 +3,21 @@ from typing import Any, cast
3
3
  from pysat.solvers import Solver
4
4
 
5
5
  from flamapy.core.operations import Operation
6
+ from flamapy.core.operations.descriptor import OperationDescriptor
6
7
  from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
7
8
  from flamapy.core.models import VariabilityModel
8
9
 
9
10
 
10
11
  class PySATBackbone(Operation):
12
+ facade = OperationDescriptor(
13
+ doc=(
14
+ 'Returns the backbone of the feature model: the set of features that must\n'
15
+ 'always be selected (core) and those that must never be selected (dead) across\n'
16
+ "all valid configurations, grouped under 'core' and 'dead' keys."
17
+ ),
18
+ returns='Union[None, Dict[str, List[Any]]]',
19
+ name='backbone', operation='PySATBackbone', default_backend='sat'
20
+ )
11
21
 
12
22
  def __init__(self) -> None:
13
23
  self._result: dict[str, list[Any]] = {"core": [], "dead": []}
@@ -1,10 +1,25 @@
1
1
  from flamapy.core.operations import Commonality
2
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
2
3
  from flamapy.core.models import VariabilityModel
3
4
  from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
4
5
  from .pysat_configurations import PySATConfigurations
5
6
 
6
7
 
7
8
  class PySATCommonality(Commonality):
9
+ facade = OperationDescriptor(
10
+ doc=(
11
+ 'This is a measure of how often a feature appears in the products of a product\n'
12
+ "line. It's usually expressed as a percentage. A feature with 100 per cent\n"
13
+ 'commonality is a core feature, as it appears in all products.\n'
14
+ '\n'
15
+ '``configuration_path`` accepts a configuration file path, a ``{feature:\n'
16
+ 'value}`` mapping, or a Configuration object.'
17
+ ),
18
+ returns='Union[None, float]',
19
+ name='commonality', operation='PySATCommonality', default_backend='sat',
20
+ inputs=(Input('configuration_path', str, required=True, kind='configuration',
21
+ setter='set_configuration'),),
22
+ )
8
23
 
9
24
  def __init__(self) -> None:
10
25
  self.result: float = 0
@@ -4,11 +4,26 @@ from pysat.solvers import Solver
4
4
 
5
5
  from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
6
6
  from flamapy.core.operations import Filter
7
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
7
8
  from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
8
9
  from flamapy.core.models import VariabilityModel
9
10
 
10
11
 
11
12
  class PySATFilter(Filter):
13
+ facade = OperationDescriptor(
14
+ doc=(
15
+ 'This operation selects a subset of the products of a product line based on\n'
16
+ 'certain criteria. For example, you might filter the products to only include\n'
17
+ 'those that contain a certain feature.\n'
18
+ '\n'
19
+ '``configuration_path`` accepts a configuration file path, a ``{feature:\n'
20
+ 'value}`` mapping, or a Configuration object.'
21
+ ),
22
+ returns='Union[None, List[Configuration]]',
23
+ name='filter', operation='PySATFilter', default_backend='sat',
24
+ inputs=(Input('configuration_path', str, required=True, kind='configuration',
25
+ setter='set_configuration'),),
26
+ )
12
27
 
13
28
  def __init__(self) -> None:
14
29
  self.filter_products: list[list[Any]] = []
@@ -5,6 +5,7 @@ from pysat.examples.rc2 import RC2
5
5
 
6
6
  from flamapy.core.models import VariabilityModel
7
7
  from flamapy.core.operations import Operation
8
+ from flamapy.core.operations.descriptor import OperationDescriptor
8
9
  from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
9
10
  from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
10
11
 
@@ -17,6 +18,18 @@ class PySATMinimumConfiguration(Operation):
17
18
  minimum working configuration).
18
19
  """
19
20
 
21
+ facade = OperationDescriptor(
22
+ doc=(
23
+ 'Returns a valid configuration with the fewest selected features (the minimum\n'
24
+ 'working configuration).\n'
25
+ '\n'
26
+ '``backend`` selects the analysis plugin; only "sat" implements this operation.'
27
+ ),
28
+ returns='Union[None, Configuration]',
29
+ name='minimum_configuration', operation='PySATMinimumConfiguration',
30
+ default_backend='sat', selectable_backend=True,
31
+ )
32
+
20
33
  def __init__(self) -> None:
21
34
  self._result: Optional[Configuration] = None
22
35
 
@@ -5,6 +5,7 @@ from pysat.solvers import Solver
5
5
 
6
6
  from flamapy.core.models import VariabilityModel
7
7
  from flamapy.core.operations import Operation
8
+ from flamapy.core.operations.descriptor import OperationDescriptor, Input
8
9
  from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
9
10
  from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
10
11
 
@@ -18,6 +19,21 @@ class PySATTWiseSampling(Operation):
18
19
  the constraints allow.
19
20
  """
20
21
 
22
+ facade = OperationDescriptor(
23
+ doc=(
24
+ 'Returns a t-wise (combinatorial) sample: a set of valid configurations that\n'
25
+ 'covers every satisfiable combination of ``t`` feature selections (pairwise for\n'
26
+ '``t = 2``).\n'
27
+ '\n'
28
+ '``backend`` selects the analysis plugin; only "sat" implements t-wise\n'
29
+ 'sampling.'
30
+ ),
31
+ returns='Union[None, List[Configuration]]',
32
+ name='t_wise_sampling', operation='PySATTWiseSampling', default_backend='sat',
33
+ selectable_backend=True,
34
+ inputs=(Input('t', int, default=2, setter='set_t'),),
35
+ )
36
+
21
37
  def __init__(self, t: int = 2) -> None:
22
38
  self.t = t
23
39
  self.result: list[Configuration] = []
@@ -0,0 +1,37 @@
1
+ from flamapy.core.transformations import ModelToModel
2
+ from flamapy.metamodels.fm_metamodel.models import FeatureModel, ClauseSet
3
+ from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
4
+
5
+
6
+ class FmToPysat(ModelToModel):
7
+ """Transform a feature model into a python-sat ``PySATModel``.
8
+
9
+ The feature-model → CNF encoding lives in ``fm_metamodel`` (``ClauseSet.from_feature_model``);
10
+ this class just wraps the resulting clause set into the python-sat-backed ``PySATModel`` so the
11
+ SAT operations keep their existing API.
12
+ """
13
+
14
+ @staticmethod
15
+ def get_source_extension() -> str:
16
+ return 'fm'
17
+
18
+ @staticmethod
19
+ def get_destination_extension() -> str:
20
+ return 'pysat'
21
+
22
+ def __init__(self, source_model: FeatureModel, cnf_method: str = 'distributive') -> None:
23
+ self.source_model = source_model
24
+ # 'distributive' (default) or 'tseytin'; forwarded to the shared CNF encoder.
25
+ self.cnf_method = cnf_method
26
+ self.destination_model = PySATModel()
27
+ self.destination_model.original_model = source_model
28
+
29
+ def transform(self) -> PySATModel:
30
+ clause_set = ClauseSet.from_feature_model(self.source_model, cnf_method=self.cnf_method)
31
+ model = self.destination_model
32
+ for clause in clause_set.clauses:
33
+ model.add_clause(clause)
34
+ model.variables = dict(clause_set.variables)
35
+ model.features = dict(clause_set.features)
36
+ model.auxiliary_variables = set(clause_set.auxiliary_variables)
37
+ return model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-sat
3
- Version: 2.6.0.dev5
3
+ Version: 2.6.0.dev11
4
4
  Summary: flamapy-sat is a plugin to flamapy module
5
5
  Author-email: Flamapy <flamapy@us.es>
6
6
  License-Expression: GPL-3.0-or-later
@@ -8,8 +8,8 @@ Project-URL: Homepage, https://github.com/flamapy/pysat_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
11
+ Requires-Dist: flamapy-fw~=2.6.0.dev11
12
+ Requires-Dist: flamapy-fm~=2.6.0.dev11
13
13
  Requires-Dist: python-sat~=0.1.7.dev1
14
14
  Provides-Extra: dev
15
15
  Requires-Dist: pytest; extra == "dev"
@@ -1,5 +1,5 @@
1
- flamapy-fw~=2.6.0.dev5
2
- flamapy-fm~=2.6.0.dev5
1
+ flamapy-fw~=2.6.0.dev11
2
+ flamapy-fm~=2.6.0.dev11
3
3
  python-sat~=0.1.7.dev1
4
4
 
5
5
  [dev]
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flamapy-sat"
7
- version = "2.6.0.dev5"
7
+ version = "2.6.0.dev11"
8
8
  description = "flamapy-sat is a plugin to flamapy module"
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",
14
+ "flamapy-fw~=2.6.0.dev11",
15
+ "flamapy-fm~=2.6.0.dev11",
16
16
  "python-sat~=0.1.7.dev1",
17
17
  ]
18
18
 
@@ -1,73 +0,0 @@
1
- from typing import Any
2
-
3
- from flamapy.metamodels.fm_metamodel.models.feature_model import (
4
- FeatureModel,
5
- Constraint,
6
- Feature,
7
- Relation,
8
- )
9
-
10
- from flamapy.metamodels.pysat_metamodel.transformations.fm_to_pysat import FmToPysat
11
- from ..models.pysat_diagnosis_model import DiagnosisModel
12
-
13
-
14
- class FmToDiagPysat(FmToPysat):
15
- @staticmethod
16
- def get_source_extension() -> str:
17
- return 'fm'
18
-
19
- @staticmethod
20
- def get_destination_extension() -> str:
21
- return 'pysat_diagnosis'
22
-
23
- def __init__(self, source_model: FeatureModel, cnf_method: str = 'distributive') -> None:
24
- super().__init__(source_model, cnf_method)
25
- self.destination_model = DiagnosisModel()
26
-
27
- def add_root(self, feature: Feature) -> None:
28
- var = self.destination_model.variables.get(feature.name)
29
- if var is None:
30
- raise KeyError(f'Feature {feature.name} not found in the model')
31
-
32
- self.destination_model.add_clause([var])
33
- self.destination_model.add_clause_to_map(str(feature), [[var]])
34
-
35
- #def _store_constraint_relation(self, relation: Relation, clauses: List[List[int]]) -> None:
36
- # for clause in clauses:
37
- # self.destination_model.add_clause(clause)
38
- # self.destination_model.add_clause_to_map(str(relation), clauses)
39
- def add_relation(self, relation: Relation) -> None:
40
- if relation.is_mandatory():
41
- clauses = self._add_mandatory_relation(relation)
42
- elif relation.is_optional():
43
- clauses = self._add_optional_relation(relation)
44
- elif relation.is_or():
45
- clauses = self._add_or_relation(relation)
46
- elif relation.is_alternative():
47
- clauses = self._add_alternative_relation(relation)
48
- else:
49
- clauses = self._add_constraint_relation(relation)
50
- self._store_constraint_clauses(clauses)
51
- self.destination_model.add_clause_to_map(str(relation), clauses)
52
-
53
- def add_constraint(self, ctc: Constraint) -> None:
54
- if self.cnf_method == 'tseytin':
55
- clauses, aux_names = ctc.ast.get_clauses_with_aux(method='tseytin')
56
- aux_map = self._allocate_auxiliary(aux_names)
57
- else:
58
- clauses = ctc.ast.get_clauses()
59
- aux_map = {}
60
-
61
- def get_term_variable(term: Any) -> int:
62
- negated = term.startswith('-')
63
- name = term[1:] if negated else term
64
- var = aux_map[name] if name in aux_map else self.destination_model.get_variable(name)
65
- return -var if negated else var
66
-
67
- ctc_clauses = []
68
- for clause in clauses:
69
- clause_variables = list(map(get_term_variable, clause))
70
- ctc_clauses.append(clause_variables)
71
- self.destination_model.add_clause(clause_variables)
72
-
73
- self.destination_model.add_clause_to_map(str(ctc), ctc_clauses)
@@ -1,192 +0,0 @@
1
- import itertools
2
- from typing import Any, List
3
-
4
- from flamapy.core.transformations import ModelToModel
5
- from flamapy.metamodels.fm_metamodel.models.feature_model import (
6
- FeatureModel,
7
- Constraint,
8
- Feature,
9
- Relation,
10
- )
11
- from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel
12
-
13
-
14
- class FmToPysat(ModelToModel):
15
- @staticmethod
16
- def get_source_extension() -> str:
17
- return 'fm'
18
-
19
- @staticmethod
20
- def get_destination_extension() -> str:
21
- return 'pysat'
22
-
23
- def __init__(self, source_model: FeatureModel, cnf_method: str = 'distributive') -> None:
24
- self.source_model = source_model
25
- self.counter = 1
26
- # 'distributive' (default) or 'tseytin'. Tseytin keeps complex cross-tree
27
- # constraints compact at the cost of auxiliary variables; it is a framework-only
28
- # opt-in and is not exposed through the facade/CLI.
29
- self.cnf_method = cnf_method
30
- self.destination_model = PySATModel()
31
- self.destination_model.original_model = source_model
32
-
33
- def add_feature(self, feature: Feature) -> None:
34
- if feature.name not in self.destination_model.variables:
35
- self.destination_model.variables[feature.name] = self.counter
36
- self.destination_model.features[self.counter] = feature.name
37
- self.counter += 1
38
-
39
- def add_root(self, feature: Feature) -> None:
40
- value = self.destination_model.get_variable(feature.name)
41
- self.destination_model.add_clause([value])
42
-
43
- def _add_mandatory_relation(self, relation: Relation) -> List[List[int]]:
44
- value_parent = self.destination_model.get_variable(relation.parent.name)
45
- value_child = self.destination_model.get_variable(relation.children[0].name)
46
- clauses = [[-1 * value_parent, value_child], [-1 * value_child, value_parent]]
47
- return clauses
48
-
49
- def _add_optional_relation(self, relation: Relation) -> List[List[int]]:
50
- value_parent = self.destination_model.get_variable(relation.parent.name)
51
- value_children = self.destination_model.get_variable(relation.children[0].name)
52
- clauses = [[-1 * value_children, value_parent]]
53
- return clauses
54
-
55
- def _add_or_relation(self, relation: Relation) -> List[List[int]]:
56
- # this is a 1 to n relatinship with multiple childs
57
- # add the first cnf child1 or child2 or ... or childN or no parent)
58
- # first elem of the constraint
59
- value_parent = self.destination_model.get_variable(relation.parent.name)
60
-
61
- alt_cnf = [-1 * value_parent]
62
- for child in relation.children:
63
- alt_cnf.append(self.destination_model.get_variable(child.name))
64
- clauses = [alt_cnf]
65
-
66
- for child in relation.children:
67
- clauses.append([
68
- -1 * self.destination_model.get_variable(child.name),
69
- value_parent
70
- ])
71
-
72
- return clauses
73
-
74
- def _add_alternative_relation(self, relation: Relation) -> List[List[int]]:
75
- # this is a 1 to 1 relatinship with multiple childs
76
- # add the first cnf child1 or child2 or ... or childN or no parent)
77
-
78
- value_parent = self.destination_model.get_variable(relation.parent.name)
79
- # first elem of the constraint
80
- alt_cnf = [-1 * value_parent]
81
- for child in relation.children:
82
- alt_cnf.append(self.destination_model.get_variable(child.name))
83
- clauses = [alt_cnf]
84
-
85
- for i, _ in enumerate(relation.children):
86
- for j in range(i + 1, len(relation.children)):
87
- if i != j:
88
- clauses.append([
89
- -1 * self.destination_model.get_variable(relation.children[i].name),
90
- -1 * self.destination_model.get_variable(relation.children[j].name)
91
- ])
92
- clauses.append([
93
- -1 * self.destination_model.get_variable(relation.children[i].name),
94
- value_parent
95
- ])
96
- return clauses
97
-
98
- def _add_constraint_relation(self, relation: Relation) -> List[List[int]]:
99
- value_parent = self.destination_model.get_variable(relation.parent.name)
100
-
101
- # This is a _min to _max relationship
102
- _min = relation.card_min
103
- _max = relation.card_max if relation.card_max != -1 else len(relation.children)
104
-
105
- clauses = []
106
-
107
- for val in range(len(relation.children) + 1):
108
- if val < _min or val > _max:
109
- # combinations of val elements
110
- for combination in itertools.combinations(relation.children, val):
111
- cnf = [-1 * value_parent]
112
- for feat in relation.children:
113
- if feat in combination:
114
- cnf.append(-1 * self.destination_model.get_variable(feat.name))
115
- else:
116
- cnf.append(self.destination_model.get_variable(feat.name))
117
- clauses.append(cnf)
118
-
119
- # there is a special case when coping with the upper part of the thru table
120
- # In the case of allowing 0 childs, you cannot exclude the option in that
121
- # no feature in this relation is activated
122
- for val in range(1, len(relation.children) + 1):
123
-
124
- for combination in itertools.combinations(relation.children, val):
125
- cnf = [value_parent]
126
- for feat in relation.children:
127
- if feat in combination:
128
- cnf.append(-1 * self.destination_model.get_variable(feat.name))
129
- else:
130
- cnf.append(self.destination_model.get_variable(feat.name))
131
- clauses.append(cnf)
132
- return clauses
133
-
134
- def _store_constraint_clauses(self, clauses: List[List[int]]) -> None:
135
- for clause in clauses:
136
- self.destination_model.add_clause(clause)
137
-
138
- def add_relation(self, relation: Relation) -> None:
139
- if relation.is_mandatory():
140
- clauses = self._add_mandatory_relation(relation)
141
- elif relation.is_optional():
142
- clauses = self._add_optional_relation(relation)
143
- elif relation.is_or():
144
- clauses = self._add_or_relation(relation)
145
- elif relation.is_alternative():
146
- clauses = self._add_alternative_relation(relation)
147
- else:
148
- clauses = self._add_constraint_relation(relation)
149
- self._store_constraint_clauses(clauses)
150
-
151
- def _allocate_auxiliary(self, aux_names: list[str]) -> dict[str, int]:
152
- """Assign a fresh SAT variable id to each Tseytin auxiliary name, recording it as
153
- a non-feature auxiliary variable so it is excluded from feature enumeration."""
154
- aux_map: dict[str, int] = {}
155
- for name in aux_names:
156
- var = self.counter
157
- self.counter += 1
158
- aux_map[name] = var
159
- self.destination_model.auxiliary_variables.add(var)
160
- return aux_map
161
-
162
- def add_constraint(self, ctc: Constraint) -> None:
163
- if self.cnf_method == 'tseytin':
164
- clauses, aux_names = ctc.ast.get_clauses_with_aux(method='tseytin')
165
- aux_map = self._allocate_auxiliary(aux_names)
166
- else:
167
- clauses = ctc.ast.get_clauses()
168
- aux_map = {}
169
-
170
- def get_term_variable(term: Any) -> int:
171
- negated = term.startswith('-')
172
- name = term[1:] if negated else term
173
- var = aux_map[name] if name in aux_map else self.destination_model.get_variable(name)
174
- return -var if negated else var
175
-
176
- for clause in clauses:
177
- clause_variables = list(map(get_term_variable, clause))
178
- self.destination_model.add_clause(clause_variables)
179
-
180
- def transform(self) -> PySATModel:
181
- for feature in self.source_model.get_features():
182
- self.add_feature(feature)
183
-
184
- self.add_root(self.source_model.root)
185
-
186
- for relation in self.source_model.get_relations():
187
- self.add_relation(relation)
188
-
189
- for constraint in self.source_model.get_logical_constraints():
190
- self.add_constraint(constraint)
191
-
192
- return self.destination_model