@stevenvo780/st-lang 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/ast/nodes.d.ts +18 -1
  2. package/dist/ast/nodes.d.ts.map +1 -1
  3. package/dist/lexer/lexer.d.ts +2 -1
  4. package/dist/lexer/lexer.d.ts.map +1 -1
  5. package/dist/lexer/lexer.js +4 -2
  6. package/dist/lexer/lexer.js.map +1 -1
  7. package/dist/lexer/tokens.d.ts +17 -0
  8. package/dist/lexer/tokens.d.ts.map +1 -1
  9. package/dist/lexer/tokens.js +58 -21
  10. package/dist/lexer/tokens.js.map +1 -1
  11. package/dist/parser/parser.d.ts +4 -1
  12. package/dist/parser/parser.d.ts.map +1 -1
  13. package/dist/parser/parser.js +86 -2
  14. package/dist/parser/parser.js.map +1 -1
  15. package/dist/profiles/classical/dpll.d.ts +10 -0
  16. package/dist/profiles/classical/dpll.d.ts.map +1 -0
  17. package/dist/profiles/classical/dpll.js +446 -0
  18. package/dist/profiles/classical/dpll.js.map +1 -0
  19. package/dist/profiles/classical/first-order.d.ts +1 -0
  20. package/dist/profiles/classical/first-order.d.ts.map +1 -1
  21. package/dist/profiles/classical/first-order.js +35 -0
  22. package/dist/profiles/classical/first-order.js.map +1 -1
  23. package/dist/profiles/classical/propositional.d.ts +6 -1
  24. package/dist/profiles/classical/propositional.d.ts.map +1 -1
  25. package/dist/profiles/classical/propositional.js +434 -38
  26. package/dist/profiles/classical/propositional.js.map +1 -1
  27. package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
  28. package/dist/profiles/paraconsistent/belnap.js +151 -3
  29. package/dist/profiles/paraconsistent/belnap.js.map +1 -1
  30. package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
  31. package/dist/profiles/shared/tableau-engine.js +49 -22
  32. package/dist/profiles/shared/tableau-engine.js.map +1 -1
  33. package/dist/runtime/educational-notes.d.ts +27 -0
  34. package/dist/runtime/educational-notes.d.ts.map +1 -0
  35. package/dist/runtime/educational-notes.js +100 -0
  36. package/dist/runtime/educational-notes.js.map +1 -0
  37. package/dist/runtime/format.d.ts.map +1 -1
  38. package/dist/runtime/format.js +4 -0
  39. package/dist/runtime/format.js.map +1 -1
  40. package/dist/runtime/formula-factory.d.ts +26 -0
  41. package/dist/runtime/formula-factory.d.ts.map +1 -0
  42. package/dist/runtime/formula-factory.js +67 -0
  43. package/dist/runtime/formula-factory.js.map +1 -0
  44. package/dist/runtime/interpreter.d.ts +41 -0
  45. package/dist/runtime/interpreter.d.ts.map +1 -1
  46. package/dist/runtime/interpreter.js +1039 -246
  47. package/dist/runtime/interpreter.js.map +1 -1
  48. package/dist/tests/arithmetic.test.js +2 -1
  49. package/dist/tests/arithmetic.test.js.map +1 -1
  50. package/dist/tests/examples.test.js +12 -1
  51. package/dist/tests/examples.test.js.map +1 -1
  52. package/dist/tests/exhaustive-matrix.test.js +1 -1
  53. package/dist/tests/exhaustive-matrix.test.js.map +1 -1
  54. package/dist/tests/limits.test.js +17 -4
  55. package/dist/tests/limits.test.js.map +1 -1
  56. package/dist/tests/result-bindings.test.d.ts +2 -0
  57. package/dist/tests/result-bindings.test.d.ts.map +1 -0
  58. package/dist/tests/result-bindings.test.js +59 -0
  59. package/dist/tests/result-bindings.test.js.map +1 -0
  60. package/dist/tests/stress-hardware.test.d.ts +2 -0
  61. package/dist/tests/stress-hardware.test.d.ts.map +1 -0
  62. package/dist/tests/stress-hardware.test.js +673 -0
  63. package/dist/tests/stress-hardware.test.js.map +1 -0
  64. package/dist/tests/v1-features.test.js +1 -1
  65. package/dist/tests/v1-features.test.js.map +1 -1
  66. package/dist/types/index.d.ts +1 -1
  67. package/dist/types/index.d.ts.map +1 -1
  68. package/dist/utils/memo.d.ts +3 -0
  69. package/dist/utils/memo.d.ts.map +1 -1
  70. package/dist/utils/memo.js +30 -0
  71. package/dist/utils/memo.js.map +1 -1
  72. package/package.json +2 -1
