Add history for predictor moves

This commit is contained in:
Anton Vakhrushev 2020-03-22 14:38:05 +03:00
parent 9e06a1c630
commit 49bb3da4bb
6 changed files with 108 additions and 31 deletions

View File

@ -1,34 +1,42 @@
import Journal from './Journal'; import Journal from './Journal';
const DEFAULT_EPSILON = 0.01;
function create_key(steps: number[]): string { function create_key(steps: number[]): string {
return steps.join(':'); return steps.join(':');
} }
class Daemon { 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( constructor(
id: string,
base: number, base: number,
humanCount: number, humanCount: number,
robotCount: number, robotCount: number,
epsilon: number = DEFAULT_EPSILON epsilon: number = Daemon.DEFAULT_EPSILON
) { ) {
this.thisId = id;
this.base = base; this.base = base;
this.humanCount = humanCount; this.humanCount = humanCount;
this.robotCount = robotCount; this.robotCount = robotCount;
this.epsilon = epsilon; this.epsilon = epsilon;
} }
get id(): string {
return this.thisId;
}
get power(): number { get power(): number {
return this.humanCount + this.robotCount; return this.humanCount + this.robotCount;
} }
@ -53,6 +61,10 @@ class Daemon {
this._adjustWeight([...steps, humanValue], adjustmentWeight); this._adjustWeight([...steps, humanValue], adjustmentWeight);
} }
getWeights() {
return { ...this.weights };
}
private _getStepSlice(journal: Journal): number[] { private _getStepSlice(journal: Journal): number[] {
return journal.getLastMovements(this.humanCount, this.robotCount); return journal.getLastMovements(this.humanCount, this.robotCount);
} }

View File

@ -2,12 +2,20 @@
* Represents one game move. * Represents one game move.
*/ */
class Move { class Move {
public human: number; private readonly itsHuman: number;
public robot: number; private readonly itsRobot: number;
constructor(human: number, robot: number) { constructor(human: number, robot: number) {
this.human = human; this.itsHuman = human;
this.robot = robot; this.itsRobot = robot;
}
get human() {
return this.itsHuman;
}
get robot() {
return this.itsRobot;
} }
} }

View File

