From 24d733f273bc457b81a15f03ece028d1c7c2efbb Mon Sep 17 00:00:00 2001 From: Svornost <11434-svornost@users.noreply.gitgud.io> Date: Fri, 31 Jul 2020 15:55:12 -0700 Subject: [PATCH] Add cycle detection to inbreeding calculation. --- src/js/ibcJS.js | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/js/ibcJS.js b/src/js/ibcJS.js index de19d724130..c0330d7f151 100644 --- a/src/js/ibcJS.js +++ b/src/js/ibcJS.js @@ -182,30 +182,30 @@ globalThis.ibc = (() => { return n._coeff; }; - // Populate the NodeCodes. + // Populate the NodeCodes. // // Each node has a set of NodeCodes, which represent the set of paths from it to its ancestors. // NodeCodes here are represented by arrays of integers. // // NodeCodes are constructed recursively in this fashion: // - // - Assign each of the founders (nodes with both parents === null) an unique ID, starting from - // 0 and incrementing each time (the order doesn't matter); a founder's set of NodeCodes has + // - Assign each of the founders (nodes with both parents === null) an unique ID, starting from + // 0 and incrementing each time (the order doesn't matter); a founder's set of NodeCodes has // exactly one NodeCode, which is [ID] (an array containing only their ID) // - // - For each other node, let M be its child number w.r.t. its mother and N its child number - // w.r.t. its father, i.e. the number of children that the respective parent has had before - // this one (the order is not important to the algorithm, it's arbitrary here for + // - For each other node, let M be its child number w.r.t. its mother and N its child number + // w.r.t. its father, i.e. the number of children that the respective parent has had before + // this one (the order is not important to the algorithm, it's arbitrary here for // convenience). Its set of NodeCodes is the set of all its mother's NodeCodes with M appended - // and all of its father's NodeCodes with N appended. For example, if its mother has the - // NodeCodes [[2]] and M = 3 and its father has the NodeCodes [[0,1], [3,1]] and N = 1 then + // and all of its father's NodeCodes with N appended. For example, if its mother has the + // NodeCodes [[2]] and M = 3 and its father has the NodeCodes [[0,1], [3,1]] and N = 1 then // the set of NodeCodes for this node would be - // + // // [[2, 3], [0, 1, 1], [3, 1, 1]] // // We do this iteratively here, looping over the set of all nodes until each has been assigned - // a NodeCode. This requires looping through a number of times equal to the number of - // generations, since as soon as both parents have NodeCodes their children's NodeCodes may be + // a NodeCode. This requires looping through a number of times equal to the number of + // generations, since as soon as both parents have NodeCodes their children's NodeCodes may be // computed. let make_nc = nodes => { // Generate founder NodeCodes @@ -221,7 +221,9 @@ globalThis.ibc = (() => { }); // Generate the rest of the NodeCodes - while (seen.length != total) { + let oldSeenLength = -1; + while (seen.length !== total) { + oldSeenLength = seen.length; Object.keys(nodes).forEach(s=> { let n = nodes[s]; if (seen.includes(+s)) // We've already done this @@ -232,7 +234,7 @@ globalThis.ibc = (() => { seen.push(n.id); // Compute the NodeCodes from its parents [n.mother, n.father].forEach((a, i) => { - if (a === null || (n.mother === n.father && i == 1)) // Ignore missing parents/repeated + if (a === null || (n.mother === n.father && i === 1)) // Ignore missing parents/repeated return; a.nodecodes.forEach(nc => { @@ -245,8 +247,14 @@ globalThis.ibc = (() => { }); // NodeCodes must be sorted; this suffices - n.nodecodes.sort() + n.nodecodes.sort(); }); + // check to make sure we're progressing...if not, there's a cycle in the graph + // dump all the nodes participating in or descended from the cycle and let the player figure it out + if (oldSeenLength === seen.length) { + const badSlaveIDs = Object.keys(nodes).filter(s => !seen.includes(+s)).map(k => nodes[k].id); + throw(`Inbreeding calculation: heritance cycle detected. Check slave IDs: ${badSlaveIDs}`); + } } // Cache the string NodeCodes @@ -261,7 +269,7 @@ globalThis.ibc = (() => { // Recursively create the nodes we need, moving upwards from the given slave let create_node_rec = s => { - // Certain parents (e.g. 0, societal elite) are not considered to be related, despite + // Certain parents (e.g. 0, societal elite) are not considered to be related, despite // having the same ID; convert them to null let m = or_null(s.mother); let f = or_null(s.father); @@ -269,7 +277,7 @@ globalThis.ibc = (() => { // Ensure that parent nodes are created [m, f].forEach(p => { if (p !== null && !(p in nodes)) { // Not created, we have to do something - if (p === -1) { + if (p === -1) { create_node_rec(SugarCube.State.variables.PC); } else { // Search for a slave state, genePool entry, or missingTable entry -- GitLab