iapws-if97 2.1.5 → 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.
Files changed (109) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/README.md +78 -215
  3. package/dist/backward/candidate-resolution.d.ts +8 -0
  4. package/dist/backward/candidate-resolution.js +40 -0
  5. package/dist/backward/fixed-temperature-candidates.d.ts +4 -0
  6. package/dist/backward/fixed-temperature-candidates.js +188 -0
  7. package/dist/backward/fixed-temperature-solver.d.ts +6 -6
  8. package/dist/backward/fixed-temperature-solver.js +56 -9
  9. package/dist/backward/fixed-temperature-stationary.d.ts +4 -0
  10. package/dist/backward/fixed-temperature-stationary.js +146 -0
  11. package/dist/backward/hs-region.d.ts +50 -0
  12. package/dist/backward/hs-region.js +695 -0
  13. package/dist/backward/hs-state.d.ts +15 -0
  14. package/dist/backward/hs-state.js +316 -0
  15. package/dist/backward/hs.d.ts +1 -2
  16. package/dist/backward/hs.js +28 -232
  17. package/dist/backward/ph.d.ts +8 -3
  18. package/dist/backward/ph.js +102 -54
  19. package/dist/backward/pressure-property-region.d.ts +13 -0
  20. package/dist/backward/pressure-property-region.js +262 -0
  21. package/dist/backward/ps.d.ts +6 -1
  22. package/dist/backward/ps.js +103 -38
  23. package/dist/backward/solution-validation.d.ts +7 -2
  24. package/dist/backward/solution-validation.js +162 -37
  25. package/dist/backward/th.d.ts +3 -6
  26. package/dist/backward/th.js +10 -73
  27. package/dist/backward/tolerances.d.ts +3 -1
  28. package/dist/backward/tolerances.js +9 -4
  29. package/dist/backward/ts.d.ts +3 -6
  30. package/dist/backward/ts.js +10 -73
  31. package/dist/boundaries.d.ts +0 -1
  32. package/dist/boundaries.js +0 -1
  33. package/dist/constants.d.ts +11 -24
  34. package/dist/constants.js +17 -28
  35. package/dist/core/floating-point.d.ts +2 -0
  36. package/dist/core/floating-point.js +7 -0
  37. package/dist/core/formulation-seams.d.ts +27 -0
  38. package/dist/core/formulation-seams.js +289 -0
  39. package/dist/core/input-validation.d.ts +1 -1
  40. package/dist/core/input-validation.js +5 -1
  41. package/dist/core/region-detector.d.ts +3 -41
  42. package/dist/core/region-detector.js +39 -597
  43. package/dist/core/region3-pt.d.ts +12 -1
  44. package/dist/core/region3-pt.js +68 -13
  45. package/dist/core/solve-input-normalization.d.ts +0 -1
  46. package/dist/core/solve-input-normalization.js +4 -2
  47. package/dist/core/solver.d.ts +7 -11
  48. package/dist/core/solver.js +26 -18
  49. package/dist/core/thermodynamic-derivatives.d.ts +17 -0
  50. package/dist/core/thermodynamic-derivatives.js +27 -0
  51. package/dist/detect.d.ts +16 -3
  52. package/dist/detect.js +36 -3
  53. package/dist/errors.d.ts +11 -0
  54. package/dist/errors.js +16 -0
  55. package/dist/gas-constant.d.ts +5 -0
  56. package/dist/gas-constant.js +5 -0
  57. package/dist/index.d.ts +4 -6
  58. package/dist/index.js +10 -22
  59. package/dist/regions/boundaries.d.ts +0 -1
  60. package/dist/regions/boundaries.js +0 -1
  61. package/dist/regions/region1.d.ts +2 -2
  62. package/dist/regions/region1.js +1 -2
  63. package/dist/regions/region2.d.ts +2 -2
  64. package/dist/regions/region2.js +1 -2
  65. package/dist/regions/region3-data.d.ts +0 -1
  66. package/dist/regions/region3-data.js +3 -4
  67. package/dist/regions/region3-eval.d.ts +0 -1
  68. package/dist/regions/region3-eval.js +0 -1
  69. package/dist/regions/region3-subregions.d.ts +0 -1
  70. package/dist/regions/region3-subregions.js +15 -11
  71. package/dist/regions/region3.d.ts +4 -2
  72. package/dist/regions/region3.js +32 -3
  73. package/dist/regions/region4.d.ts +2 -1
  74. package/dist/regions/region4.js +39 -13
  75. package/dist/regions/region5.d.ts +2 -2
  76. package/dist/regions/region5.js +2 -3
  77. package/dist/regions.d.ts +0 -1
  78. package/dist/regions.js +0 -1
  79. package/dist/saturation/common.d.ts +2 -9
  80. package/dist/saturation/common.js +66 -45
  81. package/dist/saturation/region4-boundaries.d.ts +0 -5
  82. package/dist/saturation/region4-boundaries.js +17 -18
  83. package/dist/saturation/region4-hs.d.ts +2 -1
  84. package/dist/saturation/region4-hs.js +499 -106
  85. package/dist/saturation/two-phase.d.ts +2 -3
  86. package/dist/saturation/two-phase.js +7 -8
  87. package/dist/saturation.d.ts +0 -1
  88. package/dist/saturation.js +0 -1
  89. package/dist/solvers/bracketed-newton.d.ts +10 -6
  90. package/dist/solvers/bracketed-newton.js +19 -13
  91. package/dist/solvers/damped-newton-2d.d.ts +12 -0
  92. package/dist/solvers/damped-newton-2d.js +68 -0
  93. package/dist/solvers/extended-arithmetic.d.ts +6 -0
  94. package/dist/solvers/extended-arithmetic.js +38 -0
  95. package/dist/solvers/newton-raphson.d.ts +0 -1
  96. package/dist/solvers/newton-raphson.js +0 -1
  97. package/dist/transport/properties.d.ts +4 -3
  98. package/dist/transport/properties.js +14 -5
  99. package/dist/transport.d.ts +0 -1
  100. package/dist/transport.js +0 -1
  101. package/dist/types.d.ts +18 -1
  102. package/dist/types.js +14 -1
  103. package/package.json +18 -10
  104. package/dist/backward/objective-normalization.d.ts +0 -7
  105. package/dist/backward/objective-normalization.js +0 -8
  106. package/dist/core/public-normalization.d.ts +0 -4
  107. package/dist/core/public-normalization.js +0 -54
  108. package/dist/solvers/nelder-mead.d.ts +0 -17
  109. package/dist/solvers/nelder-mead.js +0 -116
