iapws-if97 2.1.1 → 2.1.5

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 (113) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/backward/hs.js +43 -9
  3. package/dist/backward/objective-normalization.d.ts +7 -0
  4. package/dist/backward/objective-normalization.js +8 -0
  5. package/dist/backward/ph.js +21 -5
  6. package/dist/backward/ps.js +15 -3
  7. package/dist/backward/th.js +3 -0
  8. package/dist/backward/tolerances.js +1 -1
  9. package/dist/backward/ts.js +3 -0
  10. package/dist/constants.d.ts +6 -2
  11. package/dist/constants.js +6 -2
  12. package/dist/core/input-validation.d.ts +2 -0
  13. package/dist/core/input-validation.js +7 -0
  14. package/dist/core/public-normalization.d.ts +2 -3
  15. package/dist/core/public-normalization.js +32 -9
  16. package/dist/core/region-detector.js +73 -49
  17. package/dist/core/region3-pt.js +6 -1
  18. package/dist/core/solve-input-normalization.js +4 -1
  19. package/dist/core/solver.js +3 -0
  20. package/dist/index.d.ts +8 -8
  21. package/dist/index.js +7 -16
  22. package/dist/regions/boundaries.d.ts +0 -9
  23. package/dist/regions/boundaries.js +8 -0
  24. package/dist/regions/region3-subregions.js +9 -6
  25. package/dist/regions/region3.d.ts +5 -1
  26. package/dist/regions/region3.js +9 -2
  27. package/dist/saturation/common.d.ts +1 -0
  28. package/dist/saturation/common.js +30 -3
  29. package/dist/saturation/region4-hs.js +89 -6
  30. package/dist/saturation/two-phase.js +5 -0
  31. package/dist/solvers/nelder-mead.d.ts +0 -4
  32. package/dist/solvers/nelder-mead.js +29 -12
  33. package/dist/solvers/newton-raphson.d.ts +1 -4
  34. package/dist/solvers/newton-raphson.js +15 -4
  35. package/dist/transport/properties.d.ts +1 -0
  36. package/dist/transport/properties.js +44 -4
  37. package/package.json +3 -2
  38. package/dist/backward/fixed-temperature-solver.d.ts.map +0 -1
  39. package/dist/backward/fixed-temperature-solver.js.map +0 -1
  40. package/dist/backward/hs.d.ts.map +0 -1
  41. package/dist/backward/hs.js.map +0 -1
  42. package/dist/backward/ph.d.ts.map +0 -1
  43. package/dist/backward/ph.js.map +0 -1
  44. package/dist/backward/ps.d.ts.map +0 -1
  45. package/dist/backward/ps.js.map +0 -1
  46. package/dist/backward/solution-validation.d.ts.map +0 -1
  47. package/dist/backward/solution-validation.js.map +0 -1
  48. package/dist/backward/th.d.ts.map +0 -1
  49. package/dist/backward/th.js.map +0 -1
  50. package/dist/backward/tolerances.d.ts.map +0 -1
  51. package/dist/backward/tolerances.js.map +0 -1
  52. package/dist/backward/ts.d.ts.map +0 -1
  53. package/dist/backward/ts.js.map +0 -1
  54. package/dist/boundaries.d.ts.map +0 -1
  55. package/dist/boundaries.js.map +0 -1
  56. package/dist/constants.d.ts.map +0 -1
  57. package/dist/constants.js.map +0 -1
  58. package/dist/core/public-normalization.d.ts.map +0 -1
  59. package/dist/core/public-normalization.js.map +0 -1
  60. package/dist/core/region-detector.d.ts.map +0 -1
  61. package/dist/core/region-detector.js.map +0 -1
  62. package/dist/core/region3-pt.d.ts.map +0 -1
  63. package/dist/core/region3-pt.js.map +0 -1
  64. package/dist/core/solve-input-normalization.d.ts.map +0 -1
  65. package/dist/core/solve-input-normalization.js.map +0 -1
  66. package/dist/core/solver.d.ts.map +0 -1
  67. package/dist/core/solver.js.map +0 -1
  68. package/dist/detect.d.ts.map +0 -1
  69. package/dist/detect.js.map +0 -1
  70. package/dist/index.d.ts.map +0 -1
  71. package/dist/index.js.map +0 -1
  72. package/dist/regions/boundaries.d.ts.map +0 -1
  73. package/dist/regions/boundaries.js.map +0 -1
  74. package/dist/regions/region1.d.ts.map +0 -1
  75. package/dist/regions/region1.js.map +0 -1
  76. package/dist/regions/region2.d.ts.map +0 -1
  77. package/dist/regions/region2.js.map +0 -1
  78. package/dist/regions/region3-data.d.ts.map +0 -1
  79. package/dist/regions/region3-data.js.map +0 -1
  80. package/dist/regions/region3-eval.d.ts.map +0 -1
  81. package/dist/regions/region3-eval.js.map +0 -1
  82. package/dist/regions/region3-subregions.d.ts.map +0 -1
  83. package/dist/regions/region3-subregions.js.map +0 -1
  84. package/dist/regions/region3.d.ts.map +0 -1
  85. package/dist/regions/region3.js.map +0 -1
  86. package/dist/regions/region4.d.ts.map +0 -1
  87. package/dist/regions/region4.js.map +0 -1
  88. package/dist/regions/region5.d.ts.map +0 -1
  89. package/dist/regions/region5.js.map +0 -1
  90. package/dist/regions.d.ts.map +0 -1
  91. package/dist/regions.js.map +0 -1
  92. package/dist/saturation/common.d.ts.map +0 -1
  93. package/dist/saturation/common.js.map +0 -1
  94. package/dist/saturation/region4-boundaries.d.ts.map +0 -1
  95. package/dist/saturation/region4-boundaries.js.map +0 -1
  96. package/dist/saturation/region4-hs.d.ts.map +0 -1
  97. package/dist/saturation/region4-hs.js.map +0 -1
  98. package/dist/saturation/two-phase.d.ts.map +0 -1
  99. package/dist/saturation/two-phase.js.map +0 -1
  100. package/dist/saturation.d.ts.map +0 -1
  101. package/dist/saturation.js.map +0 -1
  102. package/dist/solvers/bracketed-newton.d.ts.map +0 -1
  103. package/dist/solvers/bracketed-newton.js.map +0 -1
  104. package/dist/solvers/nelder-mead.d.ts.map +0 -1
  105. package/dist/solvers/nelder-mead.js.map +0 -1
  106. package/dist/solvers/newton-raphson.d.ts.map +0 -1
  107. package/dist/solvers/newton-raphson.js.map +0 -1
  108. package/dist/transport/properties.d.ts.map +0 -1
  109. package/dist/transport/properties.js.map +0 -1
  110. package/dist/transport.d.ts.map +0 -1
  111. package/dist/transport.js.map +0 -1
  112. package/dist/types.d.ts.map +0 -1
  113. package/dist/types.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.1.5