@ -3,9 +3,10 @@ import Journal from './Journal';
import Supervisor from './Supervisor'; import Supervisor from './Supervisor';
interface DaemonConfig { interface DaemonConfig {
id?: string;
human: number; human: number;
robot: number; robot: number;
epsilon: number; epsilon?: number;
} }
interface PredictorConfig { interface PredictorConfig {
@ -14,15 +15,22 @@ interface PredictorConfig {
daemons: DaemonConfig[]; daemons: DaemonConfig[];
} }
interface HistoryRecord {
score: number;
move: [number, number];
rates: { [id: string]: number };
weights: { [id: string]: any };
}
const DEFAULT_CONFIG: PredictorConfig = { const DEFAULT_CONFIG: PredictorConfig = {
base: 2, base: 2,
supervisor_epsilon: 0.01, supervisor_epsilon: 0.01,
daemons: [ daemons: [
{ human: 2, robot: 2, epsilon: 0.01 }, { human: 2, robot: 2 },
{ human: 3, robot: 3, epsilon: 0.01 }, { human: 3, robot: 3 },
{ human: 4, robot: 4, epsilon: 0.01 }, { human: 4, robot: 4 },
{ human: 5, robot: 5, epsilon: 0.01 }, { human: 5, robot: 5 },
{ human: 6, robot: 6, epsilon: 0.01 }, { human: 6, robot: 6 },
], ],
}; };
@ -30,7 +38,7 @@ export default class Predictor {
/** /**
* @type {Number} * @type {Number}
*/ */
base: number; readonly base: number;
/** /**
* @type {Number} * @type {Number}
@ -47,12 +55,15 @@ export default class Predictor {
*/ */
supervisor: Supervisor; supervisor: Supervisor;
history: HistoryRecord[];
constructor(config: PredictorConfig = DEFAULT_CONFIG) { constructor(config: PredictorConfig = DEFAULT_CONFIG) {
this.base = config.base; this.base = config.base;
this.score = 0; this.score = 0;
this.journal = new Journal(); this.journal = new Journal();
const daemons = this._createDaemons(config.daemons); const daemons = this._createDaemons(config.daemons);
this.supervisor = new Supervisor(daemons, config.supervisor_epsilon); this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
this.history = [];
} }
pass(humanValue: number): number { pass(humanValue: number): number {
@ -63,16 +74,29 @@ export default class Predictor {
this.score += prediction === humanValue ? -1 : 1; this.score += prediction === humanValue ? -1 : 1;
this.supervisor.adjust(this.journal, humanValue); this.supervisor.adjust(this.journal, humanValue);
this.journal.makeMove(humanValue, prediction); this.journal.makeMove(humanValue, prediction);
this.history.push({
score: this.score,
move: [humanValue, prediction],
rates: this.supervisor.rates(),
weights: this.supervisor.weights(),
});
return prediction; return prediction;
} }
showHistory(deep: number): HistoryRecord[] {
return this.history.slice(-deep);
}
private _createDaemons(daemonConfigs: DaemonConfig[]): Daemon[] { private _createDaemons(daemonConfigs: DaemonConfig[]): Daemon[] {
return daemonConfigs.map(config => { return daemonConfigs.map(config => {
const epsilon = config.epsilon || Daemon.DEFAULT_EPSILON;
return new Daemon( return new Daemon(
config.id ||
`daemon-${config.human}-${config.robot}-${epsilon}`,
this.base, this.base,
config.human, config.human,
config.robot, config.robot,
config.epsilon || 0.01 epsilon
); );
}); });
} }

View File

@ -1,8 +1,6 @@
import Journal from './Journal'; import Journal from './Journal';
import Daemon from './Daemon'; import Daemon from './Daemon';
const DEFAULT_EPSILON = 0.01;
interface DaemonRate { interface DaemonRate {
daemon: Daemon; daemon: Daemon;
rate: number; rate: number;
@ -16,11 +14,16 @@ interface Prediction {
} }
class Supervisor { class Supervisor {
static DEFAULT_EPSILON = 0.01;
daemonRates: DaemonRate[] = []; daemonRates: DaemonRate[] = [];
readonly epsilon: number; readonly epsilon: number;
constructor(daemons: Daemon[], epsilon: number = DEFAULT_EPSILON) { constructor(
daemons: Daemon[],
epsilon: number = Supervisor.DEFAULT_EPSILON
) {
if (!daemons || daemons.length === 0) { if (!daemons || daemons.length === 0) {
throw Error('Empty daemon list'); 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[] { private _createPredictions(journal: Journal): Prediction[] {
return this.daemonRates.map(daemonRate => ({ return this.daemonRates.map(daemonRate => ({
daemonRate: daemonRate, daemonRate: daemonRate,

View File

@ -3,23 +3,32 @@ import { expect } from 'chai';
import Daemon from '../src/Daemon'; import Daemon from '../src/Daemon';
import Journal from '../src/Journal'; import Journal from '../src/Journal';
import Move from '../src/Move';
describe('Daemon', function() { describe('Daemon', function() {
it('Get prediction for beginning', function() { it('Get prediction for beginning', function() {
const journal = new Journal(); const daemon = new Daemon('d1', 2, 1, 1);
const daemon = new Daemon(2, 1, 1); expect('d1').to.equals(daemon.id);
const predicted = daemon.predict(journal);
expect(predicted).to.equals(0); const predicted = daemon.predict(new Journal());
expect(0).to.equals(predicted);
}); });
it('Can get power', function() { it('Can get power', function() {
const d = new Daemon(2, 5, 8); const d = new Daemon('d1', 2, 5, 8);
expect(d.power).to.eqls(13); 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() { it('Daemon 1-1', function() {
const journal = new Journal(); 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 = [ const steps = [
{ {

View File

@ -7,7 +7,10 @@ import Journal from '../src/Journal';
describe('Supervisor', function() { describe('Supervisor', function() {
it('Get prediction for one daemon state', 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 journal = new Journal();
const human1 = 1; const human1 = 1;
@ -26,5 +29,7 @@ describe('Supervisor', function() {
journal.makeMove(human2, predicted1); journal.makeMove(human2, predicted1);
supervisor.adjust(journal, human2); supervisor.adjust(journal, human2);
expect({ d1: 1.1 ** 2 }).to.eqls(supervisor.rates());
}); });
}); });