From 7885fbe789e4327ae7135d026b5e58ed1708315b Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Tue, 7 Jul 2026 21:24:31 +0200 Subject: [PATCH 01/10] Step 2 (checkpoint): FOLStrategy costs via CostBand + FOLCost Introduce CostBand (ncore, the shared cross-theory cost-band vocabulary) and FOLCost (FOL-theory-internal ordering constants), and convert FOLStrategy's cost literals to them - byte-identical by construction. Deferred: RAP + Model-Search node-for-node verification (holding proof runs for a benchmark). Checkpoint before the Integer conversion so the two theory changes stay separable for bisection if verification later flags a mismatch. Created with AI tooling support # Conflicts: # key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java --- .../de/uka/ilkd/key/strategy/FOLCost.java | 63 +++++++++ .../de/uka/ilkd/key/strategy/FOLStrategy.java | 128 ++++++++++-------- .../prover/strategy/costbased/CostBand.java | 98 ++++++++++++++ 3 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java create mode 100644 key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java new file mode 100644 index 00000000000..af0c819f56d --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java @@ -0,0 +1,63 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * FOL-theory-internal ordering costs, used by {@link FOLStrategy}. These are the fine, within-FOL + * ordering values that are reused across FOLStrategy's normalisation / splitting / + * equation methods and therefore deserve a name of their own; genuinely one-off nudges are written + * directly as {@code CostBand..at(delta)} at the call site instead. + * + *

+ * Values are byte-identical to the literals they replace. They position FOL rules within the shared + * {@link org.key_project.prover.strategy.costbased.CostBand} ladder, so changing one shifts the + * cross-theory search — verify with a full runAllProofs and a Model-Search node-for-node comparison + * (as for {@code CostBand}). + *

