Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
5db504e
CC offset equalities: weighted union-find foundation
jhoenicke Jun 12, 2026
a6b63da
CC offset equalities: offset-aware congruence signatures
jhoenicke Jun 12, 2026
c86c95d
CC offset equalities: offset-keyed pair hash structure
jhoenicke Jun 12, 2026
f2f9f5f
Update offset-equality plan with refined increment sequencing
jhoenicke Jun 12, 2026
8cf4e2c
CC offset equalities: canonicalize pair-hash offset to avoid collisions
jhoenicke Jun 12, 2026
bda06ac
CC offset equalities: enable creation and thread offsets end-to-end
jhoenicke Jun 12, 2026
72ced6c
CC offset equalities: fix shared-term equality losing polynomial stru…
jhoenicke Jun 12, 2026
87015ba
CC offset equalities: disable offsets in the presence of quantifiers
jhoenicke Jun 12, 2026
381d964
Update offset-equality plan: status, remaining gaps, CCParameter design
jhoenicke Jun 13, 2026
ba28012
Plan: array theory rebuilds on index merge, so index keys need no reh…
jhoenicke Jun 13, 2026
3aa32c6
CC offset equalities: add CCParameter (CCTerm + Rational) abstraction
jhoenicke Jun 13, 2026
9bc1523
CC offset equalities: make checkCongruence offset-aware via CCParameter
jhoenicke Jun 13, 2026
90a285b
CC offset equalities: add CCParameter.getValueKey for offset-aware ma…
jhoenicke Jun 13, 2026
7e3fd52
Plan: add resume point for the offset-equality work
jhoenicke Jun 13, 2026
cd42ced
CC offset equalities: make ArrayTheory offset-aware via CCParameter
jhoenicke Jun 14, 2026
5265789
Plan: ArrayTheory offset migration done; LA->CC propagation is next
jhoenicke Jun 14, 2026
6a00b78
CC offset equalities: add CCAppTerm.getArgParam for the CCParameter a…
jhoenicke Jun 14, 2026
98b9d60
Plan: gap 2 (LA→CC offset propagation) is blocked on increment 4
jhoenicke Jun 14, 2026
858013d
CC offset equalities: LA->CC offset propagation + offset disequality …
jhoenicke Jun 14, 2026
083f73a
Plan: gaps 2 and 3 + offset conflict explanation done
jhoenicke Jun 14, 2026
ee590bf
nia/divaxiom2: enable produce-proofs so get-proof works
jhoenicke Jun 14, 2026
0c94d30
CC offset equalities: give LASharedTerm its full value (offset), shar…
jhoenicke Jun 15, 2026
74b73b0
Plan: CC<->LA sharing redesign done; SystemTest 6 -> 1
jhoenicke Jun 15, 2026
329f227
CC offset equalities: propagate implied offset equalities at checkpoint
jhoenicke Jun 15, 2026
104d04e
CC offset equalities: unify parallel offset arrays into one CCParamet…
jhoenicke Jun 16, 2026
a65d178
CC offset equalities: cast offset-free CCParameters to CCTerm (runtim…
jhoenicke Jun 16, 2026
497b19f
CC offset equalities: offset-aware datatype model building
jhoenicke Jun 16, 2026
b23521c
CC offset equalities: include offset in CCEquality.getSMTFormula
jhoenicke Jun 16, 2026
33f6582
Add system tests for offset-equality model construction
jhoenicke Jun 16, 2026
b32334c
CC offset equalities: offset-aware datatype dt-project / dt-injective…
jhoenicke Jun 16, 2026
63e3a90
CC offset equalities: carry CCParameters with offsets in CCAnnotation…
jhoenicke Jun 16, 2026
f6ecacf
CC offset equalities: add CCParameter.getFlatTerm() (option-2 SMT enc…
jhoenicke Jun 16, 2026
eac6718
CC offset equalities: make CCProofGenerator offset-aware
jhoenicke Jun 16, 2026
74265a8
CC offset equalities: key CongruencePath.mVisited offset-free (one su…
jhoenicke Jun 16, 2026
4a2fac4
CC offset equalities: lowlevel proof for offset cong/trans/dt lemmas
jhoenicke Jun 17, 2026
c227c9b
CC offset equalities: offset-aware CCProofGenerator keys + anti-cycle…
jhoenicke Jun 17, 2026
1087ea1
CC offset equalities: cross-class anti-cycle proof object
jhoenicke Jun 18, 2026
8ec627d
CC offset equalities: lazy disequality explanation, drop eager precom…
jhoenicke Jun 19, 2026
0277948
CC offset equalities: offset-aware array proof paths + structural-off…
jhoenicke Jun 19, 2026
7372a38
CC offset equalities: clash-slot MBTC + offset-free sharing
jhoenicke Jun 23, 2026
c7506e8
CC offset equalities: use the effective offset flag in LinArSolve
jhoenicke Jun 23, 2026
b43f9ee
CC offset equalities: carry the offset on computeCycle's main disequa…
jhoenicke Jun 23, 2026
345ecee
CC offset equalities: congruence-merge offset conflict explanation
jhoenicke Jun 23, 2026
c71138e
CC offset equalities: congruence proof lemma is offset-free
jhoenicke Jun 23, 2026
4aebeec
CC offset equalities: shared-term clash conflict + CongruencePath cle…
jhoenicke Jun 24, 2026
47debf0
CC offset equalities: getParams(anchor) + merge-conflict-diseq stitch
jhoenicke Jun 24, 2026
98de08b
CC offset equalities: orient stitched subpaths by anchor in getParams
jhoenicke Jun 24, 2026
19a70b2
CC offset equalities: consolidate all offset-cycle explainers into one
jhoenicke Jun 24, 2026
14b49bb
CC offset equalities: report merge conflicts before mutating the graph
jhoenicke Jun 24, 2026
af95763
CC offset equalities: persistent drainTodo dedup + inline grafts via …
jhoenicke Jun 25, 2026
e19c935
CC offset equalities: offset-aware array weak-eq propagation (fix tri…
jhoenicke Jun 25, 2026
fb562ec
CC offset equalities: offset-aware array extensionality fingerprint +…
jhoenicke Jun 25, 2026
f6ebca2
CC offset equalities: remove error-prone getModelValue(CCTerm) from M…
jhoenicke Jun 25, 2026
19ebbea
CC offset equalities: inline getModelValueWithOffset into getModelVal…
jhoenicke Jun 25, 2026
854ab19
Trivial refactoring: set offset of equality in constructor.
jhoenicke Jun 25, 2026
d8342e5
Refactoring of anti cycle code
jhoenicke Jun 25, 2026
5a7c41b
Remove some dead code.
jhoenicke Jun 25, 2026
5d473e6
CC offset equalities: map offseted terms to CCParameter.
jhoenicke Jun 25, 2026
811e201
CC offset equalities: order subpaths after their congruence dependencies
jhoenicke Jun 26, 2026
14e58e0
Handle Offset Equalities in all CC/Array/DT-Lemmas
jhoenicke Jul 4, 2026
10a25cd
CC offset equalities: annotate select/const edge in weakeq-ext lemmas
jhoenicke Jul 4, 2026
23e63e1
Fix-up for offset equality change for CC-Lemmas
jhoenicke Jul 4, 2026
e45b0b4
Always use the select edge and fail if not present
jhoenicke Jul 4, 2026
694da7a
Canonicalize sums as offset-free part plus trailing constant
jhoenicke Jul 4, 2026
135564b
Move OffsetEqKey and OffsetTerm to util package.
jhoenicke Jul 4, 2026
5cd35cb
Support offset equalities in CCInterpolator.
jhoenicke Jul 5, 2026
db10b2c
Support offset equalities in DatatypeInterpolator.
jhoenicke Jul 5, 2026
d09f5cd
Fix two lifetime bugs in the master reverse trigger machinery.
jhoenicke Jul 5, 2026
0082239
Pass empty source annotation for theory-created equality atoms.
jhoenicke Jul 5, 2026
25b8fb8
Support printing int arrays (for dt_cycle proof terms)
jhoenicke Jul 5, 2026
2f97d92
Avoid pivoting on literals not occuring in clause
jhoenicke Jul 5, 2026
fea5a80
Support offset equalities in ArrayInterpolator.
jhoenicke Jul 5, 2026
2efaa53
Support offset equalities in e-matching and instantiation.
jhoenicke Jul 5, 2026
09582e9
Enable offset equalities in the presence of quantifiers.
jhoenicke Jul 5, 2026
a220871
Fix fresh model values colliding with offset use-sites
jhoenicke Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ private void run(final Appendable appender) throws IOException {
}
}
appender.append('(');
} else if (next instanceof int[]) {
final int[] arr = (int[]) next;
mTodo.addLast(")");
for (int i = arr.length - 1; i >= 0; i--) {
mTodo.addLast(arr[i]);
if (i > 0) {
mTodo.addLast(" ");
}
}
appender.append('(');
} else {
appender.append(next.toString());
}
Expand Down
1,467 changes: 1,467 additions & 0 deletions SMTInterpol/doc/offset-equality-plan.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@

import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol;
import de.uni_freiburg.informatik.ultimate.logic.Rational;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.smtinterpol.proof.SourceAnnotation;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CCParameter;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CCTerm;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CClosure;

Expand All @@ -35,19 +37,29 @@ public class CCTermBuilder {
private final SourceAnnotation mSource;

private final ArrayDeque<Operation> mOps = new ArrayDeque<>();
private final ArrayDeque<CCTerm> mConverted = new ArrayDeque<>();
/**
* The converted results as {@link CCParameter}s: the term that produced {@code mConverted.peek()} has value
* {@code peek().getCCTerm() + peek().getOffset()}. The offset carries the {@code +5} of an offset-free term like
* {@code x+5} up to the enclosing application, where it ends up in that argument's {@link CCParameter}. Offsets are
* {@link Rational#ZERO} (i.e. a bare {@link CCTerm}) unless offset equalities are enabled.
*/
private final ArrayDeque<CCParameter> mConverted = new ArrayDeque<>();

public CCTermBuilder(Clausifier clausifier, final SourceAnnotation source) {
mClausifier = clausifier;
mSource = source;
}

public CCTerm convert(final Term t) {
private void pushResult(final CCParameter ccParam) {
mConverted.push(ccParam);
}

public CCParameter convert(final Term t) {
mOps.push(new BuildCCTerm(t));
while (!mOps.isEmpty()) {
mOps.pop().perform();
}
final CCTerm res = mConverted.pop();
final CCParameter res = mConverted.pop();
assert mConverted.isEmpty();
return res;
}
Expand All @@ -61,12 +73,19 @@ public BuildCCTerm(final Term term) {

@Override
public void perform() {
CCTerm ccTerm = mClausifier.getCCTerm(mTerm);
// mCCTerms is keyed by offset-free terms; probe (and below build) the offset-free part, then re-apply the
// constant as the CCParameter's offset (zero when the term is already offset-free).
final Term offsetFree = mClausifier.getOffsetFreeTerm(mTerm);
final CCTerm ccTerm = mClausifier.getCCTerm(offsetFree);
if (ccTerm != null) {
mConverted.push(ccTerm);
pushResult(CCParameter.of(ccTerm, mClausifier.getTermConstant(mTerm)));
} else {
final CClosure cclosure = mClausifier.getCClosure();
if (Clausifier.needCCTerm(mTerm) && ((ApplicationTerm) mTerm).getParameters().length > 0) {
if (offsetFree != mTerm) {
// Numeric term with a non-zero constant: build the offset-free CCTerm and remember the constant.
mOps.push(new AddOffsetToTerm(mClausifier.getTermConstant(mTerm)));
mOps.push(new BuildCCTerm(offsetFree));
} else if (Clausifier.needCCTerm(mTerm) && ((ApplicationTerm) mTerm).getParameters().length > 0) {
final FunctionSymbol fs = ((ApplicationTerm) mTerm).getFunction();
if (fs.isIntern() && fs.getName() == "select") {
mClausifier.getArrayTheory().cleanCaches();
Expand All @@ -79,16 +98,34 @@ public void perform() {
}
} else {
// We have an intern function symbol
ccTerm = cclosure.createAnonTerm(mTerm);
cclosure.addTerm(ccTerm, mTerm);
mClausifier.shareCCTerm(mTerm, ccTerm);
final CCTerm anonTerm = cclosure.createAnonTerm(mTerm);
cclosure.addTerm(anonTerm, mTerm);
mClausifier.shareCCTerm(mTerm, anonTerm);
mClausifier.addTermAxioms(mTerm, mSource);
mConverted.push(ccTerm);
pushResult(anonTerm);
}
}
}
}

/**
* Adds an offset to the (already built) numeric CCTerm of its offset-free part,
* and pushes the CCParameter with the offset.
*/
private class AddOffsetToTerm implements Operation {
private final Rational mOffset;

public AddOffsetToTerm(final Rational offset) {
mOffset = offset;
}

@Override
public void perform() {
final CCTerm offsetFreeParam = (CCTerm) mConverted.pop();
pushResult(CCParameter.of(offsetFreeParam, mOffset));
}
}

/**
* Helper class to build the intermediate CCAppTerms. Note that all these terms will be func terms.
*
Expand All @@ -103,16 +140,18 @@ public BuildCCAppTerm(ApplicationTerm appTerm) {

@Override
public void perform() {
final CCTerm[] args = new CCTerm[mAppTerm.getParameters().length];
final CCParameter[] args = new CCParameter[mAppTerm.getParameters().length];
for (int i = args.length - 1; i >= 0; i--) {
args[i] = mConverted.pop();
}
assert mClausifier.getCCTerm(mAppTerm) == null;
final CCTerm ccTerm = mClausifier.getCClosure().createAppTerm(mAppTerm.getFunction(), args, mSource);
final CCTerm ccTerm =
mClausifier.getCClosure().createAppTerm(mAppTerm.getFunction(), args, mSource);
mClausifier.getCClosure().addTerm(ccTerm, mAppTerm);
mClausifier.shareCCTerm(mAppTerm, ccTerm);
mClausifier.addTermAxioms(mAppTerm, mSource);
mConverted.push(ccTerm);
// the application term itself is not numeric-offset; its value is the application
pushResult(ccTerm);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.bitvector.BvToIntUtils;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.ArrayTheory;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CCAppTerm;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CCParameter;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CCTerm;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.CClosure;
import de.uni_freiburg.informatik.ultimate.smtinterpol.theory.cclosure.DTReverseTrigger;
Expand Down Expand Up @@ -221,10 +222,10 @@ public Clausifier(final Theory theory, final DPLLEngine engine, final ProofMode
* The source annotation that is used for auxiliary axioms.
* @return the ccterm.
*/
public CCTerm createCCTerm(final Term term, final SourceAnnotation source) {
public CCParameter createCCTerm(final Term term, final SourceAnnotation source) {
final boolean wasRunning = mIsRunning;
mIsRunning = true;
final CCTerm ccterm = new CCTermBuilder(this, source).convert(term);
final CCParameter ccterm = new CCTermBuilder(this, source).convert(term);
mIsRunning = wasRunning;
if (!wasRunning) {
run();
Expand Down Expand Up @@ -261,8 +262,13 @@ public void setTermFlags(final Term term, final int newFlags) {
}

public void share(final CCTerm ccTerm, final LASharedTerm laTerm) {
// With offset equalities several terms (e.g. 2x+4y, 2x+4y+1, 2x+4y+5) map to the same offset-free CCTerm. Each
// of them is a distinct value, so each full-value LASharedTerm must be registered with linear arithmetic (so
// mbtc sees every value); but the offset-free CCTerm is shared with congruence closure only once.
getLASolver().addSharedTerm(laTerm);
getCClosure().addSharedTerm(ccTerm);
if (ccTerm.getSharedTerm() != ccTerm) {
getCClosure().addSharedTerm(ccTerm);
}
}

public void shareLATerm(final Term term, final LASharedTerm laTerm) {
Expand All @@ -284,6 +290,10 @@ public void shareCCTerm(final Term term, final CCTerm ccTerm) {
}

public void addTermAxioms(final Term term, final SourceAnnotation source) {
// Axioms are added for offset-free terms only; an offseted term (x+5) carries its constant structurally and its
// offset-free part (x) gets the axioms. The only callers with user terms (EqualityProxy, createEqualityProxy)
// normalize via getOffsetFreeTerm before calling.
assert getTermConstant(term).equals(Rational.ZERO) : "addTermAxioms on offseted term " + term;
final int termFlags = getTermFlags(term);
if ((termFlags & Clausifier.AUX_AXIOM_ADDED) == 0) {
final boolean wasRunning = mIsRunning;
Expand All @@ -293,7 +303,7 @@ public void addTermAxioms(final Term term, final SourceAnnotation source) {
CCTerm ccTerm = getCCTerm(term);
if (ccTerm == null && (needCCTerm(term) || term.getSort().isArraySort())) {
final CCTermBuilder cc = new CCTermBuilder(this, source);
ccTerm = cc.convert(term);
ccTerm = (CCTerm) cc.convert(term);
}

final ApplicationTerm at = (ApplicationTerm) term;
Expand Down Expand Up @@ -329,7 +339,7 @@ public void addTermAxioms(final Term term, final SourceAnnotation source) {
final String funcName = at.getFunction().getName();
final boolean isStore = funcName.equals("store");
final boolean isConst = funcName.equals(SMTLIBConstants.CONST);
mArrayTheory.notifyArray(getCCTerm(term), isStore, isConst);
mArrayTheory.notifyArray(ccTerm, isStore, isConst);
}

if (fs.isConstructor()) {
Expand All @@ -353,6 +363,15 @@ public void addTermAxioms(final Term term, final SourceAnnotation source) {

}
if (term.getSort().isNumericSort()) {
// With offset equalities the CCTerm is offset-free (value 2x+4y for a term 2x+4y+1) and the
// constant is carried structurally at the use sites. The LASharedTerm must then be offset-free
// too, so it stays value-consistent with the offset-free CCTerm: clash-slot MBTC values a member
// as value(rep) + offsetToRep and must not double-count the constant. Terms differing only by a
// constant share one offset-free CCTerm; each still registers its own offset-free LASharedTerm
// (same value), which is harmless and recognized as already-known by the offset-aware guards.
//
// Without offset equalities the LASharedTerm carries the full value (its constant), as before,
// so whole-term mbtc groups shared terms by their true value.
boolean needsLA = term instanceof ConstantTerm;
if (term instanceof ApplicationTerm) {
final String func = ((ApplicationTerm) term).getFunction().getName();
Expand All @@ -364,6 +383,9 @@ public void addTermAxioms(final Term term, final SourceAnnotation source) {
final MutableAffineTerm mat = createMutableAffinTerm(new Polynomial(term), source);
assert mat.getConstant().mEps == 0;
if (!mLATerms.containsKey(term)) {
// The LASharedTerm shares the term's value with linear arithmetic. The term is offset-free (see
// the precondition), so this stays consistent with getCCTerm and the endScope unshare loop, which
// look up CC nodes by the (offset-free) key.
shareLATerm(term, new LASharedTerm(term, mat.getSummands(), mat.getConstant().mReal));
}
}
Expand Down Expand Up @@ -570,13 +592,77 @@ public MutableAffineTerm toMutableAffineTerm(final Polynomial poly) {
}

public CCTerm getCCTerm(final Term term) {
// mCCTerms is keyed by offset-free terms only; an offseted term has no entry of its own (its CCParameter, with
// the constant as offset, is produced at build time by createCCTerm). Callers must normalize via
// getOffsetFreeTerm first, or use getCCParameter for a possibly offseted term.
assert getTermConstant(term).equals(Rational.ZERO) : "getCCTerm on offseted term " + term;
return mCCTerms.get(term);
}

/**
* Get the {@link CCParameter} denoting the value of the given term: the CCTerm of the term's offset-free part plus
* the term's constant. Unlike {@link #getCCTerm} this accepts terms with a constant summand. This function does not
* create new terms.
*
* @return the value of the term, or null if no CCTerm exists for the term's offset-free part.
*/
public CCParameter getCCParameter(final Term term) {
final Rational constant = getTermConstant(term);
final CCTerm ccTerm = getCCTerm(constant.equals(Rational.ZERO) ? term : getOffsetFreeTerm(term));
return ccTerm == null ? null : CCParameter.of(ccTerm, constant);
}

public LASharedTerm getLATerm(final Term term) {
return mLATerms.get(term);
}

/**
* Whether offset equalities are enabled in the congruence closure. When enabled, numeric terms are represented by
* offset-free CCTerms with the constant carried as an offset.
*/
public boolean createOffsetEqualities() {
return getCClosure() != null && getCClosure().createOffsetEqualities();
}

/**
* The constant offset of a numeric term, i.e. the value such that {@code term == offsetFreeTerm + constant}. Returns
* {@link Rational#ZERO} when offset equalities are disabled or the term is not numeric.
*/
public Rational getTermConstant(final Term term) {
if (!createOffsetEqualities() || !term.getSort().isNumericSort()) {
return Rational.ZERO;
}
return new Polynomial(term).getConstant();
}

/**
* Build the normalized term for {@code term + constant}. The result is a single flattened polynomial term (not a
* nested {@code (+ term constant)}), so that re-parsing it as a Polynomial recovers all summands.
*/
public Term addConstantToTerm(final Term term, final Rational constant) {
return CCParameter.addConstant(term, constant);
}

/**
* The offset-free part of a numeric term, i.e. the term with its constant
* summand removed. Two terms that differ only by a constant (e.g.
* {@code 2x+4y+1} and {@code 2x+4y+5}) yield the same canonical offset-free
* term, so they share a single CCTerm. Returns the term itself when it has no
* constant or offset equalities are disabled.
*/
public Term getOffsetFreeTerm(final Term term) {
if (!createOffsetEqualities() || !term.getSort().isNumericSort()) {
return term;
}
final Polynomial poly = new Polynomial(term);
final Rational constant = poly.getConstant();
if (constant.equals(Rational.ZERO)) {
return term;
}
poly.add(constant.negate());
return mCompiler.unifyPolynomial(poly, term.getSort());
}

public ILiteral getILiteral(final Term term) {
return mLiterals.get(term);
}
Expand Down Expand Up @@ -1296,8 +1382,13 @@ public EqualityProxy createEqualityProxy(final Term lhs, final Term rhs, final S
if (sort.getName().equals("Int") && !diff.getConstant().isIntegral()) {
return EqualityProxy.getFalseProxy();
}
addTermAxioms(lhs, source);
addTermAxioms(rhs, source);
// The proxy works on the offset-free sides plus the offset (value(offLhs) == value(offRhs) + offset). The
// offset is the difference of the two sides' constants.
final Term offLhs = getOffsetFreeTerm(lhs);
final Term offRhs = getOffsetFreeTerm(rhs);
final Rational offset = getTermConstant(rhs).sub(getTermConstant(lhs));
addTermAxioms(offLhs, source);
addTermAxioms(offRhs, source);
// we cannot really normalize the sign of the term. Try both signs.
EqualityProxy eqForm = mEqualities.get(diff);
if (eqForm != null) {
Expand All @@ -1308,7 +1399,7 @@ public EqualityProxy createEqualityProxy(final Term lhs, final Term rhs, final S
if (eqForm != null) {
return eqForm;
}
eqForm = new EqualityProxy(this, lhs, rhs);
eqForm = new EqualityProxy(this, offLhs, offRhs, offset);
mEqualities.put(diff, eqForm);
return eqForm;
}
Expand Down
Loading