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
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)
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)
def create_manager(): manager = BDD() manager.declare('x', 'y') manager.reorder({'x': 1, 'y': 0}) manager.configure(reordering=False) return manager