44import com .rae .formicapi .FormicAPI ;
55import com .rae .formicapi .math .Solvers ;
66import net .createmod .catnip .data .Couple ;
7- import net .minecraft .util .Mth ;
87import org .jetbrains .annotations .NotNull ;
98
109import 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