6
+
7
+ - fix some issues
8
+
9
+ ## 2.1.2
10
+
11
+ - fix some issues
12
+
5
13
  ## 2.1.1
6
14
 
7
15
  - fix some issues: enhance end-points process
@@ -12,11 +12,14 @@ import { region1 } from '../regions/region1.js';
12
12
  import { region2 } from '../regions/region2.js';
13
13
  import { region3ByRhoT } from '../regions/region3.js';
14
14
  import { region5 } from '../regions/region5.js';
15
+ import { assertFiniteNumber } from '../core/input-validation.js';
15
16
  import { detectRegionHS } from '../core/region-detector.js';
16
17
  import { newtonRaphson } from '../solvers/newton-raphson.js';
17
18
  import { nelderMead } from '../solvers/nelder-mead.js';
18
19
  import { solvePH } from './ph.js';
19
20
  import { validateBackwardState } from './solution-validation.js';
21
+ import { sumNormalizedResiduals } from './objective-normalization.js';
22
+ import { backwardConstraintTolerance } from './tolerances.js';
20
23
  import { tryRegion4HSState, } from '../saturation/region4-hs.js';
21
24
  import { assertCriticalRegion4HSInput, } from '../saturation/region4-boundaries.js';
22
25
  // ─── Backward Polynomial Evaluator ─────────────────────────────────────────
