flamapy-fw 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_fw-2.6.0.dev4/flamapy_fw.egg-info → flamapy_fw-2.6.0.dev5}/PKG-INFO +1 -1
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/discover.py +9 -7
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/models/ast.py +111 -7
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/__init__.py +6 -0
- flamapy_fw-2.6.0.dev5/flamapy/core/operations/attribute_optimization.py +30 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/metrics_operation.py +1 -1
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/plugins.py +23 -33
- flamapy_fw-2.6.0.dev5/flamapy/core/utils.py +17 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/models/configuration.py +15 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5/flamapy_fw.egg-info}/PKG-INFO +1 -1
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy_fw.egg-info/SOURCES.txt +1 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/pyproject.toml +1 -1
- flamapy_fw-2.6.0.dev4/flamapy/core/utils.py +0 -9
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/LICENSE +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/README.md +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/config.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/exceptions.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/models/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/models/variability_model.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/abstract_operation.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/atomic_sets.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/average_branching_factor.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/commonality.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/configurations.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/configurations_number.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/core_features.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/count_leafs.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/dead_features.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/error_detection.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/error_diagnosis.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/estimated_configurations_number.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/false_optional_features.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/filter.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/sampling.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/satisfiable.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/satisfiable_configuration.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/variability.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/abstract_transformation.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/model_to_model.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/model_to_text.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/text_to_model.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/models/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/__init__.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/configuration_basic_reader.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/configuration_basic_writer.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/configuration_json_reader.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/configuration_json_writer.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/uvls_json_reader.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/metamodels/configuration_metamodel/transformations/uvls_json_writer.py +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy_fw.egg-info/dependency_links.txt +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy_fw.egg-info/entry_points.txt +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy_fw.egg-info/requires.txt +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy_fw.egg-info/top_level.txt +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/setup.cfg +0 -0
- {flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-fw
|
|
3
|
-
Version: 2.6.0.
|
|
3
|
+
Version: 2.6.0.dev5
|
|
4
4
|
Summary: Flamapy is a Python-based AAFM framework that takes into consideration previous AAFM tool designs and enables multi-solver and multi-metamodel support for the integration of AAFM tooling on the Python ecosystem.
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -15,6 +15,7 @@ from flamapy.core.plugins import Operations, Plugin, Plugins
|
|
|
15
15
|
from flamapy.core.transformations import Transformation
|
|
16
16
|
from flamapy.core.transformations.text_to_model import TextToModel
|
|
17
17
|
from flamapy.core.transformations.model_to_model import ModelToModel
|
|
18
|
+
from flamapy.core.utils import filename_matches_extension
|
|
18
19
|
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
|
|
19
20
|
|
|
20
21
|
|
|
@@ -256,14 +257,15 @@ class DiscoverMetamodels:
|
|
|
256
257
|
|
|
257
258
|
def __transform_to_model_from_file(self, file: str) -> VariabilityModel:
|
|
258
259
|
t2m_transformations = self.get_transformations_t2m()
|
|
259
|
-
extension
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if
|
|
260
|
+
# Prefer the most specific matching extension (e.g. 'uvl.json' over 'json').
|
|
261
|
+
candidates = [
|
|
262
|
+
t2m
|
|
263
|
+
for t2m in t2m_transformations
|
|
264
|
+
if filename_matches_extension(file, t2m.get_source_extension())
|
|
265
|
+
]
|
|
266
|
+
if not candidates:
|
|
266
267
|
raise TransformationNotFound()
|
|
268
|
+
t2m = max(candidates, key=lambda t: len(t.get_source_extension()))
|
|
267
269
|
|
|
268
270
|
return t2m(file).transform()
|
|
269
271
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from typing import Any, Optional
|
|
2
2
|
from enum import Enum
|
|
3
3
|
|
|
4
|
+
from flamapy.core.exceptions import FlamaException
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class ASTOperation(Enum):
|
|
6
8
|
# logical operators
|
|
@@ -187,10 +189,31 @@ class AST:
|
|
|
187
189
|
def to_cnf(self) -> "AST":
|
|
188
190
|
return convert_into_cnf(self)
|
|
189
191
|
|
|
190
|
-
def get_clauses(self) -> list[list[Any]]:
|
|
192
|
+
def get_clauses(self, method: str = 'distributive') -> list[list[Any]]:
|
|
193
|
+
"""Return the CNF clauses of this AST.
|
|
194
|
+
|
|
195
|
+
``method='distributive'`` (default) uses the classic distributive expansion and
|
|
196
|
+
returns clauses over the formula's own variables. ``method='tseytin'`` uses the
|
|
197
|
+
Tseytin transformation; the returned clauses may contain fresh auxiliary
|
|
198
|
+
variables. Use :meth:`get_clauses_with_aux` when those auxiliary variable names
|
|
199
|
+
are needed (e.g. to register them as non-feature solver variables).
|
|
200
|
+
"""
|
|
201
|
+
if method == 'tseytin':
|
|
202
|
+
clauses, _ = tseytin_cnf(self)
|
|
203
|
+
return clauses
|
|
191
204
|
ast = self.to_cnf()
|
|
192
205
|
return get_clauses(ast)
|
|
193
206
|
|
|
207
|
+
def get_clauses_with_aux(self, method: str = 'tseytin') -> tuple[list[list[Any]], list[str]]:
|
|
208
|
+
"""Return ``(clauses, aux_names)`` for the given CNF ``method``.
|
|
209
|
+
|
|
210
|
+
For ``'tseytin'`` this exposes the auxiliary variable names introduced by the
|
|
211
|
+
encoding. For ``'distributive'`` the auxiliary list is always empty.
|
|
212
|
+
"""
|
|
213
|
+
if method == 'tseytin':
|
|
214
|
+
return tseytin_cnf(self)
|
|
215
|
+
return get_clauses(self.to_cnf()), []
|
|
216
|
+
|
|
194
217
|
def get_operators(self) -> list[ASTOperation]:
|
|
195
218
|
operators = []
|
|
196
219
|
stack = [self.root]
|
|
@@ -263,20 +286,22 @@ def simplify_formula(ast: AST) -> AST:
|
|
|
263
286
|
)
|
|
264
287
|
elif logic_op == ASTOperation.EQUIVALENCE:
|
|
265
288
|
# Replace P <=> Q with P => Q ∧ Q => P.
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
new_left = simplify_formula(
|
|
290
|
+
AST.create_binary_operation(ASTOperation.IMPLIES, left, right)
|
|
291
|
+
).root
|
|
292
|
+
new_right = simplify_formula(
|
|
268
293
|
AST.create_binary_operation(ASTOperation.IMPLIES, right, left)
|
|
269
294
|
).root
|
|
270
|
-
result = AST.create_binary_operation(ASTOperation.AND,
|
|
295
|
+
result = AST.create_binary_operation(ASTOperation.AND, new_left, new_right)
|
|
271
296
|
elif logic_op == ASTOperation.XOR:
|
|
272
297
|
# Replace P XOR Q with (P ∧ !Q) v (!P ∧ Q).
|
|
273
|
-
|
|
298
|
+
new_left = simplify_formula(
|
|
274
299
|
AST.create_binary_operation(ASTOperation.AND, left, Node(ASTOperation.NOT, right))
|
|
275
300
|
).root
|
|
276
|
-
|
|
301
|
+
new_right = simplify_formula(
|
|
277
302
|
AST.create_binary_operation(ASTOperation.AND, Node(ASTOperation.NOT, left), right)
|
|
278
303
|
).root
|
|
279
|
-
result = AST.create_binary_operation(ASTOperation.OR,
|
|
304
|
+
result = AST.create_binary_operation(ASTOperation.OR, new_left, new_right)
|
|
280
305
|
elif logic_op == ASTOperation.AND:
|
|
281
306
|
left = simplify_formula(AST(left)).root
|
|
282
307
|
right = simplify_formula(AST(right)).root
|
|
@@ -376,6 +401,85 @@ def propagate_negation(node: Node, negated: bool = False) -> AST:
|
|
|
376
401
|
return result
|
|
377
402
|
|
|
378
403
|
|
|
404
|
+
TSEYTIN_AUX_PREFIX = "__tseytin_"
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _negate_literal(literal: Any) -> Any:
|
|
408
|
+
"""Flip the sign of a CNF literal that uses the ``"-name"`` string convention."""
|
|
409
|
+
if isinstance(literal, str) and literal.startswith("-"):
|
|
410
|
+
return literal[1:]
|
|
411
|
+
return "-" + str(literal)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _tseytin_gate_clauses(op: ASTOperation, gate: Any, a: Any, b: Any) -> list[list[Any]]:
|
|
415
|
+
"""Biconditional clauses encoding ``gate <=> (a op b)`` for a binary logical ``op``.
|
|
416
|
+
|
|
417
|
+
``a`` and ``b`` are literals; ``gate`` is the fresh auxiliary literal. Encoding each
|
|
418
|
+
connective directly (rather than expanding it first) is what keeps the transformation
|
|
419
|
+
linear in the formula size.
|
|
420
|
+
"""
|
|
421
|
+
g, na, nb, ng = gate, _negate_literal(a), _negate_literal(b), _negate_literal(gate)
|
|
422
|
+
if op == ASTOperation.AND:
|
|
423
|
+
return [[ng, a], [ng, b], [g, na, nb]]
|
|
424
|
+
if op == ASTOperation.OR:
|
|
425
|
+
return [[g, na], [g, nb], [ng, a, b]]
|
|
426
|
+
if op in (ASTOperation.IMPLIES, ASTOperation.REQUIRES): # a -> b == (not a) or b
|
|
427
|
+
return [[g, a], [g, nb], [ng, na, b]]
|
|
428
|
+
if op == ASTOperation.EXCLUDES: # (not a) or (not b)
|
|
429
|
+
return [[g, a], [g, b], [ng, na, nb]]
|
|
430
|
+
if op == ASTOperation.EQUIVALENCE: # a <-> b
|
|
431
|
+
return [[ng, na, b], [ng, a, nb], [g, a, b], [g, na, nb]]
|
|
432
|
+
if op == ASTOperation.XOR: # a xor b
|
|
433
|
+
return [[ng, a, b], [ng, na, nb], [g, na, b], [g, a, nb]]
|
|
434
|
+
raise FlamaException(
|
|
435
|
+
f"Operation '{op}' is not a propositional connective supported by the Tseytin "
|
|
436
|
+
f"transformation."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def tseytin_cnf(ast: AST) -> tuple[list[list[Any]], list[str]]:
|
|
441
|
+
"""Return an equisatisfiable CNF of ``ast`` using the Tseytin transformation.
|
|
442
|
+
|
|
443
|
+
Unlike the distributive :func:`to_cnf`, this produces a CNF whose size is linear
|
|
444
|
+
in the size of the formula by introducing one fresh auxiliary variable per gate.
|
|
445
|
+
Each logical connective is encoded directly (no distributive expansion), so formulas
|
|
446
|
+
that blow up under :func:`to_cnf` (XOR chains, wide equivalences) stay compact. Gates
|
|
447
|
+
are encoded as full biconditionals (``g <=> subformula``) so that every model of the
|
|
448
|
+
original formula extends to exactly one model of the encoding; this preserves model
|
|
449
|
+
counts and configuration enumeration (once auxiliary variables are projected out).
|
|
450
|
+
|
|
451
|
+
Returns a tuple ``(clauses, aux_names)`` where ``clauses`` is a list of clauses
|
|
452
|
+
(each a list of literals in the same ``"-name"`` string convention as
|
|
453
|
+
:func:`get_clauses`) and ``aux_names`` lists every auxiliary variable name
|
|
454
|
+
introduced (each prefixed with :data:`TSEYTIN_AUX_PREFIX`) so callers can register
|
|
455
|
+
them as solver variables that are not features.
|
|
456
|
+
|
|
457
|
+
Raises :class:`FlamaException` if the formula contains a non-propositional operator
|
|
458
|
+
(arithmetic, comparison or aggregation).
|
|
459
|
+
"""
|
|
460
|
+
clauses: list[list[Any]] = []
|
|
461
|
+
aux_names: list[str] = []
|
|
462
|
+
counter = [0]
|
|
463
|
+
|
|
464
|
+
def literal_of(node: Node) -> Any:
|
|
465
|
+
if node.is_term():
|
|
466
|
+
return node.data
|
|
467
|
+
if node.data == ASTOperation.NOT:
|
|
468
|
+
# Negation of a single literal is free; no gate needed.
|
|
469
|
+
return _negate_literal(literal_of(node.left))
|
|
470
|
+
left_lit = literal_of(node.left)
|
|
471
|
+
right_lit = literal_of(node.right)
|
|
472
|
+
counter[0] += 1
|
|
473
|
+
gate = f"{TSEYTIN_AUX_PREFIX}{counter[0]}"
|
|
474
|
+
aux_names.append(gate)
|
|
475
|
+
clauses.extend(_tseytin_gate_clauses(node.data, gate, left_lit, right_lit))
|
|
476
|
+
return gate
|
|
477
|
+
|
|
478
|
+
root_literal = literal_of(ast.root)
|
|
479
|
+
clauses.append([root_literal]) # assert the whole formula is true
|
|
480
|
+
return clauses, aux_names
|
|
481
|
+
|
|
482
|
+
|
|
379
483
|
def get_clauses(ast: AST) -> list[list[Any]]:
|
|
380
484
|
"""Return the list of clauses represented by the AST root node in conjunctive normal form."""
|
|
381
485
|
node = ast.root
|
|
@@ -17,9 +17,14 @@ from .variability import Variability # pylint: disable=cyclic-import
|
|
|
17
17
|
from .sampling import Sampling # pylint: disable=cyclic-import
|
|
18
18
|
from .atomic_sets import AtomicSets # pylint: disable=cyclic-import
|
|
19
19
|
from .metrics_operation import Metrics # pylint: disable=cyclic-import
|
|
20
|
+
from .attribute_optimization import ( # pylint: disable=cyclic-import
|
|
21
|
+
AttributeOptimization,
|
|
22
|
+
OptimizationGoal,
|
|
23
|
+
)
|
|
20
24
|
|
|
21
25
|
__all__ = [
|
|
22
26
|
"AtomicSets",
|
|
27
|
+
"AttributeOptimization",
|
|
23
28
|
"AverageBranchingFactor",
|
|
24
29
|
"Commonality",
|
|
25
30
|
"Configurations",
|
|
@@ -34,6 +39,7 @@ __all__ = [
|
|
|
34
39
|
"Filter",
|
|
35
40
|
"Metrics",
|
|
36
41
|
"Operation",
|
|
42
|
+
"OptimizationGoal",
|
|
37
43
|
"Sampling",
|
|
38
44
|
"Satisfiable",
|
|
39
45
|
"SatisfiableConfiguration",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from flamapy.core.operations import Operation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OptimizationGoal(Enum):
|
|
9
|
+
MAXIMIZE = 'Maximize'
|
|
10
|
+
MINIMIZE = 'Minimize'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AttributeOptimization(Operation):
|
|
14
|
+
"""Return the configuration(s) that optimize the given numeric feature attribute(s).
|
|
15
|
+
|
|
16
|
+
Attributes are identified by name and mapped to an :class:`OptimizationGoal`. The
|
|
17
|
+
objective for each attribute is the sum of its values over the selected features.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def set_attributes(self, attributes: dict[str, OptimizationGoal]) -> None:
|
|
26
|
+
"""Set the attributes to optimize as a mapping ``{attribute_name: OptimizationGoal}``."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def optimize(self) -> list[Any]:
|
|
30
|
+
"""Return the optimal configuration(s)."""
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/metrics_operation.py
RENAMED
|
@@ -19,10 +19,10 @@ class Metrics(Operation, metaclass=ABCMeta):
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
filter: Optional[list[str]] = None
|
|
22
|
-
result: list[dict[str, Any]] = []
|
|
23
22
|
|
|
24
23
|
def __init__(self) -> None:
|
|
25
24
|
self.model: Optional[VariabilityModel] = None
|
|
25
|
+
self.result: list[dict[str, Any]] = []
|
|
26
26
|
|
|
27
27
|
@abstractmethod
|
|
28
28
|
def calculate_metamodel_metrics(self, model: VariabilityModel) -> list[dict[str, Any]]:
|
|
@@ -15,7 +15,7 @@ from flamapy.core.transformations import (
|
|
|
15
15
|
ModelToText,
|
|
16
16
|
ModelToModel,
|
|
17
17
|
)
|
|
18
|
-
from flamapy.core.utils import
|
|
18
|
+
from flamapy.core.utils import filename_matches_extension
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class Transformations(UserList[Type[Transformation]]):
|
|
@@ -30,11 +30,7 @@ class Operations(UserList[Type[Operation]]):
|
|
|
30
30
|
lambda op: name in [op.__name__, op.__base__.__name__ if op.__base__ else ""], self.data
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
operation = next(candidates, None)
|
|
35
|
-
except StopIteration:
|
|
36
|
-
raise OperationNotFound(f"Operation '{name}' not found.") from StopIteration
|
|
37
|
-
|
|
33
|
+
operation = next(candidates, None)
|
|
38
34
|
if not operation:
|
|
39
35
|
raise OperationNotFound(f"Operation '{name}' not found.")
|
|
40
36
|
return operation
|
|
@@ -51,11 +47,7 @@ class Plugin:
|
|
|
51
47
|
self, filter_transformation: Callable[..., bool]
|
|
52
48
|
) -> Type[Transformation]:
|
|
53
49
|
candidates = filter(filter_transformation, self.transformations)
|
|
54
|
-
|
|
55
|
-
transformation = next(candidates, None)
|
|
56
|
-
except StopIteration:
|
|
57
|
-
raise TransformationNotFound from StopIteration
|
|
58
|
-
|
|
50
|
+
transformation = next(candidates, None)
|
|
59
51
|
if not transformation:
|
|
60
52
|
raise TransformationNotFound
|
|
61
53
|
return transformation
|
|
@@ -74,32 +66,30 @@ class Plugin:
|
|
|
74
66
|
return operation.execute(model=src)
|
|
75
67
|
|
|
76
68
|
def use_transformation_t2m(self, src: str) -> VariabilityModel:
|
|
77
|
-
extension
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
69
|
+
# Prefer the most specific matching extension (e.g. 'uvl.json' over 'json').
|
|
70
|
+
candidates = [
|
|
71
|
+
transformation
|
|
72
|
+
for transformation in self.transformations
|
|
73
|
+
if issubclass(transformation, TextToModel)
|
|
74
|
+
and filename_matches_extension(src, transformation.get_source_extension())
|
|
75
|
+
]
|
|
76
|
+
if not candidates:
|
|
77
|
+
raise TransformationNotFound
|
|
78
|
+
transformation = max(candidates, key=lambda t: len(t.get_source_extension()))
|
|
88
79
|
result = transformation(src)
|
|
89
80
|
return result.transform()
|
|
90
81
|
|
|
91
82
|
def use_transformation_m2t(self, src: VariabilityModel, dst: str) -> str:
|
|
92
|
-
extension
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
83
|
+
# Prefer the most specific matching extension (e.g. 'uvl.json' over 'json').
|
|
84
|
+
candidates = [
|
|
85
|
+
transformation
|
|
86
|
+
for transformation in self.transformations
|
|
87
|
+
if issubclass(transformation, ModelToText)
|
|
88
|
+
and filename_matches_extension(dst, transformation.get_destination_extension())
|
|
89
|
+
]
|
|
90
|
+
if not candidates:
|
|
91
|
+
raise TransformationNotFound
|
|
92
|
+
transformation = max(candidates, key=lambda t: len(t.get_destination_extension()))
|
|
103
93
|
result = transformation(path=dst, source_model=src)
|
|
104
94
|
return result.transform()
|
|
105
95
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extract_filename_extension(filename: str) -> str:
|
|
5
|
+
return filename.rsplit(".", maxsplit=1)[-1]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def filename_matches_extension(filename: str, extension: str) -> bool:
|
|
9
|
+
"""Whether the filename ends with the given extension.
|
|
10
|
+
|
|
11
|
+
Supports compound extensions such as 'uvl.json'.
|
|
12
|
+
"""
|
|
13
|
+
return filename.endswith(f".{extension}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def file_exists(filepath: str) -> bool:
|
|
17
|
+
return os.path.isfile(filepath)
|
|
@@ -30,10 +30,25 @@ class Configuration(VariabilityModel):
|
|
|
30
30
|
def __init__(self, elements: dict[Any, Any]) -> None:
|
|
31
31
|
self.elements: dict[Any, Any] = elements
|
|
32
32
|
self.is_full: bool = False
|
|
33
|
+
# Optional decision provenance: element -> 'user' or 'propagated'. Empty by default
|
|
34
|
+
# and not part of the configuration's identity (equality/hash ignore it). Useful for
|
|
35
|
+
# interactive configurators that distinguish user choices from implied decisions.
|
|
36
|
+
self.provenance: dict[Any, str] = {}
|
|
33
37
|
|
|
34
38
|
def set_full(self, is_full: bool) -> None:
|
|
35
39
|
self.is_full = is_full
|
|
36
40
|
|
|
41
|
+
def set_provenance(self, element: Any, source: str) -> None:
|
|
42
|
+
"""Record whether an element's value came from the user or from propagation."""
|
|
43
|
+
self.provenance[element] = source
|
|
44
|
+
|
|
45
|
+
def get_provenance(self, element: Any) -> str:
|
|
46
|
+
"""Return 'user', 'propagated', or 'unknown' if not recorded."""
|
|
47
|
+
return self.provenance.get(element, 'unknown')
|
|
48
|
+
|
|
49
|
+
def is_user_decision(self, element: Any) -> bool:
|
|
50
|
+
return self.provenance.get(element) == 'user'
|
|
51
|
+
|
|
37
52
|
def get_selected_elements(self) -> list[Any]:
|
|
38
53
|
"""Get the list of selected elements in the configuration."""
|
|
39
54
|
return [e for e in self.elements if self.is_selected(e)]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flamapy-fw
|
|
3
|
-
Version: 2.6.0.
|
|
3
|
+
Version: 2.6.0.dev5
|
|
4
4
|
Summary: Flamapy is a Python-based AAFM framework that takes into consideration previous AAFM tool designs and enables multi-solver and multi-metamodel support for the integration of AAFM tooling on the Python ecosystem.
|
|
5
5
|
Author-email: Flamapy <flamapy@us.es>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -14,6 +14,7 @@ flamapy/core/models/variability_model.py
|
|
|
14
14
|
flamapy/core/operations/__init__.py
|
|
15
15
|
flamapy/core/operations/abstract_operation.py
|
|
16
16
|
flamapy/core/operations/atomic_sets.py
|
|
17
|
+
flamapy/core/operations/attribute_optimization.py
|
|
17
18
|
flamapy/core/operations/average_branching_factor.py
|
|
18
19
|
flamapy/core/operations/commonality.py
|
|
19
20
|
flamapy/core/operations/configurations.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flamapy-fw"
|
|
7
|
-
version = "2.6.0.
|
|
7
|
+
version = "2.6.0.dev5"
|
|
8
8
|
description = "Flamapy is a Python-based AAFM framework that takes into consideration previous AAFM tool designs and enables multi-solver and multi-metamodel support for the integration of AAFM tooling on the Python ecosystem."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/abstract_operation.py
RENAMED
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/average_branching_factor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/configurations_number.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/false_optional_features.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/operations/satisfiable_configuration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/model_to_model.py
RENAMED
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/model_to_text.py
RENAMED
|
File without changes
|
{flamapy_fw-2.6.0.dev4 → flamapy_fw-2.6.0.dev5}/flamapy/core/transformations/text_to_model.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
|