@@ -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 { saturationTemperature } from '../regions/region4.js';
6
- import { boundary23_P_to_T, boundary23_T_to_P } from '../regions/boundaries.js';
7
- import { region1 } from '../regions/region1.js';
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';
3
+ import { saturationPressure } from '../regions/region4.js';
4
+ import { boundary23_T_to_P } from '../regions/boundaries.js';
12
5
  import { Region } from '../types.js';
13
- import { bracketedNewton } from '../solvers/bracketed-newton.js';
14
- import { solveRegion3PTBasic } from './region3-pt.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,599 +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
- const Tsat = saturationTemperature(p);
25
- if (T >= C.T_MIN && T <= Tsat)
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;
49
- }
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)
62
- return Region.Region2;
63
- if (h25 < h && p <= C.R5_P_MAX)
64
- return Region.Region5;
65
- }
66
- else if (p > C.B23_P_MIN && p <= C.P_MAX) {
67
- const hmin = region1(p, C.T_MIN).enthalpy;
68
- const h13 = region1(p, C.R2_T_MIN).enthalpy;
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;
83
- }
84
- else if (h13 < h && h < h32) {
85
- return Region.Region3;
86
- }
87
- if (h32 <= h && h <= h25)
88
- return Region.Region2;
89
- if (h25 < h && p <= C.R5_P_MAX)
90
- return Region.Region5;
91
- }
92
- return -1;
93
- }
94
- /**
95
- * Detect region for P-S inputs.
96
- */
97
- export function detectRegionPS(p, s) {
98
- if (p >= C.Pt && p < C.Pc) {
99
- const endpoints = saturationEndpointsAtPressure(p);
100
- if (isClose(s, endpoints.liquid.entropy) || isClose(s, endpoints.vapor.entropy)) {
101
- return Region.Region4;
15
+ if (T >= C.T_MIN && T <= C.R2_T_MIN) {
16
+ return p >= saturationPressure(T) ? Region.Region1 : Region.Region2;
102
17
  }
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)
18
+ if (T > C.R2_T_MIN && T <= C.R2_T_MAX)
115
19
  return Region.Region2;
116
- if (s25 < s && p <= C.R5_P_MAX)
117
- return Region.Region5;
118
20
  }
119
21
  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;
22
+ if (T >= C.T_MIN && T <= C.R2_T_MIN) {
23
+ return p >= saturationPressure(T) ? Region.Region1 : Region.Region2;
136
24
  }
137
- else if (s13 < s && s < s32) {
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)))
138
27
  return Region.Region3;
