diff --git a/dist/dedicated-workers/libs/common.js b/dist/dedicated-workers/libs/common.js index d3aae4c..19024b4 100644 --- a/dist/dedicated-workers/libs/common.js +++ b/dist/dedicated-workers/libs/common.js @@ -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; + } + }; +} diff --git a/dist/dedicated-workers/libs/physics-2d.js b/dist/dedicated-workers/libs/physics-2d.js index cd81e1b..c4c7edf 100644 --- a/dist/dedicated-workers/libs/physics-2d.js +++ b/dist/dedicated-workers/libs/physics-2d.js @@ -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); diff --git a/dist/dedicated-workers/sequence-evaluator.js b/dist/dedicated-workers/sequence-evaluator.js index af2cbd7..4da7a24 100644 --- a/dist/dedicated-workers/sequence-evaluator.js +++ b/dist/dedicated-workers/sequence-evaluator.js @@ -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); diff --git a/dist/dedicated-workers/sequence-generator.js b/dist/dedicated-workers/sequence-generator.js index 91fdbbc..c794c76 100644 --- a/dist/dedicated-workers/sequence-generator.js +++ b/dist/dedicated-workers/sequence-generator.js @@ -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); diff --git a/dist/dedicated-workers/trajectory-optimizer.js b/dist/dedicated-workers/trajectory-optimizer.js index af9603f..c72c517 100644 --- a/dist/dedicated-workers/trajectory-optimizer.js +++ b/dist/dedicated-workers/trajectory-optimizer.js @@ -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); diff --git a/src/dedicated-workers/libs/common.ts b/src/dedicated-workers/libs/common.ts index 2470e28..ea7adbb 100644 --- a/src/dedicated-workers/libs/common.ts +++ b/src/dedicated-workers/libs/common.ts @@ -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) => { - 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) => { + 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; + } } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/dedicated-workers/libs/evolution.ts b/src/dedicated-workers/libs/evolution.ts index f1ef2bb..5710fb1 100644 --- a/src/dedicated-workers/libs/evolution.ts +++ b/src/dedicated-workers/libs/evolution.ts @@ -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. */ diff --git a/src/dedicated-workers/libs/physics-2d.ts b/src/dedicated-workers/libs/physics-2d.ts index a32c711..5c786da 100644 --- a/src/dedicated-workers/libs/physics-2d.ts +++ b/src/dedicated-workers/libs/physics-2d.ts @@ -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; diff --git a/src/dedicated-workers/sequence-evaluator.ts b/src/dedicated-workers/sequence-evaluator.ts index 6b2d277..6215655 100644 --- a/src/dedicated-workers/sequence-evaluator.ts +++ b/src/dedicated-workers/sequence-evaluator.ts @@ -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 | null = null; + private _sequences!: number[][]; + private _current: Generator | 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; } -} \ No newline at end of file +} + +initWorker(SequenceEvaluator); \ No newline at end of file diff --git a/src/dedicated-workers/sequence-generator.ts b/src/dedicated-workers/sequence-generator.ts index ae14f2e..d040129 100644 --- a/src/dedicated-workers/sequence-generator.ts +++ b/src/dedicated-workers/sequence-generator.ts @@ -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 = new Map(); - for(let i = 0; i < allowedBodies.length; ++i){ - relativePos.set(allowedBodies[i], i); + 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; } /** - * 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, 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 = 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; } /** - * 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++; } -} \ No newline at end of file +} + +initWorker(SequenceGenerator); \ No newline at end of file diff --git a/src/dedicated-workers/trajectory-optimizer.ts b/src/dedicated-workers/trajectory-optimizer.ts index 5dca2ad..6a91eb7 100644 --- a/src/dedicated-workers/trajectory-optimizer.ts +++ b/src/dedicated-workers/trajectory-optimizer.ts @@ -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(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 = system[sequence[index]].orbit.sideralPeriod; + const sideralPeriod = 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}; } } -} \ No newline at end of file +} + +initWorker(TrajectoryOptimizer); \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts index ea4029a..b816be2 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -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[];