Tactic reference#
This page is the inventory of formula-valued tactics and tacticals currently in Primo. It complements Tactic architecture, which defines their semantics, ownership, and invariants. The inventory distinguishes four levels of modularity:
public builder: declared in a public
src/smt/*.hppheader and available to other SMT-layer strategy code;production composite: a named tactic assembled and used by the current built-in strategy;
internal phase: a named tactic with its own counters, but constructible only as part of a larger composite;
session leaf: adapted to the tactic contract for each
check-sat, but its concrete solver closure is assembled by the solver-leaf module.
The names below are also the names used in
:tactic.<name>.invocations and :time.tactic.<name>-us statistics whenever a
named tactic is invoked.
Generic tacticals#
Executable builders are declared in src/smt/tactic.hpp; the immutable
inspection/composition layer is declared in src/smt/strategy.hpp.
Builder or type |
Operation |
Termination and branching behavior |
|---|---|---|
|
Lifts a single-goal transformation to |
Applies the leaf independently to every inconclusive alternative and preserves terminal alternatives |
|
Identity tactic, named |
Returns the same immutable |
|
Ordered composition |
Passes each result to the next tactic and stops when the combined result is terminal |
|
Per-goal branch |
Evaluates a read-only predicate and runs exactly one branch |
|
Bounded fixed-point iteration |
Stops on equivalence, a terminal result, a continuation decision, or the iteration bound; the optional observer sees every completed iteration |
|
Portfolio fallback |
Gives every arm the same original input and returns the first terminal result, or the last inconclusive result |
|
Execution policy for |
Runs arms sequentially in declaration order; there is no parallel scheduler yet |
Strategy provides Leaf, Sequence, Conditional, Repeat, TryInOrder,
TryFor, and Skip nodes, plus canonical printing and a stable fnv1a64:
fingerprint. compile(strategy) lowers the immutable AST to the executable
tacticals above. Strategy construction is C++-only.
assertion_count() is the exported named numeric probe and at_most(...)
constructs a named Boolean probe. The
solver dispatcher additionally computes a FormulaFeatureSummary, and the
preprocessing repeat uses reachable-term counts to decide whether adaptive
passes are worthwhile. Those two analyses are currently embedded in their
respective strategies rather than exported as reusable probe objects.
Preprocessing tactics#
Composites#
Runtime name |
API status |
Contents |
|---|---|---|
|
Public typed builder |
Typed NNF, implication, and associative-flattening settings; the integer-safe profile uses NNF without distribution |
|
Public typed builder |
Boolean-unit propagation followed by forced-theory-atom propagation |
|
Public typed builder |
Solved constants, optional Real Gaussian candidates, and configured ground-unit substitution |
|
Public typed builder |
Equality bridging and/or positive unit-equality consequences |
|
Public typed builder |
Selected equality evaluation and congruence phases plus the inseparable symmetry, AMO, and equality-definition choreography |
|
Public typed builder |
Arithmetic-atom canonicalization and/or Real-equality lowering at a typed pipeline stage |
|
Public typed builder |
Monotone-variable elimination with an ordered model-conversion record when model production is requested |
|
Public typed builder |
Mandatory mixed-theory purification and its dependent comparison-only arithmetic recanonicalization |
|
Public builder: |
Runs the selected phases below in a fixed order and deliberately completes the round even if an intermediate phase makes the formula terminal |
|
Production strategy leaf |
Records the immutable selected |
|
Production |
Repeats |
purify-mixed needs fresh proxy constants. It obtains them from
TacticContext::fresh_constant, a FreshConstantFactory the session installs
when it builds the context; the tactic holds no Frontend reference and throws
TacticError if a context without a factory executes it.
PreprocessingRoundOptions is immutable after strategy construction. It
selects phases without allowing them to read command-line state.
Named phases inside preprocessing-round#
Runtime name |
Enabled when |
Formula effect |
API status |
|---|---|---|---|
|
Equality bridging is selected |
Adds equality consequences recognized under supported Boolean structure |
Internal phase |
|
|
Simplifies enumerated finite-domain equalities |
Internal phase; the current production strategy always leaves this setting off |
|
Ground congruence generation is selected |
Adds bounded congruence clauses for existing ground applications |
Internal phase |
|
Always |
Propagates Boolean units and records assignments needed for model reconstruction |
Internal phase |
|
Always |
Propagates forced theory atoms and records equality/disequality facts |
Internal phase |
|
Solved-constant substitution is selected |
Eliminates accepted acyclic definitions and records substitutions |
Internal phase |
|
Unguarded or guarded ground substitution is selected |
Substitutes positive ground unit equalities; the guarded mode accepts only its bounded shrink criteria |
Internal phase |
|
Always |
Adds equality consequences from positive unit equalities |
Internal phase |
These fine-grained phase factories remain private. They are reachable through
the meaningful public composites above, whose typed option records are the
supported composition API. Their separate runtime names provide observability,
not permission to split operations that share an analysis or reconstruction
contract. The legacy completed-pass statistic is emitted by the repeat observer;
preprocessing-pass is no longer an executable tactic. Both
:preprocessing.passes and the compatibility counter
:tactic.preprocessing-pass.invocations count completed repeat iterations.
Gaussian elimination is not a separate tactic. It is an optional candidate
source inside solved-constant-substitution; syntactic candidates, Gaussian
candidates, cycle rejection, size guards, rewriting, and model reconstruction
share the same transformation boundary. Disabling solved-constant substitution
therefore also prevents that Gaussian candidate path from running.
Solver tactics#
Runtime name |
Selection |
API status |
|---|---|---|
|
Classifies each goal by declared logic and formula features |
Public builder: |
|
Used for formulas with no detected equality, application, or arithmetic; tries Boolean solving and then QF_UF on the same original goal |
Production |
|
Pure propositional conjunction |
Solver-leaf module |
|
Equality, uninterpreted applications, or Boolean-UF predicates without arithmetic |
Solver-leaf module |
|
Arithmetic without the mixed EUF features |
Solver-leaf module |
|
No theory atoms, but declared Real constants require a model |
Solver-leaf module |
|
Both arithmetic and equality or uninterpreted-predicate features |
Solver-leaf module |
|
Declared |
Solver-leaf module |
|
Declared |
Solver-leaf module; complete non-convex Int arrangement |
Experimental goal-generation tactics#
Runtime name |
Typed configuration |
Formula effect |
Status |
|---|---|---|---|
|
|
Selects up to |
Public typed builder, default off; enabled by |
experimental-split-bools is intentionally an experiment for exercising
multi-goal execution rather than a performance claim. k is restricted to
0–16 because the tactic eagerly materializes the goal vector. It selects from
the current transformed goal, not the original input: Boolean constants and
Bool-valued uninterpreted applications come first in deterministic depth-first
encounter order, followed by equality and arithmetic comparison atoms in the
same encounter order. With only m < k candidates, it returns 2^m goals.
The statistics :experimental.split-bools.input-goals, .selected, and
.output-goals expose the expansion.
make_solver_tactic(name, terms, solve) is the common formula adapter.
make_session_solver_strategy in solver_leaves owns all seven concrete leaf
closures, their model-term selection, conversion-chain forwarding, and the
typed SolverAnswer boundary. solver_engines exposes the complete
single-goal QF_UF, QF_LRA, QF_LIA, QF_UFLRA, and QF_UFLIA engine entry points and
contains their implementations and shared theory/model support. The
session supplies only its frontend, immutable solve options, statistics, and
execution context. With the experimental splitter disabled it compiles the
returned solver-dispatch strategy leaf; with splitting enabled it compiles a
two-child sequence containing the split leaf and dispatch.
The executable leaf borrows the frontend, statistics, and context through that
single execution; immutable solve options are held in shared value storage. A
solver strategy is therefore session-scoped, unlike a preprocessing strategy,
which reads everything it needs – terms, statistics, and fresh constants –
from the executing TacticContext and stays valid after its session is gone.
Model-term selection and the solve-qf-lra-model-only dispatch branch consult
the context capability. Model production is intentionally absent from the
canonical strategy parameters and fingerprint.
Every solver leaf returns a typed internal SolverAnswer through the
formula-valued contract:
an empty goal for sat, a goal containing false for unsat, and an
inconclusive goal with a typed reason for unknown, cancellation, resource
limits, or unsupported input. Message text is presentation-only. For an
AnyOf, leaves stop at the first satisfiable alternative, collapse all-unsat to
one false goal, and otherwise preserve inconclusive alternatives.
Transformations not yet represented as tactics#
The following production control step remains outside formula-level strategy composition and must not be mistaken for an available formula tactic:
the experimental QF_UF session portfolio, implemented by
execute_qf_uf_session_portfolioover theSessionExecutorinterface. Its sequential implementation reparses into fresh solver sessions and therefore cannot pass aGoalacross arms. Only the first arm’s typedResourceLimittriggers fallback; cancellation and ordinary unknown stop.
These are migration candidates, not a commitment that each should become a separate tactic. A useful portfolio boundary must have an independently useful configuration, a stable formula/reconstruction contract, meaningful cost or benefit variation, and its own measurements. Small implementation stages that cannot safely be reordered should remain inside one tactic. In particular, splitting candidate discovery from substitution merely to increase the tactic count would expose an unsafe and unhelpful portfolio action.
Current limitations for portfolio work#
Initial normalization executes before profile analysis, so production evaluates the prepared full strategy as a prefix and suffix rather than with one call to
compile.Only sequential formula-tactic and session-portfolio executors exist.
Strategy probes are partly embedded in
preprocessing_strategy.cppandsolver_strategy.cpprather than collected behind a reusable probe API.There is no tactic-expression language, tactic registry, parser for the canonical representation, or per-arm isolated statistics record. Canonical strategy text and fingerprints are inspection identities only.
All alternatives in one
AnyOfshare aTermManager; session-level arms that reparse input require isolated contexts and cannot exchangeTermIdvalues.
These limitations define the next modularization work needed before learned or hand-built portfolios can treat tactic pipelines as stable selectable actions.