@@ -5,6 +5,8 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ClassicalPropositional = void 0;
7
7
  exports.collectAtoms = collectAtoms;
8
+ exports.evaluateClassical = evaluateClassical;
9
+ exports.generateValuationsLazy = generateValuationsLazy;
8
10
  exports.formulaToString = formulaToString;
9
11
  exports.toNNF = toNNF;
10
12
  exports.toCNF = toCNF;
@@ -12,7 +14,9 @@ exports.toDNF = toDNF;
12
14
  exports.extractClauses = extractClauses;
13
15
  const formula_classifier_1 = require("../../runtime/formula-classifier");
14
16
  const format_1 = require("../../runtime/format");
17
+ const educational_notes_1 = require("../../runtime/educational-notes");
15
18
  const memo_1 = require("../../utils/memo");
19
+ const dpll_1 = require("./dpll");
16
20
  // --- Utilidades de fórmulas ---
17
21
  function collectAtoms(f) {
18
22
  return (0, memo_1.memoizeAtoms)(f, computeCollectAtoms);
@@ -32,39 +36,39 @@ function computeCollectAtoms(f) {
32
36
  walk(f);
33
37
  return atoms;
34
38
  }
35
- function evaluate(f, v) {
39
+ function evaluateClassical(f, v) {
36
40
  switch (f.kind) {
37
41
  case 'atom':
38
42
  return f.name ? (v[f.name] ?? false) : false;
39
43
  case 'not':
40
- return f.args && f.args[0] ? !evaluate(f.args[0], v) : false;
44
+ return f.args && f.args[0] ? !evaluateClassical(f.args[0], v) : false;
41
45
  case 'and':
42
46
  return f.args && f.args[0] && f.args[1]
43
- ? evaluate(f.args[0], v) && evaluate(f.args[1], v)
47
+ ? evaluateClassical(f.args[0], v) && evaluateClassical(f.args[1], v)
44
48
  : false;
45
49
  case 'or':
46
50
  return f.args && f.args[0] && f.args[1]
47
- ? evaluate(f.args[0], v) || evaluate(f.args[1], v)
51
+ ? evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v)
48
52
  : false;
49
53
  case 'implies':
50
54
  return f.args && f.args[0] && f.args[1]
51
- ? !evaluate(f.args[0], v) || evaluate(f.args[1], v)
55
+ ? !evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v)
52
56
  : false;
53
57
  case 'biconditional':
54
58
  return f.args && f.args[0] && f.args[1]
55
- ? evaluate(f.args[0], v) === evaluate(f.args[1], v)
59
+ ? evaluateClassical(f.args[0], v) === evaluateClassical(f.args[1], v)
56
60
  : false;
57
61
  case 'nand':
58
62
  return f.args && f.args[0] && f.args[1]
59
- ? !(evaluate(f.args[0], v) && evaluate(f.args[1], v))
63
+ ? !(evaluateClassical(f.args[0], v) && evaluateClassical(f.args[1], v))
60
64
  : false;
61
65
  case 'nor':
62
66
  return f.args && f.args[0] && f.args[1]
63
- ? !(evaluate(f.args[0], v) || evaluate(f.args[1], v))
67
+ ? !(evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v))
64
68
  : false;
65
69
  case 'xor':
66
70
  return f.args && f.args[0] && f.args[1]
67
- ? evaluate(f.args[0], v) !== evaluate(f.args[1], v)
71
+ ? evaluateClassical(f.args[0], v) !== evaluateClassical(f.args[1], v)
68
72
  : false;
69
73
  default:
70
74
  throw new Error(`Operador lógico no soportado en evaluación clásica: ${f.kind}`);
@@ -79,8 +83,8 @@ function generateValuations(atoms) {
79
83
  const n = atoms.length;
80
84
  if (n === 0)
81
85
  return [{}];
82
- if (n > 20)
83
- throw new Error('Demasiadas variables para tabla de verdad (>20)');
86
+ if (n > 23)
87
+ throw new Error('Demasiadas variables para tabla de verdad (>23)');
84
88
  const total = 1 << n;
85
89
  const valuations = new Array(total);
86
90
  for (let i = 0; i < total; i++) {
@@ -93,6 +97,184 @@ function generateValuations(atoms) {
93
97
  }
94
98
  return valuations;
95
99
  }
100
+ /**
101
+ * Generador lazy de valuaciones para streaming (usado por el intérprete para truth_table masivas).
102
+ */
103
+ function* generateValuationsLazy(atoms) {
104
+ const n = atoms.length;
105
+ if (n === 0) {
106
+ yield {};
107
+ return;
108
+ }
109
+ const total = 1 << n;
110
+ for (let i = 0; i < total; i++) {
111
+ const v = {};
112
+ for (let j = 0; j < n; j++) {
113
+ v[atoms[j]] = Boolean((i >> (n - 1 - j)) & 1);
114
+ }
115
+ yield v;
116
+ }
117
+ }
118
+ function bvCreate(total) {
119
+ return new Uint32Array((total + 31) >>> 5);
120
+ }
121
+ function bvOnes(total) {
122
+ const words = (total + 31) >>> 5;
123
+ const v = new Uint32Array(words);
124
+ v.fill(0xffffffff);
125
+ // Clear trailing bits in last word
126
+ const tail = total & 31;
127
+ if (tail)
128
+ v[words - 1] = (1 << tail) - 1;
129
+ return v;
130
+ }
131
+ function bvAnd(a, b) {
132
+ const r = new Uint32Array(a.length);
133
+ for (let i = 0; i < a.length; i++)
134
+ r[i] = a[i] & b[i];
135
+ return r;
136
+ }
137
+ function bvOr(a, b) {
138
+ const r = new Uint32Array(a.length);
139
+ for (let i = 0; i < a.length; i++)
140
+ r[i] = a[i] | b[i];
141
+ return r;
142
+ }
143
+ function bvXor(a, b) {
144
+ const r = new Uint32Array(a.length);
145
+ for (let i = 0; i < a.length; i++)
146
+ r[i] = a[i] ^ b[i];
147
+ return r;
148
+ }
149
+ function bvNot(a, ones) {
150
+ const r = new Uint32Array(a.length);
151
+ for (let i = 0; i < a.length; i++)
152
+ r[i] = ~a[i] & ones[i];
153
+ return r;
154
+ }
155
+ function bvIsZero(a) {
156
+ for (let i = 0; i < a.length; i++)
157
+ if (a[i] !== 0)
158
+ return false;
159
+ return true;
160
+ }
161
+ function bvEquals(a, b) {
162
+ for (let i = 0; i < a.length; i++)
163
+ if (a[i] !== b[i])
164
+ return false;
165
+ return true;
166
+ }
167
+ function bvPopcount(a) {
168
+ let count = 0;
169
+ for (let i = 0; i < a.length; i++) {
170
+ let v = a[i];
171
+ v = v - ((v >>> 1) & 0x55555555);
172
+ v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
173
+ count += (((v + (v >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
174
+ }
175
+ return count;
176
+ }
177
+ function bvTestBit(a, i) {
178
+ return (a[i >>> 5] & (1 << (i & 31))) !== 0;
179
+ }
180
+ // Find first set bit, or -1
181
+ function bvFirstSet(a) {
182
+ for (let w = 0; w < a.length; w++) {
183
+ if (a[w] !== 0)
184
+ return ((w << 5) + Math.clz32(a[w] & (-a[w] | 0))) ^ 31;
185
+ }
186
+ return -1;
187
+ }
188
+ function evaluateBitset(formula, atoms) {
189
+ const n = atoms.length;
190
+ if (n > 26)
191
+ throw new Error('Demasiadas variables para evaluación bitset (>26)');
192
+ const total = 1 << n;
193
+ const allOnes = bvOnes(total);
194
+ const words = allOnes.length;
195
+ // Build atom masks: atom j is true when bit j of the row index is 1.
196
+ // Row index i has bit j set when (i >>> (n-1-j)) & 1.
197
+ // Equivalent: word w, bit b (i = w*32+b), atom j true iff ((w*32+b) >>> (n-1-j)) & 1.
198
+ const atomMasks = new Map();
199
+ for (let j = 0; j < n; j++) {
200
+ const shift = n - 1 - j;
201
+ const mask = bvCreate(total);
202
+ // The pattern for atom j repeats with period 2^(shift+1).
203
+ // Within each period, the first 2^shift bits are 0, next 2^shift are 1.
204
+ // For shift < 5, the pattern fits within single words and we can use word-level fill.
205
+ if (shift < 5) {
206
+ // Pattern period in bits
207
+ const period = 1 << (shift + 1);
208
+ const halfPeriod = 1 << shift;
209
+ // Build a 32-bit pattern
210
+ let pattern = 0;
211
+ for (let b = 0; b < 32; b++) {
212
+ if (b % period >= halfPeriod)
213
+ pattern |= 1 << b;
214
+ }
215
+ mask.fill(pattern);
216
+ }
217
+ else {
218
+ // shift >= 5: consecutive words are all-0 or all-1
219
+ const wordPeriod = 1 << (shift - 5 + 1); // period in words
220
+ const halfWordPeriod = wordPeriod >>> 1;
221
+ for (let w = 0; w < words; w++) {
222
+ const posInPeriod = w % wordPeriod;
223
+ mask[w] = posInPeriod >= halfWordPeriod ? 0xffffffff : 0;
224
+ }
225
+ }
226
+ // Clear trailing bits
227
+ const tail = total & 31;
228
+ if (tail && words > 0)
229
+ mask[words - 1] &= (1 << tail) - 1;
230
+ atomMasks.set(atoms[j], mask);
231
+ }
232
+ function evalBits(f) {
233
+ switch (f.kind) {
234
+ case 'atom':
235
+ return atomMasks.get(f.name) ?? bvCreate(total);
236
+ case 'not':
237
+ return bvNot(evalBits(f.args[0]), allOnes);
238
+ case 'and':
239
+ return bvAnd(evalBits(f.args[0]), evalBits(f.args[1]));
240
+ case 'or':
241
+ return bvOr(evalBits(f.args[0]), evalBits(f.args[1]));
242
+ case 'implies':
243
+ return bvOr(bvNot(evalBits(f.args[0]), allOnes), evalBits(f.args[1]));
244
+ case 'biconditional':
245
+ return bvNot(bvXor(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
246
+ case 'xor':
247
+ return bvXor(evalBits(f.args[0]), evalBits(f.args[1]));
248
+ case 'nand':
249
+ return bvNot(bvAnd(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
250
+ case 'nor':
251
+ return bvNot(bvOr(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
252
+ default:
253
+ throw new Error(`Operador no soportado en evaluación bitset: ${f.kind}`);
254
+ }
255
+ }
256
+ return { result: evalBits(formula), atomMasks, total, allOnes };
257
+ }
258
+ function bitsetPopcount(a) {
259
+ return bvPopcount(a);
260
+ }
261
+ function isPurePropositional(f) {
262
+ switch (f.kind) {
263
+ case 'atom':
264
+ return true;
265
+ case 'not':
266
+ case 'and':
267
+ case 'or':
268
+ case 'implies':
269
+ case 'biconditional':
270
+ case 'xor':
271
+ case 'nand':
272
+ case 'nor':
273
+ return (f.args || []).every(isPurePropositional);
274
+ default:
275
+ return false;
276
+ }
277
+ }
96
278
  /**
97
279
  * Aplana recursivamente nodos binarios del mismo kind asociativo.
98
280
  * Ej: or(or(P,Q), R) → [P, Q, R]
@@ -219,6 +401,9 @@ function computeFormulaToString(f) {
219
401
  }
220
402
  }
221
403
  function toNNF(f) {
404
+ return (0, memo_1.memoizeNNF)(f, computeNNF);
405
+ }
406
+ function computeNNF(f) {
222
407
  const simplify = (node, negated) => {
223
408
  const k = node.kind;
224
409
  const args = node.args || [];
@@ -351,7 +536,7 @@ function distributeOrOverAnd(f) {
351
536
  return f;
352
537
  }
353
538
  function toCNF(f) {
354
- return distributeOrOverAnd(toNNF(f));
539
+ return (0, memo_1.memoizeCNF)(f, (formula) => distributeOrOverAnd(toNNF(formula)));
355
540
  }
356
541
  function distributeAndOverOr(f) {
357
542
  if (f.kind === 'and' && f.args?.[0] && f.args?.[1]) {
@@ -382,7 +567,7 @@ function distributeAndOverOr(f) {
382
567
  return f;
383
568
  }
384
569
  function toDNF(f) {
385
- return distributeAndOverOr(toNNF(f));
570
+ return (0, memo_1.memoizeDNF)(f, (formula) => distributeAndOverOr(toNNF(formula)));
386
571
  }
387
572
  /**
388
573
  * Extracts clauses from a CNF formula for resolution analysis (#28)
@@ -943,23 +1128,67 @@ function tryDerive(goal, theory, premiseNames) {
943
1128
  for (const f of allAxiomFormulas)
944
1129
  collectAtoms(f).forEach((a) => atoms.add(a));
945
1130
  collectAtoms(goal).forEach((a) => atoms.add(a));
946
- const atomList = Array.from(atoms);
947
- const valuations = generateValuations(atomList);
948
- let semanticallyValid = true;
949
- for (const v of valuations) {
950
- const premisesTrue = allAxiomFormulas.every((f) => evaluate(f, v));
951
- if (premisesTrue && !evaluate(goal, v)) {
952
- semanticallyValid = false;
953
- break;
1131
+ const atomList = Array.from(atoms).sort();
1132
+ // Fast path: bitset semantic check
1133
+ const allPure = allAxiomFormulas.every(isPurePropositional) && isPurePropositional(goal);
1134
+ if (allPure && atomList.length <= 26) {
1135
+ const premiseBits = allAxiomFormulas.map((f) => evaluateBitset(f, atomList).result);
1136
+ const goalBits = evaluateBitset(goal, atomList).result;
1137
+ const allOnes = bvOnes(1 << atomList.length);
1138
+ // Conjunction of all premises
1139
+ let premisesConj = allOnes;
1140
+ for (const pb of premiseBits)
1141
+ premisesConj = bvAnd(premisesConj, pb);
1142
+ // Valid if: wherever premises are true, goal is also true
1143
+ // i.e., premisesConj & ~goalBits === 0
1144
+ if (bvIsZero(bvAnd(premisesConj, bvNot(goalBits, allOnes)))) {
1145
+ return {
1146
+ goal,
1147
+ steps: state.steps,
1148
+ status: 'complete',
1149
+ derivedFrom: premiseNames,
1150
+ };
954
1151
  }
955
1152
  }
956
- if (semanticallyValid) {
957
- return {
958
- goal,
959
- steps: state.steps,
960
- status: 'complete',
961
- derivedFrom: premiseNames,
962
- };
1153
+ else if (allPure && atomList.length > 26) {
1154
+ // DPLL fallback for >26 atoms
1155
+ // Build: (premise1 & premise2 & ... & premiseN) -> goal
1156
+ // Valid iff NOT satisfiable: (premises & !goal)
1157
+ let conjunction = allAxiomFormulas[0];
1158
+ for (let i = 1; i < allAxiomFormulas.length; i++) {
1159
+ conjunction = { kind: 'and', args: [conjunction, allAxiomFormulas[i]] };
1160
+ }
1161
+ const negGoal = { kind: 'not', args: [goal] };
1162
+ const check = { kind: 'and', args: [conjunction, negGoal] };
1163
+ const result = (0, dpll_1.dpll)(check);
1164
+ if (!result.satisfiable) {
1165
+ return {
1166
+ goal,
1167
+ steps: state.steps,
1168
+ status: 'complete',
1169
+ derivedFrom: premiseNames,
1170
+ };
1171
+ }
1172
+ }
1173
+ else {
1174
+ // Classic fallback
1175
+ const valuations = generateValuations(atomList);
1176
+ let semanticallyValid = true;
1177
+ for (const v of valuations) {
1178
+ const premisesTrue = allAxiomFormulas.every((f) => evaluateClassical(f, v));
1179
+ if (premisesTrue && !evaluateClassical(goal, v)) {
1180
+ semanticallyValid = false;
1181
+ break;
1182
+ }
1183
+ }
1184
+ if (semanticallyValid) {
1185
+ return {
1186
+ goal,
1187
+ steps: state.steps,
1188
+ status: 'complete',
1189
+ derivedFrom: premiseNames,
1190
+ };
1191
+ }
963
1192
  }
964
1193
  }
965
1194
  return null;
@@ -1054,24 +1283,56 @@ class ClassicalPropositional {
1054
1283
  if (wf.length > 0) {
1055
1284
  return { status: 'error', diagnostics: wf, formula };
1056
1285
  }
1286
+ // Fast path: bitset validity check (no row materialization)
1287
+ const atoms = Array.from(collectAtoms(formula)).sort();
1288
+ if (isPurePropositional(formula) && atoms.length <= 26) {
1289
+ const { result, allOnes } = evaluateBitset(formula, atoms);
1290
+ const isValid = bvEquals(result, allOnes);
1291
+ return {
1292
+ status: isValid ? 'valid' : 'invalid',
1293
+ output: isValid
1294
+ ? `${formulaToString(formula)} es VALIDA (tautologia)`
1295
+ : `${formulaToString(formula)} NO es valida`,
1296
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'valid', valid: isValid }),
1297
+ diagnostics: [],
1298
+ formula,
1299
+ };
1300
+ }
1301
+ // DPLL path: for formulas with >26 atoms, use SAT solver
1302
+ if (isPurePropositional(formula) && atoms.length > 26) {
1303
+ const negated = { kind: 'not', args: [formula] };
1304
+ const result = (0, dpll_1.dpll)(negated);
1305
+ const isValid = !result.satisfiable;
1306
+ return {
1307
+ status: isValid ? 'valid' : 'invalid',
1308
+ output: isValid
1309
+ ? `${formulaToString(formula)} es VALIDA (tautologia)`
1310
+ : `${formulaToString(formula)} NO es valida`,
1311
+ model: !isValid && result.model ? { type: 'propositional', valuation: result.model } : undefined,
1312
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'valid', valid: isValid }),
1313
+ diagnostics: [],
1314
+ formula,
1315
+ };
1316
+ }
1057
1317
  const tt = this.truthTable(formula);
1058
1318
  if (tt.isTautology) {
1059
1319
  return {
1060
1320
  status: 'valid',
1061
1321
  output: `${formulaToString(formula)} es VALIDA (tautologia)`,
1062
1322
  truthTable: tt,
1323
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'valid', valid: true }),
1063
1324
  diagnostics: [],
1064
1325
  formula,
1065
1326
  };
1066
1327
  }
1067
1328
  else {
1068
- // Encontrar contramodelo
1069
1329
  const cm = tt.rows.find((r) => !r.result);
1070
1330
  return {
1071
1331
  status: 'invalid',
1072
1332
  output: `${formulaToString(formula)} NO es valida`,
1073
1333
  truthTable: tt,
1074
1334
  model: cm ? { type: 'propositional', valuation: cm.valuation } : undefined,
1335
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'valid', valid: false }),
1075
1336
  diagnostics: [],
1076
1337
  formula,
1077
1338
  };
@@ -1082,6 +1343,37 @@ class ClassicalPropositional {
1082
1343
  if (wf.length > 0) {
1083
1344
  return { status: 'error', diagnostics: wf, formula };
1084
1345
  }
1346
+ // Fast path: bitset satisfiability check (no row materialization)
1347
+ const atoms = Array.from(collectAtoms(formula)).sort();
1348
+ if (isPurePropositional(formula) && atoms.length <= 26) {
1349
+ const { result } = evaluateBitset(formula, atoms);
1350
+ const isSat = !bvIsZero(result);
1351
+ return {
1352
+ status: isSat ? 'satisfiable' : 'unsatisfiable',
1353
+ output: isSat
1354
+ ? `${formulaToString(formula)} es SATISFACIBLE`
1355
+ : `${formulaToString(formula)} es INSATISFACIBLE (contradiccion)`,
1356
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'satisfiable', sat: isSat }),
1357
+ diagnostics: [],
1358
+ formula,
1359
+ };
1360
+ }
1361
+ // DPLL path: for formulas with >26 atoms, use SAT solver
1362
+ if (isPurePropositional(formula) && atoms.length > 26) {
1363
+ const result = (0, dpll_1.dpll)(formula);
1364
+ return {
1365
+ status: result.satisfiable ? 'satisfiable' : 'unsatisfiable',
1366
+ output: result.satisfiable
1367
+ ? `${formulaToString(formula)} es SATISFACIBLE`
1368
+ : `${formulaToString(formula)} es INSATISFACIBLE (contradiccion)`,
1369
+ model: result.satisfiable && result.model
1370
+ ? { type: 'propositional', valuation: result.model }
1371
+ : undefined,
1372
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'satisfiable', sat: result.satisfiable }),
1373
+ diagnostics: [],
1374
+ formula,
1375
+ };
1376
+ }
1085
1377
  const tt = this.truthTable(formula);
1086
1378
  if (tt.isSatisfiable) {
1087
1379
  const sat = tt.rows.find((r) => r.result);
@@ -1090,6 +1382,7 @@ class ClassicalPropositional {
1090
1382
  output: `${formulaToString(formula)} es SATISFACIBLE`,
1091
1383
  model: sat ? { type: 'propositional', valuation: sat.valuation } : undefined,
1092
1384
  truthTable: tt,
1385
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'satisfiable', sat: true }),
1093
1386
  diagnostics: [],
1094
1387
  formula,
1095
1388
  };
@@ -1099,6 +1392,7 @@ class ClassicalPropositional {
1099
1392
  status: 'unsatisfiable',
1100
1393
  output: `${formulaToString(formula)} es INSATISFACIBLE (contradiccion)`,
1101
1394
  truthTable: tt,
1395
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'satisfiable', sat: false }),
1102
1396
  diagnostics: [],
1103
1397
  formula,
1104
1398
  };
@@ -1116,6 +1410,7 @@ class ClassicalPropositional {
1116
1410
  status: 'provable',
1117
1411
  output: `${formulaToString(goal)} es DEMOSTRABLE desde la teoria`,
1118
1412
  proof,
1413
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'prove', ok: true }),
1119
1414
  diagnostics: [],
1120
1415
  formula: goal,
1121
1416
  };
@@ -1123,6 +1418,7 @@ class ClassicalPropositional {
1123
1418
  return {
1124
1419
  status: 'refutable',
1125
1420
  output: `${formulaToString(goal)} NO es demostrable desde la teoria dada`,
1421
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'prove', ok: false }),
1126
1422
  diagnostics: [],
1127
1423
  formula: goal,
1128
1424
  };
@@ -1154,7 +1450,12 @@ class ClassicalPropositional {
1154
1450
  : rulesUsed.has('Silogismo Hipotetico')
1155
1451
  ? 'φ → ψ, ψ → χ ⊢ φ → χ'
1156
1452
  : undefined,
1157
- educationalNote: `Consecuencia semántica (⊨): Verificada — no existe valuación donde las premisas sean V y la conclusión F.\nConsecuencia sintáctica (⊢): Derivación formal completada en ${proof.steps.length} pasos.\nNota: Por completitud de la lógica proposicional clásica, ⊨ y ⊢ coinciden.`,
1453
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({
1454
+ op: 'derive',
1455
+ ok: true,
1456
+ steps: proof.steps.length,
1457
+ rules: Array.from(rulesUsed),
1458
+ }),
1158
1459
  diagnostics: [],
1159
1460
  formula: goal,
1160
1461
  };
@@ -1162,6 +1463,7 @@ class ClassicalPropositional {
1162
1463
  return {
1163
1464
  status: 'refutable',
1164
1465
  output: `No se puede derivar ${formulaToString(goal)} desde las premisas dadas`,
1466
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'derive', ok: false }),
1165
1467
  diagnostics: [],
1166
1468
  formula: goal,
1167
1469
  };
@@ -1171,16 +1473,70 @@ class ClassicalPropositional {
1171
1473
  if (wf.length > 0) {
1172
1474
  return { status: 'error', diagnostics: wf, formula };
1173
1475
  }
1174
- const atoms = Array.from(collectAtoms(formula));
1476
+ const atoms = Array.from(collectAtoms(formula)).sort();
1477
+ const n = atoms.length;
1478
+ // Fast path: bitset finds countermodel in one pass
1479
+ if (isPurePropositional(formula) && n <= 26) {
1480
+ const { result, allOnes } = evaluateBitset(formula, atoms);
1481
+ if (bvEquals(result, allOnes)) {
1482
+ return {
1483
+ status: 'valid',
1484
+ output: `${formulaToString(formula)} es tautologia, no hay contramodelo`,
1485
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: false }),
1486
+ diagnostics: [],
1487
+ formula,
1488
+ };
1489
+ }
1490
+ // Find first 0 bit (first falsifying row)
1491
+ const inverted = bvNot(result, allOnes);
1492
+ const idx = bvFirstSet(inverted);
1493
+ const v = {};
1494
+ for (let j = 0; j < n; j++) {
1495
+ v[atoms[j]] = Boolean((idx >> (n - 1 - j)) & 1);
1496
+ }
1497
+ const valStr = atoms.map((a) => `${a}=${v[a] ? 'V' : 'F'}`).join(', ');
1498
+ return {
1499
+ status: 'invalid',
1500
+ output: `Contramodelo encontrado para ${formulaToString(formula)}\n ← ${valStr}`,
1501
+ model: { type: 'propositional', valuation: v },
1502
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: true }),
1503
+ diagnostics: [],
1504
+ formula,
1505
+ };
1506
+ }
1507
+ // DPLL path: for formulas with >26 atoms, use SAT solver to find countermodel
1508
+ if (isPurePropositional(formula) && n > 26) {
1509
+ const negated = { kind: 'not', args: [formula] };
1510
+ const result = (0, dpll_1.dpll)(negated);
1511
+ if (result.satisfiable && result.model) {
1512
+ const valStr = atoms.map((a) => `${a}=${result.model[a] ? 'V' : 'F'}`).join(', ');
1513
+ return {
1514
+ status: 'invalid',
1515
+ output: `Contramodelo encontrado para ${formulaToString(formula)}\n ← ${valStr}`,
1516
+ model: { type: 'propositional', valuation: result.model },
1517
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: true }),
1518
+ diagnostics: [],
1519
+ formula,
1520
+ };
1521
+ }
1522
+ return {
1523
+ status: 'valid',
1524
+ output: `${formulaToString(formula)} es tautologia, no hay contramodelo`,
1525
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: false }),
1526
+ diagnostics: [],
1527
+ formula,
1528
+ };
1529
+ }
1530
+ // Fallback: classic evaluation
1175
1531
  const valuations = generateValuations(atoms);
1176
1532
  for (const v of valuations) {
1177
- if (!evaluate(formula, v)) {
1178
- // #25: mark the countermodel valuation with ←
1533
+ if (!evaluateClassical(formula, v)) {
1179
1534
  const valStr = atoms.map((a) => `${a}=${v[a] ? 'V' : 'F'}`).join(', ');
1180
1535
  return {
1181
1536
  status: 'invalid',
1182
1537
  output: `Contramodelo encontrado para ${formulaToString(formula)}\n ← ${valStr}`,
1183
1538
  model: { type: 'propositional', valuation: v },
1539
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: true }),
1184
1540
  diagnostics: [],
1185
1541
  formula,
1186
1542
  };
@@ -1189,6 +1545,7 @@ class ClassicalPropositional {
1189
1545
  return {
1190
1546
  status: 'valid',
1191
1547
  output: `${formulaToString(formula)} es tautologia, no hay contramodelo`,
1548
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'countermodel', found: false }),
1192
1549
  diagnostics: [],
1193
1550
  formula,
1194
1551
  };
@@ -1296,17 +1653,54 @@ class ClassicalPropositional {
1296
1653
  }
1297
1654
  truthTable(formula) {
1298
1655
  const atoms = Array.from(collectAtoms(formula)).sort();
1299
- const valuations = generateValuations(atoms);
1656
+ const n = atoms.length;
1300
1657
  const subForms = getSubFormulas(formula);
1658
+ const subFormulasInfo = subForms.map((sf) => ({ formula: sf, label: formulaToString(sf) }));
1659
+ // Fast path: bitset evaluation for pure propositional formulas
1660
+ // Limit to n<=20 for row materialization (2^20 = ~1M rows)
1661
+ if (isPurePropositional(formula) && n <= 20) {
1662
+ const { result, atomMasks, total, allOnes } = evaluateBitset(formula, atoms);
1663
+ const satisfyingCount = bitsetPopcount(result);
1664
+ // Evaluate subformulas with bitsets too
1665
+ const subBitsets = subForms.map((sf) => isPurePropositional(sf) ? evaluateBitset(sf, atoms).result : bvCreate(total));
1666
+ // Materialize rows from bitset results
1667
+ const rows = new Array(total);
1668
+ for (let i = 0; i < total; i++) {
1669
+ const v = {};
1670
+ for (let j = 0; j < n; j++) {
1671
+ v[atoms[j]] = bvTestBit(atomMasks.get(atoms[j]), i);
1672
+ }
1673
+ rows[i] = { valuation: v, result: bvTestBit(result, i) };
1674
+ }
1675
+ const subFormulaValues = rows.map((_, i) => {
1676
+ const vals = {};
1677
+ subForms.forEach((sf, si) => {
1678
+ vals[formulaToString(sf)] = bvTestBit(subBitsets[si], i);
1679
+ });
1680
+ return vals;
1681
+ });
1682
+ return {
1683
+ variables: atoms,
1684
+ rows,
1685
+ isTautology: bvEquals(result, allOnes),
1686
+ isContradiction: bvIsZero(result),
1687
+ isSatisfiable: !bvIsZero(result),
1688
+ subFormulas: subFormulasInfo,
1689
+ subFormulaValues,
1690
+ satisfyingCount,
1691
+ totalCount: total,
1692
+ };
1693
+ }
1694
+ // Fallback: classic evaluation
1695
+ const valuations = generateValuations(atoms);
1301
1696
  const rows = valuations.map((v) => ({
1302
1697
  valuation: v,
1303
- result: evaluate(formula, v),
1698
+ result: evaluateClassical(formula, v),
1304
1699
  }));
1305
- const subFormulasInfo = subForms.map((sf) => ({ formula: sf, label: formulaToString(sf) }));
1306
1700
  const subFormulaValues = valuations.map((v) => {
1307
1701
  const vals = {};
1308
1702
  subForms.forEach((sf) => {
1309
- vals[formulaToString(sf)] = evaluate(sf, v);
1703
+ vals[formulaToString(sf)] = evaluateClassical(sf, v);
1310
1704
  });
1311
1705
  return vals;
1312
1706
  });
@@ -1317,7 +1711,7 @@ class ClassicalPropositional {
1317
1711
  isContradiction: rows.every((r) => !r.result),
1318
1712
  isSatisfiable: rows.some((r) => r.result),
1319
1713
  subFormulas: subFormulasInfo,
1320
- subFormulaValues: subFormulaValues,
1714
+ subFormulaValues,
1321
1715
  satisfyingCount: rows.filter((r) => r.result).length,
1322
1716
  totalCount: rows.length,
1323
1717
  };
@@ -1335,6 +1729,7 @@ class ClassicalPropositional {
1335
1729
  status: 'valid',
1336
1730
  output: `${formulaToString(a)} y ${formulaToString(b)} son EQUIVALENTES`,
1337
1731
  truthTable: tt,
1732
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'equivalent', equiv: true }),
1338
1733
  diagnostics: [],
1339
1734
  };
1340
1735
  }
@@ -1343,6 +1738,7 @@ class ClassicalPropositional {
1343
1738
  status: 'invalid',
1344
1739
  output: `${formulaToString(a)} y ${formulaToString(b)} NO son equivalentes`,
1345
1740
  model: cm ? { type: 'propositional', valuation: cm.valuation } : undefined,
1741
+ educationalNote: (0, educational_notes_1.pickEducationalNote)({ op: 'equivalent', equiv: false }),
1346
1742
  diagnostics: [],
1347
1743
  };
1348
1744
  }