Example #1
0
def reweighAndPrint():
	toPrint = []
	toAddToLeftSide=[]
	toAddToRightSide=[]
	for i in range(len(equalities)):
		leftSideLeftOvers=[]
		leftSide = functools.reduce(lambda x,y:x^y, [False]+InitialWallaceWeights('a',n,'a',n,i,toAddToLeftSide,leftSideLeftOvers))
		# EXCEPTION - MOVING -1 TO LEFT SIDE OF EQUATION TO SUBTRACT:
		if i==0:
			leftSide ^= 1
		#print("left side:")
		#print(leftSide)
		rightSideLeftOvers=[]
		rightSide = functools.reduce(lambda x,y:x^y, [False]+InitialWallaceWeights('n',n,'k',n,i,toAddToRightSide,rightSideLeftOvers))
		#print("right side")
		#print(rightSide)
		for j in equalities[:i+1]:
			leftSide = leftSide.subs(j)
			rightSide = rightSide.subs(j)
		toAddToLeftSide = leftSideLeftOvers
		toAddToRightSide = rightSideLeftOvers
		equations.append((leftSide,rightSide))
	for i,j in enumerate(equations):
		if str(sympy.simplify_logic(j[0])) != str(sympy.simplify_logic(j[1])):
			toPrint.append(("w:{:>2}{:>"+str(math.floor(shutil.get_terminal_size((80,20))[0]/2)-2-2-10)+"}").format(i,sympy.pretty(sympy.simplify_logic(j[0])))+" = "+sympy.pretty(sympy.simplify_logic(j[1])))
	print("\n".join(toPrint))
def symbolic_equality(test_expr, target_expr):
    """Test if two expressions are symbolically equivalent.

       Use the sympy 'simplify_logic' function to simplify the two boolean
       expressions as much as possible. Two equilvalent expressions MUST simplify
       to the same thing, and then they can be tested for equivalence again.

       Returns True if sympy can determine that the two expressions are equal,
       and returns False if they are not equal.

        - 'test_expr' should be the untrusted sympy expression to check.
        - 'target_expr' should be the trusted sympy expression to match against.
    """
    print("[SYMBOLIC TEST]")
    try:
        simplified_target = sympy.simplify_logic(target_expr)
        simplified_test = sympy.simplify_logic(test_expr)
        if simplified_target == simplified_test or sympy.srepr(simplified_target) == sympy.srepr(simplified_test):
            print("Symbolic match.")
            print("INFO: Adding known pair ({0}, {1})".format(target_expr, test_expr))
            KNOWN_PAIRS[(target_expr, test_expr)] = EqualityType.SYMBOLIC
            return True
        else:
            return False
    except NotImplementedError as e:
        print("{0}: {1} - Can't check symbolic equality!".format(type(e).__name__, str(e).capitalize()))
        return False
Example #3
0
def _recursive_simplify(expr):
    """Simplify the expression as much as possible based on
    domain knowledge."""
    input_expr = expr

    # Simplify even further, based on domain knowledge:
    # windowses = ('WIN32', 'WINRT')
    apples = ("MACOS", "UIKIT", "IOS", "TVOS", "WATCHOS")
    bsds = ("FREEBSD", "OPENBSD", "NETBSD")
    androids = ("ANDROID", )
    unixes = (
        "APPLE",
        *apples,
        "BSD",
        *bsds,
        "LINUX",
        *androids,
        "HAIKU",
        "INTEGRITY",
        "VXWORKS",
        "QNX",
        "WASM",
    )

    unix_expr = simplify_logic("UNIX")
    win_expr = simplify_logic("WIN32")
    false_expr = simplify_logic("false")
    true_expr = simplify_logic("true")

    expr = expr.subs(Not(unix_expr), win_expr)  # NOT UNIX -> WIN32
    expr = expr.subs(Not(win_expr), unix_expr)  # NOT WIN32 -> UNIX

    # UNIX [OR foo ]OR WIN32 -> ON [OR foo]
    expr = _simplify_expressions(expr, Or, (unix_expr, win_expr), true_expr)
    # UNIX  [AND foo ]AND WIN32 -> OFF [AND foo]
    expr = _simplify_expressions(expr, And, (unix_expr, win_expr), false_expr)

    expr = _simplify_flavors_in_condition("WIN32", ("WINRT", ), expr)
    expr = _simplify_flavors_in_condition("APPLE", apples, expr)
    expr = _simplify_flavors_in_condition("BSD", bsds, expr)
    expr = _simplify_flavors_in_condition("UNIX", unixes, expr)
    expr = _simplify_flavors_in_condition("ANDROID", (), expr)

    # Simplify families of OSes against other families:
    expr = _simplify_os_families(expr, ("WIN32", "WINRT"), unixes)
    expr = _simplify_os_families(expr, androids, unixes)
    expr = _simplify_os_families(expr, ("BSD", *bsds), unixes)

    for family in ("HAIKU", "QNX", "INTEGRITY", "LINUX", "VXWORKS"):
        expr = _simplify_os_families(expr, (family, ), unixes)

    # Now simplify further:
    expr = simplify_logic(expr)

    while expr != input_expr:
        input_expr = expr
        expr = _recursive_simplify(expr)

    return expr
