Calculate the congruence lattice of a finite algebra in JavaScript
2003 May 28 jipsen@chapman.edu
<script>
function checkRelation(A,rel) {//rel is a partial binary relation
//Check that rel is transitive and compatible with the operations of A
var op;
var n = A.size;
for (var x=0; x<n; x++)
for (var y=0; y<n; y++)
if (rel[x][y]==1 && x!=y) {
for (var z=x+1; z<n; z++)
if (rel[y][z]==1)
if (rel[x][z]==0)
return false; //not transitive
for (var r=0; r<A.operation.length; r++) {
op = A.operation[r].value;
if (op.length!=null)
if (op[0].length==null) {
if (rel[op[x]][op[y]]==0)
return false;
} else
for (var z=0; z<n; z++) {
if (rel[op[x][z]][op[y][z]]==0)
return false;
if (rel[op[z][x]][op[z][y]]==0)
return false;
}
}
}
return true;
}
function copyOf(arr) {
var a = new Array(arr.length);
for (var i=0; i<arr.length; i++) {
a[i] = new Array(arr[i].length);
for (var j=0; j<arr[i].length; j++) a[i][j] = arr[i][j];
}
return a;
}
function completeRelation(A,rel,i,j) {
// find next i,j where rel[i][j]=2=undefined; for each val=0 or 1
// set rel[i][j]=val, check transitivity and compatibility
// restore and return if no completetion,
// else call completeRelation(rel,i,j+1)
var ok = true;
while (ok && i<rel.length) {
while (j<rel.length && rel[i][j]!=2) j++;
if (j>=rel.length) {
j=0;
i++;
} else ok = false;
}
if (ok) congl[congl.length] = copyOf(rel);
else for (var val=0; val<2; val++){
rel[i][j] = val;
rel[j][i] = val;
ok = checkRelation(A,rel);
if (ok) completeRelation(A,rel,i,j+1);
rel[i][j] = 2;
rel[j][i] = 2;
}
}
function congruences(A) {
// A is a finite algebra (JavaScript object)
congl = [];
var rel = new Array(A.size);
for (var i=0; i<A.size; i++) {
rel[i] = new Array(A.size);
for (var j=0; j<A.size; j++)
if (i!=j) rel[i][j] = 2;
else rel[i][j]=1;
}
completeRelation(A,rel,0,0);
return congl;
}
function isTransitiveRel(rel) {
var n = rel.length;
for (var x=0; x<n; x++)
for (var y=0; y<n; y++)
if (rel[x][y]==1 && x!=y)
for (var z=0; z<n; z++)
if (rel[y][z]==1)
if (rel[x][z]==0) return false; //not transitive
return true;
}
function isSubrelation(R,S) { //assumes symmetry of relations
for (var i=0; i<R.length; i++)
for (var j=i+1; j<R.length; j++)
if (R[i][j]>S[i][j]) return false;
return true;
}
function conLatleq(cong) { //input list of 0-1-relations
var leq = new Array(cong.length);
for (var i=0; i<cong.length; i++) {
leq[i] = new Array(cong.length);
leq[i][i] = true;
for (var j=0; j<cong.length; j++)
if (i!=j) leq[i][j] = isSubrelation(cong[i],cong[j]);
}
return leq;
}
function leq2uppercovers(rel) {
var n = rel.length;
var uc = new Array(n);
for (var i=0; i<n; i++) {
uc[i] = [];
for (var j=0; j<n; j++)
if (rel[i][j] && i!=j) {
for (var k=0; k<n &&
!(rel[i][k] && i!=k && rel[k][j] && k!=j); k++);
if (k==n) uc[i][uc[i].length] = j;
}
}
return uc;
}
function congblock(co,i) {
var block = [];
for (var j=0; j<co.length; j++)
if (co[i][j]==1)
block[block.length] = j;
return block;
}
function cong2part(co) {
var part = [];
var flag = new Array(co.length);
for (var i=0; i<co.length; i++)
if (flag[i]==null) {
cb = congblock(co,i);
for (var j=0; j<cb.length; j++) flag[cb[j]]=true;
part[part.length] = cb;
}
return part;
}
function conLat(A) {
var conA = {name:"Con("+A.name+")", relation:[]};
var cl = congruences(A);
conA.size = cl.length;
conA.element = new Array(conA.size);
for (var i=0; i<conA.size; i++) {
conA.element[i]={id:i};
conA.element[i].value = cong2part(cl[i]).join(" | ");
}
conA.relation[0] = {name:"uppercovers", arity:2, type:"list"};
conA.relation[0].value = leq2uppercovers(conLatleq(cl));
return conA;
}
function readStructure(st) {
// A structure is an object {name:"String", size:Number, element:Array,
// operation:Array, relation:Array}
var A = {};
A.name = /name=\"(.*?)\"/.exec(st)[1];
A.size = parseInt(/size=\"(.*?)\"/.exec(st)[1]);
var elementRe=/id=\"(.*?)\"/g;
var arr=elementRe.exec(st);
A.element = [];
var i = 0;
while (arr!=null) {
A.element[i] = {};
A.element[i].id = arr[1];
i++;
arr=elementRe.exec(st);
}
var oparr=st.split("<operation").slice(1);
var row = [];
A.operation = [];
for (var k=0; k<oparr.length; k++) {
opname = /name=\"(.*?)\"/.exec(oparr[k])[1];
A.operation[k] = {name:opname};
A.operation[k].symbol = /symbol=\"(.*?)\"/.exec(oparr[k])[1];
A.operation[k].arity = /arity=\"(.*?)\"/.exec(oparr[k])[1];
if (A.operation[k].arity == 0) A[opname] = parseInt(/>\s*(.*?)\s*<\/oper/.exec(oparr[k])[1]);
else {
A[opname] = new Array(A.size);
var rowRe = /row.*?>\s*(.*?)\s*<\/row>/g;
row = rowRe.exec(oparr[k]);
i = 0;
while (row!=null) {
A[opname][i] = new Array(A.size);
arr = row[1].split(/\s+/);
for (var j=0; j<arr.length; j++)
A[opname][i][j] = parseInt(arr[j]);
i++;
row = rowRe.exec(oparr[k]);
}
}
if (A.operation[k].arity == 1) A[opname] = A[opname][1];
A.operation[k].value = A[opname];
} // ********* still need to read relations and comments
return A;
}
function structureToString(A) {
var st="<\;structure name=\""+A.name+"\" size=\""+A.size+"\"><br>\n";
if (A.element != null) for (var i=0; i<A.size; i++)
st=st+" <\;element id=\""+A.element[i].id+
(A.element[i].x!=null?"\" x=\""+A.element[i].x+"\" y=\""+A.element[i].y:"")+
(A.element[i].value!=null?"\"> "+A.element[i].value+" <\;/element>":"\"/>")+"<br>\n";
if (A.operation != null) for (var k=0; k<A.operation.length; k++) {
var ar = A.operation[k].arity;
st=st+" <\;operation name=\""+A.operation[k].name+"\" symbol=\""+
A.operation[k].symbol+"\" arity=\""+ar+"\" type=\"table\">";
if (ar == 0) st = st+" "+A.operation[k].value;
else for (var i=0; i<(ar==2?A.size:2); i++) {
st = st+(i==0?"<br>\n":"")+" <\;row id=\""+
(ar==2?(A.element!=null?A.element[i].id:i):(i==0?"s":"t"))+"\"> ";
for (var j=0; j<A.size; j++)
st = st+(ar==2?A.operation[k].value[i][j]:(i==0?j:A.operation[k].value[j]))+" ";
st = st+"<\;/row><br>\n";
}
st = st+" <\;/operation><br>\n";
}
if (A.relation != null) for (var k=0; k<A.relation.length; k++) {
var ar = A.relation[k].arity;
st=st+" <\;relation name=\""+A.relation[k].name+
(A.relation[k].symbol!=null?"\" symbol=\""+
A.relation[k].symbol:"")+"\" arity=\""+ar+"\" type=\"list\">";
if (ar == 1) st = st+" "+A.relation[k].value.join(" ");
else for (var i=0; i<A.size; i++)
st = st+(i==0?"<br>\n":"")+" <\;set id=\""+
(A.element!=null?A.element[i].id:i)+"\"> "+
A.relation[k].value[i].join(" ")+" <\;/set><br>\n";
st = st+" <\;/relation><br>\n";
}
st = st+"<\;/structure>\n\n<p>";
return st;
}
function process(st) {
var A, conA;
var strString = "";
var arr=st.split("<structure");
for (var i=1; i<arr.length; i++) {
A = readStructure(arr[i]);
conA = conLat(A);
strString = strString + structureToString(conA);
}
document.getElementById("output").innerHTML = strString;
}
document.write(
'<form>Compute congruence lattices of: (paste a list of algebras) <br>'+
'<textarea name="input" rows="10" cols="80"><structure name="Sgrp_4_4" size="4">\n\
<element id="0"/>\n\
<element id="1"/>\n\
<element id="2"/>\n\
<element id="3"/>\n\
<operation name="product" symbol="\cdot" arity="2" type="table">\n\
<row id="0"> 0 0 0 0 </row>\n\
<row id="1"> 0 0 0 0 </row>\n\
<row id="2"> 0 0 0 1 </row>\n\
<row id="3"> 0 0 1 0 </row>\n\
</operation>\n\
</structure>\n </textarea><br>'+
'<input type="button" value="Start" onClick="process(document.forms[0].input.value)"></form><p><div id="output"></div>');
</script>