node-red-contrib-genetic-charging-strategy
Version:
A module for Node-RED that adds a battery charging strategy to node-red-contrib-power-saver. It uses genetic algorithms to find the best schedule
126 lines (125 loc) • 4.55 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.geneticAlgorithmConstructor = geneticAlgorithmConstructor;
function geneticAlgorithmConstructor(options) {
function settingDefaults() {
return {
mutationFunction: function (phenotype) {
return phenotype;
},
crossoverFunction: function (a, b) {
return [a, b];
},
fitnessFunction: function () {
return 0;
},
doesABeatBFunction: undefined,
population: [],
populationSize: 100,
};
}
function settingWithDefaults(settings, defaults) {
const s = { ...defaults, ...settings };
if (s.population.length <= 0)
throw Error('population must be an array and contain at least 1 phenotypes');
if (s.populationSize <= 0)
throw Error('populationSize must be greater than 0');
return s;
}
let settings = settingWithDefaults(options, settingDefaults());
function populate() {
const size = settings.population.length;
while (settings.population.length < settings.populationSize) {
settings.population.push(mutate(cloneJSON(settings.population[Math.floor(Math.random() * size)])));
}
}
function cloneJSON(object) {
if (Array.isArray(object)) {
return [...object];
}
return { ...object };
}
function mutate(phenotype) {
return settings.mutationFunction(cloneJSON(phenotype));
}
function crossover(phenotype) {
phenotype = cloneJSON(phenotype);
let mate = settings.population[Math.floor(Math.random() * settings.population.length)];
mate = cloneJSON(mate);
return settings.crossoverFunction(phenotype, mate)[0];
}
function doesABeatB(a, b) {
if (settings.doesABeatBFunction) {
return settings.doesABeatBFunction(a, b);
}
else {
return settings.fitnessFunction(a) >= settings.fitnessFunction(b);
}
}
function compete() {
const nextGeneration = [];
for (let p = 0; p < settings.population.length - 1; p += 2) {
const phenotype = settings.population[p];
const competitor = settings.population[p + 1];
nextGeneration.push(phenotype);
if (doesABeatB(phenotype, competitor)) {
if (Math.random() < 0.5) {
nextGeneration.push(mutate(phenotype));
}
else {
nextGeneration.push(crossover(phenotype));
}
}
else {
nextGeneration.push(competitor);
}
}
settings.population = nextGeneration;
}
function randomizePopulationOrder() {
for (let index = 0; index < settings.population.length; index++) {
const otherIndex = Math.floor(Math.random() * settings.population.length);
const temp = settings.population[otherIndex];
settings.population[otherIndex] = settings.population[index];
settings.population[index] = temp;
}
}
return {
evolve: function (options) {
if (options) {
settings = settingWithDefaults(options, settings);
}
populate();
randomizePopulationOrder();
compete();
return this;
},
best: function () {
const scored = this.scoredPopulation();
const result = scored.reduce(function (a, b) {
return a.score >= b.score ? a : b;
}, scored[0]).phenotype;
return cloneJSON(result);
},
bestScore: function () {
return settings.fitnessFunction(this.best());
},
population: function () {
return cloneJSON(this.config().population);
},
scoredPopulation: function () {
return this.population().map(function (phenotype) {
return {
phenotype: cloneJSON(phenotype),
score: settings.fitnessFunction(phenotype),
};
});
},
config: function () {
return cloneJSON(settings);
},
clone: function (options) {
return geneticAlgorithmConstructor(settingWithDefaults(options, settingWithDefaults(this.config(), settings)));
},
};
}