+ */ +final class FOLCost { + private FOLCost() {} + + /** + * Base cost of applying an equation (applyEq / apply_equations). Deliberately its own band — + * not {@code EXECUTE}, which is reserved for symbolic execution. + */ + static final long APPLY_EQUATIONS = -4000; + + /** + * Distribution / swapping of quantifiers ({@code distrQuantifier}, {@code swapQuantifiers}). + */ + static final long QUANTIFIER_DISTRIBUTION = -300; + + /** + * Restructuring of CNF clauses by associativity / distribution ({@code cnf_orAssoc}, + * {@code cnf_andAssoc}, {@code cnf_dist}); the small {@code ± delta} at the call sites orders + * these among themselves. + */ + static final long CNF_RESTRUCTURE = -35; + + /** + * Ordering of formula-normalisation steps in the boolean/CNF area, at one shared priority: + * {@code conjNormalForm} (CNF conversion) and {@code apply_equations_andOr} (contextual + * equation application inside and/or). + */ + static final long CNF_ORDERING = -150; + + /** + * Defer {@code replace_known_right} when its target is in the consequent of an implication or + * inside an equivalence, so the connective is decomposed first (which makes the antecedent + * available as a known-true fact). Deliberately not applied to + * {@code replace_known_left}, whose antecedent-true facts stay valid across the decomposition. + */ + static final long REPLACE_KNOWN_UNDER_CONNECTIVE = 100; + + /** Standard cost of a direct cut ({@code cut_direct}). */ + static final long CUT_DIRECT_STANDARD = 100; + + /** Preferred direction of the {@code pullOutQuantifier*} rules. */ + static final long PULL_OUT_QUANTIFIER = -20; + + /** Dispreferred direction of the {@code pullOutQuantifier*} rules. */ + static final long PULL_OUT_QUANTIFIER_REVERSE = -40; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java index c4cda4a412e..a8bccf5aebc 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java @@ -31,6 +31,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.RuleAppCost; import org.key_project.prover.strategy.costbased.TopRuleAppCost; @@ -82,7 +83,7 @@ public FOLStrategy(Proof proof, StrategyProperties strategyProperties) { private Feature setUpGlobalF(RuleSetDispatchFeature d) { final Feature oneStepSimplificationF = - oneStepSimplificationFeature(longConst(-11000)); + oneStepSimplificationFeature(longConst(CostBand.REWRITE.cost())); return add(d, oneStepSimplificationF); } @@ -95,20 +96,20 @@ private Feature oneStepSimplificationFeature(Feature cost) { private RuleSetDispatchFeature setupCostComputationF() { final RuleSetDispatchFeature d = new RuleSetDispatchFeature(); - bindRuleSet(d, "closure", -15000); - bindRuleSet(d, "alpha", -7000); - bindRuleSet(d, "delta", -6000); - bindRuleSet(d, "simplify_boolean", -200); + bindRuleSet(d, "closure", CostBand.CLOSE.cost()); + bindRuleSet(d, "alpha", CostBand.DECOMPOSE.cost()); + bindRuleSet(d, "delta", CostBand.TYPE.cost()); + bindRuleSet(d, "simplify_boolean", CostBand.PREFER.at(300)); final Feature findDepthFeature = FindDepthFeature.getInstance(); bindRuleSet(d, "concrete", - add(longConst(-11000), + add(longConst(CostBand.REWRITE.cost()), ScaleFeature.createScaled(findDepthFeature, 10.0))); - bindRuleSet(d, "simplify", -4500); - bindRuleSet(d, "simplify_enlarging", -2000); - bindRuleSet(d, "simplify_ENLARGING", -1900); + bindRuleSet(d, "simplify", CostBand.SIMPLIFY.cost()); + bindRuleSet(d, "simplify_enlarging", CostBand.ENLARGE.cost()); + bindRuleSet(d, "simplify_ENLARGING", CostBand.ENLARGE.at(100)); // always give infinite cost to obsolete rules bindRuleSet(d, "obsolete", inftyConst()); @@ -120,46 +121,54 @@ private RuleSetDispatchFeature setupCostComputationF() { not(contains(AssumptionProjection.create(0), FocusProjection.INSTANCE)))); bindRuleSet(d, "update_elim", - add(longConst(-8000), ScaleFeature.createScaled(findDepthFeature, 10.0))); + add(longConst(CostBand.ELIMINATE.cost()), + ScaleFeature.createScaled(findDepthFeature, 10.0))); bindRuleSet(d, "update_apply_on_update", - add(longConst(-7000), ScaleFeature.createScaled(findDepthFeature, 10.0))); - bindRuleSet(d, "update_join", -4600); - bindRuleSet(d, "update_apply", -4500); + add(longConst(CostBand.DECOMPOSE.cost()), + ScaleFeature.createScaled(findDepthFeature, 10.0))); + bindRuleSet(d, "update_join", CostBand.SIMPLIFY.at(-100)); + bindRuleSet(d, "update_apply", CostBand.SIMPLIFY.cost()); setupSplitting(d); bindRuleSet(d, "gamma", add(not(isInstantiated("t")), - ifZero(allowQuantifierSplitting(), longConst(0), longConst(50)))); + ifZero(allowQuantifierSplitting(), longConst(CostBand.DEFAULT.cost()), + longConst(CostBand.DEFAULT.at(50))))); bindRuleSet(d, "gamma_destructive", inftyConst()); - bindRuleSet(d, "triggered", add(not(isTriggerVariableInstantiated()), longConst(500))); + bindRuleSet(d, "triggered", + add(not(isTriggerVariableInstantiated()), longConst(CostBand.DEFER.cost()))); bindRuleSet(d, "comprehension_split", add(applyTF(FocusFormulaProjection.INSTANCE, ff.notContainsExecutable), - ifZero(allowQuantifierSplitting(), longConst(2500), longConst(5000)))); + ifZero(allowQuantifierSplitting(), longConst(CostBand.DEFER.at(2000)), + longConst(CostBand.DEFER.at(4500))))); setupReplaceKnown(d); setupEquationReasoning(d); bindRuleSet(d, "order_terms", - add(termSmallerThan("commEqLeft", "commEqRight"), longConst(-5000))); + add(termSmallerThan("commEqLeft", "commEqRight"), + longConst(CostBand.NORMALIZE.cost()))); bindRuleSet(d, "simplify_instanceof_static", - add(EqNonDuplicateAppFeature.INSTANCE, longConst(-500))); + add(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.PREFER.cost()))); - bindRuleSet(d, "evaluate_instanceof", longConst(-500)); + bindRuleSet(d, "evaluate_instanceof", longConst(CostBand.PREFER.cost())); bindRuleSet(d, "instanceof_to_exists", TopLevelFindFeature.ANTEC); bindRuleSet(d, "try_apply_subst", - add(EqNonDuplicateAppFeature.INSTANCE, longConst(-10000))); + add(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.SUBST.cost()))); // delete cast bindRuleSet(d, "cast_deletion", - ifZero(implicitCastNecessary(instOf("castedTerm")), longConst(-5000), inftyConst())); + ifZero(implicitCastNecessary(instOf("castedTerm")), + longConst(CostBand.NORMALIZE.cost()), + inftyConst())); - bindRuleSet(d, "type_hierarchy_def", -6500); + bindRuleSet(d, "type_hierarchy_def", CostBand.TYPE.at(-500)); bindRuleSet(d, "cut", not(isInstantiated("cutFormula"))); @@ -280,28 +289,28 @@ public Name name() { protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "negationNormalForm", add(BelowBinderFeature.getInstance(), - longConst(-500), + longConst(CostBand.PREFER.cost()), ScaleFeature.createScaled(FindDepthFeature.getInstance(), 10.0))); bindRuleSet(d, "moveQuantToLeft", - add(quantifiersMightSplit() ? longConst(0) + add(quantifiersMightSplit() ? longConst(CostBand.DEFAULT.cost()) : applyTF(FocusFormulaProjection.INSTANCE, ff.quantifiedPureLitConjDisj), - longConst(-550))); + longConst(CostBand.PREFER.at(-50)))); bindRuleSet(d, "conjNormalForm", ifZero( add(or(FocusInAntecFeature.getInstance(), notBelowQuantifier()), NotInScopeOfModalityFeature.INSTANCE), - add(longConst(-150), + add(longConst(FOLCost.CNF_ORDERING), ScaleFeature.createScaled(FindDepthFeature.getInstance(), 20)), inftyConst())); - bindRuleSet(d, "setEqualityBlastingRight", longConst(-100)); + bindRuleSet(d, "setEqualityBlastingRight", longConst(CostBand.DEFAULT.at(-100))); - bindRuleSet(d, "elimQuantifier", -1000); - bindRuleSet(d, "elimQuantifierWithCast", 50); + bindRuleSet(d, "elimQuantifier", CostBand.PREFER.at(-500)); + bindRuleSet(d, "elimQuantifierWithCast", CostBand.DEFAULT.at(50)); final TermBuffer left = new TermBuffer(); final TermBuffer right = new TermBuffer(); @@ -309,7 +318,7 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { add(let(left, instOf("applyEqLeft"), let(right, instOf("applyEqRight"), TermSmallerThanFeature.create(right, left))), - longConst(-150))); + longConst(FOLCost.CNF_ORDERING))); bindRuleSet(d, "distrQuantifier", add(or( @@ -321,28 +330,30 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { ifZero(FocusInAntecFeature.getInstance(), applyTF(FocusProjection.INSTANCE, sub(ff.andF)), applyTF(FocusProjection.INSTANCE, sub(ff.orF))))), - longConst(-300))); + longConst(FOLCost.QUANTIFIER_DISTRIBUTION))); bindRuleSet(d, "swapQuantifiers", add(applyTF(FocusProjection.INSTANCE, add(ff.quantifiedClauseSet, EliminableQuantifierTF.INSTANCE, sub(not(EliminableQuantifierTF.INSTANCE)))), - longConst(-300))); + longConst(FOLCost.QUANTIFIER_DISTRIBUTION))); // category "conjunctive normal form" bindRuleSet(d, "cnf_orAssoc", SumFeature.createSum(applyTF("assoc0", ff.clause), - applyTF("assoc1", ff.clause), applyTF("assoc2", ff.literal), longConst(-80))); + applyTF("assoc1", ff.clause), applyTF("assoc2", ff.literal), + longConst(FOLCost.CNF_RESTRUCTURE - 45))); bindRuleSet(d, "cnf_andAssoc", SumFeature.createSum(applyTF("assoc0", ff.clauseSet), - applyTF("assoc1", ff.clauseSet), applyTF("assoc2", ff.clause), longConst(-10))); + applyTF("assoc1", ff.clauseSet), applyTF("assoc2", ff.clause), + longConst(FOLCost.CNF_RESTRUCTURE + 25))); bindRuleSet(d, "cnf_dist", SumFeature.createSum(applyTF("distRight0", ff.clauseSet), applyTF("distRight1", ff.clauseSet), ifZero(applyTF("distLeft", ff.clause), - longConst(-15), applyTF("distLeft", ff.clauseSet)), - longConst(-35))); + longConst(FOLCost.CNF_RESTRUCTURE + 20), applyTF("distLeft", ff.clauseSet)), + longConst(FOLCost.CNF_RESTRUCTURE))); final TermBuffer superFor = new TermBuffer(); final Feature onlyBelowQuanAndOr = @@ -364,13 +375,16 @@ EliminableQuantifierTF.INSTANCE, sub(not(EliminableQuantifierTF.INSTANCE)))), add(isBelow(OperatorClassTF.create(Quantifier.class)), onlyBelowQuanAndOr, applyTF( FocusProjection.create(0), sub(ff.quantifiedClauseSet, ff.quantifiedClauseSet))); - bindRuleSet(d, "pullOutQuantifierUnifying", -20); + bindRuleSet(d, "pullOutQuantifierUnifying", FOLCost.PULL_OUT_QUANTIFIER); bindRuleSet(d, "pullOutQuantifierAll", add(pullOutQuantifierAllowed, - ifZero(FocusInAntecFeature.getInstance(), longConst(-20), longConst(-40)))); + ifZero(FocusInAntecFeature.getInstance(), longConst(FOLCost.PULL_OUT_QUANTIFIER), + longConst(FOLCost.PULL_OUT_QUANTIFIER_REVERSE)))); bindRuleSet(d, "pullOutQuantifierEx", add(pullOutQuantifierAllowed, - ifZero(FocusInAntecFeature.getInstance(), longConst(-40), longConst(-20)))); + ifZero(FocusInAntecFeature.getInstance(), + longConst(FOLCost.PULL_OUT_QUANTIFIER_REVERSE), + longConst(FOLCost.PULL_OUT_QUANTIFIER)))); } // ////////////////////////////////////////////////////////////////////////// @@ -396,17 +410,18 @@ private void setupQuantifierInstantiation(RuleSetDispatchFeature d) { : ff.notContainsExecutable)), forEach(varInst, HeuristicInstantiation.forOption(classicTriggers()), add(instantiate("t", varInst), - add(branchPrediction, longConst(10), + add(branchPrediction, + longConst(CostBand.DEFAULT.at(10), // orders candidates of one predicted-cost band by their // connection to the sequent instead of formula position InstantiationTieBreakFeature.create(varInst, - triggersOption())))))); + triggersOption()))))))); final TermBuffer splitInst = new TermBuffer(); bindRuleSet(d, "triggered", SumFeature.createSum(forEach(splitInst, TriggeredInstantiations.create(true), - add(instantiateTriggeredVariable(splitInst), longConst(500))), - longConst(1500))); + add(instantiateTriggeredVariable(splitInst), longConst(CostBand.DEFER.cost()))), + longConst(CostBand.DEFER.at(1000)))); } else { bindRuleSet(d, "gamma", inftyConst()); @@ -423,7 +438,7 @@ private void setupQuantifierInstantiationApproval(RuleSetDispatchFeature d) { not(eq(instOf("t"), varInst)))), InstantiationCostScalerFeature.create( InstantiationCost.create(instOf("t"), classicTriggers()), - longConst(0)))); + longConst(CostBand.DEFAULT.cost())))); final TermBuffer splitInst = new TermBuffer(); bindRuleSet(d, "triggered", @@ -453,15 +468,18 @@ protected Feature notBelowQuantifier() { private void setupReplaceKnown(RuleSetDispatchFeature d) { final Feature commonF = add(ifZero(MatchedAssumesFeature.INSTANCE, DiffFindAndIfFeature.INSTANCE), - longConst(-5000), + longConst(CostBand.NORMALIZE.cost()), add(DiffFindAndReplacewithFeature.INSTANCE, ScaleFeature.createScaled(CountMaxDPathFeature.INSTANCE, 10.0))); bindRuleSet(d, "replace_known_left", commonF); bindRuleSet(d, "replace_known_right", - add(commonF, ifZero(directlyBelowSymbolAtIndex(Junctor.IMP, 1), longConst(100), - ifZero(directlyBelowSymbolAtIndex(Equality.EQV, -1), longConst(100))))); + add(commonF, + ifZero(directlyBelowSymbolAtIndex(Junctor.IMP, 1), + longConst(FOLCost.REPLACE_KNOWN_UNDER_CONNECTIVE), + ifZero(directlyBelowSymbolAtIndex(Equality.EQV, -1), + longConst(FOLCost.REPLACE_KNOWN_UNDER_CONNECTIVE))))); } // ////////////////////////////////////////////////////////////////////////// @@ -478,9 +496,10 @@ protected void setupSplitting(RuleSetDispatchFeature d) { sum(subFor, AllowedCutPositionsGenerator.INSTANCE, not(applyTF(subFor, ff.cutAllowed))); bindRuleSet(d, "beta", SumFeature.createSum(noCutsAllowed, - ifZero(PurePosDPathFeature.INSTANCE, longConst(-200)), + ifZero(PurePosDPathFeature.INSTANCE, longConst(CostBand.PREFER.at(300))), ScaleFeature.createScaled(CountPosDPathFeature.INSTANCE, -3.0), - ScaleFeature.createScaled(CountMaxDPathFeature.INSTANCE, 10.0), longConst(20))); + ScaleFeature.createScaled(CountMaxDPathFeature.INSTANCE, 10.0), + longConst(CostBand.DEFAULT.at(20)))); TermBuffer superF = new TermBuffer(); final ProjectionToTerm splitCondition = sub(FocusProjection.INSTANCE, 0); bindRuleSet(d, "split_cond", add(// do not split over formulas containing auxiliary @@ -496,7 +515,7 @@ protected void setupSplitting(RuleSetDispatchFeature d) { sum(superF, SuperTermGenerator.upwards(any(), getServices()), applyTF(superF, not(ff.elemUpdate))), ifZero(applyTF(FocusProjection.INSTANCE, ContainsExecutableCodeTermFeature.PROGRAMS), - longConst(-100), longConst(25)))); + longConst(CostBand.DEFAULT.at(-100)), longConst(CostBand.DEFAULT.at(25))))); ProjectionToTerm cutFormula = instOf("cutFormula"); Feature countOccurrencesInSeq = ScaleFeature.createAffine(countOccurrences(cutFormula), -10, 10); @@ -512,13 +531,14 @@ protected void setupSplitting(RuleSetDispatchFeature d) { // auxiliary variables rec(any(), not(selectSkolemConstantTermFeature())))), countOccurrencesInSeq, // standard costs - longConst(100)), + longConst(FOLCost.CUT_DIRECT_STANDARD)), SumFeature // check for cuts below quantifiers .createSum(applyTF(cutFormula, ff.cutAllowedBelowQuantifier), applyTF(FocusFormulaProjection.INSTANCE, ff.quantifiedClauseSet), - ifZero(allowQuantifierSplitting(), longConst(0), - longConst(100)))))); + ifZero(allowQuantifierSplitting(), + longConst(CostBand.DEFAULT.cost()), + longConst(FOLCost.CUT_DIRECT_STANDARD)))))); } private void setupSplittingApproval(RuleSetDispatchFeature d) { @@ -567,7 +587,7 @@ private void setupEquationReasoning(RuleSetDispatchFeature d) { let(left, sub(equation, 0), let(right, sub(equation, 1), TermSmallerThanFeature.create(right, left))))))), - longConst(-4000))); + longConst(FOLCost.APPLY_EQUATIONS))); bindRuleSet(d, "insert_eq_nonrigid", applyTF(FocusProjection.create(0), IsNonRigidTermFeature.INSTANCE)); diff --git a/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java new file mode 100644 index 00000000000..dfbe711c025 --- /dev/null +++ b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java @@ -0,0 +1,98 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package org.key_project.prover.strategy.costbased; + +/** + * The shared, cross-theory priority ladder for the cost-based strategies. + * + *

+ * Rule costs from every component strategy (theory) are summed per rule and the globally + * cheapest applicable rule is applied, so a band's absolute value fixes its priority + * against every other theory — in practice the theories interleave at almost every + * step. A band is therefore combination-relevant. The fine ordering of rules within a + * band is expressed as {@code TIER.at(delta)} with a small delta; ordering that is internal to a + * single theory lives in that theory (e.g. {@code IntegerCost} for the integer (in)equality and + * division solver steps), not here. + *

+ * + *

+ * Care when changing: altering a band's value, or its order relative to other bands, + * shifts the cross-theory search of all proofs — always re-verify with a full + * runAllProofs and a Model-Search node-for-node comparison. Respect the hard ordering + * constraints noted on individual bands (notably {@link #BLOCK_CONTRACT}). + *

+ */ +public enum CostBand { + /** + * Apply a block/loop contract instead of executing the block. MUST stay more eager (smaller) + * than {@link #REWRITE} and every symbolic-execution program rule, otherwise the block starts + * to execute instead of being contracted. Value is the current sentinel; step 3 normalizes it + * to a modest value between {@link #CLOSE} and {@link #REWRITE}. + */ + BLOCK_CONTRACT(Long.MIN_VALUE), + /** + * Apply a loop invariant instead of unrolling. Only needs to beat loop-unrolling / method + * expansion when enabled. (Currently above {@link #CLOSE}; step 3 flips it below CLOSE.) + */ + LOOP_INVARIANT(-20_000), + /** + * Close the goal. Most eager of the ordinary bands: eager closure is completeness-neutral + * (no free-variable calculus), so closing may always take precedence. + */ + CLOSE(-15_000), + /** One-Step-Simplification and decidable ground rewrites (rule set {@code concrete}). */ + REWRITE(-11_000), + /** Force a pending substitution / eager equality ({@code try_apply_subst}). */ + SUBST(-10_000), + /** Eliminate updates and literals. */ + ELIMINATE(-8_000), + /** Non-splitting sequent decomposition (alpha rules, update-apply-on-update). */ + DECOMPOSE(-7_000), + /** Type reasoning (delta rules, type hierarchy). */ + TYPE(-6_000), + /** Canonicalize / order / commute terms. */ + NORMALIZE(-5_000), + /** Safe, size-reducing definitional simplification and symbolic-execution steps. */ + SIMPLIFY(-4_500), + /** Symbolic-execution program step / state merge. */ + EXECUTE(-4_000), + /** Solve direct (in)equations; apply query axioms. */ + SOLVE(-3_000), + /** Useful but size-increasing simplification (e.g. comprehension / map unfolding). */ + ENLARGE(-2_000), + /** Minor local structural preference. */ + PREFER(-500), + /** + * The default cost. A taclet whose rule sets carry no explicit feature in a strategy already + * contributes 0 (the dispatcher sums only the bound rule sets), so binding a rule set to + * DEFAULT is a deliberate "no strategic bias — apply in due (age) order", cost-identical to + * leaving it unbound. + */ + DEFAULT(0), + /** Defer: lazy definitional unfolding, applied only when needed. */ + DEFER(500), + /** Strongly defer. */ + DEFER_STRONG(10_000), + /** Finite last resort — reachable, but only when nothing else applies (soft infinity). */ + LAST_RESORT(1_000_000); + + private final long base; + + CostBand(long base) { + this.base = base; + } + + /** The band's cost. */ + public long cost() { + return base; + } + + /** + * The band's cost shifted by a small theory-internal ordering delta. Use only for fine + * ordering within the band; larger, cross-theory steps deserve their own band. + */ + public long at(long delta) { + return base + delta; + } +} From 0bc232d90a5cabf0b5fa08e0450f364837b10b4c Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Tue, 7 Jul 2026 22:59:13 +0200 Subject: [PATCH 02/10] Step 2 (checkpoint): IntegerStrategy costs via CostBand + IntegerArithmeticCosts Convert IntegerStrategy's ~50 cost literals to the shared CostBand ladder (anchors) and to per-sub-theory holders in IntegerArithmeticCosts (PolynomialCost / LinearEquationCost / LinearInequationCost / NonlinearArithmeticCost / DivModCost), grouped to anticipate a future split of the strategy. Byte-identical by construction; the old IN_EQ_SIMP_NON_LIN_COST / POLY_DIVISION_COST fields move into the holders. Deferred: RAP + Model-Search node-for-node verification (holding proof runs for a benchmark). Checkpoint after FOL (21a475ebb9) so the two theory conversions stay bisectable. Created with AI tooling support --- .../key/strategy/IntegerArithmeticCosts.java | 125 ++++++++++++++++ .../ilkd/key/strategy/IntegerStrategy.java | 139 ++++++++++-------- 2 files changed, 200 insertions(+), 64 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java new file mode 100644 index 00000000000..1c34f5b58b0 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java @@ -0,0 +1,125 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/* + * Theory-internal cost constants for the integer arithmetic strategy, grouped by the arithmetic + * sub-theory they belong to. The grouping deliberately anticipates a future split of + * IntegerStrategy into separate sub-strategies (polynomial / linear / non-linear / div-mod): each + * holder is meant to move with its sub-strategy. Cross-theory levels stay in + * {@link org.key_project.prover.strategy.costbased.CostBand}; only the arithmetic-internal ordering + * lives here. All values are byte-identical to the literals they replace. + */ + +/** Polynomial normal-form canonicalisation (Buchberger normalisation) — the "basic" substrate. */ +final class PolynomialCost { + private PolynomialCost() {} + + /** elimSubNeg, homo, pullOutFactor, elimOneLeft/Right. */ + static final long EXPAND = -120; + static final long MUL_ORDER = -100; + static final long MUL_ASSOC = -80; + static final long ADD_ORDER = -60; + static final long ADD_ASSOC = -10; + /** polySimp_dist base; the distLeft sub-case is {@code DISTRIBUTE + 20}. */ + static final long DISTRIBUTE = -35; + static final long PULLOUT_GCD = -2250; +} + + +/** + * Polynomial equation solving (Gaussian / Gröbner). These {@code polySimp_*} rule sets + * live + * here rather than in {@link PolynomialCost} on purpose: they do not just canonicalise a term, they + * derive from and apply equations, so they belong to the "linear" (solving) layer next to + * {@link LinearInequationCost}. + */ +final class LinearEquationCost { + private LinearEquationCost() {} + + /** + * The general {@code apply_equations} rule set (applyEq / applyEqReverse), used to reduce + * polynomials. Distinct from {@link #APPLY_EQ}, which is the monomial-specialised polySimp + * variant. Its own band, not {@code EXECUTE} (reserved for symbolic execution). + *

+ * Step-3 idea: test whether the specialised {@link #APPLY_EQ} is needed at all, or should just + * be a small delta that prefers these original {@code apply_equations} rules. + *

+ */ + static final long APPLY_EQUATIONS = -4000; + static final long APPLY_EQ_AND_OR = -150; + /** polySimp_balance, polySimp_normalise. */ + static final long BALANCE = -30; + /** + * polySimp_applyEq — the monomial-coefficient-specialised equation application. The rigid + * variant polySimp_applyEqRigid is written {@code APPLY_EQ + 1} at the call site; that +1 is + * only an (uninteresting) tie-break between the two rules, a step-3 candidate to flatten to a + * single cost. + */ + static final long APPLY_EQ = 1; +} + + +/** Linear inequation solving — the Omega / Fourier-Motzkin machinery ({@code inEqSimp_*}). */ +final class LinearInequationCost { + private LinearInequationCost() {} + + static final long PROPAGATION = -2400; + static final long SATURATE = -1900; + /** General GCD normalisation of inequations — confluent, hence safe to apply eagerly. */ + static final long PULLOUT_GCD_CONFLUENT = -2150; + static final long FOR_NORMALISATION = -1100; + static final long MOVE_LEFT = -90; + static final long MAKE_NON_STRICT = -80; + static final long CONTRAD = -60; + static final long COMMUTE = -40; + static final long STRENGTHEN = -30; + static final long ANTISYMM = -20; + /** + * Faster antecedent-specialised GCD pull-out, but not confluent (the result can depend + * on application order), hence pinned near baseline so it fires only opportunistically — in + * contrast to the eager {@link #PULLOUT_GCD_CONFLUENT}. + */ + static final long PULLOUT_GCD_ANTEC_NONCONFLUENT = -10; +} + + +/** Non-linear arithmetic — cross-multiplication, sign cases, root inferences (Model Search). */ +final class NonlinearArithmeticCost { + private NonlinearArithmeticCost() {} + + /** + * inEqSimp_nonLin (cross-multiplication) base; case-distinction offsets are + * {@code MULTIPLY + n}. + */ + static final long MULTIPLY = 1000; + /** + * Divide an inequation by a factor of known sign/bound to bound the quotient (the + * {@code divide_inEq*} taclets) — the inverse of cross-multiplication. Kept clearly more eager + * than {@link #MULTIPLY}, since dividing/reducing is safer and more productive than + * multiplying. + */ + static final long DIVIDE_INEQUATION = -1400; + static final long SPLIT_EQ = -100; +} + + +/** Division / modulo and DefOps expansion ({@code polyDivision}, {@code defOps_*}). */ +final class DivModCost { + private DivModCost() {} + + static final long POLY_DIVISION = -2250; + static final long EXPAND_MODULO = -600; + /** extra cost for defOps_div/jdiv applied below a modality. */ + static final long BELOW_MODALITY = 200; + static final long EXPAND_RANGES = -8000; + static final long MOD_HOMO_EQ = -5000; + static final long EXPAND_NUMERIC_OP = -500; + /** defOps_jdiv_inline for a literal numerator (eager, concrete division). */ + static final long INLINE = -5000; + /** literal-only division / modulo (defOps_mod, off-mode jdiv_inline). */ + static final long MOD = -4000; + /** modulo expansion for a polynomial (non-literal) modulus (defOps_mod). */ + static final long MOD_EXPAND = -3500; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java index 4896a823ea5..02a3f9ef118 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java @@ -26,6 +26,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.RuleAppCost; import org.key_project.prover.strategy.costbased.TopRuleAppCost; @@ -205,7 +206,8 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "order_terms", add(applyTF("commEqRight", tf.monomial), applyTF("commEqLeft", tf.polynomial), - monSmallerThan("commEqLeft", "commEqRight", numbers), longConst(-5000))); + monSmallerThan("commEqLeft", "commEqRight", numbers), + longConst(CostBand.NORMALIZE.cost()))); final TermBuffer equation = new TermBuffer(); final TermBuffer left = new TermBuffer(); @@ -228,7 +230,7 @@ private RuleSetDispatchFeature setupCostComputationF() { applyTF(right, tf.polynomial), MonomialsSmallerThanFeature.create(right, left, numbers)))))))), - longConst(-4000))); + longConst(LinearEquationCost.APPLY_EQUATIONS))); final TermBuffer l = new TermBuffer(); final TermBuffer r = new TermBuffer(); @@ -237,7 +239,7 @@ private RuleSetDispatchFeature setupCostComputationF() { let(r, instOf("applyEqRight"), add(applyTF(l, tf.nonNegOrNonCoeffMonomial), applyTF(r, tf.polynomial), MonomialsSmallerThanFeature.create(r, l, numbers)))), - longConst(-150))); + longConst(LinearEquationCost.APPLY_EQ_AND_OR))); // For taclets that need instantiation, but where the instantiation is // deterministic and does not have to be repeated at a later point, we @@ -270,79 +272,80 @@ private void setupArithPrimaryCategories(RuleSetDispatchFeature d) { // Buchberger's algorithmus for handling polynomial equations over // the integers - bindRuleSet(d, "polySimp_expand", -4500); - bindRuleSet(d, "polySimp_directEquations", -3000); - bindRuleSet(d, "polySimp_pullOutGcd", -2250); - bindRuleSet(d, "polySimp_leftNonUnit", -2000); - bindRuleSet(d, "polySimp_saturate", 0); + bindRuleSet(d, "polySimp_expand", CostBand.SIMPLIFY.cost()); + bindRuleSet(d, "polySimp_directEquations", CostBand.SOLVE.cost()); + bindRuleSet(d, "polySimp_pullOutGcd", PolynomialCost.PULLOUT_GCD); + bindRuleSet(d, "polySimp_leftNonUnit", CostBand.ENLARGE.cost()); + bindRuleSet(d, "polySimp_saturate", CostBand.DEFAULT.cost()); // Omega test for handling linear arithmetic and inequalities over the // integers; cross-multiplication + case distinctions for nonlinear // inequalities - bindRuleSet(d, "inEqSimp_expand", -4400); - bindRuleSet(d, "inEqSimp_directInEquations", -2900); - bindRuleSet(d, "inEqSimp_propagation", -2400); - bindRuleSet(d, "inEqSimp_pullOutGcd", -2150); - bindRuleSet(d, "inEqSimp_saturate", -1900); - bindRuleSet(d, "inEqSimp_forNormalisation", -1100); - bindRuleSet(d, "inEqSimp_special_nonLin", -1400); + bindRuleSet(d, "inEqSimp_expand", CostBand.SIMPLIFY.at(100)); + bindRuleSet(d, "inEqSimp_directInEquations", CostBand.SOLVE.at(100)); + bindRuleSet(d, "inEqSimp_propagation", LinearInequationCost.PROPAGATION); + bindRuleSet(d, "inEqSimp_pullOutGcd", LinearInequationCost.PULLOUT_GCD_CONFLUENT); + bindRuleSet(d, "inEqSimp_saturate", LinearInequationCost.SATURATE); + bindRuleSet(d, "inEqSimp_forNormalisation", LinearInequationCost.FOR_NORMALISATION); + bindRuleSet(d, "inEqSimp_special_nonLin", NonlinearArithmeticCost.DIVIDE_INEQUATION); if (arith == ArithTreatment.MODEL_SEARCH) { - bindRuleSet(d, "inEqSimp_nonLin", IN_EQ_SIMP_NON_LIN_COST); + bindRuleSet(d, "inEqSimp_nonLin", NonlinearArithmeticCost.MULTIPLY); } else { bindRuleSet(d, "inEqSimp_nonLin", inftyConst()); } - bindRuleSet(d, "polyDivision", POLY_DIVISION_COST); + bindRuleSet(d, "polyDivision", DivModCost.POLY_DIVISION); } private void setupPolySimp(RuleSetDispatchFeature d, IntegerLDT numbers) { // category "expansion" (normalising polynomial terms) - bindRuleSet(d, "polySimp_elimSubNeg", longConst(-120)); + bindRuleSet(d, "polySimp_elimSubNeg", longConst(PolynomialCost.EXPAND)); bindRuleSet(d, "polySimp_homo", add(applyTF("homoRight", add(not(tf.zeroLiteral), tf.polynomial)), or(applyTF("homoLeft", or(tf.addF, tf.negMonomial)), not(monSmallerThan("homoRight", "homoLeft", numbers))), - longConst(-120))); + longConst(PolynomialCost.EXPAND))); bindRuleSet(d, "polySimp_pullOutFactor", add(applyTFNonStrict("pullOutLeft", tf.literal), - applyTFNonStrict("pullOutRight", tf.literal), longConst(-120))); + applyTFNonStrict("pullOutRight", tf.literal), longConst(PolynomialCost.EXPAND))); - bindRuleSet(d, "polySimp_elimOneLeft", -120); + bindRuleSet(d, "polySimp_elimOneLeft", PolynomialCost.EXPAND); - bindRuleSet(d, "polySimp_elimOneRight", -120); + bindRuleSet(d, "polySimp_elimOneRight", PolynomialCost.EXPAND); bindRuleSet(d, "polySimp_mulOrder", add(applyTF("commRight", tf.monomial), or( applyTF("commLeft", tf.addF), add(applyTF("commLeft", tf.atom), atomSmallerThan("commLeft", "commRight", numbers))), - longConst(-100))); + longConst(PolynomialCost.MUL_ORDER))); bindRuleSet(d, "polySimp_mulAssoc", SumFeature.createSum(applyTF("mulAssocMono0", tf.monomial), applyTF("mulAssocMono1", tf.monomial), applyTF("mulAssocAtom", tf.atom), - longConst(-80))); + longConst(PolynomialCost.MUL_ASSOC))); bindRuleSet(d, "polySimp_addOrder", SumFeature.createSum(applyTF("commLeft", tf.monomial), applyTF("commRight", tf.polynomial), - monSmallerThan("commRight", "commLeft", numbers), longConst(-60))); + monSmallerThan("commRight", "commLeft", numbers), + longConst(PolynomialCost.ADD_ORDER))); bindRuleSet(d, "polySimp_addAssoc", SumFeature.createSum(applyTF("addAssocPoly0", tf.polynomial), applyTF("addAssocPoly1", tf.polynomial), applyTF("addAssocMono", tf.monomial), - longConst(-10))); + longConst(PolynomialCost.ADD_ASSOC))); bindRuleSet(d, "polySimp_dist", SumFeature.createSum(applyTF("distSummand0", tf.polynomial), applyTF("distSummand1", tf.polynomial), - ifZero(applyTF("distCoeff", tf.monomial), longConst(-15), + ifZero(applyTF("distCoeff", tf.monomial), longConst(PolynomialCost.DISTRIBUTE + 20), applyTF("distCoeff", tf.polynomial)), applyTF("distSummand0", tf.polynomial), - applyTF("distSummand1", tf.polynomial), longConst(-35))); + applyTF("distSummand1", tf.polynomial), longConst(PolynomialCost.DISTRIBUTE))); // category "direct equations" @@ -355,10 +358,10 @@ private void setupPolySimp(RuleSetDispatchFeature d, IntegerLDT numbers) { ifZero(isInstantiated("sepNegMono"), add(applyTF("sepNegMono", tf.negMonomial), monSmallerThan("sepResidue", "sepNegMono", numbers))), - longConst(-30))); + longConst(LinearEquationCost.BALANCE))); bindRuleSet(d, "polySimp_normalise", add(applyTF("invertRight", tf.zeroLiteral), - applyTF("invertLeft", tf.negMonomial), longConst(-30))); + applyTF("invertLeft", tf.negMonomial), longConst(LinearEquationCost.BALANCE))); // application of equations: some specialised rules that handle // monomials and their coefficients properly @@ -381,13 +384,15 @@ private void setupPolySimp(RuleSetDispatchFeature d, IntegerLDT numbers) { ifZero(MatchedAssumesFeature.INSTANCE, let(focus, FocusProjection.create(0), let(eqLeft, sub(AssumptionProjection.create(0), 0), validEqApplication)))); - bindRuleSet(d, "polySimp_applyEq", add(eqMonomialFeature, longConst(1))); + bindRuleSet(d, "polySimp_applyEq", + add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ))); - bindRuleSet(d, "polySimp_applyEqRigid", add(eqMonomialFeature, longConst(2))); + bindRuleSet(d, "polySimp_applyEqRigid", + add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ + 1))); // bindRuleSet(d, "defOps_expandModulo", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(-600))); + add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(DivModCost.EXPAND_MODULO))); // category "saturate" @@ -435,8 +440,8 @@ private void setupDivModDivision(RuleSetDispatchFeature d) { // no possible division has been found so far add(NotInScopeOfModalityFeature.INSTANCE, ifZero(isReduciblePolyE, // try again later - longConst(-POLY_DIVISION_COST)))))), - longConst(100))); + longConst(-DivModCost.POLY_DIVISION)))))), + longConst(CostBand.DEFAULT.at(100)))); } @@ -527,14 +532,15 @@ private void setupInEqSimp(RuleSetDispatchFeature d, IntegerLDT numbers) { // category "expansion" (normalising inequations) - bindRuleSet(d, "inEqSimp_moveLeft", -90); + bindRuleSet(d, "inEqSimp_moveLeft", LinearInequationCost.MOVE_LEFT); - bindRuleSet(d, "inEqSimp_makeNonStrict", -80); + bindRuleSet(d, "inEqSimp_makeNonStrict", LinearInequationCost.MAKE_NON_STRICT); bindRuleSet(d, "inEqSimp_commute", SumFeature.createSum(applyTF("commRight", tf.monomial), applyTF("commLeft", tf.polynomial), - monSmallerThan("commLeft", "commRight", numbers), longConst(-40))); + monSmallerThan("commLeft", "commRight", numbers), + longConst(LinearInequationCost.COMMUTE))); // this is copied from "polySimp_homo" bindRuleSet(d, "inEqSimp_homo", @@ -559,7 +565,7 @@ private void setupInEqSimp(RuleSetDispatchFeature d, IntegerLDT numbers) { // category "saturate" - bindRuleSet(d, "inEqSimp_antiSymm", longConst(-20)); + bindRuleSet(d, "inEqSimp_antiSymm", longConst(LinearInequationCost.ANTISYMM)); bindRuleSet(d, "inEqSimp_exactShadow", SumFeature.createSum(applyTF("esLeft", tf.nonCoeffMonomial), @@ -590,9 +596,9 @@ private void setupInEqSimp(RuleSetDispatchFeature d, IntegerLDT numbers) { SumFeature.createSum(applyTF("contradRightSmaller", tf.polynomial), applyTF("contradRightBigger", tf.polynomial), PolynomialValuesCmpFeature .lt(instOf("contradRightSmaller"), instOf("contradRightBigger")))), - longConst(-60))); + longConst(LinearInequationCost.CONTRAD))); - bindRuleSet(d, "inEqSimp_strengthen", longConst(-30)); + bindRuleSet(d, "inEqSimp_strengthen", longConst(LinearInequationCost.STRENGTHEN)); bindRuleSet(d, "inEqSimp_subsumption", add(applyTF("subsumLeft", tf.monomial), @@ -609,10 +615,11 @@ private void setupInEqSimp(RuleSetDispatchFeature d, IntegerLDT numbers) { // category "handling of non-linear inequations" if (arith == ArithTreatment.MODEL_SEARCH) { - setupMultiplyInequations(d, longConst(IN_EQ_SIMP_NON_LIN_COST), longConst(100), - AT_COST); + setupMultiplyInequations(d, longConst(IN_EQ_SIMP_NON_LIN_COST), longConst(CostBand.DEFAULT.at(100), + AT_COST)); - bindRuleSet(d, "inEqSimp_split_eq", add(TopLevelFindFeature.SUCC, longConst(-100))); + bindRuleSet(d, "inEqSimp_split_eq", + add(TopLevelFindFeature.SUCC, longConst(NonlinearArithmeticCost.SPLIT_EQ))); bindRuleSet(d, "inEqSimp_signCases", not(isInstantiated("signCasesLeft"))); } else if (arith == ArithTreatment.DEF_OPS) { @@ -780,8 +787,9 @@ private void setupMultiplyInequations(RuleSetDispatchFeature d, Feature baseCost ifZero(MatchedAssumesFeature.INSTANCE, SumFeature.createSum( applyTF("multFacLeft", tf.nonNegMonomial), - ifZero(applyTF("multRight", tf.literal), longConst(-100)), - ifZero(applyTF("multFacRight", tf.literal), longConst(-100), + ifZero(applyTF("multRight", tf.literal), longConst(CostBand.DEFAULT.at(-100))), + ifZero(applyTF("multFacRight", tf.literal), + longConst(CostBand.DEFAULT.at(-100)), applyTF("multFacRight", tf.polynomial)), /* * ifZero ( applyTF ( "multRight", tf.literal ), longConst ( -100 ), applyTF ( @@ -800,9 +808,9 @@ private void setupMultiplyInequations(RuleSetDispatchFeature d, Feature baseCost ? ifZero(BranchMultiplicationCountFeature.atMost("multiply_2_inEq", BRANCH_MULT_CAP), longConst(0), notAllowedF) : longConst(0), - ifZero(exactlyBounded, longConst(0), + ifZero(exactlyBounded, longConst(CostBand.DEFAULT.cost()), onlyExactlyBounded ? notAllowedF - : ifZero(totallyBounded, longConst(100), notAllowedF)) + : ifZero(totallyBounded, longConst(CostBand.DEFAULT.at(100)), notAllowedF)) /* * ifZero ( partiallyBounded, longConst ( 400 ), notAllowedF ) ) ), */ @@ -839,7 +847,8 @@ private void setupInEqSimpInstantiationWithoutRetry(RuleSetDispatchFeature d) { setupPullOutGcd(d, "inEqSimp_pullOutGcd_geq", true); // more efficient (but not confluent) versions for the antecedent - bindRuleSet(d, "inEqSimp_pullOutGcd_antec", -10); + bindRuleSet(d, "inEqSimp_pullOutGcd_antec", + LinearInequationCost.PULLOUT_GCD_ANTEC_NONCONFLUENT); // category "handling of non-linear inequations" @@ -917,7 +926,7 @@ private void setupInEqCaseDistinctions(RuleSetDispatchFeature d) { forEach(atom, SubtermGenerator.leftTraverse(sub(intRel, 0), tf.mulF), SumFeature.createSum(applyTF(atom, add(tf.atom, not(tf.literal))), allowPosNegCaseDistinction(atom), instantiate("signCasesLeft", atom), - longConst(IN_EQ_SIMP_NON_LIN_COST + 200) + longConst(NonlinearArithmeticCost.MULTIPLY + 200) // , // applyTF ( atom, rec ( any (), // longTermConst ( 5 ) ) ) @@ -929,7 +938,7 @@ private void setupInEqCaseDistinctions(RuleSetDispatchFeature d) { SumFeature.createSum( applyTF(intRel, add(or(tf.geqF, tf.leqF), sub(tf.atom, tf.literal))), instantiate("cutFormula", opTerm(tf.eq, sub(intRel, 0), sub(intRel, 1))), - longConst(IN_EQ_SIMP_NON_LIN_COST + 300) + longConst(NonlinearArithmeticCost.MULTIPLY + 300) // , // applyTF ( sub ( intRel, 0 ), // rec ( any (), longTermConst ( 5 ) ) ) @@ -939,9 +948,11 @@ private void setupInEqCaseDistinctions(RuleSetDispatchFeature d) { add(isRootInferenceProducer(intRel), forEach(rootInf, RootsGenerator.create(intRel, getServices()), add(instantiate("cutFormula", rootInf), - ifZero(applyTF(rootInf, op(Junctor.OR)), longConst(50)), - ifZero(applyTF(rootInf, op(Junctor.AND)), longConst(20)))), - longConst(IN_EQ_SIMP_NON_LIN_COST))); + ifZero(applyTF(rootInf, op(Junctor.OR)), + longConst(CostBand.DEFAULT.at(50))), + ifZero(applyTF(rootInf, op(Junctor.AND)), + longConst(CostBand.DEFAULT.at(20))))), + longConst(NonlinearArithmeticCost.MULTIPLY))); // noinspection unchecked bindRuleSet(d, "cut", oneOf(new Feature[] { strengthening, rootInferences })); @@ -1017,32 +1028,32 @@ private void setupDefOpsPrimaryCategories(RuleSetDispatchFeature d) { applyTF("divNum", tf.polynomial), applyTF("divDenom", tf.polynomial), applyTF("divNum", tf.notContainsDivMod), applyTF("divDenom", tf.notContainsDivMod), - ifZero(isBelow(ff.modalOperator), longConst(200)))); + ifZero(isBelow(ff.modalOperator), longConst(DivModCost.BELOW_MODALITY)))); bindRuleSet(d, "defOps_jdiv", SumFeature.createSum(NonDuplicateAppModPositionFeature.INSTANCE, applyTF("divNum", tf.polynomial), applyTF("divDenom", tf.polynomial), applyTF("divNum", tf.notContainsDivMod), applyTF("divDenom", tf.notContainsDivMod), - ifZero(isBelow(ff.modalOperator), longConst(200)))); + ifZero(isBelow(ff.modalOperator), longConst(DivModCost.BELOW_MODALITY)))); bindRuleSet(d, "defOps_jdiv_inline", add(applyTF("divNum", tf.literal), - applyTF("divDenom", tf.polynomial), longConst(-5000))); + applyTF("divDenom", tf.polynomial), longConst(DivModCost.INLINE))); setupDefOpsExpandMod(d); - bindRuleSet(d, "defOps_expandRanges", -8000); - bindRuleSet(d, "defOps_expandJNumericOp", -500); - bindRuleSet(d, "defOps_modHomoEq", -5000); + bindRuleSet(d, "defOps_expandRanges", DivModCost.EXPAND_RANGES); + bindRuleSet(d, "defOps_expandJNumericOp", DivModCost.EXPAND_NUMERIC_OP); + bindRuleSet(d, "defOps_modHomoEq", DivModCost.MOD_HOMO_EQ); } else { bindRuleSet(d, "defOps_div", inftyConst()); bindRuleSet(d, "defOps_jdiv", inftyConst()); bindRuleSet(d, "defOps_jdiv_inline", add(applyTF("divNum", tf.literal), - applyTF("divDenom", tf.literal), longConst(-4000))); + applyTF("divDenom", tf.literal), longConst(DivModCost.MOD))); bindRuleSet(d, "defOps_mod", add(applyTF("divNum", tf.literal), - applyTF("divDenom", tf.literal), longConst(-4000))); + applyTF("divDenom", tf.literal), longConst(DivModCost.MOD))); bindRuleSet(d, "defOps_expandRanges", inftyConst()); bindRuleSet(d, "defOps_expandJNumericOp", inftyConst()); @@ -1066,13 +1077,13 @@ private void setupDefOpsExpandMod(RuleSetDispatchFeature d) { bindRuleSet(d, "defOps_mod", ifZero(add(applyTF("divNum", tf.literal), applyTF("divDenom", tf.literal)), - longConst(-4000), + longConst(DivModCost.MOD), SumFeature.createSum(applyTF("divNum", tf.polynomial), applyTF("divDenom", tf.polynomial), ifZero(isBelow(ff.modalOperator), exSubsumedModulus, or(add(applyTF("divNum", tf.notContainsDivMod), applyTF("divDenom", tf.notContainsDivMod)), exSubsumedModulus)), - longConst(-3500)))); + longConst(DivModCost.MOD_EXPAND)))); } /** From 3a66fc374786dda58a0d69b5541a5824c6c996ec Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Wed, 8 Jul 2026 18:36:08 +0200 Subject: [PATCH 03/10] Step 2 (checkpoint): SymExStrategy costs via CostBand + SymExCost Byte-identical. Shared-ladder anchors -> CostBand (block/loop contracts BLOCK_CONTRACT, loopInvariant LOOP_INVARIANT, concrete_java REWRITE, simplify_java SIMPLIFY, simplify_prog_subset EXECUTE). SE-internal nudges -> SymExCost (PROGRAM_STEP and its throwing/under-quantifier variants, method expand pair, LOOP_SCOPE_EXPAND, MERGE_RULE + MERGE_POINT_SKIP delta, MODAL_TAUTOLOGY, BOX_DIAMOND_CONV, SPLIT_IF, the 42 loop-contract tie-break). EXECUTE reserved for genuine program-execution rules (merge is not one); modal_tautology named separately from SUBST. Enabled-gates (longConst(0)) and the findDepth scale left raw. Created with AI tooling support --- .../de/uka/ilkd/key/strategy/SymExCost.java | 87 +++++++++++++++++++ .../uka/ilkd/key/strategy/SymExStrategy.java | 60 +++++++------ 2 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java new file mode 100644 index 00000000000..6130f1d88e4 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java @@ -0,0 +1,87 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * Symbolic-execution-internal ordering costs, used by {@link SymExStrategy}. These are the fine, + * within-SE ordering values that deserve a speaking name of their own; the coarse priorities that + * place SE rules relative to the other theories are written as + * {@code CostBand..cost()/at(delta)} at the call site instead (e.g. block/loop contracts + * {@code BLOCK_CONTRACT}, {@code loopInvariant} {@code LOOP_INVARIANT}, {@code concrete_java} + * {@code REWRITE}). + * + *

