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.
Files changed (83) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/README.md +69 -246
  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 +3 -3
  8. package/dist/backward/fixed-temperature-solver.js +38 -17
  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.js +28 -242
  16. package/dist/backward/ph.d.ts +2 -0
  17. package/dist/backward/ph.js +72 -21
  18. package/dist/backward/pressure-property-region.d.ts +13 -0
  19. package/dist/backward/pressure-property-region.js +262 -0
  20. package/dist/backward/ps.d.ts +6 -0
  21. package/dist/backward/ps.js +81 -28
  22. package/dist/backward/solution-validation.d.ts +7 -1
  23. package/dist/backward/solution-validation.js +138 -41
  24. package/dist/backward/th.d.ts +3 -5
  25. package/dist/backward/th.js +10 -71
  26. package/dist/backward/tolerances.d.ts +3 -0
  27. package/dist/backward/tolerances.js +9 -3
  28. package/dist/backward/ts.d.ts +3 -5
  29. package/dist/backward/ts.js +10 -71
  30. package/dist/constants.d.ts +11 -15
  31. package/dist/constants.js +14 -15
  32. package/dist/core/floating-point.d.ts +2 -0
  33. package/dist/core/floating-point.js +7 -0
  34. package/dist/core/formulation-seams.d.ts +27 -0
  35. package/dist/core/formulation-seams.js +289 -0
  36. package/dist/core/input-validation.d.ts +1 -0
  37. package/dist/core/input-validation.js +5 -0
  38. package/dist/core/region-detector.d.ts +3 -40
  39. package/dist/core/region-detector.js +37 -762
  40. package/dist/core/region3-pt.d.ts +1 -1
  41. package/dist/core/region3-pt.js +42 -16
  42. package/dist/core/solve-input-normalization.js +4 -1
  43. package/dist/core/solver.d.ts +7 -10
  44. package/dist/core/solver.js +26 -17
  45. package/dist/detect.d.ts +16 -2
  46. package/dist/detect.js +36 -2
  47. package/dist/errors.d.ts +11 -0
  48. package/dist/errors.js +16 -0
  49. package/dist/gas-constant.d.ts +5 -0
  50. package/dist/gas-constant.js +5 -0
  51. package/dist/index.d.ts +4 -5
  52. package/dist/index.js +10 -21
  53. package/dist/regions/region1.d.ts +2 -1
  54. package/dist/regions/region1.js +1 -1
  55. package/dist/regions/region2.d.ts +2 -1
  56. package/dist/regions/region2.js +1 -1
  57. package/dist/regions/region3-data.js +3 -3
  58. package/dist/regions/region3-subregions.js +15 -10
  59. package/dist/regions/region3.d.ts +4 -1
  60. package/dist/regions/region3.js +32 -2
  61. package/dist/regions/region4.d.ts +2 -0
  62. package/dist/regions/region4.js +39 -12
  63. package/dist/regions/region5.d.ts +2 -1
  64. package/dist/regions/region5.js +2 -2
  65. package/dist/saturation/common.d.ts +2 -8
  66. package/dist/saturation/common.js +64 -36
  67. package/dist/saturation/region4-boundaries.js +13 -13
  68. package/dist/saturation/region4-hs.js +282 -58
  69. package/dist/saturation/two-phase.d.ts +2 -2
  70. package/dist/saturation/two-phase.js +7 -7
  71. package/dist/solvers/bracketed-newton.d.ts +6 -3
  72. package/dist/solvers/bracketed-newton.js +15 -8
  73. package/dist/solvers/extended-arithmetic.d.ts +6 -0
  74. package/dist/solvers/extended-arithmetic.js +38 -0
  75. package/dist/transport/properties.d.ts +4 -2
  76. package/dist/transport/properties.js +14 -4
  77. package/dist/types.d.ts +18 -0
  78. package/dist/types.js +14 -0
  79. package/package.json +4 -4
  80. package/dist/core/public-normalization.d.ts +0 -3
  81. package/dist/core/public-normalization.js +0 -53
  82. /package/dist/{backward → core}/thermodynamic-derivatives.d.ts +0 -0
  83. /package/dist/{backward → core}/thermodynamic-derivatives.js +0 -0
@@ -16,7 +16,7 @@ import type { BasicProperties } from '../types.js';
16
16
  * @param rho0 - Initial density guess [kg/m³]
17
17
  * @returns Basic thermodynamic properties at the converged density
18
18
  */
19
- export declare function solveRegion3StateAtPressure(p: number, T: number, rho0: number): BasicProperties;
19
+ export declare function solveRegion3StateAtPressure(p: number, T: number, rho0: number, pressureLow?: number): BasicProperties;
20
20
  /**
21
21
  * Compute Region 3 basic properties from pressure and temperature.
22
22
  *
@@ -1,8 +1,10 @@
1
1
  import * as C from '../constants.js';
2
- import { IF97Error } from '../types.js';
3
- import { region3ByRhoT } from '../regions/region3.js';
2
+ import { ConvergenceError, IF97Error } from '../types.js';
3
+ import { region3ByRhoT, region3PressureResidual } from '../regions/region3.js';
4
4
  import { region3Volume } from '../regions/region3-subregions.js';
5
+ import { saturationPressure, saturationPressurePair } from '../regions/region4.js';
5
6
  import { newtonRaphson } from '../solvers/newton-raphson.js';
7
+ import { bracketedNewton } from '../solvers/bracketed-newton.js';
6
8
  /**
7
9
  * Solve for the Region 3 state at a target pressure and temperature.
8
10
  *
@@ -14,9 +16,8 @@ import { newtonRaphson } from '../solvers/newton-raphson.js';
14
16
  * @param rho0 - Initial density guess [kg/m³]
15
17
  * @returns Basic thermodynamic properties at the converged density
16
18
  */
