def assert_disequality(self, i, coeff, j): """ Adds the equality "ti != coeff * tj" This should never be called directly; rather, assert_comparison should be used. """ # Print this now, in case the disequality is superseded; we want to see this first. self.announce_comparison(i, terms.NE, coeff, j) superseded = False if (i, j) in self.inequalities: for c in self.inequalities[i, j]: comp = c.to_comp(terms.IVar(i), terms.IVar(j)) comp1, coeff1 = comp.comp, comp.term2.coeff if coeff1 == coeff: if comp1 == terms.GE: self.assert_inequality(i, terms.GT, coeff, j) superseded = True elif comp1 == terms.LE: self.assert_inequality(i, terms.LT, coeff, j) superseded = True if not superseded: if (i, j) in self.disequalities: if coeff not in self.disequalities[i, j]: self.disequalities[i, j].add(coeff) else: self.disequalities[i, j] = {coeff} self.update_clause(i, j) self.tracker.update((i, j))
def info_dump(self): """ For debugging purposes. Prints out all information known by the Blackboard. """ st = '\n******\n' for i in self.term_defs: st += '{0!s} := {1!s}\n'.format(terms.IVar(i), self.term_defs[i]) for i in self.zero_equalities: st += '{0!s} = 0\n'.format(terms.IVar(i)) for i in self.zero_inequalities: st += '{0!s} {1!s} 0\n'.format( terms.IVar(i), terms.comp_str[self.zero_inequalities[i]]) for i in self.zero_disequalities: st += '{0!s} != 0\n'.format(terms.IVar(i)) for (i, j) in sorted(self.equalities.keys()): st += '{0!s} = {1!s}\n'.format( terms.IVar(i), self.equalities[i, j] * terms.IVar(j)) for (i, j) in sorted(self.inequalities.keys()): for c in self.inequalities[i, j]: if c.a != 0 and c.b != 0: st += str(c.to_comp(terms.IVar(i), terms.IVar(j))) + '\n' for (i, j) in sorted(self.disequalities.keys()): for val in self.disequalities[i, j]: st += '{0!s} != {1!s}\n'.format(terms.IVar(i), val * terms.IVar(j)) st += '\n******\n' return st
def one_comparison_to_comparison(c, B): """ Converts a one comparison to a blackboard comparison between IVars, or None if the comparison is not of that form. """ p = c.term l = len(p.args) comp = terms.GT if c.strong else terms.GE if l == 0: #print c #return mul_util.process_mul_comp(mulpair_one, mulpair_one, p.coeff, comp, B) return terms.comp_eval[comp]( terms.IVar(0), mul_util.round_coeff(fractions.Fraction(1, p.coeff), comp) * terms.IVar(0)) #assert c.strong # comparisons 1 >= 1 should have been eliminated #return p.coeff*terms.IVar(0) > 0 # return None if l == 1: m = multiplicand_to_mulpair(p.args[0]) return mul_util.process_mul_comp(m, mulpair_one, p.coeff, comp, B) # return None elif l == 2: m1 = multiplicand_to_mulpair(p.args[0]) m2 = multiplicand_to_mulpair(p.args[1]) return mul_util.process_mul_comp(m1, m2, p.coeff, comp, B) else: return None
def get_equalities(self): """ Returns a list of equalities t_i == c*t_j or ti == 0. Does not include definitional eqs. """ equalities = [terms.IVar(i) == 0 for i in self.zero_equalities] for p in self.equalities: coeff = self.equalities[p] equalities.append(terms.IVar(p[0]) == coeff * terms.IVar(p[1])) return equalities
def raise_contradiction(self, i, comp, coeff, j): """ Called when the given information contradicts something already known. """ msg = 'Contradiction: {0!s}\n'.format( terms.TermComparison(terms.IVar(i), comp, coeff * terms.IVar(j))) msg += ' := {0!s}'.format( terms.TermComparison(self.terms[i], comp, coeff * self.terms[j])) raise terms.Contradiction(msg)
def derive_info_from_definitions(B): def mulpair_sign(p): if p.exponent % 2 == 0: return GT if B.implies(p.term.index, terms.NE, 0, 0) else GE # return 1 if B.sign(p.term.index) != 0 else 0 else: s = B.zero_inequalities.get(p.term.index, None) return comp_to_sign[s] if s is not None else 0 # return B.sign(p.term.index) # def weak_mulpair_sign(p): # if p.exponent % 2 == 0: # return 1 # else: # return B.weak_sign(p.term.index) for key in (k for k in B.term_defs if isinstance(B.term_defs[k], terms.MulTerm)): #signs = [mulpair_sign(p) for p in B.term_defs[key].args] #s = reduce(lambda x, y: x*y, signs) if any((B.implies(p.term.index, terms.EQ, 0, 0) and p.exponent >= 0) for p in B.term_defs[key].args): # This term is 0 * something else. B.assert_comparison(terms.IVar(key) == 0) if B.implies(key, terms.NE, 0, 0) and all((p.exponent > 0 or B.implies(p.term.index, terms.NE, 0, 0)) for p in B.term_defs[key].args): # we have strict information about key already. So everything must have a strict sign. for p in B.term_defs[key].args: #print 'from {0} != 0, we get {1} != 0'.format(B.term_defs[key], p.term) B.assert_comparison(p.term != 0) signs = [mulpair_sign(p) for p in B.term_defs[key].args] unsigned = [i for i in range(len(signs)) if signs[i] == 0] if B.weak_sign(key) != 0: if len(unsigned) == 0: s = reduce(lambda x, y: x*y, signs) B.assert_comparison(terms.comp_eval[sign_to_comp[s.dir, s.strong]](terms.IVar(key), 0)) if len(unsigned) == 1: ind = unsigned[0] s = reduce(lambda x, y: x*y, [signs[i] for i in range(len(signs)) if i is not ind], GT) if s.dir == B.weak_sign(key): # remaining arg is pos dir = terms.GT if B.sign(key) != 0 else terms.GE else: dir = terms.LT if B.sign(key) != 0 else terms.LE B.assert_comparison(terms.comp_eval[dir](B.term_defs[key].args[ind].term, 0)) elif len(unsigned) == 0: # we don't know any information about the sign of key. s = reduce(lambda x, y: x*y, signs) B.assert_comparison(terms.comp_eval[sign_to_comp[s.dir, s.strong]](terms.IVar(key), 0))
def get_disequalities(self): """ Returns a list of disequalities t_i != c*t_j or t_i != 0. """ disequalities = [terms.IVar(i) != 0 for i in self.zero_disequalities] for p in self.disequalities: coeff_list = self.disequalities[p] for coeff in coeff_list: disequalities.append( terms.IVar(p[0]) != coeff * terms.IVar(p[1])) return disequalities
def abs_of(i): """ Assuming abs_present(i), returns an expression equal to abs(t_i). """ if B.weak_sign(i) == 1: return terms.IVar(i) elif B.weak_sign(i) == -1: return terms.IVar(i) * -1 else: return terms.IVar( next(j for j in abs_indices if abs_arg_index(j) == i))
def announce_comparison(self, i, comp, coeff, j): """ Reports a successful assertion to the user. """ if messages.visible(messages.ASSERTION): messages.announce_strong('Asserting {0!s}'.format( terms.TermComparison(terms.IVar(i), comp, coeff * terms.IVar(j)))) if messages.visible(messages.ASSERTION_FULL): messages.announce_strong(' := {0!s}'.format( terms.TermComparison(self.terms[i], comp, coeff * self.terms[j])))
def assert_zero_equality(self, i): """ Adds the equality "ti = 0" This should never be called directly; rather, assert_comparison should be used. """ for k in self.zero_equalities: self.assert_comparison(terms.IVar(i) == terms.IVar(k)) self.zero_equalities.add(i) # todo: there's a lot of simplification that could happen if a term is equal to 0 self.announce_zero_comparison(i, terms.EQ) self.update_clause(i) self.tracker.update(i)
def get_inequalities(self): """ Returns a list of comparisons t_i <> c*t_j or t_i <> 0. """ inequalities = [] for i in self.zero_inequalities: comp = self.zero_inequalities[i] inequalities.append(terms.comp_eval[comp](terms.IVar(i), 0)) for (i, j) in self.inequalities: for hp in self.inequalities[i, j]: if hp.a != 0 and hp.b != 0: inequalities.append( hp.to_comp(terms.IVar(i), terms.IVar(j))) return inequalities
def term_name(self, ti): """ Assumes ti is a canonized term without IVars. Returns an IVar that represents t, if there is one. If not, recursively creates indices representing t and all its subterms, as needed. """ t = self.expand_term(ti) if isinstance(t, terms.IVar): return t if t.key in self.term_names: return terms.IVar(self.term_names[t.key]) else: if isinstance(t, terms.Var): new_def = t elif isinstance(t, terms.AddTerm): new_def = terms.AddTerm([ terms.STerm(a.coeff, self.term_name(a.term)) for a in t.args ]) elif isinstance(t, terms.MulTerm): new_def = terms.MulTerm([ terms.MulPair(self.term_name(a.term), a.exponent) for a in t.args ]) elif isinstance(t, terms.FuncTerm): new_def = t.func(*[ terms.STerm(a.coeff, self.term_name(a.term)) for a in t.args ]) #new_def = terms.FuncTerm(t.func_name, [terms.STerm(a.coeff, self.term_name(a.term)) # for a in t.args]) else: print isinstance(t, terms.STerm) raise Error('cannot create name for {0!s}'.format(t)) i = self.num_terms # index of the new term self.term_defs[i] = new_def self.terms[i] = t self.term_names[t.key] = i self.num_terms += 1 if messages.visible(messages.DEF): messages.announce_strong('Defining t{0!s} := {1!s}'.format( i, new_def)) if messages.visible(messages.DEF_FULL): messages.announce_strong(' := {1!s}'.format(i, t)) for j in self.zero_inequalities: hp = geometry.halfplane_of_comp(self.zero_inequalities[j], 0) self.inequalities[j, i] = [hp] return terms.IVar(i)
def expand_term(self, ti): """ Expands a term with IVars into its full definition. """ return ti.substitute( {terms.IVar(i).key: self.terms[i] for i in range(self.num_terms)})
def assert_comparison(self, c): """ Take an instance of terms.TermComparison, and adds the comparison to the blackboard. If the comparison terms are not both IVars, finds or creates indices for the terms. """ c = c.canonize() # c is now of the form "term comp sterm" term1, comp, coeff, term2 = c.term1, c.comp, c.term2.coeff, c.term2.term if coeff == 0: term2 = terms.IVar(0) if not isinstance(term1, terms.IVar) or not isinstance( term2, terms.IVar): ivar1 = term1 if isinstance(term1, terms.IVar) else self.term_name(term1) ivar2 = term2 if isinstance(term2, terms.IVar) else self.term_name(term2) c = terms.TermComparison(ivar1, comp, coeff * ivar2).canonize() term1, comp, coeff, term2 = c.term1, c.comp, c.term2.coeff, c.term2.term if coeff == 0: term2 = terms.IVar(0) if self.implies(term1.index, comp, coeff, term2.index): return elif self.implies(term1.index, terms.comp_negate(comp), coeff, term2.index): self.raise_contradiction(term1.index, comp, coeff, term2.index) if comp in (terms.GE, terms.GT, terms.LE, terms.LT): if coeff == 0: self.assert_zero_inequality(term1.index, comp) else: self.assert_inequality(term1.index, comp, coeff, term2.index) elif comp == terms.EQ: if coeff == 0: self.assert_zero_equality(term1.index) else: self.assert_equality(term1.index, coeff, term2.index) elif comp == terms.NE: if coeff == 0: self.assert_zero_disequality(term1.index) else: self.assert_disequality(term1.index, coeff, term2.index) else: raise Error('Unrecognized comparison: {0!s}'.format())
def instantiate(axiom, used_envs, B): """ Given an Axiom object, finds appropriate instantiations by unifying with B. Returns a list of clauses. """ # Get a list of assignments that work for all of axiom's triggers. envs = unify(B, axiom.triggers, list(axiom.vars), list(axiom.trig_arg_vars)) messages.announce(' Environments:', messages.DEBUG) for e in envs: messages.announce(' ' + str(e), messages.DEBUG) # For each assignment, use it to instantiate a Clause from axiom and assert it in B. clauses = [] astr = str(axiom) for env in [e for e in envs if (axiom, str(e)) not in used_envs]: literals = [] for l in axiom.literals: comp = l.comp red = reduce_term(l.term1, env)[0].canonize() red_coeff, red_term = red.coeff, red.term try: lcoeff, lterm = find_problem_term(B, red_term) lcoeff *= red_coeff except NoTermException: lterm = B.term_name(red.term).index lcoeff = red.coeff red = reduce_term(l.term2.term, env)[0].canonize() red_coeff, red_term = red.coeff, red.term try: rcoeff, rterm = find_problem_term(B, red.term) rcoeff *= l.term2.coeff * red_coeff except NoTermException: #sred = red.canonize() rterm = B.term_name(red.term).index rcoeff = red.coeff * l.term2.coeff literals.append(terms.comp_eval[comp](lcoeff * terms.IVar(lterm), rcoeff * terms.IVar(rterm))) clauses.append(literals) used_envs.add((axiom, str(env))) return clauses
def exp_factor_both(B): """ Adds axioms of the form exp(c1 * t1 + ... + cn * tn) = exp(t1)^c1 * ... * exp(tn)^cn (also when n = 1). """ exp_inds = [i for i in range(B.num_terms) if (isinstance(B.term_defs[i], terms.FuncTerm) and B.term_defs[i].func == terms.exp)] for i in exp_inds: coeff, t = B.term_defs[i].args[0].coeff, B.term_defs[B.term_defs[i].args[0].term.index] if isinstance(t, terms.AddTerm): margs = [B.term_name((terms.exp(a) ** coeff).canonize().term) for a in t.args] t2 = reduce(lambda x, y: x*y, margs, 1).canonize() n = B.term_name(t2.term) B.assert_comparison(terms.IVar(i) == t2.coeff * n) elif coeff != 1: term2 = (terms.exp(t)**coeff).canonize() n = B.term_name(term2.term) B.assert_comparison(terms.IVar(i) == term2.coeff * n)
def update_blackboard(self, B): """ Checks the blackboard B for function terms with equal arguments, and asserts that the function terms are equal. """ def eq_func_terms(f1, f2): """ Returns true if f1 and f2 have the same name and arity, and all args are equal. """ if f1.func_name != f2.func_name or len(f1.args) != len(f2.args): return False for i in range(len(f1.args)): arg1, arg2 = f1.args[i], f2.args[i] if arg1.coeff == 0: eq = B.implies(arg2.term.index, terms.EQ, 0, 0) or arg2.coeff == 0 else: eq = B.implies(arg1.term.index, terms.EQ, fractions.Fraction(arg2.coeff, arg1.coeff), arg2.term.index) if not eq: return False return True timer.start(timer.CCM) messages.announce_module('congruence closure module') func_classes = {} for i in (d for d in range(B.num_terms) if isinstance(B.term_defs[d], terms.FuncTerm)): name = B.term_defs[i].func_name func_classes[name] = func_classes.get(name, []) + [i] for name in func_classes: tinds = func_classes[name] for (i, j) in itertools.combinations(tinds, 2): # ti and tj are function terms with the same symbols. check if they're equal. f1, f2 = B.term_defs[i], B.term_defs[j] if eq_func_terms(f1, f2): B.assert_comparison(terms.IVar(i) == terms.IVar(j)) timer.stop(timer.CCM)
def get_additive_information(B): """ Retrieves the relevant information from the blackboard. """ comparisons = B.get_inequalities() + B.get_equalities() for key in B.term_defs: if isinstance(B.term_defs[key], terms.AddTerm): comparisons.append( terms.TermComparison(B.term_defs[key], terms.EQ, terms.IVar(key)) ) return comparisons
def exp_factor_constant(B): """ Takes a Blackboard B. For each i, If B.term_defs[i] is of the form exp(c*t), will declare that it is equal to exp(t)**c """ exp_inds = [i for i in range(B.num_terms) if (isinstance(B.term_defs[i], terms.FuncTerm) and B.term_defs[i].func == terms.exp)] for i in exp_inds: exponent = B.term_defs[i].args[0] if exponent.coeff != 1: term2 = (terms.exp(exponent.term)**exponent.coeff).canonize() n = B.term_name(term2.term) B.assert_comparison(terms.IVar(i) == term2.coeff * n)
def log_factor_exponent(B): """ Takes a Blackboard B. Looks for terms of the form log(t**e), and asserts that they are equal to e*log(t). """ log_inds = [i for i in range(B.num_terms) if (isinstance(B.term_defs[i], terms.FuncTerm) and B.term_defs[i].func == terms.log)] for i in log_inds: coeff, t = B.term_defs[i].args[0].coeff, B.term_defs[B.term_defs[i].args[0].term.index] if (coeff == 1 and isinstance(t, terms.MulTerm) and len(t.args) == 1 and t.args[0].exponent != 1 and t.args[0].exponent != 0 and B.implies_zero_comparison(t.args[0].term.index, terms.GT)): B.assert_comparison(terms.IVar(i) == t.args[0].exponent * terms.log(t.args[0].term))
def reduce_mul_term(t): """ Takes a MulTerm t in which variables t_j could appear multiple times: t_j^3 * t_j^-2 * t_j^-1 Since t_j is assumed to be positive, combines these so that each t_j appears once """ inds = set(a.term.index for a in t.args) ind_lists = [[i for i in range(len(t.args)) if t.args[i].term.index == j] for j in inds] rt = terms.One() for l in ind_lists: exp = sum(t.args[k].exponent for k in l) if exp != 0: rt *= t.args[l[0]].term ** exp if isinstance(rt, terms.One): return terms.IVar(0) return rt
def implies_comparison(self, c): """ Takes an instance of terms.TermComparison, and assume the comparison canonizes to a comparison between IVars. Determines whether the comparison is implied by information in the blackboard. """ c = c.canonize() term1, comp, coeff, term2 = c.term1, c.comp, c.term2.coeff, c.term2.term if coeff == 0: term2 = terms.IVar(0) if isinstance(term1, terms.IVar) and isinstance(term2, terms.IVar): return self.implies(term1.index, comp, coeff, term2.index) else: return False
def get_additive_information(B): """ Retrieves known additive comparisons and inequalities from the blackboard B. """ zero_equalities = [ equality_to_zero_equality(c) for c in B.get_equalities() ] zero_comparisons = [ inequality_to_zero_comparison(c) for c in B.get_inequalities() ] # convert each definition ti = s0 + s1 + ... + sn to a zero equality for i in range(B.num_terms): if isinstance(B.term_defs[i], terms.AddTerm): zero_equalities.append(cast_to_sum(terms.IVar(i) - B.term_defs[i])) return zero_equalities, zero_comparisons
def reduce_term(term, env): """ env maps UVar indices to (coeff, IVar index) pairs. Replaces all defined UVars in term with their designated values. Returns a pair of a new STerm and a flag whether all UVars have been replaced. """ # TODO: this duplicates some functionality of Term.substitute(), but adds the check for UVars. # can we recover this some other way? if isinstance(term, terms.STerm): l = reduce_term(term.term, env) return terms.STerm(term.coeff * l[0].coeff, l[0].term), l[1] if isinstance(term, terms.UVar): if term.index in env: c, j = env[term.index] return terms.STerm(c, terms.IVar(j)), True else: return terms.STerm(1, term), False elif isinstance(term, terms.Atom): return terms.STerm(1, term), True elif isinstance(term, terms.AddTerm): rfunc = lambda (s1, flag1), (s2, flag2): (s1 + s2, flag1 and flag2) t = reduce(rfunc, [(lambda a: (ap.coeff * a[0], a[1]))(reduce_term(ap.term, env)) for ap in term.args]) return terms.STerm(1, t[0]), t[1] elif isinstance(term, terms.MulTerm): rfunc = lambda (s1, flag1), (s2, flag2): (s1 * s2, flag1 and flag2) t = reduce(rfunc, [(lambda a: (a[0]**mp.exponent, a[1]))(reduce_term(mp.term, env)) for mp in term.args]) return terms.STerm(1, t[0]), t[1] elif isinstance(term, terms.FuncTerm): flag1 = True nargs = [] for a in term.args: t = reduce_term(a.term, env) s, flag2 = t[0], t[1] nargs.append(a.coeff * s) flag1 = flag1 and flag2 #return terms.STerm(1, terms.FuncTerm(term.func_name, nargs)), flag1 return terms.STerm(1, term.func(*nargs)), flag1 else: raise Exception('Unknown term type encountered in reduce_term: ' + str(term))
def log_factor_product(B): """ Takes a Blackboard and looks for terms of the form log(a*b*...). Asserts that they are equal to log(a)+log(b)+... """ def is_pos(mulpair): return (B.implies_zero_comparison(mulpair.term.index, terms.GT) or (mulpair.exponent % 2 == 0 and B.implies_zero_comparison(mulpair.term.index, terms.NE))) log_inds = [i for i in range(B.num_terms) if (isinstance(B.term_defs[i], terms.FuncTerm) and B.term_defs[i].func == terms.log)] for i in log_inds: coeff, t = B.term_defs[i].args[0].coeff, B.term_defs[B.term_defs[i].args[0].term.index] if coeff == 1 and isinstance(t, terms.MulTerm) and all(is_pos(a) for a in t.args): margs = [terms.log(p.term**p.exponent) for p in t.args] t2 = reduce(lambda x, y: x+y, margs, 0).canonize() B.assert_comparison(terms.IVar(i) == t2)
def exp_factor_sum(B): """ Takes a Blackboard and a list of IVar indices, s.t. i in exp_inds implies B.term_defs[i] is an exponential function. Asserts a number of comparisons to B. If B.term_defs[i] is of the form exp(t_1 + ct_2 + ...), will declare that it is equal to exp(t_1)*exp(ct_2)*... """ exp_inds = [i for i in range(B.num_terms) if (isinstance(B.term_defs[i], terms.FuncTerm) and B.term_defs[i].func == terms.exp)] for i in exp_inds: coeff, t = B.term_defs[i].args[0].coeff, B.term_defs[B.term_defs[i].args[0].term.index] if isinstance(t, terms.AddTerm) and coeff == 1: margs = [B.term_name(terms.exp(a).canonize().term) for a in t.args] t2 = reduce(lambda x, y: x*y, margs, 1).canonize() n = B.term_name(t2.term) B.assert_comparison(terms.IVar(i) == t2.coeff * n)
def zero_comparison_to_comparison(c): """ Converts a zero comparison to a blackboard comparison between IVars, or None if the comparison is not of that form. """ s = c.term l = len(s.args) if l == 0: assert c.strong # comparisons 0 >= 0 should have been eliminated return terms.IVar( 0) < 0 # TODO: is the a better way of returning a contradiction? if l == 1: t = summand_to_sterm(s.args[0]) return t > 0 if c.strong else t >= 0 elif l == 2: t1 = summand_to_sterm(s.args[0]) t2 = summand_to_sterm(s.args[1]) return t1 > -t2 if c.strong else t1 >= -t2 else: return None
def get_multiplicative_information(B): """ Retrieves the relevant information from the blackboard. Filters to only comparisons and equations where sign information is known, and converts to absolute value form. Note: in the returned comparisons, t_j represents |t_j| """ comparisons = [] for c in (c for c in B.get_inequalities() + B.get_equalities() if c.term2.coeff != 0): ind1 = c.term1.index ind2 = c.term2.term.index if B.sign(ind1) != 0 and B.sign(ind2) != 0: comparisons.append(make_term_comparison_abs(c, B)) for key in B.term_defs: if (isinstance(B.term_defs[key], terms.MulTerm) and B.sign(key) != 0 and all(B.sign(p.term.index) != 0 for p in B.term_defs[key].args)): lhs, rhs = reduce_mul_term(B.term_defs[key]), terms.IVar(key) comparisons.append( terms.TermComparison(lhs, terms.EQ, rhs) ) return comparisons
def multiplicand_to_mulpair(m): return terms.MulPair(terms.IVar(m.index), m.exp)
Retrieves known multiplicative equalities and inequalities from the blackboard B. """ m_comparisons = mul_util.get_multiplicative_information(B) # Each ti in m_comparisons really represents |t_i|. one_equalities = [ equality_to_one_equality(c) for c in m_comparisons if c.comp == terms.EQ ] one_comparisons = [ inequality_to_one_comparison(c) for c in m_comparisons if c.comp in (terms.GT, terms.GE, terms.LT, terms.LE) ] return one_equalities, one_comparisons mulpair_one = terms.MulPair(terms.IVar(0), 1) def one_equality_to_comparison(e, B): """ Converts an equation e == 0 to a blackboard comparison between IVars, or None if the equation is not of that form. Restores sign information from the Blackboard. """ l = len(e.args) if l == 1: m = multiplicand_to_mulpair(e.args[0]) return mul_util.process_mul_comp(m, mulpair_one, e.coeff, terms.EQ, B) elif l == 2: m1 = multiplicand_to_mulpair(e.args[0]) m2 = multiplicand_to_mulpair(e.args[1]) return mul_util.process_mul_comp(m1, m2, e.coeff, terms.EQ, B)