The demo below runs a simplified DPLL(T) loop. You write a small formula that mixes boolean variables (p, q, r) with integer constraints (written as xopn, e.g. x > 3). The solver assigns truth values to the booleans, then asks the arithmetic theory solver whether the active integer constraints are jointly satisfiable.
<p class="hint">
Build a formula mixing <b>boolean variables</b> (p, q, r) and <b>integer constraints</b>
on x (e.g. <code>x > 3</code>, <code>x <= 10</code>). Toggle which literals
appear in the formula, then watch DPLL(T) alternate between the SAT layer and the
arithmetic theory solver to find a satisfying assignment — or prove none exists.
</p>
<div class="panel">
<div class="col">
<div class="section-label">Boolean literals</div>
<div id="bool-toggles" class="toggle-group"></div>
</div>
<div class="col">
<div class="section-label">Integer constraints on x</div>
<div id="int-toggles" class="toggle-group"></div>
</div>
</div>
<div class="formula-box">
<span class="label">Active formula:</span>
<span id="formula-display" class="formula">—</span>
</div>
<div class="btns">
<button id="btn-solve" type="button">Run DPLL(T)</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="trace" class="trace"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; font-size: .93rem; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
code { background: #eef; border-radius: 3px; padding: 0 3px; font-size: .85em; }
.panel { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: .7rem; }
.col { flex: 1; min-width: 160px; }
.section-label { font-weight: 700; font-size: .82rem; color: #555; margin-bottom: .35rem; text-transform: uppercase; letter-spacing: .04em; }
.toggle-group { display: flex; flex-direction: column; gap: .3rem; }
.toggle { display: flex; align-items: center; gap: .5rem; cursor: pointer; }
.toggle input[type=checkbox] { width: 16px; height: 16px; cursor: pointer; }
.toggle label { cursor: pointer; font-size: .9rem; }
.formula-box { background: #f4f7fa; border: 1px solid #d0dae4; border-radius: 8px;
padding: .5rem .8rem; margin-bottom: .7rem; font-size: .9rem; }
.formula-box .label { font-weight: 600; color: #555; margin-right: .4rem; }
.formula { font-family: ui-monospace, monospace; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.trace { font-family: ui-monospace, monospace; font-size: .82rem; line-height: 1.6;
background: #f8fafb; border: 1px solid #d0dae4; border-radius: 8px;
padding: .6rem .9rem; max-height: 240px; overflow-y: auto; }
.trace:empty { display: none; }
.trace .step { margin-bottom: .15rem; }
.trace .ok { color: #0a7d33; }
.trace .bad { color: #c92f3c; }
.trace .info { color: #1d3557; }
.trace .result { font-weight: 700; margin-top: .4rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simplified DPLL(T) demo.
// The "formula" is a conjunction (AND) of the active literals.
// Boolean literals: p, ¬p, q, ¬q, r, ¬r
// Integer literals: constraints on a single integer variable x (linear arithmetic).
// The theory solver checks feasibility of the active integer constraints using
// a simple interval-propagation approach (exact for linear integer constraints).
// DPLL(T) loop:
// 1. SAT layer assigns truth values to boolean vars; collects active int constraints.
// 2. Theory solver checks if those constraints are jointly satisfiable.
// 3. If UNSAT, SAT layer learns a conflict clause and backtracks.
// 4. If SAT, the whole formula is SAT.
const BOOL_LITERALS = [
{ label: 'p', neg: false, var: 'p' },
{ label: '¬p', neg: true, var: 'p' },
{ label: 'q', neg: false, var: 'q' },
{ label: '¬q', neg: true, var: 'q' },
{ label: 'r', neg: false, var: 'r' },
{ label: '¬r', neg: true, var: 'r' },
];
const INT_LITERALS = [
{ label: 'x > 2', op: '>', val: 2 },
{ label: 'x > 5', op: '>', val: 5 },
{ label: 'x < 8', op: '<', val: 8 },
{ label: 'x < 3', op: '<', val: 3 },
{ label: 'x <= 5', op: '<=', val: 5 },
{ label: 'x >= 4', op: '>=', val: 4 },
];
const activeBool = new Set();
const activeInt = new Set();
function buildToggles() {
const bt = document.getElementById('bool-toggles');
BOOL_LITERALS.forEach((lit, i) => {
const id = 'bl' + i;
const wrap = document.createElement('label');
wrap.className = 'toggle';
const cb = document.createElement('input');
cb.type = 'checkbox'; cb.id = id;
cb.addEventListener('change', () => {
if (cb.checked) activeBool.add(i); else activeBool.delete(i);
updateFormula(); clearTrace();
});
const lbl = document.createElement('label');
lbl.htmlFor = id; lbl.textContent = lit.label;
wrap.appendChild(cb); wrap.appendChild(lbl);
bt.appendChild(wrap);
});
const it = document.getElementById('int-toggles');
INT_LITERALS.forEach((lit, i) => {
const id = 'il' + i;
const wrap = document.createElement('label');
wrap.className = 'toggle';
const cb = document.createElement('input');
cb.type = 'checkbox'; cb.id = id;
cb.addEventListener('change', () => {
if (cb.checked) activeInt.add(i); else activeInt.delete(i);
updateFormula(); clearTrace();
});
const lbl = document.createElement('label');
lbl.htmlFor = id; lbl.textContent = lit.label;
wrap.appendChild(cb); wrap.appendChild(lbl);
it.appendChild(wrap);
});
}
function updateFormula() {
const parts = [];
activeBool.forEach(i => parts.push(BOOL_LITERALS[i].label));
activeInt.forEach(i => parts.push(INT_LITERALS[i].label));
document.getElementById('formula-display').textContent =
parts.length ? parts.join(' ∧ ') : '(empty — trivially SAT)';
}
function clearTrace() {
document.getElementById('trace').innerHTML = '';
}
// Theory solver: given a set of int constraint indices, decide feasibility.
// Uses interval arithmetic: maintain [lo, hi] for x.
// Returns { sat: bool, reason: string, witness?: number }
function theorySolve(intIdxSet, log) {
let lo = -Infinity, hi = Infinity;
const active = [...intIdxSet].map(i => INT_LITERALS[i]);
if (active.length === 0) {
log(' Theory: no integer constraints — trivially SAT (x = 0)', 'info');
return { sat: true, reason: 'no constraints', witness: 0 };
}
active.forEach(c => {
if (c.op === '>') lo = Math.max(lo, c.val + 1);
if (c.op === '>=') lo = Math.max(lo, c.val);
if (c.op === '<') hi = Math.min(hi, c.val - 1);
if (c.op === '<=') hi = Math.min(hi, c.val);
});
const loStr = lo === -Infinity ? '-∞' : lo;
const hiStr = hi === Infinity ? '+∞' : hi;
log(" " + fmt('Theory: x ∈ [%loStr%, %hiStr%]', { loStr: loStr, hiStr: hiStr }), 'info');
if (lo <= hi) {
const w = lo === -Infinity ? (hi === Infinity ? 0 : hi) : lo;
log(" " + fmt('Theory: SAT — witness x = %w%', { w: w }), 'ok');
return { sat: true, reason: 'interval feasible', witness: w };
}
log(" " + fmt('Theory: UNSAT — empty interval [%loStr%, %hiStr%]', { loStr: loStr, hiStr: hiStr }), 'bad');
return { sat: false, reason: 'empty interval [' + loStr + ', ' + hiStr + ']' };
}
// DPLL(T) main loop.
// For this demo the "formula" is a flat conjunction; we enumerate all 2^3=8
// boolean assignments and check each against the theory.
function runDPLL() {
const trace = document.getElementById('trace');
trace.innerHTML = '';
const lines = [];
function log(msg, cls) {
const d = document.createElement('div');
d.className = 'step ' + (cls || '');
d.textContent = msg;
trace.appendChild(d);
}
const boolIdxs = [...activeBool];
const intIdxs = [...activeInt];
// Collect unique boolean variables used in active boolean literals.
const usedVars = [...new Set(boolIdxs.map(i => BOOL_LITERALS[i].var))];
log('=== DPLL(T) start ===', 'info');
if (boolIdxs.length === 0 && intIdxs.length === 0) {
log('Formula is empty — trivially SAT.', 'ok result');
return;
}
// Check for immediate boolean contradiction (p and ¬p both active).
const posVars = new Set(boolIdxs.filter(i => !BOOL_LITERALS[i].neg).map(i => BOOL_LITERALS[i].var));
const negVars = new Set(boolIdxs.filter(i => BOOL_LITERALS[i].neg).map(i => BOOL_LITERALS[i].var));
const boolConflict = [...posVars].filter(v => negVars.has(v));
if (boolConflict.length > 0) {
log(fmt('SAT layer: Boolean conflict — %map% both required.', { map: boolConflict.map(v => v + ' and ¬' + v).join(', ') }), 'bad');
log('Result: UNSATISFIABLE', 'bad result');
return;
}
// In a flat conjunction the boolean part forces exactly one assignment.
const assign = {};
usedVars.forEach(v => {
if (posVars.has(v)) assign[v] = true;
else if (negVars.has(v)) assign[v] = false;
else assign[v] = true; // free — pick true
});
const assignStr = usedVars.length
? usedVars.map(v => v + '=' + assign[v]).join(', ')
: '(no boolean vars)';
log(fmt('SAT layer: Boolean assignment — %assignStr%', { assignStr: assignStr }), 'info');
log('SAT layer: Sending integer constraints to theory solver...', 'info');
const tResult = theorySolve(new Set(intIdxs), log);
if (tResult.sat) {
const witStr = intIdxs.length > 0 ? ', x = ' + tResult.witness : '';
log(fmt('Result: SATISFIABLE (%assignStr%%witStr%)', { assignStr: assignStr, witStr: witStr }), 'ok result');
} else {
log('SAT layer: Theory conflict — learning clause and backtracking.', 'bad');
// In a unit conjunction with a forced boolean assignment, backtracking yields UNSAT.
log('SAT layer: No remaining boolean assignments to try.', 'bad');
log('Result: UNSATISFIABLE', 'bad result');
}
}
document.getElementById('btn-solve').addEventListener('click', runDPLL);
document.getElementById('btn-reset').addEventListener('click', () => {
activeBool.clear(); activeInt.clear();
document.querySelectorAll('input[type=checkbox]').forEach(cb => cb.checked = false);
updateFormula(); clearTrace();
});
buildToggles();
updateFormula();
<p class="hint">
Build a formula mixing <b>boolean variables</b> (p, q, r) and <b>integer constraints</b>
on x (e.g. <code>x > 3</code>, <code>x <= 10</code>). Toggle which literals
appear in the formula, then watch DPLL(T) alternate between the SAT layer and the
arithmetic theory solver to find a satisfying assignment — or prove none exists.
</p>
<div class="panel">
<div class="col">
<div class="section-label">Boolean literals</div>
<div id="bool-toggles" class="toggle-group"></div>
</div>
<div class="col">
<div class="section-label">Integer constraints on x</div>
<div id="int-toggles" class="toggle-group"></div>
</div>
</div>
<div class="formula-box">
<span class="label">Active formula:</span>
<span id="formula-display" class="formula">—</span>
</div>
<div class="btns">
<button id="btn-solve" type="button">Run DPLL(T)</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="trace" class="trace"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; font-size: .93rem; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
code { background: #eef; border-radius: 3px; padding: 0 3px; font-size: .85em; }
.panel { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: .7rem; }
.col { flex: 1; min-width: 160px; }
.section-label { font-weight: 700; font-size: .82rem; color: #555; margin-bottom: .35rem; text-transform: uppercase; letter-spacing: .04em; }
.toggle-group { display: flex; flex-direction: column; gap: .3rem; }
.toggle { display: flex; align-items: center; gap: .5rem; cursor: pointer; }
.toggle input[type=checkbox] { width: 16px; height: 16px; cursor: pointer; }
.toggle label { cursor: pointer; font-size: .9rem; }
.formula-box { background: #f4f7fa; border: 1px solid #d0dae4; border-radius: 8px;
padding: .5rem .8rem; margin-bottom: .7rem; font-size: .9rem; }
.formula-box .label { font-weight: 600; color: #555; margin-right: .4rem; }
.formula { font-family: ui-monospace, monospace; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .7rem; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.trace { font-family: ui-monospace, monospace; font-size: .82rem; line-height: 1.6;
background: #f8fafb; border: 1px solid #d0dae4; border-radius: 8px;
padding: .6rem .9rem; max-height: 240px; overflow-y: auto; }
.trace:empty { display: none; }
.trace .step { margin-bottom: .15rem; }
.trace .ok { color: #0a7d33; }
.trace .bad { color: #c92f3c; }
.trace .info { color: #1d3557; }
.trace .result { font-weight: 700; margin-top: .4rem; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simplified DPLL(T) demo.
// The "formula" is a conjunction (AND) of the active literals.
// Boolean literals: p, ¬p, q, ¬q, r, ¬r
// Integer literals: constraints on a single integer variable x (linear arithmetic).
// The theory solver checks feasibility of the active integer constraints using
// a simple interval-propagation approach (exact for linear integer constraints).
// DPLL(T) loop:
// 1. SAT layer assigns truth values to boolean vars; collects active int constraints.
// 2. Theory solver checks if those constraints are jointly satisfiable.
// 3. If UNSAT, SAT layer learns a conflict clause and backtracks.
// 4. If SAT, the whole formula is SAT.
const BOOL_LITERALS = [
{ label: 'p', neg: false, var: 'p' },
{ label: '¬p', neg: true, var: 'p' },
{ label: 'q', neg: false, var: 'q' },
{ label: '¬q', neg: true, var: 'q' },
{ label: 'r', neg: false, var: 'r' },
{ label: '¬r', neg: true, var: 'r' },
];
const INT_LITERALS = [
{ label: 'x > 2', op: '>', val: 2 },
{ label: 'x > 5', op: '>', val: 5 },
{ label: 'x < 8', op: '<', val: 8 },
{ label: 'x < 3', op: '<', val: 3 },
{ label: 'x <= 5', op: '<=', val: 5 },
{ label: 'x >= 4', op: '>=', val: 4 },
];
const activeBool = new Set();
const activeInt = new Set();
function buildToggles() {
const bt = document.getElementById('bool-toggles');
BOOL_LITERALS.forEach((lit, i) => {
const id = 'bl' + i;
const wrap = document.createElement('label');
wrap.className = 'toggle';
const cb = document.createElement('input');
cb.type = 'checkbox'; cb.id = id;
cb.addEventListener('change', () => {
if (cb.checked) activeBool.add(i); else activeBool.delete(i);
updateFormula(); clearTrace();
});
const lbl = document.createElement('label');
lbl.htmlFor = id; lbl.textContent = lit.label;
wrap.appendChild(cb); wrap.appendChild(lbl);
bt.appendChild(wrap);
});
const it = document.getElementById('int-toggles');
INT_LITERALS.forEach((lit, i) => {
const id = 'il' + i;
const wrap = document.createElement('label');
wrap.className = 'toggle';
const cb = document.createElement('input');
cb.type = 'checkbox'; cb.id = id;
cb.addEventListener('change', () => {
if (cb.checked) activeInt.add(i); else activeInt.delete(i);
updateFormula(); clearTrace();
});
const lbl = document.createElement('label');
lbl.htmlFor = id; lbl.textContent = lit.label;
wrap.appendChild(cb); wrap.appendChild(lbl);
it.appendChild(wrap);
});
}
function updateFormula() {
const parts = [];
activeBool.forEach(i => parts.push(BOOL_LITERALS[i].label));
activeInt.forEach(i => parts.push(INT_LITERALS[i].label));
document.getElementById('formula-display').textContent =
parts.length ? parts.join(' ∧ ') : '(empty — trivially SAT)';
}
function clearTrace() {
document.getElementById('trace').innerHTML = '';
}
// Theory solver: given a set of int constraint indices, decide feasibility.
// Uses interval arithmetic: maintain [lo, hi] for x.
// Returns { sat: bool, reason: string, witness?: number }
function theorySolve(intIdxSet, log) {
let lo = -Infinity, hi = Infinity;
const active = [...intIdxSet].map(i => INT_LITERALS[i]);
if (active.length === 0) {
log(' Theory: no integer constraints — trivially SAT (x = 0)', 'info');
return { sat: true, reason: 'no constraints', witness: 0 };
}
active.forEach(c => {
if (c.op === '>') lo = Math.max(lo, c.val + 1);
if (c.op === '>=') lo = Math.max(lo, c.val);
if (c.op === '<') hi = Math.min(hi, c.val - 1);
if (c.op === '<=') hi = Math.min(hi, c.val);
});
const loStr = lo === -Infinity ? '-∞' : lo;
const hiStr = hi === Infinity ? '+∞' : hi;
log(" " + fmt('Theory: x ∈ [%loStr%, %hiStr%]', { loStr: loStr, hiStr: hiStr }), 'info');
if (lo <= hi) {
const w = lo === -Infinity ? (hi === Infinity ? 0 : hi) : lo;
log(" " + fmt('Theory: SAT — witness x = %w%', { w: w }), 'ok');
return { sat: true, reason: 'interval feasible', witness: w };
}
log(" " + fmt('Theory: UNSAT — empty interval [%loStr%, %hiStr%]', { loStr: loStr, hiStr: hiStr }), 'bad');
return { sat: false, reason: 'empty interval [' + loStr + ', ' + hiStr + ']' };
}
// DPLL(T) main loop.
// For this demo the "formula" is a flat conjunction; we enumerate all 2^3=8
// boolean assignments and check each against the theory.
function runDPLL() {
const trace = document.getElementById('trace');
trace.innerHTML = '';
const lines = [];
function log(msg, cls) {
const d = document.createElement('div');
d.className = 'step ' + (cls || '');
d.textContent = msg;
trace.appendChild(d);
}
const boolIdxs = [...activeBool];
const intIdxs = [...activeInt];
// Collect unique boolean variables used in active boolean literals.
const usedVars = [...new Set(boolIdxs.map(i => BOOL_LITERALS[i].var))];
log('=== DPLL(T) start ===', 'info');
if (boolIdxs.length === 0 && intIdxs.length === 0) {
log('Formula is empty — trivially SAT.', 'ok result');
return;
}
// Check for immediate boolean contradiction (p and ¬p both active).
const posVars = new Set(boolIdxs.filter(i => !BOOL_LITERALS[i].neg).map(i => BOOL_LITERALS[i].var));
const negVars = new Set(boolIdxs.filter(i => BOOL_LITERALS[i].neg).map(i => BOOL_LITERALS[i].var));
const boolConflict = [...posVars].filter(v => negVars.has(v));
if (boolConflict.length > 0) {
log(fmt('SAT layer: Boolean conflict — %map% both required.', { map: boolConflict.map(v => v + ' and ¬' + v).join(', ') }), 'bad');
log('Result: UNSATISFIABLE', 'bad result');
return;
}
// In a flat conjunction the boolean part forces exactly one assignment.
const assign = {};
usedVars.forEach(v => {
if (posVars.has(v)) assign[v] = true;
else if (negVars.has(v)) assign[v] = false;
else assign[v] = true; // free — pick true
});
const assignStr = usedVars.length
? usedVars.map(v => v + '=' + assign[v]).join(', ')
: '(no boolean vars)';
log(fmt('SAT layer: Boolean assignment — %assignStr%', { assignStr: assignStr }), 'info');
log('SAT layer: Sending integer constraints to theory solver...', 'info');
const tResult = theorySolve(new Set(intIdxs), log);
if (tResult.sat) {
const witStr = intIdxs.length > 0 ? ', x = ' + tResult.witness : '';
log(fmt('Result: SATISFIABLE (%assignStr%%witStr%)', { assignStr: assignStr, witStr: witStr }), 'ok result');
} else {
log('SAT layer: Theory conflict — learning clause and backtracking.', 'bad');
// In a unit conjunction with a forced boolean assignment, backtracking yields UNSAT.
log('SAT layer: No remaining boolean assignments to try.', 'bad');
log('Result: UNSATISFIABLE', 'bad result');
}
}
document.getElementById('btn-solve').addEventListener('click', runDPLL);
document.getElementById('btn-reset').addEventListener('click', () => {
activeBool.clear(); activeInt.clear();
document.querySelectorAll('input[type=checkbox]').forEach(cb => cb.checked = false);
updateFormula(); clearTrace();
});
buildToggles();
updateFormula();
Comments
Loading comments...