def _simplify_flavors_in_condition(base: str, flavors, expr):
    """Simplify conditions based on the knowledge of which flavors
    belong to which OS."""
    base_expr = simplify_logic(base)
    false_expr = simplify_logic("false")
    for flavor in flavors:
        flavor_expr = simplify_logic(flavor)
        expr = _simplify_expressions(expr, And, (base_expr, flavor_expr), flavor_expr)
        expr = _simplify_expressions(expr, Or, (base_expr, flavor_expr), base_expr)
        expr = _simplify_expressions(expr, And, (Not(base_expr), flavor_expr), false_expr)
    return expr
def _simplify_os_families(expr, family_members, other_family_members):
    for family in family_members:
        for other in other_family_members:
            if other in family_members:
                continue  # skip those in the sub-family

            f_expr = simplify_logic(family)
            o_expr = simplify_logic(other)

            expr = _simplify_expressions(expr, And, (f_expr, Not(o_expr)), f_expr)
            expr = _simplify_expressions(expr, And, (Not(f_expr), o_expr), o_expr)
            expr = _simplify_expressions(expr, And, (f_expr, o_expr), simplify_logic("false"))
    return expr
Example #6
0
def simp_guarantee_conjunct(Gset, Gvars):
    """
    Convert a conjunction of guarantees to an equivalent an Boolean expression
    input: Gset - a list of guarantee conjuncts
           Gvars - set of all variables involved in the conjuncts
    output: an equivalent Boolean expression
    """
    ldict = {}
    G_all = []
    for idx in Gset:  # picks out guarantees from a particular fixpoint
        G_all.append(Gi[idx])
    for var in Gvars:
        exec(var + '= Symbol(\'' + var + '\')', globals(), ldict)
    B = None
    for disj in G_all:
        A = None
        for conj in disj:
            if A == None:
                exec('A = ' + conj, locals(), ldict)
                A = ldict['A']
            else:
                exec('A = A | ' + conj, locals(), ldict)  # TODO: check
                A = ldict['A']
        if B == None:
            exec('B = A', locals(), ldict)
            B = ldict['B']
        else:
            exec('B = B & A', locals(), ldict)
            B = ldict['B']
    if B != None:
        B = simplify_logic(B, force=True)
    return str(B)
 def _convert_to_dnf(self):
     self._debug(f'Converting to DNF ...',
                 header=self._middle_code.full_name)
     exp = simplify_logic(self._middle_code.exp,
                          form='dnf',
                          force=self._deep_simplify)
     self._middle_code.exp = to_dnf(exp)
     self._debug(f'Converted to DNF', header=self._middle_code.full_name)
Example #8
0
 def simplify_condition(cond, depth_limit=8, variables_limit=8):
     memo = {}
     if cond.depth > depth_limit or len(cond.variables) > variables_limit:
         return cond
     sympy_expr = ConditionProcessor.claripy_ast_to_sympy_expr(cond,
                                                               memo=memo)
     r = ConditionProcessor.sympy_expr_to_claripy_ast(
         sympy.simplify_logic(sympy_expr, deep=False), memo)
     return r
