iapws-if97 2.2.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -2
- package/README.md +69 -246
- package/dist/backward/candidate-resolution.d.ts +8 -0
- package/dist/backward/candidate-resolution.js +40 -0
- package/dist/backward/fixed-temperature-candidates.d.ts +4 -0
- package/dist/backward/fixed-temperature-candidates.js +188 -0
- package/dist/backward/fixed-temperature-solver.d.ts +3 -3
- package/dist/backward/fixed-temperature-solver.js +38 -17
- package/dist/backward/fixed-temperature-stationary.d.ts +4 -0
- package/dist/backward/fixed-temperature-stationary.js +146 -0
- package/dist/backward/hs-region.d.ts +50 -0
- package/dist/backward/hs-region.js +695 -0
- package/dist/backward/hs-state.d.ts +15 -0
- package/dist/backward/hs-state.js +316 -0
- package/dist/backward/hs.js +28 -242
- package/dist/backward/ph.d.ts +2 -0
- package/dist/backward/ph.js +72 -21
- package/dist/backward/pressure-property-region.d.ts +13 -0
- package/dist/backward/pressure-property-region.js +262 -0
- package/dist/backward/ps.d.ts +6 -0
- package/dist/backward/ps.js +81 -28
- package/dist/backward/solution-validation.d.ts +7 -1
- package/dist/backward/solution-validation.js +138 -41
- package/dist/backward/th.d.ts +3 -5
- package/dist/backward/th.js +10 -71
- package/dist/backward/tolerances.d.ts +3 -0
- package/dist/backward/tolerances.js +9 -3
- package/dist/backward/ts.d.ts +3 -5
- package/dist/backward/ts.js +10 -71
- package/dist/constants.d.ts +11 -15
- package/dist/constants.js +14 -15
- package/dist/core/floating-point.d.ts +2 -0
- package/dist/core/floating-point.js +7 -0
- package/dist/core/formulation-seams.d.ts +27 -0
- package/dist/core/formulation-seams.js +289 -0
- package/dist/core/input-validation.d.ts +1 -0
- package/dist/core/input-validation.js +5 -0
- package/dist/core/region-detector.d.ts +3 -40
- package/dist/core/region-detector.js +37 -762
- package/dist/core/region3-pt.d.ts +1 -1
- package/dist/core/region3-pt.js +42 -16
- package/dist/core/solve-input-normalization.js +4 -1
- package/dist/core/solver.d.ts +7 -10
- package/dist/core/solver.js +26 -17
- package/dist/detect.d.ts +16 -2
- package/dist/detect.js +36 -2
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +16 -0
- package/dist/gas-constant.d.ts +5 -0
- package/dist/gas-constant.js +5 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +10 -21
- package/dist/regions/region1.d.ts +2 -1
- package/dist/regions/region1.js +1 -1
- package/dist/regions/region2.d.ts +2 -1
- package/dist/regions/region2.js +1 -1
- package/dist/regions/region3-data.js +3 -3
- package/dist/regions/region3-subregions.js +15 -10
- package/dist/regions/region3.d.ts +4 -1
- package/dist/regions/region3.js +32 -2
- package/dist/regions/region4.d.ts +2 -0
- package/dist/regions/region4.js +39 -12
- package/dist/regions/region5.d.ts +2 -1
- package/dist/regions/region5.js +2 -2
- package/dist/saturation/common.d.ts +2 -8
- package/dist/saturation/common.js +64 -36
- package/dist/saturation/region4-boundaries.js +13 -13
- package/dist/saturation/region4-hs.js +282 -58
- package/dist/saturation/two-phase.d.ts +2 -2
- package/dist/saturation/two-phase.js +7 -7
- package/dist/solvers/bracketed-newton.d.ts +6 -3
- package/dist/solvers/bracketed-newton.js +15 -8
- package/dist/solvers/extended-arithmetic.d.ts +6 -0
- package/dist/solvers/extended-arithmetic.js +38 -0
- package/dist/transport/properties.d.ts +4 -2
- package/dist/transport/properties.js +14 -4
- package/dist/types.d.ts +18 -0
- package/dist/types.js +14 -0
- package/package.json +4 -4
- package/dist/core/public-normalization.d.ts +0 -3
- package/dist/core/public-normalization.js +0 -53
- /package/dist/{backward → core}/thermodynamic-derivatives.d.ts +0 -0
- /package/dist/{backward → core}/thermodynamic-derivatives.js +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { FormulationSeamResolution } from '../core/formulation-seams.js';
|
|
1
2
|
import type { BasicProperties } from '../types.js';
|
|
2
|
-
import { Region } from '../types.js';
|
|
3
|
+
import { IF97Error, Region } from '../types.js';
|
|
3
4
|
export interface BackwardConstraint {
|
|
4
5
|
label: 'pressure' | 'temperature' | 'enthalpy' | 'entropy';
|
|
5
6
|
expected: number;
|
|
@@ -8,5 +9,10 @@ export interface BackwardConstraint {
|
|
|
8
9
|
export interface BackwardValidationOptions {
|
|
9
10
|
solverName: string;
|
|
10
11
|
expectedRegion?: Region;
|
|
12
|
+
seam?: FormulationSeamResolution;
|
|
11
13
|
}
|
|
14
|
+
/** Inspect a candidate without moving it across a formulation or domain boundary. */
|
|
15
|
+
export declare function backwardCandidateError(state: BasicProperties, constraints: readonly BackwardConstraint[], options: BackwardValidationOptions): IF97Error | null;
|
|
16
|
+
export declare function normalizeBackwardState(state: BasicProperties, constraints: readonly BackwardConstraint[], solverName: string): BasicProperties;
|
|
12
17
|
export declare function validateBackwardState(state: BasicProperties, constraints: readonly BackwardConstraint[], options: BackwardValidationOptions): BasicProperties;
|
|
18
|
+
export declare function validateRegion3PTReconstruction(state: BasicProperties, solverName: string): BasicProperties;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as C from '../constants.js';
|
|
2
|
-
import { detectRegionPT } from '../core/region-detector.js';
|
|
2
|
+
import { detectRegionPT, nativeB23TemperatureBracket, nativeSaturationTemperatureBracket } from '../core/region-detector.js';
|
|
3
|
+
import { solveRegion3PTBasic } from '../core/region3-pt.js';
|
|
4
|
+
import { nextPositiveFloat } from '../core/floating-point.js';
|
|
5
|
+
import { region2 } from '../regions/region2.js';
|
|
6
|
+
import { region1 } from '../regions/region1.js';
|
|
3
7
|
import { saturationPressure } from '../regions/region4.js';
|
|
8
|
+
import { region5 } from '../regions/region5.js';
|
|
4
9
|
import { IF97Error, Region } from '../types.js';
|
|
10
|
+
import { NumericalStateError, RegionMismatchError } from '../errors.js';
|
|
5
11
|
import { backwardConstraintTolerance, region4SaturationPressureTolerance, } from './tolerances.js';
|
|
6
12
|
function propertyValue(state, label) {
|
|
7
13
|
switch (label) {
|
|
@@ -31,7 +37,12 @@ function snapDownFromAbove(value, boundary) {
|
|
|
31
37
|
: value;
|
|
32
38
|
}
|
|
33
39
|
function normalizeComputedPT(state) {
|
|
34
|
-
|
|
40
|
+
if (state.region === Region.Region4)
|
|
41
|
+
return state;
|
|
42
|
+
let pressure = state.pressure < C.P_MIN &&
|
|
43
|
+
C.P_MIN - state.pressure <= backwardConstraintTolerance('pressure', C.P_MIN)
|
|
44
|
+
? C.P_MIN
|
|
45
|
+
: state.pressure;
|
|
35
46
|
pressure = snapDownFromAbove(pressure, state.region === Region.Region5 ? C.R5_P_MAX : C.P_MAX);
|
|
36
47
|
let temperature = snapUpFromBelow(state.temperature, C.T_MIN);
|
|
37
48
|
if (state.region === Region.Region1) {
|
|
@@ -41,73 +52,159 @@ function normalizeComputedPT(state) {
|
|
|
41
52
|
temperature = snapDownFromAbove(temperature, C.R2_T_MAX);
|
|
42
53
|
}
|
|
43
54
|
temperature = snapDownFromAbove(temperature, C.T_MAX);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
if (pressure === state.pressure && temperature === state.temperature)
|
|
56
|
+
return state;
|
|
57
|
+
switch (state.region) {
|
|
58
|
+
case Region.Region1: return region1(pressure, temperature);
|
|
59
|
+
case Region.Region2: return region2(pressure, temperature);
|
|
60
|
+
case Region.Region3: return solveRegion3PTBasic(pressure, temperature);
|
|
61
|
+
case Region.Region5: return region5(pressure, temperature);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function normalizeB23State(state) {
|
|
65
|
+
if ((state.region !== Region.Region2 && state.region !== Region.Region3) ||
|
|
66
|
+
state.pressure <= C.B23_P_MIN || state.pressure > C.P_MAX ||
|
|
67
|
+
(state.temperature <= C.R2_T_MIN &&
|
|
68
|
+
(state.region !== Region.Region2 || state.pressure <= saturationPressure(C.R2_T_MIN))) ||
|
|
69
|
+
state.temperature >= C.B23_T_MAX ||
|
|
70
|
+
detectRegionPT(state.pressure, state.temperature) === state.region) {
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
const [lower, upper] = nativeB23TemperatureBracket(state.pressure);
|
|
74
|
+
const vaporT = state.region === Region.Region2 && state.pressure < C.Pc
|
|
75
|
+
? nativeSaturationTemperatureBracket(state.pressure)[1] : upper;
|
|
76
|
+
const temperature = state.region === Region.Region3 ? lower : Math.max(upper, vaporT);
|
|
77
|
+
if (Math.abs(temperature - state.temperature) > boundaryTolerance(temperature, state.temperature)) {
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
return state.region === Region.Region3
|
|
81
|
+
? solveRegion3PTBasic(state.pressure, temperature)
|
|
82
|
+
: region2(state.pressure, temperature);
|
|
83
|
+
}
|
|
84
|
+
function normalizeRegion3LowerState(state) {
|
|
85
|
+
if (state.region !== Region.Region3 || !(state.temperature <= C.R2_T_MIN) ||
|
|
86
|
+
C.R2_T_MIN - state.temperature > boundaryTolerance(state.temperature, C.R2_T_MIN))
|
|
87
|
+
return state;
|
|
88
|
+
return solveRegion3PTBasic(state.pressure, nextPositiveFloat(C.R2_T_MIN, 1));
|
|
89
|
+
}
|
|
90
|
+
function normalizeSaturationState(state, constraints, solverName) {
|
|
91
|
+
if ((state.region !== Region.Region1 && state.region !== Region.Region2) ||
|
|
92
|
+
state.pressure > saturationPressure(C.R2_T_MIN) ||
|
|
93
|
+
state.temperature < C.T_MIN || state.temperature > C.R2_T_MIN ||
|
|
94
|
+
detectRegionPT(state.pressure, state.temperature) === state.region) {
|
|
95
|
+
return state;
|
|
96
|
+
}
|
|
97
|
+
if (!constraints.some(({ label }) => label === 'pressure')) {
|
|
98
|
+
const psat = saturationPressure(state.temperature);
|
|
99
|
+
const pressure = state.region === Region.Region1 ? psat : nextPositiveFloat(psat, -1);
|
|
100
|
+
const projected = state.region === Region.Region1
|
|
101
|
+
? region1(pressure, state.temperature)
|
|
102
|
+
: region2(pressure, state.temperature);
|
|
103
|
+
if (Math.abs(pressure - state.pressure) <= backwardConstraintTolerance('pressure', psat) &&
|
|
104
|
+
backwardCandidateError(projected, constraints, { solverName }) === null)
|
|
105
|
+
return projected;
|
|
106
|
+
}
|
|
107
|
+
const [liquidT, vaporT] = nativeSaturationTemperatureBracket(state.pressure);
|
|
108
|
+
const temperature = state.region === Region.Region1 ? liquidT : vaporT;
|
|
109
|
+
if (Math.abs(temperature - state.temperature) > boundaryTolerance(temperature, state.temperature)) {
|
|
110
|
+
return state;
|
|
111
|
+
}
|
|
112
|
+
return state.region === Region.Region1
|
|
113
|
+
? region1(state.pressure, temperature)
|
|
114
|
+
: region2(state.pressure, temperature);
|
|
47
115
|
}
|
|
48
|
-
function
|
|
116
|
+
function basicEnvelopeError(state, solverName) {
|
|
49
117
|
if (!Number.isFinite(state.pressure) || !Number.isFinite(state.temperature) ||
|
|
50
|
-
!Number.isFinite(state.specificVolume)
|
|
51
|
-
|
|
118
|
+
!Number.isFinite(state.specificVolume)) {
|
|
119
|
+
return new NumericalStateError(`${solverName} produced an invalid thermodynamic state`);
|
|
52
120
|
}
|
|
121
|
+
if (state.specificVolume <= 0)
|
|
122
|
+
return new IF97Error(`${solverName} produced an invalid thermodynamic state`);
|
|
53
123
|
if (state.pressure < C.P_MIN || state.pressure > C.P_MAX ||
|
|
54
124
|
state.temperature < C.T_MIN || state.temperature > C.T_MAX) {
|
|
55
|
-
|
|
125
|
+
return new IF97Error(`${solverName} produced a state outside the IF97 PT envelope`);
|
|
56
126
|
}
|
|
127
|
+
if (state.region === Region.Region3) {
|
|
128
|
+
const stability = [state.cp, state.cv, state.isothermalCompressibility, state.speedOfSound];
|
|
129
|
+
if (!stability.every((value) => value !== null && Number.isFinite(value) && value > 0)) {
|
|
130
|
+
const ErrorType = stability.some((value) => value === null || !Number.isFinite(value))
|
|
131
|
+
? NumericalStateError : IF97Error;
|
|
132
|
+
return new ErrorType(`${solverName}: Region 3 numerical inconsistency: computed EOS state at p=${state.pressure} MPa, T=${state.temperature} K failed stability checks`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
57
136
|
}
|
|
58
|
-
function
|
|
137
|
+
function regionConsistencyError(state, solverName, expectedRegion) {
|
|
59
138
|
if (expectedRegion !== undefined && state.region !== expectedRegion) {
|
|
60
|
-
|
|
139
|
+
return new IF97Error(`${solverName} returned Region ${state.region}, expected Region ${expectedRegion}`);
|
|
61
140
|
}
|
|
62
141
|
if (state.region === Region.Region4) {
|
|
63
142
|
if (state.quality === null || !Number.isFinite(state.quality) || state.quality < 0 || state.quality > 1) {
|
|
64
|
-
|
|
143
|
+
return new IF97Error(`${solverName} produced an invalid Region 4 quality`);
|
|
65
144
|
}
|
|
66
145
|
const psat = saturationPressure(state.temperature);
|
|
67
146
|
const pTolerance = region4SaturationPressureTolerance(state.pressure);
|
|
68
147
|
if (Math.abs(psat - state.pressure) > pTolerance) {
|
|
69
|
-
|
|
148
|
+
return new IF97Error(`${solverName} produced a Region 4 state off the saturation curve`);
|
|
70
149
|
}
|
|
71
|
-
return;
|
|
150
|
+
return null;
|
|
72
151
|
}
|
|
73
152
|
const detectedRegion = detectRegionPT(state.pressure, state.temperature);
|
|
74
153
|
if (detectedRegion !== state.region) {
|
|
75
|
-
|
|
154
|
+
return new RegionMismatchError(solverName, detectedRegion, state.region);
|
|
76
155
|
}
|
|
156
|
+
return null;
|
|
77
157
|
}
|
|
78
|
-
function
|
|
158
|
+
function constraintError(state, constraints, solverName) {
|
|
79
159
|
for (const constraint of constraints) {
|
|
80
160
|
const actual = propertyValue(state, constraint.label);
|
|
81
161
|
const tolerance = constraint.tolerance ?? backwardConstraintTolerance(constraint.label, constraint.expected);
|
|
82
|
-
if (
|
|
83
|
-
|
|
162
|
+
if (!Number.isFinite(actual)) {
|
|
163
|
+
return new NumericalStateError(`${solverName} failed to match ${constraint.label}: expected ${constraint.expected}, got ${actual}`);
|
|
164
|
+
}
|
|
165
|
+
if (!Number.isFinite(constraint.expected) || !Number.isFinite(tolerance) ||
|
|
166
|
+
tolerance < 0 || Math.abs(actual - constraint.expected) > tolerance) {
|
|
167
|
+
return new IF97Error(`${solverName} failed to match ${constraint.label}: expected ${constraint.expected}, got ${actual}`);
|
|
84
168
|
}
|
|
85
169
|
}
|
|
170
|
+
return null;
|
|
86
171
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
172
|
+
/** Inspect a candidate without moving it across a formulation or domain boundary. */
|
|
173
|
+
export function backwardCandidateError(state, constraints, options) {
|
|
174
|
+
return basicEnvelopeError(state, options.solverName) ??
|
|
175
|
+
constraintError(state, constraints, options.solverName) ??
|
|
176
|
+
regionConsistencyError(state, options.solverName, options.expectedRegion);
|
|
177
|
+
}
|
|
178
|
+
export function normalizeBackwardState(state, constraints, solverName) {
|
|
179
|
+
const computed = normalizeComputedPT(state);
|
|
180
|
+
return constraints.some(({ label }) => label === 'temperature')
|
|
181
|
+
? computed
|
|
182
|
+
: normalizeSaturationState(normalizeB23State(normalizeRegion3LowerState(computed)), constraints, solverName);
|
|
183
|
+
}
|
|
184
|
+
export function validateBackwardState(state, constraints, options) {
|
|
185
|
+
const seam = options.seam;
|
|
186
|
+
if (seam !== undefined && state !== seam.state) {
|
|
187
|
+
throw new IF97Error(`${options.solverName} supplied an unrelated formulation seam state`);
|
|
104
188
|
}
|
|
189
|
+
const checkedConstraints = seam?.kind === 'gap'
|
|
190
|
+
? constraints.map((constraint) => constraint.label === 'enthalpy' || constraint.label === 'entropy'
|
|
191
|
+
? { ...constraint, tolerance: seam.budgets[constraint.label] }
|
|
192
|
+
: constraint)
|
|
193
|
+
: constraints;
|
|
194
|
+
const initialError = constraintError(state, checkedConstraints, options.solverName);
|
|
195
|
+
if (initialError !== null)
|
|
196
|
+
throw initialError;
|
|
197
|
+
const normalized = normalizeBackwardState(state, checkedConstraints, options.solverName);
|
|
198
|
+
const error = backwardCandidateError(normalized, checkedConstraints, options);
|
|
199
|
+
if (error !== null)
|
|
200
|
+
throw error;
|
|
105
201
|
return normalized;
|
|
106
202
|
}
|
|
107
|
-
export function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
203
|
+
export function validateRegion3PTReconstruction(state, solverName) {
|
|
204
|
+
return validateBackwardState(solveRegion3PTBasic(state.pressure, state.temperature), [
|
|
205
|
+
{ label: 'pressure', expected: state.pressure },
|
|
206
|
+
{ label: 'temperature', expected: state.temperature },
|
|
207
|
+
{ label: 'enthalpy', expected: state.enthalpy },
|
|
208
|
+
{ label: 'entropy', expected: state.entropy },
|
|
209
|
+
], { solverName, expectedRegion: Region.Region3 });
|
|
113
210
|
}
|
package/dist/backward/th.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backward equation T,H → state.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* and solves for pressure using a bracketed Newton method, then returns
|
|
6
|
-
* the full set of basic thermodynamic properties.
|
|
4
|
+
* Returns a state only when temperature and enthalpy determine it uniquely.
|
|
7
5
|
*/
|
|
8
6
|
import type { BasicProperties } from '../types.js';
|
|
9
7
|
/**
|
|
10
8
|
* Solve for full basic thermodynamic state from temperature and enthalpy.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
* within the appropriate bracket for that region.
|
|
10
|
+
* Searches every valid isotherm branch and rejects ambiguous inputs.
|
|
14
11
|
*
|
|
15
12
|
* Temperatures within ±CRITICAL_T_EXCLUSION_BAND of Tc are rejected because
|
|
16
13
|
* the enthalpy surface is nearly flat there, making pressure recovery unreliable.
|
|
@@ -20,5 +17,6 @@ import type { BasicProperties } from '../types.js';
|
|
|
20
17
|
* @returns Basic thermodynamic properties (without transport)
|
|
21
18
|
* @throws {OutOfRangeError} if T is outside [T_MIN, T_MAX]
|
|
22
19
|
* @throws {IF97Error} if T is within the critical exclusion band
|
|
20
|
+
* @throws {AmbiguousStateError} if multiple physical states match T and h
|
|
23
21
|
*/
|
|
24
22
|
export declare function solveTH(T: number, h: number): BasicProperties;
|
package/dist/backward/th.js
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import * as C from '../constants.js';
|
|
2
|
-
import { IF97Error, OutOfRangeError
|
|
3
|
-
import { assertFiniteNumber } from '../core/input-validation.js';
|
|
4
|
-
import {
|
|
5
|
-
import { solveRegion3PTBasic } from '../core/region3-pt.js';
|
|
6
|
-
import { region1 } from '../regions/region1.js';
|
|
7
|
-
import { region2 } from '../regions/region2.js';
|
|
8
|
-
import { region5 } from '../regions/region5.js';
|
|
9
|
-
import { boundary23_T_to_P } from '../regions/boundaries.js';
|
|
10
|
-
import { saturationPressure } from '../regions/region4.js';
|
|
11
|
-
import { mixSaturationState, qualityFromSaturationProperty, saturationEndpointsAtTemperature, } from '../saturation/common.js';
|
|
12
|
-
import { assertRegion4StateAllowed } from '../saturation/region4-boundaries.js';
|
|
13
|
-
import { solveFixedTemperaturePressure } from './fixed-temperature-solver.js';
|
|
14
|
-
import { validateBackwardState } from './solution-validation.js';
|
|
2
|
+
import { IF97Error, OutOfRangeError } from '../types.js';
|
|
3
|
+
import { assertFiniteNumber, withinClosedLowerBound } from '../core/input-validation.js';
|
|
4
|
+
import { uniqueFixedTemperatureState } from './fixed-temperature-candidates.js';
|
|
15
5
|
/**
|
|
16
6
|
* Solve for full basic thermodynamic state from temperature and enthalpy.
|
|
17
7
|
*
|
|
18
|
-
*
|
|
19
|
-
* within the appropriate bracket for that region.
|
|
8
|
+
* Searches every valid isotherm branch and rejects ambiguous inputs.
|
|
20
9
|
*
|
|
21
10
|
* Temperatures within ±CRITICAL_T_EXCLUSION_BAND of Tc are rejected because
|
|
22
11
|
* the enthalpy surface is nearly flat there, making pressure recovery unreliable.
|
|
@@ -26,6 +15,7 @@ import { validateBackwardState } from './solution-validation.js';
|
|
|
26
15
|
* @returns Basic thermodynamic properties (without transport)
|
|
27
16
|
* @throws {OutOfRangeError} if T is outside [T_MIN, T_MAX]
|
|
28
17
|
* @throws {IF97Error} if T is within the critical exclusion band
|
|
18
|
+
* @throws {AmbiguousStateError} if multiple physical states match T and h
|
|
29
19
|
*/
|
|
30
20
|
export function solveTH(T, h) {
|
|
31
21
|
assertFiniteNumber('Temperature', T);
|
|
@@ -33,66 +23,15 @@ export function solveTH(T, h) {
|
|
|
33
23
|
if (T < C.T_MIN || T > C.T_MAX) {
|
|
34
24
|
throw new OutOfRangeError('Temperature', T, C.T_MIN, C.T_MAX);
|
|
35
25
|
}
|
|
36
|
-
if (h
|
|
26
|
+
if (!withinClosedLowerBound(h, C.H_MIN, 'enthalpy') || h > C.H_MAX) {
|
|
37
27
|
throw new OutOfRangeError('Enthalpy', h, C.H_MIN, C.H_MAX);
|
|
38
28
|
}
|
|
39
29
|
if (Math.abs(T - C.Tc) < C.CRITICAL_T_EXCLUSION_BAND) {
|
|
40
30
|
throw new IF97Error(`solveTH does not support temperatures within ${C.CRITICAL_T_EXCLUSION_BAND} K of the critical-point temperature Tc=${C.Tc} K; received T=${T} K`);
|
|
41
31
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return validateBackwardState(solveFixedTemperaturePressure(region1, T, h, saturationPressure(T), C.P_MAX, 'enthalpy', 'solveTH'), [
|
|
46
|
-
{ label: 'temperature', expected: T },
|
|
47
|
-
{ label: 'enthalpy', expected: h },
|
|
48
|
-
], {
|
|
49
|
-
solverName: 'solveTH',
|
|
50
|
-
expectedRegion: Region.Region1,
|
|
51
|
-
});
|
|
52
|
-
case Region.Region2: {
|
|
53
|
-
const upper = T <= C.Tc
|
|
54
|
-
? saturationPressure(T)
|
|
55
|
-
: T <= C.B23_T_MAX
|
|
56
|
-
? boundary23_T_to_P(T)
|
|
57
|
-
: C.P_MAX;
|
|
58
|
-
return validateBackwardState(solveFixedTemperaturePressure(region2, T, h, C.P_MIN, upper, 'enthalpy', 'solveTH'), [
|
|
59
|
-
{ label: 'temperature', expected: T },
|
|
60
|
-
{ label: 'enthalpy', expected: h },
|
|
61
|
-
], {
|
|
62
|
-
solverName: 'solveTH',
|
|
63
|
-
expectedRegion: Region.Region2,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
case Region.Region3: {
|
|
67
|
-
const lower = T <= C.Tc
|
|
68
|
-
? saturationPressure(T)
|
|
69
|
-
: boundary23_T_to_P(T);
|
|
70
|
-
return validateBackwardState(solveFixedTemperaturePressure(solveRegion3PTBasic, T, h, lower, C.P_MAX, 'enthalpy', 'solveTH'), [
|
|
71
|
-
{ label: 'temperature', expected: T },
|
|
72
|
-
{ label: 'enthalpy', expected: h },
|
|
73
|
-
], {
|
|
74
|
-
solverName: 'solveTH',
|
|
75
|
-
expectedRegion: Region.Region3,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
case Region.Region4: {
|
|
79
|
-
const endpoints = saturationEndpointsAtTemperature(T);
|
|
80
|
-
assertRegion4StateAllowed(endpoints.pressure, endpoints.temperature, 'solveTH');
|
|
81
|
-
const x = qualityFromSaturationProperty(endpoints.liquid.enthalpy, endpoints.vapor.enthalpy, h);
|
|
82
|
-
return validateBackwardState(mixSaturationState(endpoints, x), [
|
|
83
|
-
{ label: 'temperature', expected: T },
|
|
84
|
-
{ label: 'enthalpy', expected: h },
|
|
85
|
-
], { solverName: 'solveTH', expectedRegion: Region.Region4 });
|
|
86
|
-
}
|
|
87
|
-
case Region.Region5:
|
|
88
|
-
return validateBackwardState(solveFixedTemperaturePressure(region5, T, h, C.P_MIN, C.R5_P_MAX, 'enthalpy', 'solveTH'), [
|
|
89
|
-
{ label: 'temperature', expected: T },
|
|
90
|
-
{ label: 'enthalpy', expected: h },
|
|
91
|
-
], {
|
|
92
|
-
solverName: 'solveTH',
|
|
93
|
-
expectedRegion: Region.Region5,
|
|
94
|
-
});
|
|
95
|
-
default:
|
|
96
|
-
throw new IF97Error(`solveTH cannot determine a valid IF97 state for T=${T} K, h=${h} kJ/kg`);
|
|
32
|
+
const state = uniqueFixedTemperatureState(T, h, 'enthalpy');
|
|
33
|
+
if (state === null) {
|
|
34
|
+
throw new IF97Error(`solveTH cannot determine a valid IF97 state for T=${T} K, h=${h} kJ/kg`);
|
|
97
35
|
}
|
|
36
|
+
return state;
|
|
98
37
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type BackwardToleranceLabel = 'pressure' | 'temperature' | 'enthalpy' | 'entropy';
|
|
2
2
|
export declare const BACKWARD_REGION4_QUALITY_TOLERANCE = 0.000001;
|
|
3
|
+
export declare const BACKWARD_HS_NEWTON_TOLERANCE = 1e-12;
|
|
4
|
+
export declare const BACKWARD_PRESSURE_PROPERTY_TEMPERATURE_TOLERANCE = 1e-13;
|
|
5
|
+
export declare function fixedTemperatureRootTolerance(property: 'enthalpy' | 'entropy'): number;
|
|
3
6
|
export declare function backwardConstraintTolerance(label: BackwardToleranceLabel, expected: number): number;
|
|
4
7
|
export declare function region4SaturationPressureTolerance(pressure: number): number;
|
|
5
8
|
export declare function backwardSpecificVolumeTolerance(expected: number): number;
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
export const BACKWARD_REGION4_QUALITY_TOLERANCE = 1e-6;
|
|
2
|
+
export const BACKWARD_HS_NEWTON_TOLERANCE = 1e-12;
|
|
3
|
+
export const BACKWARD_PRESSURE_PROPERTY_TEMPERATURE_TOLERANCE = 1e-13;
|
|
4
|
+
export function fixedTemperatureRootTolerance(property) {
|
|
5
|
+
return property === 'enthalpy' ? 1e-9 : 1e-12;
|
|
6
|
+
}
|
|
2
7
|
export function backwardConstraintTolerance(label, expected) {
|
|
3
8
|
switch (label) {
|
|
4
9
|
case 'pressure':
|
|
10
|
+
return 1e-9 * Math.abs(expected);
|
|
5
11
|
case 'temperature':
|
|
6
12
|
return 1e-9 * Math.max(1, Math.abs(expected));
|
|
7
13
|
case 'enthalpy':
|
|
8
|
-
return 1e-
|
|
14
|
+
return 1e-9 * Math.max(1, Math.abs(expected));
|
|
9
15
|
case 'entropy':
|
|
10
|
-
return 1e-
|
|
16
|
+
return 1e-9 * Math.max(1, Math.abs(expected));
|
|
11
17
|
default:
|
|
12
18
|
return 1e-9;
|
|
13
19
|
}
|
|
14
20
|
}
|
|
15
21
|
export function region4SaturationPressureTolerance(pressure) {
|
|
16
|
-
return 1e-8 * Math.
|
|
22
|
+
return 1e-8 * Math.abs(pressure);
|
|
17
23
|
}
|
|
18
24
|
export function backwardSpecificVolumeTolerance(expected) {
|
|
19
25
|
return 1e-6 * Math.max(1, Math.abs(expected));
|
package/dist/backward/ts.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Backward equation T,S → state.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* and solves for pressure using a bracketed Newton method, then returns
|
|
6
|
-
* the full set of basic thermodynamic properties.
|
|
4
|
+
* Returns a state only when temperature and entropy determine it uniquely.
|
|
7
5
|
*/
|
|
8
6
|
import type { BasicProperties } from '../types.js';
|
|
9
7
|
/**
|
|
10
8
|
* Solve for full basic thermodynamic state from temperature and entropy.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
* within the appropriate bracket for that region.
|
|
10
|
+
* Searches every valid isotherm branch and rejects ambiguous inputs.
|
|
14
11
|
*
|
|
15
12
|
* Temperatures within ±CRITICAL_T_EXCLUSION_BAND of Tc are rejected because
|
|
16
13
|
* the entropy surface is nearly flat there, making pressure recovery unreliable.
|
|
@@ -20,5 +17,6 @@ import type { BasicProperties } from '../types.js';
|
|
|
20
17
|
* @returns Basic thermodynamic properties (without transport)
|
|
21
18
|
* @throws {OutOfRangeError} if T is outside [T_MIN, T_MAX]
|
|
22
19
|
* @throws {IF97Error} if T is within the critical exclusion band
|
|
20
|
+
* @throws {AmbiguousStateError} if multiple physical states match T and s
|
|
23
21
|
*/
|
|
24
22
|
export declare function solveTS(T: number, s: number): BasicProperties;
|
package/dist/backward/ts.js
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import * as C from '../constants.js';
|
|
2
|
-
import { IF97Error, OutOfRangeError
|
|
3
|
-
import { assertFiniteNumber } from '../core/input-validation.js';
|
|
4
|
-
import {
|
|
5
|
-
import { solveRegion3PTBasic } from '../core/region3-pt.js';
|
|
6
|
-
import { region1 } from '../regions/region1.js';
|
|
7
|
-
import { region2 } from '../regions/region2.js';
|
|
8
|
-
import { region5 } from '../regions/region5.js';
|
|
9
|
-
import { boundary23_T_to_P } from '../regions/boundaries.js';
|
|
10
|
-
import { saturationPressure } from '../regions/region4.js';
|
|
11
|
-
import { mixSaturationState, qualityFromSaturationProperty, saturationEndpointsAtTemperature, } from '../saturation/common.js';
|
|
12
|
-
import { assertRegion4StateAllowed } from '../saturation/region4-boundaries.js';
|
|
13
|
-
import { solveFixedTemperaturePressure } from './fixed-temperature-solver.js';
|
|
14
|
-
import { validateBackwardState } from './solution-validation.js';
|
|
2
|
+
import { IF97Error, OutOfRangeError } from '../types.js';
|
|
3
|
+
import { assertFiniteNumber, withinClosedLowerBound } from '../core/input-validation.js';
|
|
4
|
+
import { uniqueFixedTemperatureState } from './fixed-temperature-candidates.js';
|
|
15
5
|
/**
|
|
16
6
|
* Solve for full basic thermodynamic state from temperature and entropy.
|
|
17
7
|
*
|
|
18
|
-
*
|
|
19
|
-
* within the appropriate bracket for that region.
|
|
8
|
+
* Searches every valid isotherm branch and rejects ambiguous inputs.
|
|
20
9
|
*
|
|
21
10
|
* Temperatures within ±CRITICAL_T_EXCLUSION_BAND of Tc are rejected because
|
|
22
11
|
* the entropy surface is nearly flat there, making pressure recovery unreliable.
|
|
@@ -26,6 +15,7 @@ import { validateBackwardState } from './solution-validation.js';
|
|
|
26
15
|
* @returns Basic thermodynamic properties (without transport)
|
|
27
16
|
* @throws {OutOfRangeError} if T is outside [T_MIN, T_MAX]
|
|
28
17
|
* @throws {IF97Error} if T is within the critical exclusion band
|
|
18
|
+
* @throws {AmbiguousStateError} if multiple physical states match T and s
|
|
29
19
|
*/
|
|
30
20
|
export function solveTS(T, s) {
|
|
31
21
|
assertFiniteNumber('Temperature', T);
|
|
@@ -33,66 +23,15 @@ export function solveTS(T, s) {
|
|
|
33
23
|
if (T < C.T_MIN || T > C.T_MAX) {
|
|
34
24
|
throw new OutOfRangeError('Temperature', T, C.T_MIN, C.T_MAX);
|
|
35
25
|
}
|
|
36
|
-
if (s
|
|
26
|
+
if (!withinClosedLowerBound(s, C.S_MIN, 'entropy') || s > C.S_MAX) {
|
|
37
27
|
throw new OutOfRangeError('Entropy', s, C.S_MIN, C.S_MAX);
|
|
38
28
|
}
|
|
39
29
|
if (Math.abs(T - C.Tc) < C.CRITICAL_T_EXCLUSION_BAND) {
|
|
40
30
|
throw new IF97Error(`solveTS does not support temperatures within ${C.CRITICAL_T_EXCLUSION_BAND} K of the critical-point temperature Tc=${C.Tc} K; received T=${T} K`);
|
|
41
31
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return validateBackwardState(solveFixedTemperaturePressure(region1, T, s, saturationPressure(T), C.P_MAX, 'entropy', 'solveTS'), [
|
|
46
|
-
{ label: 'temperature', expected: T },
|
|
47
|
-
{ label: 'entropy', expected: s },
|
|
48
|
-
], {
|
|
49
|
-
solverName: 'solveTS',
|
|
50
|
-
expectedRegion: Region.Region1,
|
|
51
|
-
});
|
|
52
|
-
case Region.Region2: {
|
|
53
|
-
const upper = T <= C.Tc
|
|
54
|
-
? saturationPressure(T)
|
|
55
|
-
: T <= C.B23_T_MAX
|
|
56
|
-
? boundary23_T_to_P(T)
|
|
57
|
-
: C.P_MAX;
|
|
58
|
-
return validateBackwardState(solveFixedTemperaturePressure(region2, T, s, C.P_MIN, upper, 'entropy', 'solveTS'), [
|
|
59
|
-
{ label: 'temperature', expected: T },
|
|
60
|
-
{ label: 'entropy', expected: s },
|
|
61
|
-
], {
|
|
62
|
-
solverName: 'solveTS',
|
|
63
|
-
expectedRegion: Region.Region2,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
case Region.Region3: {
|
|
67
|
-
const lower = T <= C.Tc
|
|
68
|
-
? saturationPressure(T)
|
|
69
|
-
: boundary23_T_to_P(T);
|
|
70
|
-
return validateBackwardState(solveFixedTemperaturePressure(solveRegion3PTBasic, T, s, lower, C.P_MAX, 'entropy', 'solveTS'), [
|
|
71
|
-
{ label: 'temperature', expected: T },
|
|
72
|
-
{ label: 'entropy', expected: s },
|
|
73
|
-
], {
|
|
74
|
-
solverName: 'solveTS',
|
|
75
|
-
expectedRegion: Region.Region3,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
case Region.Region4: {
|
|
79
|
-
const endpoints = saturationEndpointsAtTemperature(T);
|
|
80
|
-
assertRegion4StateAllowed(endpoints.pressure, endpoints.temperature, 'solveTS');
|
|
81
|
-
const x = qualityFromSaturationProperty(endpoints.liquid.entropy, endpoints.vapor.entropy, s);
|
|
82
|
-
return validateBackwardState(mixSaturationState(endpoints, x), [
|
|
83
|
-
{ label: 'temperature', expected: T },
|
|
84
|
-
{ label: 'entropy', expected: s },
|
|
85
|
-
], { solverName: 'solveTS', expectedRegion: Region.Region4 });
|
|
86
|
-
}
|
|
87
|
-
case Region.Region5:
|
|
88
|
-
return validateBackwardState(solveFixedTemperaturePressure(region5, T, s, C.P_MIN, C.R5_P_MAX, 'entropy', 'solveTS'), [
|
|
89
|
-
{ label: 'temperature', expected: T },
|
|
90
|
-
{ label: 'entropy', expected: s },
|
|
91
|
-
], {
|
|
92
|
-
solverName: 'solveTS',
|
|
93
|
-
expectedRegion: Region.Region5,
|
|
94
|
-
});
|
|
95
|
-
default:
|
|
96
|
-
throw new IF97Error(`solveTS cannot determine a valid IF97 state for T=${T} K, s=${s} kJ/(kg·K)`);
|
|
32
|
+
const state = uniqueFixedTemperatureState(T, s, 'entropy');
|
|
33
|
+
if (state === null) {
|
|
34
|
+
throw new IF97Error(`solveTS cannot determine a valid IF97 state for T=${T} K, s=${s} kJ/(kg·K)`);
|
|
97
35
|
}
|
|
36
|
+
return state;
|
|
98
37
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
* Physical constants, critical point properties, and range limits
|
|
5
5
|
* for the IAPWS Industrial Formulation 1997.
|
|
6
6
|
*/
|
|
7
|
-
|
|
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`. */
|
|
11
|
-
export declare const R = 0.461526;
|
|
7
|
+
export { R } from './gas-constant.js';
|
|
12
8
|
/** Specific gas constant used by the IAPWS 2011 thermal conductivity λ₂ correlation [kJ/(kg·K)].
|
|
13
9
|
* Intentionally differs from IF97's `R`; keep both constants distinct and named. */
|
|
14
10
|
export declare const R_IAPWS_2011_THERMAL = 0.46151805;
|
|
@@ -16,13 +12,13 @@ export declare const R_IAPWS_2011_THERMAL = 0.46151805;
|
|
|
16
12
|
export declare const Tc = 647.096;
|
|
17
13
|
/** Temperature exclusion half-band around the critical point [K] */
|
|
18
14
|
export declare const CRITICAL_T_EXCLUSION_BAND = 0.001;
|
|
19
|
-
/** Region 4
|
|
15
|
+
/** Region 4 tolerance for outward temperature-boundary roundoff [K] */
|
|
20
16
|
export declare const REGION4_TEMPERATURE_TOLERANCE = 1e-9;
|
|
21
17
|
/** Safety margin used when clamping Region 4 temperatures below Tc [K] */
|
|
22
18
|
export declare const REGION4_SUBCRITICAL_TEMPERATURE_MARGIN = 1e-8;
|
|
23
19
|
/** Critical pressure [MPa] */
|
|
24
20
|
export declare const Pc = 22.064;
|
|
25
|
-
/**
|
|
21
|
+
/** Relative tolerance for outward Region 4 pressure-boundary roundoff */
|
|
26
22
|
export declare const REGION4_PRESSURE_TOLERANCE = 1e-9;
|
|
27
23
|
/** Tolerance for identifying the critical h-s endpoint in Region 4 */
|
|
28
24
|
export declare const REGION4_CRITICAL_HS_TOLERANCE = 0.000001;
|
|
@@ -44,14 +40,14 @@ export declare const P_MAX = 100;
|
|
|
44
40
|
export declare const T_MIN = 273.15;
|
|
45
41
|
/** Maximum temperature [K] (2273.15 K = 2000 °C) */
|
|
46
42
|
export declare const T_MAX = 2273.15;
|
|
47
|
-
/** Minimum specific entropy [kJ/(kg·K)] */
|
|
48
|
-
export declare const S_MIN = -0.
|
|
49
|
-
/** Maximum specific entropy [kJ/(kg·K)] */
|
|
50
|
-
export declare const S_MAX
|
|
51
|
-
/** Minimum specific enthalpy [kJ/kg] */
|
|
52
|
-
export declare const H_MIN = -0.
|
|
53
|
-
/** Maximum specific enthalpy [kJ/kg] */
|
|
54
|
-
export declare const H_MAX
|
|
43
|
+
/** Minimum specific entropy [kJ/(kg·K)], at T_MIN and P_MAX in Region 1. */
|
|
44
|
+
export declare const S_MIN = -0.00858228709261889;
|
|
45
|
+
/** Maximum specific entropy [kJ/(kg·K)], at T_MAX and P_MIN in Region 5. */
|
|
46
|
+
export declare const S_MAX: number;
|
|
47
|
+
/** Minimum specific enthalpy [kJ/kg], at T_MIN and P_MIN in Region 1. */
|
|
48
|
+
export declare const H_MIN = -0.041587825987655716;
|
|
49
|
+
/** Maximum specific enthalpy [kJ/kg], at T_MAX and P_MIN in Region 5. */
|
|
50
|
+
export declare const H_MAX: number;
|
|
55
51
|
/** Region 2 minimum temperature for high-P subregions [K] (623.15 K = 350 °C) */
|
|
56
52
|
export declare const R2_T_MIN = 623.15;
|
|
57
53
|
/** Region 2 maximum temperature for backward eqs [K] (1073.15 K = 800 °C) */
|