diff --git a/flamapy/metamodels/pysat_metamodel/models/pysat_model.py b/flamapy/metamodels/pysat_metamodel/models/pysat_model.py index 5afada4..bb906e2 100644 --- a/flamapy/metamodels/pysat_metamodel/models/pysat_model.py +++ b/flamapy/metamodels/pysat_metamodel/models/pysat_model.py @@ -14,7 +14,7 @@ def __init__(self) -> None: self.variables: dict[str, int] = {} # feature's name -> id self.features: dict[int, str] = {} # id -> feature's name self.original_model: VariabilityModel - + def add_clause(self, clause: list[int]) -> None: self._cnf.append(clause) diff --git a/flamapy/metamodels/pysat_metamodel/models/txtcnf_model.py b/flamapy/metamodels/pysat_metamodel/models/txtcnf_model.py index 0ec2dc9..e496f05 100644 --- a/flamapy/metamodels/pysat_metamodel/models/txtcnf_model.py +++ b/flamapy/metamodels/pysat_metamodel/models/txtcnf_model.py @@ -1,5 +1,6 @@ from typing import Optional from enum import Enum, auto + from flamapy.core.exceptions import FlamaException diff --git a/flamapy/metamodels/pysat_metamodel/operations/__init__.py b/flamapy/metamodels/pysat_metamodel/operations/__init__.py index 38374f0..8198443 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/__init__.py +++ b/flamapy/metamodels/pysat_metamodel/operations/__init__.py @@ -10,6 +10,7 @@ from .pysat_false_optional_features import PySATFalseOptionalFeatures from .pysat_metrics import PySATMetrics + __all__ = [ 'PySATValid', 'PySATValidConfiguration', @@ -21,4 +22,5 @@ 'PySATCoreFeatures', 'PySATDeadFeatures', 'PySATFalseOptionalFeatures', + 'PySATMetrics', ] diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_commonality.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_commonality.py index 0127591..dda88dc 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_commonality.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_commonality.py @@ -5,32 +5,31 @@ class PySATCommonality(Commonality): + def __init__(self) -> None: - self.commonality: float = 0 + self.result: float = 0 self.configuration = Configuration({}) def set_configuration(self, configuration: Configuration) -> None: self.configuration = configuration def get_commonality(self) -> float: - return self.commonality + return self.get_result() def get_result(self) -> float: - return self.get_commonality() + return self.result def execute(self, model: VariabilityModel) -> 'PySATCommonality': - glucose3products = PySATProducts() - glucose3products.execute(model) - - products = glucose3products.get_result() + pysat_products_op = PySATProducts() + pysat_products_op.execute(model) + products = pysat_products_op.get_result() feature = list(self.configuration.elements.keys())[0] count = 0 for product in products: - count = count + \ - 1 if feature.name in product else count + count = count + 1 if feature in product else count - self.commonality = count / len(products) + self.result = count / len(products) return self diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_core_features.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_core_features.py index 6a28518..1488d0c 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_core_features.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_core_features.py @@ -10,13 +10,13 @@ class PySATCoreFeatures(CoreFeatures): def __init__(self) -> None: - self.core_features: list[list[Any]] = [] + self.core_features: list[Any] = [] self.solver = Solver(name='glucose3') - def get_core_features(self) -> list[list[Any]]: + def get_core_features(self) -> list[Any]: return self.core_features - def get_result(self) -> list[list[Any]]: + def get_result(self) -> list[Any]: return self.get_core_features() def execute(self, model: VariabilityModel) -> 'PySATCoreFeatures': diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_dead_features.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_dead_features.py index 7a0467b..5697cb4 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_dead_features.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_dead_features.py @@ -10,13 +10,13 @@ class PySATDeadFeatures(DeadFeatures): def __init__(self) -> None: - self.dead_features: list[list[Any]] = [] + self.dead_features: list[Any] = [] self.solver = Solver(name='glucose3') - def get_dead_features(self) -> list[list[Any]]: + def get_dead_features(self) -> list[Any]: return self.dead_features - def get_result(self) -> list[list[Any]]: + def get_result(self) -> list[Any]: return self.get_dead_features() def execute(self, model: VariabilityModel) -> 'PySATDeadFeatures': diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_false_optional_features.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_false_optional_features.py index f09609d..d8f5524 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_false_optional_features.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_false_optional_features.py @@ -9,6 +9,7 @@ from flamapy.core.models import VariabilityModel from flamapy.core.exceptions import FlamaException + LOGGER = logging.getLogger('PySATFalseOptionalFeatures') @@ -19,12 +20,11 @@ def __init__(self) -> None: self.solver = Solver(name='glucose3') def execute(self, model: VariabilityModel) -> 'PySATFalseOptionalFeatures': - model = cast(PySATModel, model) - - self.result = self._get_false_optional_features(model) + sat_model = cast(PySATModel, model) + self.result = self._get_false_optional_features(sat_model) return self - def get_false_optional_features(self) -> list[list[Any]]: + def get_false_optional_features(self) -> list[Any]: return self.get_result() def get_result(self) -> list[Any]: @@ -32,11 +32,12 @@ def get_result(self) -> list[Any]: def _get_false_optional_features(self, sat_model: PySATModel) -> list[Any]: try: - feature_model=cast(FeatureModel,sat_model.original_model) + feature_model = cast(FeatureModel, sat_model.original_model) except FlamaException: - LOGGER.exception("The transformation didn't attach the source model, which is required for this operation." ) - - real_optional_features = [f for f in feature_model.get_features() + LOGGER.exception("The transformation didn't attach the source model, " + "which is required for this operation.") + + real_optional_features = [f.name for f in feature_model.get_features() if not f.is_root() and not f.is_mandatory()] result = [] @@ -45,10 +46,12 @@ def _get_false_optional_features(self, sat_model: PySATModel) -> list[Any]: for feature in real_optional_features: variable = sat_model.variables.get(feature.name) - parent_variable = sat_model.variables.get(feature.get_parent().name) - assert variable is not None - satisfiable = self.solver.solve(assumptions=[parent_variable, -variable]) - if not satisfiable: - result.append(feature.name) + parent_feature = feature.get_parent() + if parent_feature is not None: + parent_variable = sat_model.variables.get(parent_feature.name) + assert variable is not None + satisfiable = self.solver.solve(assumptions=[parent_variable, -variable]) + if not satisfiable: + result.append(feature.name) self.solver.delete() return result diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_filter.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_filter.py index ee17114..b542341 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_filter.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_filter.py @@ -30,16 +30,19 @@ def execute(self, model: VariabilityModel) -> 'PySATFilter': for clause in model.get_all_clauses(): # AC es conjunto de conjuntos self.solver.add_clause(clause) # añadimos la constraint - assumptions = [ - model.variables.get(feat[0].name) if feat[1] - else -model.variables.get(feat[0].name) - for feat in self.configuration.elements.items() - ] + assumptions = [] + for feat in self.configuration.elements.items(): + variable = model.variables.get(feat[0]) + if variable is not None: + if feat[1]: + assumptions.append(variable) + else: + assumptions.append(-variable) for solution in self.solver.enum_models(assumptions=assumptions): product = [] for variable in solution: - if variable > 0: + if variable is not None and variable > 0: product.append(model.features.get(variable)) self.filter_products.append(product) self.solver.delete() diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_metrics.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_metrics.py index fd11565..a8cf613 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_metrics.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_metrics.py @@ -1,38 +1,43 @@ -from typing import List, Tuple, Union, cast, Callable, Dict, Any +from typing import Any, cast, Callable, Optional +from flamapy.core.models import VariabilityModel +from flamapy.core.exceptions import FlamaException from flamapy.core.operations.metrics_operation import Metrics -from flamapy.core.models.variability_model import VariabilityModel - from flamapy.metamodels.pysat_metamodel.models import PySATModel from flamapy.metamodels.pysat_metamodel import operations as sat_operations - def metric_method(func: Callable) -> Callable: - """Decorator to mark a method as a metric method. - It has the value of the measure, it can also have a size and a ratio. - Example: - property name: Abstract Features. - description: The description of the property - value (optional): the list of abstract features. - size (optional): the length of the list. - ratio (optional): the percentage of abstract features with regards the total number of features. - """ - if not hasattr(func, '_is_metric_method'): - setattr(func, '_is_metric_method', True) - return func + """Decorator to mark a method as a metric method. + It has the value of the measure, it can also have a size and a ratio. + Example: + property name: Abstract Features. + description: The description of the property. + value (optional): the list of abstract features. + size (optional): the length of the list. + ratio (optional): the percentage of abstract features with regards the total number + of features. + """ + if not hasattr(func, '_is_metric_method'): + setattr(func, '_is_metric_method', True) + return func + class PySATMetrics(Metrics): def __init__(self) -> None: - self.model = None - self.result: List[Dict[str, Any]] = [] - self.model_type_extension="pysat" - - def get_result(self) -> Dict[str, Any]: + super().__init__() + self.model: Optional[VariabilityModel] = None + self.result: list[dict[str, Any]] = [] + self.model_type_extension = "pysat" + self._features: dict[int, str] = {} + self._common_features: list[Any] = [] + self._dead_features: list[Any] = [] + + def get_result(self) -> list[dict[str, Any]]: return self.result - def calculate_metamodel_metrics(self,model) -> Dict[str, Any]: + def calculate_metamodel_metrics(self, model: VariabilityModel) -> list[dict[str, Any]]: self.model = cast(PySATModel, model) #Do some basic calculations to speedup the rest @@ -40,66 +45,70 @@ def calculate_metamodel_metrics(self,model) -> Dict[str, Any]: self._common_features = sat_operations.PySATCoreFeatures().execute(self.model).get_result() self._dead_features = sat_operations.PySATDeadFeatures().execute(self.model).get_result() # Get all methods that are marked with the metric_method decorator - metric_methods = [getattr(self, method_name) for method_name in dir(self) - if callable(getattr(self, method_name)) and hasattr(getattr(self, method_name), '_is_metric_method')] + metric_methods = [getattr(self, method_name) for method_name in dir(self) + if callable(getattr(self, method_name)) and + hasattr(getattr(self, method_name), '_is_metric_method')] if self.filter is not None: - metric_methods = [method for method in metric_methods if method.__name__ in self.filter] - + metric_methods = [method for method in metric_methods + if method.__name__ in self.filter] + return [method() for method in metric_methods] - + @metric_method - def valid(self) -> Dict[str, Any]: + def valid(self) -> dict[str, Any]: """A feature model is valid if it represents at least one configuration.""" + if self.model is None: + raise FlamaException('Model not initialized.') name = "Valid (not void)" _valid = sat_operations.PySATValid().execute(self.model).get_result() - result = self.construct_result( name=name, - doc=self.valid.__doc__, - result=_valid) + result = self.construct_result(name=name, doc=self.valid.__doc__, result=_valid) return result - + @metric_method - def core_features(self) -> Dict[str, Any]: + def core_features(self) -> dict[str, Any]: """Features that are part of all the configurations (aka 'common features').""" name = "Core features" _core = self._common_features - result = self.construct_result( name=name, - doc=self.core_features.__doc__, - result=_core, - size=len(_core), - ratio=self.get_ratio(_core, self._features, 2)) + result = self.construct_result(name=name, + doc=self.core_features.__doc__, + result=_core, + size=len(_core), + ratio=self.get_ratio(_core, self._features, 2)) return result @metric_method - def variant_features(self) -> Dict[str, Any]: + def variant_features(self) -> dict[str, Any]: """Features that do not appear in all the configurations.""" name = "Variant features" _variant_features = [f for f in self._features.values() - if f not in self._common_features and - f not in self._dead_features] - result = self.construct_result( name=name, - doc=self.variant_features.__doc__, - result=_variant_features, - size=len(_variant_features), - ratio=self.get_ratio(_variant_features, self._features, 2)) + if f not in self._common_features and + f not in self._dead_features] + result = self.construct_result(name=name, + doc=self.variant_features.__doc__, + result=_variant_features, + size=len(_variant_features), + ratio=self.get_ratio(_variant_features, self._features, 2)) return result @metric_method - def dead_features(self) -> Dict[str, Any]: + def dead_features(self) -> dict[str, Any]: """Features that cannot appear in any configuration.""" + if self.model is None: + raise FlamaException('Model not initialized.') name = "Dead features" _dead_features = sat_operations.PySATDeadFeatures().execute(self.model).get_result() - result = self.construct_result( name=name, - doc=self.dead_features.__doc__, - result=_dead_features, - size=len(_dead_features), - ratio=self.get_ratio(_dead_features, self._features, 2)) + result = self.construct_result(name=name, + doc=self.dead_features.__doc__, + result=_dead_features, + size=len(_dead_features), + ratio=self.get_ratio(_dead_features, self._features, 2)) return result @metric_method - def unique_features(self) -> Dict[str, Any]: + def unique_features(self) -> dict[str, Any]: """Features that appear in exactly one configuration.""" name = "Unique features" - + seen_once = set() seen_multiple = set() @@ -114,33 +123,40 @@ def unique_features(self) -> Dict[str, Any]: # Step 3: Find features that are in seen_once but not in seen_multiple _unique_features = seen_once - seen_multiple - result = self.construct_result( name=name, - doc=self.unique_features.__doc__, - result=_unique_features, - size=len(_unique_features), - ratio=self.get_ratio(_unique_features, self._features, 2)) + result = self.construct_result(name=name, + doc=self.unique_features.__doc__, + result=_unique_features, + size=len(_unique_features), + ratio=self.get_ratio(_unique_features, self._features, 2)) return result @metric_method - def false_optional_features(self) -> Dict[str, Any]: - """Features defined as optionals the selection of their parents make the feature itself selected as well.""" + def false_optional_features(self) -> dict[str, Any]: + """Features defined as optionals the selection of their parents make the feature itself + selected as well.""" + if self.model is None: + raise FlamaException('Model not initialized.') name = "False-optional features" - _false_optional_features=sat_operations.PySATFalseOptionalFeatures().execute(self.model).get_result() - result = self.construct_result( name=name, - doc=self.false_optional_features.__doc__, - result=_false_optional_features, - size=len(_false_optional_features), - ratio=self.get_ratio(_false_optional_features, self._features, 2)) + _fof = sat_operations.PySATFalseOptionalFeatures().execute(self.model).get_result() + result = self.construct_result(name=name, + doc=self.false_optional_features.__doc__, + result=_fof, + size=len(_fof), + ratio=self.get_ratio(_fof, self._features, 2)) return result @metric_method - def configurations(self) -> Tuple[str, str, Union[int, float]]: - """Number of configurations represented by the feature model. If <= is shown, the number represents an upper estimation bound.""" + def configurations(self) -> dict[str, Any]: + """Number of configurations represented by the feature model. + + If <= is shown, the number represents an upper estimation bound. + """ + if self.model is None: + raise FlamaException('Model not initialized.') name = "Configurations" _configurations = sat_operations.PySATProducts().execute(self.model).get_result() - result = self.construct_result( name=name, - doc=self.configurations.__doc__, - result=_configurations, - size=len(_configurations)) + result = self.construct_result(name=name, + doc=self.configurations.__doc__, + result=_configurations, + size=len(_configurations)) return result - \ No newline at end of file diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_products.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_products.py index 95f8f90..3a5775b 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_products.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_products.py @@ -3,6 +3,7 @@ from pysat.solvers import Solver from flamapy.core.operations import Products +from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel from flamapy.core.models import VariabilityModel @@ -10,26 +11,31 @@ class PySATProducts(Products): def __init__(self) -> None: - self.products: list[list[Any]] = [] + self.result: list[Configuration] = [] self.solver = Solver(name='glucose3') - def get_products(self) -> list[list[Any]]: - return self.products + def get_products(self) -> list[Configuration]: + return self.get_result() - def get_result(self) -> list[list[Any]]: - return self.get_products() + def get_result(self) -> list[Configuration]: + return self.result def execute(self, model: VariabilityModel) -> 'PySATProducts': - model = cast(PySATModel, model) - - for clause in model.get_all_clauses(): # AC es conjunto de conjuntos - self.solver.add_clause(clause) # añadimos la constraint - - for solutions in self.solver.enum_models(): - product = [] - for variable in solutions: - if variable > 0: - product.append(model.features.get(variable)) - self.products.append(product) - self.solver.delete() + sat_model = cast(PySATModel, model) + self.result = products(self.solver, sat_model) return self + + +def products(solver: Solver, model: PySATModel) -> list[Configuration]: + for clause in model.get_all_clauses(): + solver.add_clause(clause) + + result = [] + for solutions in solver.enum_models(): + product: dict[Any, bool] = {} + for variable in solutions: + if variable > 0: + product[model.features.get(variable)] = True + result.append(Configuration(product)) + solver.delete() + return result diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_sampling.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_sampling.py new file mode 100644 index 0000000..52e0aca --- /dev/null +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_sampling.py @@ -0,0 +1,71 @@ +from typing import Any, cast, Optional + +from pysat.solvers import Solver + +from flamapy.core.models import VariabilityModel +from flamapy.core.operations import Sampling +from flamapy.core.exceptions import FlamaException +from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration +from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel + + +class PySATSampling(Sampling): + + def __init__(self) -> None: + self.result: list[Configuration] = [] + self.sample_size: int = 0 + self.with_replacement: bool = False + self.partial_configuration: Configuration = None + self.solver = Solver(name='glucose3') + + def set_sample_size(self, sample_size: int) -> None: + if sample_size < 0: + raise FlamaException(f'Sample size {sample_size} cannot be negative.') + self.sample_size = sample_size + + def set_with_replacement(self, with_replacement: bool) -> None: + self.with_replacement = with_replacement + + def set_partial_configuration(self, partial_configuration: Configuration) -> None: + self.partial_configuration = partial_configuration + + def get_sample(self) -> list[Configuration]: + return self.get_result() + + def get_result(self) -> list[Configuration]: + return self.result + + def execute(self, model: VariabilityModel) -> 'PySATSampling': + sat_model = cast(PySATModel, model) + self.result = sample(self.solver, + sat_model, + self.sample_size, + self.with_replacement, + self.partial_configuration) + return self + + +def sample(solver: Solver, + model: PySATModel, + sample_size: int, + with_replacement: bool, # pylint: disable=unused-argument + partial_configuration: Optional[Configuration] # pylint: disable=unused-argument + ) -> list[Configuration]: + if sample_size == 0: + return [] + + for clause in model.get_all_clauses(): + solver.add_clause(clause) + + products = [] + for solutions in solver.enum_models(): + product: dict[Any, bool] = {} + for variable in solutions: + if variable > 0: + product[model.features.get(variable)] = True + products.append(Configuration(product)) + if len(products) == sample_size: + solver.delete() + return products + solver.delete() + return products diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid.py index 5d01b23..8d69195 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid.py @@ -15,16 +15,20 @@ def __init__(self) -> None: self.solver = Solver(name='glucose3') def is_valid(self) -> bool: - return self.result + return self.get_result() def get_result(self) -> bool: - return self.is_valid() + return self.result def execute(self, model: VariabilityModel) -> 'PySATValid': - model = cast(PySATModel, model) - - for clause in model.get_all_clauses(): # AC es conjunto de conjuntos - self.solver.add_clause(clause) # añadimos la constraint - self.result = self.solver.solve() - self.solver.delete() + sat_model = cast(PySATModel, model) + self.result = valid(self.solver, sat_model) return self + + +def valid(solver: Solver, model: PySATModel) -> bool: + for clause in model.get_all_clauses(): + solver.add_clause(clause) + result = solver.solve() + solver.delete() + return result diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_configuration.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_configuration.py index 25fcd1f..26777c0 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_configuration.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_configuration.py @@ -2,19 +2,17 @@ from pysat.solvers import Solver +from flamapy.core.models import VariabilityModel from flamapy.core.operations import ValidConfiguration from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration - - from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel -from flamapy.core.models import VariabilityModel class PySATValidConfiguration(ValidConfiguration): def __init__(self) -> None: self.result = False - self.configuration = Configuration({}) + self.configuration = Configuration(elements={}) self.solver = Solver(name='glucose3') def is_valid(self) -> bool: @@ -27,17 +25,17 @@ def set_configuration(self, configuration: Configuration) -> None: self.configuration = configuration def execute(self, model: VariabilityModel) -> 'PySATValidConfiguration': - model = cast(PySATModel, model) + sat_model = cast(PySATModel, model) - for clause in model.get_all_clauses(): # AC es conjunto de conjuntos + for clause in sat_model.get_all_clauses(): # AC es conjunto de conjuntos self.solver.add_clause(clause) # añadimos la constraint assumptions = [] - for feat in self.configuration.elements.items(): - if feat[1]: - assumptions.append(model.variables[feat[0].name]) - elif not feat[1]: - assumptions.append(-model.variables[feat[0].name]) + for feature, selected in self.configuration.elements.items(): + if selected: + assumptions.append(sat_model.variables[feature]) + else: + assumptions.append(-sat_model.variables[feature]) self.result = self.solver.solve(assumptions=assumptions) self.solver.delete() diff --git a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_product.py b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_product.py index 3964cd1..de4a54f 100644 --- a/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_product.py +++ b/flamapy/metamodels/pysat_metamodel/operations/pysat_valid_product.py @@ -2,18 +2,17 @@ from pysat.solvers import Solver +from flamapy.core.models import VariabilityModel from flamapy.core.operations import ValidProduct from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration - from flamapy.metamodels.pysat_metamodel.models.pysat_model import PySATModel -from flamapy.core.models import VariabilityModel class PySATValidProduct(ValidProduct): def __init__(self) -> None: self.result = False - self.configuration = Configuration({}) + self.configuration = Configuration(elements={}) self.solver = Solver(name='glucose3') def is_valid(self) -> bool: @@ -26,27 +25,22 @@ def set_configuration(self, configuration: Configuration) -> None: self.configuration = configuration def execute(self, model: VariabilityModel) -> 'PySATValidProduct': - model = cast(PySATModel, model) + sat_model = cast(PySATModel, model) + + if any(feature not in sat_model.variables.keys() + for feature in self.configuration.elements.keys()): + self.result = False + return self - for clause in model.get_all_clauses(): # AC es conjunto de conjuntos + for clause in sat_model.get_all_clauses(): # AC es conjunto de conjuntos self.solver.add_clause(clause) # añadimos la constraint assumptions = [] - - config: list[str] = [] - if self.configuration is not None: - config = [feat.name for feat in self.configuration.elements] - - for feat in config: - if feat not in model.variables.keys(): - self.result = False - return self - - for feat in model.features.values(): - if feat in config: - assumptions.append(model.variables[feat]) + for feature in sat_model.features.values(): + if feature in self.configuration.elements and self.configuration.elements[feature]: + assumptions.append(sat_model.variables[feature]) else: - assumptions.append(-model.variables[feat]) + assumptions.append(-sat_model.variables[feature]) self.result = self.solver.solve(assumptions=assumptions) self.solver.delete() diff --git a/flamapy/metamodels/pysat_metamodel/transformations/dimacs_reader.py b/flamapy/metamodels/pysat_metamodel/transformations/dimacs_reader.py index 655d7b0..bbc8951 100644 --- a/flamapy/metamodels/pysat_metamodel/transformations/dimacs_reader.py +++ b/flamapy/metamodels/pysat_metamodel/transformations/dimacs_reader.py @@ -25,16 +25,17 @@ def transform(self) -> PySATModel: features_lines.append(line) elif line.startswith('p'): problem = line - else: + elif line != '': clauses_lines.append(line) if problem is None: - raise FlamaException(f'Incorrect Dimacs format of {self.path}. No problem statement.') + raise FlamaException(f'Incorrect Dimacs format of {self.path}. ' + f'No problem statement.') problem_list = problem.split() - n_features = int(problem_list[2]) n_clauses = int(problem_list[3]) if n_clauses != len(clauses_lines): - raise FlamaException(f'Incorrect Dimacs format of {self.path}. Inconsistent number of clauses.') + raise FlamaException(f'Incorrect Dimacs format of {self.path}. ' + f'Inconsistent number of clauses.') features, variables = self._parse_features_variables(features_lines) sat_model = PySATModel() sat_model.features = features diff --git a/flamapy/metamodels/pysat_metamodel/transformations/dimacs_writer.py b/flamapy/metamodels/pysat_metamodel/transformations/dimacs_writer.py index 420327f..dd4d716 100644 --- a/flamapy/metamodels/pysat_metamodel/transformations/dimacs_writer.py +++ b/flamapy/metamodels/pysat_metamodel/transformations/dimacs_writer.py @@ -15,8 +15,9 @@ def __init__(self, path: str, source_model: PySATModel) -> None: def transform(self) -> str: dimacs_str = pysat_to_dimacs(self.source_model) - with open(self.path, 'w', encoding='utf8') as file: - file.write(dimacs_str) + if self.path is not None: + with open(self.path, 'w', encoding='utf8') as file: + file.write(dimacs_str) return dimacs_str diff --git a/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py b/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py index 8c7d9f8..2a259cc 100644 --- a/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py +++ b/flamapy/metamodels/pysat_metamodel/transformations/fm_to_pysat.py @@ -1,6 +1,6 @@ import itertools from typing import Any, List -import logging + from flamapy.core.transformations import ModelToModel from flamapy.metamodels.fm_metamodel.models.feature_model import ( FeatureModel, @@ -24,7 +24,7 @@ def __init__(self, source_model: FeatureModel) -> None: self.source_model = source_model self.counter = 1 self.destination_model = PySATModel() - self.destination_model.original_model =source_model + self.destination_model.original_model = source_model # self.r_cnf = self.destination_model.r_cnf # self.ctc_cnf = self.destination_model.ctc_cnf @@ -38,18 +38,17 @@ def add_root(self, feature: Feature) -> None: # self.r_cnf.append([self.destination_model.variables.get(feature.name)]) value = self.destination_model.get_variable(feature.name) self.destination_model.add_clause([value]) - + def _add_mandatory_relation(self, relation: Relation) -> List[List[int]]: value_parent = self.destination_model.get_variable(relation.parent.name) value_child = self.destination_model.get_variable(relation.children[0].name) clauses = [[-1 * value_parent, value_child], [-1 * value_child, value_parent]] return clauses - + def _add_optional_relation(self, relation: Relation) -> List[List[int]]: value_parent = self.destination_model.get_variable(relation.parent.name) value_children = self.destination_model.get_variable(relation.children[0].name) - - clauses =[[-1 * value_children, value_parent]] + clauses = [[-1 * value_children, value_parent]] return clauses def _add_or_relation(self, relation: Relation) -> List[List[int]]: @@ -61,7 +60,7 @@ def _add_or_relation(self, relation: Relation) -> List[List[int]]: alt_cnf = [-1 * value_parent] for child in relation.children: alt_cnf.append(self.destination_model.get_variable(child.name)) - clauses=[alt_cnf] + clauses = [alt_cnf] for child in relation.children: clauses.append([ @@ -70,9 +69,8 @@ def _add_or_relation(self, relation: Relation) -> List[List[int]]: ]) return clauses - + def _add_alternative_relation(self, relation: Relation) -> List[List[int]]: - # pylint: disable=too-many-nested-blocks # this is a 1 to 1 relatinship with multiple childs # add the first cnf child1 or child2 or ... or childN or no parent) @@ -81,7 +79,7 @@ def _add_alternative_relation(self, relation: Relation) -> List[List[int]]: alt_cnf = [-1 * value_parent] for child in relation.children: alt_cnf.append(self.destination_model.get_variable(child.name)) - clauses=[alt_cnf] + clauses = [alt_cnf] for i, _ in enumerate(relation.children): for j in range(i + 1, len(relation.children)): @@ -95,7 +93,7 @@ def _add_alternative_relation(self, relation: Relation) -> List[List[int]]: value_parent ]) return clauses - + def _add_constraint_relation(self, relation: Relation) -> List[List[int]]: value_parent = self.destination_model.get_variable(relation.parent.name) @@ -130,12 +128,13 @@ def _add_constraint_relation(self, relation: Relation) -> List[List[int]]: else: cnf.append(self.destination_model.get_variable(feat.name)) clauses.append(cnf) + return clauses - def _store_constraint_relation(self, relation: Relation, clauses:List[List[int]]) -> None: + def _store_constraint_clauses(self, clauses: List[List[int]]) -> None: for clause in clauses: self.destination_model.add_clause(clause) - - def add_relation(self, relation: Relation) -> None: # noqa: MC0001 + + def add_relation(self, relation: Relation) -> None: if relation.is_mandatory(): clauses = self._add_mandatory_relation(relation) elif relation.is_optional(): @@ -146,8 +145,7 @@ def add_relation(self, relation: Relation) -> None: # noqa: MC0001 clauses = self._add_alternative_relation(relation) else: clauses = self._add_constraint_relation(relation) - - self._store_constraint_relation(relation,clauses) + self._store_constraint_clauses(clauses) def add_constraint(self, ctc: Constraint) -> None: def get_term_variable(term: Any) -> int: diff --git a/setup.py b/setup.py index 323dfb0..027b7d1 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ python_requires='>=3.9', install_requires=[ 'flamapy~=1.6.0.dev0', - 'flamapy-fm~=1.5.0.dev0', + 'flamapy-fm~=1.6.0.dev0', 'python-sat>=0.1.7.dev6' ], extras_require={