From 49bb3da4bb64bd193b18758d65162160c4f53e7a Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 22 Mar 2020 14:38:05 +0300 Subject: [PATCH] Add history for predictor moves --- src/Daemon.ts | 28 ++++++++++++++++++++-------- src/Move.ts | 16 ++++++++++++---- src/Predictor.ts | 40 ++++++++++++++++++++++++++++++++-------- src/Supervisor.ts | 25 ++++++++++++++++++++++--- tests/DaemonTest.ts | 23 ++++++++++++++++------- tests/SupervisorTest.ts | 7 ++++++- 6 files changed, 108 insertions(+), 31 deletions(-) diff --git a/src/Daemon.ts b/src/Daemon.ts index 4da53a8..70f7e72 100644 --- a/src/Daemon.ts +++ b/src/Daemon.ts @@ -1,34 +1,42 @@ import Journal from './Journal'; -const DEFAULT_EPSILON = 0.01; - function create_key(steps: number[]): string { return steps.join(':'); } class Daemon { - base: number; + static DEFAULT_EPSILON = 0.01; - humanCount: number; + private readonly thisId: string; - robotCount: number; + private readonly base: number; - epsilon: number; + private readonly humanCount: number; - weights: { [key: string]: number } = {}; + private readonly robotCount: number; + + private readonly epsilon: number; + + private weights: { [key: string]: number } = {}; constructor( + id: string, base: number, humanCount: number, robotCount: number, - epsilon: number = DEFAULT_EPSILON + epsilon: number = Daemon.DEFAULT_EPSILON ) { + this.thisId = id; this.base = base; this.humanCount = humanCount; this.robotCount = robotCount; this.epsilon = epsilon; } + get id(): string { + return this.thisId; + } + get power(): number { return this.humanCount + this.robotCount; } @@ -53,6 +61,10 @@ class Daemon { this._adjustWeight([...steps, humanValue], adjustmentWeight); } + getWeights() { + return { ...this.weights }; + } + private _getStepSlice(journal: Journal): number[] { return journal.getLastMovements(this.humanCount, this.robotCount); } diff --git a/src/Move.ts b/src/Move.ts index 28bf5c3..9fef8e5 100644 --- a/src/Move.ts +++ b/src/Move.ts @@ -2,12 +2,20 @@ * Represents one game move. */ class Move { - public human: number; - public robot: number; + private readonly itsHuman: number; + private readonly itsRobot: number; constructor(human: number, robot: number) { - this.human = human; - this.robot = robot; + this.itsHuman = human; + this.itsRobot = robot; + } + + get human() { + return this.itsHuman; + } + + get robot() { + return this.itsRobot; } } diff --git a/src/Predictor.ts b/src/Predictor.ts index 8a3bf9a..edb5bcc 100644 --- a/src/Predictor.ts +++ b/src/Predictor.ts @@ -3,9 +3,10 @@ import Journal from './Journal'; import Supervisor from './Supervisor'; interface DaemonConfig { + id?: string; human: number; robot: number; - epsilon: number; + epsilon?: number; } interface PredictorConfig { @@ -14,15 +15,22 @@ interface PredictorConfig { daemons: DaemonConfig[]; } +interface HistoryRecord { + score: number; + move: [number, number]; + rates: { [id: string]: number }; + weights: { [id: string]: any }; +} + const DEFAULT_CONFIG: PredictorConfig = { base: 2, supervisor_epsilon: 0.01, daemons: [ - { human: 2, robot: 2, epsilon: 0.01 }, - { human: 3, robot: 3, epsilon: 0.01 }, - { human: 4, robot: 4, epsilon: 0.01 }, - { human: 5, robot: 5, epsilon: 0.01 }, - { human: 6, robot: 6, epsilon: 0.01 }, + { human: 2, robot: 2 }, + { human: 3, robot: 3 }, + { human: 4, robot: 4 }, + { human: 5, robot: 5 }, + { human: 6, robot: 6 }, ], }; @@ -30,7 +38,7 @@ export default class Predictor { /** * @type {Number} */ - base: number; + readonly base: number; /** * @type {Number} @@ -47,12 +55,15 @@ export default class Predictor { */ supervisor: Supervisor; + history: HistoryRecord[]; + constructor(config: PredictorConfig = DEFAULT_CONFIG) { this.base = config.base; this.score = 0; this.journal = new Journal(); const daemons = this._createDaemons(config.daemons); this.supervisor = new Supervisor(daemons, config.supervisor_epsilon); + this.history = []; } pass(humanValue: number): number { @@ -63,16 +74,29 @@ export default class Predictor { this.score += prediction === humanValue ? -1 : 1; this.supervisor.adjust(this.journal, humanValue); this.journal.makeMove(humanValue, prediction); + this.history.push({ + score: this.score, + move: [humanValue, prediction], + rates: this.supervisor.rates(), + weights: this.supervisor.weights(), + }); return prediction; } + showHistory(deep: number): HistoryRecord[] { + return this.history.slice(-deep); + } + private _createDaemons(daemonConfigs: DaemonConfig[]): Daemon[] { return daemonConfigs.map(config => { + const epsilon = config.epsilon || Daemon.DEFAULT_EPSILON; return new Daemon( + config.id || + `daemon-${config.human}-${config.robot}-${epsilon}`, this.base, config.human, config.robot, - config.epsilon || 0.01 + epsilon ); }); } diff --git a/src/Supervisor.ts b/src/Supervisor.ts index 95e67e7..34f999c 100644 --- a/src/Supervisor.ts +++ b/src/Supervisor.ts @@ -1,8 +1,6 @@ import Journal from './Journal'; import Daemon from './Daemon'; -const DEFAULT_EPSILON = 0.01; - interface DaemonRate { daemon: Daemon; rate: number; @@ -16,11 +14,16 @@ interface Prediction { } class Supervisor { + static DEFAULT_EPSILON = 0.01; + daemonRates: DaemonRate[] = []; readonly epsilon: number; - constructor(daemons: Daemon[], epsilon: number = DEFAULT_EPSILON) { + constructor( + daemons: Daemon[], + epsilon: number = Supervisor.DEFAULT_EPSILON + ) { if (!daemons || daemons.length === 0) { throw Error('Empty daemon list'); } @@ -52,6 +55,22 @@ class Supervisor { } } + rates() { + const result = {}; + this.daemonRates.forEach(r => { + result[r.daemon.id] = r.rate; + }); + return result; + } + + weights() { + const result = {}; + this.daemonRates.forEach(r => { + result[r.daemon.id] = r.daemon.getWeights(); + }); + return result; + } + private _createPredictions(journal: Journal): Prediction[] { return this.daemonRates.map(daemonRate => ({ daemonRate: daemonRate, diff --git a/tests/DaemonTest.ts b/tests/DaemonTest.ts index be08a69..074f743 100644 --- a/tests/DaemonTest.ts +++ b/tests/DaemonTest.ts @@ -3,23 +3,32 @@ import { expect } from 'chai'; import Daemon from '../src/Daemon'; import Journal from '../src/Journal'; +import Move from '../src/Move'; describe('Daemon', function() { it('Get prediction for beginning', function() { - const journal = new Journal(); - const daemon = new Daemon(2, 1, 1); - const predicted = daemon.predict(journal); - expect(predicted).to.equals(0); + const daemon = new Daemon('d1', 2, 1, 1); + expect('d1').to.equals(daemon.id); + + const predicted = daemon.predict(new Journal()); + expect(0).to.equals(predicted); }); it('Can get power', function() { - const d = new Daemon(2, 5, 8); - expect(d.power).to.eqls(13); + const d = new Daemon('d1', 2, 5, 8); + expect(13).to.eqls(d.power); + }); + + it('Can adjust', function() { + const journal = new Journal([new Move(0, 0), new Move(0, 0)]); + const daemon = new Daemon('d1', 2, 1, 1, 0.1); + daemon.adjust(journal, 1); + expect({ '0:0:1': 1.1 ** 2 }).to.eqls(daemon.getWeights()); }); it('Daemon 1-1', function() { const journal = new Journal(); - const daemon = new Daemon(2, 1, 1, 0.1); + const daemon = new Daemon('d1', 2, 1, 1, 0.1); const steps = [ { diff --git a/tests/SupervisorTest.ts b/tests/SupervisorTest.ts index fdf1d59..e1dfbf6 100644 --- a/tests/SupervisorTest.ts +++ b/tests/SupervisorTest.ts @@ -7,7 +7,10 @@ import Journal from '../src/Journal'; describe('Supervisor', function() { it('Get prediction for one daemon state', function() { - const supervisor = new Supervisor([new Daemon(2, 1, 1, 0.01)], 0.01); + const supervisor = new Supervisor( + [new Daemon('d1', 2, 1, 1, 0.1)], + 0.1 + ); const journal = new Journal(); const human1 = 1; @@ -26,5 +29,7 @@ describe('Supervisor', function() { journal.makeMove(human2, predicted1); supervisor.adjust(journal, human2); + + expect({ d1: 1.1 ** 2 }).to.eqls(supervisor.rates()); }); });