Example #9
0
def _aggregate_explanations(local_explanations_accuracy, topk_explanations,
                            target_class, x, y, max_accuracy, val_mask,
                            threshold):
    """
    Sort explanations by accuracy and then aggregate explanations which increase the accuracy of the aggregated formula.
    :param local_explanations_accuracy: dictionary of explanations and related accuracies.
    :param topk_explanations: limits the number of explanations to be aggregated.
    :param target_class: target class.
    :param x: observations in validation set.
    :param y: labels in validation set.
    :param max_accuracy: if True a formula is simplified only if the simplified formula gets 100% accuracy.
    :return:
    """
    if len(local_explanations_accuracy) == 0:
        return ""

    else:
        # get the topk most accurate local explanations
        local_explanations_sorted = sorted(
            local_explanations_accuracy.items(),
            key=lambda x: -x[1])[:topk_explanations]
        explanations = []
        best_accuracy = 0
        best_explanation = ""
        for explanation_raw, accuracy in local_explanations_sorted:
            explanation = _simplify_formula(explanation_raw, x, y,
                                            target_class, max_accuracy,
                                            val_mask, threshold)
            if not explanation:
                continue

            explanations.append(explanation)

            # aggregate example-level explanations
            aggregated_explanation = " | ".join(explanations)
            aggregated_explanation_simplified = simplify_logic(
                aggregated_explanation, "dnf", force=True)
            aggregated_explanation_simplified = f"({aggregated_explanation_simplified})"

            if aggregated_explanation_simplified in [
                    "",
                    "False",
                    "True",
                    "(False)",
                    "(True)",
            ]:
                continue
            accuracy, _ = test_explanation(aggregated_explanation_simplified,
                                           x, y, target_class, val_mask,
                                           threshold)
            if accuracy > best_accuracy:
                best_accuracy = accuracy
                best_explanation = aggregated_explanation_simplified
                explanations = [best_explanation]

    return best_explanation, best_accuracy
Example #10
0
def to_truth_table(func):
    """
    Translates Python code into truth table (represented as integer)
    """
    sfunc, args = _parse(func)
    symbols = sympy.symbols(args)
    return sum(
        (1 if sympy.simplify_logic(sfunc.subs(zip(symbols, reversed(bs)))
                                   ) else 0) << i
        for i, bs in enumerate(
            itertools.product([False, True], repeat=len(symbols))))
Example #11
0
def to_hex_truth_table(func):
    """
    Translates Python code into truth table (represented as hex string)
    """
    sfunc, args = _parse(func)
    symbols = sympy.symbols(args)
    nargs = len(symbols)
    v = sum(
        (1 if sympy.simplify_logic(sfunc.subs(zip(symbols, reversed(bs)))
                                   ) else 0) << i
        for i, bs in enumerate(itertools.product([False, True], repeat=nargs)))
    return "{:0{}x}".format(v, max(1, 2**(nargs - 2)))
Example #12
0
def to_binary_truth_table(func):
    """
    Translates Python code into truth table (represented as binary string)
    """
    sfunc, args = _parse(func)
    symbols = sympy.symbols(args)
    return ''.join(
        reversed([
            ('1' if sympy.simplify_logic(sfunc.subs(zip(
                symbols, reversed(bs)))) else '0')
            for bs in itertools.product([False, True], repeat=len(symbols))
        ]))
Example #13
0
def simp_assume_disjunct(Aset, Avars):
    """
    Convert a disjunction oof assumptions to an equivalent an Boolean expression
    input: Aset - a list of assumptions disjuncts
           Avars - set of all variables involved in the disjuncts
    output: an equivalent Boolean expression
    """
    ldict = {}  # local dictionary has to be created because of a bug in exec()
    A_all = []  #
    for idx in Aset:
        A_all.append(Ai[idx])
    for var in Avars:
        exec(var + '= Symbol(\'' + var + '\')', globals(), ldict)
    B = None
    for conj in A_all:
        A = None
        for disj in conj:
            if A == None:
                exec('A = ' + disj, locals(), ldict)
                A = ldict['A']
            else:
                exec('A = A & ' + disj, locals(), ldict)
                A = ldict['A']
        if B == None:
            exec('B = A', locals(), ldict)
            B = ldict['B']
        else:
            exec('B = B | A', locals(), ldict)
            B = ldict['B']
    if B != None:
        B = simplify_logic(B, force=True)
        # simplify init conditions
        # TODO: define init to automate this process
        exp1 = ldict['r1_far'] | ldict['r1_near'] | ldict['r1_home']
        exp2 = ldict['r2_far'] | ldict['r2_near'] | ldict['r2_home']
        B = B.subs(exp1, True)
        B = B.subs(exp2, True)
        B = simplify_logic(B, force=True)
    return str(B)
