Tactic reference#
This page is the inventory of formula-valued tactics and tacticals currently in Primo. It complements Goals, tactics, and strategies, 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; cancellation returns immediately without trying later arms |
|
Execution policy for |
Runs arms sequentially in declaration order; there is no parallel scheduler yet |
The sequential scheduler checks cancellation separately from a terminal SAT/UNSAT result.
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() and at_most(...) are the only built-in probe builders.
Custom predicates and named probes can be supplied in C++; see
conditional predicates and probes
for their semantics and the distinction from production selection analyses.
A broader reusable probe library
remains open work.
Preprocessing tactics#
These tactics form the production preprocessing pipeline. For their order, the effect of profiles and options, and how they preserve model-reconstruction information, see how tactics execute preprocessing. The tables below identify the API boundary of each block; being named and counted does not by itself make a phase independently configurable.
Initial and profile-specific tactics#
These stages surround the repeated simplification round. See the execution order and extended-term transformations.
Runtime name |
API status |
Enabled when and effect |
|---|---|---|
|
Public builder: make_simplify_constant_ites |
Both extended simplification and constant-ITE simplification are enabled; simplifies comparisons involving constant if-then-else terms. |
|
Public builder: make_simplify_extended_terms |
Extended simplification is enabled; applies bounded rewrites before introducing definitions. |
|
Public builder: make_lower_extended_terms |
Always included, even with preprocessing off; translates remaining extended operators to Boolean/linear terms and fresh definitions. |
|
Internal profile leaf |
Integer profile; up to eight syntactic substitution passes, without Gaussian elimination. |
|
Internal profile leaf |
Large-input bypass with solved-constant substitution enabled; one substitution pass with the configured Gaussian option. |
The two internal leaves are constructed by the profile-specific preparation. As with other sequences, an earlier terminal goal can prevent a later stage from running.
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 typed 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.
Finite-domain emission#
The internal finite-domain-constraints phase
belongs to finite-domain-preprocess. It is enabled when symmetry breaking,
at-most-one clauses, or equality definitions are selected, and emits the
corresponding constraints. See
finite domains and symmetry
for the required order and retained analysis results.
Solver tactics#
Runtime name |
Selection |
API status |
|---|---|---|
|
Classifies each goal by declared logic and formula features |
Public builder: make_solver_dispatch |
|
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 |
make_solver_tactic adapts a single-goal solver to the tactic contract: SAT produces an empty goal, UNSAT a false goal, and an inconclusive result a goal with a typed reason. See goal truth and alternatives for combination and stopping rules.
make_session_solver_strategy constructs
these engine leaves using the
solver engine entry points.
Its returned strategy borrows one solve’s frontend, statistics and context;
those objects must remain alive through execution. Preprocessing builders read
execution resources from TacticContext, but prepared finite-domain snapshots
still contain IDs tied to the original manager and query. See
representation and lifetime.
Dispatch priorities include model requirements as well as formula features. Model production is an execution-context capability, not part of the canonical strategy fingerprint. The experimental splitter, when enabled, precedes dispatch in a two-child sequence.
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.
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_portfolio over the SessionExecutor interface. Its sequential implementation replays original input in fresh contexts and therefore cannot pass a
Goalacross 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 constrain strategy experiments; none is an implemented parallel scheduler or external tactic language.
Typed parameters and source#
The parameter definitions are preprocessing tactics,
round options,
experimental tactics,
composition and
strategy nodes. Named invocations expose
:tactic.<name>.invocations and :time.tactic.<name>-us; a composed tactic
need not invoke every child on an already terminal goal. See the
composition experiment procedure.