Documentation Index
Fetch the complete documentation index at: https://mintlify.com/niklasso/minisat/llms.txt
Use this file to discover all available pages before exploring further.
MiniSat supports incremental solving, allowing you to add clauses between solve calls and reuse learned information.
Basic incremental solving
You can add clauses between solve calls:
#include "minisat/core/Solver.h"
#include <iostream>
using namespace Minisat;
int main() {
Solver solver;
Var a = solver.newVar();
Var b = solver.newVar();
Var c = solver.newVar();
// Initial formula: (a OR b)
solver.addClause(mkLit(a), mkLit(b));
// First solve
bool sat = solver.solve();
std::cout << "First solve: " << (sat ? "SAT" : "UNSAT") << std::endl;
if (sat) {
std::cout << "a = " << (solver.modelValue(a) == l_True) << std::endl;
std::cout << "b = " << (solver.modelValue(b) == l_True) << std::endl;
}
// Add more clauses: (NOT a) AND (NOT b)
solver.addClause(~mkLit(a));
solver.addClause(~mkLit(b));
// Second solve - should be UNSAT
sat = solver.solve();
std::cout << "Second solve: " << (sat ? "SAT" : "UNSAT") << std::endl;
return 0;
}
Incremental solving only works in one direction: you can add clauses but not remove them. Each solve call considers all previously added clauses.
Solving multiple configurations
A common pattern is testing different configurations:
#include "minisat/core/Solver.h"
#include <iostream>
#include <vector>
using namespace Minisat;
void testConfiguration(Solver& solver,
const std::vector<std::pair<Var, bool>>& config) {
// Add configuration as unit clauses
for (const auto& [var, value] : config) {
if (value) {
solver.addClause(mkLit(var));
} else {
solver.addClause(~mkLit(var));
}
}
bool sat = solver.solve();
std::cout << "Configuration " << (sat ? "valid" : "invalid") << std::endl;
}
int main() {
Solver solver;
Var x = solver.newVar();
Var y = solver.newVar();
Var z = solver.newVar();
// Add constraints
solver.addClause(mkLit(x), mkLit(y)); // At least one of x or y
solver.addClause(~mkLit(y), mkLit(z)); // If y then z
// Test configuration: x=true, y=false
Solver test1 = solver; // Make a copy
test1.addClause(mkLit(x));
test1.addClause(~mkLit(y));
std::cout << "x=T, y=F: " << (test1.solve() ? "SAT" : "UNSAT") << std::endl;
// Test configuration: x=false, y=true, z=false
Solver test2 = solver; // Make another copy
test2.addClause(~mkLit(x));
test2.addClause(mkLit(y));
test2.addClause(~mkLit(z));
std::cout << "x=F, y=T, z=F: " << (test2.solve() ? "SAT" : "UNSAT") << std::endl;
return 0;
}
Copying a Solver object creates an independent copy. For truly incremental solving without copying, use assumptions (see the assumptions page).
You can construct complex formulas step by step:
#include "minisat/core/Solver.h"
#include <iostream>
#include <vector>
using namespace Minisat;
// Add at-most-one constraint: at most one variable can be true
void addAtMostOne(Solver& solver, const std::vector<Var>& vars) {
for (size_t i = 0; i < vars.size(); i++) {
for (size_t j = i + 1; j < vars.size(); j++) {
// NOT xi OR NOT xj
solver.addClause(~mkLit(vars[i]), ~mkLit(vars[j]));
}
}
}
// Add at-least-one constraint: at least one variable must be true
void addAtLeastOne(Solver& solver, const std::vector<Var>& vars) {
vec<Lit> clause;
for (Var v : vars) {
clause.push(mkLit(v));
}
solver.addClause(clause);
}
// Add exactly-one constraint
void addExactlyOne(Solver& solver, const std::vector<Var>& vars) {
addAtLeastOne(solver, vars);
addAtMostOne(solver, vars);
}
int main() {
Solver solver;
// Create variables for 3 choices
std::vector<Var> choices;
for (int i = 0; i < 3; i++) {
choices.push_back(solver.newVar());
}
// Exactly one choice must be selected
addExactlyOne(solver, choices);
// Add application-specific constraints
// If choice 0, then some other constraint...
// ...
bool sat = solver.solve();
if (sat) {
std::cout << "Selected choice: ";
for (size_t i = 0; i < choices.size(); i++) {
if (solver.modelValue(choices[i]) == l_True) {
std::cout << i << std::endl;
break;
}
}
}
return 0;
}
Each solve call benefits from clauses learned in previous calls:
Solver solver;
// ... add clauses and solve ...
bool sat1 = solver.solve();
std::cout << "Learned clauses: " << solver.nLearnts() << std::endl;
// Next solve benefits from learned clauses
solver.addClause(/* new clause */);
bool sat2 = solver.solve();
Call simplify between solves
Periodically simplify the clause database:
solver.addClause(/* ... */);
solver.simplify(); // Remove satisfied clauses
bool sat = solver.solve();
Instead of copying the solver, use assumptions (see next page) for temporary constraints:
// Bad: copying is expensive
Solver copy = original;
copy.addClause(~mkLit(x));
bool sat = copy.solve();
// Good: use assumptions instead
vec<Lit> assumptions;
assumptions.push(~mkLit(x));
bool sat = original.solve(assumptions);
Checking satisfiability multiple times
You can solve multiple times as long as the solver remains in a consistent state:
Solver solver;
// Add base constraints
solver.addClause(mkLit(a), mkLit(b));
for (int i = 0; i < 10; i++) {
// Add dynamic constraints
Var v = solver.newVar();
solver.addClause(~mkLit(v), mkLit(a));
if (!solver.solve()) {
std::cout << "Became UNSAT at iteration " << i << std::endl;
break;
}
std::cout << "Still SAT with " << solver.nVars() << " variables" << std::endl;
}
The solver maintains all learned information between calls, making incremental solving much faster than creating a new solver each time.