def replace_allof(ast): for expr in ast: if isinstance(expr, Flag): yield expr elif isinstance(expr, Implication): condition = expr.condition constraint = list(replace_allof(expr.constraint)) if any(isinstance(x, AllOfOperator) for x in condition): if all(x.enabled for x in condition): yield Implication(list(replace_nary(condition)), constraint) else: if any(x.enabled for x in condition): raise NotImplementedError( 'Only pure negative or pure positive implication conditions supported' ) # we need to replace !(a && b && c) -> !a || !b || !c # per de Morgan's law, then convert to CNF # (!a || !b) && (!c || !d) -> (!a && !c) || (!a && !d) || ... for cset in itertools.product( *expand_conditions(condition)): yield Implication(list(cset), list(constraint)) else: yield Implication(condition, constraint) elif isinstance(expr, NaryOperator): raise ValueError('Flat n-ary operators should be replaced already') else: raise ValueError('Unknown AST expr: %s' % expr)
def to_implication(expr): if isinstance(expr, Flag): return [Implication([], [expr], strict=True, stricter=True)] elif isinstance(expr, Implication): l = [] for x in expr.constraint: l += to_implication(x) return [ Implication(expr.condition + c.condition, c.constraint, strict=True, stricter=True) for c in l ] elif isinstance(expr, AnyOfOperator): f = expr.constraint.pop(0) if len(expr.constraint) > 0: return list( merge_and_expand_implications( merge(negate(AnyOfOperator(expr.constraint)), to_implication(f)))) else: return list(merge_and_expand_implications(to_implication(f))) elif isinstance(expr, AllOfOperator): r = [] for i in expr.constraint: r += to_implication(i) return list(merge_and_expand_implications(r)) else: raise ValueError('Invalid operator in %s' % expr)
def merge_and_expand_implications(ast): for expr in ast: if isinstance(expr, Implication): for i in expr.constraint: if isinstance(i, Implication): for j in merge_and_expand_implications([i]): yield Implication(expr.condition + j.condition, j.constraint) elif isinstance(i, AllOfOperator): for j in i.constraint: yield Implication(expr.condition, [j], strict=True) else: yield Implication(expr.condition, [i], strict=True) else: yield expr
def replace_nary(ast): for expr in ast: if isinstance(expr, Flag): yield expr elif isinstance(expr, Implication): yield Implication(expr.condition, list(replace_nary(expr.constraint))) elif isinstance(expr, AtMostOneOfOperator): # ?? ( a b c ... ) -> a? ( !b !c ... ) b? ( !c ... ) ... # -> && ( || ( ( !b !c ... ) !a ) || ( ( !c ... ) !b ) ... ) constraint = [negate(x) for x in replace_nary(expr.constraint)] result = [] while len(constraint) > 0: r = constraint.pop(0) l = AllOfOperator([x for x in constraint]) result.append(AnyOfOperator([l, r])) yield AllOfOperator(result) elif isinstance(expr, ExactlyOneOfOperator): # ^^ ( a b c ... ) -> || ( a b c ... ) ?? ( a b c ... ) constraint = list(replace_nary(expr.constraint)) m = list(replace_nary([AtMostOneOfOperator(constraint)])) yield AllOfOperator([AnyOfOperator(constraint)] + m) elif isinstance(expr, AllOfOperator): yield AllOfOperator(list(replace_nary(expr.constraint))) elif isinstance(expr, AnyOfOperator): yield AnyOfOperator(list(replace_nary(expr.constraint))) else: raise ValueError('Unknown AST expr: %s' % expr)
def flatten_implications(ast, current_implications=[]): for expr in ast: if isinstance(expr, Flag): yield Implication(current_implications, [expr]) elif isinstance(expr, Implication): for x in flatten_implications( expr.constraint, current_implications + expr.condition): yield x elif isinstance(expr, NaryOperator): raise ValueError('N-ary operators should be replaced already') else: raise ValueError('Unknown AST expr: %s' % expr)
def simplify_with_immutables(ast, immutables): if type(ast) == list: r = [] for x in ast: m = simplify_with_immutables(x, immutables) if m is True: continue if m is False: return False r.append(m) return r elif isinstance(ast, Flag): if ast.name in immutables: if ast.enabled: return immutables[ast.name] else: return not immutables[ast.name] else: return ast elif isinstance(ast, Implication): nc = [] for c in ast.condition: if c.name in immutables: if not immutables[c.name]: return True else: nc.append(c) if len(nc) <= 0: return simplify_with_immutables(AllOfOperator(ast.constraint), immutables) ncons = [] for c in ast.constraint: m = simplify_with_immutables(c, immutables) if m is True: continue if m is False: return False ncons.append(m) if len(ncons) <= 0: return True return Implication(nc, ncons) elif isinstance(ast, AllOfOperator): r = [] for x in ast.constraint: m = simplify_with_immutables(x, immutables) if m is True: continue if m is False: return False r.append(m) if len(r) <= 0: return True if len(r) == 1: return r[0] return AllOfOperator(r) elif isinstance(ast, AnyOfOperator): r = [] for x in ast.constraint: m = simplify_with_immutables(x, immutables) if m is True: return True if m is False: continue r.append(m) if len(r) <= 0: return False if len(r) == 1: return r[0] return AnyOfOperator(r) else: raise ValueError('Unknown AST expr: %s' % ast)
def selftest(): check_equal('|| ( a b )', [Implication([Flag('b').negated()], [Flag('a')])]) check_equal('?? ( a b )', [Implication([Flag('a')], [Flag('b').negated()])]) check_equal('|| ( a b c? ( d ) )', [ Implication( [Flag('b').negated(), Flag('c').negated()], [Flag('a')]), Implication( [Flag('b').negated(), Flag('d').negated()], [Flag('a')]) ]) check_equal( 'a b? ( c )', [Implication([], [Flag('a')]), Implication([Flag('b')], [Flag('c')])]) check_equal('^^ ( a b )', [ Implication([Flag('b').negated()], [Flag('a')]), Implication([Flag('a')], [Flag('b').negated()]) ])
def merge(cond, cons): if isinstance(cond, Flag): return [ Implication([cond] + c.condition, c.constraint, strict=True, stricter=True) for c in cons ] elif isinstance(cond, AnyOfOperator): r = [] for c in cond.constraint: r += merge(c, cons) return list(merge_and_expand_implications(r)) elif isinstance(cond, AllOfOperator): if len(cond.constraint) <= 0: return cons return list( merge_and_expand_implications( merge(cond.constraint[0], merge(AllOfOperator(cond.constraint[1:]), cons)))) else: raise ValueError('Invalid operator in %s' % cond)
def print_solutions(constraint_str, immutable_str): # sort n-ary expressions immutable_flags = parse_immutables(immutable_str) ast = sort_nary(validate_ast_passthrough(parse_string(constraint_str)), immutability_sort(immutable_str)) ast = list(ast) print(ast) print() # implication variant impl_ast = [] for c, e in flatten3(ast): if c: e = Implication(c, [e]) impl_ast.append(e) all_flags = frozenset(x.name for x in get_all_flags(ast)) # print flag names, vertically sorted_flags = sorted(all_flags) no_flags = len(sorted_flags) y_max = max(len(x) for x in sorted_flags) for y in range(0, y_max): for f in sorted_flags + ['|'] + sorted_flags: print(' %s' % (f[len(f)-y_max+y] if y >= y_max - len(f) else ' '), end='') print('') # solve for input = 000... to 111... max_iters = 0 unsolvable = 0 mismatched_solutions = 0 for values in itertools.product((False, True), repeat=no_flags): inp_flags = dict(zip(sorted_flags, values)) skip = False for k, v in immutable_flags.items(): # skip mismatches for immutables if inp_flags[k] != v: skip = True break if skip: continue for f in sorted_flags: if f in immutable_flags: print('\033[33m', end='') print(' %d' % inp_flags[f], end='') if f in immutable_flags: print('\033[0m', end='') print(' |', end='') if validate_constraint(inp_flags, ast): print('\033[32m', end='') for f in sorted_flags: print(' %d' % inp_flags[f], end='') print(' (==)\033[0m') else: try: ret, iters = do_solving(sorted_flags, inp_flags, ast, immutable_flags) except (ImmutabilityError, InfiniteLoopError): unsolvable += 1 else: if iters > max_iters: max_iters = iters ret_impl, ret_iters = do_solving(sorted_flags, inp_flags, impl_ast, immutable_flags, verbose=False) if ret != ret_impl: mismatched_solutions += 1 print('%*s |\033[31m' % (len(sorted_flags) * 2, ''), end='') for f in sorted_flags: if ret_impl[f] != ret[f]: print(' \033[1m%d\033[22m' % ret_impl[f], end='') else: print(' %d' % ret_impl[f], end='') print(' [mismatch between implication and basic form]\033[0m') print() print('max iterations: %d; unsolvable: %d; mismatched solutions for transform: %d' % (max_iters, unsolvable, mismatched_solutions))