Skip to content

Commit 68130f5

Browse files
committed
networkDesign backend: checkpoint data preparation and recovery
This adds proper data in internal_data to easily recover the services created for the network design job and the line's level of services from the capnp cache.
1 parent b2d58aa commit 68130f5

File tree

2 files changed

+119
-58
lines changed

2 files changed

+119
-58
lines changed

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

Lines changed: 117 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ import Generation from '../../../evolutionaryAlgorithm/generation/Generation';
2424
import * as AlgoTypes from '../../../evolutionaryAlgorithm/internalTypes';
2525
import LineAndNumberOfVehiclesGeneration, { generateFirstCandidates, reproduceCandidates } from '../../../evolutionaryAlgorithm/generation/LineAndNumberOfVehiclesGeneration';
2626
import TrError from 'chaire-lib-common/lib/utils/TrError';
27-
import { collectionToCache as serviceCollectionToCache } from '../../../../models/capnpCache/transitServices.cache.queries';
28-
import { objectsToCache as linesToCache } from '../../../../models/capnpCache/transitLines.cache.queries';
27+
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';
2929
import { prepareServices, saveSimulationScenario } from '../../../evolutionaryAlgorithm/preparation/ServicePreparation';
30+
import Line from 'transition-common/lib/services/line/Line';
3031

3132
/**
3233
* Do batch calculation on a csv file input
@@ -67,6 +68,36 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
6768
this.options = this.parameters.algorithmConfiguration.config;
6869
}
6970

71+
private _getSimulatedLineCollection = (collections: {
72+
lines: LineCollection;
73+
agencies: AgencyCollection;
74+
services: ServiceCollection;
75+
}): LineCollection => {
76+
const {
77+
nonSimulatedServices,
78+
simulatedAgencies,
79+
linesToKeep: linesToKeepParam
80+
} = this.parameters.transitNetworkDesignParameters;
81+
const linesToKeep = linesToKeepParam || [];
82+
83+
const agencies = simulatedAgencies?.map((agencyId) => collections.agencies.getById(agencyId)) || [];
84+
const lines = agencies.flatMap((agency) => (agency ? agency.getLines() : []));
85+
86+
// Sort lines so lines to keep are at the beginning
87+
console.log('Sorting lines...');
88+
lines.sort((lineA, lineB) =>
89+
linesToKeep.includes(lineA.getId()) && linesToKeep.includes(lineB.getId())
90+
? 0
91+
: linesToKeep.includes(lineA.getId())
92+
? -1
93+
: linesToKeep.includes(lineB.getId())
94+
? 1
95+
: 0
96+
);
97+
const simulatedLineCollection = new LineCollection(lines, {});
98+
return simulatedLineCollection;
99+
}
100+
70101
/**
71102
* Prepare the data for the current simulation: Create services for the
72103
* lines to simulate and prepare collections containing only the required
@@ -98,28 +129,8 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
98129
*/
99130
simulatedServices: ServiceCollection;
100131
}> => {
101-
const {
102-
nonSimulatedServices,
103-
simulatedAgencies,
104-
linesToKeep: linesToKeepParam
105-
} = this.parameters.transitNetworkDesignParameters;
106-
const linesToKeep = linesToKeepParam || [];
107-
108-
const agencies = simulatedAgencies?.map((agencyId) => collections.agencies.getById(agencyId)) || [];
109-
const lines = agencies.flatMap((agency) => (agency ? agency.getLines() : []));
110-
111-
// Sort lines so lines to keep are at the beginning
112-
console.log('Sorting lines...');
113-
lines.sort((lineA, lineB) =>
114-
linesToKeep.includes(lineA.getId()) && linesToKeep.includes(lineB.getId())
115-
? 0
116-
: linesToKeep.includes(lineA.getId())
117-
? -1
118-
: linesToKeep.includes(lineB.getId())
119-
? 1
120-
: 0
121-
);
122-
const simulatedLineCollection = new LineCollection(lines, {});
132+
133+
const simulatedLineCollection = this._getSimulatedLineCollection(collections);
123134

124135
// Prepare various services for lines
125136
console.log('Preparing services...');
@@ -146,7 +157,7 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
146157

147158
// Add non simulated services to the collection, ie those used by the simulation, but not generated by it
148159
const existingServices =
149-
nonSimulatedServices
160+
this.parameters.transitNetworkDesignParameters.nonSimulatedServices
150161
?.map((serviceId) => collections.services.getById(serviceId))
151162
.filter((service) => service !== undefined) || [];
152163
existingServices.forEach((service) => services.add(service as Service));
@@ -162,55 +173,103 @@ class EvolutionaryTransitNetworkDesignJobExecutor extends TransitNetworkDesignJo
162173
return this.currentIteration > (this.options.numberOfGenerations || 0);
163174
};
164175