+ * Values are byte-identical to the literals they replace. Changing one reorders symbolic execution; + * verify with a full runAllProofs (as for + * {@link org.key_project.prover.strategy.costbased.CostBand}). + *

+ */ +final class SymExCost { + private SymExCost() {} + + /** + * A cheap concrete program step: {@code simplify_expression}, {@code execute*Assignment} and + * the ordinary {@code simplify_prog} case — "advance the program by one small step". + */ + static final long PROGRAM_STEP = -100; + + /** + * {@code simplify_prog} step that would raise a tracked runtime exception + * (NullPointer/ArrayIndexOutOfBounds/…): pushed back so the non-exceptional path is explored + * first. + */ + static final long THROWING_PROGRAM_STEP = 500; + + /** {@code simplify_prog} step underneath a quantifier / non-atom: mildly dispreferred. */ + static final long PROGRAM_STEP_UNDER_QUANTIFIER = 200; + + /** Method-body expansion in METHOD_EXPAND mode. */ + static final long METHOD_EXPAND = 100; + + /** + * Method-body expansion in METHOD_CONTRACT mode: raised (from {@link #METHOD_EXPAND}) so that + * contract application is preferred over expanding the body. + */ + static final long METHOD_EXPAND_REPRESSED = 2000; + + /** Preference offset of the method-contract feature ({@code methodSpec}). */ + static final long METHOD_CONTRACT_PREFERENCE = -20; + + /** {@code loop_scope_expand} when that loop treatment is selected. */ + static final long LOOP_SCOPE_EXPAND = 1000; + + /** + * Tie-break so that in BLOCK_CONTRACT_EXTERNAL mode the external loop contract is applied in + * preference to the internal one when both match; any small positive value would do. + *

+ * TODO: this delta should be anchored to the external loop contract cost rather than standing + * alone (deferred to the step-3 band normalization); kept byte-identical for now. + *

+ */ + static final long LOOP_CONTRACT_INTERNAL_TIEBREAK = 42; + + /** + * The merge rule ({@code MergeRule}), applied eagerly. NOT the EXECUTE band: that is reserved + * for genuine program-execution rules, and a branch merge is not one. + */ + static final long MERGE_RULE = -4000; + + /** + * Deleting a merge point in MPS_SKIP mode: a delta below {@link #MERGE_RULE} so the skip is + * preferred over performing a merge. + */ + static final long MERGE_POINT_SKIP = MERGE_RULE - 1000; + + /** + * Closing a modal tautology ({@code modal_tautology}). A distinct concept from a substitution; + * it merely shares the numeric level of {@code CostBand.SUBST} and so gets its own name. + */ + static final long MODAL_TAUTOLOGY = -10000; + + /** Prefer converting a box/diamond modality towards the antecedent-polarity program. */ + static final long BOX_DIAMOND_CONV = -1000; + + /** Mildly defer {@code split_if} so straight-line simplification runs first. */ + static final long SPLIT_IF = 50; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java index baa90455843..b72933ba9b9 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java @@ -21,6 +21,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.NumberRuleAppCost; import org.key_project.prover.strategy.costbased.RuleAppCost; @@ -91,7 +92,7 @@ private Feature setupGlobalF(Feature dispatcher) { strategyProperties.getProperty(StrategyProperties.METHOD_OPTIONS_KEY); switch (methProp) { case StrategyProperties.METHOD_CONTRACT -> - methodSpecF = methodSpecFeature(longConst(-20)); + methodSpecF = methodSpecFeature(longConst(SymExCost.METHOD_CONTRACT_PREFERENCE)); case StrategyProperties.METHOD_EXPAND, StrategyProperties.METHOD_NONE -> methodSpecF = methodSpecFeature(inftyConst()); default -> { @@ -114,15 +115,20 @@ private Feature setupGlobalF(Feature dispatcher) { final String blockProperty = strategyProperties.getProperty(StrategyProperties.BLOCK_OPTIONS_KEY); if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_INTERNAL)) { - blockFeature = blockContractInternalFeature(longConst(Long.MIN_VALUE)); - loopBlockFeature = loopContractInternalFeature(longConst(Long.MIN_VALUE)); - loopBlockApplyHeadFeature = loopContractApplyHead(longConst(Long.MIN_VALUE)); + blockFeature = blockContractInternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); + loopBlockFeature = + loopContractInternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); + loopBlockApplyHeadFeature = + loopContractApplyHead(longConst(CostBand.BLOCK_CONTRACT.cost())); } else if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_EXTERNAL)) { - blockFeature = blockContractExternalFeature(longConst(Long.MIN_VALUE)); + blockFeature = blockContractExternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); loopBlockFeature = - SumFeature.createSum(loopContractExternalFeature(longConst(Long.MIN_VALUE)), - loopContractInternalFeature(longConst(42))); - loopBlockApplyHeadFeature = loopContractApplyHead(longConst(Long.MIN_VALUE)); + SumFeature.createSum( + loopContractExternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())), + loopContractInternalFeature( + longConst(SymExCost.LOOP_CONTRACT_INTERNAL_TIEBREAK))); + loopBlockApplyHeadFeature = + loopContractApplyHead(longConst(CostBand.BLOCK_CONTRACT.cost())); } else { blockFeature = blockContractInternalFeature(inftyConst()); loopBlockFeature = loopContractExternalFeature(inftyConst()); @@ -133,7 +139,7 @@ private Feature setupGlobalF(Feature dispatcher) { final String mpsProperty = strategyProperties.getProperty(StrategyProperties.MPS_OPTIONS_KEY); if (mpsProperty.equals(StrategyProperties.MPS_MERGE)) { - mergeRuleF = mergeRuleFeature(longConst(-4000)); + mergeRuleF = mergeRuleFeature(longConst(SymExCost.MERGE_RULE)); } else { mergeRuleF = mergeRuleFeature(inftyConst()); } @@ -151,26 +157,28 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_prog", ifZero(ThrownExceptionFeature.create(exceptionsWithPenalty, getServices()), - longConst(500), - ifZero(isBelow(add(ff.forF, not(ff.atom))), longConst(200), longConst(-100)))); + longConst(SymExCost.THROWING_PROGRAM_STEP), + ifZero(isBelow(add(ff.forF, not(ff.atom))), + longConst(SymExCost.PROGRAM_STEP_UNDER_QUANTIFIER), + longConst(SymExCost.PROGRAM_STEP)))); - bindRuleSet(d, "simplify_prog_subset", longConst(-4000)); + bindRuleSet(d, "simplify_prog_subset", longConst(CostBand.EXECUTE.cost())); - bindRuleSet(d, "simplify_expression", -100); + bindRuleSet(d, "simplify_expression", SymExCost.PROGRAM_STEP); - bindRuleSet(d, "simplify_java", -4500); + bindRuleSet(d, "simplify_java", CostBand.SIMPLIFY.cost()); - bindRuleSet(d, "executeIntegerAssignment", -100); - bindRuleSet(d, "executeDoubleAssignment", -100); + bindRuleSet(d, "executeIntegerAssignment", SymExCost.PROGRAM_STEP); + bindRuleSet(d, "executeDoubleAssignment", SymExCost.PROGRAM_STEP); final Feature findDepthFeature = FindDepthFeature.getInstance(); bindRuleSet(d, "concrete_java", - add(longConst(-11000), + add(longConst(CostBand.REWRITE.cost()), ScaleFeature.createScaled(findDepthFeature, 10.0))); // taclets for special invariant handling - bindRuleSet(d, "loopInvariant", -20000); + bindRuleSet(d, "loopInvariant", CostBand.LOOP_INVARIANT.cost()); boolean useLoopExpand = strategyProperties.getProperty(StrategyProperties.LOOP_OPTIONS_KEY) .equals(StrategyProperties.LOOP_EXPAND); @@ -183,7 +191,8 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "loop_expand", useLoopExpand ? longConst(0) : inftyConst()); bindRuleSet(d, "loop_scope_inv_taclet", useLoopInvTaclets ? longConst(0) : inftyConst()); - bindRuleSet(d, "loop_scope_expand", useLoopScopeExpand ? longConst(1000) : inftyConst()); + bindRuleSet(d, "loop_scope_expand", + useLoopScopeExpand ? longConst(SymExCost.LOOP_SCOPE_EXPAND) : inftyConst()); final String methProp = @@ -196,9 +205,9 @@ private RuleSetDispatchFeature setupCostComputationF() { * is disabled. The original cost was 200 and is now increased to 2000 in order to * repress method expansion stronger when method treatment by contracts is chosen. */ - bindRuleSet(d, "method_expand", longConst(2000)); + bindRuleSet(d, "method_expand", longConst(SymExCost.METHOD_EXPAND_REPRESSED)); case StrategyProperties.METHOD_EXPAND -> - bindRuleSet(d, "method_expand", longConst(100)); + bindRuleSet(d, "method_expand", longConst(SymExCost.METHOD_EXPAND)); case StrategyProperties.METHOD_NONE -> bindRuleSet(d, "method_expand", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + methProp); } @@ -227,12 +236,13 @@ private RuleSetDispatchFeature setupCostComputationF() { mState); } }); - case StrategyProperties.MPS_SKIP -> bindRuleSet(d, "merge_point", longConst(-5000)); + case StrategyProperties.MPS_SKIP -> + bindRuleSet(d, "merge_point", longConst(SymExCost.MERGE_POINT_SKIP)); case StrategyProperties.MPS_NONE -> bindRuleSet(d, "merge_point", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + mpsProp); } - bindRuleSet(d, "modal_tautology", longConst(-10000)); + bindRuleSet(d, "modal_tautology", longConst(SymExCost.MODAL_TAUTOLOGY)); if (programsToRight) { bindRuleSet(d, "boxDiamondConv", @@ -240,7 +250,7 @@ private RuleSetDispatchFeature setupCostComputationF() { new FindPrefixRestrictionFeature( FindPrefixRestrictionFeature.PositionModifier.ALLOW_UPDATE_AS_PARENT, FindPrefixRestrictionFeature.PrefixChecker.ANTEC_POLARITY), - longConst(-1000))); + longConst(SymExCost.BOX_DIAMOND_CONV))); } else { bindRuleSet(d, "boxDiamondConv", inftyConst()); } @@ -251,7 +261,7 @@ private RuleSetDispatchFeature setupCostComputationF() { final TermBuffer superFor = new TermBuffer(); bindRuleSet(d, "split_if", add(sum(superFor, SuperTermGenerator.upwards(any(), getServices()), - applyTF(superFor, not(ff.program))), longConst(50))); + applyTF(superFor, not(ff.program))), longConst(SymExCost.SPLIT_IF))); return d; } From 167ed2f16d7197d5890114260508bea0d921e5da Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Thu, 9 Jul 2026 08:48:56 +0200 Subject: [PATCH 04/10] Step 2 (checkpoint): StringStrategy costs via CostBand + StringCost Byte-identical. String theory splits into an eager side (negatives, named in StringCost: INTEGER_TO_STRING, REPLACE_INLINE, CHAR_TO_INT_LITERAL, BELOW_MODALITY_PENALTY; plus stringsSimplify -> CostBand.NORMALIZE) and a lazy unfold ladder (positives) anchored to CostBand.DEFER.at(delta), so each defOps*/ strings* rule reads as "defer, by this much". instantiate default longConst(0) left raw; stringsConcatNotBothLiterals stays inftyConst. Created with AI tooling support --- .../de/uka/ilkd/key/strategy/StringCost.java | 42 +++++++++++++++++++ .../uka/ilkd/key/strategy/StringStrategy.java | 36 +++++++++------- 2 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java new file mode 100644 index 00000000000..ffdf87545dc --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java @@ -0,0 +1,42 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * String/sequence-theory-internal ordering costs, used by {@link StringStrategy}. + * + *

+ * The theory splits into an eager side (negative costs: normalisations done early, named + * here) and a lazy unfold ladder (positive costs: unfolding of string definitions is + * deferred so it happens only when needed). The ladder is anchored to + * {@code CostBand.DEFER.at(delta)} at the call sites, so each rule reads as "defer, by this much". + *

+ * + *

+ * Values are byte-identical to the literals they replace; changing one reorders string reasoning + * (verify with a full runAllProofs, as for + * {@link org.key_project.prover.strategy.costbased.CostBand}). + *

+ */ +final class StringCost { + private StringCost() {} + + /** Translate an integer to its string representation ({@code integerToString}): very eager. */ + static final long INTEGER_TO_STRING = -10000; + + /** + * Inline a {@code replace} when string, search- and replace-char are all literals + * ({@code defOpsReplaceInline}): eager, closed-form. + */ + static final long REPLACE_INLINE = -2500; + + /** Convert a char literal to an int literal (outside string functions). */ + static final long CHAR_TO_INT_LITERAL = -100; + + /** + * Extra penalty for unfolding a string definition below a modal operator: postpone it until + * the program has been symbolically executed. Shared by the {@code defOps*} rules. + */ + static final long BELOW_MODALITY_PENALTY = 500; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java index 1f8270ac099..bd6acb68815 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java @@ -20,6 +20,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.RuleAppCost; import org.key_project.prover.strategy.costbased.feature.Feature; @@ -70,7 +71,7 @@ private RuleSetDispatchFeature setupCostComputationF() { private void setUpStringNormalisation(RuleSetDispatchFeature d) { // translates an integer into its string representation - bindRuleSet(d, "integerToString", -10000); + bindRuleSet(d, "integerToString", StringCost.INTEGER_TO_STRING); // do not convert char to int when inside a string function // feature used to recognize if one is inside a string literal @@ -84,7 +85,7 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { or(op(charListLDT.getClReplace()), op(charListLDT.getClLastIndexOfChar())))); bindRuleSet(d, "charLiteral_to_intLiteral", - ifZero(isBelow(keepChar), inftyConst(), longConst(-100))); + ifZero(isBelow(keepChar), inftyConst(), longConst(StringCost.CHAR_TO_INT_LITERAL))); // establish normalform @@ -95,25 +96,26 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { final TermFeature seqLiteral = rec(anyLiteral, or(op(seqLDT.getSeqConcat()), or(op(seqLDT.getSeqSingleton()), or(anyLiteral, inftyTermConst())))); - Feature belowModOpPenality = ifZero(isBelow(ff.modalOperator), longConst(500)); + Feature belowModOpPenality = + ifZero(isBelow(ff.modalOperator), longConst(StringCost.BELOW_MODALITY_PENALTY)); bindRuleSet(d, "defOpsSeqEquality", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero(add(applyTF("left", seqLiteral), applyTF("right", seqLiteral)), - longConst(1000), inftyConst()), + longConst(CostBand.DEFER.at(500)), inftyConst()), belowModOpPenality)); bindRuleSet(d, "defOpsConcat", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero( or(applyTF("leftStr", not(seqLiteral)), applyTF("rightStr", not(seqLiteral))), - longConst(1000) + longConst(CostBand.DEFER.at(500)) // concat is often introduced for construction purposes, // we do not want to use its definition right at the // beginning ), belowModOpPenality)); - bindRuleSet(d, "stringsSimplify", longConst(-5000)); + bindRuleSet(d, "stringsSimplify", longConst(CostBand.NORMALIZE.cost())); final TermFeature charOrIntLiteral = or(tf.charLiteral, tf.literal, or(add(OperatorClassTF.create(ParametricFunctionInstance.class), // XXX: @@ -122,17 +124,19 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "defOpsReplaceInline", ifZero(add(applyTF("str", seqLiteral), applyTF("searchChar", charOrIntLiteral), - applyTF("replChar", charOrIntLiteral)), longConst(-2500), inftyConst())); + applyTF("replChar", charOrIntLiteral)), longConst(StringCost.REPLACE_INLINE), + inftyConst())); bindRuleSet(d, "defOpsReplace", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero(or(applyTF("str", not(seqLiteral)), applyTF("searchChar", not(charOrIntLiteral)), - applyTF("replChar", not(charOrIntLiteral))), longConst(500), inftyConst()), + applyTF("replChar", not(charOrIntLiteral))), longConst(CostBand.DEFER.cost()), + inftyConst()), belowModOpPenality)); bindRuleSet(d, "stringsReduceSubstring", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(100))); + add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(-400)))); - bindRuleSet(d, "defOpsStartsEndsWith", longConst(250)); + bindRuleSet(d, "defOpsStartsEndsWith", longConst(CostBand.DEFER.at(-250))); bindRuleSet(d, "stringsConcatNotBothLiterals", ifZero(MatchedAssumesFeature.INSTANCE, ifZero( @@ -140,19 +144,21 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { applyTF(instOf("rightStr"), seqLiteral)), inftyConst()), inftyConst())); - bindRuleSet(d, "stringsReduceConcat", longConst(100)); + bindRuleSet(d, "stringsReduceConcat", longConst(CostBand.DEFER.at(-400))); bindRuleSet(d, "stringsReduceOrMoveOutsideConcat", - ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(800), inftyConst())); + ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(300)), + inftyConst())); bindRuleSet(d, "stringsMoveReplaceInside", - ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(400), inftyConst())); + ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(-100)), + inftyConst())); - bindRuleSet(d, "stringsExpandDefNormalOp", longConst(500)); + bindRuleSet(d, "stringsExpandDefNormalOp", longConst(CostBand.DEFER.cost())); bindRuleSet(d, "stringsContainsDefInline", SumFeature - .createSum(EqNonDuplicateAppFeature.INSTANCE, longConst(1000))); + .createSum(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.DEFER.at(500)))); } @Override From 091391c5f80ea2b013f788e0829a1c8765d52520 Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Fri, 10 Jul 2026 07:48:53 +0200 Subject: [PATCH 05/10] Step 2 (checkpoint): JavaCardDLStrategy costs via CostBand + holders Byte-identical; RAP verification pending (deferred to resume). Band uses verified against the band docs: simplify_literals -> ELIMINATE, query_axiom -> SOLVE, partialInvAxiom -> DEFER_STRONG, information_flow_contract_appl (1000000 sentinel) -> LAST_RESORT. The pull-out-select ladder -> HeapSelectCost (its own holder, pre-staging a future heap/select module; carries a comment on the apply_equations ~-5700 cross-theory coupling). Remaining axiom/observer/ comprehension/induction nudges -> JavaCardDLCost, named by meaning not by value coincidence (JAVA_INTEGER_SEMANTICS/COMPREHENSION_SIMPLIFY != NORMALIZE, AUTO_INDUCTION != TYPE). QueryExpandCost tuples, branch count, and enabled-gates left raw. Created with AI tooling support --- .../uka/ilkd/key/strategy/HeapSelectCost.java | 50 +++++++++++++ .../uka/ilkd/key/strategy/JavaCardDLCost.java | 73 +++++++++++++++++++ .../ilkd/key/strategy/JavaCardDLStrategy.java | 66 ++++++++++------- 3 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java new file mode 100644 index 00000000000..92551b1bf48 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java @@ -0,0 +1,50 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * Costs of the pull-out-select heap simplification pipeline, used by {@link JavaCardDLStrategy}. + * This is the coherent heap/select cluster that is a candidate to be promoted into its own + * component strategy later; it is kept in a dedicated holder to pre-stage that split. + * + *

+ * Combination-relevant, not purely local: this ladder is tuned relative to the + * FOL/Integer {@code apply_equations} cost. In particular {@link #APPLY_SELECT_EQ} is chosen so + * that, together with the cost of {@code apply_equations}, the effective cost of replacing a select + * comes out at about −5700 (see the inline comment at {@code apply_select_eq}). Keep that coupling + * in mind when changing either side. + *

+ * + *

+ * Values are byte-identical to the literals they replace; verify changes with a full runAllProofs + * (as for {@link org.key_project.prover.strategy.costbased.CostBand}). + *

+ */ +final class HeapSelectCost { + private HeapSelectCost() {} + + /** {@code pull_out_select} when the focus select sits below an update (pull it out harder). */ + static final long PULL_OUT_SELECT_BELOW_UPDATE = -4200; + + /** {@code pull_out_select} otherwise. */ + static final long PULL_OUT_SELECT = -1900; + + /** + * {@code apply_select_eq}: replace a not-yet-simplified select by the skolem constant of its + * pull-out. Tuned so that with {@code apply_equations} the effective cost is about −5700. + */ + static final long APPLY_SELECT_EQ = -1700; + + /** {@code simplify_select}: simplify the select term in the pulled-out equation. */ + static final long SIMPLIFY_SELECT = -5600; + + /** {@code apply_auxiliary_eq}: replace the skolem constant by its computed value. */ + static final long APPLY_AUXILIARY_EQ = -5500; + + /** {@code hide_auxiliary_eq}: hide the auxiliary equation once the constant is replaced. */ + static final long HIDE_AUXILIARY_EQ = -5400; + + /** {@code hide_auxiliary_eq_const}: same, for the constant-valued case. */ + static final long HIDE_AUXILIARY_EQ_CONST = -500; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java new file mode 100644 index 00000000000..13621bec686 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java @@ -0,0 +1,73 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * JavaCardDL-theory-internal ordering costs, used by {@link JavaCardDLStrategy} (the axiom / + * observer / comprehension / induction reasoning; the heap/select pipeline has its own + * {@link HeapSelectCost}). + * + *

+ * Values are byte-identical to the literals they replace. Names are chosen by meaning, not + * by numeric coincidence with a {@link org.key_project.prover.strategy.costbased.CostBand} tier — + * e.g. {@link #AUTO_INDUCTION} shares −6500 with the FOL type-hierarchy rule but is not type + * reasoning, and {@link #JAVA_INTEGER_SEMANTICS} / {@link #COMPREHENSION_SIMPLIFY} share −5000 with + * NORMALIZE but are a definitional expansion / a simplify-like step. Verify changes with a full + * runAllProofs. + *

+ */ +final class JavaCardDLCost { + private JavaCardDLCost() {} + + /** + * Insert the java integer operator definitions ({@code javaIntegerSemantics}) once no program + * is left / on a single branch: a definitional expansion, not a canonicalization. + */ + static final long JAVA_INTEGER_SEMANTICS = -5000; + + /** Loc-set CNF commutation ({@code cnf_setComm}). */ + static final long LOCSET_CNF_COMMUTE = -800; + + /** Apply a class axiom ({@code classAxiom}). */ + static final long CLASS_AXIOM = -250; + + /** {@code inReachableStateImplication}. */ + static final long IN_REACHABLE_STATE = 100; + + /** + * Limit an observer symbol ({@code limitObserver}); must have better priority than classAxiom. + */ + static final long LIMIT_OBSERVER = -200; + + /** Dependency-contract application ({@code UseDependencyContractRule} / dependency feature). */ + static final long DEPENDENCY_CONTRACT = 250; + + // Comprehensions form a simplify/enlarge-style pair (cf. simplify / simplify_ENLARGING). The + // ENLARGE band doc even names "comprehension / map unfolding" — a step-3 candidate to normalize + // COMPREHENSION_SIMPLIFY -> SIMPLIFY and COMPREHENSION_ENLARGE -> ENLARGE. Byte-identical here. + /** Ordinary comprehension handling ({@code comprehensions}). */ + static final long COMPREHENSION = -50; + /** Cheap, simplify-like comprehension application ({@code comprehensions_low_costs}). */ + static final long COMPREHENSION_SIMPLIFY = -5000; + /** Expensive, enlarge-like comprehension application ({@code comprehensions_high_costs}). */ + static final long COMPREHENSION_ENLARGE = 10000; + + /** + * Auto-induction ({@code auto_induction}); must be applied like a delta rule. NOT the TYPE band + * despite sharing −6500 with the FOL type-hierarchy rule. + */ + static final long AUTO_INDUCTION = -6500; + + /** + * Auto-induction lemma ({@code auto_induction_lemma}); a beta rule with higher-than-usual + * priority. + */ + static final long AUTO_INDUCTION_LEMMA = -300; + + /** User taclets set to low priority: applied late. */ + static final long USER_TACLET_LOW_PRIORITY = 10000; + + /** User taclets set to high priority: mildly preferred. */ + static final long USER_TACLET_HIGH_PRIORITY = -50; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java index d3a856e7572..acb162ccc67 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java @@ -27,6 +27,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.RuleAppCost; import org.key_project.prover.strategy.costbased.TopRuleAppCost; @@ -133,7 +134,8 @@ protected Feature setupGlobalF(@NonNull Feature dispatcher) { final SetRuleFilter depFilter = new SetRuleFilter(); depFilter.addRuleToSet(UseDependencyContractRule.INSTANCE); if (depProp.equals(StrategyProperties.DEP_ON)) { - depSpecF = ConditionalFeature.createConditional(depFilter, longConst(250)); + depSpecF = ConditionalFeature.createConditional(depFilter, + longConst(JavaCardDLCost.DEPENDENCY_CONTRACT)); } else { depSpecF = ConditionalFeature.createConditional(depFilter, inftyConst()); } @@ -183,8 +185,10 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_heap_high_costs", inftyConst()); bindRuleSet(d, "javaIntegerSemantics", - ifZero(sequentContainsNoPrograms(), longConst(-5000), ifZero( - leq(CountBranchFeature.INSTANCE, longConst(1)), longConst(-5000), inftyConst()))); + ifZero(sequentContainsNoPrograms(), longConst(JavaCardDLCost.JAVA_INTEGER_SEMANTICS), + ifZero( + leq(CountBranchFeature.INSTANCE, longConst(1)), + longConst(JavaCardDLCost.JAVA_INTEGER_SEMANTICS), inftyConst()))); setupSelectSimplification(d); @@ -201,7 +205,8 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "cnf_setComm", add(SetsSmallerThanFeature.create(instOf("commRight"), instOf("commLeft"), locSetLDT), - NotInScopeOfModalityFeature.INSTANCE, longConst(-800))); + NotInScopeOfModalityFeature.INSTANCE, + longConst(JavaCardDLCost.LOCSET_CNF_COMMUTE))); } else { bindRuleSet(d, "cnf_setComm", inftyConst()); } @@ -209,19 +214,22 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_literals", // ifZero ( ConstraintStrengthenFeatureUC.create(proof), // longConst ( 0 ), - longConst(-8000)); + longConst(CostBand.ELIMINATE.cost())); bindRuleSet(d, "nonDuplicateAppCheckEq", EqNonDuplicateAppFeature.INSTANCE); // TODO: rename rule set? bindRuleSet(d, "comprehensions", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(-50))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(JavaCardDLCost.COMPREHENSION))); bindRuleSet(d, "comprehensions_high_costs", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(10000))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(JavaCardDLCost.COMPREHENSION_ENLARGE))); bindRuleSet(d, "comprehensions_low_costs", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(-5000))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(JavaCardDLCost.COMPREHENSION_SIMPLIFY))); // features influenced by the strategy options /* @@ -233,13 +241,13 @@ private RuleSetDispatchFeature setupCostComputationF() { strategyProperties.getProperty(StrategyProperties.QUERYAXIOM_OPTIONS_KEY); switch (queryAxProp) { case StrategyProperties.QUERYAXIOM_ON -> - bindRuleSet(d, "query_axiom", longConst(-3000)); + bindRuleSet(d, "query_axiom", longConst(CostBand.SOLVE.cost())); case StrategyProperties.QUERYAXIOM_OFF -> bindRuleSet(d, "query_axiom", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + queryAxProp); } if (classAxiomApplicationEnabled()) { - bindRuleSet(d, "classAxiom", longConst(-250)); + bindRuleSet(d, "classAxiom", longConst(JavaCardDLCost.CLASS_AXIOM)); } else { bindRuleSet(d, "classAxiom", inftyConst()); } @@ -250,15 +258,18 @@ private RuleSetDispatchFeature setupCostComputationF() { // partial inv axiom bindRuleSet(d, "partialInvAxiom", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(10000))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(CostBand.DEFER_STRONG.cost()))); // inReachableState bindRuleSet(d, "inReachableStateImplication", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(100))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(JavaCardDLCost.IN_REACHABLE_STATE))); // limit observer (must have better priority than "classAxiom") bindRuleSet(d, "limitObserver", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(-200))); + add(NonDuplicateAppModPositionFeature.INSTANCE, + longConst(JavaCardDLCost.LIMIT_OBSERVER))); setupUserTaclets(d); @@ -266,7 +277,7 @@ private RuleSetDispatchFeature setupCostComputationF() { // chrisg: The following rule, if active, must be applied delta rules. if (autoInductionEnabled()) { - bindRuleSet(d, "auto_induction", -6500); // chrisg + bindRuleSet(d, "auto_induction", JavaCardDLCost.AUTO_INDUCTION); // chrisg } else { bindRuleSet(d, "auto_induction", inftyConst()); // chrisg } @@ -274,12 +285,12 @@ private RuleSetDispatchFeature setupCostComputationF() { // chrisg: The following rule is a beta rule that, if active, must have // a higher priority than other beta rules. if (autoInductionLemmaEnabled()) { - bindRuleSet(d, "auto_induction_lemma", -300); + bindRuleSet(d, "auto_induction_lemma", JavaCardDLCost.AUTO_INDUCTION_LEMMA); } else { bindRuleSet(d, "auto_induction_lemma", inftyConst()); } - bindRuleSet(d, "information_flow_contract_appl", longConst(1000000)); + bindRuleSet(d, "information_flow_contract_appl", longConst(CostBand.LAST_RESORT.cost())); if (strategyProperties.contains(StrategyProperties.AUTO_INDUCTION_ON) || strategyProperties.contains(StrategyProperties.AUTO_INDUCTION_LEMMA_ON)) { @@ -301,8 +312,9 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { // function symbol) add(applyTF("h", not(or(PrimitiveHeapTermFeature.create(heapLDT), anonHeapTermFeature()))), - ifZero(applyTF(FocusFormulaProjection.INSTANCE, ff.update), longConst(-4200), - longConst(-1900)), + ifZero(applyTF(FocusFormulaProjection.INSTANCE, ff.update), + longConst(HeapSelectCost.PULL_OUT_SELECT_BELOW_UPDATE), + longConst(HeapSelectCost.PULL_OUT_SELECT)), NonDuplicateAppModPositionFeature.INSTANCE)); bindRuleSet(d, "apply_select_eq", // replace non-simplified select by the skolem constant @@ -312,7 +324,7 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { ifZero(applyTF("s", not(rec(any(), SimplifiedSelectTermFeature.create(heapLDT)))), // together with the costs of apply_equations the // resulting costs are about -5700 - longConst(-1700))); + longConst(HeapSelectCost.APPLY_SELECT_EQ))); bindRuleSet(d, "simplify_select", // simplify_select term in pulled out equation (right hand // side has to be a skolem constant which has been @@ -322,12 +334,12 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { add(isSelectSkolemConstantTerm("sk"), applyTF(sub(FocusProjection.INSTANCE, 0), not(SimplifiedSelectTermFeature.create(heapLDT))), - longConst(-5600))); + longConst(HeapSelectCost.SIMPLIFY_SELECT))); bindRuleSet(d, "simplify_select_concrete", longConst(-6000)); bindRuleSet(d, "simplify_select_elim_store", longConst(-7000)); bindRuleSet(d, "apply_auxiliary_eq", // replace a skolem constant by its computed value - add(isSelectSkolemConstantTerm("t1"), longConst(-5500))); + add(isSelectSkolemConstantTerm("t1"), longConst(HeapSelectCost.APPLY_AUXILIARY_EQ))); // hide an auxiliary equation once the skolem constant has been replaced by its value final Feature hideReplacedAuxiliaryEq = add(isSelectSkolemConstantTerm("auxiliarySK"), applyTF("result", @@ -338,7 +350,7 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { final int pullOutHeapBound = getHeapSizeBound(); bindRuleSet(d, "hide_auxiliary_eq", pullOutHeapBound <= 0 ? add(hideReplacedAuxiliaryEq, longConst(-5400)) - : ifZero(hideReplacedAuxiliaryEq, longConst(-5400), + : ifZero(hideReplacedAuxiliaryEq, longConst(HeapSelectCost.HIDE_AUXILIARY_EQ), ifZero(isTermAPulledOutHeap(), ifZero(isSkolemConstantUsedElsewhereInSequent(), longConst(-5400), longConst(HIDE_DEFERRAL_COST)), @@ -346,7 +358,8 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { bindRuleSet(d, "hide_auxiliary_eq_const", // hide an auxiliary equation once the skolem constant has been replaced by its // value - add(isSelectSkolemConstantTerm("auxiliarySK"), longConst(-500))); + add(isSelectSkolemConstantTerm("auxiliarySK"), + longConst(HeapSelectCost.HIDE_AUXILIARY_EQ_CONST))); } private void setupUserTaclets(RuleSetDispatchFeature d) { @@ -354,9 +367,9 @@ private void setupUserTaclets(RuleSetDispatchFeature d) { final String userTacletsProbs = strategyProperties.getProperty(StrategyProperties.userTacletsOptionsKey(i)); if (StrategyProperties.USER_TACLETS_LOW.equals(userTacletsProbs)) { - bindRuleSet(d, "userTaclets" + i, 10000); + bindRuleSet(d, "userTaclets" + i, JavaCardDLCost.USER_TACLET_LOW_PRIORITY); } else if (StrategyProperties.USER_TACLETS_HIGH.equals(userTacletsProbs)) { - bindRuleSet(d, "userTaclets" + i, -50); + bindRuleSet(d, "userTaclets" + i, JavaCardDLCost.USER_TACLET_HIGH_PRIORITY); } else { bindRuleSet(d, "userTaclets" + i, inftyConst()); } @@ -480,7 +493,8 @@ protected Feature setupApprovalF() { depFilter.addRuleToSet(UseDependencyContractRule.INSTANCE); if (depProp.equals(StrategyProperties.DEP_ON)) { depSpecF = ConditionalFeature.createConditional(depFilter, - ifZero(new DependencyContractFeature(), longConst(250), inftyConst())); + ifZero(new DependencyContractFeature(), + longConst(JavaCardDLCost.DEPENDENCY_CONTRACT), inftyConst())); } else { depSpecF = ConditionalFeature.createConditional(depFilter, inftyConst()); } From c841740cbb1a431ac1470571b63d28435fdab6e3 Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Fri, 10 Jul 2026 09:08:01 +0200 Subject: [PATCH 06/10] Step 2 (consolidation): CombinationCost + naming/structure alignment Byte-identical consolidation after the five theory conversions: - New CombinationCost holder for costs whose meaning spans strategies, with two documented admission mechanisms: (1) conflict-dispatched rule sets (ORDERED_REWRITING for apply_equations - the demodulation cost, guarded by term/monomial-ordering decrease at all four call sites; CNF_CONVERSION for apply_equations_andOr, shared deliberately by FOL's conjNormalForm), and (2) cost-sum couplings (APPLY_SELECT_EQ_EFFECTIVE = tuned sum of the dual- tagged applyEq taclet; HeapSelectCost.APPLY_SELECT_EQ is now derived from it, turning the old "about -5700" comment into checked arithmetic). FOLCost.APPLY_EQUATIONS/CNF_ORDERING and LinearEquationCost.APPLY_EQUATIONS/ APPLY_EQ_AND_OR are removed in favour of the shared constants. - JavaCardDL holders grouped per theory into JavaCardDLCosts.java (JavaCardDLCost + HeapSelectCost, Integer-style one file per theory). - Below-scope penalty names aligned: StringCost.BELOW_MODALITY (matching DivModCost.BELOW_MODALITY), SymExCost.PROGRAM_STEP_BELOW_QUANTIFIER. A sweep over all taclets and rule-set bindings verified completeness: exactly the three conflict-dispatched rule sets exist (order_terms, apply_equations, apply_equations_andOr, all FOL/Integer), and the only tuned cost-sum coupling is the applyEq taclet covered above. Created with AI tooling support --- .../ilkd/key/strategy/CombinationCost.java | 67 +++++++++++++++++++ .../de/uka/ilkd/key/strategy/FOLCost.java | 17 +---- .../de/uka/ilkd/key/strategy/FOLStrategy.java | 6 +- .../uka/ilkd/key/strategy/HeapSelectCost.java | 50 -------------- .../key/strategy/IntegerArithmeticCosts.java | 26 +++---- .../ilkd/key/strategy/IntegerStrategy.java | 4 +- ...vaCardDLCost.java => JavaCardDLCosts.java} | 58 ++++++++++++++++ .../ilkd/key/strategy/JavaCardDLStrategy.java | 4 +- .../de/uka/ilkd/key/strategy/StringCost.java | 2 +- .../uka/ilkd/key/strategy/StringStrategy.java | 2 +- .../de/uka/ilkd/key/strategy/SymExCost.java | 2 +- .../uka/ilkd/key/strategy/SymExStrategy.java | 2 +- 12 files changed, 150 insertions(+), 90 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/CombinationCost.java delete mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java rename key.core/src/main/java/de/uka/ilkd/key/strategy/{JavaCardDLCost.java => JavaCardDLCosts.java} (54%) diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/CombinationCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/CombinationCost.java new file mode 100644 index 00000000000..8906ec57a60 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/CombinationCost.java @@ -0,0 +1,67 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +/** + * Costs whose meaning spans more than one component strategy — the theory-combination + * constants, as opposed to the theory-internal holders ({@link FOLCost}, the integer holders, + * {@link SymExCost}, {@link StringCost}, {@link JavaCardDLCost}, {@link HeapSelectCost}). + * + *

+ * A constant is admitted here through one of two mechanisms: + *

+ *
    + *
  1. Conflict-dispatched rule sets: the same rule set is bound by two strategies, and + * {@link ModularJavaDLStrategy}#resolveConflict dispatches between the two bindings by the focus + * term (integer-typed focus → Integer half, otherwise FOL half). The two bindings are two halves of + * ONE combination decision, so their base costs must agree. Currently: {@code apply_equations} and + * {@code apply_equations_andOr} (the third conflict case, {@code order_terms}, needs no constant + * here — both halves anchor it at {@code CostBand.NORMALIZE}).
  2. + *
  3. Cost-sum couplings: one taclet carries rule sets owned by different + * strategies, so the dispatch sums their contributions and the tuned quantity is the sum, not + * either summand (e.g. the {@code applyEq} taclet, see {@link #APPLY_SELECT_EQ_EFFECTIVE}).
  4. + *
+ * + *

+ * A theory-local rule may also reference a constant here when sharing the level is the documented + * intent (e.g. {@code conjNormalForm} at {@link #CNF_CONVERSION}), so that retuning the + * combination level moves the coupled rule with it. + *

+ * + *

+ * Values are byte-identical to the literals they replace; verify changes with a full runAllProofs + * (as for {@link org.key_project.prover.strategy.costbased.CostBand}). + *

+ */ +final class CombinationCost { + private CombinationCost() {} + + /** + * Demodulation: use an oriented equation as a rewrite rule, only in the decreasing direction + * of the reduction ordering (see the {@code TermSmallerThanFeature} / + * {@code MonomialsSmallerThanFeature} guards at the call sites; right ≺ left). The FOL half + * instantiates this with the generic term ordering, the Integer half with the monomial + * ordering. The orientation step itself ({@code order_terms}) sits at + * {@code CostBand.NORMALIZE}. Deliberately its own level — not {@code EXECUTE}, which + * is reserved for symbolic execution. + */ + static final long ORDERED_REWRITING = -4000; + + /** + * Priority at which CNF conversion runs: {@code conjNormalForm} (associativity, commutation, + * distribution, if-then-else expansion — the fine ordering among these comes from the + * {@code cnf_*} co-rule-sets, {@link FOLCost#CNF_RESTRUCTURE}) and ordered rewriting within + * and/or clause contexts ({@code apply_equations_andOr}, conflict-dispatched). + */ + static final long CNF_CONVERSION = -150; + + /** + * Effective cost of replacing a select term via the {@code applyEq} taclet: the taclet carries + * BOTH {@code apply_equations} (FOL/Integer, {@link #ORDERED_REWRITING}) and + * {@code apply_select_eq} (JavaCardDL, {@link HeapSelectCost#APPLY_SELECT_EQ}), so the + * dispatch sums the two contributions. This constant is the tuned target of that sum; + * {@link HeapSelectCost#APPLY_SELECT_EQ} is derived from it. + */ + static final long APPLY_SELECT_EQ_EFFECTIVE = -5700; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java index af0c819f56d..1cd7907686f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLCost.java @@ -19,12 +19,6 @@ final class FOLCost { private FOLCost() {} - /** - * Base cost of applying an equation (applyEq / apply_equations). Deliberately its own band — - * not {@code EXECUTE}, which is reserved for symbolic execution. - */ - static final long APPLY_EQUATIONS = -4000; - /** * Distribution / swapping of quantifiers ({@code distrQuantifier}, {@code swapQuantifiers}). */ @@ -33,17 +27,12 @@ private FOLCost() {} /** * Restructuring of CNF clauses by associativity / distribution ({@code cnf_orAssoc}, * {@code cnf_andAssoc}, {@code cnf_dist}); the small {@code ± delta} at the call sites orders - * these among themselves. + * these among themselves. These are the fine deltas summed on top of the + * {@link CombinationCost#CNF_CONVERSION} level via the dual rule-set tags of the + * {@code conjNormalForm} taclets. */ static final long CNF_RESTRUCTURE = -35; - /** - * Ordering of formula-normalisation steps in the boolean/CNF area, at one shared priority: - * {@code conjNormalForm} (CNF conversion) and {@code apply_equations_andOr} (contextual - * equation application inside and/or). - */ - static final long CNF_ORDERING = -150; - /** * Defer {@code replace_known_right} when its target is in the consequent of an implication or * inside an equivalence, so the connective is decomposed first (which makes the antecedent diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java index a8bccf5aebc..506fddef19c 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java @@ -301,7 +301,7 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { ifZero( add(or(FocusInAntecFeature.getInstance(), notBelowQuantifier()), NotInScopeOfModalityFeature.INSTANCE), - add(longConst(FOLCost.CNF_ORDERING), + add(longConst(CombinationCost.CNF_CONVERSION), ScaleFeature.createScaled(FindDepthFeature.getInstance(), 20)), inftyConst())); @@ -318,7 +318,7 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { add(let(left, instOf("applyEqLeft"), let(right, instOf("applyEqRight"), TermSmallerThanFeature.create(right, left))), - longConst(FOLCost.CNF_ORDERING))); + longConst(CombinationCost.CNF_CONVERSION))); bindRuleSet(d, "distrQuantifier", add(or( @@ -587,7 +587,7 @@ private void setupEquationReasoning(RuleSetDispatchFeature d) { let(left, sub(equation, 0), let(right, sub(equation, 1), TermSmallerThanFeature.create(right, left))))))), - longConst(FOLCost.APPLY_EQUATIONS))); + longConst(CombinationCost.ORDERED_REWRITING))); bindRuleSet(d, "insert_eq_nonrigid", applyTF(FocusProjection.create(0), IsNonRigidTermFeature.INSTANCE)); diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java deleted file mode 100644 index 92551b1bf48..00000000000 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/HeapSelectCost.java +++ /dev/null @@ -1,50 +0,0 @@ -/* This file is part of KeY - https://key-project.org - * KeY is licensed under the GNU General Public License Version 2 - * SPDX-License-Identifier: GPL-2.0-only */ -package de.uka.ilkd.key.strategy; - -/** - * Costs of the pull-out-select heap simplification pipeline, used by {@link JavaCardDLStrategy}. - * This is the coherent heap/select cluster that is a candidate to be promoted into its own - * component strategy later; it is kept in a dedicated holder to pre-stage that split. - * - *

- * Combination-relevant, not purely local: this ladder is tuned relative to the - * FOL/Integer {@code apply_equations} cost. In particular {@link #APPLY_SELECT_EQ} is chosen so - * that, together with the cost of {@code apply_equations}, the effective cost of replacing a select - * comes out at about −5700 (see the inline comment at {@code apply_select_eq}). Keep that coupling - * in mind when changing either side. - *

- * - *

- * Values are byte-identical to the literals they replace; verify changes with a full runAllProofs - * (as for {@link org.key_project.prover.strategy.costbased.CostBand}). - *

- */ -final class HeapSelectCost { - private HeapSelectCost() {} - - /** {@code pull_out_select} when the focus select sits below an update (pull it out harder). */ - static final long PULL_OUT_SELECT_BELOW_UPDATE = -4200; - - /** {@code pull_out_select} otherwise. */ - static final long PULL_OUT_SELECT = -1900; - - /** - * {@code apply_select_eq}: replace a not-yet-simplified select by the skolem constant of its - * pull-out. Tuned so that with {@code apply_equations} the effective cost is about −5700. - */ - static final long APPLY_SELECT_EQ = -1700; - - /** {@code simplify_select}: simplify the select term in the pulled-out equation. */ - static final long SIMPLIFY_SELECT = -5600; - - /** {@code apply_auxiliary_eq}: replace the skolem constant by its computed value. */ - static final long APPLY_AUXILIARY_EQ = -5500; - - /** {@code hide_auxiliary_eq}: hide the auxiliary equation once the constant is replaced. */ - static final long HIDE_AUXILIARY_EQ = -5400; - - /** {@code hide_auxiliary_eq_const}: same, for the constant-valued case. */ - static final long HIDE_AUXILIARY_EQ_CONST = -500; -} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java index 1c34f5b58b0..679634e1f6b 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java @@ -38,24 +38,20 @@ private PolynomialCost() {} final class LinearEquationCost { private LinearEquationCost() {} - /** - * The general {@code apply_equations} rule set (applyEq / applyEqReverse), used to reduce - * polynomials. Distinct from {@link #APPLY_EQ}, which is the monomial-specialised polySimp - * variant. Its own band, not {@code EXECUTE} (reserved for symbolic execution). - *

- * Step-3 idea: test whether the specialised {@link #APPLY_EQ} is needed at all, or should just - * be a small delta that prefers these original {@code apply_equations} rules. - *

- */ - static final long APPLY_EQUATIONS = -4000; - static final long APPLY_EQ_AND_OR = -150; + // The base costs of apply_equations / apply_equations_andOr are combination-shared with + // FOLStrategy (conflict-dispatched; the Integer halves use the monomial ordering as + // demodulation guard): see CombinationCost.ORDERED_REWRITING and + // CombinationCost.CNF_CONVERSION. + /** polySimp_balance, polySimp_normalise. */ static final long BALANCE = -30; /** - * polySimp_applyEq — the monomial-coefficient-specialised equation application. The rigid - * variant polySimp_applyEqRigid is written {@code APPLY_EQ + 1} at the call site; that +1 is - * only an (uninteresting) tie-break between the two rules, a step-3 candidate to flatten to a - * single cost. + * polySimp_applyEq — the monomial-coefficient-specialised equation application (the general + * demodulation cost is {@link CombinationCost#ORDERED_REWRITING}). The rigid variant + * polySimp_applyEqRigid is written {@code APPLY_EQ + 1} at the call site; that +1 is only an + * (uninteresting) tie-break between the two rules, a step-3 candidate to flatten to a single + * cost. Step-3 idea: test whether this specialised variant is needed at all, or should just be + * a small delta preferring the original {@code apply_equations} rules. */ static final long APPLY_EQ = 1; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java index 02a3f9ef118..d0e2177e587 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java @@ -230,7 +230,7 @@ private RuleSetDispatchFeature setupCostComputationF() { applyTF(right, tf.polynomial), MonomialsSmallerThanFeature.create(right, left, numbers)))))))), - longConst(LinearEquationCost.APPLY_EQUATIONS))); + longConst(CombinationCost.ORDERED_REWRITING))); final TermBuffer l = new TermBuffer(); final TermBuffer r = new TermBuffer(); @@ -239,7 +239,7 @@ private RuleSetDispatchFeature setupCostComputationF() { let(r, instOf("applyEqRight"), add(applyTF(l, tf.nonNegOrNonCoeffMonomial), applyTF(r, tf.polynomial), MonomialsSmallerThanFeature.create(r, l, numbers)))), - longConst(LinearEquationCost.APPLY_EQ_AND_OR))); + longConst(CombinationCost.CNF_CONVERSION))); // For taclets that need instantiation, but where the instantiation is // deterministic and does not have to be repeated at a later point, we diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java similarity index 54% rename from key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java rename to key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java index 13621bec686..78a371093ef 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCost.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java @@ -3,6 +3,14 @@ * SPDX-License-Identifier: GPL-2.0-only */ package de.uka.ilkd.key.strategy; +/* + * Theory-internal cost constants for the JavaCardDL strategy, grouped per sub-area + * (Integer-style, one file per theory): JavaCardDLCost for the axiom / observer / + * comprehension / induction reasoning, HeapSelectCost for the pull-out-select pipeline + * (a future component strategy of its own; it is kept as a separate holder to pre-stage + * that split and moves with it). + */ + /** * JavaCardDL-theory-internal ordering costs, used by {@link JavaCardDLStrategy} (the axiom / * observer / comprehension / induction reasoning; the heap/select pipeline has its own @@ -71,3 +79,53 @@ private JavaCardDLCost() {} /** User taclets set to high priority: mildly preferred. */ static final long USER_TACLET_HIGH_PRIORITY = -50; } + + +/** + * Costs of the pull-out-select heap simplification pipeline, used by {@link JavaCardDLStrategy}. + * This is the coherent heap/select cluster that is a candidate to be promoted into its own + * component strategy later; it is kept in a dedicated holder to pre-stage that split. + * + *

+ * Combination-relevant, not purely local: this ladder is tuned relative to the + * demodulation cost {@link CombinationCost#ORDERED_REWRITING}: {@link #APPLY_SELECT_EQ} is the + * JavaCardDL-side remainder of the tuned sum {@link CombinationCost#APPLY_SELECT_EQ_EFFECTIVE} + * (the {@code applyEq} taclet carries both rule sets, so the dispatch sums the contributions). + *

+ * + *

+ * Values are byte-identical to the literals they replace; verify changes with a full runAllProofs + * (as for {@link org.key_project.prover.strategy.costbased.CostBand}). + *

+ */ +final class HeapSelectCost { + private HeapSelectCost() {} + + /** {@code pull_out_select} when the focus select sits below an update (pull it out harder). */ + static final long PULL_OUT_SELECT_BELOW_UPDATE = -4200; + + /** {@code pull_out_select} otherwise. */ + static final long PULL_OUT_SELECT = -1900; + + /** + * {@code apply_select_eq}: replace a not-yet-simplified select by the skolem constant of its + * pull-out. The {@code applyEq} taclet carries both {@code apply_equations} and + * {@code apply_select_eq}, so the effective cost is the SUM of the two bindings; the tuned + * quantity is {@link CombinationCost#APPLY_SELECT_EQ_EFFECTIVE} and this constant is the + * JavaCardDL-side remainder (currently −1700). + */ + static final long APPLY_SELECT_EQ = + CombinationCost.APPLY_SELECT_EQ_EFFECTIVE - CombinationCost.ORDERED_REWRITING; + + /** {@code simplify_select}: simplify the select term in the pulled-out equation. */ + static final long SIMPLIFY_SELECT = -5600; + + /** {@code apply_auxiliary_eq}: replace the skolem constant by its computed value. */ + static final long APPLY_AUXILIARY_EQ = -5500; + + /** {@code hide_auxiliary_eq}: hide the auxiliary equation once the constant is replaced. */ + static final long HIDE_AUXILIARY_EQ = -5400; + + /** {@code hide_auxiliary_eq_const}: same, for the constant-valued case. */ + static final long HIDE_AUXILIARY_EQ_CONST = -500; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java index acb162ccc67..515adc60a4c 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java @@ -322,8 +322,8 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { // needs to be not simplified yet; additional restrictions // in isApproved() ifZero(applyTF("s", not(rec(any(), SimplifiedSelectTermFeature.create(heapLDT)))), - // together with the costs of apply_equations the - // resulting costs are about -5700 + // the applyEq taclet also carries apply_equations, so the dispatch sums the + // bindings; the tuned sum is CombinationCost.APPLY_SELECT_EQ_EFFECTIVE longConst(HeapSelectCost.APPLY_SELECT_EQ))); bindRuleSet(d, "simplify_select", // simplify_select term in pulled out equation (right hand diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java index ffdf87545dc..953080d5f42 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringCost.java @@ -38,5 +38,5 @@ private StringCost() {} * Extra penalty for unfolding a string definition below a modal operator: postpone it until * the program has been symbolically executed. Shared by the {@code defOps*} rules. */ - static final long BELOW_MODALITY_PENALTY = 500; + static final long BELOW_MODALITY = 500; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java index bd6acb68815..5a2bc6fe493 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java @@ -97,7 +97,7 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { or(op(seqLDT.getSeqSingleton()), or(anyLiteral, inftyTermConst())))); Feature belowModOpPenality = - ifZero(isBelow(ff.modalOperator), longConst(StringCost.BELOW_MODALITY_PENALTY)); + ifZero(isBelow(ff.modalOperator), longConst(StringCost.BELOW_MODALITY)); bindRuleSet(d, "defOpsSeqEquality", add(NonDuplicateAppModPositionFeature.INSTANCE, diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java index 6130f1d88e4..798fdde2cef 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java @@ -34,7 +34,7 @@ private SymExCost() {} static final long THROWING_PROGRAM_STEP = 500; /** {@code simplify_prog} step underneath a quantifier / non-atom: mildly dispreferred. */ - static final long PROGRAM_STEP_UNDER_QUANTIFIER = 200; + static final long PROGRAM_STEP_BELOW_QUANTIFIER = 200; /** Method-body expansion in METHOD_EXPAND mode. */ static final long METHOD_EXPAND = 100; diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java index b72933ba9b9..b944d73cea4 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java @@ -159,7 +159,7 @@ private RuleSetDispatchFeature setupCostComputationF() { ifZero(ThrownExceptionFeature.create(exceptionsWithPenalty, getServices()), longConst(SymExCost.THROWING_PROGRAM_STEP), ifZero(isBelow(add(ff.forF, not(ff.atom))), - longConst(SymExCost.PROGRAM_STEP_UNDER_QUANTIFIER), + longConst(SymExCost.PROGRAM_STEP_BELOW_QUANTIFIER), longConst(SymExCost.PROGRAM_STEP)))); bindRuleSet(d, "simplify_prog_subset", longConst(CostBand.EXECUTE.cost())); From c2beb4bd55a6a51e8976e00816eda4b9b41a2964 Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Fri, 10 Jul 2026 11:24:31 +0200 Subject: [PATCH 07/10] Step 2 (readability): static-import theory-local costs; APPLY_EQ rename Pure cosmetic (byte-identical, no cost value changes): - Rename LinearEquationCost.APPLY_EQ -> APPLY_EQ_MONOMIAL_TIEBREAK (it is a small rider delta on CombinationCost.ORDERED_REWRITING via the dual-tagged apply_eq_monomials taclet, not a standalone "cost 1"); javadoc clarified. - Static-wildcard-import the theory-local cost holders in the single-holder strategies (FOLCost, SymExCost, StringCost, JavaCardDLCost+HeapSelectCost) so call sites read as bare names; the qualifier was pure redundancy there and forced spotless line wraps. CostBand and CombinationCost stay qualified (the prefix signals the shared ladder / a cross-theory cost). IntegerStrategy stays fully qualified: its 5 sub-holders' prefixes say which arithmetic layer. Created with AI tooling support --- .../de/uka/ilkd/key/strategy/FOLStrategy.java | 32 +++++++------ .../key/strategy/IntegerArithmeticCosts.java | 15 +++--- .../ilkd/key/strategy/IntegerStrategy.java | 4 +- .../ilkd/key/strategy/JavaCardDLStrategy.java | 47 ++++++++++--------- .../uka/ilkd/key/strategy/StringStrategy.java | 10 ++-- .../uka/ilkd/key/strategy/SymExStrategy.java | 34 +++++++------- 6 files changed, 76 insertions(+), 66 deletions(-) diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java index 506fddef19c..9960d7c9446 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java @@ -43,6 +43,8 @@ import org.jspecify.annotations.NonNull; +import static de.uka.ilkd.key.strategy.FOLCost.*; + /// Strategy for general FOL rules. This does not consider other /// theories like integers or Java-specific functions. /// @@ -330,30 +332,30 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { ifZero(FocusInAntecFeature.getInstance(), applyTF(FocusProjection.INSTANCE, sub(ff.andF)), applyTF(FocusProjection.INSTANCE, sub(ff.orF))))), - longConst(FOLCost.QUANTIFIER_DISTRIBUTION))); + longConst(QUANTIFIER_DISTRIBUTION))); bindRuleSet(d, "swapQuantifiers", add(applyTF(FocusProjection.INSTANCE, add(ff.quantifiedClauseSet, EliminableQuantifierTF.INSTANCE, sub(not(EliminableQuantifierTF.INSTANCE)))), - longConst(FOLCost.QUANTIFIER_DISTRIBUTION))); + longConst(QUANTIFIER_DISTRIBUTION))); // category "conjunctive normal form" bindRuleSet(d, "cnf_orAssoc", SumFeature.createSum(applyTF("assoc0", ff.clause), applyTF("assoc1", ff.clause), applyTF("assoc2", ff.literal), - longConst(FOLCost.CNF_RESTRUCTURE - 45))); + longConst(CNF_RESTRUCTURE - 45))); bindRuleSet(d, "cnf_andAssoc", SumFeature.createSum(applyTF("assoc0", ff.clauseSet), applyTF("assoc1", ff.clauseSet), applyTF("assoc2", ff.clause), - longConst(FOLCost.CNF_RESTRUCTURE + 25))); + longConst(CNF_RESTRUCTURE + 25))); bindRuleSet(d, "cnf_dist", SumFeature.createSum(applyTF("distRight0", ff.clauseSet), applyTF("distRight1", ff.clauseSet), ifZero(applyTF("distLeft", ff.clause), - longConst(FOLCost.CNF_RESTRUCTURE + 20), applyTF("distLeft", ff.clauseSet)), - longConst(FOLCost.CNF_RESTRUCTURE))); + longConst(CNF_RESTRUCTURE + 20), applyTF("distLeft", ff.clauseSet)), + longConst(CNF_RESTRUCTURE))); final TermBuffer superFor = new TermBuffer(); final Feature onlyBelowQuanAndOr = @@ -375,16 +377,16 @@ EliminableQuantifierTF.INSTANCE, sub(not(EliminableQuantifierTF.INSTANCE)))), add(isBelow(OperatorClassTF.create(Quantifier.class)), onlyBelowQuanAndOr, applyTF( FocusProjection.create(0), sub(ff.quantifiedClauseSet, ff.quantifiedClauseSet))); - bindRuleSet(d, "pullOutQuantifierUnifying", FOLCost.PULL_OUT_QUANTIFIER); + bindRuleSet(d, "pullOutQuantifierUnifying", PULL_OUT_QUANTIFIER); bindRuleSet(d, "pullOutQuantifierAll", add(pullOutQuantifierAllowed, - ifZero(FocusInAntecFeature.getInstance(), longConst(FOLCost.PULL_OUT_QUANTIFIER), - longConst(FOLCost.PULL_OUT_QUANTIFIER_REVERSE)))); + ifZero(FocusInAntecFeature.getInstance(), longConst(PULL_OUT_QUANTIFIER), + longConst(PULL_OUT_QUANTIFIER_REVERSE)))); bindRuleSet(d, "pullOutQuantifierEx", add(pullOutQuantifierAllowed, ifZero(FocusInAntecFeature.getInstance(), - longConst(FOLCost.PULL_OUT_QUANTIFIER_REVERSE), - longConst(FOLCost.PULL_OUT_QUANTIFIER)))); + longConst(PULL_OUT_QUANTIFIER_REVERSE), + longConst(PULL_OUT_QUANTIFIER)))); } // ////////////////////////////////////////////////////////////////////////// @@ -477,9 +479,9 @@ private void setupReplaceKnown(RuleSetDispatchFeature d) { bindRuleSet(d, "replace_known_right", add(commonF, ifZero(directlyBelowSymbolAtIndex(Junctor.IMP, 1), - longConst(FOLCost.REPLACE_KNOWN_UNDER_CONNECTIVE), + longConst(REPLACE_KNOWN_UNDER_CONNECTIVE), ifZero(directlyBelowSymbolAtIndex(Equality.EQV, -1), - longConst(FOLCost.REPLACE_KNOWN_UNDER_CONNECTIVE))))); + longConst(REPLACE_KNOWN_UNDER_CONNECTIVE))))); } // ////////////////////////////////////////////////////////////////////////// @@ -531,14 +533,14 @@ protected void setupSplitting(RuleSetDispatchFeature d) { // auxiliary variables rec(any(), not(selectSkolemConstantTermFeature())))), countOccurrencesInSeq, // standard costs - longConst(FOLCost.CUT_DIRECT_STANDARD)), + longConst(CUT_DIRECT_STANDARD)), SumFeature // check for cuts below quantifiers .createSum(applyTF(cutFormula, ff.cutAllowedBelowQuantifier), applyTF(FocusFormulaProjection.INSTANCE, ff.quantifiedClauseSet), ifZero(allowQuantifierSplitting(), longConst(CostBand.DEFAULT.cost()), - longConst(FOLCost.CUT_DIRECT_STANDARD)))))); + longConst(CUT_DIRECT_STANDARD)))))); } private void setupSplittingApproval(RuleSetDispatchFeature d) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java index 679634e1f6b..fb572e7c594 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java @@ -46,14 +46,15 @@ private LinearEquationCost() {} /** polySimp_balance, polySimp_normalise. */ static final long BALANCE = -30; /** - * polySimp_applyEq — the monomial-coefficient-specialised equation application (the general - * demodulation cost is {@link CombinationCost#ORDERED_REWRITING}). The rigid variant - * polySimp_applyEqRigid is written {@code APPLY_EQ + 1} at the call site; that +1 is only an - * (uninteresting) tie-break between the two rules, a step-3 candidate to flatten to a single - * cost. Step-3 idea: test whether this specialised variant is needed at all, or should just be - * a small delta preferring the original {@code apply_equations} rules. + * polySimp_applyEq — a small tie-break rider on top of the general demodulation cost + * {@link CombinationCost#ORDERED_REWRITING} (the {@code apply_eq_monomials} taclet carries both + * rule sets, so the effective cost is their sum). The rigid variant polySimp_applyEqRigid is + * written {@code APPLY_EQ_MONOMIAL_TIEBREAK + 1} at the call site; that +1 is only an + * (uninteresting) tie-break between the two rules, a step-3 candidate to flatten. Step-3 idea: + * test whether this specialised variant is needed at all, or should just be a small delta + * preferring the original {@code apply_equations} rules. */ - static final long APPLY_EQ = 1; + static final long APPLY_EQ_MONOMIAL_TIEBREAK = 1; } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java index d0e2177e587..3d4eaa6bc00 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java @@ -385,10 +385,10 @@ private void setupPolySimp(RuleSetDispatchFeature d, IntegerLDT numbers) { let(eqLeft, sub(AssumptionProjection.create(0), 0), validEqApplication)))); bindRuleSet(d, "polySimp_applyEq", - add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ))); + add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ_MONOMIAL_TIEBREAK))); bindRuleSet(d, "polySimp_applyEqRigid", - add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ + 1))); + add(eqMonomialFeature, longConst(LinearEquationCost.APPLY_EQ_MONOMIAL_TIEBREAK + 1))); // bindRuleSet(d, "defOps_expandModulo", diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java index 515adc60a4c..82d7e9186e6 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java @@ -39,6 +39,9 @@ import org.jspecify.annotations.NonNull; +import static de.uka.ilkd.key.strategy.HeapSelectCost.*; +import static de.uka.ilkd.key.strategy.JavaCardDLCost.*; + /// This strategy is the catch-all for Java related features that are either /// cross-cutting or one of the features that do not fit well into any other /// strategy. @@ -135,7 +138,7 @@ protected Feature setupGlobalF(@NonNull Feature dispatcher) { depFilter.addRuleToSet(UseDependencyContractRule.INSTANCE); if (depProp.equals(StrategyProperties.DEP_ON)) { depSpecF = ConditionalFeature.createConditional(depFilter, - longConst(JavaCardDLCost.DEPENDENCY_CONTRACT)); + longConst(DEPENDENCY_CONTRACT)); } else { depSpecF = ConditionalFeature.createConditional(depFilter, inftyConst()); } @@ -185,10 +188,10 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_heap_high_costs", inftyConst()); bindRuleSet(d, "javaIntegerSemantics", - ifZero(sequentContainsNoPrograms(), longConst(JavaCardDLCost.JAVA_INTEGER_SEMANTICS), + ifZero(sequentContainsNoPrograms(), longConst(JAVA_INTEGER_SEMANTICS), ifZero( leq(CountBranchFeature.INSTANCE, longConst(1)), - longConst(JavaCardDLCost.JAVA_INTEGER_SEMANTICS), inftyConst()))); + longConst(JAVA_INTEGER_SEMANTICS), inftyConst()))); setupSelectSimplification(d); @@ -206,7 +209,7 @@ private RuleSetDispatchFeature setupCostComputationF() { add(SetsSmallerThanFeature.create(instOf("commRight"), instOf("commLeft"), locSetLDT), NotInScopeOfModalityFeature.INSTANCE, - longConst(JavaCardDLCost.LOCSET_CNF_COMMUTE))); + longConst(LOCSET_CNF_COMMUTE))); } else { bindRuleSet(d, "cnf_setComm", inftyConst()); } @@ -221,15 +224,15 @@ private RuleSetDispatchFeature setupCostComputationF() { // TODO: rename rule set? bindRuleSet(d, "comprehensions", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(JavaCardDLCost.COMPREHENSION))); + longConst(COMPREHENSION))); bindRuleSet(d, "comprehensions_high_costs", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(JavaCardDLCost.COMPREHENSION_ENLARGE))); + longConst(COMPREHENSION_ENLARGE))); bindRuleSet(d, "comprehensions_low_costs", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(JavaCardDLCost.COMPREHENSION_SIMPLIFY))); + longConst(COMPREHENSION_SIMPLIFY))); // features influenced by the strategy options /* @@ -247,7 +250,7 @@ private RuleSetDispatchFeature setupCostComputationF() { } if (classAxiomApplicationEnabled()) { - bindRuleSet(d, "classAxiom", longConst(JavaCardDLCost.CLASS_AXIOM)); + bindRuleSet(d, "classAxiom", longConst(CLASS_AXIOM)); } else { bindRuleSet(d, "classAxiom", inftyConst()); } @@ -264,12 +267,12 @@ private RuleSetDispatchFeature setupCostComputationF() { // inReachableState bindRuleSet(d, "inReachableStateImplication", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(JavaCardDLCost.IN_REACHABLE_STATE))); + longConst(IN_REACHABLE_STATE))); // limit observer (must have better priority than "classAxiom") bindRuleSet(d, "limitObserver", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(JavaCardDLCost.LIMIT_OBSERVER))); + longConst(LIMIT_OBSERVER))); setupUserTaclets(d); @@ -277,7 +280,7 @@ private RuleSetDispatchFeature setupCostComputationF() { // chrisg: The following rule, if active, must be applied delta rules. if (autoInductionEnabled()) { - bindRuleSet(d, "auto_induction", JavaCardDLCost.AUTO_INDUCTION); // chrisg + bindRuleSet(d, "auto_induction", AUTO_INDUCTION); // chrisg } else { bindRuleSet(d, "auto_induction", inftyConst()); // chrisg } @@ -285,7 +288,7 @@ private RuleSetDispatchFeature setupCostComputationF() { // chrisg: The following rule is a beta rule that, if active, must have // a higher priority than other beta rules. if (autoInductionLemmaEnabled()) { - bindRuleSet(d, "auto_induction_lemma", JavaCardDLCost.AUTO_INDUCTION_LEMMA); + bindRuleSet(d, "auto_induction_lemma", AUTO_INDUCTION_LEMMA); } else { bindRuleSet(d, "auto_induction_lemma", inftyConst()); } @@ -313,8 +316,8 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { add(applyTF("h", not(or(PrimitiveHeapTermFeature.create(heapLDT), anonHeapTermFeature()))), ifZero(applyTF(FocusFormulaProjection.INSTANCE, ff.update), - longConst(HeapSelectCost.PULL_OUT_SELECT_BELOW_UPDATE), - longConst(HeapSelectCost.PULL_OUT_SELECT)), + longConst(PULL_OUT_SELECT_BELOW_UPDATE), + longConst(PULL_OUT_SELECT)), NonDuplicateAppModPositionFeature.INSTANCE)); bindRuleSet(d, "apply_select_eq", // replace non-simplified select by the skolem constant @@ -324,7 +327,7 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { ifZero(applyTF("s", not(rec(any(), SimplifiedSelectTermFeature.create(heapLDT)))), // the applyEq taclet also carries apply_equations, so the dispatch sums the // bindings; the tuned sum is CombinationCost.APPLY_SELECT_EQ_EFFECTIVE - longConst(HeapSelectCost.APPLY_SELECT_EQ))); + longConst(APPLY_SELECT_EQ))); bindRuleSet(d, "simplify_select", // simplify_select term in pulled out equation (right hand // side has to be a skolem constant which has been @@ -334,12 +337,12 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { add(isSelectSkolemConstantTerm("sk"), applyTF(sub(FocusProjection.INSTANCE, 0), not(SimplifiedSelectTermFeature.create(heapLDT))), - longConst(HeapSelectCost.SIMPLIFY_SELECT))); + longConst(SIMPLIFY_SELECT))); bindRuleSet(d, "simplify_select_concrete", longConst(-6000)); bindRuleSet(d, "simplify_select_elim_store", longConst(-7000)); bindRuleSet(d, "apply_auxiliary_eq", // replace a skolem constant by its computed value - add(isSelectSkolemConstantTerm("t1"), longConst(HeapSelectCost.APPLY_AUXILIARY_EQ))); + add(isSelectSkolemConstantTerm("t1"), longConst(APPLY_AUXILIARY_EQ))); // hide an auxiliary equation once the skolem constant has been replaced by its value final Feature hideReplacedAuxiliaryEq = add(isSelectSkolemConstantTerm("auxiliarySK"), applyTF("result", @@ -350,7 +353,7 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { final int pullOutHeapBound = getHeapSizeBound(); bindRuleSet(d, "hide_auxiliary_eq", pullOutHeapBound <= 0 ? add(hideReplacedAuxiliaryEq, longConst(-5400)) - : ifZero(hideReplacedAuxiliaryEq, longConst(HeapSelectCost.HIDE_AUXILIARY_EQ), + : ifZero(hideReplacedAuxiliaryEq, longConst(HIDE_AUXILIARY_EQ), ifZero(isTermAPulledOutHeap(), ifZero(isSkolemConstantUsedElsewhereInSequent(), longConst(-5400), longConst(HIDE_DEFERRAL_COST)), @@ -359,7 +362,7 @@ private void setupSelectSimplification(final RuleSetDispatchFeature d) { // hide an auxiliary equation once the skolem constant has been replaced by its // value add(isSelectSkolemConstantTerm("auxiliarySK"), - longConst(HeapSelectCost.HIDE_AUXILIARY_EQ_CONST))); + longConst(HIDE_AUXILIARY_EQ_CONST))); } private void setupUserTaclets(RuleSetDispatchFeature d) { @@ -367,9 +370,9 @@ private void setupUserTaclets(RuleSetDispatchFeature d) { final String userTacletsProbs = strategyProperties.getProperty(StrategyProperties.userTacletsOptionsKey(i)); if (StrategyProperties.USER_TACLETS_LOW.equals(userTacletsProbs)) { - bindRuleSet(d, "userTaclets" + i, JavaCardDLCost.USER_TACLET_LOW_PRIORITY); + bindRuleSet(d, "userTaclets" + i, USER_TACLET_LOW_PRIORITY); } else if (StrategyProperties.USER_TACLETS_HIGH.equals(userTacletsProbs)) { - bindRuleSet(d, "userTaclets" + i, JavaCardDLCost.USER_TACLET_HIGH_PRIORITY); + bindRuleSet(d, "userTaclets" + i, USER_TACLET_HIGH_PRIORITY); } else { bindRuleSet(d, "userTaclets" + i, inftyConst()); } @@ -494,7 +497,7 @@ protected Feature setupApprovalF() { if (depProp.equals(StrategyProperties.DEP_ON)) { depSpecF = ConditionalFeature.createConditional(depFilter, ifZero(new DependencyContractFeature(), - longConst(JavaCardDLCost.DEPENDENCY_CONTRACT), inftyConst())); + longConst(DEPENDENCY_CONTRACT), inftyConst())); } else { depSpecF = ConditionalFeature.createConditional(depFilter, inftyConst()); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java index 5a2bc6fe493..339637ac505 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java @@ -30,6 +30,8 @@ import org.jspecify.annotations.NonNull; +import static de.uka.ilkd.key.strategy.StringCost.*; + /// Strategy for string related rules. /// /// Do not create directly; use [StringStrategyFactory] instead. @@ -71,7 +73,7 @@ private RuleSetDispatchFeature setupCostComputationF() { private void setUpStringNormalisation(RuleSetDispatchFeature d) { // translates an integer into its string representation - bindRuleSet(d, "integerToString", StringCost.INTEGER_TO_STRING); + bindRuleSet(d, "integerToString", INTEGER_TO_STRING); // do not convert char to int when inside a string function // feature used to recognize if one is inside a string literal @@ -85,7 +87,7 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { or(op(charListLDT.getClReplace()), op(charListLDT.getClLastIndexOfChar())))); bindRuleSet(d, "charLiteral_to_intLiteral", - ifZero(isBelow(keepChar), inftyConst(), longConst(StringCost.CHAR_TO_INT_LITERAL))); + ifZero(isBelow(keepChar), inftyConst(), longConst(CHAR_TO_INT_LITERAL))); // establish normalform @@ -97,7 +99,7 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { or(op(seqLDT.getSeqSingleton()), or(anyLiteral, inftyTermConst())))); Feature belowModOpPenality = - ifZero(isBelow(ff.modalOperator), longConst(StringCost.BELOW_MODALITY)); + ifZero(isBelow(ff.modalOperator), longConst(BELOW_MODALITY)); bindRuleSet(d, "defOpsSeqEquality", add(NonDuplicateAppModPositionFeature.INSTANCE, @@ -124,7 +126,7 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "defOpsReplaceInline", ifZero(add(applyTF("str", seqLiteral), applyTF("searchChar", charOrIntLiteral), - applyTF("replChar", charOrIntLiteral)), longConst(StringCost.REPLACE_INLINE), + applyTF("replChar", charOrIntLiteral)), longConst(REPLACE_INLINE), inftyConst())); bindRuleSet(d, "defOpsReplace", add(NonDuplicateAppModPositionFeature.INSTANCE, diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java index b944d73cea4..fbdcdf353e6 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java @@ -32,6 +32,8 @@ import org.jspecify.annotations.NonNull; +import static de.uka.ilkd.key.strategy.SymExCost.*; + /// Strategy for symbolic execution rules. /// /// Do not create directly. Use [SymExStrategyFactory] instead. @@ -92,7 +94,7 @@ private Feature setupGlobalF(Feature dispatcher) { strategyProperties.getProperty(StrategyProperties.METHOD_OPTIONS_KEY); switch (methProp) { case StrategyProperties.METHOD_CONTRACT -> - methodSpecF = methodSpecFeature(longConst(SymExCost.METHOD_CONTRACT_PREFERENCE)); + methodSpecF = methodSpecFeature(longConst(METHOD_CONTRACT_PREFERENCE)); case StrategyProperties.METHOD_EXPAND, StrategyProperties.METHOD_NONE -> methodSpecF = methodSpecFeature(inftyConst()); default -> { @@ -126,7 +128,7 @@ private Feature setupGlobalF(Feature dispatcher) { SumFeature.createSum( loopContractExternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())), loopContractInternalFeature( - longConst(SymExCost.LOOP_CONTRACT_INTERNAL_TIEBREAK))); + longConst(LOOP_CONTRACT_INTERNAL_TIEBREAK))); loopBlockApplyHeadFeature = loopContractApplyHead(longConst(CostBand.BLOCK_CONTRACT.cost())); } else { @@ -139,7 +141,7 @@ private Feature setupGlobalF(Feature dispatcher) { final String mpsProperty = strategyProperties.getProperty(StrategyProperties.MPS_OPTIONS_KEY); if (mpsProperty.equals(StrategyProperties.MPS_MERGE)) { - mergeRuleF = mergeRuleFeature(longConst(SymExCost.MERGE_RULE)); + mergeRuleF = mergeRuleFeature(longConst(MERGE_RULE)); } else { mergeRuleF = mergeRuleFeature(inftyConst()); } @@ -157,19 +159,19 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_prog", ifZero(ThrownExceptionFeature.create(exceptionsWithPenalty, getServices()), - longConst(SymExCost.THROWING_PROGRAM_STEP), + longConst(THROWING_PROGRAM_STEP), ifZero(isBelow(add(ff.forF, not(ff.atom))), - longConst(SymExCost.PROGRAM_STEP_BELOW_QUANTIFIER), - longConst(SymExCost.PROGRAM_STEP)))); + longConst(PROGRAM_STEP_BELOW_QUANTIFIER), + longConst(PROGRAM_STEP)))); bindRuleSet(d, "simplify_prog_subset", longConst(CostBand.EXECUTE.cost())); - bindRuleSet(d, "simplify_expression", SymExCost.PROGRAM_STEP); + bindRuleSet(d, "simplify_expression", PROGRAM_STEP); bindRuleSet(d, "simplify_java", CostBand.SIMPLIFY.cost()); - bindRuleSet(d, "executeIntegerAssignment", SymExCost.PROGRAM_STEP); - bindRuleSet(d, "executeDoubleAssignment", SymExCost.PROGRAM_STEP); + bindRuleSet(d, "executeIntegerAssignment", PROGRAM_STEP); + bindRuleSet(d, "executeDoubleAssignment", PROGRAM_STEP); final Feature findDepthFeature = FindDepthFeature.getInstance(); @@ -192,7 +194,7 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "loop_expand", useLoopExpand ? longConst(0) : inftyConst()); bindRuleSet(d, "loop_scope_inv_taclet", useLoopInvTaclets ? longConst(0) : inftyConst()); bindRuleSet(d, "loop_scope_expand", - useLoopScopeExpand ? longConst(SymExCost.LOOP_SCOPE_EXPAND) : inftyConst()); + useLoopScopeExpand ? longConst(LOOP_SCOPE_EXPAND) : inftyConst()); final String methProp = @@ -205,9 +207,9 @@ private RuleSetDispatchFeature setupCostComputationF() { * is disabled. The original cost was 200 and is now increased to 2000 in order to * repress method expansion stronger when method treatment by contracts is chosen. */ - bindRuleSet(d, "method_expand", longConst(SymExCost.METHOD_EXPAND_REPRESSED)); + bindRuleSet(d, "method_expand", longConst(METHOD_EXPAND_REPRESSED)); case StrategyProperties.METHOD_EXPAND -> - bindRuleSet(d, "method_expand", longConst(SymExCost.METHOD_EXPAND)); + bindRuleSet(d, "method_expand", longConst(METHOD_EXPAND)); case StrategyProperties.METHOD_NONE -> bindRuleSet(d, "method_expand", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + methProp); } @@ -237,12 +239,12 @@ private RuleSetDispatchFeature setupCostComputationF() { } }); case StrategyProperties.MPS_SKIP -> - bindRuleSet(d, "merge_point", longConst(SymExCost.MERGE_POINT_SKIP)); + bindRuleSet(d, "merge_point", longConst(MERGE_POINT_SKIP)); case StrategyProperties.MPS_NONE -> bindRuleSet(d, "merge_point", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + mpsProp); } - bindRuleSet(d, "modal_tautology", longConst(SymExCost.MODAL_TAUTOLOGY)); + bindRuleSet(d, "modal_tautology", longConst(MODAL_TAUTOLOGY)); if (programsToRight) { bindRuleSet(d, "boxDiamondConv", @@ -250,7 +252,7 @@ private RuleSetDispatchFeature setupCostComputationF() { new FindPrefixRestrictionFeature( FindPrefixRestrictionFeature.PositionModifier.ALLOW_UPDATE_AS_PARENT, FindPrefixRestrictionFeature.PrefixChecker.ANTEC_POLARITY), - longConst(SymExCost.BOX_DIAMOND_CONV))); + longConst(BOX_DIAMOND_CONV))); } else { bindRuleSet(d, "boxDiamondConv", inftyConst()); } @@ -261,7 +263,7 @@ private RuleSetDispatchFeature setupCostComputationF() { final TermBuffer superFor = new TermBuffer(); bindRuleSet(d, "split_if", add(sum(superFor, SuperTermGenerator.upwards(any(), getServices()), - applyTF(superFor, not(ff.program))), longConst(SymExCost.SPLIT_IF))); + applyTF(superFor, not(ff.program))), longConst(SPLIT_IF))); return d; } From 51a0458cb24dd4a19cb24319c3ea142b5587e650 Mon Sep 17 00:00:00 2001 From: Richard Bubel Date: Fri, 10 Jul 2026 12:02:05 +0200 Subject: [PATCH 08/10] CostBand: cost()/at() return a constant Feature; document layer relation - cost() and at(delta) return the cost as a constant strategy Feature (ConstFeature over NumberRuleAppCost - exactly what longConst produced), so call sites lose the redundant wrapper: longConst(CostBand.DEFER.at(500)) becomes CostBand.DEFER.at(500), and bindRuleSet sites switch transparently to the Feature overload. The raw number stays available via value(); no code needed it. Behaviour unchanged (runAllProofs 674/674). - Document the deliberate relation between the layers: theory-local constants are absolute values on the same cost line, not anchored to a band, so retuning a tier moves exactly the rules placed on it. Point CostBand's javadoc at a real theory holder (was a stale IntegerCost reference), and note in IntegerArithmeticCosts why the file is not named "IntegerCosts" (would read as an integer-valued RuleAppCost type). --- .../de/uka/ilkd/key/strategy/FOLStrategy.java | 56 +++++++++---------- .../key/strategy/IntegerArithmeticCosts.java | 4 ++ .../ilkd/key/strategy/IntegerStrategy.java | 16 +++--- .../ilkd/key/strategy/JavaCardDLStrategy.java | 8 +-- .../uka/ilkd/key/strategy/StringStrategy.java | 22 ++++---- .../uka/ilkd/key/strategy/SymExStrategy.java | 16 +++--- .../prover/strategy/costbased/CostBand.java | 32 ++++++++--- 7 files changed, 86 insertions(+), 68 deletions(-) diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java index 9960d7c9446..4d007d76b9f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java @@ -85,7 +85,7 @@ public FOLStrategy(Proof proof, StrategyProperties strategyProperties) { private Feature setUpGlobalF(RuleSetDispatchFeature d) { final Feature oneStepSimplificationF = - oneStepSimplificationFeature(longConst(CostBand.REWRITE.cost())); + oneStepSimplificationFeature(CostBand.REWRITE.cost()); return add(d, oneStepSimplificationF); } @@ -107,7 +107,7 @@ private RuleSetDispatchFeature setupCostComputationF() { FindDepthFeature.getInstance(); bindRuleSet(d, "concrete", - add(longConst(CostBand.REWRITE.cost()), + add(CostBand.REWRITE.cost(), ScaleFeature.createScaled(findDepthFeature, 10.0))); bindRuleSet(d, "simplify", CostBand.SIMPLIFY.cost()); bindRuleSet(d, "simplify_enlarging", CostBand.ENLARGE.cost()); @@ -123,10 +123,10 @@ private RuleSetDispatchFeature setupCostComputationF() { not(contains(AssumptionProjection.create(0), FocusProjection.INSTANCE)))); bindRuleSet(d, "update_elim", - add(longConst(CostBand.ELIMINATE.cost()), + add(CostBand.ELIMINATE.cost(), ScaleFeature.createScaled(findDepthFeature, 10.0))); bindRuleSet(d, "update_apply_on_update", - add(longConst(CostBand.DECOMPOSE.cost()), + add(CostBand.DECOMPOSE.cost(), ScaleFeature.createScaled(findDepthFeature, 10.0))); bindRuleSet(d, "update_join", CostBand.SIMPLIFY.at(-100)); bindRuleSet(d, "update_apply", CostBand.SIMPLIFY.cost()); @@ -134,17 +134,17 @@ private RuleSetDispatchFeature setupCostComputationF() { setupSplitting(d); bindRuleSet(d, "gamma", add(not(isInstantiated("t")), - ifZero(allowQuantifierSplitting(), longConst(CostBand.DEFAULT.cost()), - longConst(CostBand.DEFAULT.at(50))))); + ifZero(allowQuantifierSplitting(), CostBand.DEFAULT.cost(), + CostBand.DEFAULT.at(50)))); bindRuleSet(d, "gamma_destructive", inftyConst()); bindRuleSet(d, "triggered", - add(not(isTriggerVariableInstantiated()), longConst(CostBand.DEFER.cost()))); + add(not(isTriggerVariableInstantiated()), CostBand.DEFER.cost())); bindRuleSet(d, "comprehension_split", add(applyTF(FocusFormulaProjection.INSTANCE, ff.notContainsExecutable), - ifZero(allowQuantifierSplitting(), longConst(CostBand.DEFER.at(2000)), - longConst(CostBand.DEFER.at(4500))))); + ifZero(allowQuantifierSplitting(), CostBand.DEFER.at(2000), + CostBand.DEFER.at(4500)))); setupReplaceKnown(d); @@ -152,22 +152,22 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "order_terms", add(termSmallerThan("commEqLeft", "commEqRight"), - longConst(CostBand.NORMALIZE.cost()))); + CostBand.NORMALIZE.cost())); bindRuleSet(d, "simplify_instanceof_static", - add(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.PREFER.cost()))); + add(EqNonDuplicateAppFeature.INSTANCE, CostBand.PREFER.cost())); - bindRuleSet(d, "evaluate_instanceof", longConst(CostBand.PREFER.cost())); + bindRuleSet(d, "evaluate_instanceof", CostBand.PREFER.cost()); bindRuleSet(d, "instanceof_to_exists", TopLevelFindFeature.ANTEC); bindRuleSet(d, "try_apply_subst", - add(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.SUBST.cost()))); + add(EqNonDuplicateAppFeature.INSTANCE, CostBand.SUBST.cost())); // delete cast bindRuleSet(d, "cast_deletion", ifZero(implicitCastNecessary(instOf("castedTerm")), - longConst(CostBand.NORMALIZE.cost()), + CostBand.NORMALIZE.cost(), inftyConst())); bindRuleSet(d, "type_hierarchy_def", CostBand.TYPE.at(-500)); @@ -291,13 +291,13 @@ public Name name() { protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "negationNormalForm", add(BelowBinderFeature.getInstance(), - longConst(CostBand.PREFER.cost()), + CostBand.PREFER.cost(), ScaleFeature.createScaled(FindDepthFeature.getInstance(), 10.0))); bindRuleSet(d, "moveQuantToLeft", - add(quantifiersMightSplit() ? longConst(CostBand.DEFAULT.cost()) + add(quantifiersMightSplit() ? CostBand.DEFAULT.cost() : applyTF(FocusFormulaProjection.INSTANCE, ff.quantifiedPureLitConjDisj), - longConst(CostBand.PREFER.at(-50)))); + CostBand.PREFER.at(-50))); bindRuleSet(d, "conjNormalForm", ifZero( @@ -307,7 +307,7 @@ protected void setupFormulaNormalisation(RuleSetDispatchFeature d) { ScaleFeature.createScaled(FindDepthFeature.getInstance(), 20)), inftyConst())); - bindRuleSet(d, "setEqualityBlastingRight", longConst(CostBand.DEFAULT.at(-100))); + bindRuleSet(d, "setEqualityBlastingRight", CostBand.DEFAULT.at(-100)); @@ -413,17 +413,17 @@ private void setupQuantifierInstantiation(RuleSetDispatchFeature d) { forEach(varInst, HeuristicInstantiation.forOption(classicTriggers()), add(instantiate("t", varInst), add(branchPrediction, - longConst(CostBand.DEFAULT.at(10), + CostBand.DEFAULT.at(10), // orders candidates of one predicted-cost band by their // connection to the sequent instead of formula position InstantiationTieBreakFeature.create(varInst, - triggersOption()))))))); + triggersOption())))))); final TermBuffer splitInst = new TermBuffer(); bindRuleSet(d, "triggered", SumFeature.createSum(forEach(splitInst, TriggeredInstantiations.create(true), - add(instantiateTriggeredVariable(splitInst), longConst(CostBand.DEFER.cost()))), - longConst(CostBand.DEFER.at(1000)))); + add(instantiateTriggeredVariable(splitInst), CostBand.DEFER.cost())), + CostBand.DEFER.at(1000))); } else { bindRuleSet(d, "gamma", inftyConst()); @@ -440,7 +440,7 @@ private void setupQuantifierInstantiationApproval(RuleSetDispatchFeature d) { not(eq(instOf("t"), varInst)))), InstantiationCostScalerFeature.create( InstantiationCost.create(instOf("t"), classicTriggers()), - longConst(CostBand.DEFAULT.cost())))); + CostBand.DEFAULT.cost()))); final TermBuffer splitInst = new TermBuffer(); bindRuleSet(d, "triggered", @@ -470,7 +470,7 @@ protected Feature notBelowQuantifier() { private void setupReplaceKnown(RuleSetDispatchFeature d) { final Feature commonF = add(ifZero(MatchedAssumesFeature.INSTANCE, DiffFindAndIfFeature.INSTANCE), - longConst(CostBand.NORMALIZE.cost()), + CostBand.NORMALIZE.cost(), add(DiffFindAndReplacewithFeature.INSTANCE, ScaleFeature.createScaled(CountMaxDPathFeature.INSTANCE, 10.0))); @@ -498,10 +498,10 @@ protected void setupSplitting(RuleSetDispatchFeature d) { sum(subFor, AllowedCutPositionsGenerator.INSTANCE, not(applyTF(subFor, ff.cutAllowed))); bindRuleSet(d, "beta", SumFeature.createSum(noCutsAllowed, - ifZero(PurePosDPathFeature.INSTANCE, longConst(CostBand.PREFER.at(300))), + ifZero(PurePosDPathFeature.INSTANCE, CostBand.PREFER.at(300)), ScaleFeature.createScaled(CountPosDPathFeature.INSTANCE, -3.0), ScaleFeature.createScaled(CountMaxDPathFeature.INSTANCE, 10.0), - longConst(CostBand.DEFAULT.at(20)))); + CostBand.DEFAULT.at(20))); TermBuffer superF = new TermBuffer(); final ProjectionToTerm splitCondition = sub(FocusProjection.INSTANCE, 0); bindRuleSet(d, "split_cond", add(// do not split over formulas containing auxiliary @@ -517,7 +517,7 @@ protected void setupSplitting(RuleSetDispatchFeature d) { sum(superF, SuperTermGenerator.upwards(any(), getServices()), applyTF(superF, not(ff.elemUpdate))), ifZero(applyTF(FocusProjection.INSTANCE, ContainsExecutableCodeTermFeature.PROGRAMS), - longConst(CostBand.DEFAULT.at(-100)), longConst(CostBand.DEFAULT.at(25))))); + CostBand.DEFAULT.at(-100), CostBand.DEFAULT.at(25)))); ProjectionToTerm cutFormula = instOf("cutFormula"); Feature countOccurrencesInSeq = ScaleFeature.createAffine(countOccurrences(cutFormula), -10, 10); @@ -539,7 +539,7 @@ protected void setupSplitting(RuleSetDispatchFeature d) { applyTF(FocusFormulaProjection.INSTANCE, ff.quantifiedClauseSet), ifZero(allowQuantifierSplitting(), - longConst(CostBand.DEFAULT.cost()), + CostBand.DEFAULT.cost(), longConst(CUT_DIRECT_STANDARD)))))); } diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java index fb572e7c594..527151c6a3e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerArithmeticCosts.java @@ -10,6 +10,10 @@ * holder is meant to move with its sub-strategy. Cross-theory levels stay in * {@link org.key_project.prover.strategy.costbased.CostBand}; only the arithmetic-internal ordering * lives here. All values are byte-identical to the literals they replace. + * + * The file is named after the integer-arithmetic *theory*; a plain "IntegerCost(s)" would suggest + * an integer-valued cost type (an implementation of RuleAppCost) rather than a holder of cost + * constants. */ /** Polynomial normal-form canonicalisation (Buchberger normalisation) — the "basic" substrate. */ diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java index 3d4eaa6bc00..6fd7bdf60bf 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java @@ -207,7 +207,7 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "order_terms", add(applyTF("commEqRight", tf.monomial), applyTF("commEqLeft", tf.polynomial), monSmallerThan("commEqLeft", "commEqRight", numbers), - longConst(CostBand.NORMALIZE.cost()))); + CostBand.NORMALIZE.cost())); final TermBuffer equation = new TermBuffer(); final TermBuffer left = new TermBuffer(); @@ -441,7 +441,7 @@ private void setupDivModDivision(RuleSetDispatchFeature d) { add(NotInScopeOfModalityFeature.INSTANCE, ifZero(isReduciblePolyE, // try again later longConst(-DivModCost.POLY_DIVISION)))))), - longConst(CostBand.DEFAULT.at(100)))); + CostBand.DEFAULT.at(100))); } @@ -787,9 +787,9 @@ private void setupMultiplyInequations(RuleSetDispatchFeature d, Feature baseCost ifZero(MatchedAssumesFeature.INSTANCE, SumFeature.createSum( applyTF("multFacLeft", tf.nonNegMonomial), - ifZero(applyTF("multRight", tf.literal), longConst(CostBand.DEFAULT.at(-100))), + ifZero(applyTF("multRight", tf.literal), CostBand.DEFAULT.at(-100)), ifZero(applyTF("multFacRight", tf.literal), - longConst(CostBand.DEFAULT.at(-100)), + CostBand.DEFAULT.at(-100), applyTF("multFacRight", tf.polynomial)), /* * ifZero ( applyTF ( "multRight", tf.literal ), longConst ( -100 ), applyTF ( @@ -808,9 +808,9 @@ private void setupMultiplyInequations(RuleSetDispatchFeature d, Feature baseCost ? ifZero(BranchMultiplicationCountFeature.atMost("multiply_2_inEq", BRANCH_MULT_CAP), longConst(0), notAllowedF) : longConst(0), - ifZero(exactlyBounded, longConst(CostBand.DEFAULT.cost()), + ifZero(exactlyBounded, CostBand.DEFAULT.cost(), onlyExactlyBounded ? notAllowedF - : ifZero(totallyBounded, longConst(CostBand.DEFAULT.at(100)), notAllowedF)) + : ifZero(totallyBounded, CostBand.DEFAULT.at(100), notAllowedF)) /* * ifZero ( partiallyBounded, longConst ( 400 ), notAllowedF ) ) ), */ @@ -949,9 +949,9 @@ private void setupInEqCaseDistinctions(RuleSetDispatchFeature d) { forEach(rootInf, RootsGenerator.create(intRel, getServices()), add(instantiate("cutFormula", rootInf), ifZero(applyTF(rootInf, op(Junctor.OR)), - longConst(CostBand.DEFAULT.at(50))), + CostBand.DEFAULT.at(50)), ifZero(applyTF(rootInf, op(Junctor.AND)), - longConst(CostBand.DEFAULT.at(20))))), + CostBand.DEFAULT.at(20)))), longConst(NonlinearArithmeticCost.MULTIPLY))); // noinspection unchecked diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java index 82d7e9186e6..8674af6982f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLStrategy.java @@ -217,7 +217,7 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "simplify_literals", // ifZero ( ConstraintStrengthenFeatureUC.create(proof), // longConst ( 0 ), - longConst(CostBand.ELIMINATE.cost())); + CostBand.ELIMINATE.cost()); bindRuleSet(d, "nonDuplicateAppCheckEq", EqNonDuplicateAppFeature.INSTANCE); @@ -244,7 +244,7 @@ private RuleSetDispatchFeature setupCostComputationF() { strategyProperties.getProperty(StrategyProperties.QUERYAXIOM_OPTIONS_KEY); switch (queryAxProp) { case StrategyProperties.QUERYAXIOM_ON -> - bindRuleSet(d, "query_axiom", longConst(CostBand.SOLVE.cost())); + bindRuleSet(d, "query_axiom", CostBand.SOLVE.cost()); case StrategyProperties.QUERYAXIOM_OFF -> bindRuleSet(d, "query_axiom", inftyConst()); default -> throw new RuntimeException("Unexpected strategy property " + queryAxProp); } @@ -262,7 +262,7 @@ private RuleSetDispatchFeature setupCostComputationF() { // partial inv axiom bindRuleSet(d, "partialInvAxiom", add(NonDuplicateAppModPositionFeature.INSTANCE, - longConst(CostBand.DEFER_STRONG.cost()))); + CostBand.DEFER_STRONG.cost())); // inReachableState bindRuleSet(d, "inReachableStateImplication", @@ -293,7 +293,7 @@ private RuleSetDispatchFeature setupCostComputationF() { bindRuleSet(d, "auto_induction_lemma", inftyConst()); } - bindRuleSet(d, "information_flow_contract_appl", longConst(CostBand.LAST_RESORT.cost())); + bindRuleSet(d, "information_flow_contract_appl", CostBand.LAST_RESORT.cost()); if (strategyProperties.contains(StrategyProperties.AUTO_INDUCTION_ON) || strategyProperties.contains(StrategyProperties.AUTO_INDUCTION_LEMMA_ON)) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java index 339637ac505..207e5b1a996 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/StringStrategy.java @@ -104,20 +104,20 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "defOpsSeqEquality", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero(add(applyTF("left", seqLiteral), applyTF("right", seqLiteral)), - longConst(CostBand.DEFER.at(500)), inftyConst()), + CostBand.DEFER.at(500), inftyConst()), belowModOpPenality)); bindRuleSet(d, "defOpsConcat", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero( or(applyTF("leftStr", not(seqLiteral)), applyTF("rightStr", not(seqLiteral))), - longConst(CostBand.DEFER.at(500)) + CostBand.DEFER.at(500) // concat is often introduced for construction purposes, // we do not want to use its definition right at the // beginning ), belowModOpPenality)); - bindRuleSet(d, "stringsSimplify", longConst(CostBand.NORMALIZE.cost())); + bindRuleSet(d, "stringsSimplify", CostBand.NORMALIZE.cost()); final TermFeature charOrIntLiteral = or(tf.charLiteral, tf.literal, or(add(OperatorClassTF.create(ParametricFunctionInstance.class), // XXX: @@ -131,14 +131,14 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { bindRuleSet(d, "defOpsReplace", add(NonDuplicateAppModPositionFeature.INSTANCE, ifZero(or(applyTF("str", not(seqLiteral)), applyTF("searchChar", not(charOrIntLiteral)), - applyTF("replChar", not(charOrIntLiteral))), longConst(CostBand.DEFER.cost()), + applyTF("replChar", not(charOrIntLiteral))), CostBand.DEFER.cost(), inftyConst()), belowModOpPenality)); bindRuleSet(d, "stringsReduceSubstring", - add(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(-400)))); + add(NonDuplicateAppModPositionFeature.INSTANCE, CostBand.DEFER.at(-400))); - bindRuleSet(d, "defOpsStartsEndsWith", longConst(CostBand.DEFER.at(-250))); + bindRuleSet(d, "defOpsStartsEndsWith", CostBand.DEFER.at(-250)); bindRuleSet(d, "stringsConcatNotBothLiterals", ifZero(MatchedAssumesFeature.INSTANCE, ifZero( @@ -146,21 +146,21 @@ private void setUpStringNormalisation(RuleSetDispatchFeature d) { applyTF(instOf("rightStr"), seqLiteral)), inftyConst()), inftyConst())); - bindRuleSet(d, "stringsReduceConcat", longConst(CostBand.DEFER.at(-400))); + bindRuleSet(d, "stringsReduceConcat", CostBand.DEFER.at(-400)); bindRuleSet(d, "stringsReduceOrMoveOutsideConcat", - ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(300)), + ifZero(NonDuplicateAppModPositionFeature.INSTANCE, CostBand.DEFER.at(300), inftyConst())); bindRuleSet(d, "stringsMoveReplaceInside", - ifZero(NonDuplicateAppModPositionFeature.INSTANCE, longConst(CostBand.DEFER.at(-100)), + ifZero(NonDuplicateAppModPositionFeature.INSTANCE, CostBand.DEFER.at(-100), inftyConst())); - bindRuleSet(d, "stringsExpandDefNormalOp", longConst(CostBand.DEFER.cost())); + bindRuleSet(d, "stringsExpandDefNormalOp", CostBand.DEFER.cost()); bindRuleSet(d, "stringsContainsDefInline", SumFeature - .createSum(EqNonDuplicateAppFeature.INSTANCE, longConst(CostBand.DEFER.at(500)))); + .createSum(EqNonDuplicateAppFeature.INSTANCE, CostBand.DEFER.at(500))); } @Override diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java index fbdcdf353e6..d782ad55aac 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java @@ -117,20 +117,20 @@ private Feature setupGlobalF(Feature dispatcher) { final String blockProperty = strategyProperties.getProperty(StrategyProperties.BLOCK_OPTIONS_KEY); if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_INTERNAL)) { - blockFeature = blockContractInternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); + blockFeature = blockContractInternalFeature(CostBand.BLOCK_CONTRACT.cost()); loopBlockFeature = - loopContractInternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); + loopContractInternalFeature(CostBand.BLOCK_CONTRACT.cost()); loopBlockApplyHeadFeature = - loopContractApplyHead(longConst(CostBand.BLOCK_CONTRACT.cost())); + loopContractApplyHead(CostBand.BLOCK_CONTRACT.cost()); } else if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_EXTERNAL)) { - blockFeature = blockContractExternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())); + blockFeature = blockContractExternalFeature(CostBand.BLOCK_CONTRACT.cost()); loopBlockFeature = SumFeature.createSum( - loopContractExternalFeature(longConst(CostBand.BLOCK_CONTRACT.cost())), + loopContractExternalFeature(CostBand.BLOCK_CONTRACT.cost()), loopContractInternalFeature( longConst(LOOP_CONTRACT_INTERNAL_TIEBREAK))); loopBlockApplyHeadFeature = - loopContractApplyHead(longConst(CostBand.BLOCK_CONTRACT.cost())); + loopContractApplyHead(CostBand.BLOCK_CONTRACT.cost()); } else { blockFeature = blockContractInternalFeature(inftyConst()); loopBlockFeature = loopContractExternalFeature(inftyConst()); @@ -164,7 +164,7 @@ private RuleSetDispatchFeature setupCostComputationF() { longConst(PROGRAM_STEP_BELOW_QUANTIFIER), longConst(PROGRAM_STEP)))); - bindRuleSet(d, "simplify_prog_subset", longConst(CostBand.EXECUTE.cost())); + bindRuleSet(d, "simplify_prog_subset", CostBand.EXECUTE.cost()); bindRuleSet(d, "simplify_expression", PROGRAM_STEP); @@ -176,7 +176,7 @@ private RuleSetDispatchFeature setupCostComputationF() { final Feature findDepthFeature = FindDepthFeature.getInstance(); bindRuleSet(d, "concrete_java", - add(longConst(CostBand.REWRITE.cost()), + add(CostBand.REWRITE.cost(), ScaleFeature.createScaled(findDepthFeature, 10.0))); // taclets for special invariant handling diff --git a/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java index dfbe711c025..36f535485a6 100644 --- a/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java +++ b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java @@ -3,6 +3,9 @@ * SPDX-License-Identifier: GPL-2.0-only */ package org.key_project.prover.strategy.costbased; +import org.key_project.prover.strategy.costbased.feature.ConstFeature; +import org.key_project.prover.strategy.costbased.feature.Feature; + /** * The shared, cross-theory priority ladder for the cost-based strategies. * @@ -12,8 +15,11 @@ * against every other theory — in practice the theories interleave at almost every * step. A band is therefore combination-relevant. The fine ordering of rules within a * band is expressed as {@code TIER.at(delta)} with a small delta; ordering that is internal to a - * single theory lives in that theory (e.g. {@code IntegerCost} for the integer (in)equality and - * division solver steps), not here. + * single theory lives in that theory's cost holder (e.g. {@code LinearInequationCost} for the + * integer inequation solver steps), not here. Theory-local constants are deliberately + * absolute values on the same cost line, not anchored to a band: they order the theory's + * own steps among each other and are unaffected when a tier is retuned — retuning a band moves + * exactly those rules that were deliberately placed on it. *

* *

@@ -78,21 +84,29 @@ public enum CostBand { LAST_RESORT(1_000_000); private final long base; + private final Feature costFeature; CostBand(long base) { this.base = base; + this.costFeature = ConstFeature.createConst(NumberRuleAppCost.create(base)); } - /** The band's cost. */ - public long cost() { - return base; + /** The band's cost, as a constant strategy {@link Feature} (ready to use in feature terms). */ + public Feature cost() { + return costFeature; } /** - * The band's cost shifted by a small theory-internal ordering delta. Use only for fine - * ordering within the band; larger, cross-theory steps deserve their own band. + * The band's cost shifted by a small theory-internal ordering delta, as a constant strategy + * {@link Feature}. Use only for fine ordering within the band; larger, cross-theory steps + * deserve their own band. */ - public long at(long delta) { - return base + delta; + public Feature at(long delta) { + return ConstFeature.createConst(NumberRuleAppCost.create(base + delta)); + } + + /** The band's raw cost value. */ + public long value() { + return base; } } From d1476294445d63ce38c1ae249c4f35d6dde0425c Mon Sep 17 00:00:00 2001 From: Drodt Date: Mon, 17 Aug 2026 08:31:27 +0200 Subject: [PATCH 09/10] Fix compilation error and move SymEx specific constants to SymExCost --- .../de/uka/ilkd/key/strategy/FOLStrategy.java | 2 +- .../uka/ilkd/key/strategy/IntegerStrategy.java | 5 +++-- .../de/uka/ilkd/key/strategy/SymExCost.java | 17 +++++++++++++++++ .../de/uka/ilkd/key/strategy/SymExStrategy.java | 14 +++++++------- .../prover/strategy/costbased/CostBand.java | 14 +------------- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java index 4d007d76b9f..796cfa8b09a 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/FOLStrategy.java @@ -413,7 +413,7 @@ private void setupQuantifierInstantiation(RuleSetDispatchFeature d) { forEach(varInst, HeuristicInstantiation.forOption(classicTriggers()), add(instantiate("t", varInst), add(branchPrediction, - CostBand.DEFAULT.at(10), + CostBand.DEFAULT.at(10), // orders candidates of one predicted-cost band by their // connection to the sequent instead of formula position InstantiationTieBreakFeature.create(varInst, diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java index 6fd7bdf60bf..f070759d05f 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/IntegerStrategy.java @@ -615,8 +615,9 @@ private void setupInEqSimp(RuleSetDispatchFeature d, IntegerLDT numbers) { // category "handling of non-linear inequations" if (arith == ArithTreatment.MODEL_SEARCH) { - setupMultiplyInequations(d, longConst(IN_EQ_SIMP_NON_LIN_COST), longConst(CostBand.DEFAULT.at(100), - AT_COST)); + setupMultiplyInequations(d, longConst(IN_EQ_SIMP_NON_LIN_COST), + CostBand.DEFAULT.at(100), + AT_COST); bindRuleSet(d, "inEqSimp_split_eq", add(TopLevelFindFeature.SUCC, longConst(NonlinearArithmeticCost.SPLIT_EQ))); diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java index 798fdde2cef..d7691d47825 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExCost.java @@ -20,6 +20,23 @@ final class SymExCost { private SymExCost() {} + /** + * Apply a block/loop contract instead of executing the block. MUST stay more eager (smaller) + * than {@link org.key_project.prover.strategy.costbased.CostBand#REWRITE} and every + * symbolic-execution program rule, otherwise the block starts + * to execute instead of being contracted. Value is the current sentinel; step 3 normalizes it + * to a modest value between {@link org.key_project.prover.strategy.costbased.CostBand#CLOSE} + * and {@link org.key_project.prover.strategy.costbased.CostBand#REWRITE}. + */ + static final long BLOCK_CONTRACT = Long.MIN_VALUE; + /** + * Apply a loop invariant instead of unrolling. Only needs to beat loop-unrolling / method + * expansion when enabled. (Currently above + * {@link org.key_project.prover.strategy.costbased.CostBand#CLOSE}; step 3 flips it below + * CLOSE.) + */ + static final long LOOP_INVARIANT = -20_000; + /** * A cheap concrete program step: {@code simplify_expression}, {@code execute*Assignment} and * the ordinary {@code simplify_prog} case — "advance the program by one small step". diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java index d782ad55aac..1511ae3ef29 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SymExStrategy.java @@ -117,20 +117,20 @@ private Feature setupGlobalF(Feature dispatcher) { final String blockProperty = strategyProperties.getProperty(StrategyProperties.BLOCK_OPTIONS_KEY); if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_INTERNAL)) { - blockFeature = blockContractInternalFeature(CostBand.BLOCK_CONTRACT.cost()); + blockFeature = blockContractInternalFeature(longConst(BLOCK_CONTRACT)); loopBlockFeature = - loopContractInternalFeature(CostBand.BLOCK_CONTRACT.cost()); + loopContractInternalFeature(longConst(BLOCK_CONTRACT)); loopBlockApplyHeadFeature = - loopContractApplyHead(CostBand.BLOCK_CONTRACT.cost()); + loopContractApplyHead(longConst(BLOCK_CONTRACT)); } else if (blockProperty.equals(StrategyProperties.BLOCK_CONTRACT_EXTERNAL)) { - blockFeature = blockContractExternalFeature(CostBand.BLOCK_CONTRACT.cost()); + blockFeature = blockContractExternalFeature(longConst(BLOCK_CONTRACT)); loopBlockFeature = SumFeature.createSum( - loopContractExternalFeature(CostBand.BLOCK_CONTRACT.cost()), + loopContractExternalFeature(longConst(BLOCK_CONTRACT)), loopContractInternalFeature( longConst(LOOP_CONTRACT_INTERNAL_TIEBREAK))); loopBlockApplyHeadFeature = - loopContractApplyHead(CostBand.BLOCK_CONTRACT.cost()); + loopContractApplyHead(longConst(BLOCK_CONTRACT)); } else { blockFeature = blockContractInternalFeature(inftyConst()); loopBlockFeature = loopContractExternalFeature(inftyConst()); @@ -180,7 +180,7 @@ private RuleSetDispatchFeature setupCostComputationF() { ScaleFeature.createScaled(findDepthFeature, 10.0))); // taclets for special invariant handling - bindRuleSet(d, "loopInvariant", CostBand.LOOP_INVARIANT.cost()); + bindRuleSet(d, "loopInvariant", longConst(LOOP_INVARIANT)); boolean useLoopExpand = strategyProperties.getProperty(StrategyProperties.LOOP_OPTIONS_KEY) .equals(StrategyProperties.LOOP_EXPAND); diff --git a/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java index 36f535485a6..63bc0686467 100644 --- a/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java +++ b/key.ncore.calculus/src/main/java/org/key_project/prover/strategy/costbased/CostBand.java @@ -26,22 +26,10 @@ * Care when changing: altering a band's value, or its order relative to other bands, * shifts the cross-theory search of all proofs — always re-verify with a full * runAllProofs and a Model-Search node-for-node comparison. Respect the hard ordering - * constraints noted on individual bands (notably {@link #BLOCK_CONTRACT}). + * constraints noted on individual bands. *

*/ public enum CostBand { - /** - * Apply a block/loop contract instead of executing the block. MUST stay more eager (smaller) - * than {@link #REWRITE} and every symbolic-execution program rule, otherwise the block starts - * to execute instead of being contracted. Value is the current sentinel; step 3 normalizes it - * to a modest value between {@link #CLOSE} and {@link #REWRITE}. - */ - BLOCK_CONTRACT(Long.MIN_VALUE), - /** - * Apply a loop invariant instead of unrolling. Only needs to beat loop-unrolling / method - * expansion when enabled. (Currently above {@link #CLOSE}; step 3 flips it below CLOSE.) - */ - LOOP_INVARIANT(-20_000), /** * Close the goal. Most eager of the ordinary bands: eager closure is completeness-neutral * (no free-variable calculus), so closing may always take precedence. From a1139a157d873b81da4fc0b56df5e88acbec1894 Mon Sep 17 00:00:00 2001 From: Drodt Date: Mon, 17 Aug 2026 10:12:28 +0200 Subject: [PATCH 10/10] Integrate set into new structure --- .../de/uka/ilkd/key/strategy/JavaCardDLCosts.java | 3 --- .../java/de/uka/ilkd/key/strategy/SetCosts.java | 14 ++++++++++++++ .../java/de/uka/ilkd/key/strategy/SetStrategy.java | 12 ++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/strategy/SetCosts.java diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java index 78a371093ef..9e978ea3c89 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/JavaCardDLCosts.java @@ -34,9 +34,6 @@ private JavaCardDLCost() {} */ static final long JAVA_INTEGER_SEMANTICS = -5000; - /** Loc-set CNF commutation ({@code cnf_setComm}). */ - static final long LOCSET_CNF_COMMUTE = -800; - /** Apply a class axiom ({@code classAxiom}). */ static final long CLASS_AXIOM = -250; diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SetCosts.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SetCosts.java new file mode 100644 index 00000000000..c0132bd13da --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SetCosts.java @@ -0,0 +1,14 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.strategy; + +public final class SetCosts { + private SetCosts() {} + + /// Set commutation (`setComm`). + static final long COMMUTE = -800; + + /// Set distribution (`setDist`). + static final long DIST = -2000; +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/strategy/SetStrategy.java b/key.core/src/main/java/de/uka/ilkd/key/strategy/SetStrategy.java index e4adec94f3e..a67f2b7642c 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/strategy/SetStrategy.java +++ b/key.core/src/main/java/de/uka/ilkd/key/strategy/SetStrategy.java @@ -19,6 +19,7 @@ import org.key_project.prover.rules.RuleApp; import org.key_project.prover.rules.RuleSet; import org.key_project.prover.sequent.PosInOccurrence; +import org.key_project.prover.strategy.costbased.CostBand; import org.key_project.prover.strategy.costbased.MutableState; import org.key_project.prover.strategy.costbased.RuleAppCost; import org.key_project.prover.strategy.costbased.feature.Feature; @@ -27,6 +28,9 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import static de.uka.ilkd.key.strategy.SetCosts.COMMUTE; +import static de.uka.ilkd.key.strategy.SetCosts.DIST; + /// Strategy for the sort generic theory of sets. /// Do not create directly; use [SetStrategyFactory] instead. public class SetStrategy extends AbstractFeatureStrategy implements ComponentStrategy { @@ -49,7 +53,7 @@ public SetStrategy(Proof proof, StrategyProperties strategyProperties) { private RuleSetDispatchFeature setupCostComputationF() { final RuleSetDispatchFeature d = new RuleSetDispatchFeature(); - bindRuleSet(d, "setEqualityBlastingRight", longConst(-90)); + bindRuleSet(d, "setEqualityBlastingRight", CostBand.DEFAULT.at(-90)); // Distribution duplicates the distributed set, so it is allowed only // where a resulting set is known to collapse. @@ -57,8 +61,8 @@ private RuleSetDispatchFeature setupCostComputationF() { final Feature operandCollapses = or(applyTF("distributedSet", collapses), applyTF("unionLeft", collapses), applyTF("unionRight", collapses)); bindRuleSet(d, "setDist", - add(ifZero(MatchedAssumesFeature.INSTANCE, operandCollapses, longConst(0)), - longConst(-2000))); + add(ifZero(MatchedAssumesFeature.INSTANCE, operandCollapses, CostBand.DEFAULT.cost()), + longConst(DIST))); bindRuleSet(d, "setAssoc", longConst(-850)); @@ -68,7 +72,7 @@ private RuleSetDispatchFeature setupCostComputationF() { add(applyTF("commLeft", not(or(stf.unionF, stf.intersectF))), applyTF("commRight", not(or(stf.unionF, stf.intersectF))), SetsSmallerThanFeature.create(instOf("commRight"), instOf("commLeft"), stf), - longConst(-800))); + longConst(COMMUTE))); return d; }