示例#1
0
文件: kconfig.py 项目: derf/dfatool
    def enumerate(self,
                  cudd=True,
                  export_pdf=None,
                  return_count=False,
                  return_solutions=False):
        if cudd:
            from dd.cudd import BDD
        else:
            from dd.autoref import BDD
        kconfig_file = f"{self.cwd}/{self.kconfig}"
        kconfig_hash = self.file_hash(kconfig_file)
        with cd(self.cwd):
            kconf = kconfiglib.Kconfig(kconfig_file)
        pre_variables = list()
        pre_expressions = list()
        for choice in kconf.choices:
            var_name = f"_choice_{choice.name}"
            pre_variables.append(var_name)

            # Build "exactly_one", expressing that exactly one of the symbols managed by this choice must be selected
            symbols = list(map(lambda sym: sym.name, choice.syms))
            exactly_one = list()
            for sym1 in symbols:
                subexpr = list()
                for sym2 in symbols:
                    if sym1 == sym2:
                        subexpr.append(sym2)
                    else:
                        subexpr.append(f"!{sym2}")
                exactly_one.append("(" + " & ".join(subexpr) + ")")
            exactly_one = " | ".join(exactly_one)

            # If the choice is selected, exactly one choice element must be selected
            pre_expressions.append(f"{var_name} -> ({exactly_one})")
            # Each choice symbol in exactly_once depends on the choice itself, which will lead to "{symbol} -> {var_name}" rules being generated later on. This
            # ensures that if the choice is false, each symbol is false too. We do not need to handle that case here.

            # The choice may depend on other variables
            depends_on = self._dependencies_to_bdd_expr(choice.direct_dep)

            if depends_on:
                if choice.is_optional:
                    pre_expressions.append(f"{var_name} -> {depends_on}")
                else:
                    pre_expressions.append(f"{var_name} <-> {depends_on}")
            elif not choice.is_optional:
                # Always active
                pre_expressions.append(var_name)

        for symbol in kconf.syms.values():
            if not self._can_be_handled_by_bdd(symbol):
                continue
            pre_variables.append(symbol.name)
            depends_on = self._dependencies_to_bdd_expr(symbol.direct_dep)
            if depends_on:
                pre_expressions.append(f"{symbol.name} -> {depends_on}")
            for selected_symbol, depends_on in symbol.selects:
                depends_on = self._dependencies_to_bdd_expr(depends_on)
                if depends_on:
                    pre_expressions.append(
                        f"({symbol.name} & ({depends_on})) -> {selected_symbol.name}"
                    )
                else:
                    pre_expressions.append(
                        f"{symbol.name} -> {selected_symbol.name}")

        logger.debug("Variables:")
        logger.debug("\n".join(pre_variables))
        logger.debug("Expressions:")
        logger.debug("\n".join(pre_expressions))

        variables = list()
        expressions = list()

        bdd = BDD()
        variable_count = 0
        for variable in pre_variables:
            if variable[0] != "#":
                variables.append(variable)
                variable_count += 1
                bdd.declare(variable)
        logger.debug(f"Got {variable_count} variables")

        constraint = "True"
        expression_count = 0
        for expression in pre_expressions:
            if expression[0] != "#":
                expressions.append(expression)
                expression_count += 1
                constraint += f" & ({expression})"
        logger.debug(f"Got {expression_count} rules")
        logger.debug(constraint)

        constraint = bdd.add_expr(constraint)

        if cudd:
            # Egal?
            logger.debug("Reordering ...")
            BDD.reorder(bdd)

        else:
            # Wichtig! Lesbarkeit++ falls gedumpt wird, Performance vermutlich auch.
            logger.debug("Collecting Garbage ...")
            bdd.collect_garbage()

            # See <http://www.ecs.umass.edu/ece/labs/vlsicad/ece667/reading/somenzi99bdd.pdf> for how to read the graphical representation.
            # A solid line is followed if the origin node is 1
            # A dashed line is followed if the origin node is 0
            # A path from a top node to 1 satisfies the function iff the number of negations ("-1" annotations) is even
            if export_pdf is not None:
                logger.info(f"Dumping to {export_pdf} ...")
                bdd.dump(export_pdf)

        logger.debug("Solving ...")

        # still need to be set, otherwise autoref and cudd complain and set them anyways.
        # care_vars = list(filter(lambda x: "meta_" not in x and "_choice_" not in x, variables))

        if return_solutions:
            return bdd.pick_iter(constraint, care_vars=variables)

        if return_count:
            return len(bdd.pick_iter(constraint, care_vars=variables))

        config_file = f"{self.cwd}/.config"
        for solution in bdd.pick_iter(constraint, care_vars=variables):
            logger.debug(f"Set {solution}")
            with open(config_file, "w") as f:
                for k, v in solution.items():
                    if v:
                        print(f"CONFIG_{k}=y", file=f)
                    else:
                        print(f"# CONFIG_{k} is not set", file=f)
            with cd(self.cwd):
                kconf = kconfiglib.Kconfig(kconfig_file)
            kconf.load_config(config_file)

            int_values = list()
            int_names = list()
            for symbol in kconf.syms.values():
                if (kconfiglib.TYPE_TO_STR[symbol.type] == "int"
                        and symbol.visibility and symbol.ranges):
                    for min_val, max_val, condition in symbol.ranges:
                        if condition.tri_value:
                            int_names.append(symbol.name)
                            min_val = int(min_val.str_value, 0)
                            max_val = int(max_val.str_value, 0)
                            step_size = (max_val - min_val) // 8
                            if step_size == 0:
                                step_size = 1
                            int_values.append(
                                list(range(min_val, max_val + 1, step_size)))
                            continue

            for int_config in itertools.product(*int_values):
                for i, int_name in enumerate(int_names):
                    val = int_config[i]
                    symbol = kconf.syms[int_name]
                    logger.debug(f"Set {symbol.name} to {val}")
                    symbol.set_value(str(val))
                self._run_explore_experiment(kconf, kconfig_hash, config_file)
