iapws-if97 2.1.5 → 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 +8 -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 +66 -56
- 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 +232 -65
- 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 +17 -9
- package/dist/backward/objective-normalization.d.ts +0 -7
- package/dist/backward/objective-normalization.js +0 -8
- package/dist/solvers/nelder-mead.d.ts +0 -17
- package/dist/solvers/nelder-mead.js +0 -116
|
@@ -48,4 +48,3 @@ export declare function qualityFromSaturationProperty(liquidValue: number, vapor
|
|
|
48
48
|
* @param qualityInput - Vapour quality (0–1)
|
|
49
49
|
*/
|
|
50
50
|
export declare function mixSaturationState(endpoints: SaturationEndpoints, qualityInput: number): BasicProperties;
|
|
51
|
-
//# sourceMappingURL=common.d.ts.map
|
|
@@ -5,27 +5,21 @@ import { region2 } from '../regions/region2.js';
|
|
|
5
5
|
import { region3ByRhoT } from '../regions/region3.js';
|
|
6
6
|
import { saturationPressure, saturationTemperature } from '../regions/region4.js';
|
|
7
7
|
import { region3SatVolume } from '../regions/region3-subregions.js';
|
|
8
|
-
import {
|
|
8
|
+
import { solveRegion3StateAtPressure } from '../core/region3-pt.js';
|
|
9
9
|
import { normalizeRegion4Pressure, normalizeRegion4Temperature, } from './region4-boundaries.js';
|
|
10
10
|
/** Tag a single-phase state as Region 4 with the given vapour quality. */
|
|
11
11
|
function withRegion4Metadata(state, quality) {
|
|
12
12
|
return { ...state, region: Region.Region4, quality };
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Solve for the Region 3 saturation endpoint state.
|
|
16
16
|
*
|
|
17
17
|
* @param p - Saturation pressure [MPa]
|
|
18
18
|
* @param T - Saturation temperature [K]
|
|
19
19
|
* @param x - Phase indicator: 0 = liquid, 1 = vapour
|
|
20
20
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
return newtonRaphson((rho) => {
|
|
24
|
-
if (!Number.isFinite(rho) || rho <= 0) {
|
|
25
|
-
return Number.NaN;
|
|
26
|
-
}
|
|
27
|
-
return region3ByRhoT(rho, T).pressure - p;
|
|
28
|
-
}, 1 / v0);
|
|
21
|
+
function solveR3Endpoint(p, T, x) {
|
|
22
|
+
return solveRegion3StateAtPressure(p, T, 1 / region3SatVolume(p, T, x));
|
|
29
23
|
}
|
|
30
24
|
/** Clamp vapour quality to [0, 1], snapping near-zero/one values to the boundary. */
|
|
31
25
|
function clampQuality(x) {
|
|
@@ -69,8 +63,8 @@ export function saturationEndpointsAtPressure(p) {
|
|
|
69
63
|
return {
|
|
70
64
|
pressure,
|
|
71
65
|
temperature,
|
|
72
|
-
liquid:
|
|
73
|
-
vapor:
|
|
66
|
+
liquid: solveR3Endpoint(pressure, temperature, 0),
|
|
67
|
+
vapor: solveR3Endpoint(pressure, temperature, 1),
|
|
74
68
|
};
|
|
75
69
|
}
|
|
76
70
|
return {
|
|
@@ -150,4 +144,3 @@ export function mixSaturationState(endpoints, qualityInput) {
|
|
|
150
144
|
isothermalCompressibility: null,
|
|
151
145
|
};
|
|
152
146
|
}
|
|
153
|
-
//# sourceMappingURL=common.js.map
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
export declare function normalizeRegion4Pressure(p: number): number;
|
|
2
2
|
export declare function normalizeRegion4Temperature(T: number): number;
|
|
3
3
|
export declare function clampRegion4TemperatureBelowCritical(T: number): number;
|
|
4
|
-
export declare function isRegion4CriticalPressure(p: number): boolean;
|
|
5
|
-
export declare function isRegion4CriticalTemperature(T: number): boolean;
|
|
6
4
|
export declare function assertRegion4PressureStateInput(p: number, solverName: string): number;
|
|
7
5
|
export declare function assertRegion4TemperatureStateInput(T: number, solverName: string): number;
|
|
8
6
|
export declare function assertRegion4StateAllowed(pressure: number, temperature: number, solverName: string): void;
|
|
9
|
-
export declare function isCriticalRegion4Enthalpy(h: number): boolean;
|
|
10
|
-
export declare function isCriticalRegion4Entropy(s: number): boolean;
|
|
11
7
|
export declare function assertCriticalRegion4PHInput(p: number, h: number, solverName: string): void;
|
|
12
8
|
export declare function assertCriticalRegion4PSInput(p: number, s: number, solverName: string): void;
|
|
13
9
|
export declare function assertCriticalRegion4HSInput(h: number, s: number, solverName: string): void;
|
|
14
|
-
//# sourceMappingURL=region4-boundaries.d.ts.map
|
|
@@ -21,10 +21,10 @@ export function normalizeRegion4Temperature(T) {
|
|
|
21
21
|
export function clampRegion4TemperatureBelowCritical(T) {
|
|
22
22
|
return Math.max(C.Tt, Math.min(T, C.Tc - C.REGION4_SUBCRITICAL_TEMPERATURE_MARGIN));
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
function isRegion4CriticalPressure(p) {
|
|
25
25
|
return normalizeRegion4Pressure(p) === C.Pc;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
function isRegion4CriticalTemperature(T) {
|
|
28
28
|
return normalizeRegion4Temperature(T) === C.Tc;
|
|
29
29
|
}
|
|
30
30
|
function criticalPointMessage(solverName) {
|
|
@@ -55,10 +55,10 @@ export function assertRegion4StateAllowed(pressure, temperature, solverName) {
|
|
|
55
55
|
throw new IF97Error(criticalPointMessage(solverName));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
function isCriticalRegion4Enthalpy(h) {
|
|
59
59
|
return Math.abs(h - C.R3_H_CRT) <= C.REGION4_CRITICAL_HS_TOLERANCE;
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
function isCriticalRegion4Entropy(s) {
|
|
62
62
|
return Math.abs(s - C.R3_S_CRT) <= C.REGION4_CRITICAL_HS_TOLERANCE;
|
|
63
63
|
}
|
|
64
64
|
export function assertCriticalRegion4PHInput(p, h, solverName) {
|
|
@@ -76,4 +76,3 @@ export function assertCriticalRegion4HSInput(h, s, solverName) {
|
|
|
76
76
|
throw new IF97Error(criticalPointMessage(solverName));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
//# sourceMappingURL=region4-boundaries.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { BasicProperties } from '../types.js';
|
|
2
|
+
/** IAPWS SR4-04(2014), Eq. 9. Valid for Region 4 states at T <= 623.15 K. */
|
|
3
|
+
export declare function region4BackwardTemperatureHS(h: number, s: number): number;
|
|
2
4
|
export declare function tryRegion4HSState(h: number, s: number): BasicProperties | null;
|
|
3
|
-
//# sourceMappingURL=region4-hs.d.ts.map
|
|
@@ -1,19 +1,54 @@
|
|
|
1
1
|
import * as C from '../constants.js';
|
|
2
2
|
import { bracketedNewton } from '../solvers/bracketed-newton.js';
|
|
3
|
+
import { dampedNewton2D } from '../solvers/damped-newton-2d.js';
|
|
4
|
+
import { IF97Error } from '../types.js';
|
|
3
5
|
import { mixSaturationState, rawQualityFromSaturationProperty, saturationEndpointsAtTemperature, } from './common.js';
|
|
4
6
|
import { assertRegion4StateAllowed, clampRegion4TemperatureBelowCritical, } from './region4-boundaries.js';
|
|
5
7
|
const REGION4_QUALITY_TOLERANCE = 1e-6;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
const REGION4_ENDPOINT_QUALITY_SNAP_TOLERANCE = 1e-10;
|
|
9
|
+
/** Lower entropy limit of the SR4-04 T_sat(h,s) equation [kJ/(kg·K)].
|
|
10
|
+
* Published range of validity is s''(623.15 K) = 5.210887825 to
|
|
11
|
+
* s''(273.15 K) = 9.155759395; both reproduce exactly from Region 2 on the
|
|
12
|
+
* saturation line. Below this bound Eq. 9 is extrapolation, so the caller
|
|
13
|
+
* falls through to the general Newton path instead.
|
|
14
|
+
* Ref: IAPWS SR4-04(2014), Eq. 9. */
|
|
15
|
+
const REGION4_HS_BACKWARD_MIN_ENTROPY = 5.210887825;
|
|
16
|
+
const REGION4_HS_ENDPOINT_RESIDUAL_TOLERANCE = 1e-14;
|
|
17
|
+
const REGION4_HS_NEWTON_UPPER = C.Tc - C.REGION4_SUBCRITICAL_TEMPERATURE_MARGIN;
|
|
18
|
+
const REGION4_HS_NEAR_CRITICAL_UPPER = REGION4_HS_NEWTON_UPPER;
|
|
19
|
+
const REGION4_TRIPLE_POINT_ENDPOINTS = saturationEndpointsAtTemperature(C.Tt);
|
|
20
|
+
const REGION4_HS_T = [
|
|
21
|
+
[0, 0, 0.179882673606601], [0, 3, -0.267507455199603], [0, 12, 1.16276722612600],
|
|
22
|
+
[1, 0, 0.147545428713616], [1, 1, -0.512871635973248], [1, 2, 0.421333567697984],
|
|
23
|
+
[1, 5, 0.563749522189870], [2, 0, 0.429274443819153], [2, 5, -3.35704552142140],
|
|
24
|
+
[2, 8, 10.8890916499278], [3, 0, -0.248483390456012], [3, 2, 0.304153221906390],
|
|
25
|
+
[3, 3, -0.494819763939905], [3, 4, 1.07551674933261], [4, 0, 0.0733888415457688],
|
|
26
|
+
[4, 1, 0.0140170545411085], [5, 1, -0.106110975998808], [5, 2, 0.0168324361811875],
|
|
27
|
+
[5, 4, 1.25028363714877], [5, 16, 1013.16840309509], [6, 6, -1.51791558000712],
|
|
28
|
+
[6, 8, 52.4277865990866], [6, 22, 23049.5545563912], [8, 1, 0.0249459806365456],
|
|
29
|
+
[10, 20, 2107964.67412137], [10, 36, 366836848.613065], [12, 24, -144814105.365163],
|
|
30
|
+
[14, 1, -0.00179276373003590], [14, 28, 4899556021.00459], [16, 12, 471.262212070518],
|
|
31
|
+
[16, 32, -82929439019.8652], [18, 14, -1715.45662263191], [18, 22, 3557776.82973575],
|
|
32
|
+
[18, 36, 586062760258.436], [20, 24, -12988763.5078195], [28, 36, 31724744937.1057],
|
|
33
|
+
];
|
|
34
|
+
/** IAPWS SR4-04(2014), Eq. 9. Valid for Region 4 states at T <= 623.15 K. */
|
|
35
|
+
export function region4BackwardTemperatureHS(h, s) {
|
|
36
|
+
const eta = h / 2800;
|
|
37
|
+
const sigma = s / 9.2;
|
|
38
|
+
let theta = 0;
|
|
39
|
+
for (const [I, J, n] of REGION4_HS_T) {
|
|
40
|
+
theta += n * Math.pow(eta - 0.119, I) * Math.pow(sigma - 1.07, J);
|
|
41
|
+
}
|
|
42
|
+
return 550 * theta;
|
|
8
43
|
}
|
|
9
44
|
function normalizeEndpointQuality(quality) {
|
|
10
45
|
if (!Number.isFinite(quality)) {
|
|
11
46
|
return Number.NaN;
|
|
12
47
|
}
|
|
13
|
-
if (
|
|
48
|
+
if (quality < 0 && quality >= -REGION4_QUALITY_TOLERANCE) {
|
|
14
49
|
return 0;
|
|
15
50
|
}
|
|
16
|
-
if (
|
|
51
|
+
if (quality > 1 && quality <= 1 + REGION4_QUALITY_TOLERANCE) {
|
|
17
52
|
return 1;
|
|
18
53
|
}
|
|
19
54
|
return quality;
|
|
@@ -23,13 +58,186 @@ function isAdmissibleQuality(quality) {
|
|
|
23
58
|
&& quality >= -REGION4_QUALITY_TOLERANCE
|
|
24
59
|
&& quality <= 1 + REGION4_QUALITY_TOLERANCE;
|
|
25
60
|
}
|
|
26
|
-
function
|
|
61
|
+
function endpointQuality(qualityFromEnthalpy, qualityFromEntropy) {
|
|
62
|
+
if (Math.abs(qualityFromEnthalpy) <= REGION4_ENDPOINT_QUALITY_SNAP_TOLERANCE &&
|
|
63
|
+
Math.abs(qualityFromEntropy) <= REGION4_ENDPOINT_QUALITY_SNAP_TOLERANCE) {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
if (Math.abs(qualityFromEnthalpy - 1) <= REGION4_ENDPOINT_QUALITY_SNAP_TOLERANCE &&
|
|
67
|
+
Math.abs(qualityFromEntropy - 1) <= REGION4_ENDPOINT_QUALITY_SNAP_TOLERANCE) {
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function entropyResidualEvaluation(h, s, temperatureGuess) {
|
|
27
73
|
const endpoints = saturationEndpointsAtTemperature(clampRegion4TemperatureBelowCritical(temperatureGuess));
|
|
28
|
-
const
|
|
74
|
+
const T = endpoints.temperature;
|
|
75
|
+
const { liquid, vapor } = endpoints;
|
|
76
|
+
const quality = rawQualityFromSaturationProperty(liquid.enthalpy, vapor.enthalpy, h);
|
|
29
77
|
if (!Number.isFinite(quality)) {
|
|
30
|
-
return
|
|
78
|
+
return {
|
|
79
|
+
residual: Number.NaN,
|
|
80
|
+
derivative: Number.NaN,
|
|
81
|
+
quality,
|
|
82
|
+
liquidEnthalpy: liquid.enthalpy,
|
|
83
|
+
vaporEnthalpy: vapor.enthalpy,
|
|
84
|
+
liquidEntropy: liquid.entropy,
|
|
85
|
+
vaporEntropy: vapor.entropy,
|
|
86
|
+
liquidDhDT: Number.NaN,
|
|
87
|
+
vaporDhDT: Number.NaN,
|
|
88
|
+
liquidDsDT: Number.NaN,
|
|
89
|
+
vaporDsDT: Number.NaN,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const deltaH = vapor.enthalpy - liquid.enthalpy;
|
|
93
|
+
const deltaS = vapor.entropy - liquid.entropy;
|
|
94
|
+
const deltaV = vapor.specificVolume - liquid.specificVolume;
|
|
95
|
+
const pressureDerivative = deltaS / (1000 * deltaV);
|
|
96
|
+
const phaseDerivatives = (state) => {
|
|
97
|
+
const cp = state.cp ?? Number.NaN;
|
|
98
|
+
const alpha = state.isobaricExpansion ?? Number.NaN;
|
|
99
|
+
return [
|
|
100
|
+
cp + 1000 * state.specificVolume * (1 - T * alpha) * pressureDerivative,
|
|
101
|
+
cp / T - 1000 * state.specificVolume * alpha * pressureDerivative,
|
|
102
|
+
];
|
|
103
|
+
};
|
|
104
|
+
const [liquidDhDT, liquidDsDT] = phaseDerivatives(liquid);
|
|
105
|
+
const [vaporDhDT, vaporDsDT] = phaseDerivatives(vapor);
|
|
106
|
+
const qualityDerivative = -(liquidDhDT + quality * (vaporDhDT - liquidDhDT)) / deltaH;
|
|
107
|
+
return {
|
|
108
|
+
residual: liquid.entropy * (1 - quality) + vapor.entropy * quality - s,
|
|
109
|
+
derivative: liquidDsDT + quality * (vaporDsDT - liquidDsDT) + qualityDerivative * deltaS,
|
|
110
|
+
quality,
|
|
111
|
+
liquidEnthalpy: liquid.enthalpy,
|
|
112
|
+
vaporEnthalpy: vapor.enthalpy,
|
|
113
|
+
liquidEntropy: liquid.entropy,
|
|
114
|
+
vaporEntropy: vapor.entropy,
|
|
115
|
+
liquidDhDT,
|
|
116
|
+
vaporDhDT,
|
|
117
|
+
liquidDsDT,
|
|
118
|
+
vaporDsDT,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function entropyResidual(h, s, temperatureGuess) {
|
|
122
|
+
return entropyResidualEvaluation(h, s, temperatureGuess).residual;
|
|
123
|
+
}
|
|
124
|
+
function gibbsResidualEvaluation(h, s, temperatureGuess) {
|
|
125
|
+
const endpoints = saturationEndpointsAtTemperature(clampRegion4TemperatureBelowCritical(temperatureGuess));
|
|
126
|
+
const T = endpoints.temperature;
|
|
127
|
+
const { liquid, vapor } = endpoints;
|
|
128
|
+
const deltaS = vapor.entropy - liquid.entropy;
|
|
129
|
+
const deltaV = vapor.specificVolume - liquid.specificVolume;
|
|
130
|
+
const pressureDerivative = deltaS / (1000 * deltaV);
|
|
131
|
+
return {
|
|
132
|
+
residual: liquid.enthalpy - T * liquid.entropy + T * s - h,
|
|
133
|
+
derivative: 1000 * liquid.specificVolume * pressureDerivative - liquid.entropy + s,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function tryRegion4HSGibbsState(h, s) {
|
|
137
|
+
let cachedTemperature = Number.NaN;
|
|
138
|
+
let cachedEvaluation = null;
|
|
139
|
+
const evaluate = (temperature) => {
|
|
140
|
+
const stableTemperature = Math.round(temperature / C.REGION4_TEMPERATURE_TOLERANCE) * C.REGION4_TEMPERATURE_TOLERANCE;
|
|
141
|
+
if (stableTemperature !== cachedTemperature || cachedEvaluation === null) {
|
|
142
|
+
cachedTemperature = stableTemperature;
|
|
143
|
+
cachedEvaluation = gibbsResidualEvaluation(h, s, stableTemperature);
|
|
144
|
+
}
|
|
145
|
+
return cachedEvaluation;
|
|
146
|
+
};
|
|
147
|
+
if (h < 10 && s < 0.1) {
|
|
148
|
+
let temperature = C.Tt;
|
|
149
|
+
for (let iteration = 0; iteration < 20; iteration += 1) {
|
|
150
|
+
const value = entropyResidualEvaluation(h, s, temperature);
|
|
151
|
+
if (Math.abs(value.residual) <= REGION4_HS_ENDPOINT_RESIDUAL_TOLERANCE) {
|
|
152
|
+
const state = finalizeRegion4HSState(h, s, temperature);
|
|
153
|
+
if (state !== null)
|
|
154
|
+
return state;
|
|
155
|
+
}
|
|
156
|
+
if (!Number.isFinite(value.derivative) || value.derivative === 0)
|
|
157
|
+
break;
|
|
158
|
+
const candidate = temperature - value.residual / value.derivative;
|
|
159
|
+
if (!(candidate >= C.Tt && candidate <= C.Tt + 0.1))
|
|
160
|
+
break;
|
|
161
|
+
temperature = Math.round(candidate / C.REGION4_TEMPERATURE_TOLERANCE) * C.REGION4_TEMPERATURE_TOLERANCE;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (Math.abs(h - C.R3_H_CRT) >= 10 || Math.abs(s - C.R3_S_CRT) >= 0.1) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const initialTemperature = C.Tc - 1e-4;
|
|
168
|
+
try {
|
|
169
|
+
const initialState = finalizeRegion4HSState(h, s, initialTemperature);
|
|
170
|
+
if (initialState !== null)
|
|
171
|
+
return initialState;
|
|
172
|
+
const root = bracketedNewton((T) => evaluate(T).residual, C.Tc - C.CRITICAL_T_EXCLUSION_BAND, REGION4_HS_NEAR_CRITICAL_UPPER, initialTemperature, {
|
|
173
|
+
tolerance: C.REGION4_HS_RESIDUAL_TOLERANCE,
|
|
174
|
+
derivative: (T) => evaluate(T).derivative,
|
|
175
|
+
});
|
|
176
|
+
const stableRoot = Math.round(root / C.REGION4_TEMPERATURE_TOLERANCE) * C.REGION4_TEMPERATURE_TOLERANCE;
|
|
177
|
+
return finalizeRegion4HSState(h, s, stableRoot);
|
|
31
178
|
}
|
|
32
|
-
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (!(error instanceof IF97Error))
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function tryRegion4HSNewtonState(h, s) {
|
|
186
|
+
// h/s carries units of K and, because IF97 zeroes h and s at the triple-point
|
|
187
|
+
// liquid, approximates the mean temperature of heat addition from that state —
|
|
188
|
+
// close enough to land inside the dome for most inputs. It is clamped to the
|
|
189
|
+
// saturation range and backed by fixed seeds spanning Tt to just below Tc.
|
|
190
|
+
const seeds = [h / s, 300, 400, 500, 580, 630, 645];
|
|
191
|
+
const tried = new Set();
|
|
192
|
+
const hScale = Math.max(1, Math.abs(h));
|
|
193
|
+
const sScale = Math.max(1, Math.abs(s));
|
|
194
|
+
for (const rawSeed of seeds) {
|
|
195
|
+
const seed = Math.max(C.Tt, Math.min(REGION4_HS_NEWTON_UPPER, rawSeed));
|
|
196
|
+
if (!Number.isFinite(seed) || tried.has(seed))
|
|
197
|
+
continue;
|
|
198
|
+
tried.add(seed);
|
|
199
|
+
try {
|
|
200
|
+
const initialValue = entropyResidualEvaluation(h, s, seed);
|
|
201
|
+
if (!Number.isFinite(initialValue.quality))
|
|
202
|
+
continue;
|
|
203
|
+
const [temperature, quality] = dampedNewton2D(([T, x]) => {
|
|
204
|
+
const value = entropyResidualEvaluation(h, s, T);
|
|
205
|
+
const deltaH = value.vaporEnthalpy - value.liquidEnthalpy;
|
|
206
|
+
const deltaS = value.vaporEntropy - value.liquidEntropy;
|
|
207
|
+
return {
|
|
208
|
+
residual: [
|
|
209
|
+
(value.liquidEnthalpy * (1 - x) + value.vaporEnthalpy * x - h) / hScale,
|
|
210
|
+
(value.liquidEntropy * (1 - x) + value.vaporEntropy * x - s) / sScale,
|
|
211
|
+
],
|
|
212
|
+
jacobian: [
|
|
213
|
+
[
|
|
214
|
+
(value.liquidDhDT * (1 - x) + value.vaporDhDT * x) / hScale,
|
|
215
|
+
deltaH / hScale,
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
(value.liquidDsDT * (1 - x) + value.vaporDsDT * x) / sScale,
|
|
219
|
+
deltaS / sScale,
|
|
220
|
+
],
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
}, [seed, initialValue.quality], {
|
|
224
|
+
tolerance: 1e-12,
|
|
225
|
+
maxIterations: 30,
|
|
226
|
+
isValid: ([T]) => T >= C.Tt && T <= REGION4_HS_NEWTON_UPPER,
|
|
227
|
+
});
|
|
228
|
+
if (isAdmissibleQuality(quality)) {
|
|
229
|
+
const stableTemperature = Math.round(temperature / C.REGION4_TEMPERATURE_TOLERANCE) * C.REGION4_TEMPERATURE_TOLERANCE;
|
|
230
|
+
const state = finalizeRegion4HSState(h, s, stableTemperature);
|
|
231
|
+
if (state !== null)
|
|
232
|
+
return state;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (!(error instanceof IF97Error))
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
33
241
|
}
|
|
34
242
|
function finalizeRegion4HSState(h, s, temperature) {
|
|
35
243
|
const endpoints = saturationEndpointsAtTemperature(temperature);
|
|
@@ -43,7 +251,9 @@ function finalizeRegion4HSState(h, s, temperature) {
|
|
|
43
251
|
if (Math.abs(normalizedQualityFromEnthalpy - normalizedQualityFromEntropy) > REGION4_QUALITY_TOLERANCE) {
|
|
44
252
|
return null;
|
|
45
253
|
}
|
|
46
|
-
|
|
254
|
+
const mixedEntropy = endpoints.liquid.entropy * (1 - qualityFromEnthalpy) +
|
|
255
|
+
endpoints.vapor.entropy * qualityFromEnthalpy;
|
|
256
|
+
if (Math.abs(mixedEntropy - s) > C.REGION4_HS_RESIDUAL_TOLERANCE) {
|
|
47
257
|
return null;
|
|
48
258
|
}
|
|
49
259
|
try {
|
|
@@ -52,114 +262,73 @@ function finalizeRegion4HSState(h, s, temperature) {
|
|
|
52
262
|
catch {
|
|
53
263
|
return null;
|
|
54
264
|
}
|
|
55
|
-
|
|
265
|
+
const exactEndpointQuality = endpointQuality(qualityFromEnthalpy, qualityFromEntropy);
|
|
266
|
+
return mixSaturationState(endpoints, exactEndpointQuality ?? normalizedQualityFromEnthalpy);
|
|
56
267
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
268
|
+
function isTriplePointHS(h, s) {
|
|
269
|
+
const { liquid, vapor } = REGION4_TRIPLE_POINT_ENDPOINTS;
|
|
270
|
+
const quality = rawQualityFromSaturationProperty(liquid.entropy, vapor.entropy, s);
|
|
271
|
+
if (quality < 0 || quality > 1)
|
|
272
|
+
return false;
|
|
273
|
+
const expectedH = liquid.enthalpy * (1 - quality) + vapor.enthalpy * quality;
|
|
274
|
+
return Math.abs(expectedH - h) <= C.REGION4_HS_RESIDUAL_TOLERANCE;
|
|
275
|
+
}
|
|
276
|
+
function tryRegion4HSBackwardState(h, s) {
|
|
277
|
+
if (s < REGION4_HS_BACKWARD_MIN_ENTROPY)
|
|
67
278
|
return null;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
catch {
|
|
279
|
+
const lower = C.Tt;
|
|
280
|
+
const upper = C.R2_T_MIN;
|
|
281
|
+
const rawSeed = region4BackwardTemperatureHS(h, s);
|
|
282
|
+
if (!Number.isFinite(rawSeed) || rawSeed < lower - 0.1 || rawSeed > upper + 0.1) {
|
|
73
283
|
return null;
|
|
74
284
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (let i = 1; i <= C.REGION4_HS_BRACKET_SEGMENTS; i++) {
|
|
102
|
-
const temperature = lower + (upper - lower) * (i / C.REGION4_HS_BRACKET_SEGMENTS);
|
|
103
|
-
const residual = endpointEnthalpyResidual(h, temperature, phase);
|
|
104
|
-
if (!Number.isFinite(previousResidual) || !Number.isFinite(residual)) {
|
|
105
|
-
previousTemperature = temperature;
|
|
106
|
-
previousResidual = residual;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (isClose(previousResidual, 0)) {
|
|
110
|
-
return endpointStateForPhase(h, s, previousTemperature, phase);
|
|
111
|
-
}
|
|
112
|
-
if (isClose(residual, 0)) {
|
|
113
|
-
return endpointStateForPhase(h, s, temperature, phase);
|
|
114
|
-
}
|
|
115
|
-
if (previousResidual * residual < 0) {
|
|
116
|
-
const root = bracketedNewton((candidateTemperature) => endpointEnthalpyResidual(h, candidateTemperature, phase), previousTemperature, temperature, (previousTemperature + temperature) / 2, { tolerance: C.REGION4_HS_RESIDUAL_TOLERANCE });
|
|
117
|
-
return endpointStateForPhase(h, s, root, phase);
|
|
118
|
-
}
|
|
119
|
-
previousTemperature = temperature;
|
|
120
|
-
previousResidual = residual;
|
|
285
|
+
const seed = Math.max(lower, Math.min(upper, rawSeed));
|
|
286
|
+
const seedState = finalizeRegion4HSState(h, s, seed);
|
|
287
|
+
if (seedState !== null)
|
|
288
|
+
return seedState;
|
|
289
|
+
let radius = 0.01;
|
|
290
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
291
|
+
const bracketLower = Math.max(lower, seed - radius);
|
|
292
|
+
const bracketUpper = Math.min(upper, seed + radius);
|
|
293
|
+
const lowerResidual = entropyResidual(h, s, bracketLower);
|
|
294
|
+
const upperResidual = entropyResidual(h, s, bracketUpper);
|
|
295
|
+
if (Number.isFinite(lowerResidual) && Number.isFinite(upperResidual) &&
|
|
296
|
+
lowerResidual * upperResidual <= 0) {
|
|
297
|
+
let cachedTemperature = Number.NaN;
|
|
298
|
+
let cachedEvaluation = { residual: Number.NaN, derivative: Number.NaN };
|
|
299
|
+
const evaluate = (candidateTemperature) => {
|
|
300
|
+
if (candidateTemperature !== cachedTemperature) {
|
|
301
|
+
cachedTemperature = candidateTemperature;
|
|
302
|
+
cachedEvaluation = entropyResidualEvaluation(h, s, candidateTemperature);
|
|
303
|
+
}
|
|
304
|
+
return cachedEvaluation;
|
|
305
|
+
};
|
|
306
|
+
const root = bracketedNewton((candidateTemperature) => evaluate(candidateTemperature).residual, bracketLower, bracketUpper, seed, {
|
|
307
|
+
tolerance: C.REGION4_HS_RESIDUAL_TOLERANCE,
|
|
308
|
+
derivative: (candidateTemperature) => evaluate(candidateTemperature).derivative,
|
|
309
|
+
});
|
|
310
|
+
return finalizeRegion4HSState(h, s, root);
|
|
121
311
|
}
|
|
312
|
+
if (bracketLower === lower && bracketUpper === upper)
|
|
313
|
+
break;
|
|
314
|
+
radius *= 2;
|
|
122
315
|
}
|
|
123
316
|
return null;
|
|
124
317
|
}
|
|
125
318
|
export function tryRegion4HSState(h, s) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const lower = C.Tt;
|
|
131
|
-
const upper = clampRegion4TemperatureBelowCritical(C.Tc);
|
|
132
|
-
const lowerState = finalizeRegion4HSState(h, s, lower);
|
|
133
|
-
if (lowerState !== null) {
|
|
134
|
-
return lowerState;
|
|
135
|
-
}
|
|
136
|
-
const upperState = finalizeRegion4HSState(h, s, upper);
|
|
137
|
-
if (upperState !== null) {
|
|
138
|
-
return upperState;
|
|
139
|
-
}
|
|
140
|
-
let previousTemperature = lower;
|
|
141
|
-
let previousResidual = entropyResidual(h, s, lower);
|
|
142
|
-
for (let i = 1; i <= C.REGION4_HS_BRACKET_SEGMENTS; i++) {
|
|
143
|
-
const temperature = lower + (upper - lower) * (i / C.REGION4_HS_BRACKET_SEGMENTS);
|
|
144
|
-
const residual = entropyResidual(h, s, temperature);
|
|
145
|
-
if (!Number.isFinite(previousResidual) || !Number.isFinite(residual)) {
|
|
146
|
-
previousTemperature = temperature;
|
|
147
|
-
previousResidual = residual;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (isClose(previousResidual, 0)) {
|
|
151
|
-
return finalizeRegion4HSState(h, s, previousTemperature);
|
|
152
|
-
}
|
|
153
|
-
if (isClose(residual, 0)) {
|
|
154
|
-
return finalizeRegion4HSState(h, s, temperature);
|
|
155
|
-
}
|
|
156
|
-
if (previousResidual * residual < 0) {
|
|
157
|
-
const root = bracketedNewton((candidateTemperature) => entropyResidual(h, s, candidateTemperature), previousTemperature, temperature, (previousTemperature + temperature) / 2, { tolerance: C.REGION4_HS_RESIDUAL_TOLERANCE });
|
|
158
|
-
return finalizeRegion4HSState(h, s, root);
|
|
159
|
-
}
|
|
160
|
-
previousTemperature = temperature;
|
|
161
|
-
previousResidual = residual;
|
|
319
|
+
if (isTriplePointHS(h, s)) {
|
|
320
|
+
const triplePointState = finalizeRegion4HSState(h, s, C.Tt);
|
|
321
|
+
if (triplePointState !== null)
|
|
322
|
+
return triplePointState;
|
|
162
323
|
}
|
|
324
|
+
const backwardState = tryRegion4HSBackwardState(h, s);
|
|
325
|
+
if (backwardState !== null)
|
|
326
|
+
return backwardState;
|
|
327
|
+
const gibbsState = tryRegion4HSGibbsState(h, s);
|
|
328
|
+
if (gibbsState !== null)
|
|
329
|
+
return gibbsState;
|
|
330
|
+
const newtonState = tryRegion4HSNewtonState(h, s);
|
|
331
|
+
if (newtonState !== null)
|
|
332
|
+
return newtonState;
|
|
163
333
|
return null;
|
|
164
334
|
}
|
|
165
|
-
//# sourceMappingURL=region4-hs.js.map
|
package/dist/saturation.d.ts
CHANGED
package/dist/saturation.js
CHANGED
|
@@ -6,13 +6,15 @@ export interface BracketedNewtonOptions {
|
|
|
6
6
|
maxIterations?: number;
|
|
7
7
|
/** Relative step size for central-difference derivative (default 1e-6) */
|
|
8
8
|
derivativeStep?: number;
|
|
9
|
+
/** Optional analytical derivative. */
|
|
10
|
+
derivative?: (x: number) => number;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Find a root of f(x) = 0 within the bracket [lower, upper].
|
|
12
14
|
*
|
|
13
|
-
* Each iteration attempts a Newton step using
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* Each iteration attempts a Newton step using the supplied analytical derivative,
|
|
16
|
+
* or a central difference when none is supplied. If the Newton candidate falls
|
|
17
|
+
* outside the current bracket, a bisection step is used instead.
|
|
16
18
|
*
|
|
17
19
|
* @param f - Continuous function whose root is sought
|
|
18
20
|
* @param lower - Lower bound of the bracket
|
|
@@ -23,4 +25,3 @@ export interface BracketedNewtonOptions {
|
|
|
23
25
|
* @throws {ConvergenceError} if the bracket signs are the same or max iterations exceeded
|
|
24
26
|
*/
|
|
25
27
|
export declare function bracketedNewton(f: (x: number) => number, lower: number, upper: number, initialGuess?: number, options?: BracketedNewtonOptions): number;
|
|
26
|
-
//# sourceMappingURL=bracketed-newton.d.ts.map
|
|
@@ -20,9 +20,9 @@ function numericalDerivative(f, x, lower, upper, step) {
|
|
|
20
20
|
/**
|
|
21
21
|
* Find a root of f(x) = 0 within the bracket [lower, upper].
|
|
22
22
|
*
|
|
23
|
-
* Each iteration attempts a Newton step using
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Each iteration attempts a Newton step using the supplied analytical derivative,
|
|
24
|
+
* or a central difference when none is supplied. If the Newton candidate falls
|
|
25
|
+
* outside the current bracket, a bisection step is used instead.
|
|
26
26
|
*
|
|
27
27
|
* @param f - Continuous function whose root is sought
|
|
28
28
|
* @param lower - Lower bound of the bracket
|
|
@@ -69,9 +69,9 @@ export function bracketedNewton(f, lower, upper, initialGuess, options) {
|
|
|
69
69
|
return midpoint;
|
|
70
70
|
}
|
|
71
71
|
const step = Math.min(derivativeStep * Math.max(1, Math.abs(x)), (b - a) / 4);
|
|
72
|
-
const derivative = step > 0
|
|
72
|
+
const derivative = options?.derivative?.(x) ?? (step > 0
|
|
73
73
|
? numericalDerivative(f, x, a, b, step)
|
|
74
|
-
: null;
|
|
74
|
+
: null);
|
|
75
75
|
const newtonCandidate = derivative === null || !Number.isFinite(derivative) || derivative === 0
|
|
76
76
|
? NaN
|
|
77
77
|
: x - fx / derivative;
|
|
@@ -81,4 +81,3 @@ export function bracketedNewton(f, lower, upper, initialGuess, options) {
|
|
|
81
81
|
}
|
|
82
82
|
throw new ConvergenceError('bracketedNewton', maxIterations);
|
|
83
83
|
}
|
|
84
|
-
//# sourceMappingURL=bracketed-newton.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Vector2 = readonly [number, number];
|
|
2
|
+
export interface Newton2DEvaluation {
|
|
3
|
+
residual: Vector2;
|
|
4
|
+
jacobian: readonly [Vector2, Vector2];
|
|
5
|
+
}
|
|
6
|
+
export interface DampedNewton2DOptions {
|
|
7
|
+
tolerance?: number;
|
|
8
|
+
maxIterations?: number;
|
|
9
|
+
maxBacktracks?: number;
|
|
10
|
+
isValid?: (point: Vector2) => boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function dampedNewton2D(evaluate: (point: Vector2) => Newton2DEvaluation, initial: Vector2, options?: DampedNewton2DOptions): [number, number];
|