diff --git a/plugins/subspace_storage/dole.js b/plugins/subspace_storage/dole.js index 74d995d6..d5816abf 100644 --- a/plugins/subspace_storage/dole.js +++ b/plugins/subspace_storage/dole.js @@ -6,7 +6,8 @@ const { Gauge } = require("@clusterio/lib/prometheus"); const prometheusNNDoleGauge = new Gauge( "clusterio_subspace_storage_nn_dole_gauge", - "Current demand being supplied by Neural Network ; 1 means all demand covered, 0.5 means about half of each supply is covered, 0 means no items are given", + "Current demand being supplied by Neural Network ; 1 means all demand covered, "+ + "0.5 means about half of each supply is covered, 0 means no items are given", { labels: ["resource"] } ); const prometheusDoleFactorGauge = new Gauge( @@ -16,24 +17,24 @@ const prometheusDoleFactorGauge = new Gauge( ); class NeuralDole { - - getRequestStats(itemname,samples) - { - var sum=0; - samples=Math.min(samples,(this.stats[itemname] || []).length-1); - if (samples<1) return 0.1; - for (var i = 1; i <= samples; i++) { - sum+=this.stats[itemname][i].req; + getRequestStats(itemname, samples) { + let sum = 0; + samples = Math.min(samples, (this.stats[itemname] || []).length - 1); + if (samples < 1) { + return 0.1; + } + for (let i = 1; i <= samples; i++) { + sum += this.stats[itemname][i].req; } if (sum === 0) { return 0.1; } - return sum/samples; + return sum / samples; } constructor({ items, - }){ + }) { // Set some storage variables for the dole divider this.items = items; this.itemsLastTick = new Map(items._items); @@ -50,11 +51,13 @@ class NeuralDole { count, this.dole[name], this.itemsLastTick.get(name), - this.getRequestStats(name,5) + this.getRequestStats(name, 5) ); - this.stats[name]=this.stats[name] || []; - this.stats[name].unshift({ req: 0, given: 0 });//stats[name][0] is the one we currently collect - if (this.stats[name].length>10) this.stats[name].pop();//remove if too many samples in stats + this.stats[name] = this.stats[name] || []; + this.stats[name].unshift({ req: 0, given: 0 }); // stats[name][0] is the one we currently collect + if (this.stats[name].length>10) { + this.stats[name].pop(); // remove if too many samples in stats + } this.dole[name] = magicData[0]; // DONE handle magicData[1] for graphing for our users @@ -69,23 +72,22 @@ class NeuralDole { this.items.getItemCount(object.name), this.itemsLastTick.get(object.name) || 0, this.dole[object.name], - this.carry[object.name+" "+object.instanceID] || 0, - this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0, - this.getRequestStats(object.name,5), - this.debt[object.name+" "+object.instanceID] || 0 + this.carry[`${object.name} ${object.instanceID}`] || 0, + this.lastRequest[`${object.name}_${object.instanceID}_${object.instanceName}`] || 0, + this.getRequestStats(object.name, 5), + this.debt[`${object.name} ${object.instanceID}`] || 0 ); - if ((this.stats[object.name] || []).length>0) - { - this.stats[object.name][0].req+=Number(object.count); - this.stats[object.name][0].given+=Number(magicData[0]); + if ((this.stats[object.name] || []).length > 0) { + this.stats[object.name][0].req += Number(object.count); + this.stats[object.name][0].given += Number(magicData[0]); } - this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] = object.count; - //0.Number of items to give in that dose - //1.New dole for item X - //2.New carry for item X slave Y + this.lastRequest[`${object.name}_${object.instanceID}_${object.instanceName}`] = object.count; + // 0. Number of items to give in that dose + // 1. New dole for item X + // 2. New carry for item X slave Y this.dole[object.name] = magicData[1]; - this.carry[object.name+" "+object.instanceID] = magicData[2]; - this.debt[object.name+" "+object.instanceID] = magicData[3]; + this.carry[`${object.name} ${object.instanceID}`] = magicData[2]; + this.debt[`${object.name} ${object.instanceID}`] = magicData[3]; // Remove item from DB and send it this.items.removeItem(object.name, magicData[0]); @@ -94,39 +96,50 @@ class NeuralDole { } } -// If the server regularly can't fulfill requests, this number grows until it can. Then it slowly shrinks back down. +// If the server regularly can't fulfill requests, this number grows until +// it can. Then it slowly shrinks back down. let _doleDivisionFactor = {}; function doleDivider({ object, items, logItemTransfers, -}){ +}) { let itemCount = items.getItemCount(object.name); - const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings - const maxDoleDivision = 250; //a higher cap will divide the store more ways, but will take longer to recover as supplies increase + // lower rates will equal more dramatic swings + const doleDivisionRetardation = 10; + // a higher cap will divide the store more ways, but will take longer to + // recover as supplies increase + const maxDoleDivision = 250; const originalCount = Number(object.count) || 0; - object.count /= ((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation; + object.count /= ((_doleDivisionFactor[object.name] || 0) + doleDivisionRetardation) / doleDivisionRetardation; object.count = Math.round(object.count); if (logItemTransfers) { - console.info(`Serving ${object.count}/${originalCount} ${object.name} from ${itemCount} ${object.name} with dole division factor ${(_doleDivisionFactor[object.name]||0)} (real=${((_doleDivisionFactor[object.name]||0)+doleDivisionRetardation)/doleDivisionRetardation}), item is ${itemCount > object.count?"stocked":"short"}.`); + console.info( + `Serving ${object.count}/${originalCount} ${object.name} from ${itemCount} ${object.name} `+ + `with dole division factor ${(_doleDivisionFactor[object.name]||0)} `+ + `(real=${((_doleDivisionFactor[object.name] || 0) + doleDivisionRetardation) / doleDivisionRetardation}), `+ + `item is ${itemCount > object.count?"stocked":"short"}.` + ); } // Update existing items if item name already exists if (itemCount > object.count) { - //If successful, increase dole - _doleDivisionFactor[object.name] = Math.max((_doleDivisionFactor[object.name]||0)||1, 1) - 1; + // If successful, increase dole + _doleDivisionFactor[object.name] = Math.max((_doleDivisionFactor[object.name] || 0) || 1, 1) - 1; items.removeItem(object.name, object.count); prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); return object.count; - } else { - // if we didn't have enough, attempt giving out a smaller amount next time - _doleDivisionFactor[object.name] = Math.min(maxDoleDivision, Math.max((_doleDivisionFactor[object.name]||0)||1, 1) * 2); - prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); - return 0; - //console.log('failure out of ' + object.name + " | " + object.count + " from " + object.instanceID + " ("+object.instanceName+")"); } + + // if we didn't have enough, attempt giving out a smaller amount next time + _doleDivisionFactor[object.name] = Math.min( + maxDoleDivision, Math.max((_doleDivisionFactor[object.name] || 0) || 1, 1) * 2 + ); + prometheusDoleFactorGauge.labels(object.name).set(_doleDivisionFactor[object.name] || 0); + return 0; + // console.log(`failure out of ${object.name}/${object.count} from ${object.instanceID} (${object.instanceName})`); } module.exports = { diff --git a/plugins/subspace_storage/dole_nn_base.js b/plugins/subspace_storage/dole_nn_base.js index 7afbf2cf..a6f56508 100644 --- a/plugins/subspace_storage/dole_nn_base.js +++ b/plugins/subspace_storage/dole_nn_base.js @@ -1,122 +1,154 @@ -//Sigmoids... +// Sigmoids... "use strict"; -function sigmoid0(x) -{ - return x/(1+Math.abs(x)); +function sigmoid0(x) { + return x / (1 + Math.abs(x)); } -function sigmoid1(x) -{ - if (x>=0.0) { + +function sigmoid1(x) { + if (x >= 0.0) { return 1.0; - } else { - return 0.0; } + return 0.0; } -function sigmoid2(x) -{ - if (x>=0.0){ + +function sigmoid2(x) { + if (x >= 0.0) { return x; - } else { - return 0.0; } + return 0.0; } -function sigmoid3(x) -{ + +function sigmoid3(x) { return x; } -//DOSE - Doses items for item X slave Y -//Numreq - number of items that are requested for item X for slave Y -//Instore - number of items for item X in master gauge (currently) -//store_last_tick - Number of items for item X in master gauge at time of last tick -//dole - Current dole for item X -//carry - carry for item X slave Y -//prev_req - Numreq for previous request for item X slave Y -//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item -//debt - debt for item X slave Y +// DOSE - Doses items for item X slave Y +// Numreq - number of items that are requested for item X for slave Y +// Instore - number of items for item X in master gauge (currently) +// store_last_tick - +// Number of items for item X in master gauge at time of last tick +// dole - Current dole for item X +// carry - carry for item X slave Y +// prev_req - Numreq for previous request for item X slave Y +// * doleinfo_last - +// Object with 2 values for last "tick period": numreq - total number or +// requests for item, numslave - number of slaves requesting that item +// debt - debt for item X slave Y -//Returns: Array of 3 elements -//0.Number of items to give in that dose -//1.New dole for item X -//2.New carry for item X slave Y -//3.New debt +// Returns: Array of 3 elements +// 0.Number of items to give in that dose +// 1.New dole for item X +// 2.New carry for item X slave Y +// 3.New debt function dose(numreq, instore, store_last_tick, dole, carry, prev_req, numreq_total_adj, debt) { - numreq=Number(numreq); - instore=Number(instore); - var instore_adj=instore; + numreq = Number(numreq); + instore = Number(instore); + let instore_adj = instore; if (instore_adj === 0) { instore_adj = 0.1; } - var b=Math.trunc(prev_req/instore_adj*numreq); - var inp1=[numreq,instore_adj,store_last_tick/numreq,dole,carry];//0..4 - var inp2=[prev_req/numreq,numreq/numreq_total_adj,store_last_tick/numreq_total_adj,prev_req/instore_adj,b,numreq_total_adj,store_last_tick,debt];//5..12 - var inputs=inp1.concat(inp2); - var internal=new Array(20).fill(0); - var outputs=[0,0,0,0]; - //NN: Please don't touch :), code is generated + let b = Math.trunc(prev_req / instore_adj * numreq); + let inp1 = [numreq, instore_adj, store_last_tick / numreq, dole, carry]; // 0..4 + let inp2 = [ + prev_req / numreq, + numreq / numreq_total_adj, + store_last_tick / numreq_total_adj, + prev_req/instore_adj, + b, + numreq_total_adj, + store_last_tick, + debt, + ]; // 5..12 + let inputs = inp1.concat(inp2); + let internal = new Array(20).fill(0); + let outputs = [0, 0, 0, 0]; + // NN: Please don't touch :), code is generated - outputs[0]=inputs[7]*0.8058582544; - outputs[0]=sigmoid2(outputs[0]); - outputs[1]=sigmoid0(outputs[1]); - outputs[2]=sigmoid0(outputs[2]); - outputs[3]=inputs[6]*0.2294657379; - outputs[3]=sigmoid0(outputs[3]); + outputs[0] = inputs[7] * 0.8058582544; + outputs[0] = sigmoid2(outputs[0]); + outputs[1] = sigmoid0(outputs[1]); + outputs[2] = sigmoid0(outputs[2]); + outputs[3] = inputs[6] * 0.2294657379; + outputs[3] = sigmoid0(outputs[3]); - debt=debt || 0; - if (outputs[0]>0.99) outputs[0]=1.0; + debt = debt || 0; + if (outputs[0] > 0.99) { + outputs[0]=1.0; + } - var res1=Number(outputs[0])*numreq+outputs[3]+debt; - var res_capped=Math.round(res1); + let res1 = Number(outputs[0]) * numreq+outputs[3] + debt; + let res_capped = Math.round(res1); - if (res_capped>numreq) res_capped=numreq; - if (res_capped>instore) res_capped=instore; - if (res_capped<0) res_capped=0; + if (res_capped > numreq) { res_capped = numreq; } + if (res_capped > instore) { res_capped = instore; } + if (res_capped < 0) { res_capped = 0; } - res1=res1 || 0; - res_capped=res_capped || 0;//Safeguard against wrong values - debt=res1-res_capped; - debt_cap=Math.round(numreq*1.5)+5; - if (debt>debt_cap) debt=debt_cap; + res1 = res1 || 0; + res_capped = res_capped || 0; // Safeguard against wrong values + debt = res1 - res_capped; + debt_cap = Math.round(numreq * 1.5) + 5; + if (debt > debt_cap) { + debt = debt_cap; + } - carry=sigmoid0(outputs[2]*carry+outputs[0]) || 0; - return [res_capped,dole+outputs[1],carry,debt]; + carry = sigmoid0(outputs[2] * carry + outputs[0]) || 0; + return [res_capped, dole + outputs[1], carry, debt]; } -//TICK - Updates values for item X, should be called once for each item for each round of all slaves requesting that item -//Instore - number of items for item X in master gauge (currently) -//dole - Current dole for item X -//store_last_tick - Number of items for item X in master gauge at time of last tick (not this one) -//* doleinfo_last - Object with 2 values for last "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item -//* doleinfo_new - Object with 2 values for new "tick period": numreq - total number or requests for item, numslave - number of slaves requesting that item +// TICK - +// Updates values for item X, should be called once for each item for +// each round of all slaves requesting that item Instore - number of +// items for item X in master gauge (currently) +// dole - Current dole for item X +// store_last_tick - +// Number of items for item X in master gauge at time of last tick (not this one) +// * doleinfo_last - +// Object with 2 values for last "tick period": numreq - total number or +// requests for item, numslave - number of slaves requesting that item +// +// * doleinfo_new - +// Object with 2 values for new "tick period": numreq - total number or +// requests for item, numslave - number of slaves requesting that item -//Returns: Array of 2 elements -//0.New dole for item X -//1.Guessed percentage average of items supplied in last 10 ticks +// Returns: Array of 2 elements +// 0.New dole for item X +// 1.Guessed percentage average of items supplied in last 10 ticks function tick(instore, dole, store_last_tick, numreq_total_adj) { - var instore_adj=instore; + let instore_adj = instore; if (instore_adj === 0) { instore_adj = 0.1; } - var inputs=[instore,dole,store_last_tick/instore_adj,store_last_tick/numreq_total_adj,store_last_tick,numreq_total_adj]; - var internal=new Array(10).fill(0); - var outputs=[0,0]; - //NN: Please don't touch :), code is generated + let inputs = [ + instore, + dole, + store_last_tick / instore_adj, + store_last_tick / numreq_total_adj, + store_last_tick, + numreq_total_adj, + ]; + let internal = new Array(10).fill(0); + let outputs = [0, 0]; + // NN: Please don't touch :), code is generated - outputs[0]=inputs[3]*0.9364774823; - outputs[0]=outputs[0]*0.4590721726; - outputs[0]=sigmoid2(outputs[0]); - outputs[1]=outputs[0]*1.828422785; - outputs[1]=sigmoid3(outputs[1]); + outputs[0] = inputs[3] * 0.9364774823; + outputs[0] = outputs[0] * 0.4590721726; + outputs[0] = sigmoid2(outputs[0]); + outputs[1] = outputs[0] * 1.828422785; + outputs[1] = sigmoid3(outputs[1]); - avg=outputs[1]; - if (numreq_total_adj<0.11) avg=1;//When no item's are requested, result is 0.1 ; In that case there is no deficit, so return 1 = 100% demand fulfilled - if (avg>1) avg=1; - if (avg<0) avg=0; + avg = outputs[1]; + if (numreq_total_adj < 0.11) { + // When no item's are requested, result is 0.1; In that case there is + // no deficit, so return 1 = 100% demand fulfilled + avg = 1; + } + if (avg > 1) { avg=1; } + if (avg < 0) { avg=0; } - return [sigmoid0(outputs[0]),avg]; + return [sigmoid0(outputs[0]), avg]; } module.exports = { dose, tick };