@@ -22,12 +22,14 @@ import { ResultSerialization } from '../../../evolutionaryAlgorithm/candidate/Ca
2222import NetworkCandidate from '../../../evolutionaryAlgorithm/candidate/LineAndNumberOfVehiclesNetworkCandidate' ;
2323import Generation from '../../../evolutionaryAlgorithm/generation/Generation' ;
2424import * as AlgoTypes from '../../../evolutionaryAlgorithm/internalTypes' ;
25- import LineAndNumberOfVehiclesGeneration , { generateFirstCandidates , reproduceCandidates } from '../../../evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration' ;
25+ import LineAndNumberOfVehiclesGeneration , { generateFirstCandidates , reproduceCandidates , resumeCandidatesFromChromosomes } from '../../../evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration' ;
2626import TrError from 'chaire-lib-common/lib/utils/TrError' ;
2727import { 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' ;
2930import { prepareServices , saveSimulationScenario } from '../../../evolutionaryAlgorithm/preparation/ServicePreparation' ;
3031import 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' ,
0 commit comments