flamapy-dnnf 2.6.0.dev12__tar.gz → 2.9.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 (23) hide show
  1. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/PKG-INFO +3 -3
  2. flamapy_dnnf-2.9.0.dev0/flamapy/metamodels/dnnf_metamodel/__init__.py +35 -0
  3. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/models/dnnf_model.py +3 -2
  4. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/transformations/fm_to_dnnf.py +2 -2
  5. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy_dnnf.egg-info/PKG-INFO +3 -3
  6. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy_dnnf.egg-info/SOURCES.txt +1 -0
  7. flamapy_dnnf-2.9.0.dev0/flamapy_dnnf.egg-info/entry_points.txt +2 -0
  8. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy_dnnf.egg-info/requires.txt +2 -2
  9. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/pyproject.toml +6 -3
  10. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/tests/test_dnnf.py +63 -0
  11. flamapy_dnnf-2.6.0.dev12/flamapy/metamodels/dnnf_metamodel/__init__.py +0 -0
  12. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/LICENSE +0 -0
  13. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/README.md +0 -0
  14. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/models/__init__.py +0 -0
  15. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/operations/__init__.py +0 -0
  16. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/operations/d4_runner.py +0 -0
  17. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/operations/dnnf_configurations_number.py +0 -0
  18. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/operations/dnnf_satisfiable.py +0 -0
  19. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy/metamodels/dnnf_metamodel/transformations/__init__.py +0 -0
  20. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy_dnnf.egg-info/dependency_links.txt +0 -0
  21. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/flamapy_dnnf.egg-info/top_level.txt +0 -0
  22. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/setup.cfg +0 -0
  23. {flamapy_dnnf-2.6.0.dev12 → flamapy_dnnf-2.9.0.dev0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-dnnf
3
- Version: 2.6.0.dev12
3
+ Version: 2.9.0.dev0
4
4
  Summary: flamapy-dnnf is a plugin to flamapy providing exact analysis of feature models via d-DNNF knowledge compilation with the d4 compiler
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/dnnf_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.dev12
12
- Requires-Dist: flamapy-fm~=2.6.0.dev12
11
+ Requires-Dist: flamapy-fw~=2.9.0.dev0
12
+ Requires-Dist: flamapy-fm~=2.9.0.dev0
13
13
  Provides-Extra: dev
14
14
  Requires-Dist: pytest; extra == "dev"
15
15
  Requires-Dist: pytest-mock; extra == "dev"
@@ -0,0 +1,35 @@
1
+ from flamapy.core.exceptions import FlamaException
2
+ from flamapy.core.manifest import PluginManifest, OperationCapability
3
+ from flamapy.core.models.language_level import LanguageLevel, MajorLevel, MinorLevel
4
+ from flamapy.metamodels.dnnf_metamodel.models.dnnf_model import locate_d4
5
+
6
+ # d-DNNF compilation scales further than BDDs for counting. The cost is a placeholder: the
7
+ # 2026-11-06 calibration machine had no d4, so this plugin is the one without measured curves.
8
+ # It declares no ``calibration``, so the planner only picks it when no calibrated plugin can
9
+ # run the operation.
10
+ # d4 is a native binary with no PyPI distribution (see models.locate_d4): without it the
11
+ # operations fail (the transformation, which only writes the DIMACS file, still runs), so the
12
+ # manifest declares the plugin unavailable and the planner never routes to it. The check runs
13
+ # once, when the plugin is imported: set FLAMAPY_D4_PATH before that.
14
+
15
+
16
+ def _d4_available() -> bool:
17
+ try:
18
+ locate_d4()
19
+ except FlamaException:
20
+ return False
21
+ return True
22
+
23
+
24
+ _D4_AVAILABLE = _d4_available()
25
+
26
+ MANIFEST = PluginManifest(
27
+ name='dnnf',
28
+ extension='dnnf',
29
+ available=_D4_AVAILABLE,
30
+ unavailable_reason='the d4 compiler is not installed (set FLAMAPY_D4_PATH or put d4 on PATH)',
31
+ supported_level=LanguageLevel(MajorLevel.BOOLEAN, {MinorLevel.GROUP_CARDINALITY}),
32
+ operations={
33
+ 'configurations_number': OperationCapability(cost=80),
34
+ },
35
+ )
@@ -14,9 +14,10 @@ def locate_d4() -> str:
14
14
  installed separately.
15
15
  """
16
16
  candidate = os.environ.get('FLAMAPY_D4_PATH') or shutil.which('d4')
17
- if not candidate or not os.path.exists(candidate):
17
+ if not candidate or not os.path.isfile(candidate) or not os.access(candidate, os.X_OK):
18
18
  raise FlamaException(
19
- 'The d4 compiler was not found. Install d4 (https://github.com/crillab/d4) and '
19
+ 'The d4 compiler was not found (or is not executable). Install d4 '
20
+ '(https://github.com/crillab/d4) and '
20
21
  'expose it as "d4" on PATH or via the FLAMAPY_D4_PATH environment variable.'
21
22
  )
22
23
  return candidate
@@ -3,7 +3,7 @@ import subprocess
3
3
  import tempfile
4
4
 
5
5
  from flamapy.core.transformations import ModelToModel
6
- from flamapy.metamodels.fm_metamodel.models import FeatureModel, ClauseSet
6
+ from flamapy.metamodels.fm_metamodel.models import ClauseSet, DEFAULT_CNF_METHOD, FeatureModel
7
7
  from flamapy.metamodels.dnnf_metamodel.models import DDNNFModel, locate_d4
8
8
 
9
9
 
@@ -26,7 +26,7 @@ class FmToDDNNF(ModelToModel):
26
26
  def __init__(
27
27
  self,
28
28
  source_model: FeatureModel,
29
- cnf_method: str = 'distributive',
29
+ cnf_method: str = DEFAULT_CNF_METHOD,
30
30
  compile: bool = False,
31
31
  ) -> None:
32
32
  self.source_model = source_model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flamapy-dnnf
3
- Version: 2.6.0.dev12
3
+ Version: 2.9.0.dev0
4
4
  Summary: flamapy-dnnf is a plugin to flamapy providing exact analysis of feature models via d-DNNF knowledge compilation with the d4 compiler
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/dnnf_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.dev12
12
- Requires-Dist: flamapy-fm~=2.6.0.dev12
11
+ Requires-Dist: flamapy-fw~=2.9.0.dev0
12
+ Requires-Dist: flamapy-fm~=2.9.0.dev0
13
13
  Provides-Extra: dev
14
14
  Requires-Dist: pytest; extra == "dev"
15
15
  Requires-Dist: pytest-mock; extra == "dev"
@@ -14,6 +14,7 @@ flamapy/metamodels/dnnf_metamodel/transformations/fm_to_dnnf.py
14
14
  flamapy_dnnf.egg-info/PKG-INFO
15
15
  flamapy_dnnf.egg-info/SOURCES.txt
16
16
  flamapy_dnnf.egg-info/dependency_links.txt
17
+ flamapy_dnnf.egg-info/entry_points.txt
17
18
  flamapy_dnnf.egg-info/requires.txt
18
19
  flamapy_dnnf.egg-info/top_level.txt
19
20
  tests/test_dnnf.py
@@ -0,0 +1,2 @@
1
+ [flamapy.plugins]
2
+ dnnf = flamapy.metamodels.dnnf_metamodel
@@ -1,5 +1,5 @@
1
- flamapy-fw~=2.6.0.dev12
2
- flamapy-fm~=2.6.0.dev12
1
+ flamapy-fw~=2.9.0.dev0
2
+ flamapy-fm~=2.9.0.dev0
3
3
 
4
4
  [dev]
5
5
  pytest
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flamapy-dnnf"
7
- version = "2.6.0.dev12"
7
+ version = "2.9.0.dev0"
8
8
  description = "flamapy-dnnf is a plugin to flamapy providing exact analysis of feature models via d-DNNF knowledge compilation with the d4 compiler"
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.dev12",
15
- "flamapy-fm~=2.6.0.dev12",
14
+ "flamapy-fw~=2.9.0.dev0",
15
+ "flamapy-fm~=2.9.0.dev0",
16
16
  ]
17
17
 
18
18
  [project.optional-dependencies]
@@ -24,6 +24,9 @@ dev = [
24
24
  "coverage",
25
25
  ]
26
26
 
27
+ [project.entry-points."flamapy.plugins"]
28
+ dnnf = "flamapy.metamodels.dnnf_metamodel"
29
+
27
30
  [project.urls]
28
31
  Homepage = "https://github.com/flamapy/dnnf_metamodel"
29
32
 
@@ -82,3 +82,66 @@ def test_transformation_writes_dimacs():
82
82
  header = dimacs.readline()
83
83
  assert header.startswith('p cnf ')
84
84
  assert model.var_count >= 5 # Root, Base, A, B, C
85
+
86
+
87
+ _TSEYTIN_UVL = """features
88
+ Root {abstract}
89
+ optional
90
+ A
91
+ B
92
+ C
93
+ D
94
+ E
95
+ F
96
+ constraints
97
+ (A & B & C) | (D & E & F) | (B & E & F)
98
+ """
99
+
100
+ # [1..2] over A, B, C (3 + 3 selections) times D optional: 12 configurations.
101
+ _GROUP_UVL = """features
102
+ Root {abstract}
103
+ [1..2]
104
+ A
105
+ B
106
+ C
107
+ optional
108
+ D
109
+ """
110
+
111
+
112
+ def test_tseytin_adds_variables_to_the_dimacs():
113
+ """Without d4: the 'auto'/'tseytin' CNF written for d4 holds the auxiliary variables."""
114
+ fm = _fm(_TSEYTIN_UVL)
115
+ plain = FmToDDNNF(fm, cnf_method='distributive').transform()
116
+ gated = FmToDDNNF(fm, cnf_method='tseytin').transform()
117
+ assert gated.var_count > plain.var_count == 7 # Root and A-F
118
+
119
+
120
+ @requires_d4
121
+ def test_cnf_methods_agree():
122
+ fm = _fm(_TSEYTIN_UVL)
123
+ exact = _exact_count(fm)
124
+ for method in ('distributive', 'tseytin', 'auto'):
125
+ model = FmToDDNNF(fm, cnf_method=method).transform()
126
+ assert DDNNFConfigurationsNumber().execute(model).get_result() == exact, method
127
+
128
+
129
+ @requires_d4
130
+ def test_group_cardinality():
131
+ model = FmToDDNNF(_fm(_GROUP_UVL)).transform()
132
+ assert DDNNFConfigurationsNumber().execute(model).get_result() == 12
133
+
134
+
135
+ def test_availability_follows_locate_d4(monkeypatch, tmp_path):
136
+ import importlib # noqa: PLC0415
137
+ import flamapy.metamodels.dnnf_metamodel as plugin # noqa: PLC0415
138
+ monkeypatch.setenv('FLAMAPY_D4_PATH', str(tmp_path / 'missing-d4'))
139
+ monkeypatch.setattr(shutil, 'which', lambda _name: None)
140
+ assert importlib.reload(plugin).MANIFEST.available is False
141
+ fake = tmp_path / 'd4'
142
+ fake.write_text('#!/bin/sh\n')
143
+ fake.chmod(0o755)
144
+ monkeypatch.setenv('FLAMAPY_D4_PATH', str(fake))
145
+ assert importlib.reload(plugin).MANIFEST.available is True
146
+ monkeypatch.undo()
147
+ importlib.reload(plugin)