Last active
March 8, 2020 00:56
-
-
Save rasmushaglund/8fb753d9e37584bc5396ff187aa79a54 to your computer and use it in GitHub Desktop.
Calculate coefficient of relationship in javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Used to calculate Coefficient of relationship | |
// https://en.wikipedia.org/wiki/Coefficient_of_relationship | |
// Limitations: Does not handle identical twin common ancestors | |
// Test examples based on http://www.genetic-genealogy.co.uk/Toc115570135.html | |
var init = function (persons) { | |
for (p in persons) { | |
persons[p].children = []; | |
} | |
// Add reversed relationship | |
for (p in persons) { | |
const o = persons[p]; | |
if (o.mother) { | |
o.mother.children.push(o); | |
} | |
if (o.father) { | |
o.father.children.push(o); | |
} | |
} | |
} | |
// Find distinct paths from p to ancestors | |
var findPaths = function(_acc, p) { | |
if (!p) { | |
return []; | |
} | |
// Create a new object since javascript uses references as default | |
const acc = _acc.slice(); | |
acc.push(p.name); | |
return [acc].concat(findPaths(acc, p.mother)).concat(findPaths(acc, p.father)); | |
} | |
// Check if an array has duplicates | |
var hasDuplicates = function (arr) { | |
return new Set(arr).size !== arr.length; | |
} | |
// Find all paths through ancestors between a and b | |
var findAncestorPaths = function(a, b) { | |
var result = []; | |
var aPaths = findPaths([], a); | |
var bPaths = findPaths([], b); | |
if (aPaths.length > 1 && bPaths.length > 1) { | |
for (const i in aPaths) { | |
// Choose a node to check as common ancestor | |
const nodeToCheck = aPaths[i].slice(-1)[0]; | |
for (const j in bPaths) { | |
// Find a path which shares the same end node (to test for common ancestor) | |
if (nodeToCheck == bPaths[j].slice(-1)[0]) { | |
const aPath = aPaths[i].slice(0,-1); | |
const bPath = bPaths[j].slice(0,-1); | |
console.log('Testing ' + aPaths[i] + ', ' + bPaths[j]) | |
// Make sure the paths doesn't share any nodes except last/ancestor | |
if (!hasDuplicates(aPath.concat(bPath))) { | |
console.log('Found match: ' + aPaths[i] + ' - ' + bPaths[j]); | |
result.push(aPath.concat(bPaths[j])); | |
} | |
} | |
} | |
} | |
} else { | |
// In case one of the nodes is a root node | |
var nodeToCheck; | |
var paths; | |
if (aPaths.length == 0) { | |
nodeToCheck = a.name; | |
paths = bPaths; | |
} else { | |
nodeToCheck = b.name; | |
paths = aPaths; | |
} | |
for (i in paths) { | |
const path = paths[i]; | |
if (nodeToCheck == path.slice(-1)[0]) { | |
console.log('Found match: ' + path + ', ' + nodeToCheck); | |
result.push(path); | |
} | |
} | |
} | |
return result; | |
} | |
var coefficientOfRelationship = function(a, b) { | |
console.log("\n"); | |
var paths = findAncestorPaths(a, b); | |
var sum = 0; | |
console.log("\n"); | |
for (const i in paths) { | |
const path = paths[i]; | |
// Generations between a and b | |
const n = path.length - 1; | |
// Coefficients of inbreeding, default 1.0 | |
const fa = a.f || 1.0; | |
const fo = b.f || 1.0; | |
const result = Math.pow(2, -n)*Math.pow((1+fa)/(1+fo), 1/2); | |
sum += result; | |
console.log("Calculating coefficient for n=" + n, "a=" + a.name, "b=" + b.name, "fa=" + fa, "fo=" + fo, "path=" + path, "result=" + result, "sum=" + sum); | |
} | |
return sum; | |
} | |
// = 0.0625 | |
var testFigure8 = function() { | |
console.log('\n\n=== Running test figure 8 ==='); | |
const a = {name: 'a', f: 1.0}; | |
const b = {name: 'b', f: 1.0}; | |
const c = {name: 'c', f: 1.0}; | |
const d = {name: 'd', f: 1.0}; | |
const e = {name: 'e', f: 1.0}; | |
const f = {name: 'f', f: 1.0}; | |
const g = {name: 'g', f: 1.0}; | |
const h = {name: 'h', f: 1.0}; | |
const i = {name: 'i', f: 1.0}; | |
i.mother=g; | |
i.father=h; | |
g.mother=e; | |
g.father=f; | |
e.mother=c; | |
e.father=d; | |
c.mother=a; | |
c.father=b; | |
const persons = [a, b, c, d, e, f, g, h, i]; | |
init(persons); | |
return coefficientOfRelationship(i, a); | |
} | |
// = 0.125 | |
var testFigure9 = function() { | |
console.log('\n\n=== Running test figure 9 ==='); | |
const a = {name: 'a', f: 1.0}; | |
const b = {name: 'b', f: 1.0}; | |
const c = {name: 'c', f: 1.0}; | |
const d = {name: 'd', f: 1.0}; | |
const e = {name: 'e', f: 1.0}; | |
const f = {name: 'f', f: 1.0}; | |
const g = {name: 'g', f: 1.0}; | |
const h = {name: 'h', f: 1.0}; | |
const i = {name: 'i', f: 1.0}; | |
const j = {name: 'j', f: 1.0}; | |
const k = {name: 'k', f: 1.0}; | |
const l = {name: 'l', f: 1.0}; | |
const m = {name: 'm', f: 1.0}; | |
m.mother=k; | |
m.father=l; | |
k.mother=g; | |
k.father=h; | |
l.mother=i; | |
l.father=j; | |
h.mother=c; | |
h.father=d; | |
i.mother=e; | |
i.father=f; | |
d.mother=a; | |
d.father=b; | |
e.mother=a; | |
e.father=b; | |
const persons = [a, b, c, d, e, f, g, h, i, j, k, l, m]; | |
init(persons); | |
return coefficientOfRelationship(m, a); | |
} | |
// = 0.125 | |
var testFigure10 = function() { | |
console.log('\n\n=== Running test figure 10 ==='); | |
const a = {name: 'a', f: 1.0}; | |
const b = {name: 'b', f: 1.0}; | |
const c = {name: 'c', f: 1.0}; | |
const d = {name: 'd', f: 1.0}; | |
const e = {name: 'e', f: 1.0}; | |
const f = {name: 'f', f: 1.0}; | |
const g = {name: 'g', f: 1.0}; | |
const h = {name: 'h', f: 1.0}; | |
g.mother=c; | |
g.father=d; | |
h.mother=e; | |
h.father=f; | |
d.mother=a; | |
d.father=b; | |
e.mother=a; | |
e.father=b; | |
const persons = [a, b, c, d, e, f, g, h]; | |
init(persons); | |
return coefficientOfRelationship(g, h); | |
} | |
// = 0.5625 | |
var testFigure11 = function() { | |
console.log('\n\n=== Running test figure 11 ==='); | |
const a = {name: 'a', f: 1.0}; | |
const b = {name: 'b', f: 1.0}; | |
const c = {name: 'c', f: 1.0}; | |
const d = {name: 'd', f: 1.0}; | |
const e = {name: 'e', f: 1.0}; | |
const f = {name: 'f', f: 1.0}; | |
const g = {name: 'g', f: 1.0}; | |
const h = {name: 'h', f: 1.0}; | |
const i = {name: 'i', f: 1.0}; | |
const j = {name: 'j', f: 1.0}; | |
i.mother=g; | |
i.father=h; | |
j.mother=g; | |
j.father=h; | |
g.mother=c; | |
g.father=d; | |
h.mother=e; | |
h.father=f; | |
d.mother=a; | |
d.father=b; | |
e.mother=a; | |
e.father=b; | |
const persons = [a, b, c, d, e, f, g, h, i, j]; | |
init(persons); | |
return coefficientOfRelationship(i, j); | |
} | |
// = 0.1875 | |
var testFigure12 = function() { | |
console.log('\n\n=== Running test figure 12 ==='); | |
const a = {name: 'a', f: 1.0}; | |
const b = {name: 'b', f: 1.0}; | |
const c = {name: 'c', f: 1.0}; | |
const d = {name: 'd', f: 1.0}; | |
const e = {name: 'e', f: 1.0}; | |
const f = {name: 'f', f: 1.0}; | |
const g = {name: 'g', f: 1.0}; | |
const h = {name: 'h', f: 1.0}; | |
const i = {name: 'i', f: 1.0}; | |
const j = {name: 'j', f: 1.0}; | |
const k = {name: 'k', f: 1.0}; | |
const l = {name: 'l', f: 1.0}; | |
const m = {name: 'm', f: 1.0}; | |
m.mother=k; | |
m.father=l; | |
k.mother=g; | |
k.father=h; | |
l.mother=i; | |
l.father=j; | |
h.mother=c; | |
h.father=d; | |
i.mother=e; | |
i.father=f; | |
d.mother=a; | |
d.father=b; | |
e.mother=a; | |
e.father=b; | |
const persons = [a, b, c, d, e, f, g, h, i, j, k, l, m]; | |
init(persons); | |
return coefficientOfRelationship(m, d); | |
} | |
console.log(testFigure8()); | |
console.log(testFigure9()); | |
console.log(testFigure10()); | |
console.log(testFigure11()); | |
console.log(testFigure12()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment