示例#1
0
def test_path_negation():
    levels = {'c1': 0, 'a1': 1, 'c2': 2, 'a2': 3}
    manager = BDD(levels)
    manager.configure(reordering=False)

    c1, a1, c2, a2 = map(manager.var, ['c1', 'a1', 'c2', 'a2'])
    bexpr = ((c1 & a1) | (~c1 & ~a1)) & ((c2 & a2) | (~c2 & ~a2))

    assert bexpr.low.negated
    assert not bexpr.high.negated

    assert len(list(path(bexpr, (True, False, False, False)))) == 3
    assert len(list(path(bexpr, (True, True, True, True)))) == 5

    def merge(ctx, val, acc):
        if ctx.is_leaf:
            return ctx.path_negated ^ ctx.node_val
        return None

    def evaluate(vals):
        return fold_path(merge, bexpr, vals, initial=[])

    for val in product(*(4 * [[False, True]])):
        expected = (val[0] == val[1]) and (val[2] == val[3])
        assert evaluate(val) == expected
示例#2
0
 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)
示例#3
0
class DataManager():
    def __init__(self, variables, dimension=20):
        super().__init__()
        self.variables = set(variables)
        self.dimension = dimension  # Dimension per variable

        self.bdd = BDD()  # BDD Manager (dd/CUDD/Sylvan)
        self.bdd.declare(*[
            '{}{}'.format(var, i) for var in self.variables
            for i in range(self.dimension)
        ])

        self.size = {var: 0
                     for var in self.variables}  # Current size for variables
        self.dejavu = {var: dict()
                       for var in self.variables
                       }  # Dicts of values seen before

    def const(self, value):
        return self.bdd.true if value else self.bdd.false

    def encode(self, value, variable):

        if value == None:
            return self.bdd.false

        try:
            cube = self.dejavu[variable][value]
        except KeyError:  # If the value is not seen before
            index = self.size[variable]

            # Bit-blasting integer-valued index
            d = {
                '{}{}'.format(variable, bit_index): bit_value == '1'
                for (bit_index, bit_value) in zip(
                    range(self.dimension), "{0:0{length}b}".format(
                        index, length=self.dimension))
            }

            cube = self.bdd.cube(d)
            self.dejavu[variable][value] = cube

            self.size[variable] = self.size[variable] + 1

        return cube

    def decode(self, value, variable):
        pass
示例#4
0
    def __init__(self, variables, dimension=20):
        super().__init__()
        self.variables = set(variables)
        self.dimension = dimension  # Dimension per variable

        self.bdd = BDD()  # BDD Manager (dd/CUDD/Sylvan)
        self.bdd.declare(*[
            '{}{}'.format(var, i) for var in self.variables
            for i in range(self.dimension)
        ])

        self.size = {var: 0
                     for var in self.variables}  # Current size for variables
        self.dejavu = {var: dict()
                       for var in self.variables
                       }  # Dicts of values seen before
示例#5
0
def to_bdd(aag: AAG):
    assert len(aag.outputs) == 1
    assert len(aag.latches) == 0

    gate_deps = {a & -2: {b & -2, c & -2} for a, b, c in aag.gates}
    gate_lookup = {a & -2: (a, b, c) for a, b, c in aag.gates}
    eval_order = list(toposort(gate_deps))

    assert eval_order[0] <= set(aag.inputs)

    bdd = BDD()
    bdd.declare(*(f'x{i}' for i in aag.inputs))
    gate_nodes = {i: bdd.add_expr(f'x{i}') for i in aag.inputs}
    for gate in chain(*eval_order[1:]):
        out, i1, i2 = gate_lookup[gate]
        f1 = ~gate_nodes[i1 & -2] if i1 & 1 else gate_nodes[i1 & -2]
        f2 = ~gate_nodes[i2 & -2] if i2 & 1 else gate_nodes[i2 & -2]
        gate_nodes[out] = f1 & f2

    out = aag.outputs[0]
    return ~gate_nodes[out & -2] if out & 1 else gate_nodes[out & -2]