Example #14
0
def test_invert():
    from sympy import simplify_logic

    k0 = (K.Egap > 6) & (K.PV_cell < 13)
    kn0 = ~k0
    # The not simplifies version
    assert _expr_to_strings(kn0) == "!(Egap(!*6),PV_cell(!13*))"

    kn0 = simplify_logic(kn0)
    strings = _expr_to_strings(kn0)
    assert ":" in strings
    for itm in strings.split(":"):
        assert "!" not in itm

    #     reset()

    k1 = (K.Egap >= 6) & (K.PV_cell <= 13)
    kn1 = simplify_logic(~k1)
    strings = _expr_to_strings(kn1)
    assert ":" in strings
    for itm in strings.split(":"):
        assert "!" in itm
Example #15
0
def checkCmpExpression(s1, s2):
    """
    验证 两个式子是否一致
    :param s1: 标准参考答案
    :param s2: 用户输入结果
    :return:
    """

    if ':' in s1:
        answer_type, answer = s1.split(':')
        # 默认添加 : 的都是 矩阵类型的, 因为其他类型的貌似 sympy simplify 能处理
        # 一般矩阵乘法不满足交换律, 所以一般来说, 矩阵的表达式不需要化简
        # 貌似可以去掉空格而不影响式子, 所以处理方式是去掉空格后比较。
        answer = answer.replace(' ', '')
        s2 = s2.replace(' ', '')
        if s2 == answer:
            return True
        else:
            return False

    if is_expression_cmp(s1):
        return u'代数式方可简化,但不是方程,不能带=><'
    else:

        try:
            correct_answer = simplify_logic(s1)
        except:
            return u'作者编写的参考答案有bug,请联系管理员'

        try:
            input_answer = simplify_logic(s2)
        except:
            return u'你输入的代数式 %s ' % s2

        if input_answer != correct_answer:
            return False

    return True
Example #16
0
    def get_sympy(self, query: str, simplify: bool = True) -> sp.Basic:
        """Converts the given string query into a SymPy expression.

        :param query: The query to convert.
        :param simplify: If true (default) the result will be simplified using boolean logic.
        :return: A SymPy expression.
        """
        parsed_query = self._parser.parse(query)
        # noinspection PyUnresolvedReferences
        bool_expr = self.transform(parsed_query)

        if simplify:
            bool_expr = sp.simplify_logic(bool_expr)

        return bool_expr
 def longest_subexpression(self) -> SympyLogicMiddleCode:
     return SympyLogicMiddleCode(exp=simplify_logic(
         self._middle_code.exp.replace(sympy.Or, sympy.And)))
Example #18
0
 def simplify_condition(cond):
     memo = {}
     sympy_expr = ConditionProcessor.claripy_ast_to_sympy_expr(cond,
                                                               memo=memo)
     return ConditionProcessor.sympy_expr_to_claripy_ast(
         sympy.simplify_logic(sympy_expr), memo)
Example #19
0
def _aggregate_explanations_try_all(local_explanations_accuracy,
                                    topk_explanations, target_class, x, y,
                                    max_accuracy, val_mask, c_threshold):
    """
    Sort explanations by accuracy and then aggregate explanations which increase the accuracy of the aggregated formula.
    :param local_explanations_accuracy: dictionary of explanations and related accuracies.
    :param topk_explanations: limits the number of explanations to be aggregated.
    :param target_class: target class.
    :param x: observations in validation set.
    :param y: labels in validation set.
    :param max_accuracy: if True a formula is simplified only if the simplified formula gets 100% accuracy.
    :return:
    """
    if len(local_explanations_accuracy) == 0:
        return ""

    else:
        # get the topk most accurate local explanations
        local_explanations_sorted = sorted(
            local_explanations_accuracy.items(),
            key=lambda x: -x[1])[:topk_explanations]
        predictions = []
        explanations = []

        best_accuracy = 0.0
        best_explanation = ""

        for explanation_raw, accuracy in local_explanations_sorted:
            explanation = _simplify_formula(explanation_raw, x, y,
                                            target_class, max_accuracy,
                                            val_mask, c_threshold)
            if not explanation:
                continue

            predictions.append(
                get_predictions(explanation, x, target_class, c_threshold))
            explanations.append(explanation)

        predictions = np.array(predictions)
        explanations = np.array(explanations)

        y = y[:, target_class]
        y = y.detach().numpy()

        for i in range(1, 1 << len(predictions)):
            include = i & (1 << np.arange(len(predictions))) > 0
            pred = predictions[np.nonzero(include)]
            pred = np.sum(pred, axis=0)
            pred = pred > 0.5

            accuracy = np.sum(pred == y)
            if accuracy > best_accuracy:
                # aggregate example-level explanations
                explanation = explanations[np.nonzero(include)]
                aggregated_explanation = " | ".join(explanation)
                aggregated_explanation_simplified = simplify_logic(
                    aggregated_explanation, "dnf", force=True)
                aggregated_explanation_simplified = (
                    f"({aggregated_explanation_simplified})")

                if aggregated_explanation_simplified in [
                        "",
                        "False",
                        "True",
                        "(False)",
                        "(True)",
                ]:
                    continue
                else:
                    best_accuracy = accuracy
                    best_explanation = aggregated_explanation_simplified

    return best_explanation, best_accuracy
