| Author | djarvis <email> |
|---|---|
| Date | 2024-12-17 14:17:24 GMT-0800 |
| Commit | 0819c99060bb75c99d3fc5ad36fc07bbb19f46f8 |
| Parent | 0d18a4c |
| STRATOSPHERE: [{ | ||
| range: [11, 20], | ||
| - temperature: (altitude) => 216.65 | ||
| + temperature: (_) => 216.65 | ||
| }, { | ||
| range: [20, 32], | ||
| temperature: (altitude) => 196.65 + altitude | ||
| }, { | ||
| range: [32, 47], | ||
| temperature: (altitude) => 139.05 + 2.8 * altitude | ||
| }], | ||
| MESOSPHERE: [{ | ||
| range: [47, 51], | ||
| - temperature: (altitude) => 270.65 | ||
| + temperature: (_) => 270.65 | ||
| }, { | ||
| range: [51, 71], | ||
| temperature: (altitude) => 413.45 - 2.8 * altitude | ||
| }, { | ||
| range: [71, 84.852], | ||
| temperature: (altitude) => 356.65 - 2.0 * altitude | ||
| }], | ||
| THERMOSPHERE: [{ | ||
| range: [84.852, 91], | ||
| - temperature: (altitude) => 186.8673 | ||
| + temperature: (_) => 186.8673 | ||
| }, { | ||
| range: [91, 110], | ||
| temperature: (altitude) => 263.1905 - 76.3232 * Math.sqrt(1 - Math.pow((altitude - 91) / -19.9429, 2)) | ||
| }, { | ||
| range: [110, 120], | ||
| temperature: (altitude) => 240 + 12 * (altitude - 110) | ||
| }, { | ||
| range: [120, 1000], | ||
| temperature: (altitude) => { | ||
| - const ξ = (altitude - 120) * (6356.766 + 120) / (6356.766 + altitude); | ||
| - return 1000 - 640 * Math.exp(-0.01875 * ξ); | ||
| + const E = (altitude - 120) * (6356.766 + 120) / (6356.766 + altitude); | ||
| + return 1000 - 640 * Math.exp(-0.01875 * E); | ||
| } | ||
| }, { | ||
| range: [1000, Infinity], | ||
| - temperature: (altitude) => 1000 | ||
| + temperature: (_) => 1000 | ||
| }] | ||
| }; | ||
| temperature(altitude) { | ||
| - let result; | ||
| const temperatures = Layer.TEMPERATURES[this.name]; | ||
| + let result; | ||
| for (let t of temperatures) { | ||
| constructor(latitude) { | ||
| + console.assert(typeof latitude === 'number'); | ||
| + console.assert(latitude !== undefined); | ||
| + | ||
| this.latitude = latitude; | ||
| } | ||
| layerValue(altitude, callback) { | ||
| + console.assert( altitude >= 0 ); | ||
| + | ||
| let result; | ||
| constructor(latitude) { | ||
| + console.assert(typeof latitude === 'number'); | ||
| + console.assert(latitude !== undefined); | ||
| + | ||
| this.atmosphere = new Atmosphere(latitude); | ||
| } |
| } | ||
| + maxAltitude() { | ||
| + return this.altitudes.max(); | ||
| + } | ||
| + | ||
| azimuth(time, value) { | ||
| this.azimuths.record(time, value); | ||
| } | ||
| horizontalVelocity(time, value) { | ||
| this.horizontalVelocities.record(time, value); | ||
| + } | ||
| + | ||
| + maxHorizontalVelocity() { | ||
| + return this.horizontalVelocities.max(); | ||
| } | ||
| mass(time, value) { | ||
| this.masses.record(time, value); | ||
| + } | ||
| + | ||
| + minMass() { | ||
| + return this.masses.min(); | ||
| } | ||
| */ | ||
| constructor(wetMass, payloadMass) { | ||
| + this.mass = wetMass; | ||
| this.payloadMass = payloadMass; | ||
| this.dryMass = wetMass * 0.1 + this.payloadMass; | ||
| - this.mass = wetMass; | ||
| this.azimuth = 0.0; | ||
| */ | ||
| translocate(latitude, altitude) { | ||
| + console.assert(altitude >= 0); | ||
| + | ||
| this.latitude = latitude; | ||
| this.altitude = altitude; | ||
| apogee(targetAltitude) { | ||
| this.targetAltitude = targetAltitude; | ||
| - } | ||
| - /** | ||
| - * Calculates the gravitational acceleration at the current position. | ||
| - * | ||
| - * @returns {number} Gravitational acceleration (meters per second squared). | ||
| - */ | ||
| - gravity() { | ||
| - return this.planet.gravity(this.latitude, this.altitude); | ||
| + this.hVelocityTarget = this.planet.orbitalSpeed( | ||
| + this.latitude, this.targetAltitude | ||
| + ); | ||
| } | ||
| rotational() { | ||
| return this.planet.rotationalSpeed(this.latitude, this.altitude); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Calculates the orbital speed required at the current latitude and | ||
| - * target altitude. | ||
| - * | ||
| - * @returns {number} Orbital speed (meters per second). | ||
| - */ | ||
| - orbital() { | ||
| - return this.planet.orbitalSpeed(this.latitude, this.targetAltitude); | ||
| } | ||
| /** | ||
| * Launches the rocket with an initial velocity. | ||
| * | ||
| - * @param {number} initialVelocity - Initial velocity as a fraction of the | ||
| - * speed of sound (dimensionless). | ||
| + * @param {number} velocity - Initial velocity (Mach). | ||
| */ | ||
| - launch(initialVelocity) { | ||
| - this.exhaustVelocity = this.specificImpulse * this.gravity(); | ||
| - this.maximumAcceleration = 5.0 * this.gravity(); | ||
| - this.vVelocity = initialVelocity * this.planet.soundSpeed(this.altitude); | ||
| + launch(velocity) { | ||
| + const g = this.planet.gravity(this.latitude, this.targetAltitude); | ||
| + this.exhaustVelocity = this.specificImpulse * g; | ||
| - this.hVelocity = this.rotational(); | ||
| - this.hVelocityTarget = this.orbital(); | ||
| - this.massFlowRate = Math.abs(this.drag()[1]) / | ||
| - this.exhaustVelocity * 1.001; | ||
| + this.hVelocityInitial = this.rotational(); | ||
| + this.hVelocity = this.hVelocityInitial; | ||
| + this.vVelocity = velocity * this.planet.soundSpeed(this.altitude); | ||
| + this.maxAcceleration = 5.0 * 9.8; | ||
| + | ||
| + const drag = this.drag(); | ||
| + this.massFlowRate = Math.abs(this.drag()[1]) / this.exhaustVelocity * 1.001; | ||
| + this.tThrust = this.exhaustVelocity * this.massFlowRate; | ||
| + | ||
| + const vThrust = -drag[1]; | ||
| + const hThrust = Math.sqrt(this.tThrust ** 2 - vThrust ** 2); | ||
| + | ||
| + this.vAcceleration = drag[1] / this.mass + g + vThrust / this.mass; | ||
| + this.hAcceleration = drag[0] / this.mass + hThrust / this.mass; | ||
| } | ||
| drag() { | ||
| const rho = this.planet.airDensity(this.altitude); | ||
| - const vHorNet = this.hVelocity - this.rotational(); | ||
| - const speedTotal = this.magnitude(vHorNet, this.vVelocity); | ||
| + const vHorNet = this.hVelocity - this.hVelocityInitial; | ||
| + let speedTotal = this.magnitude(vHorNet, this.vVelocity); | ||
| + | ||
| + if (speedTotal === 0) { | ||
| + speedTotal = 1e-10; | ||
| + } | ||
| + | ||
| const dragForce = 0.5 * rho * this.ballisticCoefficient * | ||
| speedTotal ** 2; | ||
| const ratio = [-vHorNet / speedTotal, -this.vVelocity / speedTotal]; | ||
| - return [ratio[0] * dragForce, ratio[1] * dragForce]; | ||
| + return [dragForce * ratio[0], dragForce * ratio[1]]; | ||
| } | ||
| /** | ||
| * Checks if the rocket is still flying, i.e., if it has fuel and has not | ||
| * reached the target altitude. | ||
| * | ||
| * @returns {boolean} True if the rocket is flying. | ||
| */ | ||
| flying() { | ||
| - return (this.mass > this.dryMass && this.altitude < this.targetAltitude) | ||
| - || (this.hVelocity < this.hVelocityTarget && this.altitude > 0); | ||
| + return this.mass > this.dryMass && this.altitude < this.targetAltitude && this.altitude > 0; | ||
| } | ||
| const absDrag = Math.abs(drag[1]); | ||
| - if (accMag > this.maximumAcceleration && | ||
| + if (accMag > this.maxAcceleration && | ||
| this.exhaustVelocity * this.massFlowRate * 0.98 > absDrag) { | ||
| this.massFlowRate *= 0.99; | ||
| this.massFlowRate = 0.0; | ||
| } | ||
| + | ||
| + console.assert(this.massFlowRate >= 0); | ||
| // Compute the total thrust. | ||
| this.tThrust = this.exhaustVelocity * this.massFlowRate; | ||
| const thrusting = this.tThrust > 0; | ||
| const vThrust = thrusting ? -drag[1] : 0; | ||
| const hThrust = thrusting ? Math.sqrt(this.tThrust ** 2 - vThrust ** 2) : 0; | ||
| + const g = -this.planet.gravity(this.latitude, this.altitude); | ||
| + this.vAcceleration = drag[1] / this.mass + g + vThrust / this.mass; | ||
| this.hAcceleration = drag[0] / this.mass + hThrust / this.mass; | ||
| - this.vAcceleration = drag[1] / this.mass + this.gravity() + vThrust / this.mass; | ||
| this.vVelocity += this.vAcceleration * step; | ||
| magnitude(x, y) { | ||
| return Math.sqrt(x ** 2 + y ** 2); | ||
| + } | ||
| + | ||
| + emptyMass() { | ||
| + return this.dryMass; | ||
| + } | ||
| + | ||
| + initialOrbitalVelocity() { | ||
| + return this.hVelocityInitial; | ||
| } | ||
| } | ||
| +Array.prototype.max = function () { | ||
| + let maxVal = this[0]; | ||
| + | ||
| + for (let i = 1; i < this.length; i++) { | ||
| + if (this[i] > maxVal) { | ||
| + maxVal = this[i]; | ||
| + } | ||
| + } | ||
| + | ||
| + return maxVal; | ||
| +}; | ||
| + | ||
| +Array.prototype.min = function () { | ||
| + let minVal = this[0]; | ||
| + | ||
| + for (let i = 1; i < this.length; i++) { | ||
| + if (this[i] < minVal) { | ||
| + minVal = this[i]; | ||
| + } | ||
| + } | ||
| + | ||
| + return minVal; | ||
| +}; | ||
| + | ||
| class Telemetry { | ||
| constructor(measureName) { | ||
| this.times.push(time); | ||
| this.values.push(value); | ||
| + } | ||
| + | ||
| + max() { | ||
| + return this.values.max(); | ||
| + } | ||
| + | ||
| + min() { | ||
| + return this.values.min(); | ||
| } | ||
| plot(plotName, yLabel) { | ||
| const data = this.times.map((time, index) => ({ | ||
| time: time, | ||
| value: this.values[index] | ||
| })); | ||
| + const minYValue = d3.min(data, d => d.value); | ||
| const maxYValue = d3.max(data, d => d.value); | ||
| - const numDigits = maxYValue.toFixed(0).length; | ||
| + const numDigits = Math.max(minYValue.toFixed(0).length, maxYValue.toFixed(0).length); | ||
| const margin = { | ||
| top: 30, | ||
| const y = d3.scaleLinear() | ||
| - .domain([0, maxYValue]) | ||
| + .domain([minYValue, maxYValue]) | ||
| .range([height, 0]); | ||
| .style('font-family', 'sans-serif') | ||
| .style('fill', '#333') | ||
| - .text('Time (seconds)'); | ||
| + .text('Time (s)'); | ||
| const formattedYLabel = yLabel.replace('^2', '²'); | ||
| class Simulation { | ||
| - constructor() {} | ||
| + constructor() { | ||
| + } | ||
| init() { | ||
| - const initialVelocity = $('#initial_velocity').val(); | ||
| - const initialLatitude = $('#initial_latitude').val(); | ||
| - const initialAltitude = $('#initial_altitude').val(); | ||
| + const velocity = $('#initial_velocity').val(); | ||
| + const latitude = $('#initial_latitude').val(); | ||
| + const altitude = $('#initial_altitude').val(); | ||
| const targetAltitude = $('#target_altitude').val() * 1000.0; | ||
| - const rocketDiameter = $('#diameter').val(); | ||
| + const diameter = $('#diameter').val(); | ||
| const wetMass = $('#wet_mass').val(); | ||
| const payloadMass = $('#payload_mass').val(); | ||
| const dragCoefficient = $('#drag_coefficient').val(); | ||
| const specificImpulse = $('#specific_impulse').val(); | ||
| - this.planet = new Earth(initialLatitude); | ||
| + this.planet = new Earth(latitude); | ||
| this.rocket = new Rocket(wetMass, payloadMass); | ||
| + | ||
| + $('#orbital_velocity').val( | ||
| + this.planet.orbitalSpeed(latitude, targetAltitude).toFixed(2) | ||
| + ); | ||
| this.rocket.on(this.planet); | ||
| - this.rocket.fuselage(rocketDiameter, dragCoefficient, specificImpulse); | ||
| - this.rocket.translocate(initialLatitude, initialAltitude); | ||
| + this.rocket.fuselage(diameter, dragCoefficient, specificImpulse); | ||
| + this.rocket.translocate(latitude, altitude); | ||
| this.rocket.apogee(targetAltitude); | ||
| - this.rocket.launch(initialVelocity); | ||
| + this.rocket.launch(velocity); | ||
| } | ||
| time += TIME_STEP; | ||
| } | ||
| + | ||
| + const altitude = $('#initial_altitude').val(); | ||
| + | ||
| + $('#air_density').val( | ||
| + this.planet.airDensity(altitude).toFixed(2) | ||
| + ); | ||
| + $('#initial_orbital_velocity').val( | ||
| + this.rocket.initialOrbitalVelocity().toFixed(2) | ||
| + ); | ||
| + $('#rocket_velocity').val( | ||
| + blackBox.maxHorizontalVelocity().toFixed(2) | ||
| + ); | ||
| + $('#rocket_altitude').val( | ||
| + (blackBox.maxAltitude() / 1000.0).toFixed(2) | ||
| + ); | ||
| + $('#dry_mass').val( | ||
| + this.rocket.emptyMass().toFixed(2) | ||
| + ); | ||
| + $('#final_mass').val( | ||
| + blackBox.minMass().toFixed(2) | ||
| + ); | ||
| blackBox.plot(); | ||
| section, below. Based on Professor Aaron Ridley's | ||
| <a href="https://github.com/aaronjridley/Kepler/blob/main/OrbitalInsertion/launch.py">implementation</a>. | ||
| - </p><form id="calculator"><button class="submit">Simulate</button><fieldset id="mission"><legend>Mission</legend><label for="initial_velocity">Initial velocity (Mach)</label><input tabindex="3" class="variable" type="number" step="1" min="0" value="6" id="initial_velocity" name="initial_velocity"><label for="initial_latitude">Initial latitude (°)</label><input tabindex="4" class="variable" type="number" step="any" min="0" value="1.469167" id="initial_latitude" name="initial_latitude"><label for="initial_altitude">Initial altitude (m)</label><input tabindex="5" class="variable" type="number" step="1" min="0" value="6212" id="initial_altitude" name="initial_altitude"><label for="target_altitude">Target altitude (km)</label><input tabindex="6" class="variable" type="number" step="1" min="1" value="400" id="target_altitude" name="target_altitude"></fieldset><fieldset id="rocket"><legend>Rocket</legend><label for="diameter">Diameter (m)</label><input tabindex="7" class="variable" type="number" step="any" min="0" value="0.6" id="diameter" name="diameter"><label for="wet_mass">Wet mass (kg)</label><input tabindex="8" class="variable" type="number" step="10" min="1" value="250" id="wet_mass" name="wet_mass" oninput="this.value = Math.abs(this.value)"><label for="payload">Payload mass (kg)</label><input tabindex="9" class="variable" type="number" step="1" min="1" value="25" id="payload_mass" name="payload_mass" oninput="this.value = Math.abs(this.value)"><label for="drag_coefficient">Drag coefficient</label><input tabindex="10" class="variable" type="number" step="any" min="0" value="0.219" id="drag_coefficient" name="drag_coefficient"><label for="specific_impulse">Specific impulse (s)</label><input tabindex="11" class="variable" type="number" step="10" min="1" value="1700" id="specific_impulse" name="specific_impulse"></fieldset><fieldset id="result"><legend>Result</legend><div id="plots"></div></fieldset></form><a name="equations"></a><h1>Equations</h1><p> | ||
| + </p><form id="calculator"><button class="submit">Simulate</button><fieldset id="mission"><legend>Mission</legend><label for="initial_velocity">Initial velocity (Mach)</label><input tabindex="3" class="variable" type="number" step="0.1" min="0" value="8.5" id="initial_velocity" name="initial_velocity"><label for="initial_latitude">Initial latitude (°)</label><input tabindex="4" class="variable" type="number" step="any" min="0" value="1.469167" id="initial_latitude" name="initial_latitude"><label for="initial_altitude">Initial altitude (m)</label><input tabindex="5" class="variable" type="number" step="1" min="0" value="6212" id="initial_altitude" name="initial_altitude"><label for="target_altitude">Target altitude (km)</label><input tabindex="6" class="variable" type="number" step="1" min="1" value="400" id="target_altitude" name="target_altitude"></fieldset><fieldset id="rocket"><legend>Rocket</legend><label for="diameter">Diameter (m)</label><input tabindex="7" class="variable" type="number" step="any" min="0" value="0.6" id="diameter" name="diameter"><label for="wet_mass">Wet mass (kg)</label><input tabindex="8" class="variable" type="number" step="10" min="1" value="250" id="wet_mass" name="wet_mass" oninput="this.value = Math.abs(this.value)"><label for="payload">Payload mass (kg)</label><input tabindex="9" class="variable" type="number" step="1" min="1" value="25" id="payload_mass" name="payload_mass" oninput="this.value = Math.abs(this.value)"><label for="drag_coefficient">Drag coefficient</label><input tabindex="10" class="variable" type="number" step="any" min="0" value="0.219" id="drag_coefficient" name="drag_coefficient"><label for="specific_impulse">Specific impulse (s)</label><input tabindex="11" class="variable" type="number" step="10" min="1" value="1400" id="specific_impulse" name="specific_impulse"></fieldset><fieldset id="totals"><legend>Summary</legend><label for="air_density"> | ||
| + Air density (kg/m<sup>3</sup>) | ||
| + </label><input type="text" value="" readonly id="air_density" name="air_density"><label for="initial_orbital_velocity"> | ||
| + Initial orbital velocity (m/s) | ||
| + </label><input type="text" value="" readonly id="initial_orbital_velocity" name="initial_orbital_velocity"><label for="orbital_velocity"> | ||
| + Target orbital velocity (m/s) | ||
| + </label><input type="text" value="" readonly id="orbital_velocity" name="orbital_velocity"><label for="rocket_velocity"> | ||
| + Maximum velocity (m/s) | ||
| + </label><input type="text" value="" readonly id="rocket_velocity" name="rocket_velocity"><label for="rocket_altitude"> | ||
| + Maximum altitude (km) | ||
| + </label><input type="text" value="" readonly id="rocket_altitude" name="rocket_altitude"><label for="dry_mass"> | ||
| + Dry mass (kg) | ||
| + </label><input type="text" value="" readonly id="dry_mass" name="dry_mass"><label for="final_mass"> | ||
| + Final mass (kg) | ||
| + </label><input type="text" value="" readonly id="final_mass" name="final_mass"><label for="payload_delivered"> | ||
| + Payload delivered | ||
| + </label><input type="text" value="" readonly id="payload_delivered" name="payload_delivered"></fieldset></form><h2>Results</h2><div id="plots"></div><a name="equations"></a><h1>Equations</h1><p> | ||
| This section describes equation inputs and outputs. | ||
| </p><h2>Cross-section area</h2><p> |
| <label for="initial_velocity">Initial velocity (Mach)</label> | ||
| <input tabindex="3" | ||
| - class="variable" type="number" step="1" min="0" value="6" | ||
| + class="variable" type="number" step="0.1" min="0" value="8.5" | ||
| id="initial_velocity" name="initial_velocity" /> | ||
| <label for="specific_impulse">Specific impulse (s)</label> | ||
| <input tabindex="11" | ||
| - class="variable" type="number" step="10" min="1" value="1700" | ||
| + class="variable" type="number" step="10" min="1" value="1400" | ||
| id="specific_impulse" name="specific_impulse" /> | ||
| </fieldset> | ||
| - <fieldset id="result"> | ||
| - <legend>Result</legend> | ||
| - <div id="plots"> | ||
| - </div> | ||
| + <fieldset id="totals"> | ||
| + <legend>Summary</legend> | ||
| + | ||
| + <label for="air_density"> | ||
| + Air density (kg/m<sup>3</sup>) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="air_density" name="air_density" /> | ||
| + | ||
| + <label for="initial_orbital_velocity"> | ||
| + Initial orbital velocity (m/s) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="initial_orbital_velocity" name="initial_orbital_velocity" /> | ||
| + | ||
| + <label for="orbital_velocity"> | ||
| + Target orbital velocity (m/s) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="orbital_velocity" name="orbital_velocity" /> | ||
| + | ||
| + <label for="rocket_velocity"> | ||
| + Maximum velocity (m/s) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="rocket_velocity" name="rocket_velocity" /> | ||
| + | ||
| + <label for="rocket_altitude"> | ||
| + Maximum altitude (km) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="rocket_altitude" name="rocket_altitude" /> | ||
| + | ||
| + <label for="dry_mass"> | ||
| + Dry mass (kg) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="dry_mass" name="dry_mass" /> | ||
| + | ||
| + <label for="final_mass"> | ||
| + Final mass (kg) | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="final_mass" name="final_mass" /> | ||
| + | ||
| + <label for="payload_delivered"> | ||
| + Payload delivered | ||
| + </label> | ||
| + <input | ||
| + type="text" value="" readonly="readonly" | ||
| + id="payload_delivered" name="payload_delivered" /> | ||
| </fieldset> | ||
| </form> | ||
| + | ||
| + <h2>Results</h2> | ||
| + <div id="plots"> | ||
| + </div> | ||
| <a name="equations" /> | ||
| Delta | 225 lines added, 62 lines removed, 163-line increase |
|---|