New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help individuals learn multiple things #31

Merged
merged 14 commits into from Aug 3, 2015
View
@@ -15,12 +15,12 @@ function breed (mum, dad) {
function breedFittestIndividualsForParticipant (participant, individuals, population, fittestIndividualsOfAllTime) {
var fittestIndividuals = individuals
.sort((a, b) => b.fitness - a.fitness)
.sort((a, b) => b.fitness.weightedScore - a.fitness.weightedScore)
.slice(0, Math.ceil(population / 2));
fittestIndividualsOfAllTime[participant] = fittestIndividualsOfAllTime[participant]
.concat(fittestIndividuals)
.sort((a, b) => b.fitness - a.fitness)
.sort((a, b) => b.fitness.score - a.fitness.score)
.slice(0, Math.ceil(population / 4));
var breedingPairs = eachSlice(fittestIndividuals, 2);
@@ -0,0 +1,80 @@
const reduceIntoObject = require('./reduce-into-object');
const _ = require('lodash');
// TODO - DUPLICATION OMG
const MAX_FITNESS = 1000;
function mean (numbers) {
const result = _.sum(numbers) / numbers.length;
if (result === Infinity) {
return 0;
}
return result;
}
function highestFitnessForScenario (fitnessesForScenario) {
return _.max(
fitnessesForScenario
.valuesArray()
.map(fitnesses => mean(fitnesses))
);
}
function highestFitnessForParticipantPerScenario (participant, fitnesses) {
return reduceIntoObject(
fitnesses.map((scenarioFitnesses, scenario) => {
if (scenarioFitnesses[participant] === undefined) {
return {[scenario.id]: 0};
}
return {
[scenario.id]: highestFitnessForScenario(scenarioFitnesses[participant])
};
}).valuesArray()
);
};
function getHighestFitnessesForScenarioForParticipant (participants, fitnesses) {
return reduceIntoObject(participants.map(participant => {
return {
[participant]: highestFitnessForParticipantPerScenario(
participant,
fitnesses
)
};
}));
};
function createScenarioImportances (fitnessForParticipantPerScenario) {
return Object.keys(fitnessForParticipantPerScenario)
.map(participant => ({participant, fitnesses: fitnessForParticipantPerScenario[participant]}))
.map(({participant, fitnesses}) => ({[participant]: calculateImportance(fitnesses)}))
.reduce((importances, importance) => Object.assign(importances, importance), {});
};
function replaceInfinityWithZero (number) {
if (number === Infinity) {
return 0;
}
return number;
}
function calculateImportance (fitnesses) {
return Object.keys(fitnesses)
.map(scenarioId => ({scenarioId, fitness: fitnesses[scenarioId]}))
.map(({scenarioId, fitness}) => ({[scenarioId]: replaceInfinityWithZero(MAX_FITNESS / fitness)}))
.reduce((importance, scenarioImportance) => Object.assign(importance, scenarioImportance), {});
};
function calculateScenarioImportances (participants, fitnesses) {
return createScenarioImportances(
getHighestFitnessesForScenarioForParticipant(participants, fitnesses)
);
}
module.exports = calculateScenarioImportances;
View
@@ -2,45 +2,72 @@ const _ = require('lodash');
const Entity = require('./entity');
const simulateWorld = require('./simulator');
require('../lib/map-extensions'); // To the reader, my apologies
const MAX_FITNESS = 1000;
function fitness (expectedPosition, entity) {
const distance = {
x: Math.abs(expectedPosition.x - entity.x),
y: Math.abs(expectedPosition.y - entity.y)
};
return 1000 - Math.sqrt(Math.pow(distance.x, 2) + Math.pow(distance.y, 2));
return MAX_FITNESS - Math.sqrt(Math.pow(distance.x, 2) + Math.pow(distance.y, 2));
}
function limitTo (limit, number) {
return _.max([limit, number]);
}
function meanOfSquares (numbers) {
return Math.sqrt(_.sum(numbers.map(number => Math.pow(limitTo(0, number), 2)))) / numbers.length;
}
function weightedAverage (scoresPerScenario) {
return Math.sqrt(_.sum(scoresPerScenario.map(score => Math.pow(limitTo(0, score), 2))) / scoresPerScenario.length);
return {
score: meanOfSquares(scoresPerScenario.valuesArray().map(score => score.score)),
weightedScore: meanOfSquares(scoresPerScenario.valuesArray().map(score => score.weightedScore))
};
}
function participantInScenario (participant) {
return (scenario) => {
return Object.keys(scenario).findIndex(participantKey =>
return (fitnesses, scenario) => {
return scenario.participants.findIndex(participantKey =>
participantKey === participant
) !== -1;
};
}
function boilDownIndividualScore (individual, participant, fitnesses) {
function boilDownIndividualScore (individual, participant, fitnesses, scenarioImportances) {
if (fitnesses.filter(participantInScenario(participant)).size === 0) {
return {
score: 0,
weightedScore: 0
};
}
return weightedAverage(
_.values(fitnesses)
fitnesses
.filter(participantInScenario(participant))
.map(fitnessesForScenario => fitnessesForScenario[participant].get(individual))
.map(meanOfSquares)
.map((scoreForScenario, scenario) => {
return {
score: scoreForScenario,
weightedScore: scoreForScenario * scenarioImportances[participant][scenario.id]
};
}
)
);
}
function scoreScenarios (scenarios, individuals) {
return scenarios.map(scenario => {
return { [scenario.id]: scoreScenario(scenario, individuals) };
}).reduce((scenarioScores, score) =>
Object.assign(scenarioScores, score), {}
);
return [scenario, scoreScenario(scenario, individuals)];
}).reduce((allScenarioScores, score) => {
const [scenario, scenarioScores] = score;
return allScenarioScores.set(scenario, scenarioScores);
}, new Map());
};
function scoreScenario (scenario, individuals) {
@@ -87,5 +114,4 @@ function scoreIndividualOnScenario (scenario, participant, individual) {
return fitness(expectedPosition, activeEntity);
});
}
module.exports = {scoreScenarios, boilDownIndividualScore};
@@ -0,0 +1,9 @@
function reduceIntoObject (keyValues) {
return keyValues.reduce(
(object, keyValue) => Object.assign(object, keyValue),
{}
);
};
module.exports = reduceIntoObject;
View
@@ -161,16 +161,16 @@ function newNode (schema) {
function generateIndividual (schema) {
var entropy = getRandomInt(1, 20);
return _.chain(entropy).range().map(() => {
return _.range(entropy).map(() => {
return newNode(schema);
}).value();
});
}
var Seeder = {
make (schema, numberOfIndividuals) {
return _.chain(numberOfIndividuals).range().map(() => {
return _.range(numberOfIndividuals).map(() => {
return generateIndividual(schema);
}).value();
})
}
};
View
@@ -1,46 +1,63 @@
var breedFittestIndividuals = require('./app/breeding');
var Seeder = require('./app/seeder');
var createApi = require('./app/api');
const breedFittestIndividuals = require('./app/breeding');
const Seeder = require('./app/seeder');
const createApi = require('./app/api');
const {serialize, deserialize} = require('./app/serializer');
const {scoreScenarios, boilDownIndividualScore} = require('./app/fitness-scoring');
const {
scoreScenarios,
boilDownIndividualScore
} = require('./app/fitness-scoring');
var _ = require('lodash');
const reduceIntoObject = require('./app/reduce-into-object');
function run (fitnessScenarios, generations=150, population=32, individuals = {}) {
var scenarios = fitnessScenarios.scenarios;
var entities;
var fittestIndividuals = [];
const calculateScenarioImportances = require('./app/calculate-scenario-importance');
const _ = require('lodash');
function arrayToObject (array) {
return reduceIntoObject(array.map((value, key) => ({[key]: value})));
};
function fillInIndividuals (individuals, population, participants) {
function createStub () { return function stub () { throw 'you no execute me'; }; };
var stubApi = createApi({
const stubApi = createApi({
checkCollision: createStub(),
checkButtonDown: createStub(),
checkButtonReleased: createStub()
});
_.difference(Object.keys(individuals), fitnessScenarios.participants).forEach(participant => {
individuals[participant] = [];
participants.forEach(participant => {
let existing = individuals[participant];
if (existing === undefined) {
individuals[participant] = existing = [];
};
const numberOfIndividualsToBreed = population - existing.length;
const newIndividuals = Seeder.make(stubApi, numberOfIndividualsToBreed);
individuals[participant] = existing.concat(newIndividuals);
});
return individuals;
}
function run (fitnessScenarios, generations=150, population=32, individuals = {}) {
const scenarios = fitnessScenarios.scenarios;
const participants = fitnessScenarios.participants;
var fittestIndividualsOfAllTime = _.chain(fitnessScenarios.participants).map(participant => {
let fittestIndividualsOfAllTime = _.chain(participants).map(participant => {
return [participant, []];
}).object().value();
function fillInIndividuals (individuals) {
fitnessScenarios.participants.forEach(participant => {
var existing = individuals[participant];
if (existing === undefined) {
individuals[participant] = existing = [];
};
individuals[participant] = existing.concat(Seeder.make(stubApi, population - existing.length));
});
}
let scenarioImportances = reduceIntoObject(participants.map(participant => {
return {
[participant]: arrayToObject(_.range(scenarios.length).map(_ => 1))
};
}));
_.times(generations, generation => {
fillInIndividuals(individuals);
fillInIndividuals(individuals, population, participants);
scenarios.forEach((scenario, index) => {
scenario.id = index;
@@ -50,12 +67,22 @@ function run (fitnessScenarios, generations=150, population=32, individuals = {}
_.each(individuals, (individualsForParticipant, participant) => {
individualsForParticipant.forEach(individual => {
individual.fitness = boilDownIndividualScore(individual, participant, fitnesses);
individual.fitness = boilDownIndividualScore(
individual,
participant,
fitnesses,
scenarioImportances
);
});
});
scenarioImportances = calculateScenarioImportances(participants, fitnesses);
// TODO _ fittestIndividualsOfAllTime is an OUT variable, make this design better
individuals = breedFittestIndividuals(individuals, population, fittestIndividualsOfAllTime);
individuals = breedFittestIndividuals(
individuals,
population,
fittestIndividualsOfAllTime // OUT
);
});
return fittestIndividualsOfAllTime;
View
@@ -0,0 +1,53 @@
Map.prototype.map = function (f) {
const resultingMap = new Map();
this.forEach((value, key) => {
resultingMap.set(key, f(value, key));
});
return resultingMap;
};
Map.prototype.filter = function (f) {
const resultingMap = new Map();
this.forEach((value, key) => {
if (f(value, key)) {
resultingMap.set(key, value);
};
});
return resultingMap;
};
Map.prototype.valuesArray = function () {
const values = [];
for (let value of this.values()) {
values.push(value);
}
return values;
};
Map.prototype.keysArray = function () {
const keys = [];
for (let key of this.keys()) {
keys.push(key);
}
return keys;
};
Map.prototype.log = function () {
const entries = {};
for (let [key, value] of this.entries()) {
entries[key] = value;
};
console.log(entries);
return this;
};
Oops, something went wrong.