Skip to content

Commit 509b11a

Browse files
adding a better water model
1 parent 0847d94 commit 509b11a

File tree

7 files changed

+320
-163
lines changed

7 files changed

+320
-163
lines changed

src/main/java/com/rae/formicapi/thermal_utilities/PengRobinsonEOS.java

Lines changed: 101 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.rae.formicapi.FormicAPI;
55
import com.rae.formicapi.math.Solvers;
66
import net.createmod.catnip.data.Couple;
7-
import net.minecraft.util.Mth;
87
import org.jetbrains.annotations.NotNull;
98

109
import java.util.ArrayList;
@@ -37,6 +36,15 @@ public double alpha(double T) {
3736
return Math.pow(1 + kappa * (1 - Math.sqrt(Tr)), 2);
3837
}
3938

39+
private double dAlpha_dT(double T) {
40+
double sqrtTr = Math.sqrt(T / Tc);
41+
return -kappa * (1 + kappa * (1 - sqrtTr)) / (Tc * sqrtTr);
42+
}
43+
44+
private double da_dT(double T) {
45+
return a0 * dAlpha_dT(T); // ac is the "a" at critical temperature
46+
}
47+
4048
public double a(double T) {
4149
return a0 * alpha(T);
4250
}
@@ -80,12 +88,14 @@ public double volumeMolar(double T, double P, double vaporFraction) {
8088

8189
/**
8290
* What does this represent exactly ?
83-
* @param T the temperature in Kelvin
84-
* @param P the pressure in Pascals
85-
* @return the possible compressibility coef for the given isotherm and Pressure : minimal is the value at the saturation curve for liquid
86-
* the middle on is the normal on that is non physical, and the max is for the vapor
91+
* @param T the temperature in Kelvin, needs to be strictly positive
92+
* @param P the pressure in Pascals, needs to be strictly positive
93+
* @return the possible compressibility coefficient for the given isotherm and Pressure : minimal is the value at the saturation curve for liquid
94+
* the middle is non-physical, and the max is for the vapor
8795
*/
8896
public double @NotNull [] getZFactors(double T, double P) {
97+
assert T > 0;
98+
assert P > 0;
8999
double A = a(T) * P / (R * R * T * T);
90100
double B = b * P / (R * T);
91101

@@ -101,6 +111,7 @@ public double volumeMolar(double T, double P, double vaporFraction) {
101111
.sorted()
102112
.toArray();
103113
}
114+
//not robust at low pressure
104115
public double fugacityCoefficient(double T, double P, double Z) {
105116
double R = PengRobinsonEOS.R;
106117
double a = a(T); // temperature-dependent 'a'
@@ -114,6 +125,8 @@ public double fugacityCoefficient(double T, double P, double Z) {
114125
double term3 = term2 * Math.log(logArgument);
115126

116127
double lnPhi = term1 - term3;
128+
129+
System.out.println("fug : "+Math.exp(lnPhi));
117130
return Math.exp(lnPhi);
118131
}
119132

@@ -141,7 +154,7 @@ public List<Double> findSpinodalPoints(double T, double vMin, double vMax) {
141154
return spinodals;
142155
}
143156

144-
public double computeCoexistencePressure(double T) {
157+
public double computeSaturationPressure(double T) {
145158
if (T > Tc) {
146159
throw new IllegalArgumentException("T must be lower than the critical temperature to compute a coexistence pressure");
147160
}
@@ -157,67 +170,92 @@ public double computeCoexistencePressure(double T) {
157170
assert spinodals.size() == 2;
158171

159172
double pMin = Math.max(1e-3, pressure(T, spinodals.get(0))); // lower pressure bound
160-
double pMax = pressure(T, spinodals.get(1)); // upper pressure bound
173+
double pMax = pressure(T, spinodals.get(1)); // upper pressure bound
161174

162175
// Fugacity difference function
163-
Function<Float, Float> fugacityDifference = (Float PGuess) -> {
164-
double P = PGuess;
165-
if (P <= 0) return Float.NaN;
166-
167-
double[] ZRoots = getZFactors(T, P);
168-
if (ZRoots.length < 2) return Float.NaN; // Need at least two roots for coexistence
169-
170-
double Zl = Arrays.stream(ZRoots).min().getAsDouble();
171-
double Zv = Arrays.stream(ZRoots).max().getAsDouble();
172-
173-
double phiL = fugacityCoefficient(T, P, Zl);
174-
double phiV = fugacityCoefficient(T, P, Zv);
175-
if (phiL <= 0 || phiV <= 0 || Double.isNaN(phiL) || Double.isNaN(phiV)) return Float.NaN;
176+
Function<Float, Float> areaDifferenceFunction = (Float Guess) -> {
177+
double P = Guess;
178+
double[] roots = getZFactors(T, P);
179+
if (roots.length < 2) return Float.MAX_VALUE;
180+
181+
double Zl = Arrays.stream(roots).min().getAsDouble();
182+
double Zv = Arrays.stream(roots).max().getAsDouble();
183+
double Vl = Zl * PengRobinsonEOS.R * T / P;
184+
double Vv = Zv * PengRobinsonEOS.R * T / P;
185+
if (Math.abs(Vv - Vl) < 1e-9) return Float.MAX_VALUE;
186+
187+
int n = 1000;
188+
double dV = (Vv - Vl) / n;
189+
double area = 0;
190+
for (int i = 0; i < n; i++) {
191+
double V1 = Vl + i * dV;
192+
double V2 = V1 + dV;
193+
double p1 = pressure(T, V1);
194+
double p2 = pressure(T, V2);
195+
area += 0.5 * (p1 + p2) * dV;
196+
}
176197

177-
double diff = Math.abs(Math.log(phiL) - Math.log(phiV));
178-
return (float) diff;
198+
double rect = P * (Vv - Vl);
199+
return (float)Math.abs(area - rect);
200+
};
201+
Function<Float, Float> fugacityDifference = (Float P) -> {
202+
try {
203+
double[] Zroots = getZFactors(T, P);
204+
if (Zroots.length < 2) return Float.MAX_VALUE;
205+
206+
double Zl = Arrays.stream(Zroots).min().getAsDouble();
207+
double Zv = Arrays.stream(Zroots).max().getAsDouble();
208+
209+
double phiL = fugacityCoefficient(Zl, T, P);
210+
double phiV = fugacityCoefficient(Zv, T, P);
211+
return (float) Math.abs(Math.log(phiV) - Math.log(phiL));
212+
} catch (Exception e) {
213+
return Float.MAX_VALUE;
214+
}
179215
};
180216

181-
float PCoexistence = Solvers.dichotomy(fugacityDifference, (float) pMin, (float) pMax, 1e-3f);
182-
183-
if (Float.isNaN(PCoexistence)) {
184-
throw new RuntimeException("Dichotomy failed: no valid fugacity-based root found.");
185-
}
186-
187-
return PCoexistence;
217+
return Solvers.gradientDecent(areaDifferenceFunction, (float)Math.max(1e-3,(pMin+pMax)/2), 10,1e-3f,1e-4f);
188218
}
189219

190220
/**
191-
*
221+
* Waarning only use this if you know that the fuild is in the LV phase.
192222
* @param T temperature
193223
* @return return the couple Vl, Vv or the intermediate V if it's super critical.
194224
*/
225+
@Deprecated
195226
public Couple<Double> getSaturationVolumes(double T) {
196227
try {
197-
double Pcoex = computeCoexistencePressure(T); // try normal coexistence
198-
double Vl = volumeMolar(T, Pcoex, 0.0); // saturated liquid
199-
double Vv = volumeMolar(T, Pcoex, 1.0); // saturated vapor
200-
return Couple.create(Vl, Vv);
228+
if (T < Tc ) {
229+
230+
double PSat = computeSaturationPressure(T); // try normal coexistence
231+
double Vl = volumeMolar(T, PSat, 0.0); // saturated liquid
232+
double Vv = volumeMolar(T, PSat, 1.0); // saturated vapor
233+
return Couple.create(Vl, Vv);
234+
} else {
235+
double[] critRoot = getZFactors(Tc, Pc);
236+
237+
// Start guess: approximate ideal gas volume
238+
double Z = Arrays.stream(critRoot).max().getAsDouble();
239+
//we need to find a better interpolation function.
240+
double VmInflection = Z * R * Tc / Pc;
241+
return Couple.create(VmInflection, VmInflection);
242+
}
201243
} catch (RuntimeException e) {
202-
FormicAPI.LOGGER.debug("found outside LV phase {}", T);
244+
FormicAPI.LOGGER.debug("Unexpectedly found outside LV phase {}", T);
203245
FormicAPI.LOGGER.debug(e);
204246
// Fall back to inflection point
205-
double[] critRoot = getZFactors(Tc, Pc);
206-
207-
// Start guess: approximate ideal gas volume
208-
double Z = Arrays.stream(critRoot).max().getAsDouble();
209-
//we need to find a better interpolation function.
210-
double VmInflection = Z * R * Tc / Pc;
211-
return Couple.create(VmInflection, VmInflection);
247+
return Couple.create(Double.NaN, Double.NaN);
212248
}
213249
}
214250

215-
public double idealGasEntropy(double T) {
216-
// Ideal gas: s = ∫(Cv/T)dT + R ln(P_ref/P)
217-
// Approximate Cv/R = constant for now, e.g. 3.5 for a monatomic, ~2.0 for H2O vapor
218-
double Cv = 2.0 * R;
219-
return Cv * Math.log(T); // Ignore pressure-dependent term (constant reference)
251+
public double idealGasEntropy(double T, double P) {
252+
double Cp = 3.5 * R;
253+
double Pref = 101325.0;
254+
double Tref = 298.15;
255+
256+
return Cp * Math.log(T / Tref) - R * Math.log(P / Pref);
220257
}
258+
221259
public double entropyDeparturePR(double T, double Vm) {
222260
double a = a(T); // temperature-dependent 'a' from PR EOS
223261
double sqrt2 = Math.sqrt(2);
@@ -230,8 +268,7 @@ public double entropyDeparturePR(double T, double Vm) {
230268
}
231269
public double totalEntropy(double T, double Vm) {
232270
// Ideal gas entropy (reference + integration)
233-
double sIdeal = idealGasEntropy(T) - idealGasEntropy(298.15); // reference s = 0 at 298.15 K
234-
271+
double sIdeal = idealGasEntropy(T, pressure(T, Vm));
235272
// Entropy departure from Peng-Robinson EOS
236273
double sDeparture = entropyDeparturePR(T, Vm);
237274

@@ -246,30 +283,36 @@ public double totalEntropy(double T, double Vm) {
246283
*/
247284
private double idealGasEnthalpy(double T) {
248285
double Cp = 75.0; // J/mol·K
249-
return Cp * T;
286+
return Cp * (T - 273.15);
250287
}
251288

252289
/**
253290
*
254291
* @param T temperature
255292
* @param Vm specific volume
256-
* @return residual Enthalpy
293+
* @return the residual enthalpy (H^res) at given temperature and molar volume.
294+
* Units: J/mol
257295
*/
258296
private double residualEnthalpy(double T, double Vm) {
259-
double a = a(T);
297+
double a = a(T); // attraction parameter at T
298+
double da_dT = da_dT(T); // temperature derivative of a
260299
double P = pressure(T, Vm);
261300
double Z = P * Vm / (R * T);
262301

263-
double logTerm = Math.log((Vm + (1 + Math.sqrt(2)) * b) / (Vm + (1 - Math.sqrt(2)) * b));
264-
double hRes = T * (Z - 1) * R - a / (b * Math.sqrt(8)) * logTerm;
265-
if (Double.isNaN(hRes)) {
266-
System.out.println("log therm is NaN for T "+T + " and Vm "+Vm);
267-
}
302+
// PR-specific constants
303+
double sqrt2 = Math.sqrt(2);
304+
double B = b;
305+
double logTerm = Math.log((Vm + (1 + sqrt2) * B) / (Vm + (1 - sqrt2) * B));
306+
307+
// Correct residual enthalpy formula
308+
double hRes = R * T * (Z - 1) + (T * da_dT - a) / (B * sqrt2) * logTerm;
309+
268310
return hRes;
269311
}
270312

313+
271314
public double totalEnthalpy(double T, double Vm) {
272-
return idealGasEnthalpy(T) + residualEnthalpy(T, Vm);
315+
return idealGasEnthalpy(Math.max(T,1)) + residualEnthalpy(Math.max(T,1), Math.max(Vm,b));
273316
}
274317

275318
public double getB() {

0 commit comments

Comments
 (0)