示例#2
0
class BDDModel:
    """
    A Binary Decision Diagram (BDD) representation of the feature model given as a CNF formula.

    It relies on the dd module: https://pypi.org/project/dd/
    """

    AND = '&'
    OR = '|'
    NOT = '!'

    def __init__(self, feature_model: FeatureModel, cnf_formula: str):
        self.feature_model = feature_model
        self.cnf = cnf_formula.replace('-', '')
        self.variables = self._extract_variables(self.cnf)
        self.bdd = BDD()  # Instantiate a manager
        self.declare_variables(self.variables)  # Declare variables
        self.expression = self.bdd.add_expr(self.cnf)

    def _extract_variables(self, cnf_formula: str) -> list[str]:
        variables = set()
        for v in cnf_formula.split():
            if BDDModel.AND not in v and BDDModel.OR not in v:
                var = v.strip().replace('(', '').replace(')', '').replace(
                    BDDModel.NOT, '')
                variables.add(var)
        return list(variables)

    def declare_variables(self, variables: list[str]):
        for v in variables:
            self.bdd.declare(v)

    def serialize(self, filepath: str, filetype: str = 'png'):
        self.bdd.dump(filename=filepath,
                      roots=[self.expression],
                      filetype=filetype)

    def get_number_of_configurations(
            self,
            selected_features: list[Feature] = None,
            deselected_features: list[Feature] = None) -> int:
        if not selected_features:
            expr = self.cnf
        else:
            expr = f' {BDDModel.AND} '.join(
                [f.name for f in selected_features])
        if deselected_features:
            expr += f' {BDDModel.AND} ' + f' {BDDModel.AND} !'.join(
                [f.name for f in deselected_features])

        expr += f' {BDDModel.AND} ' + '{x}'.format(x=self.expression)
        u = self.bdd.add_expr(expr)
        return self.bdd.count(u, nvars=len(self.variables))

    def get_configurations(
            self,
            selected_features: list[Feature] = None,
            deselected_features: list[Feature] = None
    ) -> list[FMConfiguration]:
        if not selected_features:
            expr = self.cnf
        else:
            expr = f' {BDDModel.AND} '.join(
                [f.name for f in selected_features])
        if deselected_features:
            expr += f' {BDDModel.AND} ' + f' {BDDModel.AND} '.join(
                ['!' + f.name for f in deselected_features])

        expr += f' {BDDModel.AND} ' + '{x}'.format(x=self.expression)
        u = self.bdd.add_expr(expr)
        configs = []
        for c in self.bdd.pick_iter(u, care_vars=self.variables):
            elements = {
                self.feature_model.get_feature_by_name(f): True
                for f in c.keys() if c[f]
            }
            configs.append(FMConfiguration(elements))
        return configs
