Updated worker scripts structure.

Created a `WorkerEnvironment` class to contain the scripts
in the dedicated workers.
This commit is contained in:
Krafpy
2021-08-20 16:38:29 +02:00
parent b405bc4675
commit 4cfdce73e4
12 changed files with 373 additions and 333 deletions

View File

@@ -1,9 +1,11 @@
"use strict";
let onWorkerInitialize = () => { };
let onWorkerRun = () => { };
let onWorkerContinue = () => { };
let onWorkerStop = () => { };
let onWorkerDataPass = () => { };
class WorkerEnvironment {
onWorkerInitialize(data) { }
onWorkerRun(input) { }
onWorkerContinue(input) { }
onWorkerStop() { }
onWorkerDataPass(data) { }
}
function postMessageSafe(msg) {
postMessage(msg);
}
@@ -16,25 +18,28 @@ function debug(...data) {
function sendResult(result) {
postMessageSafe({ label: "complete", result: result });
}
onmessage = ({ data }) => {
switch (data.label) {
case "initialize":
onWorkerInitialize(data.config);
postMessageSafe({ label: "initialized" });
break;
case "run":
onWorkerRun(data.input);
break;
case "continue":
onWorkerContinue();
break;
case "stop":
onWorkerStop();
postMessageSafe({ label: "stopped" });
break;
case "pass":
onWorkerDataPass(data.data);
postMessageSafe({ label: "received" });
break;
}
};
function initWorker(Env) {
const env = new Env();
onmessage = ({ data }) => {
switch (data.label) {
case "initialize":
env.onWorkerInitialize(data.config);
postMessageSafe({ label: "initialized" });
break;
case "run":
env.onWorkerRun(data.input);
break;
case "continue":
env.onWorkerContinue();
break;
case "stop":
env.onWorkerStop();
postMessageSafe({ label: "stopped" });
break;
case "pass":
env.onWorkerDataPass(data.data);
postMessageSafe({ label: "received" });
break;
}
};
}

View File

@@ -13,7 +13,7 @@ function getRelativeVelocity2D(globalVel, bodyVel) {
function getGlobalVelocity2D(relativeVel, bodyVel) {
return add2(relativeVel, bodyVel);
}
function calculateNextIntersection(pos0, vel0, body, attractor, config) {
function calculateNextIntersection(pos0, vel0, body, attractor) {
const mu = attractor.stdGravParam;
const v0 = mag2(vel0);
const r0 = mag2(pos0);

View File

@@ -1,30 +1,36 @@
"use strict";
{
importScripts("libs/common.js", "libs/math.js", "libs/physics.js", "libs/physics-2d.js");
let system;
let config;
let current = null;
onWorkerInitialize = data => {
system = data.system;
config = data.config;
};
onWorkerRun = input => {
current = evaluateInChunks(input);
importScripts("libs/common.js", "libs/math.js", "libs/physics.js", "libs/physics-2d.js");
class SequenceEvaluator extends WorkerEnvironment {
constructor() {
super(...arguments);
this._current = null;
}
onWorkerInitialize(data) {
this._system = data.system;
this._config = data.config;
}
onWorkerRun(input) {
this._sequences = input;
const { orbiting } = this._system[this._sequences[0][0]];
this._attractor = this._system[orbiting];
this._current = this._evaluateSequenceChunks();
sendProgress(0);
};
onWorkerContinue = () => {
if (!current)
}
onWorkerContinue() {
if (!this._current)
return;
const { done, value } = current.next();
const { done, value } = this._current.next();
if (done)
sendResult(value);
};
onWorkerStop = () => current = null;
function* evaluateInChunks(sequences) {
}
onWorkerStop() {
this._current = null;
}
*_evaluateSequenceChunks() {
const results = [];
const { progressStep } = config.workers;
for (let i = 0; i < sequences.length; i++) {
results.push(evaluateSequence(sequences[i]));
const { progressStep } = this._config.workers;
for (let i = 0; i < this._sequences.length; i++) {
results.push(this._evaluateSequence(this._sequences[i]));
if (i % progressStep == 0) {
sendProgress(i);
yield;
@@ -32,17 +38,16 @@
}
return results;
}
function evaluateSequence(sequence) {
const bodies = getSequenceBodies(sequence);
const attractor = system[bodies[0].orbiting];
_evaluateSequence(sequence) {
const bodies = this._bodySequenceOf(sequence);
const depDeltaV = hohmannTransferDeltaV(bodies[0], bodies[1]);
if (bodies.length == 2) {
const depVel = depDeltaV + bodies[0].circularVel;
const relativeArrVel = hohmannEncounterRelativeVel(bodies[0], bodies[1], depVel, attractor);
const relativeArrVel = hohmannEncounterRelativeVel(bodies[0], bodies[1], depVel, this._attractor);
return Math.abs(depDeltaV) + Math.abs(relativeArrVel);
}
const statuses = generateInitialStatuses(bodies[0], depDeltaV);
const { maxEvalStatuses, radiusSamples } = config.flybySequence;
const statuses = this._generateInitialStatuses(bodies[0], depDeltaV);
const { maxEvalStatuses, radiusSamples } = this._config.flybySequence;
let evalCount = 0;
while (statuses.length > 0) {
evalCount++;
@@ -51,7 +56,7 @@
}
const status = statuses.pop();
const targetBody = bodies[status.target];
const itsc = calculateNextIntersection(status.pos, status.vel, targetBody, attractor, config);
const itsc = calculateNextIntersection(status.pos, status.vel, targetBody, this._attractor);
if (itsc) {
const targetBodyVel = getBodyVelocity2D(itsc.pos, targetBody);
const arrivalVel = getRelativeVelocity2D(itsc.vel, targetBodyVel);
@@ -75,16 +80,16 @@
}
}
}
function getSequenceBodies(sequence) {
_bodySequenceOf(sequence) {
const bodies = [];
for (const id of sequence) {
bodies.push(system[id]);
bodies.push(this._system[id]);
}
return bodies;
}
function generateInitialStatuses(depBody, depDeltaV) {
_generateInitialStatuses(depBody, depDeltaV) {
const statuses = [];
const { initVelMaxScale, initVelSamples } = config.flybySequence;
const { initVelMaxScale, initVelSamples } = this._config.flybySequence;
for (let i = 0; i < initVelSamples; ++i) {
const scale = (i / initVelSamples) * initVelMaxScale;
statuses.push({
@@ -96,3 +101,4 @@
return statuses;
}
}
initWorker(SequenceEvaluator);

View File

@@ -1,34 +1,39 @@
"use strict";
{
importScripts("libs/common.js");
let config;
onWorkerInitialize = data => config = data;
onWorkerRun = input => {
importScripts("libs/common.js");
class SequenceGenerator extends WorkerEnvironment {
onWorkerInitialize(data) {
this._config = data;
}
onWorkerRun(input) {
const { bodies, params } = input;
const feasible = generateFeasibleSet(bodies, params);
this._bodies = bodies;
this._params = params;
this._getRelativePositions();
const feasible = this._generateFeasibleSet();
sendResult(feasible);
};
function generateFeasibleSet(allowedBodies, params) {
}
_generateFeasibleSet() {
let incFeasibleSet = [{
sequence: [params.departureId],
sequence: [this._params.departureId],
resonant: 0,
backLegs: 0
backLegs: 0,
backSpacingExceeded: false
}];
let tempSet = [];
const feasibleSet = [];
const relativePos = getRelativePositions(allowedBodies);
const feasible = (seq) => checkSequenceFeasibility(seq, relativePos, params);
const { maxEvalSequences } = config.flybySequence;
const { maxEvalSequences } = this._config.flybySequence;
outerloop: while (incFeasibleSet.length > 0) {
for (const incSeq of incFeasibleSet) {
for (const bodyId of allowedBodies) {
for (const bodyId of this._bodies) {
const tempSeq = {
sequence: [...incSeq.sequence, bodyId],
resonant: incSeq.resonant,
backLegs: incSeq.backLegs
backLegs: incSeq.backLegs,
backSpacingExceeded: false
};
if (feasible(tempSeq)) {
if (bodyId == params.destinationId) {
this._updateSequenceInfo(tempSeq);
if (this._isSequenceFeasible(tempSeq)) {
if (bodyId == this._params.destinationId) {
feasibleSet.push(tempSeq.sequence);
if (feasibleSet.length >= maxEvalSequences)
break outerloop;
@@ -44,31 +49,36 @@
}
return feasibleSet;
}
function getRelativePositions(allowedBodies) {
const relativePos = new Map();
for (let i = 0; i < allowedBodies.length; ++i) {
relativePos.set(allowedBodies[i], i);
_getRelativePositions() {
const maxId = Math.max(...this._bodies);
const relativePos = Array(maxId + 1).fill(0);
for (let i = 0; i < this._bodies.length; ++i) {
relativePos[this._bodies[i]] = i;
}
return relativePos;
this._relativePos = relativePos;
}
function checkSequenceFeasibility(seq, relativePos, params) {
const numSwingBys = seq.sequence.length - 2;
_isSequenceFeasible(info) {
const params = this._params;
const numSwingBys = info.sequence.length - 2;
if (numSwingBys > params.maxSwingBys)
return false;
const posCurr = relativePos.get(seq.sequence[seq.sequence.length - 1]);
const posPrev = relativePos.get(seq.sequence[seq.sequence.length - 2]);
const toHigherOrbit = params.destinationId > params.departureId;
if (isBackLeg(posCurr, posPrev, toHigherOrbit)) {
const jumpSpacing = Math.abs(posPrev - posCurr);
if (jumpSpacing > params.maxBackSpacing)
return false;
seq.backLegs++;
}
if (posCurr == posPrev)
seq.resonant++;
return seq.resonant <= params.maxResonant && seq.backLegs <= params.maxBackLegs;
const resonancesOk = info.resonant <= this._params.maxResonant;
const backLegsOk = info.backLegs <= this._params.maxBackLegs;
return resonancesOk && backLegsOk && !info.backSpacingExceeded;
}
function isBackLeg(posCurr, posPrev, toHigherOrbit) {
return (toHigherOrbit && posCurr < posPrev) || (!toHigherOrbit && posCurr > posPrev);
_updateSequenceInfo(info) {
const params = this._params;
const { sequence } = info;
const posCurr = this._relativePos[sequence[sequence.length - 1]];
const posPrev = this._relativePos[sequence[sequence.length - 2]];
const toHigherOrbit = params.destinationId > params.departureId;
const isBackLeg = (toHigherOrbit && posCurr < posPrev) || (!toHigherOrbit && posCurr > posPrev);
const spacing = Math.abs(posCurr - posPrev);
info.backSpacingExceeded = isBackLeg && spacing > params.maxBackSpacing;
if (isBackLeg)
info.backLegs++;
if (posCurr == posPrev)
info.resonant++;
}
}
initWorker(SequenceGenerator);

View File

@@ -1,85 +1,78 @@
"use strict";
{
importScripts("libs/common.js", "libs/evolution.js", "libs/math.js", "libs/physics.js", "libs/physics-3d.js", "libs/lambert.js", "libs/trajectory-calculator.js");
let config;
let system;
let depAltitude;
let sequence;
let startDateMin;
let startDateMax;
let bestDeltaV;
let bestSteps;
onWorkerInitialize = data => {
config = data.config;
system = data.system;
};
onWorkerDataPass = data => {
depAltitude = data.depAltitude;
sequence = data.sequence;
startDateMin = data.startDateMin;
startDateMax = data.startDateMax;
};
onWorkerRun = input => {
importScripts("libs/common.js", "libs/evolution.js", "libs/math.js", "libs/physics.js", "libs/physics-3d.js", "libs/lambert.js", "libs/trajectory-calculator.js");
class TrajectoryOptimizer extends WorkerEnvironment {
onWorkerInitialize(data) {
this._config = data.config;
this._system = data.system;
}
onWorkerDataPass(data) {
this._depAltitude = data.depAltitude;
this._sequence = data.sequence;
this._startDateMin = data.startDateMin;
this._startDateMax = data.startDateMax;
}
onWorkerRun(input) {
const evaluate = (params) => this._evaluate(params);
if (input.start) {
bestDeltaV = Infinity;
this._bestDeltaV = Infinity;
const chunkSize = input.chunkEnd - input.chunkStart + 1;
const popChunk = createRandomMGAPopulationChunk(chunkSize);
const popChunk = this._createRandomMGAPopulationChunk(chunkSize);
const dvChunk = populationChunkFitness(popChunk, evaluate);
sendResult({
bestSteps: bestSteps,
bestDeltaV: bestDeltaV,
bestSteps: this._bestSteps,
bestDeltaV: this._bestDeltaV,
fitChunk: dvChunk,
popChunk: popChunk,
});
}
else {
const { crossoverProba, diffWeight } = config.trajectorySearch;
const { crossoverProba, diffWeight } = this._config.trajectorySearch;
const results = evolvePopulationChunk(input.population, input.deltaVs, input.chunkStart, input.chunkEnd, crossoverProba, diffWeight, evaluate);
sendResult({
...results,
bestSteps: bestSteps,
bestDeltaV: bestDeltaV
bestSteps: this._bestSteps,
bestDeltaV: this._bestDeltaV
});
}
};
function evaluate(params) {
const trajectory = computeTrajectory(params);
if (trajectory.totalDeltaV < bestDeltaV) {
bestDeltaV = trajectory.totalDeltaV;
bestSteps = trajectory.steps;
}
_evaluate(params) {
const trajectory = this._computeTrajectory(params);
if (trajectory.totalDeltaV < this._bestDeltaV) {
this._bestDeltaV = trajectory.totalDeltaV;
this._bestSteps = trajectory.steps;
}
return trajectory.totalDeltaV;
}
;
function computeTrajectory(params) {
_computeTrajectory(params) {
const calculate = () => {
const trajectory = new TrajectoryCalculator(system, config.trajectorySearch, sequence);
trajectory.setParameters(depAltitude, startDateMin, startDateMax, params);
const trajectory = new TrajectoryCalculator(this._system, this._config.trajectorySearch, this._sequence);
trajectory.setParameters(this._depAltitude, this._startDateMin, this._startDateMax, params);
trajectory.compute();
return trajectory;
};
let trajectory = calculate();
while (!trajectory.noError) {
randomizeExistingAgent(params);
this._randomizeExistingAgent(params);
trajectory = calculate();
}
return trajectory;
}
function createRandomMGAPopulationChunk(chunkSize) {
_createRandomMGAPopulationChunk(chunkSize) {
const popChunk = [];
for (let i = 0; i < chunkSize; i++) {
popChunk.push(createRandomMGAAgent());
popChunk.push(this._createRandomMGAAgent());
}
return popChunk;
}
function createRandomMGAAgent() {
const dim = 4 * (sequence.length - 1) + 2;
_createRandomMGAAgent() {
const dim = 4 * (this._sequence.length - 1) + 2;
const solution = Array(dim).fill(0);
solution[0] = randomInInterval(startDateMin, startDateMax);
solution[0] = randomInInterval(this._startDateMin, this._startDateMax);
solution[1] = randomInInterval(1, 3);
for (let i = 1; i < sequence.length; i++) {
for (let i = 1; i < this._sequence.length; i++) {
const j = 2 + (i - 1) * 4;
const { legDuration, dsmOffset } = legDurationAndDSM(i);
const { legDuration, dsmOffset } = this._legDurationAndDSM(i);
solution[j] = legDuration;
solution[j + 1] = dsmOffset;
const { theta, phi } = randomPointOnSphereRing(0, Math.PI / 2);
@@ -88,17 +81,17 @@
}
return solution;
}
function randomizeExistingAgent(agent) {
const newAgent = createRandomMGAAgent();
_randomizeExistingAgent(agent) {
const newAgent = this._createRandomMGAAgent();
for (let i = 0; i < agent.length; i++) {
agent[i] = newAgent[i];
}
}
function legDurationAndDSM(index) {
const isResonant = sequence[index - 1] == sequence[index];
const { dsmOffsetMin, dsmOffsetMax } = config.trajectorySearch;
_legDurationAndDSM(index) {
const isResonant = this._sequence[index - 1] == this._sequence[index];
const { dsmOffsetMin, dsmOffsetMax } = this._config.trajectorySearch;
if (isResonant) {
const sideralPeriod = system[sequence[index]].orbit.sideralPeriod;
const sideralPeriod = this._system[this._sequence[index]].orbit.sideralPeriod;
const revs = randomInInterval(1, 4);
const legDuration = revs * sideralPeriod;
const minOffset = clamp((revs - 1) / revs, dsmOffsetMin, dsmOffsetMax);
@@ -106,9 +99,9 @@
return { legDuration, dsmOffset };
}
else {
const body1 = system[sequence[index - 1]];
const body2 = system[sequence[index]];
const attractor = system[body1.orbiting];
const body1 = this._system[this._sequence[index - 1]];
const body2 = this._system[this._sequence[index]];
const attractor = this._system[body1.orbiting];
const period = getHohmannPeriod(body1, body2, attractor);
const legDuration = randomInInterval(0.1, 1) * period;
const dsmOffset = randomInInterval(dsmOffsetMin, dsmOffsetMax);
@@ -116,3 +109,4 @@
}
}
}
initWorker(TrajectoryOptimizer);

View File

@@ -1,8 +1,10 @@
let onWorkerInitialize: (data: any) => void = () => {};
let onWorkerRun: (input?: any) => void = () => {};
let onWorkerContinue: (input?: any) => void = () => {};
let onWorkerStop: () => void = () => {};
let onWorkerDataPass: (data: any) => void = () => {};
class WorkerEnvironment {
public onWorkerInitialize(data: any) {}
public onWorkerRun(input?: any) {}
public onWorkerContinue(input?: any) {}
public onWorkerStop() {}
public onWorkerDataPass(data: any) {}
}
function postMessageSafe(msg: MessageFromWorker) {
postMessage(msg);
@@ -20,25 +22,28 @@ function sendResult(result: any) {
postMessageSafe({label: "complete", result: result});
}
onmessage = ({data}: MessageEvent<MessageToWorker>) => {
switch(data.label) {
case "initialize":
onWorkerInitialize(data.config);
postMessageSafe({label: "initialized"});
break;
case "run":
onWorkerRun(data.input);
break;
case "continue":
onWorkerContinue();
break;
case "stop":
onWorkerStop();
postMessageSafe({label: "stopped"});
break;
case "pass":
onWorkerDataPass(data.data);
postMessageSafe({label: "received"});
break;
function initWorker(Env: typeof WorkerEnvironment){
const env = new Env();
onmessage = ({data}: MessageEvent<MessageToWorker>) => {
switch(data.label) {
case "initialize":
env.onWorkerInitialize(data.config);
postMessageSafe({label: "initialized"});
break;
case "run":
env.onWorkerRun(data.input);
break;
case "continue":
env.onWorkerContinue();
break;
case "stop":
env.onWorkerStop();
postMessageSafe({label: "stopped"});
break;
case "pass":
env.onWorkerDataPass(data.data);
postMessageSafe({label: "received"});
break;
}
}
};
}

View File

@@ -1,5 +1,5 @@
/**
* @param popChunk The populatio chunk
* @param popChunk The population chunk
* @param fitness The fitness function
* @returns The fitness value for each agent in the chunk.
*/

View File

@@ -22,7 +22,7 @@ function getGlobalVelocity2D(relativeVel: Vector2, bodyVel: Vector2) {
* @param config The configuration data
* @returns The velocity and positon when intersecting the body's orbit, or undefined if no intersection
*/
function calculateNextIntersection(pos0: Vector2, vel0: Vector2, body: IOrbitingBody, attractor: ICelestialBody, config: Config)
function calculateNextIntersection(pos0: Vector2, vel0: Vector2, body: IOrbitingBody, attractor: ICelestialBody)
: {pos: Vector2, vel: Vector2} | undefined {
const mu = attractor.stdGravParam;

View File

@@ -1,40 +1,50 @@
{ // <- hack to make typescript consider file scope only
// Mozilla doesn't support module workers :(
importScripts("libs/common.js", "libs/math.js", "libs/physics.js", "libs/physics-2d.js");
// Mozilla doesn't support module workers :(
importScripts("libs/common.js", "libs/math.js", "libs/physics.js", "libs/physics-2d.js");
class SequenceEvaluator extends WorkerEnvironment {
private _system!: IOrbitingBody[];
private _config!: Config;
let system: IOrbitingBody[];
let config: Config;
let current: Generator<undefined, (number | undefined)[], unknown> | null = null;
private _sequences!: number[][];
private _current: Generator<undefined, (number | undefined)[], unknown> | null = null;
private _attractor!: ICelestialBody;
onWorkerInitialize = data => {
system = data.system;
config = data.config;
};
override onWorkerInitialize(data: any){
this._system = data.system;
this._config = data.config;
}
onWorkerRun = input => {
current = evaluateInChunks(input as number[][]);
override onWorkerRun(input: any){
this._sequences = input;
const {orbiting} = this._system[this._sequences[0][0]];
this._attractor = this._system[orbiting];
this._current = this._evaluateSequenceChunks();
sendProgress(0);
};
}
onWorkerContinue = () => {
if(!current) return;
const {done, value} = current.next();
override onWorkerContinue(){
if(!this._current) return;
const {done, value} = this._current.next();
if(done) sendResult(value);
};
}
onWorkerStop = () => current = null;
override onWorkerStop(){
this._current = null;
}
/**
* Calculates the cost of each provided sequences and pauses regularly to send progression.
* @param sequences The list of sequences to evaluate
* @returns The cost of each sequences.
*/
function* evaluateInChunks(sequences: number[][]){
private *_evaluateSequenceChunks(){
const results = [];
const {progressStep} = config.workers;
for(let i = 0; i < sequences.length; i++){
results.push(evaluateSequence(sequences[i]));
const {progressStep} = this._config.workers;
for(let i = 0; i < this._sequences.length; i++){
results.push(this._evaluateSequence(this._sequences[i]));
if(i % progressStep == 0) {
sendProgress(i);
yield;
@@ -49,22 +59,22 @@
* @param sequence The sequence to evaluate
* @returns The estimated cost in velocity of the sequence
*/
function evaluateSequence(sequence: number[]){
const bodies = getSequenceBodies(sequence);
const attractor = system[bodies[0].orbiting];
private _evaluateSequence(sequence: number[]){
const bodies = this._bodySequenceOf(sequence);
// Calculate departure deltaV, considering simple Hohmann transfer
const depDeltaV = hohmannTransferDeltaV(bodies[0], bodies[1]);
if(bodies.length == 2) {
// Calculate relative velocity at encounter with the arrival body after transfer
const depVel = depDeltaV + bodies[0].circularVel;
const relativeArrVel = hohmannEncounterRelativeVel(bodies[0], bodies[1], depVel, attractor);
const relativeArrVel = hohmannEncounterRelativeVel(bodies[0], bodies[1], depVel, this._attractor);
return Math.abs(depDeltaV) + Math.abs(relativeArrVel);
}
const statuses = generateInitialStatuses(bodies[0], depDeltaV);
const statuses = this._generateInitialStatuses(bodies[0], depDeltaV);
const {maxEvalStatuses, radiusSamples} = config.flybySequence;
const {maxEvalStatuses, radiusSamples} = this._config.flybySequence;
let evalCount = 0;
while(statuses.length > 0) {
@@ -76,7 +86,7 @@
const status = statuses.pop() as OrbitalState2D;
const targetBody = bodies[status.target];
const itsc = calculateNextIntersection(status.pos, status.vel, targetBody, attractor, config);
const itsc = calculateNextIntersection(status.pos, status.vel, targetBody, this._attractor);
if(itsc) {
const targetBodyVel = getBodyVelocity2D(itsc.pos, targetBody);
const arrivalVel = getRelativeVelocity2D(itsc.vel, targetBodyVel);
@@ -105,16 +115,15 @@
}
}
/**
/**
*
* @param sequence The sequence to get the bodies from
* @returns The corresponding sequence of bodies.
*/
function getSequenceBodies(sequence: number[]) {
// We suppose the sequence bodies are orbiting around the same attractor
private _bodySequenceOf(sequence: number[]) {
const bodies: IOrbitingBody[] = [];
for(const id of sequence){
bodies.push(system[id]);
bodies.push(this._system[id]);
}
return bodies;
}
@@ -125,10 +134,10 @@
* @param depDeltaV The departure deltaV for direct Hohmann transfer
* @returns The initial statuses to start the search from.
*/
function generateInitialStatuses(depBody: IOrbitingBody, depDeltaV: number){
private _generateInitialStatuses(depBody: IOrbitingBody, depDeltaV: number){
const statuses: OrbitalState2D[] = [];
const {initVelMaxScale, initVelSamples} = config.flybySequence;
const {initVelMaxScale, initVelSamples} = this._config.flybySequence;
for(let i = 0; i < initVelSamples; ++i) {
const scale = (i / initVelSamples) * initVelMaxScale;
statuses.push({
@@ -140,4 +149,6 @@
return statuses;
}
}
}
initWorker(SequenceEvaluator);

View File

@@ -1,48 +1,53 @@
{
importScripts("libs/common.js");
let config: Config;
importScripts("libs/common.js");
onWorkerInitialize = data => config = data;
class SequenceGenerator extends WorkerEnvironment {
private _config!: Config;
private _bodies!: number[];
private _params!: SequenceParameters;
private _relativePos!: number[];
onWorkerRun = input => {
override onWorkerInitialize(data: any){
this._config = data;
}
override onWorkerRun(input: any){
const {bodies, params} = input;
const feasible = generateFeasibleSet(bodies, params);
this._bodies = bodies;
this._params = params;
this._getRelativePositions();
const feasible = this._generateFeasibleSet();
sendResult(feasible);
}
/**
* Returns the feasible set of body sequences, i.e a list of body ids sequences that respect
* the constraints of the user settings.
* @param allowedBodies Bodies reachable according to the user settings
* @param params Parameters of the sequences to gener
* @returns The feasible set
*/
function generateFeasibleSet(allowedBodies: number[], params: SequenceParameters){
private _generateFeasibleSet(){
let incFeasibleSet: GeneratingSequence[] = [{
sequence: [params.departureId],
sequence: [this._params.departureId],
resonant: 0,
backLegs: 0
backLegs: 0,
backSpacingExceeded: false
}];
let tempSet: GeneratingSequence[] = [];
const feasibleSet: number[][] = [];
const relativePos = getRelativePositions(allowedBodies);
const feasible = (seq: GeneratingSequence) => checkSequenceFeasibility(seq, relativePos, params);
const {maxEvalSequences} = config.flybySequence;
const {maxEvalSequences} = this._config.flybySequence;
outerloop:
while(incFeasibleSet.length > 0){
for(const incSeq of incFeasibleSet){
for(const bodyId of allowedBodies){
for(const bodyId of this._bodies){
const tempSeq = {
sequence: [...incSeq.sequence, bodyId],
resonant: incSeq.resonant,
backLegs: incSeq.backLegs
backLegs: incSeq.backLegs,
backSpacingExceeded: false
};
if(feasible(tempSeq)) {
if(bodyId == params.destinationId) {
this._updateSequenceInfo(tempSeq);
if(this._isSequenceFeasible(tempSeq)) {
if(bodyId == this._params.destinationId) {
feasibleSet.push(tempSeq.sequence);
if(feasibleSet.length >= maxEvalSequences)
break outerloop;
@@ -60,57 +65,56 @@
}
/**
* Returns a mapping to the relative position/order of bodies from the attractator.
* @param allowedBodies Bodies allowed to be reached in the sequence, ordered according to their radiuses to the attractor
* @returns The mapping to relative positions.
* Calculates a mapping to the relative position/order of bodies from the attractator.
*/
function getRelativePositions(allowedBodies: number[]){
private _getRelativePositions(){
// We suppose that the bodies are sorted according to their semimajor axis radius
const relativePos: Map<number, number> = new Map();
for(let i = 0; i < allowedBodies.length; ++i){
relativePos.set(allowedBodies[i], i);
const maxId = Math.max(...this._bodies);
const relativePos = Array<number>(maxId + 1).fill(0);
for(let i = 0; i < this._bodies.length; ++i){
relativePos[this._bodies[i]] = i;
}
return relativePos;
this._relativePos = relativePos;
}
/**
* Checks if a (non fully) generated sequence respects the user contraints and updates the sequence informations.
* @param seq A (non fully) generated sequence to check
* @param relativePos The mapping of bodies' relative positions to the attractor
* @param params The parameters constraining the generated sequences
* Checks if a (non fully) generated sequence respects the user contraints.
* @param info A (non fully) generated sequence to check
* @returns Whether the sequence respects the constraints or not.
*/
function checkSequenceFeasibility(seq: GeneratingSequence, relativePos: Map<number, number>, params: SequenceParameters) {
const numSwingBys = seq.sequence.length - 2;
private _isSequenceFeasible(info: GeneratingSequence) {
const params = this._params;
const numSwingBys = info.sequence.length - 2;
if(numSwingBys > params.maxSwingBys)
return false;
const posCurr = <number>relativePos.get(seq.sequence[seq.sequence.length - 1]);
const posPrev = <number>relativePos.get(seq.sequence[seq.sequence.length - 2]);
const toHigherOrbit = params.destinationId > params.departureId;
if(isBackLeg(posCurr, posPrev, toHigherOrbit)) {
const jumpSpacing = Math.abs(posPrev - posCurr);
if(jumpSpacing > params.maxBackSpacing)
return false;
seq.backLegs++;
}
if(posCurr == posPrev)
seq.resonant++;
return seq.resonant <= params.maxResonant && seq.backLegs <= params.maxBackLegs;
const resonancesOk = info.resonant <= this._params.maxResonant;
const backLegsOk = info.backLegs <= this._params.maxBackLegs;
return resonancesOk && backLegsOk && !info.backSpacingExceeded;
}
/**
* Returns if a given leg is a back leg according to departure and destination bodies.
* @param posCurr The relative position of the body reached at the end of the leg
* @param posPrev The relative position of the starting body of the leg
* @param toHigherOrbit Whether the destination body has a higher orbit than the departure body
* @returns Whether this leg is a back leg or not.
* Updates the informations about a generating sequence which received another step.
* @param info The (non fully) generated sequence to update
*/
function isBackLeg(posCurr: number, posPrev: number, toHigherOrbit: boolean){
return (toHigherOrbit && posCurr < posPrev) || (!toHigherOrbit && posCurr > posPrev);
private _updateSequenceInfo(info: GeneratingSequence) {
const params = this._params;
const {sequence} = info;
const posCurr = this._relativePos[sequence[sequence.length - 1]];
const posPrev = this._relativePos[sequence[sequence.length - 2]];
const toHigherOrbit = params.destinationId > params.departureId;
const isBackLeg = (toHigherOrbit && posCurr < posPrev) || (!toHigherOrbit && posCurr > posPrev);
const spacing = Math.abs(posCurr - posPrev);
info.backSpacingExceeded = isBackLeg && spacing > params.maxBackSpacing;
if(isBackLeg) info.backLegs++;
if(posCurr == posPrev) info.resonant++;
}
}
}
initWorker(SequenceGenerator);

View File

@@ -1,43 +1,46 @@
{
importScripts("libs/common.js", "libs/evolution.js", "libs/math.js", "libs/physics.js", "libs/physics-3d.js", "libs/lambert.js", "libs/trajectory-calculator.js");
importScripts("libs/common.js", "libs/evolution.js", "libs/math.js", "libs/physics.js", "libs/physics-3d.js", "libs/lambert.js", "libs/trajectory-calculator.js");
let config: Config;
let system: IOrbitingBody[];
let depAltitude: number;
let sequence: number[];
let startDateMin: number;
let startDateMax: number;
class TrajectoryOptimizer extends WorkerEnvironment {
private _config!: Config;
private _system!: IOrbitingBody[];
let bestDeltaV: number;
let bestSteps: TrajectoryStep[];
private _depAltitude!: number;
private _sequence!: number[];
private _startDateMin!: number;
private _startDateMax!: number;
onWorkerInitialize = data => {
config = data.config;
system = data.system;
};
private _bestDeltaV!: number;
private _bestSteps!: TrajectoryStep[];
onWorkerDataPass = data => {
depAltitude = data.depAltitude;
sequence = data.sequence;
startDateMin = data.startDateMin;
startDateMax = data.startDateMax;
};
override onWorkerInitialize(data: any){
this._config = data.config;
this._system = data.system;
}
override onWorkerDataPass(data: any){
this._depAltitude = data.depAltitude;
this._sequence = data.sequence;
this._startDateMin = data.startDateMin;
this._startDateMax = data.startDateMax;
}
override onWorkerRun(input: any){
const evaluate = (params: Agent) => this._evaluate(params);
onWorkerRun = input => {
if(input.start) {
bestDeltaV = Infinity;
this._bestDeltaV = Infinity;
const chunkSize = input.chunkEnd - input.chunkStart + 1;
const popChunk = createRandomMGAPopulationChunk(chunkSize);
const popChunk = this._createRandomMGAPopulationChunk(chunkSize);
const dvChunk = populationChunkFitness(popChunk, evaluate);
sendResult({
bestSteps: bestSteps,
bestDeltaV: bestDeltaV,
bestSteps: this._bestSteps,
bestDeltaV: this._bestDeltaV,
fitChunk: dvChunk,
popChunk: popChunk,
});
} else {
const {crossoverProba, diffWeight} = config.trajectorySearch;
const {crossoverProba, diffWeight} = this._config.trajectorySearch;
const results = evolvePopulationChunk(
input.population,
input.deltaVs,
@@ -47,24 +50,23 @@
);
sendResult({
...results,
bestSteps: bestSteps,
bestDeltaV: bestDeltaV
bestSteps: this._bestSteps,
bestDeltaV: this._bestDeltaV
});
}
};
}
/**
* Evaluates a trajectory cost and stores it if it's the best found so far.
* @param params A trajectory paremeters (agent)
* @returns The fitness (total deltaV) of the trajectory, used for the DE algorithm.
*/
function evaluate(params: Agent) {
const trajectory = computeTrajectory(params);
if(trajectory.totalDeltaV < bestDeltaV){
bestDeltaV = trajectory.totalDeltaV;
bestSteps = trajectory.steps;
private _evaluate(params: Agent) {
const trajectory = this._computeTrajectory(params);
if(trajectory.totalDeltaV < this._bestDeltaV){
this._bestDeltaV = trajectory.totalDeltaV;
this._bestSteps = trajectory.steps;
}
return trajectory.totalDeltaV;
};
@@ -75,16 +77,16 @@
* @param params A trajectory paremeters (agent)
* @returns The calculated trajectory
*/
function computeTrajectory(params: Agent){
private _computeTrajectory(params: Agent){
const calculate = () => {
const trajectory = new TrajectoryCalculator(system, config.trajectorySearch, sequence);
trajectory.setParameters(depAltitude, startDateMin, startDateMax, params);
const trajectory = new TrajectoryCalculator(this._system, this._config.trajectorySearch, this._sequence);
trajectory.setParameters(this._depAltitude, this._startDateMin, this._startDateMax, params);
trajectory.compute();
return trajectory;
}
let trajectory = calculate();
while(!trajectory.noError) {
randomizeExistingAgent(params);
this._randomizeExistingAgent(params);
trajectory = calculate();
}
return trajectory;
@@ -96,10 +98,10 @@
* @param chunkSize The size of the population chunk to generate
* @returns A random population chunk.
*/
function createRandomMGAPopulationChunk(chunkSize: number, ){
private _createRandomMGAPopulationChunk(chunkSize: number, ){
const popChunk: Agent[] = [];
for(let i = 0; i < chunkSize; i++) {
popChunk.push(createRandomMGAAgent());
popChunk.push(this._createRandomMGAAgent());
}
return popChunk;
}
@@ -108,19 +110,19 @@
* Generates a random agent representing an MGA trajectory based on the given sequence.
* @returns A random trajectory agent
*/
function createRandomMGAAgent() {
const dim = 4 * (sequence.length - 1) + 2;
private _createRandomMGAAgent() {
const dim = 4 * (this._sequence.length - 1) + 2;
const solution: Agent = Array<number>(dim).fill(0);
// Initial ejection condition on departure body
solution[0] = randomInInterval(startDateMin, startDateMax); // departure date
solution[0] = randomInInterval(this._startDateMin, this._startDateMax); // departure date
solution[1] = randomInInterval(1, 3); // ejection deltaV scale
// Interplanetary legs' parameters
for(let i = 1; i < sequence.length; i++){
for(let i = 1; i < this._sequence.length; i++){
const j = 2 + (i - 1) * 4;
const {legDuration, dsmOffset} = legDurationAndDSM(i);
const {legDuration, dsmOffset} = this._legDurationAndDSM(i);
solution[j] = legDuration;
solution[j+1] = dsmOffset;
@@ -136,8 +138,8 @@
* Randomizes an existing agent instance.
* @param agent The angent to randomize
*/
function randomizeExistingAgent(agent: Agent){
const newAgent = createRandomMGAAgent();
private _randomizeExistingAgent(agent: Agent){
const newAgent = this._createRandomMGAAgent();
for(let i = 0; i < agent.length; i++){
agent[i] = newAgent[i];
}
@@ -147,12 +149,12 @@
* @param index The index on the planetary sequence.
* @returns A random leg duration and DSM offset
*/
function legDurationAndDSM(index: number){
const isResonant = sequence[index-1] == sequence[index];
const {dsmOffsetMin, dsmOffsetMax} = config.trajectorySearch;
private _legDurationAndDSM(index: number){
const isResonant = this._sequence[index-1] == this._sequence[index];
const {dsmOffsetMin, dsmOffsetMax} = this._config.trajectorySearch;
if(isResonant) {
const sideralPeriod = <number>system[sequence[index]].orbit.sideralPeriod;
const sideralPeriod = <number>this._system[this._sequence[index]].orbit.sideralPeriod;
const revs = randomInInterval(1, 4);
const legDuration = revs * sideralPeriod;
@@ -163,10 +165,10 @@
return {legDuration, dsmOffset};
} else {
const body1 = system[sequence[index-1]];
const body2 = system[sequence[index]];
const body1 = this._system[this._sequence[index-1]];
const body2 = this._system[this._sequence[index]];
const attractor = system[body1.orbiting];
const attractor = this._system[body1.orbiting];
const period = getHohmannPeriod(body1, body2, attractor);
const legDuration = randomInInterval(0.1, 1) * period;
@@ -175,4 +177,6 @@
return {legDuration, dsmOffset};
}
}
}
}
initWorker(TrajectoryOptimizer);

3
src/types.d.ts vendored
View File

@@ -146,7 +146,8 @@ type ProgressCallback = (progress: number, data?: any) => any;
type GeneratingSequence = {
sequence: number[],
resonant: number,
backLegs: number
backLegs: number,
backSpacingExceeded: boolean;
};
type Agent = number[];