iapws-if97 2.2.0 → 2.8.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 +25 -2
- package/README.md +69 -246
- package/dist/backward/candidate-resolution.d.ts +8 -0
- package/dist/backward/candidate-resolution.js +40 -0
- package/dist/backward/fixed-temperature-candidates.d.ts +4 -0
- package/dist/backward/fixed-temperature-candidates.js +188 -0
- package/dist/backward/fixed-temperature-solver.d.ts +3 -3
- package/dist/backward/fixed-temperature-solver.js +38 -17
- package/dist/backward/fixed-temperature-stationary.d.ts +4 -0
- package/dist/backward/fixed-temperature-stationary.js +146 -0
- package/dist/backward/hs-region.d.ts +50 -0
- package/dist/backward/hs-region.js +695 -0
- package/dist/backward/hs-state.d.ts +15 -0
- package/dist/backward/hs-state.js +316 -0
- package/dist/backward/hs.js +28 -242
- package/dist/backward/ph.d.ts +2 -0
- package/dist/backward/ph.js +72 -21
- package/dist/backward/pressure-property-region.d.ts +13 -0
- package/dist/backward/pressure-property-region.js +262 -0
- package/dist/backward/ps.d.ts +6 -0
- package/dist/backward/ps.js +81 -28
- package/dist/backward/solution-validation.d.ts +7 -1
- package/dist/backward/solution-validation.js +138 -41
- package/dist/backward/th.d.ts +3 -5
- package/dist/backward/th.js +10 -71
- package/dist/backward/tolerances.d.ts +3 -0
- package/dist/backward/tolerances.js +9 -3
- package/dist/backward/ts.d.ts +3 -5
- package/dist/backward/ts.js +10 -71
- package/dist/constants.d.ts +11 -15
- package/dist/constants.js +14 -15
- package/dist/core/floating-point.d.ts +2 -0
- package/dist/core/floating-point.js +7 -0
- package/dist/core/formulation-seams.d.ts +27 -0
- package/dist/core/formulation-seams.js +289 -0
- package/dist/core/input-validation.d.ts +1 -0
- package/dist/core/input-validation.js +5 -0
- package/dist/core/region-detector.d.ts +3 -40
- package/dist/core/region-detector.js +37 -762
- package/dist/core/region3-pt.d.ts +1 -1
- package/dist/core/region3-pt.js +42 -16
- package/dist/core/solve-input-normalization.js +4 -1
- package/dist/core/solver.d.ts +7 -10
- package/dist/core/solver.js +26 -17
- package/dist/detect.d.ts +16 -2
- package/dist/detect.js +36 -2
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +16 -0
- package/dist/gas-constant.d.ts +5 -0
- package/dist/gas-constant.js +5 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +10 -21
- package/dist/regions/region1.d.ts +2 -1
- package/dist/regions/region1.js +1 -1
- package/dist/regions/region2.d.ts +2 -1
- package/dist/regions/region2.js +1 -1
- package/dist/regions/region3-data.js +3 -3
- package/dist/regions/region3-subregions.js +15 -10
- package/dist/regions/region3.d.ts +4 -1
- package/dist/regions/region3.js +32 -2
- package/dist/regions/region4.d.ts +2 -0
- package/dist/regions/region4.js +39 -12
- package/dist/regions/region5.d.ts +2 -1
- package/dist/regions/region5.js +2 -2
- package/dist/saturation/common.d.ts +2 -8
- package/dist/saturation/common.js +64 -36
- package/dist/saturation/region4-boundaries.js +13 -13
- package/dist/saturation/region4-hs.js +282 -58
- package/dist/saturation/two-phase.d.ts +2 -2
- package/dist/saturation/two-phase.js +7 -7
- package/dist/solvers/bracketed-newton.d.ts +6 -3
- package/dist/solvers/bracketed-newton.js +15 -8
- package/dist/solvers/extended-arithmetic.d.ts +6 -0
- package/dist/solvers/extended-arithmetic.js +38 -0
- package/dist/transport/properties.d.ts +4 -2
- package/dist/transport/properties.js +14 -4
- package/dist/types.d.ts +18 -0
- package/dist/types.js +14 -0
- package/package.json +4 -4
- package/dist/core/public-normalization.d.ts +0 -3
- package/dist/core/public-normalization.js +0 -53
- /package/dist/{backward → core}/thermodynamic-derivatives.d.ts +0 -0
- /package/dist/{backward → core}/thermodynamic-derivatives.js +0 -0
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Region detection for various input modes.
|
|
3
|
-
*/
|
|
1
|
+
/** Region detection for P-T inputs. */
|
|
4
2
|
import * as C from '../constants.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { region2 } from '../regions/region2.js';
|
|
9
|
-
import { region5 } from '../regions/region5.js';
|
|
10
|
-
import { saturationEndpointsAtPressure, saturationEndpointsAtTemperature, } from '../saturation/common.js';
|
|
11
|
-
import { tryRegion4HSState } from '../saturation/region4-hs.js';
|
|
12
|
-
import { IF97Error, Region } from '../types.js';
|
|
13
|
-
import { bracketedNewton } from '../solvers/bracketed-newton.js';
|
|
14
|
-
import { solveRegion3PTBasic } from './region3-pt.js';
|
|
3
|
+
import { saturationPressure } from '../regions/region4.js';
|
|
4
|
+
import { boundary23_T_to_P } from '../regions/boundaries.js';
|
|
5
|
+
import { Region } from '../types.js';
|
|
15
6
|
/**
|
|
16
7
|
* Detect the IAPWS-IF97 region for given P and T.
|
|
17
8
|
* @returns Region number (1–5) or -1 if out of range
|
|
@@ -21,766 +12,50 @@ export function detectRegionPT(p, T) {
|
|
|
21
12
|
return Region.Region5;
|
|
22
13
|
}
|
|
23
14
|
if (p >= C.P_MIN && p <= C.B23_P_MIN) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return Region.Region1;
|
|
27
|
-
if (T > Tsat && T <= C.R2_T_MAX)
|
|
28
|
-
return Region.Region2;
|
|
29
|
-
}
|
|
30
|
-
else if (p > C.B23_P_MIN && p <= C.P_MAX) {
|
|
31
|
-
const Tb23 = boundary23_P_to_T(p);
|
|
32
|
-
if (T >= C.T_MIN && T <= C.R2_T_MIN)
|
|
33
|
-
return Region.Region1;
|
|
34
|
-
if (T > C.R2_T_MIN && T < Tb23)
|
|
35
|
-
return Region.Region3;
|
|
36
|
-
if (T >= Tb23 && T <= C.R2_T_MAX)
|
|
37
|
-
return Region.Region2;
|
|
38
|
-
}
|
|
39
|
-
return -1;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Detect region for P-H inputs.
|
|
43
|
-
*/
|
|
44
|
-
export function detectRegionPH(p, h) {
|
|
45
|
-
if (p >= C.Pt && p < C.Pc) {
|
|
46
|
-
const endpoints = saturationEndpointsAtPressure(p);
|
|
47
|
-
if (isClose(h, endpoints.liquid.enthalpy) || isClose(h, endpoints.vapor.enthalpy)) {
|
|
48
|
-
return Region.Region4;
|
|
15
|
+
if (T >= C.T_MIN && T <= C.R2_T_MIN) {
|
|
16
|
+
return p >= saturationPressure(T) ? Region.Region1 : Region.Region2;
|
|
49
17
|
}
|
|
50
|
-
|
|
51
|
-
if (p >= C.P_MIN && p <= C.B23_P_MIN) {
|
|
52
|
-
const Tsat = saturationTemperature(p);
|
|
53
|
-
const h14 = region1(p, Tsat).enthalpy;
|
|
54
|
-
const h24 = region2(p, Tsat).enthalpy;
|
|
55
|
-
const h25 = region2(p, C.R2_T_MAX).enthalpy;
|
|
56
|
-
const hmin = region1(p, C.T_MIN).enthalpy;
|
|
57
|
-
if (hmin <= h && h <= h14)
|
|
58
|
-
return Region.Region1;
|
|
59
|
-
if (h14 < h && h < h24)
|
|
60
|
-
return Region.Region4;
|
|
61
|
-
if (h24 <= h && h <= h25)
|
|
18
|
+
if (T > C.R2_T_MIN && T <= C.R2_T_MAX)
|
|
62
19
|
return Region.Region2;
|
|
63
|
-
if (h25 < h && p <= C.R5_P_MAX)
|
|
64
|
-
return Region.Region5;
|
|
65
20
|
}
|
|
66
21
|
else if (p > C.B23_P_MIN && p <= C.P_MAX) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const h32 = region2(p, boundary23_P_to_T(p)).enthalpy;
|
|
70
|
-
const h25 = region2(p, C.R2_T_MAX).enthalpy;
|
|
71
|
-
if (hmin <= h && h <= h13)
|
|
72
|
-
return Region.Region1;
|
|
73
|
-
if (p < C.Pc) {
|
|
74
|
-
const endpoints = saturationEndpointsAtPressure(p);
|
|
75
|
-
const hf = endpoints.liquid.enthalpy;
|
|
76
|
-
const hg = endpoints.vapor.enthalpy;
|
|
77
|
-
if (h13 < h && h < hf)
|
|
78
|
-
return Region.Region3;
|
|
79
|
-
if (hf < h && h < hg)
|
|
80
|
-
return Region.Region4;
|
|
81
|
-
if (hg < h && h < h32)
|
|
82
|
-
return Region.Region3;
|
|
22
|
+
if (T >= C.T_MIN && T <= C.R2_T_MIN) {
|
|
23
|
+
return p >= saturationPressure(T) ? Region.Region1 : Region.Region2;
|
|
83
24
|
}
|
|
84
|
-
|
|
25
|
+
if (T > C.R2_T_MIN && T < C.B23_T_MAX &&
|
|
26
|
+
((T < C.Tc && p >= saturationPressure(T)) || p >= boundary23_T_to_P(T)))
|
|
85
27
|
return Region.Region3;
|
|
86
|
-
|
|
87
|
-
if (h32 <= h && h <= h25)
|
|
28
|
+
if (T > C.R2_T_MIN && T <= C.R2_T_MAX)
|
|
88
29
|
return Region.Region2;
|
|
89
|
-
if (h25 < h && p <= C.R5_P_MAX)
|
|
90
|
-
return Region.Region5;
|
|
91
30
|
}
|
|
92
31
|
return -1;
|
|
93
32
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
if (p >= C.P_MIN && p <= C.B23_P_MIN) {
|
|
105
|
-
const Tsat = saturationTemperature(p);
|
|
106
|
-
const smin = region1(p, C.T_MIN).entropy;
|
|
107
|
-
const s14 = region1(p, Tsat).entropy;
|
|
108
|
-
const s24 = region2(p, Tsat).entropy;
|
|
109
|
-
const s25 = region2(p, C.R2_T_MAX).entropy;
|
|
110
|
-
if (smin <= s && s <= s14)
|
|
111
|
-
return Region.Region1;
|
|
112
|
-
if (s14 < s && s < s24)
|
|
113
|
-
return Region.Region4;
|
|
114
|
-
if (s24 <= s && s <= s25)
|
|
115
|
-
return Region.Region2;
|
|
116
|
-
if (s25 < s && p <= C.R5_P_MAX)
|
|
117
|
-
return Region.Region5;
|
|
118
|
-
}
|
|
119
|
-
else if (p > C.B23_P_MIN && p <= C.P_MAX) {
|
|
120
|
-
const smin = region1(p, C.T_MIN).entropy;
|
|
121
|
-
const s13 = region1(p, C.R2_T_MIN).entropy;
|
|
122
|
-
const s32 = region2(p, boundary23_P_to_T(p)).entropy;
|
|
123
|
-
const s25 = region2(p, C.R2_T_MAX).entropy;
|
|
124
|
-
if (smin <= s && s <= s13)
|
|
125
|
-
return Region.Region1;
|
|
126
|
-
if (p < C.Pc) {
|
|
127
|
-
const endpoints = saturationEndpointsAtPressure(p);
|
|
128
|
-
const sf = endpoints.liquid.entropy;
|
|
129
|
-
const sg = endpoints.vapor.entropy;
|
|
130
|
-
if (s13 < s && s < sf)
|
|
131
|
-
return Region.Region3;
|
|
132
|
-
if (sf < s && s < sg)
|
|
133
|
-
return Region.Region4;
|
|
134
|
-
if (sg < s && s < s32)
|
|
135
|
-
return Region.Region3;
|
|
136
|
-
}
|
|
137
|
-
else if (s13 < s && s < s32) {
|
|
138
|
-
return Region.Region3;
|
|
139
|
-
}
|
|
140
|
-
if (s32 <= s && s <= s25)
|
|
141
|
-
return Region.Region2;
|
|
142
|
-
if (s25 < s && p <= C.R5_P_MAX)
|
|
143
|
-
return Region.Region5;
|
|
144
|
-
}
|
|
145
|
-
return -1;
|
|
146
|
-
}
|
|
147
|
-
/** Extract enthalpy or entropy from a BasicProperties object. */
|
|
148
|
-
function propertyOf(state, property) {
|
|
149
|
-
return property === 'enthalpy' ? state.enthalpy : state.entropy;
|
|
150
|
-
}
|
|
151
|
-
/** Check whether value lies within [min(a,b), max(a,b)] with relative tolerance. */
|
|
152
|
-
function inRange(value, a, b) {
|
|
153
|
-
const tolerance = 1e-9 * Math.max(1, Math.abs(a), Math.abs(b), Math.abs(value));
|
|
154
|
-
return value >= Math.min(a, b) - tolerance && value <= Math.max(a, b) + tolerance;
|
|
155
|
-
}
|
|
156
|
-
/** Check approximate equality with relative tolerance. */
|
|
157
|
-
function isClose(a, b) {
|
|
158
|
-
return Math.abs(a - b) <= 1e-9 * Math.max(1, Math.abs(a), Math.abs(b));
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Generic region detector for temperature + (enthalpy or entropy) inputs.
|
|
162
|
-
*
|
|
163
|
-
* Walks through the IAPWS-IF97 temperature ranges (Region 5 → supercritical →
|
|
164
|
-
* sub-critical) and checks whether the given property value falls within each
|
|
165
|
-
* region's bounds at the specified temperature.
|
|
166
|
-
*
|
|
167
|
-
* @param T - Temperature [K]
|
|
168
|
-
* @param value - Specific enthalpy [kJ/kg] or specific entropy [kJ/(kg·K)]
|
|
169
|
-
* @param property - Which property `value` represents
|
|
170
|
-
* @returns Region number (1–5) or -1 if out of range
|
|
171
|
-
*/
|
|
172
|
-
function detectRegionByTemperatureProperty(T, value, property) {
|
|
173
|
-
if (T < C.T_MIN || T > C.T_MAX) {
|
|
174
|
-
return -1;
|
|
175
|
-
}
|
|
176
|
-
if (T > C.R5_T_MIN) {
|
|
177
|
-
return inRange(value, propertyOf(region5(C.P_MIN, T), property), propertyOf(region5(C.R5_P_MAX, T), property))
|
|
178
|
-
? Region.Region5
|
|
179
|
-
: -1;
|
|
180
|
-
}
|
|
181
|
-
if (T > C.B23_T_MAX) {
|
|
182
|
-
return inRange(value, propertyOf(region2(C.P_MIN, T), property), propertyOf(region2(C.P_MAX, T), property))
|
|
183
|
-
? Region.Region2
|
|
184
|
-
: -1;
|
|
185
|
-
}
|
|
186
|
-
if (T >= C.Tc) {
|
|
187
|
-
const p23 = boundary23_T_to_P(T);
|
|
188
|
-
if (inRange(value, propertyOf(region2(C.P_MIN, T), property), propertyOf(region2(p23, T), property))) {
|
|
189
|
-
return Region.Region2;
|
|
190
|
-
}
|
|
191
|
-
return inRange(value, propertyOf(solveRegion3PTBasic(p23, T), property), propertyOf(solveRegion3PTBasic(C.P_MAX, T), property))
|
|
192
|
-
? Region.Region3
|
|
193
|
-
: -1;
|
|
194
|
-
}
|
|
195
|
-
const endpoints = saturationEndpointsAtTemperature(T);
|
|
196
|
-
const liquidProperty = propertyOf(endpoints.liquid, property);
|
|
197
|
-
const vaporProperty = propertyOf(endpoints.vapor, property);
|
|
198
|
-
if (T < C.Tt) {
|
|
199
|
-
if (inRange(value, liquidProperty, propertyOf(region1(C.P_MAX, T), property))) {
|
|
200
|
-
return Region.Region1;
|
|
201
|
-
}
|
|
202
|
-
return inRange(value, vaporProperty, propertyOf(region2(C.P_MIN, T), property))
|
|
203
|
-
? Region.Region2
|
|
204
|
-
: -1;
|
|
205
|
-
}
|
|
206
|
-
if (inRange(value, liquidProperty, vaporProperty)) {
|
|
207
|
-
return Region.Region4;
|
|
208
|
-
}
|
|
209
|
-
if (T <= C.R2_T_MIN && inRange(value, liquidProperty, propertyOf(region1(C.P_MAX, T), property))) {
|
|
210
|
-
return Region.Region1;
|
|
211
|
-
}
|
|
212
|
-
if (T > C.R2_T_MIN && inRange(value, liquidProperty, propertyOf(solveRegion3PTBasic(C.P_MAX, T), property))) {
|
|
213
|
-
return Region.Region3;
|
|
214
|
-
}
|
|
215
|
-
return inRange(value, vaporProperty, propertyOf(region2(C.P_MIN, T), property))
|
|
216
|
-
? Region.Region2
|
|
217
|
-
: -1;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Detect the IAPWS-IF97 region for given T and H.
|
|
221
|
-
*
|
|
222
|
-
* Uses explicit enthalpy comparisons (including saturation-boundary proximity
|
|
223
|
-
* checks via `isClose`) rather than the generic helper, because enthalpy is
|
|
224
|
-
* not monotonic across regions at some temperatures.
|
|
225
|
-
*
|
|
226
|
-
* @param T - Temperature [K]
|
|
227
|
-
* @param h - Specific enthalpy [kJ/kg]
|
|
228
|
-
* @returns Region number (1–5) or -1 if out of range
|
|
229
|
-
*/
|
|
230
|
-
export function detectRegionTH(T, h) {
|
|
231
|
-
if (T < C.T_MIN || T > C.T_MAX) {
|
|
232
|
-
return -1;
|
|
233
|
-
}
|
|
234
|
-
if (T > C.R5_T_MIN) {
|
|
235
|
-
return inRange(h, region5(C.P_MIN, T).enthalpy, region5(C.R5_P_MAX, T).enthalpy)
|
|
236
|
-
? Region.Region5
|
|
237
|
-
: -1;
|
|
238
|
-
}
|
|
239
|
-
if (T > C.B23_T_MAX) {
|
|
240
|
-
return inRange(h, region2(C.P_MIN, T).enthalpy, region2(C.P_MAX, T).enthalpy)
|
|
241
|
-
? Region.Region2
|
|
242
|
-
: -1;
|
|
243
|
-
}
|
|
244
|
-
if (T >= C.Tc) {
|
|
245
|
-
const p23 = boundary23_T_to_P(T);
|
|
246
|
-
if (inRange(h, region2(C.P_MIN, T).enthalpy, region2(p23, T).enthalpy)) {
|
|
247
|
-
return Region.Region2;
|
|
248
|
-
}
|
|
249
|
-
return inRange(h, solveRegion3PTBasic(p23, T).enthalpy, solveRegion3PTBasic(C.P_MAX, T).enthalpy)
|
|
250
|
-
? Region.Region3
|
|
251
|
-
: -1;
|
|
252
|
-
}
|
|
253
|
-
const endpoints = saturationEndpointsAtTemperature(T);
|
|
254
|
-
const hf = endpoints.liquid.enthalpy;
|
|
255
|
-
const hg = endpoints.vapor.enthalpy;
|
|
256
|
-
if (T < C.Tt) {
|
|
257
|
-
if (inRange(h, hf, region1(C.P_MAX, T).enthalpy)) {
|
|
258
|
-
return Region.Region1;
|
|
259
|
-
}
|
|
260
|
-
return inRange(h, hg, region2(C.P_MIN, T).enthalpy)
|
|
261
|
-
? Region.Region2
|
|
262
|
-
: -1;
|
|
263
|
-
}
|
|
264
|
-
// Exact saturation boundary: snap to Region 4 immediately.
|
|
265
|
-
if (isClose(h, hf) || isClose(h, hg)) {
|
|
266
|
-
return Region.Region4;
|
|
267
|
-
}
|
|
268
|
-
// Compressed liquid / supercritical fluid checks MUST come before the
|
|
269
|
-
// general two-phase dome check. Compressed liquid enthalpy is slightly
|
|
270
|
-
// above hf, so it falls within [hf, hg] — checking R4 first would
|
|
271
|
-
// misclassify compressed liquid states.
|
|
272
|
-
//
|
|
273
|
-
// For T > 623.15 K (near Tc), enthalpy decreases with pressure in R3,
|
|
274
|
-
// so the R3 range [h_R3_max, hf] does NOT overlap the dome [hf, hg],
|
|
275
|
-
// and the R4 fallback below correctly catches two-phase states.
|
|
276
|
-
if (T <= C.R2_T_MIN && inRange(h, hf, region1(C.P_MAX, T).enthalpy)) {
|
|
277
|
-
return Region.Region1;
|
|
278
|
-
}
|
|
279
|
-
if (T > C.R2_T_MIN && inRange(h, hf, solveRegion3PTBasic(C.P_MAX, T).enthalpy)) {
|
|
280
|
-
return Region.Region3;
|
|
281
|
-
}
|
|
282
|
-
// Remaining enthalpy values in the dome are two-phase.
|
|
283
|
-
if (inRange(h, hf, hg)) {
|
|
284
|
-
return Region.Region4;
|
|
285
|
-
}
|
|
286
|
-
return inRange(h, hg, region2(C.P_MIN, T).enthalpy)
|
|
287
|
-
? Region.Region2
|
|
288
|
-
: -1;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Detect the IAPWS-IF97 region for given T and S.
|
|
292
|
-
*
|
|
293
|
-
* Delegates to the generic temperature-property detector since entropy
|
|
294
|
-
* is monotonic across regions at fixed temperature.
|
|
295
|
-
*
|
|
296
|
-
* @param T - Temperature [K]
|
|
297
|
-
* @param s - Specific entropy [kJ/(kg·K)]
|
|
298
|
-
* @returns Region number (1–5) or -1 if out of range
|
|
299
|
-
*/
|
|
300
|
-
export function detectRegionTS(T, s) {
|
|
301
|
-
return detectRegionByTemperatureProperty(T, s, 'entropy');
|
|
302
|
-
}
|
|
303
|
-
// ─── H-S Boundary Helper Functions ──────────────────────────────────────────
|
|
304
|
-
// From IAPWS Supplementary Release on backward equations p(h,s)
|
|
305
|
-
/**
|
|
306
|
-
* Saturated liquid enthalpy boundary for Region 1 (Eq. 3).
|
|
307
|
-
* h'_1(s)
|
|
308
|
-
*/
|
|
309
|
-
function h1Sat(s) {
|
|
310
|
-
const TABLE = [
|
|
311
|
-
[0, 14, 0.332171191705237], [0, 36, 6.11217706323497e-4], [1, 3, -8.82092478906822],
|
|
312
|
-
[1, 16, -0.45562819254325], [2, 0, -2.63483840850452e-5], [2, 5, -22.3949661148062],
|
|
313
|
-
[3, 4, -4.28398660164013], [3, 36, -0.616679338856916], [4, 4, -14.68230311044],
|
|
314
|
-
[4, 16, 284.523138727299], [4, 24, -113.398503195444], [5, 18, 1156.71380760859],
|
|
315
|
-
[5, 24, 395.551267359325], [7, 1, -1.54891257229285], [8, 4, 19.4486637751291],
|
|
316
|
-
[12, 2, -3.57915139457043], [12, 4, -3.35369414148819], [14, 1, -0.66442679633246],
|
|
317
|
-
[14, 22, 32332.1885383934], [16, 10, 3317.66744667084], [20, 12, -22350.1257931087],
|
|
318
|
-
[20, 28, 5739538.75852936], [22, 8, 173.226193407919], [24, 3, -0.0363968822121321],
|
|
319
|
-
[28, 0, 8.34596332878346e-7], [32, 6, 5.03611916682674], [32, 8, 65.5444787064505],
|
|
320
|
-
];
|
|
321
|
-
const sig = s / 3.8;
|
|
322
|
-
let h = 0;
|
|
323
|
-
for (const [I, J, N] of TABLE) {
|
|
324
|
-
h += N * Math.pow(sig - 1.09, I) * Math.pow(sig + 0.0000366, J);
|
|
325
|
-
}
|
|
326
|
-
return 1700 * h;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Region 1 upper enthalpy bound at 100 MPa for a given entropy.
|
|
330
|
-
* This closes the low-entropy infeasible gap where using the global
|
|
331
|
-
* Region 1 enthalpy maximum would admit impossible h-s pairs.
|
|
332
|
-
*/
|
|
333
|
-
function h1UpperBoundAtPmax(s) {
|
|
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;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Region 1 / Region 3 boundary enthalpy (Eq. 1, B13).
|
|
348
|
-
* h_13(s)
|
|
349
|
-
*/
|
|
350
|
-
function h13Boundary(s) {
|
|
351
|
-
const TABLE = [
|
|
352
|
-
[0, 0, 0.913965547600543], [1, -2, -0.0000430944856041991], [1, 2, 60.3235694765419],
|
|
353
|
-
[3, -12, 1.17518273082168e-18], [5, -4, 0.220000904781292], [6, -3, -69.0815545851641],
|
|
354
|
-
];
|
|
355
|
-
const sig = s / 3.8;
|
|
356
|
-
let h = 0;
|
|
357
|
-
for (const [I, J, N] of TABLE) {
|
|
358
|
-
h += N * Math.pow(sig - 0.884, I) * Math.pow(sig - 0.864, J);
|
|
359
|
-
}
|
|
360
|
-
return 1700 * h;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Region 3a / Region 4 saturation boundary (Eq. 4).
|
|
364
|
-
* h'_3a(s)
|
|
365
|
-
*/
|
|
366
|
-
function h3aSat(s) {
|
|
367
|
-
const TABLE = [
|
|
368
|
-
[0, 1, 0.822673364673336], [0, 4, 0.181977213534479], [0, 10, -0.0112000260313624],
|
|
369
|
-
[0, 16, -7.46778287048033e-4], [2, 1, -0.179046263257381], [3, 36, 0.0424220110836657],
|
|
370
|
-
[4, 3, -0.341355823438768], [4, 16, -2.09881740853565], [5, 20, -8.22477343323596],
|
|
371
|
-
[5, 36, -4.99684082076008], [6, 4, 0.191413958471069], [7, 2, 0.0581062241093136],
|
|
372
|
-
[7, 28, -1655.05498701029], [7, 32, 1588.70443421201], [10, 14, -85.0623535172818],
|
|
373
|
-
[10, 32, -31771.4386511207], [10, 36, -94589.0406632871], [32, 0, -1.3927384708869e-6],
|
|
374
|
-
[32, 6, 0.63105253224098],
|
|
375
|
-
];
|
|
376
|
-
const sig = s / 3.8;
|
|
377
|
-
let h = 0;
|
|
378
|
-
for (const [I, J, N] of TABLE) {
|
|
379
|
-
h += N * Math.pow(sig - 1.09, I) * Math.pow(sig + 0.0000366, J);
|
|
380
|
-
}
|
|
381
|
-
return 1700 * h;
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Region 2ab / Region 4 saturation boundary (Eq. 5).
|
|
385
|
-
* h"_2ab(s)
|
|
386
|
-
*/
|
|
387
|
-
function h2abSat(s) {
|
|
388
|
-
const TABLE = [
|
|
389
|
-
[1, 8, -524.581170928788], [1, 24, -9269471.88142218], [2, 4, -237.385107491666],
|
|
390
|
-
[2, 32, 2.10770155812776e10], [4, 1, -23.9494562010986], [4, 2, 221.802480294197],
|
|
391
|
-
[7, 7, -5104725.33393438], [8, 5, 1249813.96109147], [8, 12, 2.00008436996201e9],
|
|
392
|
-
[10, 1, -815.158509791035], [12, 0, -157.612685637523], [12, 7, -1.14200422332791e10],
|
|
393
|
-
[18, 10, 6.62364680776872e15], [20, 12, -2.27622818296144e18], [24, 32, -1.71048081348406e31],
|
|
394
|
-
[28, 8, 6.60788766938091e15], [28, 12, 1.66320055886021e22], [28, 20, -2.18003784381501e29],
|
|
395
|
-
[28, 22, -7.87276140295618e29], [28, 24, 1.51062329700346e31], [32, 2, 7.95732170300541e6],
|
|
396
|
-
[32, 7, 1.31957647355347e15], [32, 12, -3.2509706829914e23], [32, 14, -4.18600611419248e25],
|
|
397
|
-
[32, 24, 2.97478906557467e34], [36, 10, -9.53588761745473e19], [36, 12, 1.66957699620939e24],
|
|
398
|
-
[36, 20, -1.75407764869978e32], [36, 22, 3.47581490626396e34], [36, 28, -7.10971318427851e38],
|
|
399
|
-
];
|
|
400
|
-
const sig1 = s / 5.21;
|
|
401
|
-
const sig2 = s / 9.2;
|
|
402
|
-
let h = 0;
|
|
403
|
-
for (const [I, J, N] of TABLE) {
|
|
404
|
-
h += N * Math.pow(1 / sig1 - 0.513, I) * Math.pow(sig2 - 0.524, J);
|
|
405
|
-
}
|
|
406
|
-
return 2800 * Math.exp(h);
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Region 2c / Region 3b saturation boundary (Eq. 6).
|
|
410
|
-
* h"_2c3b(s)
|
|
411
|
-
*/
|
|
412
|
-
function h2c3bSat(s) {
|
|
413
|
-
const TABLE = [
|
|
414
|
-
[0, 0, 1.04351280732769], [0, 3, -2.27807912708513], [0, 4, 1.80535256723202],
|
|
415
|
-
[1, 0, 0.420440834792042], [1, 12, -1.0572124483466e5], [5, 36, 4.36911607493884e24],
|
|
416
|
-
[6, 12, -3.28032702839753e11], [7, 16, -6.7868676080427e15], [8, 2, 7.43957464645363e3],
|
|
417
|
-
[8, 20, -3.56896445355761e19], [12, 32, 1.67590585186801e31], [16, 36, -3.55028625419105e37],
|
|
418
|
-
[22, 2, 3.96611982166538e11], [22, 32, -4.14716268484468e40], [24, 7, 3.59080103867382e18],
|
|
419
|
-
[36, 20, -1.16994334851995e40],
|
|
420
|
-
];
|
|
421
|
-
const sig = s / 5.9;
|
|
422
|
-
let h = 0;
|
|
423
|
-
for (const [I, J, N] of TABLE) {
|
|
424
|
-
h += N * Math.pow(sig - 1.02, I) * Math.pow(sig - 0.726, J);
|
|
425
|
-
}
|
|
426
|
-
return 2800 * h * h * h * h;
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* Region 2/3 boundary temperature from h,s (Eq. 8).
|
|
430
|
-
* T_B23(h,s)
|
|
431
|
-
*/
|
|
432
|
-
function tB23hs(h, s) {
|
|
433
|
-
const TABLE = [
|
|
434
|
-
[-12, 10, 6.2909626082981e-4], [-10, 8, -8.23453502583165e-4], [-8, 3, 5.15446951519474e-8],
|
|
435
|
-
[-4, 4, -1.17565945784945], [-3, 3, 3.48519684726192], [-2, -6, -5.07837382408313e-12],
|
|
436
|
-
[-2, 2, -2.84637670005479], [-2, 3, -2.36092263939673], [-2, 4, 6.01492324973779],
|
|
437
|
-
[0, 0, 1.48039650824546], [1, -3, 3.60075182221907e-4], [1, -2, -1.26700045009952e-2],
|
|
438
|
-
[1, 10, -1.22184332521413e6], [3, -2, 1.49276502463272e-1], [3, -1, 6.98733471798484e-1],
|
|
439
|
-
[5, -5, -2.52207040114321e-2], [6, -6, 1.47151930985213e-2], [6, -3, -1.08618917681849],
|
|
440
|
-
[8, -8, -9.36875039816322e-4], [8, -2, 8.19877897570217e1], [8, -1, -1.82041861521835e2],
|
|
441
|
-
[12, -12, 2.61907376402688e-6], [12, -1, -2.91626417025961e4], [14, -12, 1.40660774926165e-5],
|
|
442
|
-
[14, 1, 7.83237062349385e6],
|
|
443
|
-
];
|
|
444
|
-
const mu = h / 3000;
|
|
445
|
-
const sig = s / 5.3;
|
|
446
|
-
let T = 0;
|
|
447
|
-
for (const [I, J, N] of TABLE) {
|
|
448
|
-
T += N * Math.pow(mu - 0.727, I) * Math.pow(sig - 0.864, J);
|
|
449
|
-
}
|
|
450
|
-
return 900 * T;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Backward equation P(h,s) for Region 2c (used for R2/R3 discrimination).
|
|
454
|
-
*/
|
|
455
|
-
function backward2cPhs(h, s) {
|
|
456
|
-
const TABLE = [
|
|
457
|
-
[0, 0, 0.112225607199012], [0, 1, -3.39005953606712], [0, 2, -32.0503911730094],
|
|
458
|
-
[0, 3, -197.5973051049], [0, 4, -407.693861553446], [0, 8, 13294.3775222331],
|
|
459
|
-
[1, 0, 1.70846839774007], [1, 2, 37.3694198142245], [1, 5, 3581.44365815434],
|
|
460
|
-
[1, 8, 423014.446424664], [1, 14, -751071025.760063], [2, 2, 52.3446127607898],
|
|
461
|
-
[2, 3, -228.351290812417], [2, 7, -960652.417056937], [2, 10, -80705929.2526074],
|
|
462
|
-
[2, 18, 1626980172256.69], [3, 0, 0.772465073604171], [3, 5, 46392.9973837746],
|
|
463
|
-
[3, 8, -13731788.5134128], [3, 16, 1704703926305.12], [3, 18, -25110462818730.8],
|
|
464
|
-
[4, 18, 31774883083552.0], [5, 1, 53.8685623675312], [5, 4, -55308.9094625169],
|
|
465
|
-
[5, 6, -1028615.22421405], [5, 14, 2042494187562.34], [6, 8, 273918446.626977],
|
|
466
|
-
[6, 18, -2.63963146312685e15], [10, 7, -1078908541.08088], [12, 7, -29649262098.0124],
|
|
467
|
-
[16, 10, -1.11754907323424e15],
|
|
468
|
-
];
|
|
469
|
-
const mu = h / 3500;
|
|
470
|
-
const sig = s / 5.9;
|
|
471
|
-
let P = 0;
|
|
472
|
-
for (const [I, J, N] of TABLE) {
|
|
473
|
-
P += N * Math.pow(mu - 0.7, I) * Math.pow(sig - 1.1, J);
|
|
474
|
-
}
|
|
475
|
-
return 100 * P * P * P * P;
|
|
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;
|
|
33
|
+
export function nativeSaturationTemperatureBracket(p) {
|
|
34
|
+
let lower = C.T_MIN;
|
|
35
|
+
let upper = p > saturationPressure(C.R2_T_MIN) ? C.Tc : C.R2_T_MIN;
|
|
36
|
+
while (true) {
|
|
37
|
+
const middle = (lower + upper) / 2;
|
|
38
|
+
if (middle === lower || middle === upper)
|
|
39
|
+
break;
|
|
40
|
+
if (saturationPressure(middle) <= p)
|
|
41
|
+
lower = middle;
|
|
532
42
|
else
|
|
533
|
-
upper =
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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;
|
|
43
|
+
upper = middle;
|
|
44
|
+
}
|
|
45
|
+
return [lower, upper];
|
|
46
|
+
}
|
|
47
|
+
/** Invert the native B23 boundary; rounded Eq. (6) cannot determine PT ownership. */
|
|
48
|
+
export function nativeB23TemperatureBracket(p) {
|
|
49
|
+
let lower = C.R2_T_MIN;
|
|
50
|
+
let upper = C.B23_T_MAX;
|
|
51
|
+
while (true) {
|
|
52
|
+
const middle = (lower + upper) / 2;
|
|
53
|
+
if (middle === lower || middle === upper)
|
|
54
|
+
break;
|
|
55
|
+
if (boundary23_T_to_P(middle) <= p)
|
|
56
|
+
lower = middle;
|
|
599
57
|
else
|
|
600
|
-
upper =
|
|
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
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Detect the IAPWS-IF97 region for given H and S.
|
|
648
|
-
* Implements the zone-based detection from the IAPWS supplementary release.
|
|
649
|
-
*
|
|
650
|
-
* @param h - Specific enthalpy [kJ/kg]
|
|
651
|
-
* @param s - Specific entropy [kJ/(kg·K)]
|
|
652
|
-
* @returns Region number (1–5) or -1 if out of range
|
|
653
|
-
*/
|
|
654
|
-
export function detectRegionHS(h, s) {
|
|
655
|
-
if (s >= HS_TRIPLE_LIQUID.entropy && s <= HS_TRIPLE_VAPOR.entropy &&
|
|
656
|
-
isClose(h, hSatLow(s))) {
|
|
657
|
-
return Region.Region4;
|
|
658
|
-
}
|
|
659
|
-
const highTemperatureRegion = classifyRegion5HS(h, s);
|
|
660
|
-
if (highTemperatureRegion !== null)
|
|
661
|
-
return highTemperatureRegion;
|
|
662
|
-
// Zone 1: smin <= s <= s13 (Region 1 / Region 4)
|
|
663
|
-
if (s >= HS_TRIPLE_LIQUID.entropy && s <= HS_S13) {
|
|
664
|
-
const hmin = hSatLow(s);
|
|
665
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h1Sat(s));
|
|
666
|
-
const hmax = h1UpperBoundAtPmax(s);
|
|
667
|
-
if (h >= hmin && h <= hs)
|
|
668
|
-
return Region.Region4;
|
|
669
|
-
if (h >= hs && h <= hmax)
|
|
670
|
-
return Region.Region1;
|
|
671
|
-
}
|
|
672
|
-
// Zone 2: s13 < s <= s13s (Region 1 / Region 3 / Region 4)
|
|
673
|
-
else if (s > HS_S13 && s <= HS_S13S) {
|
|
674
|
-
const hmin = hSatLow(s);
|
|
675
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h1Sat(s));
|
|
676
|
-
const h13 = h13Boundary(s);
|
|
677
|
-
const hmax = 2800; // Region 3 upper bound
|
|
678
|
-
if (h >= hmin && h <= hs)
|
|
679
|
-
return Region.Region4;
|
|
680
|
-
if (h >= hs && h < h13)
|
|
681
|
-
return Region.Region1;
|
|
682
|
-
if (h >= h13 && h <= hmax)
|
|
683
|
-
return Region.Region3;
|
|
684
|
-
}
|
|
685
|
-
// Zone 3: s13s < s <= R3_CRT_S (Region 3 / Region 4)
|
|
686
|
-
else if (s > HS_S13S && s <= C.R3_S_CRT) {
|
|
687
|
-
const hmin = hSatLow(s);
|
|
688
|
-
const hmax = 2800; // generous upper bound for R3
|
|
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)
|
|
696
|
-
return Region.Region4;
|
|
697
|
-
if (h >= hs && h <= hmax)
|
|
698
|
-
return Region.Region3;
|
|
58
|
+
upper = middle;
|
|
699
59
|
}
|
|
700
|
-
|
|
701
|
-
else if (s > C.R3_S_CRT && s < C.B23_S_CURVE_MIN) {
|
|
702
|
-
const hmin = hSatLow(s);
|
|
703
|
-
const hmax = 2800; // generous upper bound for R3
|
|
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)
|
|
711
|
-
return Region.Region4;
|
|
712
|
-
if (h >= hs && h <= hmax)
|
|
713
|
-
return Region.Region3;
|
|
714
|
-
}
|
|
715
|
-
// Zone 5: B23_S_CURVE_MIN <= s < B23_S_CURVE_MAX (Region 2 / Region 3 / Region 4)
|
|
716
|
-
else if (s >= C.B23_S_CURVE_MIN && s < C.B23_S_CURVE_MAX) {
|
|
717
|
-
const hmin = hSatLow(s);
|
|
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)
|
|
723
|
-
return Region.Region4;
|
|
724
|
-
if (hs <= h && h < h23min)
|
|
725
|
-
return Region.Region3;
|
|
726
|
-
if (h23min <= h && h < h23max) {
|
|
727
|
-
// Discriminate R2 vs R3 using B23 boundary
|
|
728
|
-
const Tb23 = tB23hs(h, s);
|
|
729
|
-
const Pb23 = boundary23_T_to_P(Tb23);
|
|
730
|
-
const Phs = backward2cPhs(h, s);
|
|
731
|
-
if (Phs <= Pb23)
|
|
732
|
-
return Region.Region2;
|
|
733
|
-
return Region.Region3;
|
|
734
|
-
}
|
|
735
|
-
if (h23max <= h && h <= hmax)
|
|
736
|
-
return Region.Region2;
|
|
737
|
-
}
|
|
738
|
-
// Zone 6: B23_S_CURVE_MAX <= s < R2_S_CRT (Region 2 / Region 4)
|
|
739
|
-
else if (s >= C.B23_S_CURVE_MAX && s < C.R2_S_CRT) {
|
|
740
|
-
const hmin = hSatLow(s);
|
|
741
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2c3bSat(s));
|
|
742
|
-
const hmax = HS_R2_PMAX_TMAX.enthalpy;
|
|
743
|
-
if (hmin <= h && h <= hs)
|
|
744
|
-
return Region.Region4;
|
|
745
|
-
if (hs <= h && h <= hmax)
|
|
746
|
-
return Region.Region2;
|
|
747
|
-
}
|
|
748
|
-
// Zone 7: R2_S_CRT <= s < sTPmax (Region 2 / Region 4, use h2abSat)
|
|
749
|
-
else if (s >= C.R2_S_CRT && s < HS_S_TP_MAX) {
|
|
750
|
-
const hmin = hSatLow(s);
|
|
751
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
752
|
-
const hmax = HS_R2_PMAX_TMAX.enthalpy;
|
|
753
|
-
if (hmin <= h && h <= hs)
|
|
754
|
-
return Region.Region4;
|
|
755
|
-
if (hs <= h && h <= hmax)
|
|
756
|
-
return Region.Region2;
|
|
757
|
-
}
|
|
758
|
-
// Zone 8: sTPmax <= s < s2ab (Region 2 / Region 4)
|
|
759
|
-
else if (s >= HS_S_TP_MAX && s < HS_S2AB) {
|
|
760
|
-
const hmin = hSatLow(s);
|
|
761
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
762
|
-
const hmax = HS_R2_PMIN_TMAX.enthalpy;
|
|
763
|
-
if (hmin <= h && h <= hs)
|
|
764
|
-
return Region.Region4;
|
|
765
|
-
if (hs <= h && h <= hmax)
|
|
766
|
-
return Region.Region2;
|
|
767
|
-
}
|
|
768
|
-
// Zone 9: s2ab <= s < s4v (Region 2 / Region 4)
|
|
769
|
-
else if (s >= HS_S2AB && s < HS_TRIPLE_VAPOR.entropy) {
|
|
770
|
-
const hmin = hSatLow(s);
|
|
771
|
-
const hs = refinedSaturationBoundaryEnthalpy(h, s, h2abSat(s));
|
|
772
|
-
const hmax = HS_R2_PMIN_TMAX.enthalpy;
|
|
773
|
-
if (hmin <= h && h <= hs)
|
|
774
|
-
return Region.Region4;
|
|
775
|
-
if (hs <= h && h <= hmax)
|
|
776
|
-
return Region.Region2;
|
|
777
|
-
}
|
|
778
|
-
// Zone 10: s4v <= s <= smax (Region 2 only, superheated low-P)
|
|
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;
|
|
782
|
-
if (hmin <= h && h <= hmax)
|
|
783
|
-
return Region.Region2;
|
|
784
|
-
}
|
|
785
|
-
return -1;
|
|
60
|
+
return [lower, upper];
|
|
786
61
|
}
|