@stevenvo780/st-lang 2.6.1 → 2.7.0
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.
- package/dist/ast/nodes.d.ts +18 -1
- package/dist/ast/nodes.d.ts.map +1 -1
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +82 -0
- package/dist/parser/parser.js.map +1 -1
- package/dist/profiles/classical/first-order.d.ts +1 -0
- package/dist/profiles/classical/first-order.d.ts.map +1 -1
- package/dist/profiles/classical/first-order.js +35 -0
- package/dist/profiles/classical/first-order.js.map +1 -1
- package/dist/profiles/classical/propositional.d.ts +6 -1
- package/dist/profiles/classical/propositional.d.ts.map +1 -1
- package/dist/profiles/classical/propositional.js +334 -35
- package/dist/profiles/classical/propositional.js.map +1 -1
- package/dist/profiles/paraconsistent/belnap.d.ts.map +1 -1
- package/dist/profiles/paraconsistent/belnap.js +151 -3
- package/dist/profiles/paraconsistent/belnap.js.map +1 -1
- package/dist/profiles/shared/tableau-engine.d.ts.map +1 -1
- package/dist/profiles/shared/tableau-engine.js +49 -22
- package/dist/profiles/shared/tableau-engine.js.map +1 -1
- package/dist/runtime/format.d.ts.map +1 -1
- package/dist/runtime/format.js +4 -0
- package/dist/runtime/format.js.map +1 -1
- package/dist/runtime/formula-factory.d.ts +26 -0
- package/dist/runtime/formula-factory.d.ts.map +1 -0
- package/dist/runtime/formula-factory.js +67 -0
- package/dist/runtime/formula-factory.js.map +1 -0
- package/dist/runtime/interpreter.d.ts +41 -0
- package/dist/runtime/interpreter.d.ts.map +1 -1
- package/dist/runtime/interpreter.js +1029 -245
- package/dist/runtime/interpreter.js.map +1 -1
- package/dist/tests/arithmetic.test.js +2 -1
- package/dist/tests/arithmetic.test.js.map +1 -1
- package/dist/tests/examples.test.js +12 -1
- package/dist/tests/examples.test.js.map +1 -1
- package/dist/tests/exhaustive-matrix.test.js +1 -1
- package/dist/tests/exhaustive-matrix.test.js.map +1 -1
- package/dist/tests/limits.test.js +15 -3
- package/dist/tests/limits.test.js.map +1 -1
- package/dist/tests/result-bindings.test.d.ts +2 -0
- package/dist/tests/result-bindings.test.d.ts.map +1 -0
- package/dist/tests/result-bindings.test.js +59 -0
- package/dist/tests/result-bindings.test.js.map +1 -0
- package/dist/tests/stress-hardware.test.d.ts +2 -0
- package/dist/tests/stress-hardware.test.d.ts.map +1 -0
- package/dist/tests/stress-hardware.test.js +516 -0
- package/dist/tests/stress-hardware.test.js.map +1 -0
- package/dist/tests/v1-features.test.js +1 -1
- package/dist/tests/v1-features.test.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/memo.d.ts +3 -0
- package/dist/utils/memo.d.ts.map +1 -1
- package/dist/utils/memo.js +30 -0
- package/dist/utils/memo.js.map +1 -1
- 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;
|
|
@@ -32,39 +34,39 @@ function computeCollectAtoms(f) {
|
|
|
32
34
|
walk(f);
|
|
33
35
|
return atoms;
|
|
34
36
|
}
|
|
35
|
-
function
|
|
37
|
+
function evaluateClassical(f, v) {
|
|
36
38
|
switch (f.kind) {
|
|
37
39
|
case 'atom':
|
|
38
40
|
return f.name ? (v[f.name] ?? false) : false;
|
|
39
41
|
case 'not':
|
|
40
|
-
return f.args && f.args[0] ? !
|
|
42
|
+
return f.args && f.args[0] ? !evaluateClassical(f.args[0], v) : false;
|
|
41
43
|
case 'and':
|
|
42
44
|
return f.args && f.args[0] && f.args[1]
|
|
43
|
-
?
|
|
45
|
+
? evaluateClassical(f.args[0], v) && evaluateClassical(f.args[1], v)
|
|
44
46
|
: false;
|
|
45
47
|
case 'or':
|
|
46
48
|
return f.args && f.args[0] && f.args[1]
|
|
47
|
-
?
|
|
49
|
+
? evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v)
|
|
48
50
|
: false;
|
|
49
51
|
case 'implies':
|
|
50
52
|
return f.args && f.args[0] && f.args[1]
|
|
51
|
-
? !
|
|
53
|
+
? !evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v)
|
|
52
54
|
: false;
|
|
53
55
|
case 'biconditional':
|
|
54
56
|
return f.args && f.args[0] && f.args[1]
|
|
55
|
-
?
|
|
57
|
+
? evaluateClassical(f.args[0], v) === evaluateClassical(f.args[1], v)
|
|
56
58
|
: false;
|
|
57
59
|
case 'nand':
|
|
58
60
|
return f.args && f.args[0] && f.args[1]
|
|
59
|
-
? !(
|
|
61
|
+
? !(evaluateClassical(f.args[0], v) && evaluateClassical(f.args[1], v))
|
|
60
62
|
: false;
|
|
61
63
|
case 'nor':
|
|
62
64
|
return f.args && f.args[0] && f.args[1]
|
|
63
|
-
? !(
|
|
65
|
+
? !(evaluateClassical(f.args[0], v) || evaluateClassical(f.args[1], v))
|
|
64
66
|
: false;
|
|
65
67
|
case 'xor':
|
|
66
68
|
return f.args && f.args[0] && f.args[1]
|
|
67
|
-
?
|
|
69
|
+
? evaluateClassical(f.args[0], v) !== evaluateClassical(f.args[1], v)
|
|
68
70
|
: false;
|
|
69
71
|
default:
|
|
70
72
|
throw new Error(`Operador lógico no soportado en evaluación clásica: ${f.kind}`);
|
|
@@ -93,6 +95,184 @@ function generateValuations(atoms) {
|
|
|
93
95
|
}
|
|
94
96
|
return valuations;
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Generador lazy de valuaciones para streaming (usado por el intérprete para truth_table masivas).
|
|
100
|
+
*/
|
|
101
|
+
function* generateValuationsLazy(atoms) {
|
|
102
|
+
const n = atoms.length;
|
|
103
|
+
if (n === 0) {
|
|
104
|
+
yield {};
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const total = 1 << n;
|
|
108
|
+
for (let i = 0; i < total; i++) {
|
|
109
|
+
const v = {};
|
|
110
|
+
for (let j = 0; j < n; j++) {
|
|
111
|
+
v[atoms[j]] = Boolean((i >> (n - 1 - j)) & 1);
|
|
112
|
+
}
|
|
113
|
+
yield v;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function bvCreate(total) {
|
|
117
|
+
return new Uint32Array((total + 31) >>> 5);
|
|
118
|
+
}
|
|
119
|
+
function bvOnes(total) {
|
|
120
|
+
const words = (total + 31) >>> 5;
|
|
121
|
+
const v = new Uint32Array(words);
|
|
122
|
+
v.fill(0xffffffff);
|
|
123
|
+
// Clear trailing bits in last word
|
|
124
|
+
const tail = total & 31;
|
|
125
|
+
if (tail)
|
|
126
|
+
v[words - 1] = (1 << tail) - 1;
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
function bvAnd(a, b) {
|
|
130
|
+
const r = new Uint32Array(a.length);
|
|
131
|
+
for (let i = 0; i < a.length; i++)
|
|
132
|
+
r[i] = a[i] & b[i];
|
|
133
|
+
return r;
|
|
134
|
+
}
|
|
135
|
+
function bvOr(a, b) {
|
|
136
|
+
const r = new Uint32Array(a.length);
|
|
137
|
+
for (let i = 0; i < a.length; i++)
|
|
138
|
+
r[i] = a[i] | b[i];
|
|
139
|
+
return r;
|
|
140
|
+
}
|
|
141
|
+
function bvXor(a, b) {
|
|
142
|
+
const r = new Uint32Array(a.length);
|
|
143
|
+
for (let i = 0; i < a.length; i++)
|
|
144
|
+
r[i] = a[i] ^ b[i];
|
|
145
|
+
return r;
|
|
146
|
+
}
|
|
147
|
+
function bvNot(a, ones) {
|
|
148
|
+
const r = new Uint32Array(a.length);
|
|
149
|
+
for (let i = 0; i < a.length; i++)
|
|
150
|
+
r[i] = ~a[i] & ones[i];
|
|
151
|
+
return r;
|
|
152
|
+
}
|
|
153
|
+
function bvIsZero(a) {
|
|
154
|
+
for (let i = 0; i < a.length; i++)
|
|
155
|
+
if (a[i] !== 0)
|
|
156
|
+
return false;
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function bvEquals(a, b) {
|
|
160
|
+
for (let i = 0; i < a.length; i++)
|
|
161
|
+
if (a[i] !== b[i])
|
|
162
|
+
return false;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
function bvPopcount(a) {
|
|
166
|
+
let count = 0;
|
|
167
|
+
for (let i = 0; i < a.length; i++) {
|
|
168
|
+
let v = a[i];
|
|
169
|
+
v = v - ((v >>> 1) & 0x55555555);
|
|
170
|
+
v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
|
|
171
|
+
count += (((v + (v >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
|
|
172
|
+
}
|
|
173
|
+
return count;
|
|
174
|
+
}
|
|
175
|
+
function bvTestBit(a, i) {
|
|
176
|
+
return (a[i >>> 5] & (1 << (i & 31))) !== 0;
|
|
177
|
+
}
|
|
178
|
+
// Find first set bit, or -1
|
|
179
|
+
function bvFirstSet(a) {
|
|
180
|
+
for (let w = 0; w < a.length; w++) {
|
|
181
|
+
if (a[w] !== 0)
|
|
182
|
+
return ((w << 5) + Math.clz32(a[w] & (-a[w] | 0))) ^ 31;
|
|
183
|
+
}
|
|
184
|
+
return -1;
|
|
185
|
+
}
|
|
186
|
+
function evaluateBitset(formula, atoms) {
|
|
187
|
+
const n = atoms.length;
|
|
188
|
+
if (n > 26)
|
|
189
|
+
throw new Error('Demasiadas variables para evaluación bitset (>26)');
|
|
190
|
+
const total = 1 << n;
|
|
191
|
+
const allOnes = bvOnes(total);
|
|
192
|
+
const words = allOnes.length;
|
|
193
|
+
// Build atom masks: atom j is true when bit j of the row index is 1.
|
|
194
|
+
// Row index i has bit j set when (i >>> (n-1-j)) & 1.
|
|
195
|
+
// Equivalent: word w, bit b (i = w*32+b), atom j true iff ((w*32+b) >>> (n-1-j)) & 1.
|
|
196
|
+
const atomMasks = new Map();
|
|
197
|
+
for (let j = 0; j < n; j++) {
|
|
198
|
+
const shift = n - 1 - j;
|
|
199
|
+
const mask = bvCreate(total);
|
|
200
|
+
// The pattern for atom j repeats with period 2^(shift+1).
|
|
201
|
+
// Within each period, the first 2^shift bits are 0, next 2^shift are 1.
|
|
202
|
+
// For shift < 5, the pattern fits within single words and we can use word-level fill.
|
|
203
|
+
if (shift < 5) {
|
|
204
|
+
// Pattern period in bits
|
|
205
|
+
const period = 1 << (shift + 1);
|
|
206
|
+
const halfPeriod = 1 << shift;
|
|
207
|
+
// Build a 32-bit pattern
|
|
208
|
+
let pattern = 0;
|
|
209
|
+
for (let b = 0; b < 32; b++) {
|
|
210
|
+
if (b % period >= halfPeriod)
|
|
211
|
+
pattern |= 1 << b;
|
|
212
|
+
}
|
|
213
|
+
mask.fill(pattern);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// shift >= 5: consecutive words are all-0 or all-1
|
|
217
|
+
const wordPeriod = 1 << (shift - 5 + 1); // period in words
|
|
218
|
+
const halfWordPeriod = wordPeriod >>> 1;
|
|
219
|
+
for (let w = 0; w < words; w++) {
|
|
220
|
+
const posInPeriod = w % wordPeriod;
|
|
221
|
+
mask[w] = posInPeriod >= halfWordPeriod ? 0xffffffff : 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Clear trailing bits
|
|
225
|
+
const tail = total & 31;
|
|
226
|
+
if (tail && words > 0)
|
|
227
|
+
mask[words - 1] &= (1 << tail) - 1;
|
|
228
|
+
atomMasks.set(atoms[j], mask);
|
|
229
|
+
}
|
|
230
|
+
function evalBits(f) {
|
|
231
|
+
switch (f.kind) {
|
|
232
|
+
case 'atom':
|
|
233
|
+
return atomMasks.get(f.name) ?? bvCreate(total);
|
|
234
|
+
case 'not':
|
|
235
|
+
return bvNot(evalBits(f.args[0]), allOnes);
|
|
236
|
+
case 'and':
|
|
237
|
+
return bvAnd(evalBits(f.args[0]), evalBits(f.args[1]));
|
|
238
|
+
case 'or':
|
|
239
|
+
return bvOr(evalBits(f.args[0]), evalBits(f.args[1]));
|
|
240
|
+
case 'implies':
|
|
241
|
+
return bvOr(bvNot(evalBits(f.args[0]), allOnes), evalBits(f.args[1]));
|
|
242
|
+
case 'biconditional':
|
|
243
|
+
return bvNot(bvXor(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
|
|
244
|
+
case 'xor':
|
|
245
|
+
return bvXor(evalBits(f.args[0]), evalBits(f.args[1]));
|
|
246
|
+
case 'nand':
|
|
247
|
+
return bvNot(bvAnd(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
|
|
248
|
+
case 'nor':
|
|
249
|
+
return bvNot(bvOr(evalBits(f.args[0]), evalBits(f.args[1])), allOnes);
|
|
250
|
+
default:
|
|
251
|
+
throw new Error(`Operador no soportado en evaluación bitset: ${f.kind}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { result: evalBits(formula), atomMasks, total, allOnes };
|
|
255
|
+
}
|
|
256
|
+
function bitsetPopcount(a) {
|
|
257
|
+
return bvPopcount(a);
|
|
258
|
+
}
|
|
259
|
+
function isPurePropositional(f) {
|
|
260
|
+
switch (f.kind) {
|
|
261
|
+
case 'atom':
|
|
262
|
+
return true;
|
|
263
|
+
case 'not':
|
|
264
|
+
case 'and':
|
|
265
|
+
case 'or':
|
|
266
|
+
case 'implies':
|
|
267
|
+
case 'biconditional':
|
|
268
|
+
case 'xor':
|
|
269
|
+
case 'nand':
|
|
270
|
+
case 'nor':
|
|
271
|
+
return (f.args || []).every(isPurePropositional);
|
|
272
|
+
default:
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
96
276
|
/**
|
|
97
277
|
* Aplana recursivamente nodos binarios del mismo kind asociativo.
|
|
98
278
|
* Ej: or(or(P,Q), R) → [P, Q, R]
|
|
@@ -219,6 +399,9 @@ function computeFormulaToString(f) {
|
|
|
219
399
|
}
|
|
220
400
|
}
|
|
221
401
|
function toNNF(f) {
|
|
402
|
+
return (0, memo_1.memoizeNNF)(f, computeNNF);
|
|
403
|
+
}
|
|
404
|
+
function computeNNF(f) {
|
|
222
405
|
const simplify = (node, negated) => {
|
|
223
406
|
const k = node.kind;
|
|
224
407
|
const args = node.args || [];
|
|
@@ -351,7 +534,7 @@ function distributeOrOverAnd(f) {
|
|
|
351
534
|
return f;
|
|
352
535
|
}
|
|
353
536
|
function toCNF(f) {
|
|
354
|
-
return distributeOrOverAnd(toNNF(
|
|
537
|
+
return (0, memo_1.memoizeCNF)(f, (formula) => distributeOrOverAnd(toNNF(formula)));
|
|
355
538
|
}
|
|
356
539
|
function distributeAndOverOr(f) {
|
|
357
540
|
if (f.kind === 'and' && f.args?.[0] && f.args?.[1]) {
|
|
@@ -382,7 +565,7 @@ function distributeAndOverOr(f) {
|
|
|
382
565
|
return f;
|
|
383
566
|
}
|
|
384
567
|
function toDNF(f) {
|
|
385
|
-
return distributeAndOverOr(toNNF(
|
|
568
|
+
return (0, memo_1.memoizeDNF)(f, (formula) => distributeAndOverOr(toNNF(formula)));
|
|
386
569
|
}
|
|
387
570
|
/**
|
|
388
571
|
* Extracts clauses from a CNF formula for resolution analysis (#28)
|
|
@@ -943,23 +1126,47 @@ function tryDerive(goal, theory, premiseNames) {
|
|
|
943
1126
|
for (const f of allAxiomFormulas)
|
|
944
1127
|
collectAtoms(f).forEach((a) => atoms.add(a));
|
|
945
1128
|
collectAtoms(goal).forEach((a) => atoms.add(a));
|
|
946
|
-
const atomList = Array.from(atoms);
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1129
|
+
const atomList = Array.from(atoms).sort();
|
|
1130
|
+
// Fast path: bitset semantic check
|
|
1131
|
+
const allPure = allAxiomFormulas.every(isPurePropositional) && isPurePropositional(goal);
|
|
1132
|
+
if (allPure && atomList.length <= 26) {
|
|
1133
|
+
const premiseBits = allAxiomFormulas.map((f) => evaluateBitset(f, atomList).result);
|
|
1134
|
+
const goalBits = evaluateBitset(goal, atomList).result;
|
|
1135
|
+
const allOnes = bvOnes(1 << atomList.length);
|
|
1136
|
+
// Conjunction of all premises
|
|
1137
|
+
let premisesConj = allOnes;
|
|
1138
|
+
for (const pb of premiseBits)
|
|
1139
|
+
premisesConj = bvAnd(premisesConj, pb);
|
|
1140
|
+
// Valid if: wherever premises are true, goal is also true
|
|
1141
|
+
// i.e., premisesConj & ~goalBits === 0
|
|
1142
|
+
if (bvIsZero(bvAnd(premisesConj, bvNot(goalBits, allOnes)))) {
|
|
1143
|
+
return {
|
|
1144
|
+
goal,
|
|
1145
|
+
steps: state.steps,
|
|
1146
|
+
status: 'complete',
|
|
1147
|
+
derivedFrom: premiseNames,
|
|
1148
|
+
};
|
|
954
1149
|
}
|
|
955
1150
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1151
|
+
else {
|
|
1152
|
+
// Classic fallback
|
|
1153
|
+
const valuations = generateValuations(atomList);
|
|
1154
|
+
let semanticallyValid = true;
|
|
1155
|
+
for (const v of valuations) {
|
|
1156
|
+
const premisesTrue = allAxiomFormulas.every((f) => evaluateClassical(f, v));
|
|
1157
|
+
if (premisesTrue && !evaluateClassical(goal, v)) {
|
|
1158
|
+
semanticallyValid = false;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (semanticallyValid) {
|
|
1163
|
+
return {
|
|
1164
|
+
goal,
|
|
1165
|
+
steps: state.steps,
|
|
1166
|
+
status: 'complete',
|
|
1167
|
+
derivedFrom: premiseNames,
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
963
1170
|
}
|
|
964
1171
|
}
|
|
965
1172
|
return null;
|
|
@@ -1054,6 +1261,20 @@ class ClassicalPropositional {
|
|
|
1054
1261
|
if (wf.length > 0) {
|
|
1055
1262
|
return { status: 'error', diagnostics: wf, formula };
|
|
1056
1263
|
}
|
|
1264
|
+
// Fast path: bitset validity check (no row materialization)
|
|
1265
|
+
const atoms = Array.from(collectAtoms(formula)).sort();
|
|
1266
|
+
if (isPurePropositional(formula) && atoms.length <= 26) {
|
|
1267
|
+
const { result, allOnes } = evaluateBitset(formula, atoms);
|
|
1268
|
+
const isValid = bvEquals(result, allOnes);
|
|
1269
|
+
return {
|
|
1270
|
+
status: isValid ? 'valid' : 'invalid',
|
|
1271
|
+
output: isValid
|
|
1272
|
+
? `${formulaToString(formula)} es VALIDA (tautologia)`
|
|
1273
|
+
: `${formulaToString(formula)} NO es valida`,
|
|
1274
|
+
diagnostics: [],
|
|
1275
|
+
formula,
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1057
1278
|
const tt = this.truthTable(formula);
|
|
1058
1279
|
if (tt.isTautology) {
|
|
1059
1280
|
return {
|
|
@@ -1065,7 +1286,6 @@ class ClassicalPropositional {
|
|
|
1065
1286
|
};
|
|
1066
1287
|
}
|
|
1067
1288
|
else {
|
|
1068
|
-
// Encontrar contramodelo
|
|
1069
1289
|
const cm = tt.rows.find((r) => !r.result);
|
|
1070
1290
|
return {
|
|
1071
1291
|
status: 'invalid',
|
|
@@ -1082,6 +1302,20 @@ class ClassicalPropositional {
|
|
|
1082
1302
|
if (wf.length > 0) {
|
|
1083
1303
|
return { status: 'error', diagnostics: wf, formula };
|
|
1084
1304
|
}
|
|
1305
|
+
// Fast path: bitset satisfiability check (no row materialization)
|
|
1306
|
+
const atoms = Array.from(collectAtoms(formula)).sort();
|
|
1307
|
+
if (isPurePropositional(formula) && atoms.length <= 26) {
|
|
1308
|
+
const { result } = evaluateBitset(formula, atoms);
|
|
1309
|
+
const isSat = !bvIsZero(result);
|
|
1310
|
+
return {
|
|
1311
|
+
status: isSat ? 'satisfiable' : 'unsatisfiable',
|
|
1312
|
+
output: isSat
|
|
1313
|
+
? `${formulaToString(formula)} es SATISFACIBLE`
|
|
1314
|
+
: `${formulaToString(formula)} es INSATISFACIBLE (contradiccion)`,
|
|
1315
|
+
diagnostics: [],
|
|
1316
|
+
formula,
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1085
1319
|
const tt = this.truthTable(formula);
|
|
1086
1320
|
if (tt.isSatisfiable) {
|
|
1087
1321
|
const sat = tt.rows.find((r) => r.result);
|
|
@@ -1171,11 +1405,39 @@ class ClassicalPropositional {
|
|
|
1171
1405
|
if (wf.length > 0) {
|
|
1172
1406
|
return { status: 'error', diagnostics: wf, formula };
|
|
1173
1407
|
}
|
|
1174
|
-
const atoms = Array.from(collectAtoms(formula));
|
|
1408
|
+
const atoms = Array.from(collectAtoms(formula)).sort();
|
|
1409
|
+
const n = atoms.length;
|
|
1410
|
+
// Fast path: bitset finds countermodel in one pass
|
|
1411
|
+
if (isPurePropositional(formula) && n <= 18) {
|
|
1412
|
+
const { result, allOnes } = evaluateBitset(formula, atoms);
|
|
1413
|
+
if (bvEquals(result, allOnes)) {
|
|
1414
|
+
return {
|
|
1415
|
+
status: 'valid',
|
|
1416
|
+
output: `${formulaToString(formula)} es tautologia, no hay contramodelo`,
|
|
1417
|
+
diagnostics: [],
|
|
1418
|
+
formula,
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
// Find first 0 bit (first falsifying row)
|
|
1422
|
+
const inverted = bvNot(result, allOnes);
|
|
1423
|
+
const idx = bvFirstSet(inverted);
|
|
1424
|
+
const v = {};
|
|
1425
|
+
for (let j = 0; j < n; j++) {
|
|
1426
|
+
v[atoms[j]] = Boolean((idx >> (n - 1 - j)) & 1);
|
|
1427
|
+
}
|
|
1428
|
+
const valStr = atoms.map((a) => `${a}=${v[a] ? 'V' : 'F'}`).join(', ');
|
|
1429
|
+
return {
|
|
1430
|
+
status: 'invalid',
|
|
1431
|
+
output: `Contramodelo encontrado para ${formulaToString(formula)}\n ← ${valStr}`,
|
|
1432
|
+
model: { type: 'propositional', valuation: v },
|
|
1433
|
+
diagnostics: [],
|
|
1434
|
+
formula,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
// Fallback: classic evaluation
|
|
1175
1438
|
const valuations = generateValuations(atoms);
|
|
1176
1439
|
for (const v of valuations) {
|
|
1177
|
-
if (!
|
|
1178
|
-
// #25: mark the countermodel valuation with ←
|
|
1440
|
+
if (!evaluateClassical(formula, v)) {
|
|
1179
1441
|
const valStr = atoms.map((a) => `${a}=${v[a] ? 'V' : 'F'}`).join(', ');
|
|
1180
1442
|
return {
|
|
1181
1443
|
status: 'invalid',
|
|
@@ -1296,17 +1558,54 @@ class ClassicalPropositional {
|
|
|
1296
1558
|
}
|
|
1297
1559
|
truthTable(formula) {
|
|
1298
1560
|
const atoms = Array.from(collectAtoms(formula)).sort();
|
|
1299
|
-
const
|
|
1561
|
+
const n = atoms.length;
|
|
1300
1562
|
const subForms = getSubFormulas(formula);
|
|
1563
|
+
const subFormulasInfo = subForms.map((sf) => ({ formula: sf, label: formulaToString(sf) }));
|
|
1564
|
+
// Fast path: bitset evaluation for pure propositional formulas
|
|
1565
|
+
// Limit to n<=20 for row materialization (2^20 = ~1M rows)
|
|
1566
|
+
if (isPurePropositional(formula) && n <= 20) {
|
|
1567
|
+
const { result, atomMasks, total, allOnes } = evaluateBitset(formula, atoms);
|
|
1568
|
+
const satisfyingCount = bitsetPopcount(result);
|
|
1569
|
+
// Evaluate subformulas with bitsets too
|
|
1570
|
+
const subBitsets = subForms.map((sf) => isPurePropositional(sf) ? evaluateBitset(sf, atoms).result : bvCreate(total));
|
|
1571
|
+
// Materialize rows from bitset results
|
|
1572
|
+
const rows = new Array(total);
|
|
1573
|
+
for (let i = 0; i < total; i++) {
|
|
1574
|
+
const v = {};
|
|
1575
|
+
for (let j = 0; j < n; j++) {
|
|
1576
|
+
v[atoms[j]] = bvTestBit(atomMasks.get(atoms[j]), i);
|
|
1577
|
+
}
|
|
1578
|
+
rows[i] = { valuation: v, result: bvTestBit(result, i) };
|
|
1579
|
+
}
|
|
1580
|
+
const subFormulaValues = rows.map((_, i) => {
|
|
1581
|
+
const vals = {};
|
|
1582
|
+
subForms.forEach((sf, si) => {
|
|
1583
|
+
vals[formulaToString(sf)] = bvTestBit(subBitsets[si], i);
|
|
1584
|
+
});
|
|
1585
|
+
return vals;
|
|
1586
|
+
});
|
|
1587
|
+
return {
|
|
1588
|
+
variables: atoms,
|
|
1589
|
+
rows,
|
|
1590
|
+
isTautology: bvEquals(result, allOnes),
|
|
1591
|
+
isContradiction: bvIsZero(result),
|
|
1592
|
+
isSatisfiable: !bvIsZero(result),
|
|
1593
|
+
subFormulas: subFormulasInfo,
|
|
1594
|
+
subFormulaValues,
|
|
1595
|
+
satisfyingCount,
|
|
1596
|
+
totalCount: total,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
// Fallback: classic evaluation
|
|
1600
|
+
const valuations = generateValuations(atoms);
|
|
1301
1601
|
const rows = valuations.map((v) => ({
|
|
1302
1602
|
valuation: v,
|
|
1303
|
-
result:
|
|
1603
|
+
result: evaluateClassical(formula, v),
|
|
1304
1604
|
}));
|
|
1305
|
-
const subFormulasInfo = subForms.map((sf) => ({ formula: sf, label: formulaToString(sf) }));
|
|
1306
1605
|
const subFormulaValues = valuations.map((v) => {
|
|
1307
1606
|
const vals = {};
|
|
1308
1607
|
subForms.forEach((sf) => {
|
|
1309
|
-
vals[formulaToString(sf)] =
|
|
1608
|
+
vals[formulaToString(sf)] = evaluateClassical(sf, v);
|
|
1310
1609
|
});
|
|
1311
1610
|
return vals;
|
|
1312
1611
|
});
|
|
@@ -1317,7 +1616,7 @@ class ClassicalPropositional {
|
|
|
1317
1616
|
isContradiction: rows.every((r) => !r.result),
|
|
1318
1617
|
isSatisfiable: rows.some((r) => r.result),
|
|
1319
1618
|
subFormulas: subFormulasInfo,
|
|
1320
|
-
subFormulaValues
|
|
1619
|
+
subFormulaValues,
|
|
1321
1620
|
satisfyingCount: rows.filter((r) => r.result).length,
|
|
1322
1621
|
totalCount: rows.length,
|
|
1323
1622
|
};
|