meta-log-db
Version:
Native database package for Meta-Log (ProLog, DataLog, R5RS)
200 lines (172 loc) • 5.16 kB
text/typescript
/**
* Homology Validator
*
* Validates chain complexes using algebraic topology principles.
* Ensures ∂² = 0 property holds for all boundary operators.
*/
import { ChainComplex, Cell, HomologyResult } from './types.js';
/**
* Homology Validator
*
* Validates chain complexes and computes homology groups
*/
export class HomologyValidator {
private complex: ChainComplex;
constructor(complex: ChainComplex) {
this.complex = complex;
}
/**
* Validate that ∂_{n-1} ∘ ∂_n = 0 for all dimensions
*
* @param n - Dimension to validate (1, 2, 3, or 4)
* @returns true if ∂² = 0 holds, false otherwise
*/
validateComposition(n: 1 | 2 | 3 | 4): boolean {
const cells = this.getCells(n);
for (const cell of cells) {
// Compute boundary_n(cell)
const boundary_n = this.complex.boundary.get(cell.id) || [];
// For each boundary cell, compute boundary_{n-1}
for (const bId of boundary_n) {
const boundary_n_minus_1 = this.complex.boundary.get(bId) || [];
// Check if boundary forms a closed cycle (sums to zero)
if (!this.isCycle(boundary_n_minus_1)) {
return false;
}
}
}
return true;
}
/**
* Compute Betti number for dimension n
*
* Betti number β_n = dim(ker(∂_n)) - dim(im(∂_{n+1}))
*
* @param n - Dimension (0-4)
* @returns Betti number β_n
*/
computeBetti(n: number): number {
if (n < 0 || n > 4) {
throw new Error(`Invalid dimension: ${n}. Must be 0-4`);
}
const cycles = this.computeKernel(n);
const boundaries = this.computeImage(n + 1);
// Betti number = dimension of cycles - dimension of boundaries
return cycles.length - boundaries.length;
}
/**
* Compute Euler characteristic
*
* χ = Σ(-1)ⁿ|Cₙ| = |C₀| - |C₁| + |C₂| - |C₃| + |C₄|
*
* @returns Euler characteristic
*/
computeEulerCharacteristic(): number {
return this.complex.C0.length
- this.complex.C1.length
+ this.complex.C2.length
- this.complex.C3.length
+ this.complex.C4.length;
}
/**
* Full validation of chain complex
*
* @returns HomologyResult with validation status, Betti numbers, and violations
*/
validate(): HomologyResult {
const violations: string[] = [];
const betti: number[] = [];
// Validate all compositions
for (let n = 1; n <= 4; n++) {
if (!this.validateComposition(n as 1 | 2 | 3 | 4)) {
const cells = this.getCells(n);
violations.push(...cells.map(c => c.id));
}
}
// Compute Betti numbers for all dimensions
for (let n = 0; n <= 4; n++) {
betti.push(this.computeBetti(n));
}
return {
valid: violations.length === 0,
betti,
eulerCharacteristic: this.computeEulerCharacteristic(),
violations: violations.length > 0 ? violations : undefined
};
}
/**
* Get cells for dimension n
*/
private getCells(n: number): Cell<any>[] {
switch (n) {
case 0: return this.complex.C0;
case 1: return this.complex.C1;
case 2: return this.complex.C2;
case 3: return this.complex.C3;
case 4: return this.complex.C4;
default: return [];
}
}
/**
* Check if boundary forms a closed cycle
*
* For edges: each vertex should appear exactly twice (or zero times for isolated)
* For faces: each edge should appear exactly twice with opposite orientations
*
* @param boundary - Array of boundary cell IDs
* @returns true if boundary forms a closed cycle
*/
private isCycle(boundary: string[]): boolean {
if (boundary.length === 0) return true;
// Count occurrences of each cell ID
const counts = new Map<string, number>();
for (const id of boundary) {
counts.set(id, (counts.get(id) || 0) + 1);
}
// For a closed cycle, each cell should appear an even number of times
// (representing opposite orientations canceling out)
for (const [_, count] of counts) {
if (count % 2 !== 0) {
return false; // Not closed
}
}
return true;
}
/**
* Compute kernel of boundary operator ∂_n
*
* Kernel = {cells where ∂_n(cell) = 0}
*
* @param n - Dimension
* @returns Array of cell IDs in the kernel
*/
private computeKernel(n: number): string[] {
const cells = this.getCells(n);
return cells
.filter(c => {
const boundary = this.complex.boundary.get(c.id) || [];
return boundary.length === 0;
})
.map(c => c.id);
}
/**
* Compute image of boundary operator ∂_n
*
* Image = {cells that are boundaries of (n+1)-cells}
*
* @param n - Dimension
* @returns Array of cell IDs in the image
*/
private computeImage(n: number): string[] {
if (n > 4) return [];
const cells = this.getCells(n);
const image = new Set<string>();
for (const cell of cells) {
const boundary = this.complex.boundary.get(cell.id) || [];
for (const id of boundary) {
image.add(id);
}
}
return Array.from(image);
}
}