Skip to content

Commit

Permalink
New operations
Browse files Browse the repository at this point in the history
  • Loading branch information
jmhorcas committed May 26, 2024
1 parent 88dde47 commit 70fe3be
Show file tree
Hide file tree
Showing 21 changed files with 479 additions and 32 deletions.
32 changes: 15 additions & 17 deletions flamapy/metamodels/bdd_metamodel/models/bdd_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self) -> None:
@property
def bdd(self) -> _bdd.BDD | _dd_bdd.BDD:
return self._bdd

@bdd.setter
def bdd(self, new_bdd: _bdd.BDD | _dd_bdd.BDD) -> None:
self._bdd = new_bdd
Expand All @@ -51,7 +51,7 @@ def bdd(self, new_bdd: _bdd.BDD | _dd_bdd.BDD) -> None:
@property
def root(self) -> _bdd.Function | int:
return self._root

@root.setter
def root(self, new_root: _bdd.Function | int) -> None:
self._root = new_root
Expand All @@ -61,11 +61,11 @@ def root(self, new_root: _bdd.Function | int) -> None:
@property
def variables(self) -> list[Any]:
return self._variables

@classmethod
def from_logic_formula(cls, formula: str, variables: list[Any]) -> 'BDDModel':
"""Build the BDD from a logic formula, and the list of variables.
The logic formula can be a CNF formula or a propositional logic formula.
"""
bdd_model = cls()
Expand All @@ -88,24 +88,23 @@ def from_logic_formula(cls, formula: str, variables: list[Any]) -> 'BDDModel':
def level_of_var(self, var: Any) -> Optional[int]:
"""Return the level of a given variable."""
return self._bdd.var_levels.get(var, None)

def var_at_level(self, level: int) -> Optional[Any]:
"""Return the variable at the given level."""
return self._levels_variables.get(level, None)

def var(self, node: _bdd.Function | int) -> Optional[Any]:
"""Return the variable of the node.
It returns None if the node is a terminal node.
"""
if self.is_terminal_node(node):
return None
if isinstance(node, int):
level, _, _ = self._bdd.succ(node)
return self.var_at_level(level)
else:
return node.var

return node.var

