@stevenvo780/st-lang 4.13.0 → 4.14.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/reasoning/hoare-logic/index.d.ts +130 -0
- package/dist/reasoning/hoare-logic/index.d.ts.map +1 -0
- package/dist/reasoning/hoare-logic/index.js +535 -0
- package/dist/reasoning/hoare-logic/index.js.map +1 -0
- package/dist/reasoning/model-checking/index.d.ts +113 -0
- package/dist/reasoning/model-checking/index.d.ts.map +1 -0
- package/dist/reasoning/model-checking/index.js +786 -0
- package/dist/reasoning/model-checking/index.js.map +1 -0
- package/dist/reasoning/separation-logic/index.d.ts +190 -0
- package/dist/reasoning/separation-logic/index.d.ts.map +1 -0
- package/dist/reasoning/separation-logic/index.js +758 -0
- package/dist/reasoning/separation-logic/index.js.map +1 -0
- package/dist/reasoning/universal-algebra/index.d.ts +196 -0
- package/dist/reasoning/universal-algebra/index.d.ts.map +1 -0
- package/dist/reasoning/universal-algebra/index.js +865 -0
- package/dist/reasoning/universal-algebra/index.js.map +1 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.d.ts +2 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.d.ts.map +1 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.js +340 -0
- package/dist/tests/reasoning/hoare-logic/hoare-logic.test.js.map +1 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.d.ts +2 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.d.ts.map +1 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.js +222 -0
- package/dist/tests/reasoning/model-checking/model-checking.test.js.map +1 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.d.ts +2 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.d.ts.map +1 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.js +311 -0
- package/dist/tests/reasoning/separation-logic/separation-logic.test.js.map +1 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.d.ts +2 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.d.ts.map +1 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.js +289 -0
- package/dist/tests/reasoning/universal-algebra/universal-algebra.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// ST Universal Algebra — álgebras, homomorfismos, congruencias,
|
|
4
|
+
// álgebras libres (term algebras), teorías ecuacionales y
|
|
5
|
+
// variedades (Birkhoff).
|
|
6
|
+
// ============================================================
|
|
7
|
+
//
|
|
8
|
+
// Una *signatura* Σ es un conjunto de símbolos de operación con
|
|
9
|
+
// aridad. Un Σ-álgebra A = (A, (f^A)_{f∈Σ}) interpreta cada símbolo
|
|
10
|
+
// como una operación total sobre el carrier finito A.
|
|
11
|
+
//
|
|
12
|
+
// Construcciones implementadas:
|
|
13
|
+
//
|
|
14
|
+
// - Verificación estructural: signatura bien formada, álgebra
|
|
15
|
+
// cerrada bajo sus operaciones.
|
|
16
|
+
// - Homomorfismos h : A → B preservan todas las operaciones
|
|
17
|
+
// (h(f^A(a₁,…,aₙ)) = f^B(h(a₁),…,h(aₙ))). Cómputo de imagen y
|
|
18
|
+
// kernel (relación de equivalencia inducida).
|
|
19
|
+
// - Congruencias θ ⊆ A×A: equivalencia + compatibilidad con cada
|
|
20
|
+
// operación. Álgebra cociente A/θ.
|
|
21
|
+
// - Álgebra de términos T_Σ(X): álgebra libre sobre generadores X.
|
|
22
|
+
// Substitución y reducción módulo un conjunto de ecuaciones
|
|
23
|
+
// (term rewriting confluente para casos sencillos).
|
|
24
|
+
// - Ecuaciones t₁ = t₂. Comprobación finita de si A ⊨ eq por
|
|
25
|
+
// muestreo o enumeración exhaustiva (carrier pequeño).
|
|
26
|
+
// - Variedades: A ∈ V(E) sii A satisface todas las ecuaciones
|
|
27
|
+
// de E. Birkhoff caracteriza variedades como clases cerradas
|
|
28
|
+
// bajo H, S, P (homomorphic image, subalgebras, products) —
|
|
29
|
+
// no implementamos el checker completo, sí el test ecuacional.
|
|
30
|
+
// - Signaturas y ecuaciones estándar: grupos, anillos, retículos,
|
|
31
|
+
// abelianos.
|
|
32
|
+
//
|
|
33
|
+
// Igualdad de elementos del carrier: por defecto se usa Object.is.
|
|
34
|
+
// Las álgebras pueden definir su propio `eq` para soportar
|
|
35
|
+
// elementos estructurales (tuplas, conjuntos, términos).
|
|
36
|
+
// ============================================================
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.isValidSignature = isValidSignature;
|
|
39
|
+
exports.isAlgebra = isAlgebra;
|
|
40
|
+
exports.isHomomorphism = isHomomorphism;
|
|
41
|
+
exports.image = image;
|
|
42
|
+
exports.kernel = kernel;
|
|
43
|
+
exports.isCongruence = isCongruence;
|
|
44
|
+
exports.equivalenceClasses = equivalenceClasses;
|
|
45
|
+
exports.quotientAlgebra = quotientAlgebra;
|
|
46
|
+
exports.isVarTerm = isVarTerm;
|
|
47
|
+
exports.isOpTerm = isOpTerm;
|
|
48
|
+
exports.termToString = termToString;
|
|
49
|
+
exports.termEquals = termEquals;
|
|
50
|
+
exports.termSubstitute = termSubstitute;
|
|
51
|
+
exports.termAlgebra = termAlgebra;
|
|
52
|
+
exports.termEqualsModulo = termEqualsModulo;
|
|
53
|
+
exports.freeVars = freeVars;
|
|
54
|
+
exports.evalTerm = evalTerm;
|
|
55
|
+
exports.modelsEquation = modelsEquation;
|
|
56
|
+
exports.variety = variety;
|
|
57
|
+
exports.groupSignature = groupSignature;
|
|
58
|
+
exports.ringSignature = ringSignature;
|
|
59
|
+
exports.latticeSignature = latticeSignature;
|
|
60
|
+
exports.groupEquations = groupEquations;
|
|
61
|
+
exports.abelianEquations = abelianEquations;
|
|
62
|
+
exports.ringEquations = ringEquations;
|
|
63
|
+
exports.latticeEquations = latticeEquations;
|
|
64
|
+
exports.cyclicGroupAlgebra = cyclicGroupAlgebra;
|
|
65
|
+
exports.cyclicRingAlgebra = cyclicRingAlgebra;
|
|
66
|
+
exports._enumerateTuples = enumerateTuples;
|
|
67
|
+
exports.carrierIndex = carrierIndex;
|
|
68
|
+
const defaultEq = (a, b) => Object.is(a, b);
|
|
69
|
+
const eqOf = (A) => A.eq ?? defaultEq;
|
|
70
|
+
const contains = (xs, x, eq) => {
|
|
71
|
+
for (const y of xs)
|
|
72
|
+
if (eq(x, y))
|
|
73
|
+
return true;
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
const indexOfEq = (xs, x, eq) => {
|
|
77
|
+
for (let i = 0; i < xs.length; i++)
|
|
78
|
+
if (eq(xs[i], x))
|
|
79
|
+
return i;
|
|
80
|
+
return -1;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Verifica que la signatura no tenga símbolos duplicados ni aridades
|
|
84
|
+
* negativas.
|
|
85
|
+
*/
|
|
86
|
+
function isValidSignature(sig) {
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const op of sig.operations) {
|
|
89
|
+
if (op.arity < 0)
|
|
90
|
+
return false;
|
|
91
|
+
if (seen.has(op.name))
|
|
92
|
+
return false;
|
|
93
|
+
seen.add(op.name);
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verifica que `A` interpreta cada símbolo de su signatura y que
|
|
99
|
+
* las operaciones son totales y cerradas sobre el carrier.
|
|
100
|
+
*
|
|
101
|
+
* Para aridad n, se enumeran |A|^n tuplas — usar sólo con carriers
|
|
102
|
+
* finitos pequeños (n·|A|^aridad <= ~10⁶).
|
|
103
|
+
*/
|
|
104
|
+
function isAlgebra(A) {
|
|
105
|
+
if (!isValidSignature(A.signature))
|
|
106
|
+
return false;
|
|
107
|
+
const eq = eqOf(A);
|
|
108
|
+
for (const op of A.signature.operations) {
|
|
109
|
+
const f = A.operations.get(op.name);
|
|
110
|
+
if (!f)
|
|
111
|
+
return false;
|
|
112
|
+
// Enumeración de aridad-tuplas
|
|
113
|
+
const tuples = enumerateTuples(A.carrier, op.arity);
|
|
114
|
+
for (const tuple of tuples) {
|
|
115
|
+
let result;
|
|
116
|
+
try {
|
|
117
|
+
result = f(...tuple);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (!contains(A.carrier, result, eq))
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
function enumerateTuples(carrier, arity) {
|
|
129
|
+
if (arity === 0)
|
|
130
|
+
return [[]];
|
|
131
|
+
if (carrier.length === 0)
|
|
132
|
+
return [];
|
|
133
|
+
const out = [];
|
|
134
|
+
const indices = new Array(arity).fill(0);
|
|
135
|
+
const n = carrier.length;
|
|
136
|
+
// Iteración estilo odómetro: |carrier|^arity tuplas.
|
|
137
|
+
outer: while (true) {
|
|
138
|
+
const tup = new Array(arity);
|
|
139
|
+
for (let i = 0; i < arity; i++)
|
|
140
|
+
tup[i] = carrier[indices[i]];
|
|
141
|
+
out.push(tup);
|
|
142
|
+
// incrementar
|
|
143
|
+
for (let i = arity - 1; i >= 0; i--) {
|
|
144
|
+
const cur = indices[i];
|
|
145
|
+
if (cur + 1 < n) {
|
|
146
|
+
indices[i] = cur + 1;
|
|
147
|
+
continue outer;
|
|
148
|
+
}
|
|
149
|
+
indices[i] = 0;
|
|
150
|
+
if (i === 0)
|
|
151
|
+
break outer;
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Verifica que `h` preserva todas las operaciones:
|
|
159
|
+
* h(f^A(a₁,…,aₙ)) = f^B(h(a₁),…,h(aₙ)) para toda tupla y operación f.
|
|
160
|
+
*
|
|
161
|
+
* Requiere signaturas idénticas en source y target (mismos nombres y
|
|
162
|
+
* aridades).
|
|
163
|
+
*/
|
|
164
|
+
function isHomomorphism(h) {
|
|
165
|
+
const { source, target, map } = h;
|
|
166
|
+
if (!signaturesMatch(source.signature, target.signature))
|
|
167
|
+
return false;
|
|
168
|
+
const eqB = eqOf(target);
|
|
169
|
+
for (const op of source.signature.operations) {
|
|
170
|
+
const fA = source.operations.get(op.name);
|
|
171
|
+
const fB = target.operations.get(op.name);
|
|
172
|
+
if (!fA || !fB)
|
|
173
|
+
return false;
|
|
174
|
+
const tuples = enumerateTuples(source.carrier, op.arity);
|
|
175
|
+
for (const tup of tuples) {
|
|
176
|
+
const lhs = map(fA(...tup));
|
|
177
|
+
const rhs = fB(...tup.map(map));
|
|
178
|
+
if (!eqB(lhs, rhs))
|
|
179
|
+
return false;
|
|
180
|
+
if (!contains(target.carrier, lhs, eqB))
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
function signaturesMatch(a, b) {
|
|
187
|
+
if (a.operations.length !== b.operations.length)
|
|
188
|
+
return false;
|
|
189
|
+
const bMap = new Map(b.operations.map((o) => [o.name, o.arity]));
|
|
190
|
+
for (const op of a.operations) {
|
|
191
|
+
const ar = bMap.get(op.name);
|
|
192
|
+
if (ar !== op.arity)
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Imagen de un homomorfismo: { h(a) : a ∈ A }, deduplicada por igualdad
|
|
199
|
+
* del target.
|
|
200
|
+
*/
|
|
201
|
+
function image(h) {
|
|
202
|
+
const eqB = eqOf(h.target);
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const a of h.source.carrier) {
|
|
205
|
+
const y = h.map(a);
|
|
206
|
+
if (!contains(out, y, eqB))
|
|
207
|
+
out.push(y);
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Kernel: relación de equivalencia ker h = { (a,b) : h(a) = h(b) }.
|
|
213
|
+
* Devuelve sólo pares (a,b) con a ≠ b (incluyendo (b,a)); los reflexivos
|
|
214
|
+
* son implícitos.
|
|
215
|
+
*/
|
|
216
|
+
function kernel(h) {
|
|
217
|
+
const eqA = eqOf(h.source);
|
|
218
|
+
const eqB = eqOf(h.target);
|
|
219
|
+
const out = [];
|
|
220
|
+
const xs = h.source.carrier;
|
|
221
|
+
for (let i = 0; i < xs.length; i++) {
|
|
222
|
+
for (let j = 0; j < xs.length; j++) {
|
|
223
|
+
if (i === j)
|
|
224
|
+
continue;
|
|
225
|
+
const a = xs[i];
|
|
226
|
+
const b = xs[j];
|
|
227
|
+
if (eqA(a, b))
|
|
228
|
+
continue;
|
|
229
|
+
if (eqB(h.map(a), h.map(b)))
|
|
230
|
+
out.push([a, b]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* θ es congruencia sii es:
|
|
237
|
+
* 1) reflexiva : (a,a) ∈ θ para todo a ∈ A
|
|
238
|
+
* 2) simétrica : (a,b) ∈ θ ⇒ (b,a) ∈ θ
|
|
239
|
+
* 3) transitiva : (a,b),(b,c) ∈ θ ⇒ (a,c) ∈ θ
|
|
240
|
+
* 4) compatible : (aᵢ,bᵢ) ∈ θ ⇒ (f(a₁,…,aₙ), f(b₁,…,bₙ)) ∈ θ
|
|
241
|
+
*
|
|
242
|
+
* La relación puede entregarse minimamente (sólo pares relevantes); aquí
|
|
243
|
+
* se verifica el cierre completo asumiendo la relación dada.
|
|
244
|
+
*/
|
|
245
|
+
function isCongruence(c) {
|
|
246
|
+
const A = c.algebra;
|
|
247
|
+
const eq = eqOf(A);
|
|
248
|
+
const related = (a, b) => {
|
|
249
|
+
if (eq(a, b))
|
|
250
|
+
return true;
|
|
251
|
+
return c.relation.some(([x, y]) => eq(x, a) && eq(y, b));
|
|
252
|
+
};
|
|
253
|
+
// Reflexividad
|
|
254
|
+
for (const a of A.carrier)
|
|
255
|
+
if (!related(a, a))
|
|
256
|
+
return false;
|
|
257
|
+
// Simetría
|
|
258
|
+
for (const [a, b] of c.relation)
|
|
259
|
+
if (!related(b, a))
|
|
260
|
+
return false;
|
|
261
|
+
// Transitividad
|
|
262
|
+
for (const [a, b] of c.relation) {
|
|
263
|
+
for (const [c2, d] of c.relation) {
|
|
264
|
+
if (eq(b, c2)) {
|
|
265
|
+
if (!related(a, d))
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Compatibilidad con cada operación
|
|
271
|
+
for (const op of A.signature.operations) {
|
|
272
|
+
const f = A.operations.get(op.name);
|
|
273
|
+
if (!f)
|
|
274
|
+
return false;
|
|
275
|
+
const tuplesA = enumerateTuples(A.carrier, op.arity);
|
|
276
|
+
for (const aTup of tuplesA) {
|
|
277
|
+
const bTup = enumerateTuples(A.carrier, op.arity);
|
|
278
|
+
for (const bT of bTup) {
|
|
279
|
+
let allRelated = true;
|
|
280
|
+
for (let i = 0; i < op.arity; i++) {
|
|
281
|
+
if (!related(aTup[i], bT[i])) {
|
|
282
|
+
allRelated = false;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!allRelated)
|
|
287
|
+
continue;
|
|
288
|
+
if (!related(f(...aTup), f(...bT)))
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Calcula las clases de equivalencia inducidas por una relación
|
|
297
|
+
* (asumida ya transitiva-y-simétrica; el módulo `isCongruence` valida
|
|
298
|
+
* el caso). Cada clase es un T[] (en el orden del carrier).
|
|
299
|
+
*/
|
|
300
|
+
function equivalenceClasses(c) {
|
|
301
|
+
const A = c.algebra;
|
|
302
|
+
const eq = eqOf(A);
|
|
303
|
+
const related = (a, b) => {
|
|
304
|
+
if (eq(a, b))
|
|
305
|
+
return true;
|
|
306
|
+
return c.relation.some(([x, y]) => eq(x, a) && eq(y, b));
|
|
307
|
+
};
|
|
308
|
+
// Closure transitivo on-the-fly
|
|
309
|
+
const reachable = (a) => {
|
|
310
|
+
const cls = [a];
|
|
311
|
+
const queue = [a];
|
|
312
|
+
let head = 0;
|
|
313
|
+
while (head < queue.length) {
|
|
314
|
+
const cur = queue[head];
|
|
315
|
+
head++;
|
|
316
|
+
for (const x of A.carrier) {
|
|
317
|
+
if (contains(cls, x, eq))
|
|
318
|
+
continue;
|
|
319
|
+
if (related(cur, x) || related(x, cur)) {
|
|
320
|
+
cls.push(x);
|
|
321
|
+
queue.push(x);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return cls;
|
|
326
|
+
};
|
|
327
|
+
const classes = [];
|
|
328
|
+
const seen = [];
|
|
329
|
+
for (const a of A.carrier) {
|
|
330
|
+
if (contains(seen, a, eq))
|
|
331
|
+
continue;
|
|
332
|
+
const cls = reachable(a);
|
|
333
|
+
for (const x of cls)
|
|
334
|
+
seen.push(x);
|
|
335
|
+
classes.push(cls);
|
|
336
|
+
}
|
|
337
|
+
return classes;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Álgebra cociente A/θ. El carrier son las clases de equivalencia; cada
|
|
341
|
+
* operación se eleva representando la clase por su primer elemento.
|
|
342
|
+
*
|
|
343
|
+
* No verifica que la relación sea congruencia; usar `isCongruence` antes.
|
|
344
|
+
*/
|
|
345
|
+
function quotientAlgebra(c) {
|
|
346
|
+
const A = c.algebra;
|
|
347
|
+
const eq = eqOf(A);
|
|
348
|
+
const classes = equivalenceClasses(c);
|
|
349
|
+
const classOf = (x) => {
|
|
350
|
+
for (const cls of classes)
|
|
351
|
+
if (contains(cls, x, eq))
|
|
352
|
+
return cls;
|
|
353
|
+
throw new Error('quotientAlgebra: elemento fuera del carrier');
|
|
354
|
+
};
|
|
355
|
+
const ops = new Map();
|
|
356
|
+
for (const opSym of A.signature.operations) {
|
|
357
|
+
const f = A.operations.get(opSym.name);
|
|
358
|
+
if (!f)
|
|
359
|
+
throw new Error(`quotientAlgebra: operación ${opSym.name} sin implementación`);
|
|
360
|
+
ops.set(opSym.name, (...args) => {
|
|
361
|
+
const reps = args.map((cls) => cls[0]);
|
|
362
|
+
return classOf(f(...reps));
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
signature: A.signature,
|
|
367
|
+
carrier: classes,
|
|
368
|
+
operations: ops,
|
|
369
|
+
eq: (cls1, cls2) => {
|
|
370
|
+
if (cls1.length !== cls2.length)
|
|
371
|
+
return false;
|
|
372
|
+
// Las clases son canónicas (el primer representante encontrado en
|
|
373
|
+
// `carrier`), por lo que basta comparar el primer elemento.
|
|
374
|
+
return eq(cls1[0], cls2[0]);
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function isVarTerm(t) {
|
|
379
|
+
return 'var' in t;
|
|
380
|
+
}
|
|
381
|
+
function isOpTerm(t) {
|
|
382
|
+
return 'op' in t;
|
|
383
|
+
}
|
|
384
|
+
function termToString(t) {
|
|
385
|
+
if (isVarTerm(t))
|
|
386
|
+
return t.var;
|
|
387
|
+
if (t.args.length === 0)
|
|
388
|
+
return t.op;
|
|
389
|
+
return `${t.op}(${t.args.map(termToString).join(',')})`;
|
|
390
|
+
}
|
|
391
|
+
function termEquals(t1, t2) {
|
|
392
|
+
if (isVarTerm(t1) && isVarTerm(t2))
|
|
393
|
+
return t1.var === t2.var;
|
|
394
|
+
if (isOpTerm(t1) && isOpTerm(t2)) {
|
|
395
|
+
if (t1.op !== t2.op)
|
|
396
|
+
return false;
|
|
397
|
+
if (t1.args.length !== t2.args.length)
|
|
398
|
+
return false;
|
|
399
|
+
for (let i = 0; i < t1.args.length; i++) {
|
|
400
|
+
if (!termEquals(t1.args[i], t2.args[i]))
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Substitución t[x ↦ σ(x)]: reemplaza cada variable por su término.
|
|
409
|
+
* Variables fuera de `sub` quedan intactas.
|
|
410
|
+
*/
|
|
411
|
+
function termSubstitute(t, sub) {
|
|
412
|
+
if (isVarTerm(t)) {
|
|
413
|
+
return Object.prototype.hasOwnProperty.call(sub, t.var) ? sub[t.var] : t;
|
|
414
|
+
}
|
|
415
|
+
return { op: t.op, args: t.args.map((a) => termSubstitute(a, sub)) };
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Álgebra de términos T_Σ(X) hasta profundidad `maxDepth`. Útil para
|
|
419
|
+
* generar muestras del álgebra libre — el carrier completo es infinito
|
|
420
|
+
* cuando hay operaciones de aridad ≥ 1.
|
|
421
|
+
*
|
|
422
|
+
* Por defecto `maxDepth=2`: incluye constantes, variables y un nivel
|
|
423
|
+
* de aplicación.
|
|
424
|
+
*/
|
|
425
|
+
function termAlgebra(signature, generators, maxDepth = 2) {
|
|
426
|
+
if (!isValidSignature(signature))
|
|
427
|
+
throw new Error('termAlgebra: signatura inválida');
|
|
428
|
+
// BFS por profundidad
|
|
429
|
+
const seen = [];
|
|
430
|
+
const eqTerm = (a, b) => termEquals(a, b);
|
|
431
|
+
const push = (t) => {
|
|
432
|
+
if (!contains(seen, t, eqTerm))
|
|
433
|
+
seen.push(t);
|
|
434
|
+
};
|
|
435
|
+
// Profundidad 0: variables y constantes (aridad 0).
|
|
436
|
+
for (const v of generators)
|
|
437
|
+
push({ var: v });
|
|
438
|
+
for (const op of signature.operations)
|
|
439
|
+
if (op.arity === 0)
|
|
440
|
+
push({ op: op.name, args: [] });
|
|
441
|
+
let curLayerEnd = seen.length;
|
|
442
|
+
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
443
|
+
const layerStart = curLayerEnd;
|
|
444
|
+
const available = seen.slice(0, curLayerEnd);
|
|
445
|
+
for (const op of signature.operations) {
|
|
446
|
+
if (op.arity === 0)
|
|
447
|
+
continue;
|
|
448
|
+
const tuples = enumerateTuples(available, op.arity);
|
|
449
|
+
for (const tup of tuples) {
|
|
450
|
+
push({ op: op.name, args: tup });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
curLayerEnd = seen.length;
|
|
454
|
+
if (layerStart === curLayerEnd)
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
const ops = new Map();
|
|
458
|
+
for (const op of signature.operations) {
|
|
459
|
+
ops.set(op.name, (...args) => ({ op: op.name, args }));
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
signature,
|
|
463
|
+
carrier: seen,
|
|
464
|
+
operations: ops,
|
|
465
|
+
eq: eqTerm,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Comprueba si t1 y t2 son iguales módulo el conjunto de ecuaciones
|
|
470
|
+
* `eqs`, aplicando reescritura ingenua hasta profundidad limitada.
|
|
471
|
+
*
|
|
472
|
+
* NO es decidible en general; este checker es una heurística para
|
|
473
|
+
* casos pequeños: aplica cada ecuación en ambas direcciones desde t1
|
|
474
|
+
* y t2 hasta `maxSteps` pasos buscando una forma común.
|
|
475
|
+
*/
|
|
476
|
+
function termEqualsModulo(t1, t2, eqs, maxSteps = 50) {
|
|
477
|
+
if (termEquals(t1, t2))
|
|
478
|
+
return true;
|
|
479
|
+
const seen1 = [t1];
|
|
480
|
+
const seen2 = [t2];
|
|
481
|
+
const frontier1 = [t1];
|
|
482
|
+
const frontier2 = [t2];
|
|
483
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
484
|
+
const next1 = [];
|
|
485
|
+
for (const t of frontier1) {
|
|
486
|
+
for (const rewrite of rewriteOneStep(t, eqs)) {
|
|
487
|
+
if (!contains(seen1, rewrite, termEquals)) {
|
|
488
|
+
seen1.push(rewrite);
|
|
489
|
+
next1.push(rewrite);
|
|
490
|
+
if (contains(seen2, rewrite, termEquals))
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const next2 = [];
|
|
496
|
+
for (const t of frontier2) {
|
|
497
|
+
for (const rewrite of rewriteOneStep(t, eqs)) {
|
|
498
|
+
if (!contains(seen2, rewrite, termEquals)) {
|
|
499
|
+
seen2.push(rewrite);
|
|
500
|
+
next2.push(rewrite);
|
|
501
|
+
if (contains(seen1, rewrite, termEquals))
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (next1.length === 0 && next2.length === 0)
|
|
507
|
+
return false;
|
|
508
|
+
frontier1.length = 0;
|
|
509
|
+
frontier2.length = 0;
|
|
510
|
+
frontier1.push(...next1);
|
|
511
|
+
frontier2.push(...next2);
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Devuelve todas las reescrituras de un paso de `t` usando `eqs` en
|
|
517
|
+
* ambas direcciones, intentando match en la raíz y en cada subtérmino.
|
|
518
|
+
*/
|
|
519
|
+
function rewriteOneStep(t, eqs) {
|
|
520
|
+
const out = [];
|
|
521
|
+
for (const [l, r] of eqs) {
|
|
522
|
+
const m1 = matchTerm(l, t);
|
|
523
|
+
if (m1)
|
|
524
|
+
out.push(termSubstitute(r, m1));
|
|
525
|
+
const m2 = matchTerm(r, t);
|
|
526
|
+
if (m2)
|
|
527
|
+
out.push(termSubstitute(l, m2));
|
|
528
|
+
}
|
|
529
|
+
// Recurse en subtérminos
|
|
530
|
+
if (isOpTerm(t)) {
|
|
531
|
+
for (let i = 0; i < t.args.length; i++) {
|
|
532
|
+
const sub = t.args[i];
|
|
533
|
+
const subResults = rewriteOneStep(sub, eqs);
|
|
534
|
+
for (const s of subResults) {
|
|
535
|
+
const newArgs = t.args.slice();
|
|
536
|
+
newArgs[i] = s;
|
|
537
|
+
out.push({ op: t.op, args: newArgs });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Match de patrón: ¿hay σ tal que pattern[σ] ≡ term? Las variables del
|
|
545
|
+
* pattern son los `var`. Lineal-de-izquierda-a-derecha; no chequea
|
|
546
|
+
* occurs-check (innecesario para matching, sí para unificación).
|
|
547
|
+
*/
|
|
548
|
+
function matchTerm(pattern, term) {
|
|
549
|
+
const sub = {};
|
|
550
|
+
const go = (p, t) => {
|
|
551
|
+
if (isVarTerm(p)) {
|
|
552
|
+
const existing = sub[p.var];
|
|
553
|
+
if (existing)
|
|
554
|
+
return termEquals(existing, t);
|
|
555
|
+
sub[p.var] = t;
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
if (!isOpTerm(t))
|
|
559
|
+
return false;
|
|
560
|
+
if (p.op !== t.op)
|
|
561
|
+
return false;
|
|
562
|
+
if (p.args.length !== t.args.length)
|
|
563
|
+
return false;
|
|
564
|
+
for (let i = 0; i < p.args.length; i++) {
|
|
565
|
+
if (!go(p.args[i], t.args[i]))
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
return true;
|
|
569
|
+
};
|
|
570
|
+
return go(pattern, term) ? sub : null;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Variables libres de un término (orden de aparición, deduplicadas).
|
|
574
|
+
*/
|
|
575
|
+
function freeVars(t) {
|
|
576
|
+
const out = [];
|
|
577
|
+
const go = (s) => {
|
|
578
|
+
if (isVarTerm(s)) {
|
|
579
|
+
if (!out.includes(s.var))
|
|
580
|
+
out.push(s.var);
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
for (const a of s.args)
|
|
584
|
+
go(a);
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
go(t);
|
|
588
|
+
return out;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Evalúa un término en un álgebra dado un assignment de variables.
|
|
592
|
+
*/
|
|
593
|
+
function evalTerm(A, t, env) {
|
|
594
|
+
if (isVarTerm(t)) {
|
|
595
|
+
if (!Object.prototype.hasOwnProperty.call(env, t.var)) {
|
|
596
|
+
throw new Error(`evalTerm: variable libre ${t.var} sin asignación`);
|
|
597
|
+
}
|
|
598
|
+
return env[t.var];
|
|
599
|
+
}
|
|
600
|
+
const f = A.operations.get(t.op);
|
|
601
|
+
if (!f)
|
|
602
|
+
throw new Error(`evalTerm: operación ${t.op} no implementada en el álgebra`);
|
|
603
|
+
const args = t.args.map((a) => evalTerm(A, a, env));
|
|
604
|
+
return f(...args);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* A ⊨ (t₁ = t₂): para todo assignment de variables libres, evalTerm
|
|
608
|
+
* coincide. Si `samples` es positivo, sólo se prueban `samples` tuplas
|
|
609
|
+
* aleatorias; si es 0 o undefined, se enumeran todas (|A|^k).
|
|
610
|
+
*/
|
|
611
|
+
function modelsEquation(A, eq, samples = 0) {
|
|
612
|
+
const eqT = eqOf(A);
|
|
613
|
+
const vars = Array.from(new Set([...freeVars(eq.left), ...freeVars(eq.right)]));
|
|
614
|
+
if (vars.length === 0) {
|
|
615
|
+
// No vars: ecuación cerrada
|
|
616
|
+
try {
|
|
617
|
+
return eqT(evalTerm(A, eq.left, {}), evalTerm(A, eq.right, {}));
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (samples > 0) {
|
|
624
|
+
for (let s = 0; s < samples; s++) {
|
|
625
|
+
const env = {};
|
|
626
|
+
for (const v of vars) {
|
|
627
|
+
const idx = Math.floor(Math.random() * A.carrier.length);
|
|
628
|
+
env[v] = A.carrier[idx];
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
if (!eqT(evalTerm(A, eq.left, env), evalTerm(A, eq.right, env)))
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
// Enumeración exhaustiva
|
|
641
|
+
const tuples = enumerateTuples(A.carrier, vars.length);
|
|
642
|
+
for (const tup of tuples) {
|
|
643
|
+
const env = {};
|
|
644
|
+
for (let i = 0; i < vars.length; i++)
|
|
645
|
+
env[vars[i]] = tup[i];
|
|
646
|
+
try {
|
|
647
|
+
if (!eqT(evalTerm(A, eq.left, env), evalTerm(A, eq.right, env)))
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* A pertenece a la variedad V(E) sii A satisface toda ecuación de E.
|
|
658
|
+
*
|
|
659
|
+
* Es la dirección "fácil" de Birkhoff: una variedad puede definirse
|
|
660
|
+
* por ecuaciones, y la pertenencia se verifica ecuación-por-ecuación.
|
|
661
|
+
* El recíproco (clases HSP son ecuacionalmente definibles) es el
|
|
662
|
+
* contenido fuerte del teorema y no se chequea aquí.
|
|
663
|
+
*/
|
|
664
|
+
function variety(equations, A) {
|
|
665
|
+
for (const eq of equations) {
|
|
666
|
+
if (!modelsEquation(A, eq))
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
// ============================================================
|
|
672
|
+
// Standard signatures and equational theories
|
|
673
|
+
// ============================================================
|
|
674
|
+
/**
|
|
675
|
+
* Signatura de grupos en notación multiplicativa: e (constante),
|
|
676
|
+
* inv (unaria), mul (binaria).
|
|
677
|
+
*/
|
|
678
|
+
function groupSignature() {
|
|
679
|
+
return {
|
|
680
|
+
operations: [
|
|
681
|
+
{ name: 'e', arity: 0 },
|
|
682
|
+
{ name: 'inv', arity: 1 },
|
|
683
|
+
{ name: 'mul', arity: 2 },
|
|
684
|
+
],
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Signatura de anillos: 0, 1 (constantes), neg (unaria), add y mul (binarias).
|
|
689
|
+
*/
|
|
690
|
+
function ringSignature() {
|
|
691
|
+
return {
|
|
692
|
+
operations: [
|
|
693
|
+
{ name: 'zero', arity: 0 },
|
|
694
|
+
{ name: 'one', arity: 0 },
|
|
695
|
+
{ name: 'neg', arity: 1 },
|
|
696
|
+
{ name: 'add', arity: 2 },
|
|
697
|
+
{ name: 'mul', arity: 2 },
|
|
698
|
+
],
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Signatura de retículos: join y meet (binarias).
|
|
703
|
+
*/
|
|
704
|
+
function latticeSignature() {
|
|
705
|
+
return {
|
|
706
|
+
operations: [
|
|
707
|
+
{ name: 'join', arity: 2 },
|
|
708
|
+
{ name: 'meet', arity: 2 },
|
|
709
|
+
],
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
const v = (name) => ({ var: name });
|
|
713
|
+
const op = (name, ...args) => ({ op: name, args });
|
|
714
|
+
/**
|
|
715
|
+
* Ecuaciones de grupo (notación multiplicativa, signatura `groupSignature`):
|
|
716
|
+
* - asociatividad de mul
|
|
717
|
+
* - identidad por izquierda y derecha
|
|
718
|
+
* - inverso por izquierda y derecha
|
|
719
|
+
*/
|
|
720
|
+
function groupEquations() {
|
|
721
|
+
const x = v('x');
|
|
722
|
+
const y = v('y');
|
|
723
|
+
const z = v('z');
|
|
724
|
+
const e = op('e');
|
|
725
|
+
return [
|
|
726
|
+
// (x·y)·z = x·(y·z)
|
|
727
|
+
{ left: op('mul', op('mul', x, y), z), right: op('mul', x, op('mul', y, z)) },
|
|
728
|
+
// e·x = x
|
|
729
|
+
{ left: op('mul', e, x), right: x },
|
|
730
|
+
// x·e = x
|
|
731
|
+
{ left: op('mul', x, e), right: x },
|
|
732
|
+
// inv(x)·x = e
|
|
733
|
+
{ left: op('mul', op('inv', x), x), right: e },
|
|
734
|
+
// x·inv(x) = e
|
|
735
|
+
{ left: op('mul', x, op('inv', x)), right: e },
|
|
736
|
+
];
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Ecuaciones adicionales para grupos abelianos: conmutatividad de mul.
|
|
740
|
+
*/
|
|
741
|
+
function abelianEquations() {
|
|
742
|
+
const x = v('x');
|
|
743
|
+
const y = v('y');
|
|
744
|
+
return [...groupEquations(), { left: op('mul', x, y), right: op('mul', y, x) }];
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Ecuaciones de anillo conmutativo con unidad sobre `ringSignature`:
|
|
748
|
+
* grupo abeliano por +, monoide conmutativo por ·, distributividad
|
|
749
|
+
* bilateral.
|
|
750
|
+
*/
|
|
751
|
+
function ringEquations() {
|
|
752
|
+
const x = v('x');
|
|
753
|
+
const y = v('y');
|
|
754
|
+
const z = v('z');
|
|
755
|
+
const zero = op('zero');
|
|
756
|
+
const one = op('one');
|
|
757
|
+
return [
|
|
758
|
+
// (x+y)+z = x+(y+z)
|
|
759
|
+
{ left: op('add', op('add', x, y), z), right: op('add', x, op('add', y, z)) },
|
|
760
|
+
// 0+x = x
|
|
761
|
+
{ left: op('add', zero, x), right: x },
|
|
762
|
+
// x+0 = x
|
|
763
|
+
{ left: op('add', x, zero), right: x },
|
|
764
|
+
// neg(x)+x = 0
|
|
765
|
+
{ left: op('add', op('neg', x), x), right: zero },
|
|
766
|
+
// x+neg(x) = 0
|
|
767
|
+
{ left: op('add', x, op('neg', x)), right: zero },
|
|
768
|
+
// x+y = y+x
|
|
769
|
+
{ left: op('add', x, y), right: op('add', y, x) },
|
|
770
|
+
// (x·y)·z = x·(y·z)
|
|
771
|
+
{ left: op('mul', op('mul', x, y), z), right: op('mul', x, op('mul', y, z)) },
|
|
772
|
+
// 1·x = x
|
|
773
|
+
{ left: op('mul', one, x), right: x },
|
|
774
|
+
// x·1 = x
|
|
775
|
+
{ left: op('mul', x, one), right: x },
|
|
776
|
+
// x·(y+z) = x·y + x·z
|
|
777
|
+
{
|
|
778
|
+
left: op('mul', x, op('add', y, z)),
|
|
779
|
+
right: op('add', op('mul', x, y), op('mul', x, z)),
|
|
780
|
+
},
|
|
781
|
+
// (x+y)·z = x·z + y·z
|
|
782
|
+
{
|
|
783
|
+
left: op('mul', op('add', x, y), z),
|
|
784
|
+
right: op('add', op('mul', x, z), op('mul', y, z)),
|
|
785
|
+
},
|
|
786
|
+
];
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Ecuaciones de retículo sobre `latticeSignature`: idempotencia,
|
|
790
|
+
* conmutatividad, asociatividad y absorción para join y meet.
|
|
791
|
+
*/
|
|
792
|
+
function latticeEquations() {
|
|
793
|
+
const x = v('x');
|
|
794
|
+
const y = v('y');
|
|
795
|
+
const z = v('z');
|
|
796
|
+
return [
|
|
797
|
+
// idempotencia
|
|
798
|
+
{ left: op('join', x, x), right: x },
|
|
799
|
+
{ left: op('meet', x, x), right: x },
|
|
800
|
+
// conmutatividad
|
|
801
|
+
{ left: op('join', x, y), right: op('join', y, x) },
|
|
802
|
+
{ left: op('meet', x, y), right: op('meet', y, x) },
|
|
803
|
+
// asociatividad
|
|
804
|
+
{ left: op('join', op('join', x, y), z), right: op('join', x, op('join', y, z)) },
|
|
805
|
+
{ left: op('meet', op('meet', x, y), z), right: op('meet', x, op('meet', y, z)) },
|
|
806
|
+
// absorción
|
|
807
|
+
{ left: op('join', x, op('meet', x, y)), right: x },
|
|
808
|
+
{ left: op('meet', x, op('join', x, y)), right: x },
|
|
809
|
+
];
|
|
810
|
+
}
|
|
811
|
+
// ============================================================
|
|
812
|
+
// Construcciones canónicas: Z/nZ como Σ-grupo
|
|
813
|
+
// ============================================================
|
|
814
|
+
/**
|
|
815
|
+
* Construye el álgebra cíclica Z/nZ en la signatura de grupo
|
|
816
|
+
* (`groupSignature`), con `mul` interpretado como suma módulo n.
|
|
817
|
+
*/
|
|
818
|
+
function cyclicGroupAlgebra(n) {
|
|
819
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
820
|
+
throw new Error('cyclicGroupAlgebra: n debe ser un entero positivo');
|
|
821
|
+
}
|
|
822
|
+
const carrier = [];
|
|
823
|
+
for (let i = 0; i < n; i++)
|
|
824
|
+
carrier.push(i);
|
|
825
|
+
const ops = new Map();
|
|
826
|
+
ops.set('e', () => 0);
|
|
827
|
+
ops.set('inv', (a) => (n - a) % n);
|
|
828
|
+
ops.set('mul', (a, b) => (a + b) % n);
|
|
829
|
+
return {
|
|
830
|
+
signature: groupSignature(),
|
|
831
|
+
carrier,
|
|
832
|
+
operations: ops,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Construye Z/nZ como anillo conmutativo con unidad. Para n=1 colapsa
|
|
837
|
+
* al anillo trivial (0=1).
|
|
838
|
+
*/
|
|
839
|
+
function cyclicRingAlgebra(n) {
|
|
840
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
841
|
+
throw new Error('cyclicRingAlgebra: n debe ser un entero positivo');
|
|
842
|
+
}
|
|
843
|
+
const carrier = [];
|
|
844
|
+
for (let i = 0; i < n; i++)
|
|
845
|
+
carrier.push(i);
|
|
846
|
+
const ops = new Map();
|
|
847
|
+
ops.set('zero', () => 0);
|
|
848
|
+
ops.set('one', () => 1 % n);
|
|
849
|
+
ops.set('neg', (a) => (n - a) % n);
|
|
850
|
+
ops.set('add', (a, b) => (a + b) % n);
|
|
851
|
+
ops.set('mul', (a, b) => (a * b) % n);
|
|
852
|
+
return {
|
|
853
|
+
signature: ringSignature(),
|
|
854
|
+
carrier,
|
|
855
|
+
operations: ops,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Calcula el `index` de un elemento en el carrier por la igualdad del
|
|
860
|
+
* álgebra (Object.is o `eq` provisto). Devuelve -1 si no aparece.
|
|
861
|
+
*/
|
|
862
|
+
function carrierIndex(A, x) {
|
|
863
|
+
return indexOfEq(A.carrier, x, eqOf(A));
|
|
864
|
+
}
|
|
865
|
+
//# sourceMappingURL=index.js.map
|