diff --git a/lib/dole_nn_base.js b/lib/dole_nn_base.js index ace6650c..e4dda0b6 100644 --- a/lib/dole_nn_base.js +++ b/lib/dole_nn_base.js @@ -32,108 +32,87 @@ function sigmoid3(x) //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 //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 -function Dose(numreq,instore,store_last_tick,dole,carry,prev_req) +function Dose(numreq,instore,store_last_tick,dole,carry,prev_req,numreq_total_adj) { - var inputs=[numreq,instore,store_last_tick,dole,carry,prev_req]; + numreq=Number(numreq); + instore=Number(instore); + var 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];//5..9 + var inputs=inp1.concat(inp2); var internal=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; - var outputs=[0,0]; + var outputs=[0,0,0]; //NN: Please don't touch :), code is generated -internal[1]=internal[8]*-1.643964648; -internal[1]=internal[1]+internal[3]*1.475263; -internal[1]=internal[1]+internal[10]*1.137161493; -internal[1]=internal[1]+1.0*0.581692636; -internal[1]=internal[1]+inputs[3]*-0.7429554462; -internal[1]=internal[1]+inputs[1]*-4.860008717; -internal[1]=internal[1]+inputs[0]*0.8682486415; -internal[1]=internal[1]+inputs[5]*-2.332767725; -internal[1]=sigmoid0(internal[1]); -internal[2]=internal[1]*0.8746165037; -internal[2]=sigmoid0(internal[2]); -internal[3]=inputs[0]*-5.502003193; -internal[3]=internal[3]+inputs[4]*0.2463098913; -internal[3]=internal[3]+inputs[5]*2.313313961; -internal[3]=internal[3]+1.0*-0.3230940998; -internal[3]=internal[3]+inputs[3]*-0.7781001329; -internal[3]=internal[3]+inputs[2]*7.295908451; -internal[3]=sigmoid0(internal[3]); -internal[6]=internal[1]*1.406680346; -internal[6]=internal[6]+inputs[3]*0.5068955421; -internal[6]=internal[6]+inputs[1]*-0.4729067087; -internal[6]=internal[6]+inputs[2]*0.8344552517; -internal[6]=internal[6]+inputs[0]*2.739265203; -internal[6]=sigmoid0(internal[6]); -internal[7]=inputs[0]*-0.9354650974; -internal[7]=internal[7]+inputs[5]*1.853131294; -internal[7]=internal[7]+inputs[2]*-0.5912416577; -internal[7]=internal[7]+inputs[1]*1.000927806; -internal[7]=sigmoid0(internal[7]); -internal[8]=1.0*-2.179960728; -internal[8]=internal[8]+inputs[1]*1.66548419; -internal[8]=internal[8]+inputs[0]*1.550180197; -internal[8]=sigmoid0(internal[8]); -internal[10]=inputs[2]*1.491255403; -internal[10]=internal[10]+1.0*1.035691857; -internal[10]=sigmoid0(internal[10]); -outputs[0]=inputs[2]*0.5093510151; -outputs[0]=outputs[0]+inputs[4]*0.9384511113; -outputs[0]=outputs[0]+outputs[1]*-3.595440626; -outputs[0]=sigmoid1(outputs[0]); -outputs[1]=inputs[2]*1.534054637; -outputs[1]=outputs[1]+internal[2]*-2.732098818; -outputs[1]=outputs[1]+outputs[2]*1.163635135; -outputs[1]=outputs[1]+internal[1]*-0.2005333602; -outputs[1]=outputs[1]+internal[6]*0.6757783294; +outputs[0]=inputs[2]*3.101320028; +outputs[0]=outputs[0]+inputs[7]*0.5471593142; +outputs[0]=outputs[0]+inputs[3]*2.691268921; +outputs[0]=outputs[0]+outputs[1]*-1.64255929; +outputs[0]=outputs[0]+inputs[6]*0.7746122479; +outputs[0]=sigmoid0(outputs[0]); +outputs[1]=inputs[2]*-0.9946503043; +outputs[1]=outputs[1]+inputs[1]*0.2128598392; outputs[1]=sigmoid0(outputs[1]); -outputs[2]=1.0*-0.9311643243; -outputs[2]=outputs[2]+inputs[1]*-1.85346806; -outputs[2]=outputs[2]+inputs[2]*-1.232177019; -outputs[2]=outputs[2]+inputs[0]*1.937278271; -outputs[2]=outputs[2]+internal[7]*-0.728112638; -outputs[2]=sigmoid0(outputs[2]); +outputs[2]=sigmoid2(outputs[2]); + outputs[0]=Number(outputs[0]); if (outputs[0]<0) outputs[0]=0; outputs[0]=outputs[0]*numreq; - var res1=Math.trunc(outputs[0]); + var res1=Math.round(outputs[0]); if (res1>numreq) res1=numreq; if (res1>instore) res1=instore; if (res1<0) res1=0; - carry=outputs[2]+outputs[0]-res1; - return [res1,outputs[1],carry]; + carry=sigmoid0(outputs[2]); + return [res1,sigmoid0(outputs[1]),carry]; } //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 -function Tick(instore,dole,store_last_tick) +function Tick(instore,dole,store_last_tick,numreq_total_adj) { - var inputs=[instore,dole,store_last_tick]; + var 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]; var internal=[0,0,0,0,0]; var outputs=[0,0]; //NN: Please don't touch :), code is generated -outputs[0]=1.0*0.5381062031; +internal[2]=sigmoid0(internal[2]); +outputs[0]=internal[2]*0.4049594998; +outputs[0]=outputs[0]+1.0*-5.026565075; +outputs[0]=outputs[0]+inputs[0]*0.9100194573; +outputs[0]=outputs[0]+inputs[1]*-1.063795686; outputs[0]=sigmoid1(outputs[0]); -outputs[1]=1.0*-0.5653554201; -outputs[1]=outputs[1]+inputs[0]*0.65881145; -outputs[1]=outputs[1]+inputs[1]*0.6852278709; +outputs[1]=outputs[0]*1.049500823; +outputs[1]=outputs[1]*inputs[0]*3.437163115; outputs[1]=sigmoid0(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; - return [outputs[0],avg]; + + return [sigmoid0(outputs[0]),avg]; } -module.exports = {Dose,Tick} \ No newline at end of file +module.exports = {Dose,Tick} diff --git a/master.js b/master.js index 3c8a5ac3..276af857 100644 --- a/master.js +++ b/master.js @@ -258,7 +258,7 @@ db.items.addItem = function(object) { if(this[object.name] && Number(this[object.name]) != NaN){ this[object.name] = Number(this[object.name]) + Number(object.count); } else { - this[object.name] = object.count; + this[object.name] = Number(object.count); } return true; } @@ -535,9 +535,12 @@ app.post("/api/place", authenticate.middleware, function(req, res) { } }); const routes_api_remove = require("./routes/api/remove.js") -const neuralDole = new routes_api_remove.neuralDole({ - items: db.items, -}) +var neuralDole = null + +if(config.useNeuralNetDoleDivider)//Only initialize neural network when it's enabled, otherwise it might override gauge + neuralDole=new routes_api_remove.neuralDole({ + items: db.items, gaugePrefix: prometheusPrefix + }) /** POST endpoint to remove items from DB when client orders items. @@ -564,7 +567,7 @@ app.post("/api/remove", authenticate.middleware, function(req, res) { if(slaves[object.instanceID]) object.instanceName = slaves[object.instanceID].instanceName; let item = db.items[object.name] // console.dir(doc); - if(!item + if((item === undefined) ||(( config.disableImportsOfEverythingExceptElectricity === true || config.disableImportsOfEverythingExceptElectricity === "true" ) && object.name != "electricity") diff --git a/routes/api/remove.js b/routes/api/remove.js index 23476749..18feabb1 100644 --- a/routes/api/remove.js +++ b/routes/api/remove.js @@ -2,16 +2,37 @@ const averagedTimeSeries = require("averaged-timeseries") const doleNN = require("./../../lib/dole_nn_base.js") +const Prometheus = require('prom-client'); + 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; + } + if (sum==0) return 0.1; + return sum/samples; + } + constructor({ - items, + items, gaugePrefix }){ // Set some storage variables for the dole divider + this.prometheusNNDoleGauge = new Prometheus.Gauge({ + name: gaugePrefix+'nn_dole_gauge', + help: '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', + labelNames: ["itemName"], + }); this.items = items this.itemsLastTick = JSON.parse(JSON.stringify(items)) this.dole = {} this.carry = {} this.lastRequest = {} + this.stats=[] setInterval(()=>{ for(let name in this.items){ @@ -22,10 +43,16 @@ class neuralDole { let magicData = doleNN.Tick( count, this.dole[name], - this.itemsLastTick[name] + this.itemsLastTick[name], + 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.dole[name] = magicData[0] - // TODO handle magicData[1] for graphing for our users + // DONE handle magicData[1] for graphing for our users + this.prometheusNNDoleGauge.labels(name).set(magicData[1] || 0); } this.itemsLastTick = JSON.parse(JSON.stringify(this.items)) }, 1000) @@ -42,15 +69,22 @@ class neuralDole { this.items[object.name], this.itemsLastTick[object.name] || 0, this.dole[object.name], - this.carry[object.name] || 0, - this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0 + this.carry[object.name+" "+object.instanceID] || 0, + this.lastRequest[object.name+"_"+object.instanceID+"_"+object.instanceName] || 0, + this.getRequestStats(object.name,5) ) + 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.dole[object.name] = magicData[1] - this.carry[object.name] = magicData[2] + this.carry[object.name+" "+object.instanceID] = magicData[2] + // Remove item from DB and send it if(this.items.removeItem({count: magicData[0], name: object.name})){ if(config.logItemTransfers){ @@ -96,7 +130,7 @@ function doleDivider({ prometheusImportGauge, req,res, }){ - const doleDivisionRetardation = 10; //lower rates will equal more dramatic swings + 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 const originalCount = Number(object.count) || 0; @@ -143,4 +177,4 @@ function doleDivider({ module.exports = { doleDivider, neuralDole -} \ No newline at end of file +}