示例#6
0
def to_bdd(circ_or_expr, output=None, manager=None, renamer=None, levels=None):
    if renamer is None:
        _count = 0

        def renamer(*_):
            nonlocal _count
            _count += 1
            return f"x{_count}"

    if not isinstance(circ_or_expr, aiger.BoolExpr):
        circ = aiger.to_aig(circ_or_expr, allow_lazy=True)
        assert len(circ.latches) == 0

        if output is None:
            assert len(circ.outputs) == 1
            output = fn.first(circ.outputs)

        expr = aiger.BoolExpr(circ)
    else:
        expr = circ_or_expr

    manager = BDD() if manager is None else manager
    input_refs_to_var = {
        ref: renamer(i, ref)
        for i, ref in enumerate(expr.inputs)
    }

    manager.declare(*input_refs_to_var.values())
    if levels is not None:
        assert set(manager.vars.keys()) <= set(levels.keys())
        levels = fn.project(levels, manager.vars.keys())
        levels = fn.walk_keys(input_refs_to_var.get, levels)

        manager.reorder(levels)
        manager.configure(reordering=False)

    def lift(obj):
        if isinstance(obj, bool):
            return manager.true if obj else manager.false
        return obj

    inputs = {i: manager.var(input_refs_to_var[i]) for i in expr.inputs}
    out = expr(inputs, lift=lift)
    return out, out.bdd, bidict(input_refs_to_var)
示例#7
0
def test_to_nx():
    levels = {'x': 0, 'y': 1, 'z': 2, 'w': 3}
    manager = BDD()
    manager.declare(*levels.keys())
    manager.reorder(levels)

    x, y, z, w = map(manager.var, "xyzw")
    bexpr = reduce(xor, [x, y, z, w])

    g = to_nx(bexpr, pydot=True)
    assert len(g.nodes) == 4 + 2
    assert len(g.edges) == 2 * 4 + 1
示例#8
0
def to_bdd(circ_or_expr, output=None, manager=None, renamer=None):
    if renamer is None:
        _count = 0

        def renamer(*_):
            nonlocal _count
            _count += 1
            return f"x{_count}"

    if isinstance(circ_or_expr, aiger.BoolExpr):
        circ, output = circ_or_expr.aig, circ_or_expr.output
    else:
        circ = circ_or_expr

    node_map = dict(circ.node_map)

    assert len(circ.latches) == 0
    if output is None:
        assert len(circ.outputs) == 1
        output = node_map[fn.first(circ.outputs)]
    else:
        output = node_map[output]  # By name instead.

    manager = BDD() if manager is None else manager

    input_refs_to_var = {
        ref: renamer(i, ref) for i, ref in enumerate(circ.inputs)
    }
    manager.declare(*input_refs_to_var.values())

    gate_nodes = {}
    for gate in cmn.eval_order(circ):
        if isinstance(gate, aiger.aig.ConstFalse):
            gate_nodes[gate] = manager.add_expr('False')
        elif isinstance(gate, aiger.aig.Inverter):
            gate_nodes[gate] = ~gate_nodes[gate.input]
        elif isinstance(gate, aiger.aig.Input):
            gate_nodes[gate] = manager.add_expr(input_refs_to_var[gate.name])
        elif isinstance(gate, aiger.aig.AndGate):
            gate_nodes[gate] = gate_nodes[gate.left] & gate_nodes[gate.right]

    return gate_nodes[output], manager, bidict(input_refs_to_var)
示例#9
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
示例#10
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)
示例#11
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
示例#12
0
def create_manager():
    manager = BDD()
    manager.declare('x', 'y')
    manager.reorder({'x': 1, 'y': 0})
    manager.configure(reordering=False)
    return manager
示例#13
0
文件: aiger.py 项目: ericskim/redax
import funcy as fn

import aiger
from aiger_analysis.bdd import from_bdd, to_bdd

try:
    from dd.cudd import BDD
except ImportError:
    try:
        from dd.autoref import BDD
    except ImportError:
        raise ImportError("Cannot import dd.cudd or dd.autoref." +
                          "Reinstall with BDD support.")

aa = None
bddmgr = BDD()

to_bdd = fn.partial(to_bdd, manager=bddmgr)
from_bdd = fn.partial(from_bdd, manager=bddmgr)


class AAG():
    """
    Wrapper around py-aiger's BoolExpr class.

    Used to support an interface that's analogous to dd.

    """
    def __init__(self, aag):
        self.aag = aag