def level(self, node: _bdd.Function | int) -> Optional[int]:
"""Return the level of the node.
Expand Down Expand Up @@ -142,15 +141,14 @@ def negated(self, node: _bdd.Function | int) -> bool:
"""Return whether the node is negated."""
if isinstance(node, int):
return node < 0
else:
return node.negated

return node.negated

def get_terminal_node_n0(self) -> _bdd.Function | int:
return self._bdd.false

def get_terminal_node_n1(self) -> _bdd.Function | int:
return self._bdd.true

def is_terminal_node(self, node: _bdd.Function | int) -> bool:
"""Check if the node is a terminal node."""
return self.is_terminal_n0(node) or self.is_terminal_n1(node)
Expand Down Expand Up @@ -186,10 +184,10 @@ def pretty_node_str(self, node: _bdd.Function | int) -> str:
return f'{self.var(node)} ' \
f'(id: {self.get_value(node)}) ' \
f'(level: {self.level(node)}) ' \
f'(index: {self.index(node)}) ' \
f'(index: {self.index(node)}) '

def __str__(self) -> str:
result = f'Binary Decision Diagram (BDD):\n'
result = 'Binary Decision Diagram (BDD):\n'
result += f'Instance class: {type(self._bdd)}\n'
result += f'#Nodes: {self.nof_nodes()}\n'
result += f'Root: {self.pretty_node_str(self.root)}\n'
Expand Down
10 changes: 10 additions & 0 deletions flamapy/metamodels/bdd_metamodel/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
from .bdd_satisfiable import BDDSatisfiable
from .bdd_core_features import BDDCoreFeatures
from .bdd_dead_features import BDDDeadFeatures
from .bdd_variant_features import BDDVariantFeatures
from .bdd_pure_optional_features import BDDPureOptionalFeatures
from .bdd_unique_features import BDDUniqueFeatures
from .bdd_variability import BDDVariability
from .bdd_commonality_factor import BDDCommonalityFactor
from .bdd_homogeneity import BDDHomogeneity
from .bdd_metrics import BDDMetrics


Expand All @@ -18,5 +23,10 @@
'BDDSatisfiable',
'BDDCoreFeatures',
'BDDDeadFeatures',
'BDDVariantFeatures',
'BDDPureOptionalFeatures',
'BDDUniqueFeatures',
'BDDVariability',
'BDDCommonalityFactor',
'BDDHomogeneity',
'BDDMetrics']
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import cast

from flamapy.core.models import VariabilityModel
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations.interfaces import CommonalityFactor
from flamapy.metamodels.bdd_metamodel.operations import BDDConfigurationsNumber


class BDDCommonalityFactor(CommonalityFactor):

def __init__(self) -> None:
self._result: float = 0.0
self._configuration: Configuration = Configuration(elements={})

def set_configuration(self, configuration: Configuration) -> None:
self._configuration = configuration

def execute(self, model: VariabilityModel) -> 'BDDCommonalityFactor':
bdd_model = cast(BDDModel, model)
self._result = commonality_factor(bdd_model, self._configuration)
return self

def get_result(self) -> float:
return self._result

def commonality_factor(self) -> float:
return self.get_result()


def commonality_factor(bdd_model: BDDModel, config: Configuration) -> float:
configs_number_op = BDDConfigurationsNumber()
total_configs = configs_number_op.execute(bdd_model).get_result()
configs_number_op.set_partial_configuration(config)
n_configs = configs_number_op.execute(bdd_model).get_result()
return n_configs / total_configs
35 changes: 35 additions & 0 deletions flamapy/metamodels/bdd_metamodel/operations/bdd_homogeneity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import cast

from flamapy.core.models import VariabilityModel
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations.interfaces import Homogeneity
from flamapy.metamodels.bdd_metamodel.operations import BDDCommonalityFactor


class BDDHomogeneity(Homogeneity):

def __init__(self) -> None:
self._result: float = 0.0

def execute(self, model: VariabilityModel) -> 'BDDHomogeneity':
bdd_model = cast(BDDModel, model)
self._result = homogeneity(bdd_model)
return self

def get_result(self) -> float:
return self._result

def homogeneity(self) -> float:
return self.get_result()


def homogeneity(bdd_model: BDDModel) -> float:
commonality_sum = 0.0
commonality_op = BDDCommonalityFactor()

for feature in bdd_model.variables:
config = Configuration(elements={feature: True})
commonality_op.set_configuration(config)
commonality_sum += commonality_op.execute(bdd_model).get_result()
return commonality_sum / len(bdd_model.variables)
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def set_partial_configuration(self, partial_configuration: Configuration) -> Non

def get_result(self) -> list[Configuration]:
return self._result

def get_sample(self) -> list[Configuration]:
return self.get_result()

Expand Down
56 changes: 56 additions & 0 deletions flamapy/metamodels/bdd_metamodel/operations/bdd_unique_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import Any, Optional, cast

from flamapy.core.models import VariabilityModel
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations.interfaces import UniqueFeatures


class BDDUniqueFeatures(UniqueFeatures):

def __init__(self) -> None:
self._result: list[Any] = []
self._partial_configuration: Optional[Configuration] = None

def set_partial_configuration(self, partial_configuration: Optional[Configuration]) -> None:
self._partial_configuration = partial_configuration

def execute(self, model: VariabilityModel) -> 'BDDUniqueFeatures':
bdd_model = cast(BDDModel, model)
self._result = unique_features(bdd_model, self._partial_configuration)
return self

def get_result(self) -> list[Any]:
return self._result

def unique_features(self) -> list[Any]:
return self.get_result()


def unique_features(bdd_model: BDDModel,
config: Optional[Configuration] = None) -> list[Any]:
unique_features_list = []
if config is None:
for feature in bdd_model.variables:
values = {feature: True}
u_func = bdd_model.bdd.let(values, bdd_model.root)
n_vars = len(bdd_model.variables) - len(values)
n_configs = bdd_model.bdd.count(u_func, nvars=n_vars)
if n_configs == 1:
unique_features_list.append(feature)
else:
values = dict(config.elements.items())
for feature in bdd_model.variables:
feature_selected = values.get(feature, None)
values = {feature: True}
u_func = bdd_model.bdd.let(values, bdd_model.root)
n_vars = len(bdd_model.variables) - len(values)
n_configs = bdd_model.bdd.count(u_func, nvars=n_vars)
if n_configs == 1:
unique_features_list.append(feature)

if feature_selected is None:
values.pop(feature)
else:
values[feature] = feature_selected
return unique_features_list
37 changes: 37 additions & 0 deletions flamapy/metamodels/bdd_metamodel/operations/bdd_variability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import cast

from flamapy.core.models import VariabilityModel
from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations.interfaces import Variability
from flamapy.metamodels.bdd_metamodel.operations import (
BDDConfigurationsNumber,
BDDVariantFeatures
)


class BDDVariability(Variability):

def __init__(self) -> None:
self._result: tuple[float, float] = (0.0, 0.0)

def execute(self, model: VariabilityModel) -> 'BDDVariability':
bdd_model = cast(BDDModel, model)
self._result = variability(bdd_model)
return self

def get_result(self) -> tuple[float, float]:
return self._result

def total_variability(self) -> float:
return self.get_result()[0]

def partial_variability(self) -> float:
return self.get_result()[1]


def variability(bdd_model: BDDModel) -> tuple[float, float]:
n_configs = BDDConfigurationsNumber().execute(bdd_model).get_result()
total_variability = n_configs / (2**len(bdd_model.variables) - 1)
variant_features = BDDVariantFeatures().execute(bdd_model).get_result()
partial_variability = n_configs / (2**len(variant_features) - 1)
return (total_variability, partial_variability)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import Any, Optional, cast

from flamapy.core.models import VariabilityModel
from flamapy.metamodels.configuration_metamodel.models.configuration import Configuration
from flamapy.metamodels.bdd_metamodel.models import BDDModel
from flamapy.metamodels.bdd_metamodel.operations.interfaces import VariantFeatures
from flamapy.metamodels.bdd_metamodel.operations import BDDFeatureInclusionProbability


class BDDVariantFeatures(VariantFeatures):

def __init__(self) -> None:
self._result: list[Any] = []
self._partial_configuration: Optional[Configuration] = None

def set_partial_configuration(self, partial_configuration: Optional[Configuration]) -> None:
self._partial_configuration = partial_configuration

def execute(self, model: VariabilityModel) -> 'BDDVariantFeatures':
bdd_model = cast(BDDModel, model)
self._result = variant_features(bdd_model, self._partial_configuration)
return self

def get_result(self) -> list[Any]:
return self._result

def variant_features(self) -> list[Any]:
return self.get_result()


def variant_features(bdd_model: BDDModel,
config: Optional[Configuration] = None) -> list[Any]:
fip_op = BDDFeatureInclusionProbability()
fip_op.set_partial_configuration(config)
probs = fip_op.execute(bdd_model).get_result()
return [feat for feat, prob, in probs.items() if 0.0 < prob < 1.0]
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from .product_distribution import ProductDistribution
from .feature_inclusion_probability import FeatureInclusionProbability
from .pure_optional_features import PureOptionalFeatures
from .unique_features import UniqueFeatures
from .variant_features import VariantFeatures
from .commonality_factor import CommonalityFactor
from .homogeneity import Homogeneity
from .variability import Variability


__all__ = ['ProductDistribution',
'FeatureInclusionProbability',
'PureOptionalFeatures']
'PureOptionalFeatures',
'UniqueFeatures',
'VariantFeatures',
'CommonalityFactor',
'Homogeneity',
'Variability']
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from abc import abstractmethod

from flamapy.core.operations import Operation
from flamapy.metamodels.configuration_metamodel.models import Configuration


class CommonalityFactor(Operation):
"""The commonality factor of a configuration in an SPL is the percentage of products
of the SPL including the given configuration (0 if the SPL is void).
Ref.: [Duran et al. 2017. FLAME: a formal framework for the automated analysis of software
product lines validated by automated specification testing.
(https://doi.org/10.1007/s10270-015-0503-z)]
"""

@abstractmethod
def __init__(self) -> None:
pass

@abstractmethod
def set_configuration(self, configuration: Configuration) -> None:
pass

@abstractmethod
def commonality_factor(self) -> float:
pass
Loading

0 comments on commit 70fe3be

Please sign in to comment.