165-
private _run = async (
166-
167-
): Promise<boolean> => {
176+
private _loadAndPrepareData = async() :Promise<void> => {
168177

169-
// Load the necessary data from the server
170-
const jobId = this.job.id;
171-
console.time(`Preparing data for evolutionary transit network design job ${jobId}`);
172-
// FIXME This loads everything! we don't need all that
173-
await this.loadServerData(serviceLocator.socketEventManager);
174-
console.timeEnd(`Preparing data for evolutionary transit network design job ${jobId}`);
178+
// Load the necessary data from the server
179+
const jobId = this.job.id;
180+
console.time(`Preparing data for evolutionary transit network design job ${jobId}`);
181+
// FIXME This loads everything! we don't need all that
182+
await this.loadServerData(serviceLocator.socketEventManager);
183+
console.timeEnd(`Preparing data for evolutionary transit network design job ${jobId}`);
175184

176-
// Prepare the cache data for this job
177-
// FIXME Add checkpoint here
178-
console.time(`Preparing cache directory for job ${jobId}`);
179-
this.prepareCacheDirectory();
180-
console.timeEnd(`Preparing cache directory for job ${jobId}`);
181-
182-
183-
console.time(`Running evolutionary transit network design algorithm for job ${jobId}`);
184-
console.timeEnd(`Running evolutionary transit network design algorithm for job ${jobId}`);
185+
// Prepare the cache data for this job
186+
// FIXME Add checkpoint here
187+
console.time(`Preparing cache directory for job ${jobId}`);
188+
this.prepareCacheDirectory();
189+
console.timeEnd(`Preparing cache directory for job ${jobId}`);
185190

186191
// FIXME Use a seed from the job data?
187192
const randomGenerator = random;
188193
// Get the agencies data
189194

190-
// FIXME Cache preparation and service preparation are linked. There should be a checkpoint after and data reloaded from there
191-
// FIXME2 see if we need to keep anything in checkpoints
192-
// FIXME3 Some of them might be in the class instead
193195
const { simulatedLineCollection, lineServices } =
194196
await this.prepareData({ agencies: this.agencyCollection, lines: this.allLineCollection, services: this.serviceCollection });
195197
this.setLineServices(lineServices);
196198
this.simulatedLineCollection = simulatedLineCollection;
197-
// FIXME There's something to checkpoint here
198199

199200
// Initialize population size if not done yet, as well as results
200-
if (this.job.attributes.internal_data.populationSize === undefined) {
201-
const populationSize = randomInRange(
202-
[this.options.populationSizeMin, this.options.populationSizeMax],
203-
randomGenerator
204-
);
205-
this.job.attributes.internal_data.populationSize = populationSize;
206-
// FIXME Make sure results structure is well defined
207-
const algorithmResults: { generations: ResultSerialization[], scenarioIds: string[] } = { generations: [], scenarioIds: [] };
208-
this.job.attributes.data.results = algorithmResults;
209-
this.job.save(this.executorOptions.progressEmitter);
201+
const populationSize = randomInRange(
202+
[this.options.populationSizeMin, this.options.populationSizeMax],
203+
randomGenerator
204+
);
205+
206+
// Checkoint the data preparation and initialize results
207+
this.job.attributes.internal_data.populationSize = populationSize;
208+
this.job.attributes.internal_data.dataPrepared = true;
209+
this.job.attributes.internal_data.lineServices = Object.keys(lineServices).reduce((acc, lineId) => {
210+
acc[lineId] = lineServices[lineId].map((lvlOfService) => ({
211+
serviceId: lvlOfService.service.getId(),
212+
numberOfVehicles: lvlOfService.numberOfVehicles
213+
}));
214+
return acc;
215+
}, {});
216+
217+
// FIXME Make sure results structure is well defined
218+
const algorithmResults: { generations: ResultSerialization[], scenarioIds: string[] } = { generations: [], scenarioIds: [] };
219+
this.job.attributes.data.results = algorithmResults;
220+
this.job.save(this.executorOptions.progressEmitter);
221+
}
222+
223+
private _loadAndPrepareDataFromCache = async() :Promise<void> => {
224+
// Load the necessary data from the server
225+
const jobId = this.job.id;
226+
console.time(`Preparing data for evolutionary transit network design job from cache ${jobId}`);
227+
// FIXME Do we even need this? Or can we just start recovery at serviceCollectionFromCache call and get only required data?
228+
await this.loadServerData(serviceLocator.socketEventManager);
229+
230+
// Get the simulated lines
231+
const simulatedLineCollection = this._getSimulatedLineCollection({ agencies: this.agencyCollection, lines: this.allLineCollection, services: this.serviceCollection });
232+
this.simulatedLineCollection = simulatedLineCollection;
233+
234+
// Read the services from the cache, with all individual line services
235+
// Save services and lines, with their schedules, to cache
236+
const serviceCollection = await serviceCollectionFromCache(this.getCacheDirectory());
237+
this.serviceCollection.setFeatures(serviceCollection.getFeatures());
238+
// Read each of the simulation line from cache
239+
for (let i = 0; i < simulatedLineCollection.getFeatures().length; i++) {
240+
const line = simulatedLineCollection.getFeatures()[i];
241+
const lineWithSchedule = await lineFromCache(line.getId(), this.getCacheDirectory()) as Line;
242+
simulatedLineCollection.updateFeature(lineWithSchedule);
243+
}
244+
// Recreate the line level of services
245+
const lineServicesSerialized = this.job.attributes.internal_data.lineServices || {};
246+
const lineServices: AlgoTypes.LineServices = Object.keys(lineServicesSerialized).reduce((acc, lineId) => {
247+
acc[lineId] = lineServicesSerialized[lineId].map((lvlOfService) => {
248+
const service = serviceCollection.getById(lvlOfService.serviceId);
249+
if (!service) {
250+
throw new TrError('Service not found in cache while preparing evolutionary transit network design job', 'ALGOCACHE001');
251+
}
252+
return { service, numberOfVehicles: lvlOfService.numberOfVehicles };
253+
});
254+
return acc;
255+
}, {});
256+
this.setLineServices(lineServices);
257+
258+
console.timeEnd(`Preparing data for evolutionary transit network design job from cache ${jobId}`);
259+
}
260+
261+
private _run = async (
262+
263+
): Promise<boolean> => {
264+
// Prepare and load data, either from cache when resuming a started job or from server at first run
265+
if (this.job.attributes.internal_data.dataPrepared !== true) {
266+
await this._loadAndPrepareData();
267+
} else {
268+
await this._loadAndPrepareDataFromCache();
210269
}
270+
211271
const algorithmResults = this.job.attributes.data.results!;
212272

213-
214273
let candidates: NetworkCandidate[] = [];
215274
let previousGeneration: Generation | undefined = undefined;
216275
// FIXME Add a checkpoint here and cancellation check

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export type EvolutionaryTransitNetworkDesignJobType = {
3030
files: { transitDemand: true; nodeWeight: true };
3131
internal_data: {
3232
populationSize?: number;
33+
dataPrepared?: boolean;
34+
lineServices?: { [lineId: string]: { serviceId: string; numberOfVehicles: number; }[] };
3335
};
3436
};
3537

0 commit comments

Comments
 (0)