Skip to content

Commit 2a054d3

Browse files
committed
networkDesign backend: checkpoint generation data
After the scenarios are prepared for a generation, add a checkpoint for the generation, saving the candidate chromosomes and scenarioId. Upon resuming, if the previous generation is undefined and there is checkoint data, recover the previous candidates, as well as their corresponding scenarios from cache. For now, the whole generation will be re-calculated, the candidates's result are not yet saved as checkpoints.
1 parent aaeed97 commit 2a054d3

File tree

4 files changed

+102
-26
lines changed

4 files changed

+102
-26
lines changed

packages/transition-backend/src/services/evolutionaryAlgorithm/candidate/LineAndNumberOfVehiclesNetworkCandidate.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ class LineAndNumberOfVehiclesNetworkCandidate extends Candidate {
2828

2929
constructor(
3030
chromosome: AlgoTypes.CandidateChromosome,
31-
wrappedJob: TransitNetworkDesignJobWrapper<EvolutionaryTransitNetworkDesignJobType>
31+
wrappedJob: TransitNetworkDesignJobWrapper<EvolutionaryTransitNetworkDesignJobType>,
32+
scenario?: Scenario
3233
) {
3334
super(chromosome, wrappedJob);
35+
this.scenario = scenario;
3436
}
3537

3638
private prepareNetwork(): Line[] {

packages/transition-backend/src/services/evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import LineAndNumberOfVehiclesGenerationLogger from './LineAndNumberOfVehiclesGe
2121
import { EvolutionaryTransitNetworkDesignJob, EvolutionaryTransitNetworkDesignJobType } from '../../networkDesign/transitNetworkDesign/evolutionary/types';
2222
import LineCollection from 'transition-common/lib/services/line/LineCollection';
2323
import { TransitNetworkDesignJobWrapper } from '../../networkDesign/transitNetworkDesign/TransitNetworkDesignJobWrapper';
24+
import ScenarioCollection from 'transition-common/lib/services/scenario/ScenarioCollection';
25+
import Candidate from '../candidate/Candidate';
26+
import LineAndNumberOfVehiclesNetworkCandidate from '../candidate/LineAndNumberOfVehiclesNetworkCandidate';
2427

2528
const chromosomeExists = (chrom: boolean[], linesChromosomes: boolean[][]) =>
2629
linesChromosomes.findIndex((chromosome) => _isEqual(chromosome, chrom)) !== -1;
@@ -226,6 +229,20 @@ export const reproduceCandidates = (
226229
return candidates;
227230
};
228231

232+
export const resumeCandidatesFromChromosomes = (
233+
jobWrapper: TransitNetworkDesignJobWrapper<EvolutionaryTransitNetworkDesignJobType>,
234+
currentGeneration: Exclude<EvolutionaryTransitNetworkDesignJobType['internal_data']['currentGeneration'], undefined>,
235+
scenarioCollection: ScenarioCollection
236+
): NetworkCandidate[] => {
237+
return currentGeneration.candidates.map((candidateData) =>
238+
new NetworkCandidate(
239+
candidateData.chromosome,
240+
jobWrapper,
241+
scenarioCollection.getById(candidateData.scenarioId!)
242+
)
243+
);
244+
};
245+
229246
/**
230247
* Represents the current generation of the simulation
231248
*/
@@ -247,6 +264,10 @@ class LineAndNumberOfVehiclesGeneration extends Generation {
247264
);
248265
}
249266

267+
getCandidates(): LineAndNumberOfVehiclesNetworkCandidate[] {
268+
return this.candidates as LineAndNumberOfVehiclesNetworkCandidate[];
269+
}
270+
250271
// TODO Sorting candidates and calculating totalFitness maybe should not be
251272
// part of the evolutionary algorithm code, but we may need to extract some
252273
// result types. For now, it's the only algorithm we have, so keep it here

packages/transition-backend/src/services/networkDesign/transitNetworkDesign/evolutionary/EvolutionaryTransitNetworkDesignJob.ts

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import { ResultSerialization } from '../../../evolutionaryAlgorithm/candidate/Ca
2222
import NetworkCandidate from '../../../evolutionaryAlgorithm/candidate/LineAndNumberOfVehiclesNetworkCandidate';
2323
import Generation from '../../../evolutionaryAlgorithm/generation/Generation';
2424
import * as AlgoTypes from '../../../evolutionaryAlgorithm/internalTypes';
25-
import LineAndNumberOfVehiclesGeneration, { generateFirstCandidates, reproduceCandidates } from '../../../evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration';
25+
import LineAndNumberOfVehiclesGeneration, { generateFirstCandidates, reproduceCandidates, resumeCandidatesFromChromosomes } from '../../../evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration';
2626
import TrError from 'chaire-lib-common/lib/utils/TrError';
2727
import { collectionToCache as serviceCollectionToCache, collectionFromCache as serviceCollectionFromCache } from '../../../../models/capnpCache/transitServices.cache.queries';
28-
import { objectsToCache as linesToCache, objectFromCache as lineFromCache } from '../../../../models/capnpCache/transitLines.cache.queries';
28+
import { collectionFromCache as scenarioCollectionFromCache } from '../../../../models/capnpCache/transitScenarios.cache.queries';
29+
import { objectsToCache as linesToCache, objectFromCache as lineFromCache, collectionToCache as lineCollectionToCache, collectionFromCache as lineCollectionFromCache } from '../../../../models/capnpCache/transitLines.cache.queries';
2930
import { prepareServices, saveSimulationScenario } from '../../../evolutionaryAlgorithm/preparation/ServicePreparation';
3031
import Line from 'transition-common/lib/services/line/Line';
32+
import ScenarioCollection from 'transition-common/lib/services/scenario/ScenarioCollection';
3133

3234
/**
3335
* Do batch calculation on a csv file input
@@ -153,6 +155,10 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
153155
await serviceCollectionToCache(services, cacheDirectoryPath);
154156
console.log(`Saved service cache file in ${cacheDirectoryPath}.`);
155157
await linesToCache(simulatedLineCollection.getFeatures(), cacheDirectoryPath);
158+
// Save the line collection in a special cache directory. The order of
159+
// the lines is very important for the chromosome and we should make
160+
// sure it remains the same upon resume of the Job.
161+
await lineCollectionToCache(simulatedLineCollection, cacheDirectoryPath + '/simulatedLines');
156162
console.log(`Saved lines cache files in ${cacheDirectoryPath}.`);
157163

158164
// Add non simulated services to the collection, ie those used by the simulation, but not generated by it
@@ -227,8 +233,8 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
227233
// FIXME Do we even need this? Or can we just start recovery at serviceCollectionFromCache call and get only required data?
228234
await this.loadServerData(serviceLocator.socketEventManager);
229235

230-
// Get the simulated lines
231-
const simulatedLineCollection = this._getSimulatedLineCollection({ agencies: this.agencyCollection, lines: this.allLineCollection, services: this.serviceCollection });
236+
// Get the simulated lines from cache, to make sure the order is the same as before
237+
const simulatedLineCollection = await lineCollectionFromCache(this.getCacheDirectory() + '/simulatedLines');
232238
this.simulatedLineCollection = simulatedLineCollection;
233239

234240
// Read the services from the cache, with all individual line services
@@ -254,10 +260,48 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
254260
return acc;
255261
}, {});
256262
this.setLineServices(lineServices);
257-
263+
258264
console.timeEnd(`Preparing data for evolutionary transit network design job from cache ${jobId}`);
259265
}
260266

267+
private _prepareOrResumeGeneration = async (previousGeneration: LineAndNumberOfVehiclesGeneration | undefined): Promise<LineAndNumberOfVehiclesGeneration> => {
268+
// Resume candidates from checkpoint if possible, scenarios are prepared
269+
if (previousGeneration === undefined && this.job.attributes.internal_data.checkpoint !== undefined && this.job.attributes.internal_data.checkpoint > 0 && this.job.attributes.internal_data.currentGeneration !== undefined) {
270+
this.currentIteration = this.job.attributes.internal_data.checkpoint;
271+
const currentGenerationData = this.job.attributes.internal_data.currentGeneration;
272+
// Get the current scenario collection from cache to recover previous scenarios
273+
const scenarioCollection = await scenarioCollectionFromCache(this.getCacheDirectory());
274+
const candidates = resumeCandidatesFromChromosomes(this, currentGenerationData, scenarioCollection);
275+
console.log(`Resumed generation ${this.currentIteration} with ${candidates.length} candidates from checkpoint.`);
276+
return new LineAndNumberOfVehiclesGeneration(
277+
candidates,
278+
this,
279+
this.currentIteration
280+
);
281+
}
282+
// Generate or reproduce candidates
283+
const candidates = this.currentIteration === 1 || previousGeneration === undefined
284+
? generateFirstCandidates(this)
285+
: reproduceCandidates(this, previousGeneration.getCandidates(), this.currentIteration)
286+
const currentGeneration = new LineAndNumberOfVehiclesGeneration(
287+
candidates,
288+
this,
289+
this.currentIteration
290+
);
291+
const messages = await currentGeneration.prepareCandidates(this.executorOptions.progressEmitter);
292+
await this.addMessages(messages);
293+
// Add a checkpoint for the current generation
294+
this.job.attributes.internal_data.checkpoint = this.currentIteration;
295+
this.job.attributes.internal_data.currentGeneration = {
296+
candidates: candidates.map((candidate) => ({
297+
chromosome: candidate.getChromosome(),
298+
scenarioId: candidate.getScenario()?.id
299+
}))
300+
}
301+
await this.job.save(this.executorOptions.progressEmitter);
302+
return currentGeneration;
303+
}
304+
261305
private _run = async (
262306

263307
): Promise<boolean> => {
@@ -270,23 +314,13 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
270314

271315
const algorithmResults = this.job.attributes.data.results!;
272316

273-
let candidates: NetworkCandidate[] = [];
274-
let previousGeneration: Generation | undefined = undefined;
275-
// FIXME Add a checkpoint here and cancellation check
317+
let previousGeneration: LineAndNumberOfVehiclesGeneration | undefined = undefined;
276318
try {
277-
// FIXME Recover from checkpoint
278-
while (!this.isFinished()) {
279-
candidates =
280-
this.currentIteration === 1
281-
? generateFirstCandidates(this)
282-
: reproduceCandidates(this, candidates, this.currentIteration);
283-
previousGeneration = new LineAndNumberOfVehiclesGeneration(
284-
candidates,
285-
this,
286-
this.currentIteration
287-
);
288-
const messages = await previousGeneration.prepareCandidates(this.executorOptions.progressEmitter);
289-
await this.addMessages(messages);
319+
do {
320+
previousGeneration = await this._prepareOrResumeGeneration(previousGeneration);
321+
322+
// FIXME Handle checkpointing with simulations that can be long
323+
// Simulate the generation
290324
await previousGeneration.simulate();
291325
// TODO For now, we keep the best of each generation, but we should
292326
// be smarter about it, knowing that the end of the simulation can
@@ -312,7 +346,7 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
312346
this.job.attributes.data.results = algorithmResults;
313347
await this.job.save(this.executorOptions.progressEmitter);
314348
}
315-
}
349+
} while (!this.isFinished() && !this.executorOptions.isCancelled());
316350
} catch(error) {
317351
console.log('error during evolutionary algorithm generations', error);
318352
throw error;
@@ -323,9 +357,7 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
323357
throw new TrError('Evolutionary Algorithm: no generation was done!', 'ALGOGEN001');
324358
}
325359

326-
console.log('Evolutionary transit network design job completed.');
327-
this.job.setCompleted();
328-
await this.job.save(this.executorOptions.progressEmitter);
360+
329361

330362
return true;
331363
};
@@ -335,6 +367,19 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
335367
try {
336368

337369
const result = await this._run();
370+
371+
if (this.executorOptions.isCancelled()) {
372+
console.log('Evolutionary transit network design job cancelled.');
373+
return {
374+
status: 'paused',
375+
warnings: [],
376+
errors: []
377+
};
378+
}
379+
380+
console.log('Evolutionary transit network design job completed.');
381+
this.job.setCompleted();
382+
await this.job.save(this.executorOptions.progressEmitter);
338383

339384
return {
340385
status: 'success',

packages/transition-backend/src/services/networkDesign/transitNetworkDesign/evolutionary/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { AlgorithmConfigurationByType } from 'transition-common/lib/services/net
1010
import { SimulationMethodConfiguration } from 'transition-common/lib/services/networkDesign/transit/simulationMethod';
1111
import { ExecutableJob } from '../../../executableJob/ExecutableJob';
1212
import { ResultSerialization } from '../../../evolutionaryAlgorithm/candidate/Candidate';
13+
import { CandidateChromosome } from '../../../evolutionaryAlgorithm/internalTypes';
1314

1415
export type EvolutionaryTransitNetworkDesignJobParameters = {
1516
transitNetworkDesignParameters: TransitNetworkDesignParameters;
@@ -32,6 +33,13 @@ export type EvolutionaryTransitNetworkDesignJobType = {
3233
populationSize?: number;
3334
dataPrepared?: boolean;
3435
lineServices?: { [lineId: string]: { serviceId: string; numberOfVehicles: number; }[] };
36+
currentGeneration?: {
37+
candidates: {
38+
chromosome: CandidateChromosome;
39+
scenarioId?: string;
40+
fitness?: number;
41+
}[]
42+
}
3543
};
3644
};
3745

0 commit comments

Comments
 (0)