17
- export function solveRegion3StateAtPressure(p, T, rho0) {
18
- // Keyed by ρ, so the residual and its derivative at the same point share one
19
- // equation evaluation regardless of the order the solver calls them in.
19
+ export function solveRegion3StateAtPressure(p, T, rho0, pressureLow = 0) {
20
+ // Cache full EOS properties for the density derivative and final state.
20
21
  let cachedRho = Number.NaN;
21
22
  let cachedState = null;
22
23
  const stateAt = (rho) => {
@@ -26,18 +27,33 @@ export function solveRegion3StateAtPressure(p, T, rho0) {
26
27
  }
27
28
  return cachedState;
28
29
  };
29
- const rho = newtonRaphson((rhoGuess) => {
30
- if (!Number.isFinite(rhoGuess) || rhoGuess <= 0) {
30
+ const residual = (rhoGuess) => {
31
+ if (!Number.isFinite(rhoGuess) || rhoGuess <= 0)
31
32
  return Number.NaN;
32
- }
33
- return stateAt(rhoGuess).pressure - p;
34
- }, rho0, (rhoGuess) => {
35
- if (!Number.isFinite(rhoGuess) || rhoGuess <= 0) {
33
+ return region3PressureResidual(rhoGuess, T, p, pressureLow);
34
+ };
35
+ const derivative = (rhoGuess) => {
36
+ if (!Number.isFinite(rhoGuess) || rhoGuess <= 0)
36
37
  return Number.NaN;
37
- }
38
38
  const kappa = stateAt(rhoGuess).isothermalCompressibility;
39
39
  return kappa === null ? Number.NaN : 1 / (rhoGuess * kappa);
40
- });
40
+ };
41
+ let rho;
42
+ try {
43
+ rho = newtonRaphson(residual, rho0, derivative, { tolerance: 1e-12 });
44
+ }
45
+ catch (error) {
46
+ // Subcritical brackets can cross a spinodal and change the requested phase.
47
+ if (!(error instanceof ConvergenceError) || T < C.Tc)
48
+ throw error;
49
+ const lower = rho0 / 2;
50
+ const upper = rho0 * 2;
51
+ if (!(derivative(lower) > 0 && derivative(upper) > 0 && residual(lower) * residual(upper) <= 0))
52
+ throw error;
53
+ rho = bracketedNewton(residual, lower, upper, rho0, {
54
+ derivative, tolerance: 1e-12, residualTolerance: 0,
55
+ });
56
+ }
41
57
  return stateAt(rho);
42
58
  }
43
59
  /**
@@ -55,10 +71,20 @@ export function solveRegion3PTBasic(p, T) {
55
71
  if (p === C.Pc && T === C.Tc) {
56
72
  return region3ByRhoT(C.RHOc, T);
57
73
  }
58
- const state = solveRegion3StateAtPressure(p, T, 1 / region3Volume(p, T));
74
+ const saturation = T < C.Tc ? saturationPressure(T) : null;
75
+ const pressurePair = p === saturation ? saturationPressurePair(T) : null;
76
+ const pressureLow = pressurePair?.[0] === p ? pressurePair[1] : 0;
77
+ const state = solveRegion3StateAtPressure(p, T, 1 / region3Volume(p, T), pressureLow);
59
78
  const relErr = Math.abs(state.pressure - p) / p;
60
- if (relErr > 1e-6) {
79
+ if (relErr > 1e-12) {
61
80
  throw new IF97Error(`solveRegion3PTBasic: density solve did not converge — target P=${p} MPa, got P=${state.pressure.toFixed(6)} MPa (relErr=${relErr.toExponential(2)})`);
62
81
  }
63
- return state;
82
+ const rho = 1 / state.specificVolume;
83
+ const liquid = saturation !== null && p >= saturation;
84
+ if ((T < C.Tc && (liquid ? rho <= C.RHOc : rho >= C.RHOc)) ||
85
+ ![state.cp, state.cv, state.isothermalCompressibility, state.speedOfSound]
86
+ .every((value) => value !== null && Number.isFinite(value) && value > 0)) {
87
+ throw new IF97Error(`Region 3 numerical inconsistency at p=${p} MPa, T=${T} K: cannot construct the requested stable ${T < C.Tc ? liquid ? 'liquid' : 'vapor' : 'single-phase'} EOS branch`);
88
+ }
89
+ return { ...state, pressure: p };
64
90
  }
@@ -8,7 +8,7 @@ function pickNumericAlias(input, shortKey, longKey, label) {
8
8
  }
9
9
  if (shortValue !== undefined &&
10
10
  longValue !== undefined &&
11
- !Object.is(shortValue, longValue)) {
11
+ shortValue !== longValue) {
12
12
  throw new IF97Error(`solve input received conflicting values for '${shortKey}' and '${longKey}'`);
13
13
  }
14
14
  const value = shortValue ?? longValue;
@@ -17,6 +17,9 @@ function pickNumericAlias(input, shortKey, longKey, label) {
17
17
  }
18
18
  // Normalize public solve() aliases to the canonical short-key form used internally.
19
19
  export function normalizeSolveInput(input) {
20
+ if (typeof input !== 'object' || input === null) {
21
+ throw new IF97Error('solve input must be a non-null object');
22
+ }
20
23
  const raw = input;
21
24
  switch (input.mode) {
22
25
  case 'PT':
@@ -1,19 +1,16 @@
1
1
  /**
2
2
  * Main IF97 solve dispatcher.
3
- * Computes full thermodynamic state including transport properties.
3
+ * Computes basic or full thermodynamic states.
4
4
  */
5
- import type { SteamState, SolveInput } from '../types.js';
5
+ import type { BasicProperties, SteamState, SolveInput } from '../types.js';
6
6
  /**
7
- * Solve for full thermodynamic state given pressure and temperature.
8
- *
9
- * @param p - Pressure [MPa]
10
- * @param T - Temperature [K]
11
- * @returns Complete steam state with transport properties
7
+ * Solve for full thermodynamic state given pressure [MPa] and temperature [K].
12
8
  */
13
9
  export declare function solvePT(p: number, T: number): SteamState;
14
10
  /**
15
- * Unified solver: compute full thermodynamic state from any supported input pair.
16
- * Routes to the correct solver based on input mode, enriches with transport properties,
17
- * and accepts either shorthand or long-form input field names.
11
+ * Compute basic thermodynamic properties from any supported input pair.
12
+ * Accepts either shorthand or long-form input field names, without transport enrichment.
18
13
  */
14
+ export declare function solveBasic(input: SolveInput): BasicProperties;
15
+ /** Compute the full thermodynamic state, including transport properties. */
19
16
  export declare function solve(input: SolveInput): SteamState;
@@ -44,13 +44,13 @@ function enrichState(basic) {
44
44
  };
45
45
  }