Example #20
0
def simplify_condition(condition: str) -> str:
    input_condition = condition.strip()

    # Map to sympy syntax:
    condition = " " + input_condition + " "
    condition = condition.replace("(", " ( ")
    condition = condition.replace(")", " ) ")

    tmp = ""
    while tmp != condition:
        tmp = condition

        condition = condition.replace(" NOT ", " ~ ")
        condition = condition.replace(" AND ", " & ")
        condition = condition.replace(" OR ", " | ")
        condition = condition.replace(" ON ", " true ")
        condition = condition.replace(" OFF ", " false ")
        # Replace dashes with a token
        condition = condition.replace("-", "_dash_")

    # SymPy chokes on expressions that contain two tokens one next to
    # the other delimited by a space, which are not an operation.
    # So a CMake condition like "TARGET Foo::Bar" fails the whole
    # expression simplifying process.
    # Turn these conditions into a single token so that SymPy can parse
    # the expression, and thus simplify it.
    # Do this by replacing and keeping a map of conditions to single
    # token symbols.
    # Support both target names without double colons, and with double
    # colons.
    pattern = re.compile(r"(TARGET [a-zA-Z]+(?:::[a-zA-Z]+)?)")
    target_symbol_mapping = {}
    all_target_conditions = re.findall(pattern, condition)
    for target_condition in all_target_conditions:
        # Replace spaces and colons with underscores.
        target_condition_symbol_name = re.sub("[ :]", "_", target_condition)
        target_symbol_mapping[target_condition_symbol_name] = target_condition
        condition = re.sub(target_condition, target_condition_symbol_name,
                           condition)

    # Do similar token mapping for comparison operators.
    pattern = re.compile(
        r"([a-zA-Z_0-9]+ (?:STRLESS|STREQUAL|STRGREATER) [a-zA-Z_0-9]+)")
    comparison_symbol_mapping = {}
    all_comparisons = re.findall(pattern, condition)
    for comparison in all_comparisons:
        # Replace spaces and colons with underscores.
        comparison_symbol_name = re.sub("[ ]", "_", comparison)
        comparison_symbol_mapping[comparison_symbol_name] = comparison
        condition = re.sub(comparison, comparison_symbol_name, condition)

    try:
        # Generate and simplify condition using sympy:
        condition_expr = simplify_logic(condition)
        condition = str(_recursive_simplify(condition_expr))

        # Restore the target conditions.
        for symbol_name in target_symbol_mapping:
            condition = re.sub(symbol_name, target_symbol_mapping[symbol_name],
                               condition)

        # Restore comparisons.
        for comparison in comparison_symbol_mapping:
            condition = re.sub(comparison,
                               comparison_symbol_mapping[comparison],
                               condition)

        # Map back to CMake syntax:
        condition = condition.replace("~", "NOT ")
        condition = condition.replace("&", "AND")
        condition = condition.replace("|", "OR")
        condition = condition.replace("True", "ON")
        condition = condition.replace("False", "OFF")
        condition = condition.replace("_dash_", "-")
    except (SympifyError, TypeError, AttributeError):
        # sympy did not like our input, so leave this condition alone:
        condition = input_condition

    return condition or "ON"
