iapws-if97 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +134 -94
- package/dist/backward/fixed-temperature-solver.d.ts +6 -6
- package/dist/backward/fixed-temperature-solver.js +34 -8
- package/dist/backward/hs.d.ts +1 -2
- package/dist/backward/hs.js +67 -46
- package/dist/backward/ph.d.ts +8 -5
- package/dist/backward/ph.js +43 -46
- package/dist/backward/ps.d.ts +0 -1
- package/dist/backward/ps.js +30 -18
- package/dist/backward/solution-validation.d.ts +0 -1
- package/dist/backward/solution-validation.js +33 -5
- package/dist/backward/th.d.ts +0 -1
- package/dist/backward/th.js +4 -6
- package/dist/backward/thermodynamic-derivatives.d.ts +17 -0
- package/dist/backward/thermodynamic-derivatives.js +27 -0
- package/dist/backward/tolerances.d.ts +0 -1
- package/dist/backward/tolerances.js +0 -1
- package/dist/backward/ts.d.ts +0 -1
- package/dist/backward/ts.js +4 -6
- package/dist/boundaries.d.ts +0 -1
- package/dist/boundaries.js +0 -1
- package/dist/constants.d.ts +0 -9
- package/dist/constants.js +3 -13
- package/dist/core/input-validation.d.ts +0 -1
- package/dist/core/input-validation.js +0 -1
- package/dist/core/public-normalization.d.ts +0 -1
- package/dist/core/public-normalization.js +0 -1
- package/dist/core/region-detector.d.ts +0 -1
- package/dist/core/region-detector.js +234 -67
- package/dist/core/region3-pt.d.ts +12 -1
- package/dist/core/region3-pt.js +38 -9
- package/dist/core/solve-input-normalization.d.ts +0 -1
- package/dist/core/solve-input-normalization.js +0 -1
- package/dist/core/solver.d.ts +0 -1
- package/dist/core/solver.js +0 -1
- package/dist/detect.d.ts +0 -1
- package/dist/detect.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/regions/boundaries.d.ts +0 -1
- package/dist/regions/boundaries.js +0 -1
- package/dist/regions/region1.d.ts +0 -1
- package/dist/regions/region1.js +0 -1
- package/dist/regions/region2.d.ts +0 -1
- package/dist/regions/region2.js +0 -1
- package/dist/regions/region3-data.d.ts +0 -1
- package/dist/regions/region3-data.js +0 -1
- package/dist/regions/region3-eval.d.ts +0 -1
- package/dist/regions/region3-eval.js +0 -1
- package/dist/regions/region3-subregions.d.ts +0 -1
- package/dist/regions/region3-subregions.js +0 -1
- package/dist/regions/region3.d.ts +0 -1
- package/dist/regions/region3.js +0 -1
- package/dist/regions/region4.d.ts +0 -1
- package/dist/regions/region4.js +0 -1
- package/dist/regions/region5.d.ts +0 -1
- package/dist/regions/region5.js +0 -1
- package/dist/regions.d.ts +0 -1
- package/dist/regions.js +0 -1
- package/dist/saturation/common.d.ts +0 -1
- package/dist/saturation/common.js +6 -13
- package/dist/saturation/region4-boundaries.d.ts +0 -5
- package/dist/saturation/region4-boundaries.js +4 -5
- package/dist/saturation/region4-hs.d.ts +2 -1
- package/dist/saturation/region4-hs.js +277 -108
- package/dist/saturation/two-phase.d.ts +0 -1
- package/dist/saturation/two-phase.js +0 -1
- package/dist/saturation.d.ts +0 -1
- package/dist/saturation.js +0 -1
- package/dist/solvers/bracketed-newton.d.ts +5 -4
- package/dist/solvers/bracketed-newton.js +5 -6
- package/dist/solvers/damped-newton-2d.d.ts +12 -0
- package/dist/solvers/damped-newton-2d.js +68 -0
- package/dist/solvers/newton-raphson.d.ts +0 -1
- package/dist/solvers/newton-raphson.js +0 -1
- package/dist/transport/properties.d.ts +0 -1
- package/dist/transport/properties.js +0 -1
- package/dist/transport.d.ts +0 -1
- package/dist/transport.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +19 -10
- package/dist/backward/fixed-temperature-solver.d.ts.map +0 -1
- package/dist/backward/fixed-temperature-solver.js.map +0 -1
- package/dist/backward/hs.d.ts.map +0 -1
- package/dist/backward/hs.js.map +0 -1
- package/dist/backward/objective-normalization.d.ts +0 -7
- package/dist/backward/objective-normalization.d.ts.map +0 -1
- package/dist/backward/objective-normalization.js +0 -8
- package/dist/backward/objective-normalization.js.map +0 -1
- package/dist/backward/ph.d.ts.map +0 -1
- package/dist/backward/ph.js.map +0 -1
- package/dist/backward/ps.d.ts.map +0 -1
- package/dist/backward/ps.js.map +0 -1
- package/dist/backward/solution-validation.d.ts.map +0 -1
- package/dist/backward/solution-validation.js.map +0 -1
- package/dist/backward/th.d.ts.map +0 -1
- package/dist/backward/th.js.map +0 -1
- package/dist/backward/tolerances.d.ts.map +0 -1
- package/dist/backward/tolerances.js.map +0 -1
- package/dist/backward/ts.d.ts.map +0 -1
- package/dist/backward/ts.js.map +0 -1
- package/dist/boundaries.d.ts.map +0 -1
- package/dist/boundaries.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/core/input-validation.d.ts.map +0 -1
- package/dist/core/input-validation.js.map +0 -1
- package/dist/core/public-normalization.d.ts.map +0 -1
- package/dist/core/public-normalization.js.map +0 -1
- package/dist/core/region-detector.d.ts.map +0 -1
- package/dist/core/region-detector.js.map +0 -1
- package/dist/core/region3-pt.d.ts.map +0 -1
- package/dist/core/region3-pt.js.map +0 -1
- package/dist/core/solve-input-normalization.d.ts.map +0 -1
- package/dist/core/solve-input-normalization.js.map +0 -1
- package/dist/core/solver.d.ts.map +0 -1
- package/dist/core/solver.js.map +0 -1
- package/dist/detect.d.ts.map +0 -1
- package/dist/detect.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/regions/boundaries.d.ts.map +0 -1
- package/dist/regions/boundaries.js.map +0 -1
- package/dist/regions/region1.d.ts.map +0 -1
- package/dist/regions/region1.js.map +0 -1
- package/dist/regions/region2.d.ts.map +0 -1
- package/dist/regions/region2.js.map +0 -1
- package/dist/regions/region3-data.d.ts.map +0 -1
- package/dist/regions/region3-data.js.map +0 -1
- package/dist/regions/region3-eval.d.ts.map +0 -1
- package/dist/regions/region3-eval.js.map +0 -1
- package/dist/regions/region3-subregions.d.ts.map +0 -1
- package/dist/regions/region3-subregions.js.map +0 -1
- package/dist/regions/region3.d.ts.map +0 -1
- package/dist/regions/region3.js.map +0 -1
- package/dist/regions/region4.d.ts.map +0 -1
- package/dist/regions/region4.js.map +0 -1
- package/dist/regions/region5.d.ts.map +0 -1
- package/dist/regions/region5.js.map +0 -1
- package/dist/regions.d.ts.map +0 -1
- package/dist/regions.js.map +0 -1
- package/dist/saturation/common.d.ts.map +0 -1
- package/dist/saturation/common.js.map +0 -1
- package/dist/saturation/region4-boundaries.d.ts.map +0 -1
- package/dist/saturation/region4-boundaries.js.map +0 -1
- package/dist/saturation/region4-hs.d.ts.map +0 -1
- package/dist/saturation/region4-hs.js.map +0 -1
- package/dist/saturation/two-phase.d.ts.map +0 -1
- package/dist/saturation/two-phase.js.map +0 -1
- package/dist/saturation.d.ts.map +0 -1
- package/dist/saturation.js.map +0 -1
- package/dist/solvers/bracketed-newton.d.ts.map +0 -1
- package/dist/solvers/bracketed-newton.js.map +0 -1
- package/dist/solvers/nelder-mead.d.ts +0 -17
- package/dist/solvers/nelder-mead.d.ts.map +0 -1
- package/dist/solvers/nelder-mead.js +0 -116
- package/dist/solvers/nelder-mead.js.map +0 -1
- package/dist/solvers/newton-raphson.d.ts.map +0 -1
- package/dist/solvers/newton-raphson.js.map +0 -1
- package/dist/transport/properties.d.ts.map +0 -1
- package/dist/transport/properties.js.map +0 -1
- package/dist/transport.d.ts.map +0 -1
- package/dist/transport.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
|
@@ -7,9 +7,9 @@ import { boundary23_P_to_T, boundary23_T_to_P } from '../regions/boundaries.js';
|
|
|
7
7
|
import { region1 } from '../regions/region1.js';
|
|
8
8
|
import { region2 } from '../regions/region2.js';
|
|
9
9
|
import { region5 } from '../regions/region5.js';
|
|
10
|
-
import { saturationEndpointsAtPressure, saturationEndpointsAtTemperature } from '../saturation/common.js';
|
|
10
|
+
import { saturationEndpointsAtPressure, saturationEndpointsAtTemperature, } from '../saturation/common.js';
|
|
11
11
|
import { tryRegion4HSState } from '../saturation/region4-hs.js';
|
|
12
|
-
import { Region } from '../types.js';
|
|
12
|
+
import { IF97Error, Region } from '../types.js';
|
|
13
13
|
import { bracketedNewton } from '../solvers/bracketed-newton.js';
|
|
14
14
|
import { solveRegion3PTBasic } from './region3-pt.js';
|
|
15
15
|
/**
|
|
@@ -183,7 +183,7 @@ function detectRegionByTemperatureProperty(T, value, property) {
|
|
|
183
183
|
? Region.Region2
|
|
184
184
|
: -1;
|
|
185
185
|
}
|
|
186
|
-
if (T
|
|
186
|
+
if (T >= C.Tc) {
|
|
187
187
|
const p23 = boundary23_T_to_P(T);
|
|
188
188
|
if (inRange(value, propertyOf(region2(C.P_MIN, T), property), propertyOf(region2(p23, T), property))) {
|
|
189
189
|
return Region.Region2;
|
|
@@ -241,7 +241,7 @@ export function detectRegionTH(T, h) {
|
|
|
241
241
|
? Region.Region2
|
|
242
242
|
: -1;
|
|
243
243
|
}
|
|
244
|
-
if (T
|
|
244
|
+
if (T >= C.Tc) {
|
|
245
245
|
const p23 = boundary23_T_to_P(T);
|
|
246
246
|
if (inRange(h, region2(C.P_MIN, T).enthalpy, region2(p23, T).enthalpy)) {
|
|
247
247
|
return Region.Region2;
|
|
@@ -331,8 +331,17 @@ function h1Sat(s) {
|
|
|
331
331
|
* Region 1 enthalpy maximum would admit impossible h-s pairs.
|
|
332
332
|
*/
|
|
333
333
|
function h1UpperBoundAtPmax(s) {
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
let cachedTemperature = Number.NaN;
|
|
335
|
+
let cachedState = null;
|
|
336
|
+
const stateAt = (T) => {
|
|
337
|
+
if (T !== cachedTemperature || cachedState === null) {
|
|
338
|
+
cachedTemperature = T;
|
|
339
|
+
cachedState = region1(C.P_MAX, T);
|
|
340
|
+
}
|
|
341
|
+
return cachedState;
|
|
342
|
+
};
|
|
343
|
+
const temperature = bracketedNewton((T) => stateAt(T).entropy - s, C.T_MIN, C.R2_T_MIN, 350, { derivative: (T) => (stateAt(T).cp ?? Number.NaN) / T });
|
|
344
|
+
return stateAt(temperature).enthalpy;
|
|
336
345
|
}
|
|
337
346
|
/**
|
|
338
347
|
* Region 1 / Region 3 boundary enthalpy (Eq. 1, B13).
|
|
@@ -465,6 +474,175 @@ function backward2cPhs(h, s) {
|
|
|
465
474
|
}
|
|
466
475
|
return 100 * P * P * P * P;
|
|
467
476
|
}
|
|
477
|
+
const HS_R1_PMAX_623 = region1(C.P_MAX, C.R2_T_MIN);
|
|
478
|
+
const HS_R1_B23_MIN = region1(C.B23_P_MIN, C.R2_T_MIN);
|
|
479
|
+
const HS_R2_PMAX_TMAX = region2(C.P_MAX, C.R2_T_MAX);
|
|
480
|
+
const HS_R2_PMIN_TMAX = region2(C.P_MIN, C.R2_T_MAX);
|
|
481
|
+
const HS_R2_B23_MIN = region2(C.B23_P_MIN, C.R2_T_MIN);
|
|
482
|
+
const HS_R2_B23_MAX = region2(C.P_MAX, C.B23_T_MAX);
|
|
483
|
+
const HS_R2_R5_PMAX = region2(C.R5_P_MAX, C.R2_T_MAX);
|
|
484
|
+
const HS_R5_LOWER = region5(C.R5_P_MAX, C.R5_T_MIN);
|
|
485
|
+
const HS_R5_PMIN_TMIN = region5(C.P_MIN, C.R5_T_MIN);
|
|
486
|
+
const HS_R5_UPPER = region5(C.P_MIN, C.R5_T_MAX);
|
|
487
|
+
const HS_TRIPLE_POINT_ENDPOINTS = saturationEndpointsAtTemperature(C.Tt);
|
|
488
|
+
const HS_TRIPLE_LIQUID = HS_TRIPLE_POINT_ENDPOINTS.liquid;
|
|
489
|
+
const HS_TRIPLE_VAPOR = HS_TRIPLE_POINT_ENDPOINTS.vapor;
|
|
490
|
+
const HS_S13 = HS_R1_PMAX_623.entropy;
|
|
491
|
+
const HS_S13S = HS_R1_B23_MIN.entropy;
|
|
492
|
+
const HS_S_TP_MAX = HS_R2_PMAX_TMAX.entropy;
|
|
493
|
+
const HS_S2AB = region2(C.R2_P_CRT, C.R2_T_MAX).entropy;
|
|
494
|
+
// Covers the 0.0073 kJ/kg maximum boundary-equation error reported by IAPWS.
|
|
495
|
+
const HS_SATURATION_BOUNDARY_CONFIRMATION_BAND = 0.01;
|
|
496
|
+
const HS_SATURATION_BOUNDARY_ENTHALPY_TOLERANCE = 1e-8;
|
|
497
|
+
const HS_SATURATION_INVERSION_UPPER_T = C.Tc - C.REGION4_SUBCRITICAL_TEMPERATURE_MARGIN;
|
|
498
|
+
function hSatLow(s) {
|
|
499
|
+
return HS_TRIPLE_LIQUID.enthalpy +
|
|
500
|
+
(s - HS_TRIPLE_LIQUID.entropy) /
|
|
501
|
+
(HS_TRIPLE_VAPOR.entropy - HS_TRIPLE_LIQUID.entropy) *
|
|
502
|
+
(HS_TRIPLE_VAPOR.enthalpy - HS_TRIPLE_LIQUID.enthalpy);
|
|
503
|
+
}
|
|
504
|
+
function phaseState(endpoints, phase) {
|
|
505
|
+
return phase === 'liquid' ? endpoints.liquid : endpoints.vapor;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Invert one saturation endpoint's monotone entropy curve without scanning.
|
|
509
|
+
* The Clapeyron relation supplies dp_sat/dT, and the Gibbs Maxwell relation
|
|
510
|
+
* supplies the endpoint derivative along that curve.
|
|
511
|
+
*/
|
|
512
|
+
function saturationBoundaryEnthalpyAtEntropy(s, phase) {
|
|
513
|
+
let lower = C.Tt;
|
|
514
|
+
let upper = HS_SATURATION_INVERSION_UPPER_T;
|
|
515
|
+
const tripleEntropy = phaseState(HS_TRIPLE_POINT_ENDPOINTS, phase).entropy;
|
|
516
|
+
const criticalEntropy = C.R3_S_CRT;
|
|
517
|
+
const fraction = phase === 'liquid'
|
|
518
|
+
? (s - tripleEntropy) / (criticalEntropy - tripleEntropy)
|
|
519
|
+
: (tripleEntropy - s) / (tripleEntropy - criticalEntropy);
|
|
520
|
+
const boundedFraction = Math.max(0, Math.min(1, fraction));
|
|
521
|
+
let temperature = lower + boundedFraction * (upper - lower);
|
|
522
|
+
for (let iteration = 0; iteration < 12; iteration += 1) {
|
|
523
|
+
const endpoints = saturationEndpointsAtTemperature(temperature);
|
|
524
|
+
const state = phaseState(endpoints, phase);
|
|
525
|
+
const residual = state.entropy - s;
|
|
526
|
+
if (Math.abs(residual) <= 1e-12 * Math.max(1, Math.abs(s))) {
|
|
527
|
+
return state.enthalpy;
|
|
528
|
+
}
|
|
529
|
+
const residualIncreasesWithTemperature = phase === 'liquid';
|
|
530
|
+
if ((residual < 0) === residualIncreasesWithTemperature)
|
|
531
|
+
lower = temperature;
|
|
532
|
+
else
|
|
533
|
+
upper = temperature;
|
|
534
|
+
if (upper - lower <= 10 * C.REGION4_TEMPERATURE_TOLERANCE) {
|
|
535
|
+
return state.enthalpy;
|
|
536
|
+
}
|
|
537
|
+
const entropySpan = endpoints.vapor.entropy - endpoints.liquid.entropy;
|
|
538
|
+
const volumeSpan = endpoints.vapor.specificVolume - endpoints.liquid.specificVolume;
|
|
539
|
+
const dpSatDT = entropySpan / (1000 * volumeSpan);
|
|
540
|
+
const cp = state.cp;
|
|
541
|
+
const alpha = state.isobaricExpansion;
|
|
542
|
+
const derivative = cp === null || alpha === null
|
|
543
|
+
? Number.NaN
|
|
544
|
+
: cp / temperature - 1000 * state.specificVolume * alpha * dpSatDT;
|
|
545
|
+
const candidate = temperature - residual / derivative;
|
|
546
|
+
temperature = Number.isFinite(candidate) && candidate > lower && candidate < upper
|
|
547
|
+
? candidate
|
|
548
|
+
: (lower + upper) / 2;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
/** Replace the supplementary polynomial only in its narrow error band. */
|
|
553
|
+
function refinedSaturationBoundaryEnthalpy(h, s, approximateBoundaryH) {
|
|
554
|
+
if (Math.abs(h - approximateBoundaryH) > HS_SATURATION_BOUNDARY_CONFIRMATION_BAND) {
|
|
555
|
+
return approximateBoundaryH;
|
|
556
|
+
}
|
|
557
|
+
const phase = s <= C.R3_S_CRT ? 'liquid' : 'vapor';
|
|
558
|
+
let exactBoundaryH;
|
|
559
|
+
try {
|
|
560
|
+
exactBoundaryH = saturationBoundaryEnthalpyAtEntropy(s, phase);
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
if (error instanceof IF97Error)
|
|
564
|
+
return approximateBoundaryH;
|
|
565
|
+
throw error;
|
|
566
|
+
}
|
|
567
|
+
return exactBoundaryH === null
|
|
568
|
+
? approximateBoundaryH
|
|
569
|
+
: exactBoundaryH + HS_SATURATION_BOUNDARY_ENTHALPY_TOLERANCE;
|
|
570
|
+
}
|
|
571
|
+
function confirmedRegion4BoundaryState(h, s, approximateBoundaryH) {
|
|
572
|
+
if (Math.abs(h - approximateBoundaryH) > HS_SATURATION_BOUNDARY_CONFIRMATION_BAND) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
return tryRegion4HSState(h, s) !== null;
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
if (error instanceof IF97Error)
|
|
580
|
+
return false;
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function r2R5BoundaryEnthalpy(s, lowEntropy, highEntropy, stateAtPressure) {
|
|
585
|
+
if (s < lowEntropy || s > highEntropy)
|
|
586
|
+
return null;
|
|
587
|
+
let lower = C.P_MIN;
|
|
588
|
+
let upper = C.R5_P_MAX;
|
|
589
|
+
const fraction = (highEntropy - s) / (highEntropy - lowEntropy);
|
|
590
|
+
let p = Math.exp(Math.log(lower) + fraction * (Math.log(upper) - Math.log(lower)));
|
|
591
|
+
for (let iteration = 0; iteration < 12; iteration += 1) {
|
|
592
|
+
const state = stateAtPressure(p);
|
|
593
|
+
const residual = state.entropy - s;
|
|
594
|
+
if (Math.abs(residual) <= 1e-12 * Math.max(1, Math.abs(s))) {
|
|
595
|
+
return state.enthalpy;
|
|
596
|
+
}
|
|
597
|
+
if (residual > 0)
|
|
598
|
+
lower = p;
|
|
599
|
+
else
|
|
600
|
+
upper = p;
|
|
601
|
+
const alpha = state.isobaricExpansion;
|
|
602
|
+
const derivative = alpha === null ? Number.NaN : -1000 * state.specificVolume * alpha;
|
|
603
|
+
const candidate = p - residual / derivative;
|
|
604
|
+
p = Number.isFinite(candidate) && candidate > lower && candidate < upper
|
|
605
|
+
? candidate
|
|
606
|
+
: (lower + upper) / 2;
|
|
607
|
+
}
|
|
608
|
+
return stateAtPressure(p).enthalpy;
|
|
609
|
+
}
|
|
610
|
+
function classifyRegion5HS(h, s) {
|
|
611
|
+
if (!(HS_R5_LOWER.entropy < s && s <= HS_R5_UPPER.entropy &&
|
|
612
|
+
HS_R5_LOWER.enthalpy < h && h <= HS_R5_UPPER.enthalpy)) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
const region2BoundaryH = r2R5BoundaryEnthalpy(s, HS_R2_R5_PMAX.entropy, HS_R2_PMIN_TMAX.entropy, (p) => region2(p, C.R2_T_MAX));
|
|
616
|
+
const region5BoundaryH = r2R5BoundaryEnthalpy(s, HS_R5_LOWER.entropy, HS_R5_PMIN_TMIN.entropy, (p) => region5(p, C.R5_T_MIN));
|
|
617
|
+
if (region2BoundaryH === null) {
|
|
618
|
+
return region5BoundaryH === null || h > region5BoundaryH
|
|
619
|
+
? Region.Region5
|
|
620
|
+
: null;
|
|
621
|
+
}
|
|
622
|
+
if (region5BoundaryH === null) {
|
|
623
|
+
return h > region2BoundaryH ? Region.Region5 : null;
|
|
624
|
+
}
|
|
625
|
+
const tolerance = 1e-10 * Math.max(1, Math.abs(h), Math.abs(region2BoundaryH), Math.abs(region5BoundaryH));
|
|
626
|
+
if (region5BoundaryH > region2BoundaryH + tolerance) {
|
|
627
|
+
if (h > region5BoundaryH + tolerance)
|
|
628
|
+
return Region.Region5;
|
|
629
|
+
if (h > region2BoundaryH + tolerance)
|
|
630
|
+
return -1;
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
if (region2BoundaryH > region5BoundaryH + tolerance) {
|
|
634
|
+
if (h > region2BoundaryH + tolerance)
|
|
635
|
+
return Region.Region5;
|
|
636
|
+
if (h <= region5BoundaryH + tolerance)
|
|
637
|
+
return null;
|
|
638
|
+
// The independently fitted Region 2 and 5 equations overlap slightly at
|
|
639
|
+
// 1073.15 K. H-S is non-unique there; choose the branch nearest its seam.
|
|
640
|
+
return h <= (region2BoundaryH + region5BoundaryH) / 2
|
|
641
|
+
? Region.Region5
|
|
642
|
+
: null;
|
|
643
|
+
}
|
|
644
|
+
return h > region2BoundaryH + tolerance ? Region.Region5 : null;
|
|
645
|
+
}
|
|
468
646
|
/**
|
|
469
647
|
* Detect the IAPWS-IF97 region for given H and S.
|
|
470
648
|
* Implements the zone-based detection from the IAPWS supplementary release.
|
|
@@ -474,50 +652,30 @@ function backward2cPhs(h, s) {
|
|
|
474
652
|
* @returns Region number (1–5) or -1 if out of range
|
|
475
653
|
*/
|
|
476
654
|
export function detectRegionHS(h, s) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const s13s = region1(C.B23_P_MIN, C.R2_T_MIN).entropy;
|
|
480
|
-
const sTPmax = region2(C.P_MAX, C.R2_T_MAX).entropy;
|
|
481
|
-
const s2ab = region2(C.R2_P_CRT, C.R2_T_MAX).entropy;
|
|
482
|
-
const region4State = tryRegion4HSState(h, s);
|
|
483
|
-
if (region4State !== null) {
|
|
655
|
+
if (s >= HS_TRIPLE_LIQUID.entropy && s <= HS_TRIPLE_VAPOR.entropy &&
|
|
656
|
+
isClose(h, hSatLow(s))) {
|
|
484
657
|
return Region.Region4;
|
|
485
658
|
}
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const s4l = _sL.entropy;
|
|
490
|
-
const _sV = triplePointEndpoints.vapor;
|
|
491
|
-
const h4v = _sV.enthalpy;
|
|
492
|
-
const s4v = _sV.entropy;
|
|
493
|
-
const _Pmax = region2(C.P_MIN, C.R2_T_MAX);
|
|
494
|
-
const smax = _Pmax.entropy;
|
|
495
|
-
// Check Region 5 first (before R2 overlap zones)
|
|
496
|
-
const r5lo = region5(C.R5_P_MAX, C.R5_T_MIN);
|
|
497
|
-
const r5hi = region5(C.P_MIN, C.R5_T_MAX);
|
|
498
|
-
if (r5lo.entropy < s && s <= r5hi.entropy &&
|
|
499
|
-
r5lo.enthalpy < h && h <= r5hi.enthalpy) {
|
|
500
|
-
return Region.Region5;
|
|
501
|
-
}
|
|
502
|
-
// Helper: linear interpolation for saturation lower bound
|
|
503
|
-
const hSatLow = (sVal) => h4l + (sVal - s4l) / (s4v - s4l) * (h4v - h4l);
|
|
659
|
+
const highTemperatureRegion = classifyRegion5HS(h, s);
|
|
660
|
+
if (highTemperatureRegion !== null)
|
|
661
|
+
return highTemperatureRegion;
|
|
504
662
|
// Zone 1: smin <= s <= s13 (Region 1 / Region 4)
|
|
505
|
-
if (s >=
|
|
663
|
+
if (s >= HS_TRIPLE_LIQUID.entropy && s <= HS_S13) {
|
|
506
664
|
const hmin = hSatLow(s);
|
|
507
|
-
const hs = h1Sat(s);
|
|
665
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h1Sat(s));
|
|
508
666
|
const hmax = h1UpperBoundAtPmax(s);
|
|
509
|
-
if (h >= hmin && h
|
|
667
|
+
if (h >= hmin && h <= hs)
|
|
510
668
|
return Region.Region4;
|
|
511
669
|
if (h >= hs && h <= hmax)
|
|
512
670
|
return Region.Region1;
|
|
513
671
|
}
|
|
514
672
|
// Zone 2: s13 < s <= s13s (Region 1 / Region 3 / Region 4)
|
|
515
|
-
else if (s >
|
|
673
|
+
else if (s > HS_S13 && s <= HS_S13S) {
|
|
516
674
|
const hmin = hSatLow(s);
|
|
517
|
-
const hs = h1Sat(s);
|
|
675
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h1Sat(s));
|
|
518
676
|
const h13 = h13Boundary(s);
|
|
519
|
-
const hmax =
|
|
520
|
-
if (h >= hmin && h
|
|
677
|
+
const hmax = 2800; // Region 3 upper bound
|
|
678
|
+
if (h >= hmin && h <= hs)
|
|
521
679
|
return Region.Region4;
|
|
522
680
|
if (h >= hs && h < h13)
|
|
523
681
|
return Region.Region1;
|
|
@@ -525,11 +683,16 @@ export function detectRegionHS(h, s) {
|
|
|
525
683
|
return Region.Region3;
|
|
526
684
|
}
|
|
527
685
|
// Zone 3: s13s < s <= R3_CRT_S (Region 3 / Region 4)
|
|
528
|
-
else if (s >
|
|
686
|
+
else if (s > HS_S13S && s <= C.R3_S_CRT) {
|
|
529
687
|
const hmin = hSatLow(s);
|
|
530
|
-
const hs = h3aSat(s);
|
|
531
688
|
const hmax = 2800; // generous upper bound for R3
|
|
532
|
-
|
|
689
|
+
const hs = h3aSat(s);
|
|
690
|
+
const confirmedRegion4 = confirmedRegion4BoundaryState(h, s, hs);
|
|
691
|
+
if (confirmedRegion4 === true)
|
|
692
|
+
return Region.Region4;
|
|
693
|
+
if (confirmedRegion4 === false && h >= hmin && h <= hmax)
|
|
694
|
+
return Region.Region3;
|
|
695
|
+
if (h >= hmin && h <= hs)
|
|
533
696
|
return Region.Region4;
|
|
534
697
|
if (h >= hs && h <= hmax)
|
|
535
698
|
return Region.Region3;
|
|
@@ -537,9 +700,14 @@ export function detectRegionHS(h, s) {
|
|
|
537
700
|
// Zone 4: R3_CRT_S < s < B23_S_CURVE_MIN (Region 3 / Region 4)
|
|
538
701
|
else if (s > C.R3_S_CRT && s < C.B23_S_CURVE_MIN) {
|
|
539
702
|
const hmin = hSatLow(s);
|
|
540
|
-
const hs = h2c3bSat(s);
|
|
541
703
|
const hmax = 2800; // generous upper bound for R3
|
|
542
|
-
|
|
704
|
+
const hs = h2c3bSat(s);
|
|
705
|
+
const confirmedRegion4 = confirmedRegion4BoundaryState(h, s, hs);
|
|
706
|
+
if (confirmedRegion4 === true)
|
|
707
|
+
return Region.Region4;
|
|
708
|
+
if (confirmedRegion4 === false && h >= hmin && h <= hmax)
|
|
709
|
+
return Region.Region3;
|
|
710
|
+
if (h >= hmin && h <= hs)
|
|
543
711
|
return Region.Region4;
|
|
544
712
|
if (h >= hs && h <= hmax)
|
|
545
713
|
return Region.Region3;
|
|
@@ -547,11 +715,11 @@ export function detectRegionHS(h, s) {
|
|
|
547
715
|
// Zone 5: B23_S_CURVE_MIN <= s < B23_S_CURVE_MAX (Region 2 / Region 3 / Region 4)
|
|
548
716
|
else if (s >= C.B23_S_CURVE_MIN && s < C.B23_S_CURVE_MAX) {
|
|
549
717
|
const hmin = hSatLow(s);
|
|
550
|
-
const hs = h2c3bSat(s);
|
|
551
|
-
const h23max =
|
|
552
|
-
const h23min =
|
|
553
|
-
const hmax =
|
|
554
|
-
if (hmin <= h && h
|
|
718
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2c3bSat(s));
|
|
719
|
+
const h23max = HS_R2_B23_MAX.enthalpy;
|
|
720
|
+
const h23min = HS_R2_B23_MIN.enthalpy;
|
|
721
|
+
const hmax = HS_R2_PMAX_TMAX.enthalpy;
|
|
722
|
+
if (hmin <= h && h <= hs)
|
|
555
723
|
return Region.Region4;
|
|
556
724
|
if (hs <= h && h < h23min)
|
|
557
725
|
return Region.Region3;
|
|
@@ -570,50 +738,49 @@ export function detectRegionHS(h, s) {
|
|
|
570
738
|
// Zone 6: B23_S_CURVE_MAX <= s < R2_S_CRT (Region 2 / Region 4)
|
|
571
739
|
else if (s >= C.B23_S_CURVE_MAX && s < C.R2_S_CRT) {
|
|
572
740
|
const hmin = hSatLow(s);
|
|
573
|
-
const hs = h2c3bSat(s);
|
|
574
|
-
const hmax =
|
|
575
|
-
if (hmin <= h && h
|
|
741
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2c3bSat(s));
|
|
742
|
+
const hmax = HS_R2_PMAX_TMAX.enthalpy;
|
|
743
|
+
if (hmin <= h && h <= hs)
|
|
576
744
|
return Region.Region4;
|
|
577
745
|
if (hs <= h && h <= hmax)
|
|
578
746
|
return Region.Region2;
|
|
579
747
|
}
|
|
580
748
|
// Zone 7: R2_S_CRT <= s < sTPmax (Region 2 / Region 4, use h2abSat)
|
|
581
|
-
else if (s >= C.R2_S_CRT && s <
|
|
749
|
+
else if (s >= C.R2_S_CRT && s < HS_S_TP_MAX) {
|
|
582
750
|
const hmin = hSatLow(s);
|
|
583
|
-
const hs = h2abSat(s);
|
|
584
|
-
const hmax =
|
|
585
|
-
if (hmin <= h && h
|
|
751
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
752
|
+
const hmax = HS_R2_PMAX_TMAX.enthalpy;
|
|
753
|
+
if (hmin <= h && h <= hs)
|
|
586
754
|
return Region.Region4;
|
|
587
755
|
if (hs <= h && h <= hmax)
|
|
588
756
|
return Region.Region2;
|
|
589
757
|
}
|
|
590
758
|
// Zone 8: sTPmax <= s < s2ab (Region 2 / Region 4)
|
|
591
|
-
else if (s >=
|
|
759
|
+
else if (s >= HS_S_TP_MAX && s < HS_S2AB) {
|
|
592
760
|
const hmin = hSatLow(s);
|
|
593
|
-
const hs = h2abSat(s);
|
|
594
|
-
const hmax =
|
|
595
|
-
if (hmin <= h && h
|
|
761
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
762
|
+
const hmax = HS_R2_PMIN_TMAX.enthalpy;
|
|
763
|
+
if (hmin <= h && h <= hs)
|
|
596
764
|
return Region.Region4;
|
|
597
765
|
if (hs <= h && h <= hmax)
|
|
598
766
|
return Region.Region2;
|
|
599
767
|
}
|
|
600
768
|
// Zone 9: s2ab <= s < s4v (Region 2 / Region 4)
|
|
601
|
-
else if (s >=
|
|
769
|
+
else if (s >= HS_S2AB && s < HS_TRIPLE_VAPOR.entropy) {
|
|
602
770
|
const hmin = hSatLow(s);
|
|
603
|
-
const hs = h2abSat(s);
|
|
604
|
-
const hmax =
|
|
605
|
-
if (hmin <= h && h
|
|
771
|
+
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
772
|
+
const hmax = HS_R2_PMIN_TMAX.enthalpy;
|
|
773
|
+
if (hmin <= h && h <= hs)
|
|
606
774
|
return Region.Region4;
|
|
607
775
|
if (hs <= h && h <= hmax)
|
|
608
776
|
return Region.Region2;
|
|
609
777
|
}
|
|
610
778
|
// Zone 10: s4v <= s <= smax (Region 2 only, superheated low-P)
|
|
611
|
-
else if (s >=
|
|
612
|
-
const hmin =
|
|
613
|
-
const hmax =
|
|
779
|
+
else if (s >= HS_TRIPLE_VAPOR.entropy && s <= HS_R2_PMIN_TMAX.entropy) {
|
|
780
|
+
const hmin = HS_TRIPLE_VAPOR.enthalpy;
|
|
781
|
+
const hmax = HS_R2_PMIN_TMAX.enthalpy;
|
|
614
782
|
if (hmin <= h && h <= hmax)
|
|
615
783
|
return Region.Region2;
|
|
616
784
|
}
|
|
617
785
|
return -1;
|
|
618
786
|
}
|
|
619
|
-
//# sourceMappingURL=region-detector.js.map
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
* iterating on density. This module provides that inversion.
|
|
6
6
|
*/
|
|
7
7
|
import type { BasicProperties } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Solve for the Region 3 state at a target pressure and temperature.
|
|
10
|
+
*
|
|
11
|
+
* Newton-Raphson on p(ρ, T) − p_target = 0 using the analytic derivative
|
|
12
|
+
* (∂p/∂ρ)_T = 1 / (ρ κ_T).
|
|
13
|
+
*
|
|
14
|
+
* @param p - Target pressure [MPa]
|
|
15
|
+
* @param T - Temperature [K]
|
|
16
|
+
* @param rho0 - Initial density guess [kg/m³]
|
|
17
|
+
* @returns Basic thermodynamic properties at the converged density
|
|
18
|
+
*/
|
|
19
|
+
export declare function solveRegion3StateAtPressure(p: number, T: number, rho0: number): BasicProperties;
|
|
8
20
|
/**
|
|
9
21
|
* Compute Region 3 basic properties from pressure and temperature.
|
|
10
22
|
*
|
|
@@ -17,4 +29,3 @@ import type { BasicProperties } from '../types.js';
|
|
|
17
29
|
* @throws {IF97Error} if the density solve does not converge
|
|
18
30
|
*/
|
|
19
31
|
export declare function solveRegion3PTBasic(p: number, T: number): BasicProperties;
|
|
20
|
-
//# sourceMappingURL=region3-pt.d.ts.map
|
package/dist/core/region3-pt.js
CHANGED
|
@@ -3,6 +3,43 @@ import { IF97Error } from '../types.js';
|
|
|
3
3
|
import { region3ByRhoT } from '../regions/region3.js';
|
|
4
4
|
import { region3Volume } from '../regions/region3-subregions.js';
|
|
5
5
|
import { newtonRaphson } from '../solvers/newton-raphson.js';
|
|
6
|
+
/**
|
|
7
|
+
* Solve for the Region 3 state at a target pressure and temperature.
|
|
8
|
+
*
|
|
9
|
+
* Newton-Raphson on p(ρ, T) − p_target = 0 using the analytic derivative
|
|
10
|
+
* (∂p/∂ρ)_T = 1 / (ρ κ_T).
|
|
11
|
+
*
|
|
12
|
+
* @param p - Target pressure [MPa]
|
|
13
|
+
* @param T - Temperature [K]
|
|
14
|
+
* @param rho0 - Initial density guess [kg/m³]
|
|
15
|
+
* @returns Basic thermodynamic properties at the converged density
|
|
16
|
+
*/
|
|
17
|
+
export function solveRegion3StateAtPressure(p, T, rho0) {
|
|
18
|
+
// Keyed by ρ, so the residual and its derivative at the same point share one
|
|
19
|
+
// equation evaluation regardless of the order the solver calls them in.
|
|
20
|
+
let cachedRho = Number.NaN;
|
|
21
|
+
let cachedState = null;
|
|
22
|
+
const stateAt = (rho) => {
|
|
23
|
+
if (rho !== cachedRho || cachedState === null) {
|
|
24
|
+
cachedRho = rho;
|
|
25
|
+
cachedState = region3ByRhoT(rho, T);
|
|
26
|
+
}
|
|
27
|
+
return cachedState;
|
|
28
|
+
};
|
|
29
|
+
const rho = newtonRaphson((rhoGuess) => {
|
|
30
|
+
if (!Number.isFinite(rhoGuess) || rhoGuess <= 0) {
|
|
31
|
+
return Number.NaN;
|
|
32
|
+
}
|
|
33
|
+
return stateAt(rhoGuess).pressure - p;
|
|
34
|
+
}, rho0, (rhoGuess) => {
|
|
35
|
+
if (!Number.isFinite(rhoGuess) || rhoGuess <= 0) {
|
|
36
|
+
return Number.NaN;
|
|
37
|
+
}
|
|
38
|
+
const kappa = stateAt(rhoGuess).isothermalCompressibility;
|
|
39
|
+
return kappa === null ? Number.NaN : 1 / (rhoGuess * kappa);
|
|
40
|
+
});
|
|
41
|
+
return stateAt(rho);
|
|
42
|
+
}
|
|
6
43
|
/**
|
|
7
44
|
* Compute Region 3 basic properties from pressure and temperature.
|
|
8
45
|
*
|
|
@@ -18,18 +55,10 @@ export function solveRegion3PTBasic(p, T) {
|
|
|
18
55
|
if (p === C.Pc && T === C.Tc) {
|
|
19
56
|
return region3ByRhoT(C.RHOc, T);
|
|
20
57
|
}
|
|
21
|
-
const
|
|
22
|
-
const rho = newtonRaphson((rhoGuess) => {
|
|
23
|
-
if (!Number.isFinite(rhoGuess) || rhoGuess <= 0) {
|
|
24
|
-
return Number.NaN;
|
|
25
|
-
}
|
|
26
|
-
return region3ByRhoT(rhoGuess, T).pressure - p;
|
|
27
|
-
}, 1 / v0);
|
|
28
|
-
const state = region3ByRhoT(rho, T);
|
|
58
|
+
const state = solveRegion3StateAtPressure(p, T, 1 / region3Volume(p, T));
|
|
29
59
|
const relErr = Math.abs(state.pressure - p) / p;
|
|
30
60
|
if (relErr > 1e-6) {
|
|
31
61
|
throw new IF97Error(`solveRegion3PTBasic: density solve did not converge — target P=${p} MPa, got P=${state.pressure.toFixed(6)} MPa (relErr=${relErr.toExponential(2)})`);
|
|
32
62
|
}
|
|
33
63
|
return state;
|
|
34
64
|
}
|
|
35
|
-
//# sourceMappingURL=region3-pt.js.map
|
package/dist/core/solver.d.ts
CHANGED
package/dist/core/solver.js
CHANGED
package/dist/detect.d.ts
CHANGED
package/dist/detect.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -15,4 +15,3 @@ export declare function solvePx(p: number, x: number): SteamState;
|
|
|
15
15
|
export declare function solveTx(T: number, x: number): SteamState;
|
|
16
16
|
export type { SteamState, SolveInput } from './types.js';
|
|
17
17
|
export { Region, IF97Error, OutOfRangeError, ConvergenceError } from './types.js';
|
|
18
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
package/dist/regions/region1.js
CHANGED
package/dist/regions/region2.js
CHANGED
|
@@ -6,4 +6,3 @@ export declare function region3SatVolume(p: number, T: number, x: number): numbe
|
|
|
6
6
|
* Pressure thresholds from IAPWS Supplementary Release on Backward
|
|
7
7
|
* Equations v(P,T) for Region 3, Figures 2–5. */
|
|
8
8
|
export declare function region3Volume(p: number, T: number): number;
|
|
9
|
-
//# sourceMappingURL=region3-subregions.d.ts.map
|
package/dist/regions/region3.js
CHANGED
package/dist/regions/region4.js
CHANGED