46
46
  /**
47
- * Solve for full thermodynamic state given pressure and temperature.
47
+ * Solve for basic thermodynamic state given pressure and temperature.
48
48
  *
49
49
  * @param p - Pressure [MPa]
50
50
  * @param T - Temperature [K]
51
- * @returns Complete steam state with transport properties
51
+ * @returns Basic thermodynamic properties without transport
52
52
  */
53
- export function solvePT(p, T) {
53
+ function solvePTBasic(p, T) {
54
54
  assertFiniteNumber('Pressure', p);
55
55
  assertFiniteNumber('Temperature', T);
56
56
  if (p < C.P_MIN || p > C.P_MAX) {
@@ -62,7 +62,7 @@ export function solvePT(p, T) {
62
62
  if (T > C.R5_T_MIN && p > C.R5_P_MAX) {
63
63
  throw new OutOfRangeError('Pressure', p, C.P_MIN, C.R5_P_MAX);
64
64
  }
65
- if (Math.abs(T - C.Tc) < C.CRITICAL_T_EXCLUSION_BAND && Math.abs(p - C.Pc) < 1e-9) {
65
+ if (T === C.Tc && p === C.Pc) {
66
66
  throw new IF97Error(`solvePT does not support the exact critical point P=${C.Pc} MPa, T=${C.Tc} K because derivative properties become singular`);
67
67
  }
68
68
  const region = detectRegionPT(p, T);
@@ -83,25 +83,34 @@ export function solvePT(p, T) {
83
83
  default:
84
84
  throw new IF97Error(`Cannot determine region for P=${p} MPa, T=${T} K`);
85
85
  }
86
- return enrichState(basic);
86
+ return basic;
87
87
  }
88
88
  /**
89
- * Unified solver: compute full thermodynamic state from any supported input pair.
90
- * Routes to the correct solver based on input mode, enriches with transport properties,
91
- * and accepts either shorthand or long-form input field names.
89
+ * Solve for full thermodynamic state given pressure [MPa] and temperature [K].
92
90
  */
93
- export function solve(input) {
91
+ export function solvePT(p, T) {
92
+ return enrichState(solvePTBasic(p, T));
93
+ }
94
+ /**
95
+ * Compute basic thermodynamic properties from any supported input pair.
96
+ * Accepts either shorthand or long-form input field names, without transport enrichment.
97
+ */
98
+ export function solveBasic(input) {
94
99
  const normalized = normalizeSolveInput(input);
95
100
  switch (normalized.mode) {
96
- case 'PT': return solvePT(normalized.p, normalized.T);
97
- case 'PH': return enrichState(solvePH(normalized.p, normalized.h));
98
- case 'PS': return enrichState(solvePS(normalized.p, normalized.s));
99
- case 'HS': return enrichState(solveHS(normalized.h, normalized.s));
100
- case 'Px': return enrichState(solvePx(normalized.p, normalized.x));
101
- case 'Tx': return enrichState(solveTx(normalized.T, normalized.x));
102
- case 'TH': return enrichState(solveTH(normalized.T, normalized.h));
103
- case 'TS': return enrichState(solveTS(normalized.T, normalized.s));
101
+ case 'PT': return solvePTBasic(normalized.p, normalized.T);
102
+ case 'PH': return solvePH(normalized.p, normalized.h);
103
+ case 'PS': return solvePS(normalized.p, normalized.s);
104
+ case 'HS': return solveHS(normalized.h, normalized.s);
105
+ case 'Px': return solvePx(normalized.p, normalized.x);
106
+ case 'Tx': return solveTx(normalized.T, normalized.x);
107
+ case 'TH': return solveTH(normalized.T, normalized.h);
108
+ case 'TS': return solveTS(normalized.T, normalized.s);
104
109
  default:
105
110
  throw new IF97Error(`Unsupported solve mode: ${input.mode ?? 'undefined'}`);
106
111
  }
107
112
  }
113
+ /** Compute the full thermodynamic state, including transport properties. */
114
+ export function solve(input) {
115
+ return enrichState(solveBasic(input));
116
+ }
package/dist/detect.d.ts CHANGED
@@ -1,4 +1,18 @@
1
+ import type { Region } from './types.js';
2
+ export { detectRegionPT } from './core/region-detector.js';
3
+ export { detectRegionPH, detectRegionPS } from './backward/pressure-property-region.js';
4
+ export { detectRegionHS } from './backward/hs-region.js';
1
5
  /**
2
- * Public region-detection helper exports.
6
+ * Detect the region only when T and h identify a unique physical state.
7
+ * @returns Region number (1–5) or -1 if no valid state exists
8
+ * @throws {AmbiguousStateError} if multiple physical states match, even in one region
9
+ * @throws {IF97Error} if numerical state construction fails, including near-critical saturation endpoints
3
10
  */
4
- export { detectRegionPT, detectRegionPH, detectRegionPS, detectRegionHS, detectRegionTH, detectRegionTS, } from './core/region-detector.js';
11
+ export declare function detectRegionTH(T: number, h: number): Region | -1;
12
+ /**
13
+ * Detect the region only when T and s identify a unique physical state.
14
+ * @returns Region number (1–5) or -1 if no valid state exists
15
+ * @throws {AmbiguousStateError} if multiple physical states match, even in one region
16
+ * @throws {IF97Error} if numerical state construction fails, including near-critical saturation endpoints
17
+ */
18
+ export declare function detectRegionTS(T: number, s: number): Region | -1;
package/dist/detect.js CHANGED
@@ -1,4 +1,38 @@
1
+ /** Public region-detection helper exports. */
2
+ import { uniqueFixedTemperatureState } from './backward/fixed-temperature-candidates.js';
3
+ import { OutOfRangeError } from './types.js';
4
+ export { detectRegionPT } from './core/region-detector.js';
5
+ export { detectRegionPH, detectRegionPS } from './backward/pressure-property-region.js';
6
+ export { detectRegionHS } from './backward/hs-region.js';
1
7
  /**
2
- * Public region-detection helper exports.
8
+ * Detect the region only when T and h identify a unique physical state.
9
+ * @returns Region number (1–5) or -1 if no valid state exists
10
+ * @throws {AmbiguousStateError} if multiple physical states match, even in one region
11
+ * @throws {IF97Error} if numerical state construction fails, including near-critical saturation endpoints
3
12
  */
4
- export { detectRegionPT, detectRegionPH, detectRegionPS, detectRegionHS, detectRegionTH, detectRegionTS, } from './core/region-detector.js';
13
+ export function detectRegionTH(T, h) {
14
+ try {
15
+ return uniqueFixedTemperatureState(T, h, 'enthalpy')?.region ?? -1;
16
+ }
17
+ catch (error) {
18
+ if (error instanceof OutOfRangeError)
19
+ return -1;
20
+ throw error;
21
+ }
22
+ }
23
+ /**
24
+ * Detect the region only when T and s identify a unique physical state.
25
+ * @returns Region number (1–5) or -1 if no valid state exists
26
+ * @throws {AmbiguousStateError} if multiple physical states match, even in one region
27
+ * @throws {IF97Error} if numerical state construction fails, including near-critical saturation endpoints
28
+ */
29
+ export function detectRegionTS(T, s) {
30
+ try {
31
+ return uniqueFixedTemperatureState(T, s, 'entropy')?.region ?? -1;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof OutOfRangeError)
35
+ return -1;
36
+ throw error;
37
+ }
38
+ }
@@ -0,0 +1,11 @@
1
+ import { IF97Error, type Region } from './types.js';
2
+ export declare class SaturationEndpointError extends IF97Error {
3
+ }
4
+ export declare class NumericalStateError extends IF97Error {
5
+ }
6
+ export declare class RegionMismatchError extends IF97Error {
7
+ readonly solverName: string;
8
+ readonly actualRegion: Region | -1;
9
+ readonly expectedRegion: Region;
10
+ constructor(solverName: string, actualRegion: Region | -1, expectedRegion: Region);
11
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,16 @@
1
+ import { IF97Error } from './types.js';
2
+ export class SaturationEndpointError extends IF97Error {
3
+ }
4
+ export class NumericalStateError extends IF97Error {
5
+ }
6
+ export class RegionMismatchError extends IF97Error {
7
+ solverName;
8
+ actualRegion;
9
+ expectedRegion;
10
+ constructor(solverName, actualRegion, expectedRegion) {
11
+ super(`${solverName} produced a PT state in Region ${actualRegion}, not Region ${expectedRegion}`);
12
+ this.solverName = solverName;
13
+ this.actualRegion = actualRegion;
14
+ this.expectedRegion = expectedRegion;
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ /** Specific gas constant for water [kJ/(kg·K)].
2
+ * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency.
3
+ * Do not replace transport correlations with this value; thermal conductivity
4
+ * critical enhancement intentionally uses `R_IAPWS_2011_THERMAL`. */
5
+ export declare const R = 0.461526;
@@ -0,0 +1,5 @@
1
+ /** Specific gas constant for water [kJ/(kg·K)].
2
+ * IAPWS-IF97, §2 (Table 1). Fixed value for IF97 internal consistency.
3
+ * Do not replace transport correlations with this value; thermal conductivity
4
+ * critical enhancement intentionally uses `R_IAPWS_2011_THERMAL`. */
5
+ export const R = 0.461526;
package/dist/index.d.ts CHANGED
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- import type { SteamState, SolveInput } from './types.js';
7
- export declare function solvePT(p: number, T: number): SteamState;
8
- export declare function solve(input: SolveInput): SteamState;
6
+ import type { SteamState } from './types.js';
7
+ export { solve, solveBasic, solvePT } from './core/solver.js';
9
8
  export declare function solvePH(p: number, h: number): SteamState;
10
9
  export declare function solvePS(p: number, s: number): SteamState;
11
10
  export declare function solveHS(h: number, s: number): SteamState;
@@ -13,5 +12,5 @@ export declare function solveTH(T: number, h: number): SteamState;
13
12
  export declare function solveTS(T: number, s: number): SteamState;
14
13
  export declare function solvePx(p: number, x: number): SteamState;
15
14
  export declare function solveTx(T: number, x: number): SteamState;
16
- export type { SteamState, SolveInput } from './types.js';
17
- export { Region, IF97Error, OutOfRangeError, ConvergenceError } from './types.js';
15
+ export type { BasicProperties, SaturationEndpoints, SteamState, SolveInput } from './types.js';
16
+ export { Region, IF97Error, AmbiguousStateError, OutOfRangeError, ConvergenceError } from './types.js';
package/dist/index.js CHANGED
@@ -1,36 +1,25 @@
1
- import { solve as solveAny, solvePT as solvePTInternal } from './core/solver.js';
2
- import { normalizePublicState } from './core/public-normalization.js';
3
- function normalizeSteamState(state) {
4
- return normalizePublicState(state);
5
- }
1
+ import { solve as solveAny } from './core/solver.js';
6
2
  // Core solvers
7
- // Normalize only at the public package boundary so internal solver math
8
- // keeps full raw precision during iteration and region transitions.
9
- export function solvePT(p, T) {
10
- return normalizeSteamState(solvePTInternal(p, T));
11
- }
12
- export function solve(input) {
13
- return normalizeSteamState(solveAny(input));
14
- }
3
+ export { solve, solveBasic, solvePT } from './core/solver.js';
15
4
  export function solvePH(p, h) {
16
- return normalizeSteamState(solveAny({ mode: 'PH', p, h }));
5
+ return solveAny({ mode: 'PH', p, h });
17
6
  }
18
7
  export function solvePS(p, s) {
19
- return normalizeSteamState(solveAny({ mode: 'PS', p, s }));
8
+ return solveAny({ mode: 'PS', p, s });
20
9
  }
21
10
  export function solveHS(h, s) {
22
- return normalizeSteamState(solveAny({ mode: 'HS', h, s }));
11
+ return solveAny({ mode: 'HS', h, s });
23
12
  }
24
13
  export function solveTH(T, h) {
25
- return normalizeSteamState(solveAny({ mode: 'TH', T, h }));
14
+ return solveAny({ mode: 'TH', T, h });
26
15
  }
27
16
  export function solveTS(T, s) {
28
- return normalizeSteamState(solveAny({ mode: 'TS', T, s }));
17
+ return solveAny({ mode: 'TS', T, s });
29
18
  }
30
19
  export function solvePx(p, x) {
31
- return normalizeSteamState(solveAny({ mode: 'Px', p, x }));
20
+ return solveAny({ mode: 'Px', p, x });
32
21
  }
33
22
  export function solveTx(T, x) {
34
- return normalizeSteamState(solveAny({ mode: 'Tx', T, x }));
23
+ return solveAny({ mode: 'Tx', T, x });
35
24
  }
36
- export { Region, IF97Error, OutOfRangeError, ConvergenceError } from './types.js';
25
+ export { Region, IF97Error, AmbiguousStateError, OutOfRangeError, ConvergenceError } from './types.js';
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * Reference: IAPWS-IF97, Section 5 (Equations for Region 1)
8
8
  */
9
- import type { BasicProperties } from '../types.js';
9
+ import type { BasicProperties, CoefficientTable } from '../types.js';
10
+ export declare const COEFFICIENTS: CoefficientTable;
10
11
  /**
11
12
  * Compute Region 1 thermodynamic properties from pressure and temperature.
12
13
  *
@@ -9,7 +9,7 @@
9
9
  import { R } from '../constants.js';
10
10
  import { Region } from '../types.js';
11
11
  // ─── Coefficient Table (Table 2, IAPWS-IF97) ───────────────────────────────
12
- const COEFFICIENTS = [
12
+ export const COEFFICIENTS = [
13
13
  [0, -2, 0.14632971213167],
14
14
  [0, -1, -0.84548187169114],
15
15
  [0, 0, -3.756360367204],
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * Reference: IAPWS-IF97, Section 6 (Equations for Region 2)
8
8
  */
9
- import type { BasicProperties } from '../types.js';
9
+ import type { BasicProperties, CoefficientTable } from '../types.js';
10
+ export declare const RESIDUAL_COEFFICIENTS: CoefficientTable;
10
11
  /**
11
12
  * Compute Region 2 thermodynamic properties from pressure and temperature.
12
13
  *
@@ -22,7 +22,7 @@ const IDEAL_COEFFICIENTS = [
22
22
  [3, 0.021268463753307],
23
23
  ];
24
24
  // ─── Residual Part Coefficients (Table 11) ──────────────────────────────────
25
- const RESIDUAL_COEFFICIENTS = [
25
+ export const RESIDUAL_COEFFICIENTS = [
26
26
  [1, 0, -1.7731742473213e-3],
27
27
  [1, 1, -0.017834862292358],
28
28
  [1, 2, -0.045996013696365],
@@ -48,7 +48,7 @@ export const R3L = {
48
48
  };
49
49
  export const R3M = {
50
50
  pStar: 23, tStar: 650, vStar: 0.0028, piShift: 1, sigShift: 0.997, sigExp: 0.25,
51
- IJN: [[0, 0, 0.811384363481847], [3, 0, -0.568199310990094e4], [8, 0, -0.178657198172556e11], [20, 2, 0.795537657613427e32], [1, 5, -0.814568209346872e5], [3, 5, -0.659774567602874e8], [4, 5, -0.152861148659302e11], [5, 5, -0.560165667510446e12], [1, 6, 0.458384828593949e6], [6, 6, -0.385754000383848e14], [2, 7, 0.453735800004273e8], [4, 8, 0.939454935735563e12], [14, 8, 0.266572856432938e28], [2, 10, -0.547578313899097e10], [5, 10, 0.200725701112386e15], [3, 12, 0.185007245563239e13], [0, 14, 0.185135446828337e9], [1, 14, -0.170451090076385e12], [1, 18, 0.157890366037614e15], [1, 20, -0.202530509748774e16], [28, 20, 0.368193926183570e60], [2, 22, 0.170215539458936e18], [16, 22, 0.639234909918741e42], [0, 24, -0.821698160721956e15], [5, 24, -0.795260241872306e24], [0, 28, 0.233415869478510e18], [3, 28, -0.600079934586803e23], [4, 28, 0.594584382273384e25], [12, 28, 0.189461279349492e40], [16, 28, -0.810093428842645e46], [1, 32, 0.188813911076809e22], [8, 32, 0.111052244098768e36], [14, 32, 0.291133958602503e46], [0, 36, -0.329421923951460e22], [2, 36, -0.137570282536696e26], [3, 36, 0.181508996303902e28], [4, 36, -0.346865122768353e30], [8, 36, -0.211961141774260e38], [14, 36, -0.128617899887675e49], [24, 36, 0.479817895699239e65]]
51
+ IJN: [[0, 0, 0.811384363481847], [3, 0, -0.568199310990094e4], [8, 0, -0.178657198172556e11], [20, 2, 0.795537657613427e32], [1, 5, -0.814568209346872e5], [3, 5, -0.659774567602874e8], [4, 5, -0.152861148659302e11], [5, 5, -0.560165667510446e12], [1, 6, 0.458384828593949e6], [6, 6, -0.385754000383848e14], [2, 7, 0.453735800004273e8], [4, 8, 0.939454935735563e12], [14, 8, 0.266572856432938e28], [2, 10, -0.547578313899097e10], [5, 10, 0.200725701112386e15], [3, 12, 0.185007245563239e13], [0, 14, 0.185135446828337e9], [1, 14, -0.170451090076385e12], [1, 18, 0.157890366037614e15], [1, 20, -0.202530509748774e16], [28, 20, 0.368193926183570e60], [2, 22, 0.170215539458936e18], [16, 22, 0.639234909918741e42], [0, 24, -0.821698160721956e15], [5, 24, -0.795260241872306e24], [0, 28, 0.233415869478510e18], [3, 28, -0.600079934586803e23], [4, 28, 0.594584382273384e25], [12, 28, 0.189461279349492e40], [16, 28, -0.810093428842645e46], [1, 32, 0.188813911076809e22], [8, 32, 0.111052244098768e36], [14, 32, 0.291133958602503e46], [0, 36, -0.329421923951460e22], [2, 36, -0.137570282536696e26], [3, 36, 0.181508996303902e28], [4, 36, -0.346865122768353e30], [8, 36, -0.211961148774260e38], [14, 36, -0.128617899887675e49], [24, 36, 0.479817895699239e65]]
52
52
  };
53
53
  export const R3N = {
54
54
  pStar: 23, tStar: 650, vStar: 0.0031, piShift: 0.976, sigShift: 0.997, transform: 'exp',
@@ -56,7 +56,7 @@ export const R3N = {
56
56
  };
57
57
  export const R3O = {
58
58
  pStar: 23, tStar: 650, vStar: 0.0034, piShift: 0.974, sigShift: 0.996, piExp: 0.5,
59
- IJN: [[0, -12, 0.128746023979718e-34], [0, -4, -0.735234770382342e-11], [0, -1, 0.289078692149150e-2], [2, -1, 0.244482731907223], [3, -10, 0.141733492030985e-23], [4, -12, -0.354533853059476e-28], [4, -8, -0.594539202901431e-17], [4, -5, -0.585188401782779e-8], [4, -4, 0.201377325411803e-5], [4, -1, 0.138647388209306e1], [5, -4, -0.173959365084772e-4], [5, -3, 0.137680878349369e-2], [6, -8, 0.814897605805513e-14], [7, -12, 0.425596631351839e-25], [8, -10, -0.387449113787755e-17], [8, -8, 0.139814730240000e-12], [8, -4, -0.171849638951521e-2], [10, -12, 0.641890529513296e-21], [10, -8, 0.118960578072018e-10], [14, -12, -0.155282762571611e-17], [14, -8, 0.233907907347507e-7], [20, -12, -0.174093247766213e-12], [20, -10, 0.377682649089149e-8], [24, -12, -0.516720236575302e-10]]
59
+ IJN: [[0, -12, 0.128746023979718e-34], [0, -4, -0.735234770382342e-11], [0, -1, 0.289078692149150e-2], [2, -1, 0.244482731907223], [3, -10, 0.141733492030985e-23], [4, -12, -0.354533853059476e-28], [4, -8, -0.594539202901431e-17], [4, -5, -0.585188401782779e-8], [4, -4, 0.201377325411803e-5], [4, -1, 0.138647388209306e1], [5, -4, -0.173959365084772e-4], [5, -3, 0.137680878349369e-2], [6, -8, 0.814897605805513e-14], [7, -12, 0.425596631351839e-25], [8, -10, -0.387449113787755e-17], [8, -8, 0.139814747930240e-12], [8, -4, -0.171849638951521e-2], [10, -12, 0.641890529513296e-21], [10, -8, 0.118960578072018e-10], [14, -12, -0.155282762571611e-17], [14, -8, 0.233907907347507e-7], [20, -12, -0.174093247766213e-12], [20, -10, 0.377682649089149e-8], [24, -12, -0.516720236575302e-10]]
60
60
  };
61
61
  export const R3P = {
62
62
  pStar: 23, tStar: 650, vStar: 0.0041, piShift: 0.972, sigShift: 0.997, piExp: 0.5,
@@ -88,7 +88,7 @@ export const R3V = {
88
88
  };
89
89
  export const R3W = {
90
90
  pStar: 23, tStar: 650, vStar: 0.0039, piShift: 0.959, sigShift: 0.995, transform: 'pow4',
91
- IJN: [[-12, 8, -0.586219133817016e-7], [-12, 14, -0.894460355005526e11], [-10, -1, 0.531168037519774e-30], [-10, 8, 0.109892402329239], [-8, 6, -0.575368389425212e-1], [-8, 8, 0.228276853990249e5], [-8, 14, -0.158548609655002e19], [-6, -4, 0.329865748576503e-27], [-6, -3, -0.634987981190669e-24], [-6, 2, 0.615762068640611e-8], [-6, 8, -0.961109240985747e8], [-5, -10, -0.406274286652625e-44], [-4, -1, -0.471103725498077e-12], [-4, 3, 0.725937724828145], [-3, -10, 0.187768525763682e-38], [-3, 3, -0.103308436323771e4], [-2, 1, -0.662552816342168e-1], [-2, 2, 0.579514041765710e3], [-1, -8, 0.237416732616644e-26], [-1, -4, 0.271700235739893e-14], [-1, 1, -0.907886213498360e2], [0, -12, -0.171242509570207e-36], [0, 1, 0.156792067854621e3], [1, -1, 0.923261357901470], [2, -1, -0.597865988422577e1], [2, 2, 0.321988767636389e7], [3, -12, -0.399441390042203e-29], [3, -5, 0.493429086046981e-7], [5, -10, 0.812036983370565e-19], [5, -8, -0.207610284654137e-11], [5, -6, -0.340821291419719e-6], [8, -12, 0.542000573372233e-17], [8, -10, -0.856711586510214e-12], [10, -12, 0.266170454405981e-13], [10, -8, 0.858133791857099e-5]]
91
+ IJN: [[-12, 8, -0.586219133817016e-7], [-12, 14, -0.894460355005526e11], [-10, -1, 0.531168037519774e-30], [-10, 8, 0.109892402329239], [-8, 6, -0.575368389425212e-1], [-8, 8, 0.228276853990249e5], [-8, 14, -0.158548609655002e19], [-6, -4, 0.329865748576503e-27], [-6, -3, -0.634987981190669e-24], [-6, 2, 0.615762068640611e-8], [-6, 8, -0.961109240985747e8], [-5, -10, -0.406274286652625e-44], [-4, -1, -0.471103725498077e-12], [-4, 3, 0.725937724828145], [-3, -10, 0.187768525763682e-38], [-3, 3, -0.103308436323771e4], [-2, 1, -0.662552816342168e-1], [-2, 2, 0.579514041765710e3], [-1, -8, 0.237416732616644e-26], [-1, -4, 0.271700235739893e-14], [-1, 1, -0.907886213483600e2], [0, -12, -0.171242509570207e-36], [0, 1, 0.156792067854621e3], [1, -1, 0.923261357901470], [2, -1, -0.597865988422577e1], [2, 2, 0.321988767636389e7], [3, -12, -0.399441390042203e-29], [3, -5, 0.493429086046981e-7], [5, -10, 0.812036983370565e-19], [5, -8, -0.207610284654137e-11], [5, -6, -0.340821291419719e-6], [8, -12, 0.542000573372233e-17], [8, -10, -0.856711586510214e-12], [10, -12, 0.266170454405981e-13], [10, -8, 0.858133791857099e-5]]
92
92
  };
93
93
  export const R3X = {
94
94
  pStar: 23, tStar: 650, vStar: 0.0049, piShift: 0.910, sigShift: 0.988,
@@ -6,7 +6,7 @@ import { evalSubregion } from './region3-eval.js';
6
6
  import { REGION3_SUBREGIONS as R3 } from './region3-data.js';
7
7
  import { b3ab, b3cd, b3ef, b3gh, b3ij, b3jk, b3mn, b3op, b3qu, b3rx, b3uv, b3wx } from './boundaries.js';
8
8
  import { saturationPressure, saturationTemperature } from './region4.js';
9
- import { Pc } from '../constants.js';
9
+ import { Pc, P_MAX, Tc } from '../constants.js';
10
10
  import { IF97Error } from '../types.js';
11
11
  // Shared liquid-side C/S saturation threshold from the Region 3 supplementary release.
12
12
  const R3_C_S_LIQUID_BOUNDARY_PRESSURE = 19.00881189173929;
@@ -14,6 +14,9 @@ const R3_C_S_LIQUID_BOUNDARY_PRESSURE = 19.00881189173929;
14
14
  * Pressure thresholds from IAPWS Supplementary Release on Backward
15
15
  * Equations v(P,T) for Region 3, Table 1 (saturation subregion limits). */
16
16
  export function region3SatVolume(p, T, x) {
17
+ if (x !== 0 && x !== 1) {
18
+ throw new IF97Error('Region 3 saturation volume requires x = 0 (liquid) or x = 1 (vapor)');
19
+ }
17
20
  if (x === 0) {
18
21
  if (p < R3_C_S_LIQUID_BOUNDARY_PRESSURE)
19
22
  return evalSubregion(R3.C, p, T);
@@ -35,6 +38,9 @@ export function region3SatVolume(p, T, x) {
35
38
  * Pressure thresholds from IAPWS Supplementary Release on Backward
36
39
  * Equations v(P,T) for Region 3, Figures 2–5. */
37
40
  export function region3Volume(p, T) {
41
+ if (!Number.isFinite(p) || !Number.isFinite(T) || p > P_MAX) {
42
+ throw new IF97Error(`Region 3 v(P,T) requires finite p <= ${P_MAX} MPa and finite T`);
43
+ }
38
44
  if (p > 40) { // Suppl. Release Fig. 2: subregions A/B
39
45
  return T <= b3ab(p) ? evalSubregion(R3.A, p, T) : evalSubregion(R3.B, p, T);
40
46
  }
@@ -97,6 +103,7 @@ export function region3Volume(p, T) {
97
103
  }
98
104
  const Tsat643 = 643.15;
99
105
  const Psat643 = saturationPressure(Tsat643);
106
+ const liquid = T <= Tc && p >= saturationPressure(T);
100
107
  if (p > Psat643 && p <= 22.5) {
101
108
  const tcd = b3cd(p), tqu = b3qu(p), trx = b3rx(p), tjk = b3jk(p);
102
109
  if (T <= tcd)
@@ -123,8 +130,7 @@ export function region3Volume(p, T) {
123
130
  return evalSubregion(R3.Z, p, T);
124
131
  return evalSubregion(R3.X, p, T);
125
132
  }
126
- const Tsat = saturationTemperature(p);
127
- if (T > Tsat) {
133
+ if (!liquid) {
128
134
  if (p <= 21.90096265)
129
135
  return evalSubregion(R3.X, p, T);
130
136
  if (T <= twx)
@@ -142,27 +148,26 @@ export function region3Volume(p, T) {
142
148
  return evalSubregion(R3.K, p, T);
143
149
  }
144
150
  if (p > 20.5 && p <= Psat643) {
145
- const tcd = b3cd(p), Ts = saturationTemperature(p), tjk = b3jk(p);
151
+ const tcd = b3cd(p), tjk = b3jk(p);
146
152
  if (T <= tcd)
147
153
  return evalSubregion(R3.C, p, T);
148
- if (T <= Ts)
154
+ if (liquid)
149
155
  return evalSubregion(R3.S, p, T);
150
156
  if (T <= tjk)
151
157
  return evalSubregion(R3.R, p, T);
152
158
  return evalSubregion(R3.K, p, T);
153
159
  }
154
160
  if (p >= R3_C_S_LIQUID_BOUNDARY_PRESSURE && p <= 20.5) {
155
- const tcd = b3cd(p), Ts = saturationTemperature(p);
156
- if (T <= Ts) {
157
- if (T < tcd && T < Ts)
161
+ const tcd = b3cd(p);
162
+ if (liquid) {
163
+ if (T < tcd && T < saturationTemperature(p))
158
164
  return evalSubregion(R3.C, p, T);
159
165
  return evalSubregion(R3.S, p, T);
160
166
  }
161
167
  return evalSubregion(R3.T, p, T);
162
168
  }
163
169
  if (p > 16.5291642526) {
164
- const Ts = saturationTemperature(p);
165
- if (T <= Ts)
170
+ if (liquid)
166
171
  return evalSubregion(R3.C, p, T);
167
172
  return evalSubregion(R3.T, p, T);
168
173
  }
@@ -6,7 +6,10 @@
6
6
  *
7
7
  * Reference: IAPWS-IF97, Section 7 (Equations for Region 3)
8
8
  */
9
- import type { BasicProperties } from '../types.js';
9
+ import type { BasicProperties, CoefficientTable } from '../types.js';
10
+ export declare const COEFFICIENTS: CoefficientTable;
11
+ /** Internal pressure residual: near criticality, rounding pressure before subtraction hides real density error. */
12
+ export declare function region3PressureResidual(rho: number, T: number, pressure: number, pressureLow?: number): number;
10
13
  /**
11
14
  * Compute Region 3 thermodynamic properties from density and temperature.
12
15
  *
@@ -8,8 +8,9 @@
8
8
  */
9
9
  import { R } from '../constants.js';
10
10
  import { IF97Error, Region } from '../types.js';
11
+ import { addExtended, divideExtended, multiplyExtended, } from '../solvers/extended-arithmetic.js';
11
12
  // ─── Coefficient Table (Table 30, IAPWS-IF97) ──────────────────────────────
12
- const COEFFICIENTS = [
13
+ export const COEFFICIENTS = [
13
14
  // Entry 0 is the coefficient n₁ for the logarithmic term n₁·ln(δ).
14
15
  // It shares the [I=0, J=0] shape with entry 1 but is handled separately
15
16
  // in the evaluation loop (index 0 = ln term, indices 1+ = polynomial).
@@ -57,6 +58,35 @@ const COEFFICIENTS = [
57
58
  // Reduction constants for Region 3
58
59
  const RHO_STAR = 322; // ρ* [kg/m³] (critical density)
59
60
  const T_STAR = 647.096; // T* [K] (critical temperature)
61
+ function extendedPowers(value, degree) {
62
+ const powers = [[1, 0]];
63
+ for (let i = 1; i <= degree; i += 1) {
64
+ powers.push(multiplyExtended(powers[i - 1], value));
65
+ }
66
+ return powers;
67
+ }
68
+ /** Evaluate rho*R*T*(n1 + sum(n*I*delta^I*tau^J))/1000 without losing its small residual. */
69
+ function pressureExtended(rho, T) {
70
+ const delta = extendedPowers(divideExtended([rho, 0], [RHO_STAR, 0]), 11);
71
+ const tau = extendedPowers(divideExtended([T_STAR, 0], [T, 0]), 26);
72
+ let polynomial = [COEFFICIENTS[0][2], 0];
73
+ for (let idx = 1; idx < COEFFICIENTS.length; idx += 1) {
74
+ const [I, J, N] = COEFFICIENTS[idx];
75
+ if (I === 0)
76
+ continue;
77
+ const coefficient = multiplyExtended([N, 0], [I, 0]);
78
+ const term = multiplyExtended(multiplyExtended(coefficient, delta[I]), tau[J]);
79
+ polynomial = addExtended(polynomial, term);
80
+ }
81
+ const scale = multiplyExtended(multiplyExtended([rho, 0], [R, 0]), [T, 0]);
82
+ return divideExtended(multiplyExtended(scale, polynomial), [1000, 0]);
83
+ }
84
+ /** Internal pressure residual: near criticality, rounding pressure before subtraction hides real density error. */
85
+ export function region3PressureResidual(rho, T, pressure, pressureLow = 0) {
86
+ const evaluated = pressureExtended(rho, T);
87
+ const residual = addExtended(evaluated, [-pressure, -pressureLow]);
88
+ return residual[0] + residual[1];
89
+ }
60
90
  /**
61
91
  * Compute Region 3 thermodynamic properties from density and temperature.
62
92
  *
@@ -97,7 +127,7 @@ export function region3ByRhoT(rho, T) {
97
127
  }
98
128
  return {
99
129
  region: Region.Region3,
100
- pressure: rho * R * T * delta * phi_delta / 1000,
130
+ pressure: pressureExtended(rho, T)[0],
101
131
  temperature: T,
102
132
  specificVolume: v,
103
133
  internalEnergy: R * T * tau * phi_tau,
@@ -15,6 +15,8 @@
15
15
  * @throws {OutOfRangeError} if T is outside valid range
16
16
  */
17
17
  export declare function saturationPressure(T: number): number;
18
+ /** Internal unrounded target: near criticality, one pressure ULP changes endpoint density. */
19
+ export declare function saturationPressurePair(T: number): readonly [number, number];
18
20
  /**
19
21
  * Saturation temperature for a given pressure.
20
22
  * Equation 31 (pp. 34) of IAPWS-IF97.