From afe22d8bd7c97709bc187e4108f215cb1650576f Mon Sep 17 00:00:00 2001 From: Jiri Pokorny Date: Sun, 18 Aug 2024 01:32:10 +0200 Subject: [PATCH] Implemented usage of consumption options --- .../planner/src/app/workers/planning.tasks.ts | 13 +++- .../scuba-physics/src/lib/consumption.spec.ts | 60 ++++++++++--------- projects/scuba-physics/src/lib/consumption.ts | 35 +++++------ 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/projects/planner/src/app/workers/planning.tasks.ts b/projects/planner/src/app/workers/planning.tasks.ts index 5d3ab119..4ead7cfa 100644 --- a/projects/planner/src/app/workers/planning.tasks.ts +++ b/projects/planner/src/app/workers/planning.tasks.ts @@ -2,7 +2,7 @@ import { Segments, Gases, ProfileEvents, DepthConverterFactory, Consumption, Time, Diver, OtuCalculator, CnsCalculator, DensityAtDepth, EventOptions, AlgorithmParams, BuhlmannAlgorithm, - RestingParameters, Segment, PlanFactory + RestingParameters, Segment, PlanFactory, ConsumptionOptions } from 'scuba-physics'; import { ProfileRequestDto, ProfileResultDto, ConsumptionRequestDto, @@ -72,13 +72,20 @@ export class PlanningTasks { const options = DtoSerialization.toOptions(task.options); const plan = PlanningTasks.selectConsumptionPlan(segments, task.isComplex); + const consumptionOptions: ConsumptionOptions = { + diver: diver, + primaryTankReserve: Consumption.defaultPrimaryReserve, + stageTankReserve: Consumption.defaultStageReserve, + }; + + // Max bottom changes tank consumed bars, so we need it calculate before real profile consumption - const maxTime = consumption.calculateMaxBottomTime(plan, tanks, diver, options); + const maxTime = consumption.calculateMaxBottomTime(plan, tanks, consumptionOptions, options); const emergencyAscent = PlanFactory.emergencyAscent(originProfile, options, tanks); let timeToSurface = Segments.duration(emergencyAscent); timeToSurface = Time.toMinutes(timeToSurface); - consumption.consumeFromTanks2(originProfile, emergencyAscent, tanks, diver); + consumption.consumeFromTanks2(originProfile, emergencyAscent, tanks, consumptionOptions); return { diveId: task.diveId, diff --git a/projects/scuba-physics/src/lib/consumption.spec.ts b/projects/scuba-physics/src/lib/consumption.spec.ts index 46ffbb1c..3a7268fd 100644 --- a/projects/scuba-physics/src/lib/consumption.spec.ts +++ b/projects/scuba-physics/src/lib/consumption.spec.ts @@ -1,7 +1,7 @@ import { Diver } from './Diver'; import { DepthConverter } from './depth-converter'; import { Tank } from './Tanks'; -import { Consumption } from './consumption'; +import { Consumption, ConsumptionOptions } from './consumption'; import { Time } from './Time'; import { Segment, Segments } from './Segments'; import { OptionExtensions } from './Options.spec'; @@ -12,7 +12,11 @@ import { Precision } from './precision'; // TODO consumption test case: /?t=1-24-0-220-0.21-0.35,2-11.1-0-200-0.5-0&de=0-53-360-1,53-53-660-1,53-46-60-1,46-46-360-1,46-24-120-1,24-21-60-1&di=20&o=300,9,3,1,3,20,3,0.75,0.3,6,1.6,30,1.4,10,1,1,1,1,1&ao=1,0 // Ensure if it si valid: generates too high reserve for S80 = 200 b, twin = 120 b describe('Consumption', () => { - const diver = new Diver(20); + const consumtionOptions: ConsumptionOptions = { + diver: new Diver(20), + primaryTankReserve: Consumption.defaultPrimaryReserve, + stageTankReserve: Consumption.defaultStageReserve + }; const consumption = new Consumption(DepthConverter.forFreshWater()); const options2 = OptionExtensions.createOptions(1, 1, 1.4, 1.6, Salinity.fresh); options2.safetyStop = SafetyStop.never; @@ -31,7 +35,7 @@ describe('Consumption', () => { segments.add(30, tank.gas, Time.oneMinute * 0.5); segments.addFlat(tank.gas, Time.oneMinute * 10.5); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(17); }); @@ -44,7 +48,7 @@ describe('Consumption', () => { segments.add(40, airTank.gas, Time.oneMinute * 2); segments.addFlat(airTank.gas, Time.oneMinute); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(20); }); @@ -57,7 +61,7 @@ describe('Consumption', () => { segments.add(40, airTank.gas, Time.oneMinute * 2); segments.addFlat(airTank.gas, Time.oneMinute); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(5); }); @@ -69,7 +73,7 @@ describe('Consumption', () => { segments.add(30, tank.gas, Time.oneMinute * 0.5); segments.addFlat(tank.gas, Time.oneMinute * 23); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(0); }); @@ -83,7 +87,7 @@ describe('Consumption', () => { segments.addFlat(tank.gas, Time.oneMinute * 10); const startTime = performance.now(); - consumption.calculateMaxBottomTime(segments, tanks, diver, options); + consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); const endTime = performance.now(); const methodDuration = Precision.round(endTime - startTime); @@ -100,7 +104,7 @@ describe('Consumption', () => { segments.addFlat(tank.gas, Time.oneMinute * 10); segments.addFlat(tank.gas, Time.oneMinute * 10); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(50); }); @@ -113,7 +117,7 @@ describe('Consumption', () => { segments.addFlat(tank.gas, Time.oneMinute * 10); segments.add(0, tank.gas, Time.oneMinute * 10); - const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, diver, options); + const maxBottomTime = consumption.calculateMaxBottomTime(segments, tanks, consumtionOptions, options); expect(maxBottomTime).toEqual(181); }); }); @@ -132,7 +136,7 @@ describe('Consumption', () => { const options3 = OptionExtensions.createOptions(1, 1, 1.4, 1.6, Salinity.fresh); options3.safetyStop = SafetyStop.always; options3.problemSolvingDuration = 2; - consumption.consumeFromTanks(segments, options3, tanks, diver); + consumption.consumeFromTanks(segments, options3, tanks, consumtionOptions); return tank; }; @@ -161,7 +165,7 @@ describe('Consumption', () => { ]; // (2b avg depth * 2 bar/min * 1 minutes) + (3b * 2 bar/min * 10 minutes) + (2b * 2 bar/min * 2 minutes) - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); expect(tank.consumed).toEqual(72); }); }); @@ -181,7 +185,7 @@ describe('Consumption', () => { new Segment(20, 0, ean50Tank.gas, 1 * Time.oneMinute) // 2b * 2 bar/min * 1 minutes = 4b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Both tanks are consumed', () => { expect(airTank.consumed).toEqual(52); @@ -208,7 +212,7 @@ describe('Consumption', () => { new Segment(20, 0, ean50Tank.gas, 1 * Time.oneMinute) // 2b * 2 bar/min * 1 minutes = 4b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Consumption is updated from second tank only', () => { expect(airTank.consumed).toEqual(0); @@ -231,7 +235,7 @@ describe('Consumption', () => { new Segment(20, 0, ean50Tank.gas, 1 * Time.oneMinute) // 2b * 2 bar/min * 1 minutes = 4b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Consumption is updated from both air tanks', () => { expect(airTank.consumed).toEqual(7); @@ -251,7 +255,7 @@ describe('Consumption', () => { new Segment(20, 0, airTank.gas, 2 * Time.oneMinute) ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Consumes only user defined tank', () => { expect(airTank.consumed).toEqual(72); @@ -280,7 +284,7 @@ describe('Consumption', () => { new Segment(20, 0, airTank.gas, 2 * Time.oneMinute) ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('No tank is updated', () => { expect(airTank.consumed).toEqual(0); @@ -307,7 +311,7 @@ describe('Consumption', () => { new Segment(20, 0, ean50Tank.gas, 10 * Time.oneMinute) // 2b * 4 bar/min * 10 minutes = 80b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Reserve is updated from both EAN50 tanks', () => { // ((4b * 2 * 1) + (3.5b * 1 min * 1 b/min.)) * 3 @@ -337,7 +341,7 @@ describe('Consumption', () => { new Segment(20, 0, ean50Tank2.gas, 4 * Time.oneMinute), // 2 b * 2 bar/min * 4 minute = 16b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Gas is Consumed from required tank', () => { expect(airTank.consumed).toEqual(60); // user defined by swim segment @@ -370,7 +374,7 @@ describe('Consumption', () => { new Segment(20, 0, airTank.gas, 4 * Time.oneMinute), // 2 b * 1 bar/min * 4 minute = 8b ]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); it('Reserve is more than remaining', () => { expect(airTank.reserve).toEqual(30); // ((3 * 2 * 1) + (2.5 * 2 * 1)) * 3 @@ -390,7 +394,7 @@ describe('Consumption', () => { const ascent = new Segment(20, 0, airTank, 4 * Time.oneMinute); // 2 b * 1 bar/min * 4 minute = 8b const profile = [descent, swim, ascent]; - consumption.consumeFromTanks(profile, options3, tanks, diver); + consumption.consumeFromTanks(profile, options3, tanks, consumtionOptions); it('Tank is updated as with calculated segments', () => { expect(airTank.reserve).toEqual(42); // 3 min at 3 m + 2 min. solving @@ -409,7 +413,7 @@ describe('Consumption', () => { const segments = [first, second]; // 1.5 * 20 * 2 = 60 - consumption.consumeFromTanks(segments, options2, tanks, diver); + consumption.consumeFromTanks(segments, options2, tanks, consumtionOptions); expect(tank.consumed).toEqual(61); // because of pressure conversion // emergency ascent: ((2 * 2 * 2) + (1.5 * 1 * 2)) * 3 = 33 expect(tank.reserve).toEqual(33); @@ -429,7 +433,7 @@ describe('Consumption', () => { // currently tank1 30/90, tank2 112/169 (reserve/remaining) // should be tank1 129/90, tank2 94/169 - consumption.consumeFromTanks(segments, options2, tanks, diver); + consumption.consumeFromTanks(segments, options2, tanks, consumtionOptions); // ((5 * 2 * 1) + (3,3 * 10 * 1)) * 3 expect(tank1.reserve).toEqual(128); // ((1,6 * 8 * 2 = 25,6) + (1,3 * 2 * 2 = 5,20)) * 3 @@ -453,7 +457,7 @@ describe('Consumption', () => { ]; // reserve - ascent from 6. segment = ((4 bar * 1 bar/min * 2 min) + (2.5 b * 1 bar/min * 3 min)) * 3 - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); expect(tank1.reserve).toEqual(47); }); @@ -474,7 +478,7 @@ describe('Consumption', () => { ]; // reserve - ascent from 6. segment = ((3 b * 1 bar/min * 2 min) + (2 b * 1 bar/min * 2 min)) * 3 - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); expect(tank1.reserve).toEqual(30); }); @@ -494,7 +498,7 @@ describe('Consumption', () => { // reserve - ascent from 2. segment // ((4 b * 2 min * 1 bar/min) + (2.5 b * 3 min * 1 bar/min)) * 3 - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); expect(tank1.reserve).toEqual(47); }); @@ -519,7 +523,7 @@ describe('Consumption', () => { const s5 = new Segment(6, 0, tank1.gas, Time.oneMinute * 2); const profile = [s1, s2, s3, s4, s5]; - consumption.consumeFromTanks(profile, options2, tanks, diver); + consumption.consumeFromTanks(profile, options2, tanks, consumtionOptions); // custom ascent is calculated, since no used defined tank was used during ascent // 40n 120s, 40-6 600s, 6-3 18s, 3-3 285, 3-0 18 expect(tank1.reserve).toEqual(149); @@ -538,7 +542,7 @@ describe('Consumption', () => { const s5 = new Segment(6, 0, tank2, Time.oneMinute * 2); // 1.3 * 2 * 2 = 5.2 const segments = [s1, s2, s3, s4, s5]; - consumption.consumeFromTanks(segments, options2, tanks, diver); + consumption.consumeFromTanks(segments, options2, tanks, consumtionOptions); it('counts the consumption', () => { expect(tank1.consumed).toEqual(110); @@ -565,7 +569,7 @@ describe('Consumption', () => { const s3 = new Segment(5, 0, tank2, Time.oneMinute); const segments = [s1, s2, s3]; - consumption.consumeFromTanks(segments, options2, tanks, diver); + consumption.consumeFromTanks(segments, options2, tanks, consumtionOptions); it('First tank', () => { expect(tank1.reserve).toEqual(30); diff --git a/projects/scuba-physics/src/lib/consumption.ts b/projects/scuba-physics/src/lib/consumption.ts index dd4dea33..6c50d03d 100644 --- a/projects/scuba-physics/src/lib/consumption.ts +++ b/projects/scuba-physics/src/lib/consumption.ts @@ -88,15 +88,15 @@ export class Consumption { * the array needs have at least 3 items (descent, swim, ascent). * @param options Not null profile behavior options. * @param tanks All tanks used to generate the profile, their gases need to fit all used in segments param - * @param diver diver respiratory minute volumes in Liters/minute. + * @param consumptionOptions Not null definition how to consume the gases. */ - public consumeFromTanks(segments: Segment[], options: Options, tanks: Tank[], diver: Diver): void { + public consumeFromTanks(segments: Segment[], options: Options, tanks: Tank[], consumptionOptions: ConsumptionOptions): void { if (segments.length < 2) { throw new Error('Profile needs to contain at least 2 segments.'); } const emergencyAscent = PlanFactory.emergencyAscent(segments, options, tanks); - this.consumeFromTanks2(segments, emergencyAscent, tanks, diver); + this.consumeFromTanks2(segments, emergencyAscent, tanks, consumptionOptions); } /** @@ -107,29 +107,30 @@ export class Consumption { * @param emergencyAscent Not null array of segments representing the special ascent. * Doesn't have to be part of the segments parameter value, since in emergency we need current state. * @param tanks All tanks used to generate the profile, their gases need to fit all used in segments param - * @param diver diver respiratory minute volumes in Liters/minute. + * @param consumptionOptions Not null consumption definition. */ - public consumeFromTanks2(segments: Segment[], emergencyAscent: Segment[], tanks: Tank[], diver: Diver): void { + public consumeFromTanks2(segments: Segment[], emergencyAscent: Segment[], tanks: Tank[], consumptionOptions: ConsumptionOptions): void { if (segments.length < 2) { throw new Error('Profile needs to contain at least 2 segments.'); } Tanks.resetConsumption(tanks); - const remainToConsume = this.consumeByTanks(segments, diver.rmv); - this.consumeByGases(segments, tanks, diver.rmv, remainToConsume); - this.updateReserve(emergencyAscent, tanks, diver.teamStressRmv, Consumption.defaultPrimaryReserve, Consumption.defaultStageReserve); + const remainToConsume = this.consumeByTanks(segments, consumptionOptions.diver.rmv); + this.consumeByGases(segments, tanks, consumptionOptions.diver.rmv, remainToConsume); + this.updateReserve(emergencyAscent, tanks, consumptionOptions); } /** * We cant provide this method for multilevel dives, because we don't know which segment to extend * @param sourceSegments User defined profile * @param tanks The tanks used during the dive to check available gases - * @param diver Consumption SAC definition + * @param consumptionOptions Not null consumption definition * @param options ppO2 definitions needed to estimate ascent profile * @returns Number of minutes representing maximum time we can spend as bottom time. * Returns 0 in case the duration is shorter than user defined segments. */ - public calculateMaxBottomTime(sourceSegments: Segments, tanks: Tank[], diver: Diver, options: Options): number { + public calculateMaxBottomTime(sourceSegments: Segments, tanks: Tank[], + consumptionOptions: ConsumptionOptions, options: Options): number { const testSegments = this.createTestProfile(sourceSegments); const addedSegment = testSegments.last(); @@ -140,7 +141,7 @@ export class Consumption { maxValue: Time.oneDay, doWork: (newValue: number) => { addedSegment.duration = newValue; - this.consumeFromProfile(testSegments, tanks, diver, options); + this.consumeFromProfile(testSegments, tanks, consumptionOptions, options); }, meetsCondition: () => Tanks.haveReserve(tanks) }; @@ -158,9 +159,9 @@ export class Consumption { return Precision.floor(totalDuration); } - private consumeFromProfile(testSegments: Segments, tanks: Tank[], diver: Diver, options: Options) { + private consumeFromProfile(testSegments: Segments, tanks: Tank[], consumptionOptions: ConsumptionOptions, options: Options) { const profile = Consumption.calculateDecompression(testSegments, tanks, options); - this.consumeFromTanks(profile.segments, options, tanks, diver); + this.consumeFromTanks(profile.segments, options, tanks, consumptionOptions); } private createTestProfile(sourceSegments: Segments): Segments { @@ -170,10 +171,10 @@ export class Consumption { return testSegments; } - private updateReserve(ascent: Segment[], tanks: Tank[], stressSac: number, firstTankReserve: number, stageTankReserve: number): void { + private updateReserve(ascent: Segment[], tanks: Tank[], options: ConsumptionOptions): void { // here the consumed during emergency ascent means reserve // take all segments, because we expect all segments are not user defined => don't have tank assigned - const gasesConsumed: Map = this.toBeConsumed(ascent, stressSac, () => true); + const gasesConsumed: Map = this.toBeConsumed(ascent, options.diver.teamStressRmv, () => true); // add the reserve from opposite order than consumed gas for (let index = 0; index <= tanks.length - 1; index++) { @@ -249,9 +250,9 @@ export class Consumption { return consumedLiters; } - private toBeConsumed(segments: Segment[], sac: number, includeSegment: (segment: Segment) => boolean): Map { + private toBeConsumed(segments: Segment[], rmv: number, includeSegment: (segment: Segment) => boolean): Map { const emptyConsumptions = new Map(); - return this.toBeConsumedYet(segments, sac, emptyConsumptions, includeSegment); + return this.toBeConsumedYet(segments, rmv, emptyConsumptions, includeSegment); } private toBeConsumedYet(segments: Segment[], sac: number,