139
- }
140
- if (s32 <= s && s <= s25)
28
+ if (T > C.R2_T_MIN && T <= C.R2_T_MAX)
141
29
  return Region.Region2;
142
- if (s25 < s && p <= C.R5_P_MAX)
143
- return Region.Region5;
144
30
  }
145
31
  return -1;
146
32
  }
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
- const temperature = bracketedNewton((T) => region1(C.P_MAX, T).entropy - s, C.T_MIN, C.R2_T_MIN, 350);
335
- return region1(C.P_MAX, temperature).enthalpy;
336
- }
337
- /**
338
- * Region 1 / Region 3 boundary enthalpy (Eq. 1, B13).
339
- * h_13(s)
340
- */
341
- function h13Boundary(s) {
342
- const TABLE = [
343
- [0, 0, 0.913965547600543], [1, -2, -0.0000430944856041991], [1, 2, 60.3235694765419],
344
- [3, -12, 1.17518273082168e-18], [5, -4, 0.220000904781292], [6, -3, -69.0815545851641],
345
- ];
346
- const sig = s / 3.8;
347
- let h = 0;
348
- for (const [I, J, N] of TABLE) {
349
- h += N * Math.pow(sig - 0.884, I) * Math.pow(sig - 0.864, J);
350
- }
351
- return 1700 * h;
352
- }
353
- /**
354
- * Region 3a / Region 4 saturation boundary (Eq. 4).
355
- * h'_3a(s)
356
- */
357
- function h3aSat(s) {
358
- const TABLE = [
359
- [0, 1, 0.822673364673336], [0, 4, 0.181977213534479], [0, 10, -0.0112000260313624],
360
- [0, 16, -7.46778287048033e-4], [2, 1, -0.179046263257381], [3, 36, 0.0424220110836657],
361
- [4, 3, -0.341355823438768], [4, 16, -2.09881740853565], [5, 20, -8.22477343323596],
362
- [5, 36, -4.99684082076008], [6, 4, 0.191413958471069], [7, 2, 0.0581062241093136],
363
- [7, 28, -1655.05498701029], [7, 32, 1588.70443421201], [10, 14, -85.0623535172818],
364
- [10, 32, -31771.4386511207], [10, 36, -94589.0406632871], [32, 0, -1.3927384708869e-6],
365
- [32, 6, 0.63105253224098],
366
- ];
367
- const sig = s / 3.8;
368
- let h = 0;
369
- for (const [I, J, N] of TABLE) {
370
- h += N * Math.pow(sig - 1.09, I) * Math.pow(sig + 0.0000366, J);
371
- }
372
- return 1700 * h;
373
- }
374
- /**
375
- * Region 2ab / Region 4 saturation boundary (Eq. 5).
376
- * h"_2ab(s)
377
- */
378
- function h2abSat(s) {
379
- const TABLE = [
380
- [1, 8, -524.581170928788], [1, 24, -9269471.88142218], [2, 4, -237.385107491666],
381
- [2, 32, 2.10770155812776e10], [4, 1, -23.9494562010986], [4, 2, 221.802480294197],
382
- [7, 7, -5104725.33393438], [8, 5, 1249813.96109147], [8, 12, 2.00008436996201e9],
383
- [10, 1, -815.158509791035], [12, 0, -157.612685637523], [12, 7, -1.14200422332791e10],
384
- [18, 10, 6.62364680776872e15], [20, 12, -2.27622818296144e18], [24, 32, -1.71048081348406e31],
385
- [28, 8, 6.60788766938091e15], [28, 12, 1.66320055886021e22], [28, 20, -2.18003784381501e29],
386
- [28, 22, -7.87276140295618e29], [28, 24, 1.51062329700346e31], [32, 2, 7.95732170300541e6],
387
- [32, 7, 1.31957647355347e15], [32, 12, -3.2509706829914e23], [32, 14, -4.18600611419248e25],
388
- [32, 24, 2.97478906557467e34], [36, 10, -9.53588761745473e19], [36, 12, 1.66957699620939e24],
389
- [36, 20, -1.75407764869978e32], [36, 22, 3.47581490626396e34], [36, 28, -7.10971318427851e38],
390
- ];
391
- const sig1 = s / 5.21;
392
- const sig2 = s / 9.2;
393
- let h = 0;
394
- for (const [I, J, N] of TABLE) {
395
- h += N * Math.pow(1 / sig1 - 0.513, I) * Math.pow(sig2 - 0.524, J);
396
- }
397
- return 2800 * Math.exp(h);
398
- }
399
- /**
400
- * Region 2c / Region 3b saturation boundary (Eq. 6).
401
- * h"_2c3b(s)
402
- */
403
- function h2c3bSat(s) {
404
- const TABLE = [
405
- [0, 0, 1.04351280732769], [0, 3, -2.27807912708513], [0, 4, 1.80535256723202],
406
- [1, 0, 0.420440834792042], [1, 12, -1.0572124483466e5], [5, 36, 4.36911607493884e24],
407
- [6, 12, -3.28032702839753e11], [7, 16, -6.7868676080427e15], [8, 2, 7.43957464645363e3],
408
- [8, 20, -3.56896445355761e19], [12, 32, 1.67590585186801e31], [16, 36, -3.55028625419105e37],
409
- [22, 2, 3.96611982166538e11], [22, 32, -4.14716268484468e40], [24, 7, 3.59080103867382e18],
410
- [36, 20, -1.16994334851995e40],
411
- ];
412
- const sig = s / 5.9;
413
- let h = 0;
414
- for (const [I, J, N] of TABLE) {
415
- h += N * Math.pow(sig - 1.02, I) * Math.pow(sig - 0.726, J);
416
- }
417
- return 2800 * h * h * h * h;
418
- }
419
- /**
420
- * Region 2/3 boundary temperature from h,s (Eq. 8).
421
- * T_B23(h,s)
422
- */
423
- function tB23hs(h, s) {
424
- const TABLE = [
425
- [-12, 10, 6.2909626082981e-4], [-10, 8, -8.23453502583165e-4], [-8, 3, 5.15446951519474e-8],
426
- [-4, 4, -1.17565945784945], [-3, 3, 3.48519684726192], [-2, -6, -5.07837382408313e-12],
427
- [-2, 2, -2.84637670005479], [-2, 3, -2.36092263939673], [-2, 4, 6.01492324973779],
428
- [0, 0, 1.48039650824546], [1, -3, 3.60075182221907e-4], [1, -2, -1.26700045009952e-2],
429
- [1, 10, -1.22184332521413e6], [3, -2, 1.49276502463272e-1], [3, -1, 6.98733471798484e-1],
430
- [5, -5, -2.52207040114321e-2], [6, -6, 1.47151930985213e-2], [6, -3, -1.08618917681849],
431
- [8, -8, -9.36875039816322e-4], [8, -2, 8.19877897570217e1], [8, -1, -1.82041861521835e2],
432
- [12, -12, 2.61907376402688e-6], [12, -1, -2.91626417025961e4], [14, -12, 1.40660774926165e-5],
433
- [14, 1, 7.83237062349385e6],
434
- ];
435
- const mu = h / 3000;
436
- const sig = s / 5.3;
437
- let T = 0;
438
- for (const [I, J, N] of TABLE) {
439
- T += N * Math.pow(mu - 0.727, I) * Math.pow(sig - 0.864, J);
440
- }
441
- return 900 * T;
442
- }
443
- /**
444
- * Backward equation P(h,s) for Region 2c (used for R2/R3 discrimination).
445
- */
446
- function backward2cPhs(h, s) {
447
- const TABLE = [
448
- [0, 0, 0.112225607199012], [0, 1, -3.39005953606712], [0, 2, -32.0503911730094],
449
- [0, 3, -197.5973051049], [0, 4, -407.693861553446], [0, 8, 13294.3775222331],
450
- [1, 0, 1.70846839774007], [1, 2, 37.3694198142245], [1, 5, 3581.44365815434],
451
- [1, 8, 423014.446424664], [1, 14, -751071025.760063], [2, 2, 52.3446127607898],
452
- [2, 3, -228.351290812417], [2, 7, -960652.417056937], [2, 10, -80705929.2526074],
453
- [2, 18, 1626980172256.69], [3, 0, 0.772465073604171], [3, 5, 46392.9973837746],
454
- [3, 8, -13731788.5134128], [3, 16, 1704703926305.12], [3, 18, -25110462818730.8],
455
- [4, 18, 31774883083552.0], [5, 1, 53.8685623675312], [5, 4, -55308.9094625169],
456
- [5, 6, -1028615.22421405], [5, 14, 2042494187562.34], [6, 8, 273918446.626977],
457
- [6, 18, -2.63963146312685e15], [10, 7, -1078908541.08088], [12, 7, -29649262098.0124],
458
- [16, 10, -1.11754907323424e15],
459
- ];
460
- const mu = h / 3500;
461
- const sig = s / 5.9;
462
- let P = 0;
463
- for (const [I, J, N] of TABLE) {
464
- P += N * Math.pow(mu - 0.7, I) * Math.pow(sig - 1.1, J);
465
- }
466
- return 100 * P * P * P * P;
467
- }
468
- /**
469
- * Detect the IAPWS-IF97 region for given H and S.
470
- * Implements the zone-based detection from the IAPWS supplementary release.
471
- *
472
- * @param h - Specific enthalpy [kJ/kg]
473
- * @param s - Specific entropy [kJ/(kg·K)]
474
- * @returns Region number (1–5) or -1 if out of range
475
- */
476
- export function detectRegionHS(h, s) {
477
- // Pre-compute boundary reference points
478
- const s13 = region1(C.P_MAX, C.R2_T_MIN).entropy;
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) {
484
- return Region.Region4;
485
- }
486
- const triplePointEndpoints = saturationEndpointsAtTemperature(C.Tt);
487
- const _sL = triplePointEndpoints.liquid;
488
- const h4l = _sL.enthalpy;
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);
504
- // Zone 1: smin <= s <= s13 (Region 1 / Region 4)
505
- if (s >= _sL.entropy && s <= s13) {
506
- const hmin = hSatLow(s);
507
- const hs = h1Sat(s);
508
- const hmax = h1UpperBoundAtPmax(s);
509
- if (h >= hmin && h < hs)
510
- return Region.Region4;
511
- if (h >= hs && h <= hmax)
512
- return Region.Region1;
513
- }
514
- // Zone 2: s13 < s <= s13s (Region 1 / Region 3 / Region 4)
515
- else if (s > s13 && s <= s13s) {
516
- const hmin = hSatLow(s);
517
- const hs = h1Sat(s);
518
- const h13 = h13Boundary(s);
519
- const hmax = region1(C.P_MAX, C.R2_T_MIN).enthalpy * 1.1; // generous upper bound
520
- if (h >= hmin && h < hs)
521
- return Region.Region4;
522
- if (h >= hs && h < h13)
523
- return Region.Region1;
524
- if (h >= h13 && h <= hmax)
525
- return Region.Region3;
526
- }
527
- // Zone 3: s13s < s <= R3_CRT_S (Region 3 / Region 4)
528
- else if (s > s13s && s <= C.R3_S_CRT) {
529
- const hmin = hSatLow(s);
530
- const hs = h3aSat(s);
531
- const hmax = 2800; // generous upper bound for R3
532
- if (h >= hmin && h < hs)
533
- return Region.Region4;
534
- if (h >= hs && h <= hmax)
535
- return Region.Region3;
536
- }
537
- // Zone 4: R3_CRT_S < s < B23_S_CURVE_MIN (Region 3 / Region 4)
538
- else if (s > C.R3_S_CRT && s < C.B23_S_CURVE_MIN) {
539
- const hmin = hSatLow(s);
540
- const hs = h2c3bSat(s);
541
- const hmax = 2800; // generous upper bound for R3
542
- if (h >= hmin && h < hs)
543
- return Region.Region4;
544
- if (h >= hs && h <= hmax)
545
- return Region.Region3;
546
- }
547
- // Zone 5: B23_S_CURVE_MIN <= s < B23_S_CURVE_MAX (Region 2 / Region 3 / Region 4)
548
- else if (s >= C.B23_S_CURVE_MIN && s < C.B23_S_CURVE_MAX) {
549
- const hmin = hSatLow(s);
550
- const hs = h2c3bSat(s);
551
- const h23max = region2(C.P_MAX, C.B23_T_MAX).enthalpy;
552
- const h23min = region2(C.B23_P_MIN, C.R2_T_MIN).enthalpy;
553
- const hmax = region2(C.P_MAX, C.R2_T_MAX).enthalpy;
554
- if (hmin <= h && h < hs)
555
- return Region.Region4;
556
- if (hs <= h && h < h23min)
557
- return Region.Region3;
558
- if (h23min <= h && h < h23max) {
559
- // Discriminate R2 vs R3 using B23 boundary
560
- const Tb23 = tB23hs(h, s);
561
- const Pb23 = boundary23_T_to_P(Tb23);
562
- const Phs = backward2cPhs(h, s);
563
- if (Phs <= Pb23)
564
- return Region.Region2;
565
- return Region.Region3;
566
- }
567
- if (h23max <= h && h <= hmax)
568
- return Region.Region2;
569
- }
570
- // Zone 6: B23_S_CURVE_MAX <= s < R2_S_CRT (Region 2 / Region 4)
571
- else if (s >= C.B23_S_CURVE_MAX && s < C.R2_S_CRT) {
572
- const hmin = hSatLow(s);
573
- const hs = h2c3bSat(s);
574
- const hmax = region2(C.P_MAX, C.R2_T_MAX).enthalpy;
575
- if (hmin <= h && h < hs)
576
- return Region.Region4;
577
- if (hs <= h && h <= hmax)
578
- return Region.Region2;
579
- }
580
- // Zone 7: R2_S_CRT <= s < sTPmax (Region 2 / Region 4, use h2abSat)
581
- else if (s >= C.R2_S_CRT && s < sTPmax) {
582
- const hmin = hSatLow(s);
583
- const hs = h2abSat(s);
584
- const hmax = region2(C.P_MAX, C.R2_T_MAX).enthalpy;
585
- if (hmin <= h && h < hs)
586
- return Region.Region4;
587
- if (hs <= h && h <= hmax)
588
- return Region.Region2;
589
- }
590
- // Zone 8: sTPmax <= s < s2ab (Region 2 / Region 4)
591
- else if (s >= sTPmax && s < s2ab) {
592
- const hmin = hSatLow(s);
593
- const hs = h2abSat(s);
594
- const hmax = region2(C.P_MIN, C.R2_T_MAX).enthalpy;
595
- if (hmin <= h && h < hs)
596
- return Region.Region4;
597
- if (hs <= h && h <= hmax)
598
- return Region.Region2;
599
- }
600
- // Zone 9: s2ab <= s < s4v (Region 2 / Region 4)
601
- else if (s >= s2ab && s < s4v) {
602
- const hmin = hSatLow(s);
603
- const hs = h2abSat(s);
604
- const hmax = region2(C.P_MIN, C.R2_T_MAX).enthalpy;
605
- if (hmin <= h && h < hs)
606
- return Region.Region4;
607
- if (hs <= h && h <= hmax)
608
- return Region.Region2;
609
- }
610
- // Zone 10: s4v <= s <= smax (Region 2 only, superheated low-P)
611
- else if (s >= s4v && s <= smax) {
612
- const hmin = h4v;
613
- const hmax = region2(C.P_MIN, C.R2_T_MAX).enthalpy;
614
- if (hmin <= h && h <= hmax)
615
- return Region.Region2;
616
- }
617
- return -1;
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;
42
+ else
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;
57
+ else
58
+ upper = middle;
59
+ }
60
+ return [lower, upper];
618
61
  }
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, pressureLow?: 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