Connected genetic algorithm with decision tree
This commit is contained in:
parent
8e7b565dd7
commit
4bd5bbdc0a
@ -21,6 +21,7 @@
|
|||||||
<script src="logic/knowledge.js"></script>
|
<script src="logic/knowledge.js"></script>
|
||||||
<script src="logic/time.js"></script>
|
<script src="logic/time.js"></script>
|
||||||
<script src="logic/shelf.js"></script>
|
<script src="logic/shelf.js"></script>
|
||||||
|
<script src="logic/arrangment.js"></script>
|
||||||
<script src="logic/evolution.js"></script>
|
<script src="logic/evolution.js"></script>
|
||||||
|
|
||||||
<script src="view/floor.js"></script>
|
<script src="view/floor.js"></script>
|
||||||
|
116
src/logic/arrangment.js
Normal file
116
src/logic/arrangment.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
const ShelfCount = 17 * 6;
|
||||||
|
const InRow = 3 * 6;
|
||||||
|
|
||||||
|
// index to abstract coordinates
|
||||||
|
function i2ac(index) {
|
||||||
|
const y = Math.floor(index / InRow);
|
||||||
|
const x = (index % InRow);
|
||||||
|
if (y === 5 && x >= 6)
|
||||||
|
return [x + 6, y];
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ac2i(x, y) {
|
||||||
|
return y * InRow + x - (y === 5 && x >= 6) * 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// index to grid coordinates
|
||||||
|
function i2gc(index) {
|
||||||
|
const [x, y] = i2ac(index);
|
||||||
|
return [x + Number(x > 11) + Number(x > 5), y + Math.floor((y + 1) / 2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gc2i(x, y) {
|
||||||
|
x -= Number(x >= 13) + Number(x >= 6);
|
||||||
|
switch (y) {
|
||||||
|
case 0: y = 0; break;
|
||||||
|
case 2: y = 1; break;
|
||||||
|
case 3: y = 2; break;
|
||||||
|
case 5: y = 3; break;
|
||||||
|
case 6: y = 4; break;
|
||||||
|
case 8: y = 5; break;
|
||||||
|
}
|
||||||
|
return ac2i(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Arrangement {
|
||||||
|
generateRandom() {
|
||||||
|
this.products = [...Array(ShelfCount).keys()].map(() => Knowledge.semanticNetwork.getRandom().name);
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(products = []) {
|
||||||
|
this.products = products;
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.productsAmount = null;
|
||||||
|
this.productsAttributes = null;
|
||||||
|
this.similarities = null;
|
||||||
|
this.cachedValidity = null;
|
||||||
|
this.similarity = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return JSON.parse(JSON.stringify(this.products));
|
||||||
|
}
|
||||||
|
|
||||||
|
simulate() {
|
||||||
|
this.similarities ||= [...Array(ShelfCount)].map(() => 0);
|
||||||
|
this.productsAmount ||= [...Array(ShelfCount)].map(() => 50);
|
||||||
|
this.productsAttributes ||= this.products.map(name => Knowledge.productAttributes.getRandomFor(name));
|
||||||
|
|
||||||
|
this.similarity = 0;
|
||||||
|
this.products.forEach((product, i) => {
|
||||||
|
let similarityWithNeighbours = 0;
|
||||||
|
const neighbours = neighboursByIndex(i);
|
||||||
|
for (const neighbour of neighbours) {
|
||||||
|
similarityWithNeighbours += similarity(product, this.products[neighbour]);
|
||||||
|
}
|
||||||
|
similarityWithNeighbours /= neighbours.length;
|
||||||
|
this.similarities[i] = 1 - similarityWithNeighbours;
|
||||||
|
this.similarity += this.similarities[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.similarity /= ShelfCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate() {
|
||||||
|
if (this.cachedValidity)
|
||||||
|
return this.cachedValidity;
|
||||||
|
|
||||||
|
const matchingShelfs = this.products.map(async (product, i) => {
|
||||||
|
if (!this.productsAttributes[i])
|
||||||
|
return;
|
||||||
|
|
||||||
|
let shelfType = await requestJSONCached('/api/decide', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
redirect: 'follow',
|
||||||
|
body: JSON.stringify(this.productsAttributes[i])
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shelfType)
|
||||||
|
return;
|
||||||
|
shelfType = shelfType[0];
|
||||||
|
return Grid.instance.getShelf(...i2gc(i)).type === shelfType;
|
||||||
|
});
|
||||||
|
|
||||||
|
let m = 0;
|
||||||
|
for (const match of matchingShelfs)
|
||||||
|
switch (await match) {
|
||||||
|
case undefined:
|
||||||
|
case true:
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cachedValidity = m / this.productsAttributes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fitness() {
|
||||||
|
if (this.similarity === null) this.simulate();
|
||||||
|
if (this.cachedValidity === null) await this.validate();
|
||||||
|
return (this.cachedValidity + this.similarity) / 2;
|
||||||
|
}
|
||||||
|
}
|
@ -1,75 +1,3 @@
|
|||||||
const ShelfCount = 17 * 6;
|
|
||||||
const InRow = 3 * 6;
|
|
||||||
|
|
||||||
// index to abstract coordinates
|
|
||||||
function i2ac(index) {
|
|
||||||
const y = Math.floor(index / InRow);
|
|
||||||
const x = (index % InRow);
|
|
||||||
if (y === 5 && x >= 6)
|
|
||||||
return [x + 6, y];
|
|
||||||
return [x, y];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ac2i(x, y) {
|
|
||||||
return y * InRow + x - (y === 5 && x >= 6) * 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
// index to grid coordinates
|
|
||||||
function i2gc(index) {
|
|
||||||
const [x, y] = i2ac(index);
|
|
||||||
return [x + Number(x > 11) + Number(x > 5), y + Math.floor((y + 1) / 2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function gc2i(x, y) {
|
|
||||||
x -= Number(x >= 13) + Number(x >= 6);
|
|
||||||
switch (y) {
|
|
||||||
case 0: y = 0; break;
|
|
||||||
case 2: y = 1; break;
|
|
||||||
case 3: y = 2; break;
|
|
||||||
case 5: y = 3; break;
|
|
||||||
case 6: y = 4; break;
|
|
||||||
case 8: y = 5; break;
|
|
||||||
}
|
|
||||||
return ac2i(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Arrangement {
|
|
||||||
generateRandom() {
|
|
||||||
this.products = [...Array(ShelfCount).keys()].map(() => Knowledge.semanticNetwork.getRandom().name);
|
|
||||||
this.productsAmount = null;
|
|
||||||
this.similarities = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(products = []) {
|
|
||||||
this.products = products;
|
|
||||||
this.productsAmount = null;
|
|
||||||
this.similarities = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return JSON.parse(JSON.stringify(this.products));
|
|
||||||
}
|
|
||||||
|
|
||||||
simulate() {
|
|
||||||
this.similarities ||= [...Array(ShelfCount)].map(() => 0);
|
|
||||||
this.productsAmount ||= [...Array(ShelfCount)].map(() => 50);
|
|
||||||
|
|
||||||
this.similarity = 0;
|
|
||||||
this.products.forEach((product, i) => {
|
|
||||||
let similarityWithNeighbours = 0;
|
|
||||||
const neighbours = neighboursByIndex(i);
|
|
||||||
for (const neighbour of neighbours) {
|
|
||||||
similarityWithNeighbours += similarity(product, this.products[neighbour]);
|
|
||||||
}
|
|
||||||
similarityWithNeighbours /= neighbours.length;
|
|
||||||
this.similarities[i] = 1 - similarityWithNeighbours;
|
|
||||||
this.similarity += this.similarities[i];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.similarity /= ShelfCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopulationSize = 31 * 2;
|
const PopulationSize = 31 * 2;
|
||||||
const PopulationToKeep = 0.3;
|
const PopulationToKeep = 0.3;
|
||||||
const MutationRate = 0.2;
|
const MutationRate = 0.2;
|
||||||
@ -98,14 +26,15 @@ class Evolution extends Arrangement {
|
|||||||
this.max_similarity = this.population.reduce((p, c) => Math.max(p, c.similarity || 0), 0);
|
this.max_similarity = this.population.reduce((p, c) => Math.max(p, c.similarity || 0), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
next(days) {
|
async next(days) {
|
||||||
for (let i = 0; i < Number(days) - 1; ++i) {
|
for (let i = 0; i < Number(days) - 1; ++i) {
|
||||||
this.step(false);
|
await this.step(false);
|
||||||
}
|
}
|
||||||
this.step(true);
|
await this.step(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
step(render = true) {
|
|
||||||
|
async step(render = true) {
|
||||||
// Complete previous day
|
// Complete previous day
|
||||||
this.products = this.population[this.populationIndex].products;
|
this.products = this.population[this.populationIndex].products;
|
||||||
this.simulate();
|
this.simulate();
|
||||||
@ -124,7 +53,7 @@ class Evolution extends Arrangement {
|
|||||||
let newPopulation = [];
|
let newPopulation = [];
|
||||||
this.population.sort((a, b) => a.similarity - b.similarity);
|
this.population.sort((a, b) => a.similarity - b.similarity);
|
||||||
|
|
||||||
let fitness = this.population.map(({ similarity }) => similarity);
|
let fitness = await Promise.all(this.population.map(arr => arr.fitness()));
|
||||||
let sum = fitness.reduce((p, c) => p + c, 0);
|
let sum = fitness.reduce((p, c) => p + c, 0);
|
||||||
fitness = fitness.map(f => Math.pow(f / sum, 2));
|
fitness = fitness.map(f => Math.pow(f / sum, 2));
|
||||||
|
|
||||||
|
@ -52,6 +52,31 @@ class SemanticNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AttributesSemanticNetwork {
|
||||||
|
constructor(data) {
|
||||||
|
this.entries = {}
|
||||||
|
for (const { nazwa, polka, ...attr } of data) {
|
||||||
|
if (!this.entries[nazwa]) {
|
||||||
|
this.entries[nazwa] = Object.entries(attr).reduce((p, [key, value]) => ({ ...p, [key]: new Set([value]) }), {});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(attr)) {
|
||||||
|
this.entries[nazwa][key].add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRandomFor(productName) {
|
||||||
|
const attributes = this.entries[productName];
|
||||||
|
return attributes && Object.entries(attributes)
|
||||||
|
.reduce((p, [key, values]) => {
|
||||||
|
const r = randomFromSet(values);
|
||||||
|
return r !== 'nie_dotyczy' ? { ...p, [key]: r } : p;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function nearbyStorageUnitsCoords(gridX, gridY) {
|
function nearbyStorageUnitsCoords(gridX, gridY) {
|
||||||
function outsideOfStorageCenter(v) {
|
function outsideOfStorageCenter(v) {
|
||||||
const { x, y, w, h } = StorageCenterLocation
|
const { x, y, w, h } = StorageCenterLocation
|
||||||
@ -73,6 +98,22 @@ function nearbyStorageUnitsIndexes(gridX, gridY) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function loadTsvFile(path, nested = []) {
|
||||||
|
const response = await fetch(path);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
const [header, ...rows] = text.split('\n')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x)
|
||||||
|
.map(x => x.split('\t')
|
||||||
|
.map(cell => cell.split(',').map(x => x.trim())));
|
||||||
|
|
||||||
|
return rows.map(row =>
|
||||||
|
row.reduce((p, c, i) => ({
|
||||||
|
...p,
|
||||||
|
[header[i]]: nested.includes(i) ? c : c[0] }), {}));
|
||||||
|
}
|
||||||
|
|
||||||
class Knowledge {
|
class Knowledge {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,51 +121,13 @@ class Knowledge {
|
|||||||
*/
|
*/
|
||||||
static semanticNetwork
|
static semanticNetwork
|
||||||
|
|
||||||
static async loadFromFile(path) {
|
static async load() {
|
||||||
const response = await fetch(path);
|
const [ products, attributes ] = await Promise.all([
|
||||||
const text = await response.text();
|
loadTsvFile('/data/products.tsv', [ 2 ]),
|
||||||
|
loadTsvFile('/data/productsTree.tsv')
|
||||||
|
]);
|
||||||
|
|
||||||
const [header, ...rows] = text.split('\n')
|
Knowledge.semanticNetwork = new SemanticNetwork(products);
|
||||||
.map(x => x.trim())
|
Knowledge.productAttributes = new AttributesSemanticNetwork(attributes);
|
||||||
.filter(x => x)
|
|
||||||
.map(x => x.split('\t')
|
|
||||||
.map(cell => cell.split(',').map(x => x.trim())));
|
|
||||||
|
|
||||||
const data = rows.map(row =>
|
|
||||||
row.reduce((p, c, i) => ({
|
|
||||||
...p,
|
|
||||||
[header[i]]: i == 2 ? c : c[0] }), {}));
|
|
||||||
|
|
||||||
Knowledge.semanticNetwork = new SemanticNetwork(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
Knowledge.semanticNetwork = new SemanticNetwork(definition)
|
|
||||||
Knowledge.arrangement = new Arrangement()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
keywords:
|
|
||||||
p - powiązane pomieszczenia / miejsca
|
|
||||||
c - category
|
|
||||||
|
|
||||||
new Knowledge(`
|
|
||||||
Piłka :c sport,rozrywka, piłka nożna
|
|
||||||
Kubek klubowy :c sport,rozrywka,piłka nożna :p kuchnia
|
|
||||||
Waga łazienkowa :c zdrowie,agd,elektronika :p łazienka
|
|
||||||
Czajnik elektryczny :c agd,elektronika,zdrowie :p kuchnia
|
|
||||||
Widelec :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Łyżka :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Nóż stołowy :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Nóż kuchenny :c agd,gotowanie,przyrząd kuchenny :p kuchnia
|
|
||||||
Deska do krojenia :cagd,gotowanie,przyrząd kuchenny :p kuchnia
|
|
||||||
Gąbki kuchenne :c agd,gotowanie,sprzątanie :p kuchnia
|
|
||||||
Worki na śmieci :c agd,gotowanie,sprzątanie :p kuchnia
|
|
||||||
Miska :c agd,jedzenie,gotowanie,zastawa kuchenna :p kuchnia
|
|
||||||
Laptop :c elektronika,gaming :p biuro,szkoła
|
|
||||||
Telefon :c elektronika :p biuro,szkoła
|
|
||||||
`)
|
|
||||||
*/
|
|
@ -57,8 +57,8 @@ class Time {
|
|||||||
/**
|
/**
|
||||||
* Add days
|
* Add days
|
||||||
*/
|
*/
|
||||||
addDays(days) {
|
async addDays(days) {
|
||||||
Products.instance.next(days);
|
await Products.instance.next(days);
|
||||||
Products.instance.update();
|
Products.instance.update();
|
||||||
const ul = document.querySelector('ul#history');
|
const ul = document.querySelector('ul#history');
|
||||||
this.setDay(this.day += Number(days));
|
this.setDay(this.day += Number(days));
|
||||||
|
@ -14,7 +14,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const grid = new Grid(gridCanvas);
|
const grid = new Grid(gridCanvas);
|
||||||
const agent = new Agent(agentCanvas);
|
const agent = new Agent(agentCanvas);
|
||||||
|
|
||||||
await Knowledge.loadFromFile('/data/products.tsv')
|
await Knowledge.load()
|
||||||
const products = new Products(productsCanvas);
|
const products = new Products(productsCanvas);
|
||||||
|
|
||||||
const fpsElement = document.getElementById('fps');
|
const fpsElement = document.getElementById('fps');
|
||||||
|
@ -50,4 +50,32 @@ function pick(array) {
|
|||||||
return index - 1;
|
return index - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unreachable() {
|
||||||
|
throw new Error('unreachable');
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomFromSet(set) {
|
||||||
|
const r = Math.floor(Math.random() * set.size);
|
||||||
|
let i = 0;
|
||||||
|
for (const value of set.values()) {
|
||||||
|
if (i++ === r)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function nice(v) { return `${(v * 100).toFixed(1)}%` }
|
function nice(v) { return `${(v * 100).toFixed(1)}%` }
|
||||||
|
|
||||||
|
async function requestJSONCached(url, params = {}) {
|
||||||
|
const key = url + '\n' + (params?.body || '');
|
||||||
|
if (requestJSONCached.cache.has(key))
|
||||||
|
return requestJSONCached.cache.get(key);
|
||||||
|
|
||||||
|
const response = await fetch(url, params);
|
||||||
|
const json = await response.json();
|
||||||
|
requestJSONCached.cache.set(key, json);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJSONCached.cache = new Map();
|
@ -5,6 +5,9 @@ const GRID_FIELD_TYPE = Object.freeze({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class Grid {
|
class Grid {
|
||||||
|
/**
|
||||||
|
* @type{Grid}
|
||||||
|
*/
|
||||||
static instance;
|
static instance;
|
||||||
|
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
@ -31,6 +34,10 @@ class Grid {
|
|||||||
return this.shelves;
|
return this.shelves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getShelf(x, y) {
|
||||||
|
return this.shelves[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
setCanvasSize (canvas) {
|
setCanvasSize (canvas) {
|
||||||
canvas.width = 2000;
|
canvas.width = 2000;
|
||||||
canvas.height = 900;
|
canvas.height = 900;
|
||||||
|
@ -13,7 +13,7 @@ class TimeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readFromDayInput() {
|
async readFromDayInput() {
|
||||||
Time.instance.addDays(document.getElementById('dayInput').value);
|
await Time.instance.addDays(document.getElementById('dayInput').value);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user