@@ -105,9 +108,11 @@ function r2cPhs(h, s) {
105
108
  }
106
109
  function r2Phs(h, s) {
107
110
  const hBound = b2abBoundary(s);
111
+ // Region 2 h-s subregion selection: 2a below the h(s) boundary,
112
+ // otherwise split 2b/2c at the published entropy crossover.
108
113
  if (h <= hBound)
109
114
  return r2aPhs(h, s);
110
- if (s >= 5.85)
115
+ if (s >= C.R2_S_CRT)
111
116
  return r2bPhs(h, s);
112
117
  return r2cPhs(h, s);
113
118
  }
@@ -154,6 +159,33 @@ function r3Phs(h, s) {
154
159
  return r3aPhs(h, s);
155
160
  return r3bPhs(h, s);
156
161
  }
162
+ const REGION5_HS_OUT_OF_DOMAIN_PENALTY = 1e18;
163
+ function normalizedHsObjective(h, s, stateAt) {
164
+ const enthalpyTolerance = backwardConstraintTolerance('enthalpy', h);
165
+ const entropyTolerance = backwardConstraintTolerance('entropy', s);
166
+ return (pair) => {
167
+ const state = stateAt(pair);
168
+ return sumNormalizedResiduals([
169
+ { actual: state.enthalpy, expected: h, tolerance: enthalpyTolerance },
170
+ { actual: state.entropy, expected: s, tolerance: entropyTolerance },
171
+ ]);
172
+ };
173
+ }
174
+ function normalizedRegion5HsObjective(h, s) {
175
+ const enthalpyTolerance = backwardConstraintTolerance('enthalpy', h);
176
+ const entropyTolerance = backwardConstraintTolerance('entropy', s);
177
+ return (pair) => {
178
+ const [p, T] = pair;
179
+ if (p <= 0 || p > C.R5_P_MAX || T <= C.R5_T_MIN || T > C.R5_T_MAX) {
180
+ return REGION5_HS_OUT_OF_DOMAIN_PENALTY;
181
+ }
182
+ const state = region5(p, T);
183
+ return sumNormalizedResiduals([
184
+ { actual: state.enthalpy, expected: h, tolerance: enthalpyTolerance },
185
+ { actual: state.entropy, expected: s, tolerance: entropyTolerance },
186
+ ]);
187
+ };
188
+ }
157
189
  // ─── Main HS Solver ─────────────────────────────────────────────────────────
158
190
  /**
159
191
  * Solve for thermodynamic state given H and S.
@@ -161,6 +193,8 @@ function r3Phs(h, s) {
161
193
  * @param s - Specific entropy [kJ/(kg·K)]
162
194
  */
163
195
  export function solveHS(h, s) {
196
+ assertFiniteNumber('Enthalpy', h);
197
+ assertFiniteNumber('Entropy', s);
164
198
  if (h < C.H_MIN || h > C.H_MAX) {
165
199
  throw new OutOfRangeError('Enthalpy', h, C.H_MIN, C.H_MAX);
166
200
  }
@@ -179,8 +213,8 @@ export function solveHS(h, s) {
179
213
  switch (region) {
180
214
  case Region.Region1: {
181
215
  const P0 = r1Phs(h, s);
182
- const sol = nelderMead((pair) => Math.abs(region1(pair[0], pair[1]).enthalpy - h) +
183
- Math.abs(region1(pair[0], pair[1]).entropy - s), [P0, 400]);
216
+ const objective = normalizedHsObjective(h, s, (pair) => region1(pair[0], pair[1]));
217
+ const sol = nelderMead(objective, [P0, 400], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
184
218
  return validateBackwardState(region1(sol.x[0], sol.x[1]), [
185
219
  { label: 'enthalpy', expected: h },
186
220
  { label: 'entropy', expected: s },
@@ -189,8 +223,8 @@ export function solveHS(h, s) {
189
223
  case Region.Region2: {
190
224
  const P0 = r2Phs(h, s);
191
225
  const T0 = newtonRaphson((T) => region2(P0, T).enthalpy - h, 500);
192
- const sol = nelderMead((pair) => Math.abs(region2(pair[0], pair[1]).enthalpy - h) +
193
- Math.abs(region2(pair[0], pair[1]).entropy - s), [P0, T0]);
226
+ const objective = normalizedHsObjective(h, s, (pair) => region2(pair[0], pair[1]));
227
+ const sol = nelderMead(objective, [P0, T0], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
194
228
  return validateBackwardState(region2(sol.x[0], sol.x[1]), [
195
229
  { label: 'enthalpy', expected: h },
196
230
  { label: 'entropy', expected: s },
@@ -201,8 +235,8 @@ export function solveHS(h, s) {
201
235
  const init = solvePH(P0, h);
202
236
  const rho0 = 1 / init.specificVolume;
203
237
  const T0 = init.temperature;
204
- const sol = nelderMead((pair) => Math.abs(region3ByRhoT(pair[0], pair[1]).enthalpy - h) +
205
- Math.abs(region3ByRhoT(pair[0], pair[1]).entropy - s), [rho0, T0]);
238
+ const objective = normalizedHsObjective(h, s, (pair) => region3ByRhoT(pair[0], pair[1]));
239
+ const sol = nelderMead(objective, [rho0, T0], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
206
240
  return validateBackwardState(region3ByRhoT(sol.x[0], sol.x[1]), [
207
241
  { label: 'enthalpy', expected: h },
208
242
  { label: 'entropy', expected: s },
@@ -212,8 +246,8 @@ export function solveHS(h, s) {
212
246
  throw new IF97Error('solveHS identified Region 4 but failed to construct a valid saturation state');
213
247
  }
214
248
  case Region.Region5: {
215
- const sol = nelderMead((pair) => Math.abs(region5(pair[0], pair[1]).enthalpy - h) +
216
- Math.abs(region5(pair[0], pair[1]).entropy - s), [1, 1400]);
249
+ const objective = normalizedRegion5HsObjective(h, s);
250
+ const sol = nelderMead(objective, [1, 1400], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
217
251
  return validateBackwardState(region5(sol.x[0], sol.x[1]), [
218
252
  { label: 'enthalpy', expected: h },
219
253
  { label: 'entropy', expected: s },
@@ -0,0 +1,7 @@
1
+ export interface NormalizedResidual {
2
+ actual: number;
3
+ expected: number;
4
+ tolerance: number;
5
+ }
6
+ export declare function sumNormalizedResiduals(terms: readonly NormalizedResidual[]): number;
7
+ //# sourceMappingURL=objective-normalization.d.ts.map
@@ -0,0 +1,8 @@
1
+ export function sumNormalizedResiduals(terms) {
2
+ let sum = 0;
3
+ for (const term of terms) {
4
+ sum += Math.abs(term.actual - term.expected) / term.tolerance;
5
+ }
6
+ return sum;
7
+ }
8
+ //# sourceMappingURL=objective-normalization.js.map
@@ -13,10 +13,13 @@ import { region2 } from '../regions/region2.js';
13
13
  import { region3ByRhoT } from '../regions/region3.js';
14
14
  import { region5 } from '../regions/region5.js';
15
15
  import { saturationTemperature } from '../regions/region4.js';
16
+ import { assertFiniteNumber } from '../core/input-validation.js';
16
17
  import { detectRegionPH } from '../core/region-detector.js';
17
18
  import { newtonRaphson } from '../solvers/newton-raphson.js';
18
19
  import { nelderMead } from '../solvers/nelder-mead.js';
19
20
  import { validateBackwardState } from './solution-validation.js';
21
+ import { backwardConstraintTolerance } from './tolerances.js';
22
+ import { sumNormalizedResiduals } from './objective-normalization.js';
20
23
  import { mixSaturationState, qualityFromSaturationProperty, saturationEndpointsAtPressure, } from '../saturation/common.js';
21
24
  import { assertCriticalRegion4PHInput, assertRegion4StateAllowed, } from '../saturation/region4-boundaries.js';
22
25
  // ─── Backward Polynomial Evaluator ─────────────────────────────────────────
@@ -40,6 +43,10 @@ function r1BackwardT(p, h) {
40
43
  return evalPoly(R1_PH, 0, -1, p, h / 2500);
41
44
  }
42
45
  // ─── Region 2 Backward T(P,H) ─────────────────────────────────────────────
46
+ // Lowest pressure where Region 2c can appear in the P-H backward formulation.
47
+ // For 4 < p <= 6.546699678 MPa the supplementary release selects subregion 2b
48
+ // directly; above this threshold use the B2bc(h) boundary to discriminate 2b/2c.
49
+ const R2_PH_B2BC_P_MIN = 6.546699678;
43
50
  // B2bc boundary (exported for future use)
44
51
  export function b2bc_H_P(h) {
45
52
  return 0.90584278514723e-3 - 0.67955786399241 * h + 0.12809002730136e-3 * h * h;
@@ -94,11 +101,11 @@ function r2BackwardT(p, h) {
94
101
  if (p <= C.R2_P_CRT) {
95
102
  T = r2aBackwardT(p, h);
96
103
  }
97
- else if (p <= 6.546699678) {
104
+ else if (p <= R2_PH_B2BC_P_MIN) {
98
105
  T = r2bBackwardT(p, h);
99
106
  }
100
107
  else {
101
- if (p < 905.84278514723 - 0.67955786399241 * h + 1.2809002730136e-4 * h * h) {
108
+ if (p < b2bc_H_P(h)) {
102
109
  T = r2bBackwardT(p, h);
103
110
  }
104
111
  else {
@@ -190,6 +197,8 @@ export function r4EnthalpyToPsat(h) {
190
197
  * @param h - Specific enthalpy [kJ/kg]
191
198
  */
192
199
  export function solvePH(p, h) {
200
+ assertFiniteNumber('Pressure', p);
201
+ assertFiniteNumber('Enthalpy', h);
193
202
  if (p < C.P_MIN || p > C.P_MAX) {
194
203
  throw new OutOfRangeError('Pressure', p, C.P_MIN, C.P_MAX);
195
204
  }
@@ -217,6 +226,8 @@ export function solvePH(p, h) {
217
226
  }
218
227
  case Region.Region3: {
219
228
  const hBound = b3ab_P_to_H(p);
229
+ const pressureTolerance = 1e-5 * Math.max(1, Math.abs(p));
230
+ const enthalpyTolerance = backwardConstraintTolerance('enthalpy', h);
220
231
  let T0, v0;
221
232
  if (h < hBound) {
222
233
  T0 = 760 * evalPoly(R3A_PH_T, -0.240, 0.615, p / 100, h / 2300);
@@ -226,10 +237,15 @@ export function solvePH(p, h) {
226
237
  T0 = 860 * evalPoly(R3B_PH_T, -0.298, 0.720, p / 100, h / 2800);
227
238
  v0 = 0.0088 * evalPoly(R3B_PH_V, -0.0661, 0.720, p / 100, h / 2800);
228
239
  }
229
- const sol = nelderMead((pair) => Math.abs(region3ByRhoT(1 / pair[0], pair[1]).enthalpy - h) +
230
- Math.abs(region3ByRhoT(1 / pair[0], pair[1]).pressure - p), [v0, T0]);
240
+ const sol = nelderMead((pair) => {
241
+ const state = region3ByRhoT(1 / pair[0], pair[1]);
242
+ return sumNormalizedResiduals([
243
+ { actual: state.enthalpy, expected: h, tolerance: enthalpyTolerance },
244
+ { actual: state.pressure, expected: p, tolerance: pressureTolerance },
245
+ ]);
246
+ }, [v0, T0], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
231
247
  return validateBackwardState(region3ByRhoT(1 / sol.x[0], sol.x[1]), [
232
- { label: 'pressure', expected: p, tolerance: 1e-5 * Math.max(1, Math.abs(p)) },
248
+ { label: 'pressure', expected: p, tolerance: pressureTolerance },
233
249
  { label: 'enthalpy', expected: h },
234
250
  ], { solverName: 'solvePH', expectedRegion: Region.Region3 });
235
251
  }
@@ -9,10 +9,13 @@ import { region1 } from '../regions/region1.js';
9
9
  import { region2 } from '../regions/region2.js';
10
10
  import { region3ByRhoT } from '../regions/region3.js';
11
11
  import { region5 } from '../regions/region5.js';
12
+ import { assertFiniteNumber } from '../core/input-validation.js';
12
13
  import { detectRegionPS } from '../core/region-detector.js';
13
14
  import { newtonRaphson } from '../solvers/newton-raphson.js';
14
15
  import { nelderMead } from '../solvers/nelder-mead.js';
15
16
  import { validateBackwardState } from './solution-validation.js';
17
+ import { backwardConstraintTolerance } from './tolerances.js';
18
+ import { sumNormalizedResiduals } from './objective-normalization.js';
16
19
  import { mixSaturationState, qualityFromSaturationProperty, saturationEndpointsAtPressure, } from '../saturation/common.js';
17
20
  import { assertCriticalRegion4PSInput, assertRegion4StateAllowed, } from '../saturation/region4-boundaries.js';
18
21
  function evalPoly(table, piShift, sigShift, pi, sig) {
@@ -149,6 +152,8 @@ const R3B_PS_V_TABLE = [
149
152
  * @param s - Specific entropy [kJ/(kg·K)]
150
153
  */
151
154
  export function solvePS(p, s) {
155
+ assertFiniteNumber('Pressure', p);
156
+ assertFiniteNumber('Entropy', s);
152
157
  if (p < C.P_MIN || p > C.P_MAX) {
153
158
  throw new OutOfRangeError('Pressure', p, C.P_MIN, C.P_MAX);
154
159
  }
@@ -175,6 +180,8 @@ export function solvePS(p, s) {
175
180
  ], { solverName: 'solvePS', expectedRegion: Region.Region2 });
176
181
  }
177
182
  case Region.Region3: {
183
+ const pressureTolerance = 1e-5 * Math.max(1, Math.abs(p));
184
+ const entropyTolerance = backwardConstraintTolerance('entropy', s);
178
185
  let T0, v0;
179
186
  if (s <= C.R3_S_CRT) {
180
187
  T0 = 760 * evalPoly(R3A_PS_T_TABLE, -0.240, 0.703, p / 100, s / 4.4);
@@ -184,10 +191,15 @@ export function solvePS(p, s) {
184
191
  T0 = 860 * evalPoly(R3B_PS_T_TABLE, -0.760, 0.818, p / 100, s / 5.3);
185
192
  v0 = 0.0088 * evalPoly(R3B_PS_V_TABLE, -0.298, 0.816, p / 100, s / 5.3);
186
193
  }
187
- const sol = nelderMead((pair) => Math.abs(region3ByRhoT(1 / pair[0], pair[1]).entropy - s) +
188
- Math.abs(region3ByRhoT(1 / pair[0], pair[1]).pressure - p), [v0, T0]);
194
+ const sol = nelderMead((pair) => {
195
+ const state = region3ByRhoT(1 / pair[0], pair[1]);
196
+ return sumNormalizedResiduals([
197
+ { actual: state.entropy, expected: s, tolerance: entropyTolerance },
198
+ { actual: state.pressure, expected: p, tolerance: pressureTolerance },
199
+ ]);
200
+ }, [v0, T0], { maxIterations: 1000, minErrorDelta: 1e-8, minTolerance: 1e-9 });
189
201
  return validateBackwardState(region3ByRhoT(1 / sol.x[0], sol.x[1]), [
190
- { label: 'pressure', expected: p, tolerance: 1e-5 * Math.max(1, Math.abs(p)) },
202
+ { label: 'pressure', expected: p, tolerance: pressureTolerance },
191
203
  { label: 'entropy', expected: s },
192
204
  ], { solverName: 'solvePS', expectedRegion: Region.Region3 });
193
205
  }
@@ -1,5 +1,6 @@
1
1
  import * as C from '../constants.js';
2
2
  import { IF97Error, OutOfRangeError, Region } from '../types.js';
3
+ import { assertFiniteNumber } from '../core/input-validation.js';
3
4
  import { detectRegionTH } from '../core/region-detector.js';
4
5
  import { solveRegion3PTBasic } from '../core/region3-pt.js';
5
6
  import { region1 } from '../regions/region1.js';
@@ -28,6 +29,8 @@ const extractH = (s) => s.enthalpy;
28
29
  * @throws {IF97Error} if T is within the critical exclusion band
29
30
  */
30
31
  export function solveTH(T, h) {
32
+ assertFiniteNumber('Temperature', T);
33
+ assertFiniteNumber('Enthalpy', h);
31
34
  if (T < C.T_MIN || T > C.T_MAX) {
32
35
  throw new OutOfRangeError('Temperature', T, C.T_MIN, C.T_MAX);
33
36
  }
@@ -5,7 +5,7 @@ export function backwardConstraintTolerance(label, expected) {
5
5
  case 'temperature':
6
6
  return 1e-9 * Math.max(1, Math.abs(expected));
7
7
  case 'enthalpy':
8
- return 1e-4 * Math.max(1, Math.abs(expected));
8
+ return 1e-6 * Math.max(1, Math.abs(expected));
9
9
  case 'entropy':
10
10
  return 1e-6 * Math.max(1, Math.abs(expected));
11
11
  default:
@@ -1,5 +1,6 @@
1
1
  import * as C from '../constants.js';
2
2
  import { IF97Error, OutOfRangeError, Region } from '../types.js';
3
+ import { assertFiniteNumber } from '../core/input-validation.js';
3
4
  import { detectRegionTS } from '../core/region-detector.js';
4
5
  import { solveRegion3PTBasic } from '../core/region3-pt.js';
5
6
  import { region1 } from '../regions/region1.js';
@@ -28,6 +29,8 @@ const extractS = (s) => s.entropy;
28
29
  * @throws {IF97Error} if T is within the critical exclusion band
29
30
  */
30
31
  export function solveTS(T, s) {
32
+ assertFiniteNumber('Temperature', T);
33
+ assertFiniteNumber('Entropy', s);
31
34
  if (T < C.T_MIN || T > C.T_MAX) {
32
35
  throw new OutOfRangeError('Temperature', T, C.T_MIN, C.T_MAX);
33
36
  }
@@ -5,9 +5,13 @@
5
5
  * for the IAPWS Industrial Formulation 1997.
6
6
  */
7
7
  /** Specific gas constant for water [kJ/(kg·K)].
8
- * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency;
9
- * differs from the IAPWS-95 / CODATA value. */
8
+ * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency.
9
+ * Do not replace transport correlations with this value; thermal conductivity
10
+ * critical enhancement intentionally uses `R_IAPWS_2011_THERMAL`. */
10
11
  export declare const R = 0.461526;
12
+ /** Specific gas constant used by the IAPWS 2011 thermal conductivity λ₂ correlation [kJ/(kg·K)].
13
+ * Intentionally differs from IF97's `R`; keep both constants distinct and named. */
14
+ export declare const R_IAPWS_2011_THERMAL = 0.46151805;
11
15
  /** Critical temperature [K] */
12
16
  export declare const Tc = 647.096;
13
17
  /** Temperature exclusion half-band around the critical point [K] */
package/dist/constants.js CHANGED
@@ -6,9 +6,13 @@
6
6
  */
7
7
  // ─── Fundamental Constants ──────────────────────────────────────────────────
8
8
  /** Specific gas constant for water [kJ/(kg·K)].
9
- * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency;
10
- * differs from the IAPWS-95 / CODATA value. */
9
+ * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency.
10
+ * Do not replace transport correlations with this value; thermal conductivity
11
+ * critical enhancement intentionally uses `R_IAPWS_2011_THERMAL`. */
11
12
  export const R = 0.461526;
13
+ /** Specific gas constant used by the IAPWS 2011 thermal conductivity λ₂ correlation [kJ/(kg·K)].
14
+ * Intentionally differs from IF97's `R`; keep both constants distinct and named. */
15
+ export const R_IAPWS_2011_THERMAL = 0.46151805;
12
16
  // ─── Critical Point ─────────────────────────────────────────────────────────
13
17
  /** Critical temperature [K] */
14
18
  export const Tc = 647.096;
@@ -0,0 +1,2 @@
1
+ export declare function assertFiniteNumber(parameter: string, value: unknown): asserts value is number;
2
+ //# sourceMappingURL=input-validation.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { IF97Error } from '../types.js';
2
+ export function assertFiniteNumber(parameter, value) {
3
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
4
+ throw new IF97Error(`${parameter} must be a finite number`);
5
+ }
6
+ }
7
+ //# sourceMappingURL=input-validation.js.map
@@ -1,5 +1,4 @@
1
1
  import type { BasicProperties, SteamState } from '../types.js';
2
- type PublicState = BasicProperties | SteamState;
3
- export declare function normalizePublicState<T extends PublicState>(state: T): T;
4
- export {};
2
+ export declare function normalizePublicState(state: SteamState): SteamState;
3
+ export declare function normalizePublicState(state: BasicProperties): BasicProperties;
5
4
  //# sourceMappingURL=public-normalization.d.ts.map
@@ -17,15 +17,38 @@ function snapSimpleDecimal(value) {
17
17
  }
18
18
  return Object.is(value, -0) ? 0 : value;
19
19
  }
20
+ function normalizeNullableNumber(value) {
21
+ return value === null ? null : snapSimpleDecimal(value);
22
+ }
23
+ function normalizeBasicProperties(state) {
24
+ return {
25
+ region: state.region,
26
+ pressure: snapSimpleDecimal(state.pressure),
27
+ temperature: snapSimpleDecimal(state.temperature),
28
+ specificVolume: snapSimpleDecimal(state.specificVolume),
29
+ internalEnergy: snapSimpleDecimal(state.internalEnergy),
30
+ entropy: snapSimpleDecimal(state.entropy),
31
+ enthalpy: snapSimpleDecimal(state.enthalpy),
32
+ cp: normalizeNullableNumber(state.cp),
33
+ cv: normalizeNullableNumber(state.cv),
34
+ speedOfSound: normalizeNullableNumber(state.speedOfSound),
35
+ quality: normalizeNullableNumber(state.quality),
36
+ isobaricExpansion: normalizeNullableNumber(state.isobaricExpansion),
37
+ isothermalCompressibility: normalizeNullableNumber(state.isothermalCompressibility),
38
+ };
39
+ }
40
+ function normalizeSteamState(state) {
41
+ return {
42
+ ...normalizeBasicProperties(state),
43
+ density: snapSimpleDecimal(state.density),
44
+ viscosity: normalizeNullableNumber(state.viscosity),
45
+ thermalConductivity: normalizeNullableNumber(state.thermalConductivity),
46
+ surfaceTension: normalizeNullableNumber(state.surfaceTension),
47
+ dielectricConstant: normalizeNullableNumber(state.dielectricConstant),
48
+ ionizationConstant: normalizeNullableNumber(state.ionizationConstant),
49
+ };
50
+ }
20
51
  export function normalizePublicState(state) {
21
- const normalized = { ...state };
22
- for (const [key, value] of Object.entries(state)) {
23
- // Preserve enum/null fields exactly; only normalize exposed numeric outputs.
24
- if (key === 'region' || value === null || typeof value !== 'number') {
25
- continue;
26
- }
27
- normalized[key] = snapSimpleDecimal(value);
28
- }
29
- return normalized;
52
+ return 'density' in state ? normalizeSteamState(state) : normalizeBasicProperties(state);
30
53
  }
31
54
  //# sourceMappingURL=public-normalization.js.map