Example #21
0
leftSideKnowns = {
	D['a0']:True, # we assume a is odd - even can be calculated once we have odd
}
rightSideKnowns = {
	D['n0']:True, # if we substitute n or not it always will be odd so we might as well
}
substitutes = {
}
equations = []
toAddToLeftSide=[]
toAddToRightSide=[]
satEquation = True 
i = 0
while i < 2*n:
	leftSideLeftOvers=[]
	leftSide = sympy.simplify_logic(functools.reduce(lambda x,y:x^y, [False]+InitialWallaceWeights('a',n,'a',n,i,toAddToLeftSide,leftSideLeftOvers)).subs(leftSideKnowns))
	# EXCEPTION - MOVING -1 TO LEFT SIDE OF EQUATION TO SUBTRACT:
	if i==0:
		leftSide ^= 1
	leftSideLeftOvers = [sympy.simplify_logic(leftOverTerm.subs(leftSideKnowns)) for leftOverTerm in leftSideLeftOvers]
	
	rightSideLeftOvers=[]
	rightSide = functools.reduce(lambda x,y:x^y, [False]+InitialWallaceWeights('n',n,'k',n,i,toAddToRightSide,rightSideLeftOvers)).subs(rightSideKnowns).subs(substitutes)
	rightSideLeftOvers = [rightSideTerm.subs(rightSideKnowns).subs(substitutes) for rightSideTerm in rightSideLeftOvers]
	# replacing n digits
	if shouldSubN:
	    rightSide = rightSide.subs(n_Subs)
	    rightSideLeftOvers = [rightSideTerm.subs(n_Subs) for rightSideTerm in rightSideLeftOvers]
	
	toAddToLeftSide = leftSideLeftOvers
	toAddToRightSide = rightSideLeftOvers
Example #22
0
def _expr_to_strings(expr, symbol_prefix="x_", simplify=False, root=True):
    """Use expression tree to parse the symbols and return a aflow-like api-reference
    If the provided expression has only one known keyword, group all the conditions
    in one bracket.

    Other, use the sequency given by sympy
    parameter `root` controls at which level the keyword should be added
    """
    if not hasattr(expr, "func"):
        raise TypeError(
            f"Expect to take boolean expression but get {type(expr)} instead")
    if root:
        num_symbols, valid_symbols = _num_symbols_in_expr(
            expr, symbol_prefix=symbol_prefix)
        # convert keyword
        kw = symb_to_keyword[valid_symbols[0].name]
        if num_symbols > 1:
            # Use fallback mode expression convertion
            # TODO warning
            return _fallback_expr_to_strings(expr, symbol_prefix=symbol_prefix)

    # simplify should only be called at root level and 1 keyword
    # expected simplications are
    if simplify:
        expr = simplify_logic(expr)

    func = expr.func
    args = expr.args
    # Symbol or numerical are always leaf nodes
    # use expr.is_Xxx to determine type instead of func (not working for type One)
    if expr.is_Symbol:
        # Is current expression the keyword?
        if expr.name.startswith(symbol_prefix):
            return None
        else:
            # Wrap expression using single brackets
            return f"'{str(expr)}'"
    elif expr.is_Float:
        return str(float(expr))
    elif expr.is_Integer:
        return str(expr)
    else:
        pass

    # if func in (Symbol, Integer, Float):
    #     if func == Symbol:
    #         # Is current expression the keyword?
    #         if expr.name.startswith(symbol_prefix):
    #             return None
    #         else:
    #             # Wrap expression using single brackets
    #             return f"'{str(expr)}'"
    #     else:
    #         # Reduce the precision of float to default
    #         if func == Float:
    #             return str(float(expr))
    #         else:
    #             return str(expr)

    child_strings = []
    for arg in args:
        # get a partial string from each child
        # Handle only relational nodes
        cstr = _expr_to_strings(arg, symbol_prefix=symbol_prefix, root=False)
        if cstr is None:  # encounters target
            continue
        # determine the priority of operations. wrap lower priority with brackets
        #         if _expr_priority(arg) < _expr_priority(expr):
        #             cstr = "(" + cstr + ")"
        if arg.func in (And, Or):
            cstr = "(" + cstr + ")"
        child_strings.append(cstr)

    # all child calls are fallback mode!
    # import pdb; pdb.set_trace()
    current_string = _join_children(expr, child_strings)
    if root:
        current_string = f"{kw}({current_string})"

    return current_string
Example #23
0
 def simplify(expression):
     simplified_expr = sp.simplify_logic(
         parse_expr(expression, evaluate=False))
     return srepr(simplified_expr)