示例#3
0
class BDDModel:
    """
    A Binary Decision Diagram (BDD) representation of the feature model given as a CNF formula.

    It relies on the dd module: https://pypi.org/project/dd/
    """

    AND = '&'
    OR = '|'
    NOT = '!'

    def __init__(self, cnf_formula: str):
        self.cnf = cnf_formula.replace('-', '')
        self.variables = self._extract_variables(self.cnf)
        self.bdd = BDD()  # Instantiate a manager
        self.declare_variables(self.variables)  # Declare variables
        self.expression = self.bdd.add_expr(self.cnf)

    def _extract_variables(self, cnf_formula: str) -> list[str]:
        variables = set()
        for v in cnf_formula.split():
            if BDDModel.AND not in v and BDDModel.OR not in v:
                var = v.strip().replace('(', '').replace(')', '').replace(
                    BDDModel.NOT, '')
                variables.add(var)
        return list(variables)

    def declare_variables(self, variables: list[str]):
        for v in variables:
            self.bdd.declare(v)

    def serialize(self, filepath: str, filetype: str = 'png'):
        self.bdd.dump(filename=filepath,
                      roots=[self.expression],
                      filetype=filetype)

    def get_number_of_configurations(self,
                                     features: list[Feature] = None) -> int:
        if features is None:
            return self.bdd.count(self.expression, nvars=len(self.variables))
        expr = f' {BDDModel.AND} '.join([
            f.name for f in features
        ]) + f' {BDDModel.AND} ' + '{x}'.format(x=self.expression)
        u = self.bdd.add_expr(expr)
        return self.bdd.count(u, nvars=len(self.variables))

    def get_configurations(self,
                           features: list[Feature] = None
                           ) -> list[FMConfiguration]:
        if features is None:
            expr = self.cnf
        else:
            expr = f' {BDDModel.AND} '.join([
                f.name for f in features
            ]) + f' {BDDModel.AND} ' + '{x}'.format(x=self.expression)

        u = self.bdd.add_expr(expr)
        configs = []
        for c in self.bdd.pick_iter(u, care_vars=self.variables):
            configs.append(sorted([f for f in c.keys() if c[f]]))
        return configs

    def get_uniform_random_sample(self, size: int) -> list[list[str]]:
        """This generates all configurations."""
        configs = self.get_configurations()
        if size > len(configs):
            size = len(configs)
        return random.sample(configs, size)

    def get_random_configuration(self) -> list[str]:
        """This follows the Knut algorithm, but needs to be optimized"""
        solutions = self.bdd.count(self.expression, nvars=len(self.variables))
        expr = ""
        variables = list(self.variables)
        while solutions > 1:
            feature = random.choice(variables)
            variables.remove(feature)
            possible_expr = expr + f' {BDDModel.AND} '.join(
                [feature]) + f' {BDDModel.AND} '
            formula = possible_expr + '{x}'.format(x=self.expression)
            u = self.bdd.add_expr(formula)
            solutions = self.bdd.count(u, nvars=len(self.variables))
            if solutions <= 0:
                possible_expr = expr + f' {BDDModel.AND} '.join(
                    ['!' + feature]) + f' {BDDModel.AND} '
                formula = possible_expr + '{x}'.format(x=self.expression)
                u = self.bdd.add_expr(formula)
                solutions = self.bdd.count(u, nvars=len(self.variables))
            expr = possible_expr
        config = self.bdd.pick(u)
        return sorted([f for f in config.keys() if config[f]])

    def get_sample_of_configurations(self, size: int) -> list[list[str]]:
        """
        Bad implementation, we need to: 
        The original algorithm by Knuth is specified on BDDs very efficiently, as the probabilities required for all the possible SAT solutions are computed just once with a single BDD traversal, 
        and then reused every time a solution is generated.
        """

        nof_configs = self.get_number_of_configurations()
        if size > nof_configs:
            size = nof_configs

        sample = list()
        while len(sample) < size:
            config = self.get_random_configuration()
            if config not in sample:
                sample.append(config)
        return sample