Skip to content

Commit 3645128

Browse files
authored
Performance Series 2 (5/5): Faster quantifier-instantiation heuristic, plus code cleanup (#3878)
2 parents b99d9b6 + b7053e9 commit 3645128

7 files changed

Lines changed: 500 additions & 227 deletions

File tree

key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics/HandleArith.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ public class HandleArith {
2828

2929
private HandleArith() {}
3030

31+
/** The operator of {@code t} after stripping leading negations. */
32+
private static Operator strippedOp(JTerm t) {
33+
Operator op = t.op();
34+
while (op == Junctor.NOT) {
35+
t = t.sub(0);
36+
op = t.op();
37+
}
38+
return op;
39+
}
40+
41+
/** Whether {@code t} (ignoring leading negations) is an integer {@code >=} or {@code <=}. */
42+
private static boolean isArithComparison(JTerm t, IntegerLDT ig) {
43+
final Operator op = strippedOp(t);
44+
return op == ig.getGreaterOrEquals() || op == ig.getLessOrEquals();
45+
}
46+
3147
/**
3248
* try to prove atom by using polynomial
3349
*
@@ -36,6 +52,12 @@ private HandleArith() {}
3652
* <code>problem</code> if it cann't be proved.
3753
*/
3854
public static JTerm provedByArith(JTerm problem, Services services) {
55+
final IntegerLDT integerLDT = services.getTypeConverter().getIntegerLDT();
56+
if (!isArithComparison(problem, integerLDT) && strippedOp(problem) != Equality.EQUALS) {
57+
// neither an (in)equality nor an equality: formatArithTerm yields false and
58+
// provedArithEqual returns the problem unchanged -- bail before locking the cache.
59+
return problem;
60+
}
3961
final LRUCache<JTerm, JTerm> provedByArithCache =
4062
services.getCaches().getProvedByArithFstCache();
4163
JTerm result;
@@ -47,7 +69,6 @@ public static JTerm provedByArith(JTerm problem, Services services) {
4769
}
4870

4971
TermBuilder tb = services.getTermBuilder();
50-
IntegerLDT integerLDT = services.getTypeConverter().getIntegerLDT();
5172

5273
final JTerm trueT = tb.tt();
5374
final JTerm falseT = tb.ff();
@@ -127,6 +148,13 @@ private static JTerm provedArithEqual(JTerm problem, TermBuilder tb, Services se
127148
* @return trueT if true, falseT if false, and atom if can't be prove;
128149
*/
129150
public static JTerm provedByArith(JTerm problem, JTerm axiom, Services services) {
151+
final IntegerLDT integerLDT = services.getTypeConverter().getIntegerLDT();
152+
if (!isArithComparison(problem, integerLDT) || !isArithComparison(axiom, integerLDT)) {
153+
// not an arithmetic implication: formatArithTerm would yield false for one side and
154+
// this method returns the unproved problem -- bail before allocating the key and
155+
// taking the cache lock.
156+
return problem;
157+
}
130158
final Pair<JTerm, JTerm> key = new Pair<>(problem, axiom);
131159
final LRUCache<Pair<JTerm, JTerm>, JTerm> provedByArithCache =
132160
services.getCaches().getProvedByArithSndCache();
@@ -139,7 +167,6 @@ public static JTerm provedByArith(JTerm problem, JTerm axiom, Services services)
139167
}
140168

141169
final TermBuilder tb = services.getTermBuilder();
142-
final IntegerLDT integerLDT = services.getTypeConverter().getIntegerLDT();
143170
final ServiceCaches caches = services.getCaches();
144171

145172
final JTerm cd = formatArithTerm(problem, tb, integerLDT, caches);

key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics/MultiTrigger.java

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,101 +15,116 @@
1515
import org.key_project.util.collection.ImmutableMapEntry;
1616
import org.key_project.util.collection.ImmutableSet;
1717

18+
/**
19+
* A trigger built from several uni-trigger {@link #elements} that, taken together, bind all
20+
* universal variables of a clause. A match requires matching every element and merging their
21+
* substitutions into one that is consistent and total on {@link #clauseVariables}.
22+
*/
1823
class MultiTrigger implements Trigger {
1924

20-
private final ImmutableSet<Trigger> triggers;
25+
/** The uni-trigger elements that jointly have to cover {@link #clauseVariables}. */
26+
private final ImmutableSet<Trigger> elements;
2127

22-
private final ImmutableSet<QuantifiableVariable> qvs;
28+
/** The universal variables of the clause this multi-trigger must bind. */
29+
private final ImmutableSet<QuantifiableVariable> clauseVariables;
2330

2431
private final Term clause;
2532

26-
MultiTrigger(ImmutableSet<Trigger> triggers, ImmutableSet<QuantifiableVariable> qvs,
33+
MultiTrigger(ImmutableSet<Trigger> elements, ImmutableSet<QuantifiableVariable> clauseVariables,
2734
Term clause) {
28-
this.triggers = triggers;
29-
this.qvs = qvs;
35+
this.elements = elements;
36+
this.clauseVariables = clauseVariables;
3037
this.clause = clause;
3138
}
3239

3340
@Override
3441
public ImmutableSet<Substitution> getSubstitutionsFromTerms(ImmutableSet<Term> targetTerms,
3542
Services services) {
36-
ImmutableList<Substitution> res = ImmutableList.nil();
43+
ImmutableList<Substitution> total = ImmutableList.nil();
3744

38-
ImmutableSet<Substitution> mulsubs =
39-
setMultiSubstitution(triggers.iterator(), targetTerms, services);
45+
final ImmutableSet<Substitution> combined =
46+
combineElementSubstitutions(elements.iterator(), targetTerms, services);
4047

41-
for (Substitution sub : mulsubs) {
42-
if (sub.isTotalOn(qvs)) {
43-
res = res.prepend(sub);
48+
for (Substitution sub : combined) {
49+
if (sub.isTotalOn(clauseVariables)) {
50+
total = total.prepend(sub);
4451
}
4552
}
4653

47-
return DefaultImmutableSet.fromImmutableList(res);
54+
return DefaultImmutableSet.fromImmutableList(total);
4855
}
4956

50-
/** help function for getMultiSubstitution */
51-
private ImmutableSet<Substitution> setMultiSubstitution(Iterator<? extends Trigger> ts,
52-
ImmutableSet<Term> terms, Services services) {
53-
ImmutableList<Substitution> res = ImmutableList.nil();
54-
if (ts.hasNext()) {
55-
ImmutableSet<Substitution> subi = ts.next().getSubstitutionsFromTerms(terms, services);
56-
ImmutableSet<Substitution> nextSubs = setMultiSubstitution(ts, terms, services);
57-
if (nextSubs.isEmpty()) {
58-
return subi;
59-
} else if (subi.isEmpty()) {
60-
return nextSubs;
57+
/**
58+
* Matches every remaining element against the target terms and combines the per-element
59+
* substitution sets by taking the cross product and merging each compatible pair (incompatible
60+
* pairs are dropped). Used by {@link #getSubstitutionsFromTerms}.
61+
*/
62+
private ImmutableSet<Substitution> combineElementSubstitutions(
63+
Iterator<? extends Trigger> remainingElements, ImmutableSet<Term> terms,
64+
Services services) {
65+
ImmutableList<Substitution> result = ImmutableList.nil();
66+
if (remainingElements.hasNext()) {
67+
ImmutableSet<Substitution> headSubs =
68+
remainingElements.next().getSubstitutionsFromTerms(terms, services);
69+
ImmutableSet<Substitution> tailSubs =
70+
combineElementSubstitutions(remainingElements, terms, services);
71+
if (tailSubs.isEmpty()) {
72+
return headSubs;
73+
} else if (headSubs.isEmpty()) {
74+
return tailSubs;
6175
}
62-
for (Substitution sub0 : nextSubs) {
63-
for (Substitution subiSub : subi) {
64-
final Substitution sub1 = unifySubstitution(sub0, subiSub);
65-
if (sub1 != null) {
66-
res = res.prepend(sub1);
76+
for (Substitution tailSub : tailSubs) {
77+
for (Substitution headSub : headSubs) {
78+
final Substitution merged = mergeIfCompatible(tailSub, headSub);
79+
if (merged != null) {
80+
result = result.prepend(merged);
6781
}
6882
}
6983

7084
}
7185
}
72-
return DefaultImmutableSet.fromImmutableList(res);
86+
return DefaultImmutableSet.fromImmutableList(result);
7387
}
7488

7589
/**
76-
* unify two substitution, if same variable are bound with same term return a new substitution
77-
* with all universal quantifiable variables in two substituition, otherwise return null
90+
* Merges two substitutions: if they bind every shared variable to the same term, returns a new
91+
* substitution containing all bindings of both; otherwise (a conflicting binding) returns
92+
* {@code null}.
7893
*/
79-
private Substitution unifySubstitution(Substitution sub0, Substitution sub1) {
80-
final ImmutableMap<QuantifiableVariable, Term> varMap1 = sub1.getVarMap();
81-
ImmutableMap<QuantifiableVariable, Term> resMap = varMap1;
82-
83-
for (final ImmutableMapEntry<QuantifiableVariable, Term> en : sub0.getVarMap()) {
84-
QuantifiableVariable key = en.key();
85-
Term value = en.value();
86-
if (varMap1.containsKey(key)) {
87-
if (!(varMap1.get(key).equals(value))) {
94+
private Substitution mergeIfCompatible(Substitution left, Substitution right) {
95+
final ImmutableMap<QuantifiableVariable, Term> rightMap = right.getVarMap();
96+
ImmutableMap<QuantifiableVariable, Term> mergedMap = rightMap;
97+
98+
for (final ImmutableMapEntry<QuantifiableVariable, Term> entry : left.getVarMap()) {
99+
QuantifiableVariable key = entry.key();
100+
Term value = entry.value();
101+
if (rightMap.containsKey(key)) {
102+
if (!(rightMap.get(key).equals(value))) {
88103
return null;
89104
}
90105
}
91-
resMap = resMap.put(key, value);
106+
mergedMap = mergedMap.put(key, value);
92107
}
93-
return new Substitution(resMap);
108+
return new Substitution(mergedMap);
94109
}
95110

96111
@Override
97-
public boolean equals(Object arg0) {
98-
if (!(arg0 instanceof MultiTrigger a)) {
112+
public boolean equals(Object other) {
113+
if (!(other instanceof MultiTrigger otherTrigger)) {
99114
return false;
100115
}
101116

102-
return a.triggers.equals(triggers);
117+
return otherTrigger.elements.equals(elements);
103118
}
104119

105120
@Override
106121
public int hashCode() {
107-
return triggers.hashCode();
122+
return elements.hashCode();
108123
}
109124

110125
@Override
111126
public String toString() {
112-
return String.valueOf(triggers);
127+
return String.valueOf(elements);
113128
}
114129

115130
@Override

key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics/Substitution.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.key_project.logic.op.Function;
1616
import org.key_project.logic.op.QuantifiableVariable;
1717
import org.key_project.logic.sort.Sort;
18+
import org.key_project.util.collection.ImmutableList;
1819
import org.key_project.util.collection.ImmutableMap;
1920
import org.key_project.util.collection.ImmutableSet;
2021

@@ -94,10 +95,79 @@ private Term applySubst(QuantifiableVariable var, Term instance, Term t, TermBui
9495

9596
/**
9697
* Try to apply the substitution to a term, introducing casts if necessary (may never be the
97-
* case any more, XXX)
98+
* case any more, XXX).
99+
* <p>
100+
* Fast path: all variables are substituted in a single traversal ({@link #applyGroundOnePass}).
101+
* This is sound because the substitution is ground (the instances contain no free variables, so
102+
* no capture can happen); shadowing of a substituted variable by a binder inside {@code t} is
103+
* respected. If a cast is required (the slow, possibly dead path), it falls back to the
104+
* per-variable substitution {@link #applyWithoutCastsPerVar}.
98105
*/
99106
public Term applyWithoutCasts(Term t, Services services) {
100107
assert isGround() : "non-ground substitutions are not yet implemented: " + this;
108+
final TermBuilder tb = services.getTermBuilder();
109+
try {
110+
return applyGroundOnePass((JTerm) t, ImmutableList.nil(), tb);
111+
} catch (TermCreationException e) {
112+
return applyWithoutCastsPerVar(t, services);
113+
}
114+
}
115+
116+
/**
117+
* Substitute every variable of {@link #varMap} in a single traversal of {@code t}. A variable
118+
* occurrence is replaced unless it is shadowed, i.e. re-bound by a binder above it (tracked in
119+
* {@code boundAbove}). Subterms in which no substituted variable occurs free are returned
120+
* unchanged.
121+
*/
122+
private JTerm applyGroundOnePass(JTerm t, ImmutableList<QuantifiableVariable> boundAbove,
123+
TermBuilder tb) {
124+
// Variable leaves (the most common node) are handled directly, without the freeVars
125+
// bookkeeping of containsSubstVar.
126+
if (t.op() instanceof QuantifiableVariable qv) {
127+
final Term inst = varMap.get(qv);
128+
return inst != null && !boundAbove.contains(qv) ? (JTerm) inst : t;
129+
}
130+
if (!containsSubstVar(t)) {
131+
return t;
132+
}
133+
final int arity = t.arity();
134+
final JTerm[] newSubs = new JTerm[arity];
135+
boolean changed = false;
136+
for (int i = 0; i < arity; i++) {
137+
final JTerm sub = t.sub(i);
138+
ImmutableList<QuantifiableVariable> below = boundAbove;
139+
final var boundHere = t.varsBoundHere(i);
140+
for (int j = 0; j < boundHere.size(); j++) {
141+
below = below.prepend(boundHere.get(j));
142+
}
143+
newSubs[i] = applyGroundOnePass(sub, below, tb);
144+
if (newSubs[i] != sub) {
145+
changed = true;
146+
}
147+
}
148+
if (!changed) {
149+
return t;
150+
}
151+
return tb.tf().createTerm(t.op(), newSubs, t.boundVars(), t.getLabels());
152+
}
153+
154+
/** Whether any variable of {@link #varMap} occurs free in {@code t}. */
155+
private boolean containsSubstVar(JTerm t) {
156+
final ImmutableSet<QuantifiableVariable> free = t.freeVars();
157+
if (free.isEmpty()) {
158+
return false;
159+
}
160+
final Iterator<QuantifiableVariable> it = varMap.keyIterator();
161+
while (it.hasNext()) {
162+
if (free.contains(it.next())) {
163+
return true;
164+
}
165+
}
166+
return false;
167+
}
168+
169+
/** Per-variable substitution (one {@link ClashFreeSubst} pass per variable), with casts. */
170+
private Term applyWithoutCastsPerVar(Term t, Services services) {
101171
final TermBuilder tb = services.getTermBuilder();
102172
final Iterator<QuantifiableVariable> it = varMap.keyIterator();
103173
while (it.hasNext()) {

key.core/src/main/java/de/uka/ilkd/key/strategy/quantifierHeuristics/TriggerUtils.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ public static ImmutableSet<QuantifiableVariable> intersect(
5353
ImmutableSet<? extends QuantifiableVariable> set1) {
5454
ImmutableSet<QuantifiableVariable> res = DefaultImmutableSet.nil();
5555
if (!set0.isEmpty() && !set1.isEmpty()) {
56-
for (QuantifiableVariable aSet0 : set0) {
57-
final QuantifiableVariable el = aSet0;
58-
if (set1.contains(el)) {
59-
res = res.add(el);
56+
for (QuantifiableVariable element : set0) {
57+
if (set1.contains(element)) {
58+
res = res.add(element);
6059
}
6160
}
6261
}
@@ -69,13 +68,16 @@ public static ImmutableSet<QuantifiableVariable> intersect(
6968
ImmutableSet<? extends QuantifiableVariable> set2) {
7069

7170
final int size0 = set0.size();
72-
final int size1 = set0.size();
73-
final int size2 = set0.size();
71+
final int size1 = set1.size();
72+
final int size2 = set2.size();
7473

7574
if (size0 == 0 || size1 == 0 || size2 == 0) {
7675
return DefaultImmutableSet.nil();
7776
}
7877

78+
// Intersect the two smaller sets first (smaller intermediate result), leaving the largest
79+
// set for the outer intersection. The three-way result is order-independent, so this only
80+
// affects work, not the outcome.
7981
if (size0 < size2 && size1 < size2) {
8082
return intersect(intersect(set0, set1), set2);
8183
} else if (size0 < size1 && size2 < size1) {

0 commit comments

Comments
 (0)