/[ascend]/trunk/models/johnpye/fprops/pengrob.c
ViewVC logotype

Contents of /trunk/models/johnpye/fprops/pengrob.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2664 - (show annotations) (download) (as text)
Fri Jan 18 06:02:15 2013 UTC (10 years, 8 months ago) by jpye
File MIME type: text/x-csrc
File size: 23752 byte(s)
Trying to debug fprops_triple_point for Toluene with pengrob correlation. Something strange is happening with fratio.
1 /* ASCEND modelling environment
2 Copyright (C) 2011-2012 Carnegie Mellon University
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *//** @file
17 Implementation of the Peng-Robinson equation of state.
18
19 For nomenclature see Sandler, 5e and IAPWS-95 and IAPWS Derivatives.
20
21 Authors: John Pye, Richard Towers, Sean Muratet
22 */
23
24 #include "pengrob.h"
25 #include "rundata.h"
26 #include "cubicroots.h"
27 #include "fprops.h"
28 #include "sat.h"
29 #include "ideal_impl.h"
30 #include "cp0.h"
31 #include "zeroin.h"
32 #include <math.h>
33 #include <stdio.h>
34
35 #include "helmholtz.h" // for helmholtz_prepare
36
37 /* these are the 'raw' functions, they don't do phase equilibrium. */
38 PropEvalFn pengrob_p;
39 PropEvalFn pengrob_u;
40 PropEvalFn pengrob_h;
41 PropEvalFn pengrob_s;
42 PropEvalFn pengrob_a;
43 PropEvalFn pengrob_g;
44 PropEvalFn pengrob_cp;
45 PropEvalFn pengrob_cv;
46 PropEvalFn pengrob_w;
47 PropEvalFn pengrob_dpdrho_T;
48 PropEvalFn pengrob_alphap;
49 PropEvalFn pengrob_betap;
50 SatEvalFn pengrob_sat;
51 //SatEvalFn pengrob_sat_akasaka;
52
53 static double MidpointPressureCubic(double T, const FluidData *data, FpropsError *err);
54
55 #define PR_DEBUG
56 #define PR_ERRORS
57
58 #ifdef PR_DEBUG
59 # include "color.h"
60 # define MSG(FMT, ...) \
61 color_on(stderr,ASC_FG_BRIGHTRED);\
62 fprintf(stderr,"%s:%d: ",__FILE__,__LINE__);\
63 color_on(stderr,ASC_FG_BRIGHTBLUE);\
64 fprintf(stderr,"%s: ",__func__);\
65 color_off(stderr);\
66 fprintf(stderr,FMT "\n",##__VA_ARGS__)
67 #else
68 # define MSG(ARGS...) ((void)0)
69 #endif
70
71 /* TODO centralise declaration of our error-reporting function somehow...? */
72 #ifdef PR_ERRORS
73 # include "color.h"
74 # define ERRMSG(STR,...) \
75 color_on(stderr,ASC_FG_BRIGHTRED);\
76 fprintf(stderr,"ERROR:");\
77 color_off(stderr);\
78 fprintf(stderr," %s:%d:" STR "\n", __func__, __LINE__ ,##__VA_ARGS__)
79 #else
80 # define ERRMSG(ARGS...) ((void)0)
81 #endif
82
83
84 PureFluid *pengrob_prepare(const EosData *E, const ReferenceState *ref){
85 MSG("Preparing PR fluid...");
86 PureFluid *P = FPROPS_NEW(PureFluid);
87 P->data = FPROPS_NEW(FluidData);
88
89 /* metadata */
90 P->name = E->name;
91 P->source = E->source;
92 P->type = FPROPS_PENGROB;
93
94 #define D P->data
95 /* common data across all correlation types */
96 switch(E->type){
97 case FPROPS_HELMHOLTZ:
98 #define I E->data.helm
99 D->M = I->M;
100 D->R = I->R;
101 D->T_t = I->T_t;
102 D->T_c = I->T_c;
103 D->rho_c = I->rho_c;
104 D->omega = I->omega;
105 D->cp0 = cp0_prepare(I->ideal, D->R, D->T_c);
106
107 // helmholtz data doesn't include p_c so we need to calculate it somehow...
108
109 #ifdef USE_HELMHOLTZ_RHO_C
110 // use Zc = 0.307 to calculate p_c from the T_c and rho_c in the
111 // helmholtz data. This seems to be a bad choice
112 // since eg for CO2 it gives a significantly overestimated p_c.
113 {
114 double Zc = 0.307;
115 D->p_c = Zc * D->R * D->T_c * D->rho_c;
116 MSG("p_c = %f", D->p_c);
117 }
118 #else // USE HELMHOLTZ P_C
119 // Probably the preferred alternative in this case is to
120 // use rho_c and T_c to calculated helmholtz_p(T_c, rho_c), then
121 // use that pressure as the PR p_c, together with updating the
122 // value of rho_c for consistency with PR EOS (from the known
123 // Z_c = 0.307.
124 {
125 FpropsError herr = FPROPS_NO_ERROR;
126 MSG("Preparing helmholtz data '%s'...",E->name);
127 PureFluid *PH = helmholtz_prepare(E,ref);
128 if(!PH){
129 ERRMSG("Failed to create Helmholtz runtime data");
130 return NULL;
131 }
132 D->p_c = PH->p_fn(D->T_c, D->rho_c, PH->data, &herr);
133 MSG("Calculated p_c = %f from Helmholtz data",D->p_c);
134 if(herr){
135 ERRMSG("Failed to calculate critical pressure (%s)",fprops_error(herr));
136 return NULL;
137 }
138 double Zc = 0.307;
139 D->rho_c = D->p_c / (Zc * D->R * D->T_c);
140 }
141 #endif
142 break;
143 #undef I
144 case FPROPS_CUBIC:
145 #define I E->data.cubic
146 D->M = I->M;
147 D->R = R_UNIVERSAL / I->M;
148 D->T_t = I->T_t;
149 D->T_c = I->T_c;
150 D->p_c = I->p_c;
151 D->rho_c = I->rho_c;
152 D->omega = I->omega;
153 D->cp0 = cp0_prepare(I->ideal, D->R, D->T_c);
154 break;
155 default:
156 fprintf(stderr,"Invalid EOS data\n");
157 return NULL;
158 }
159
160 if(D->p_c == 0){
161 ERRMSG("ERROR p_c is zero in this data, need to calculate it here somehow");
162 return NULL;
163 }
164
165 /* FIXME note that in the following paper, the constants in the PR EOS
166 are given with lots more decimal places. Need to figure out if it's
167 preferable to use those extra DPs, or not...?
168 http://www.che.uah.edu/courseware/che641/peng-robinson-derivatives-joule-thompson.pdf
169 */
170
171 /* NOTE: we're using a mass basis for all our property calculations. That
172 means that our 'b' is the usual 'b/M' since our 'R' is 'Rm/M'. Because of
173 this, our p is still OK, since Ru/M / (Vm/M - b/M) is still the same value.*/
174 #define C P->data->corr.pengrob
175 C = FPROPS_NEW(PengrobRunData);
176 C->aTc = 0.45724 * SQ(D->R * D->T_c) / D->p_c;
177 C->b = 0.07780 * D->R * D->T_c / D->p_c;
178 C->kappa = 0.37464 + (1.54226 - 0.26992 * D->omega) * D->omega;
179
180 /* function pointers... more to come still? */
181 #define FN(VAR) P->VAR##_fn = &pengrob_##VAR
182 FN(p); FN(u); FN(h); FN(s); FN(a); FN(g); FN(cp); FN(cv); FN(w);
183 FN(dpdrho_T); FN(alphap); FN(betap);
184 FN(sat);
185 #undef FN
186 #undef I
187 #undef D
188 #undef C
189 //P->sat_fn = &pengrob_sat_akasaka;
190
191 return P;
192 }
193
194
195 /* shortcuts take us straight into the correct data structure */
196 #define PD data->corr.pengrob
197 #define PD_TCRIT data->T_c
198 #define PD_RHOCRIT data->rho_c
199 #define PD_M data->M
200 #define SQRT2 1.4142135623730951
201
202 #define DEFINE_SQRTALPHA \
203 double sqrtalpha = 1 + PD->kappa * (1 - sqrt(T / PD_TCRIT));
204 #define DEFINE_A \
205 double a = PD->aTc * SQ(sqrtalpha);
206
207 #define DEFINE_V double v = 1. / rho;
208
209 /**
210 Maxima code:
211 a(T) := aTc * (1 + kappa * (1 - sqrt(T/Tc)));
212 diff(a(T),T,1);
213 XXX
214 */
215 #define DEFINE_DADT \
216 double dadT = -PD->kappa * PD->aTc * sqrtalpha / sqrt(T * PD_TCRIT)
217
218 /**
219 Maxima code:
220 a(T) := aTc * (1 + kappa * (1 - sqrt(T/Tc)));
221 diff(a(T),T,2);
222 XXX
223 */
224 #define DEFINE_D2ADT2 \
225 double d2adt2 = PD->aTc * PD->kappa * sqrt(PD_TCRIT/T) * (1 + PD->kappa) / (2 * T * PD_TCRIT);
226
227 #define DEFINE_DPDT_RHO \
228 double dpdT_rho = data->R/(v - PD->b) - dadT/(v*(v + PD->b) + PD->b*(v - PD->b))
229
230 double pengrob_p(double T, double rho, const FluidData *data, FpropsError *err){
231 DEFINE_SQRTALPHA;
232 DEFINE_A;
233 DEFINE_V;
234 double b = PD->b;
235 if(rho > 1./b){
236 /* TODO check this: is it because it gives p < 0? */
237 MSG("Density exceeds limit value 1/b = %f",1./b);
238 *err = FPROPS_RANGE_ERROR;
239 }
240 //MSG("v = %f, b = %f",v,b);
241 double p = (data->R * T)/(v - b) - a/(v*(v + b) + b*(v - b));
242 //MSG("p(T = %f, rho = %f) = %f",T,rho,p);
243 return p;
244 }
245
246
247 double pengrob_h(double T, double rho, const FluidData *data, FpropsError *err){
248 DEFINE_SQRTALPHA;
249 DEFINE_A;
250 DEFINE_V;
251 if(rho > 1./PD->b){
252 MSG("Density exceeds limit value 1/b = %f",1./PD->b);
253 *err = FPROPS_RANGE_ERROR;
254 return 0;
255 }
256 double h0 = ideal_h(T,rho,data,err);
257 double p = pengrob_p(T, rho, data, err);
258 double Z = p * v / (data->R * T);
259 double B = p * PD->b / (data->R * T);
260 DEFINE_DADT;
261 double hr = data->R * T * (Z - 1) + (T*dadT - a)/(2*SQRT2 * PD->b) * log((Z + (1+SQRT2)*B) / (Z + (1-SQRT2)*B));
262 return h0 + hr;
263 }
264
265
266 double pengrob_s(double T, double rho, const FluidData *data, FpropsError *err){
267 DEFINE_SQRTALPHA;
268 DEFINE_V;
269 double b = PD->b;
270 if(rho > 1./b){
271 MSG("Density exceeds limit value 1/b = %f",1./b);
272 *err = FPROPS_RANGE_ERROR;
273 return 0;
274 }
275 double s0 = ideal_s(T,rho,data,err);
276 double p = pengrob_p(T, rho, data, err);
277 double Z = p * v / (data->R * T);
278 double B = p * b / (data->R * T);
279 DEFINE_DADT;
280 //MSG("s0 = %f, p = %f, Z = %f, B = %f, dadt = %f", s0, p, Z, B, dadt);
281 //MSG("log(Z-B) = %f, log((Z+(1+SQRT2)*B)/(Z+(1-SQRT2)*B = %f", log(Z-B), log((Z+(1+SQRT2)*B)/(Z+(1-SQRT2)*B)));
282 double sr = data->R * log(Z-B) + dadT/(2*SQRT2*b) * log((Z+(1+SQRT2)*B)/(Z+(1-SQRT2)*B));
283 return s0 + sr;
284 }
285
286
287 double pengrob_a(double T, double rho, const FluidData *data, FpropsError *err){
288 // FIXME maybe we can improve this with more direct maths
289 double h = pengrob_h(T,rho,data,err);
290 double s = pengrob_s(T,rho,data,err); // duplicated calculation of p!
291 double p = pengrob_p(T,rho,data,err); // duplicated calculation of p!
292 MSG("h = %f, p = %f, s = %f, rho = %f, T = %f",h,p,s,rho,T);
293 return (h - p/rho) - T * s;
294 // previous code from Richard, probably fine but need to check
295 // DEFINE_A;
296 // double vm = PD_M / rho;
297 // double p = pengrob_p(T, rho, data, err);
298 // return -log(p*(v - PD->b)/(data->R * T))+a/(2*SQRT2 * PD->b * data->R *T)*log((v + (1-SQRT2)*PD->b)/(v + (1+SQRT2)*PD->b));
299 }
300
301
302 double pengrob_u(double T, double rho, const FluidData *data, FpropsError *err){
303 // FIXME work out a cleaner approach to this...
304 double p = pengrob_p(T, rho, data, err);
305 return pengrob_h(T,rho,data,err) - p/rho; // duplicated calculation of p!
306 }
307
308 double pengrob_g(double T, double rho, const FluidData *data, FpropsError *err){
309 if(rho > 1./PD->b){
310 MSG("Density exceeds limit value 1/b = %f",1./PD->b);
311 *err = FPROPS_RANGE_ERROR;
312 }
313 #if 0
314 double h = pengrob_h(T,rho,data,err);
315 double s = pengrob_s(T,rho,data,err); // duplicated calculation of p!
316 if(isnan(h))MSG("h is nan");
317 if(isnan(s))MSG("s is nan");
318 return h - T*s;
319 #else
320 // previous code from Richard, probably fine but need to check
321 DEFINE_SQRTALPHA;
322 DEFINE_A;
323 DEFINE_V;
324 double p = pengrob_p(T, rho, data, err);
325 double Z = p*v/(data->R * T);
326 double B = p*PD->b/(data->R * T);
327 double A = p * a / SQ(data->R * T);
328 return log(fabs(Z-B))-(A/(sqrt(8)*B))*log(fabs((Z+(1+sqrt(2))*B)/(Z+(1-sqrt(2))*B)))+Z-1;
329 #endif
330 }
331
332 /**
333 \f[ c_p = \left(\frac{\partial u}{\partial T} \right)_v \f]
334
335 See Pratt, 2001. "Thermodynamic Properties Involving Derivatives",
336 ASEE Chemical Engineering Division Newsletter, Malaysia
337 http://www.che.uah.edu/courseware/che641/peng-robinson-derivatives-joule-thompson.pdf
338
339 Maxima code:
340 u(T,x):= (T*diff(a(T),T) - a(T))/b/sqrt(8) * log((Z(T) + B(T)*(1+sqrt(2)))/(Z(T)+B(T)*(1-sqrt(2))));
341 diff(u(T,v),T);
342 subst(B(T)*(alpha_p - 1/T), diff(B(T),T), %);
343 subst(Z(T)*(alpha_p - 1/T), diff(Z(T),T), %);
344 subst(B(T)*v/Z(T),b,%);
345 subst(b*Z(T)/B(T),v,%);
346 ratsimp(%);
347 */
348 double pengrob_cv(double T, double rho, const FluidData *data, FpropsError *err){
349 DEFINE_V;
350 DEFINE_D2ADT2;
351 double cv0 = ideal_cv(T, rho, data, err);
352 MSG("cv0 = %f",cv0);
353 #define DEFINE_CVR \
354 double p = pengrob_p(T, rho, data, err); \
355 double Z = p * v / (data->R * T); \
356 double B = p * PD->b / (data->R * T); \
357 double cvr1 = T * d2adt2/ (PD->b * 2*SQRT2); \
358 double cvr2 = (Z+B*(1+SQRT2))/(Z+B*(1-SQRT2)); \
359 double cvr = cvr1*log(cvr2);
360 DEFINE_CVR;
361 MSG("d2adT2 = %f", d2adt2);
362 MSG("b = %f",PD->b);
363 MSG("cvr1 = %f, cvr2 = %f, log(cvr2) = %f", cvr1, cvr2, log(cvr2));
364 MSG("cvr = %f",cvr);
365 return cv0 + cvr;// J/K*Kg
366 }
367
368 /**
369 Isobaric specific heat capacity
370 \f[ c_p = \left(\frac{\partial h}{\partial T} \right)_p \f]
371
372 See Pratt, 2001. "Thermodynamic Properties Involving Derivatives",
373 ASEE Chemical Engineering Division Newsletter, Malaysia
374 http://www.che.uah.edu/courseware/che641/peng-robinson-derivatives-joule-thompson.pdf
375
376 TODO this function needs to be checked.
377 */
378 double pengrob_cp(double T, double rho, const FluidData *data, FpropsError *err){
379 //these calculations are broken apart intentionally to help provide clarity as the calculations are tedious
380 DEFINE_SQRTALPHA;
381 DEFINE_A;
382 DEFINE_V;
383 DEFINE_DADT;
384 DEFINE_D2ADT2;
385 DEFINE_CVR;
386 double cp0 = ideal_cp(T, rho, data, err);
387 DEFINE_DPDT_RHO;
388
389 #define DEFINE_CPR \
390 double A = p * a / SQ(data->R * T); \
391 double dAdT_p = p / SQ(data->R*T) * (dadT - 2*a/ T); \
392 double dBdT_p = - PD->b * p / (data->R * SQ(T)); \
393 double num = dAdT_p * (B-Z) + dBdT_p * (6*B*Z + 2*Z - 3*SQ(B) - 2*B + A - SQ(Z)); \
394 double den = 3*SQ(Z) + 2*(B-1)*Z + (A - 2*B - 3*SQ(B)); \
395 double dZdT_p = num / den; \
396 double dvdT_p = data->R / p * (T * dZdT_p + Z); \
397 double cpr = cvr + T * dpdT_rho * dvdT_p - data->R
398 DEFINE_CPR;
399 return cp0 + cpr;
400 }
401
402 /**
403 Speed of sound (see ��engel and Boles, 2012. "Thermodynamics, An Engineering Approach", McGraw-Hill, 7SIe):
404 \f[ w = \sqrt{ \left(\frac{\partial p}{\partial \rho} \right)_s }
405 = v \sqrt{ -\frac{c_p}{c_v} \left(\frac{\partial p}{\partial v}\right)_T}
406 \f]
407
408 See Pratt, 2001. "Thermodynamic Properties Involving Derivatives",
409 ASEE Chemical Engineering Division Newsletter, Malaysia
410 http://www.che.uah.edu/courseware/che641/peng-robinson-derivatives-joule-thompson.pdf
411
412 TODO this function needs to be checked.
413 */
414 double pengrob_w(double T, double rho, const FluidData *data, FpropsError *err){
415 DEFINE_SQRTALPHA;
416 DEFINE_V;
417 DEFINE_DADT;
418 DEFINE_D2ADT2;
419 DEFINE_A;
420 DEFINE_DPDT_RHO;
421 double cv0 = ideal_cv(T, rho, data, err);
422 double cp0 = cv0 + data->R;
423 DEFINE_CVR;
424 DEFINE_CPR;
425 double k = (cp0 + cpr) / (cv0 + cvr);
426 double dpdv_T = - SQ(rho) * pengrob_dpdrho_T(T,rho,data,err);
427 return v * sqrt(-k * dpdv_T);
428 }
429
430 double pengrob_dpdrho_T(double T, double rho, const FluidData *data, FpropsError *err){
431 DEFINE_SQRTALPHA;
432 DEFINE_A;
433 DEFINE_V;
434 #define b PD->b
435 // CHECK: I check this...I think the previous expression was for dpdv_T
436 return -SQ(v)*( data->R*T / SQ(v-b) - 2 * a * (v + b) / SQ(v*(v+b) + b*(v-b)));
437 #undef b
438 }
439
440 /**
441 Relative pressure coefficient, as defined in http://www.iapws.org/relguide/Advise3.pdf
442 \f[ \alpha_p = \frac{1}{p} \left( \frac{\partial p}{\partial T} \right)_v \f]
443
444 Maxima code:
445 p(T,v) := R*T/(v-b) - a(T)/(v*(v+b)+b*(v-b))
446 alpha_p = 1/p * diff(p(T,v),T)
447
448 TODO the function is not yet checked/tested.
449 */
450 double pengrob_alphap(double T, double rho, const FluidData *data, FpropsError *err){
451 DEFINE_SQRTALPHA;
452 DEFINE_V;
453 DEFINE_DADT;
454 double p = pengrob_p(T, rho, data, err);
455 DEFINE_DPDT_RHO;
456 return 1/p * dpdT_rho;
457 }
458
459
460 /**
461 Isothermal stress coefficient, as defined in http://www.iapws.org/relguide/Advise3.pdf
462 \f[ \beta_p = - \frac{1}{p} \left( \frac{\partial p}{\partial v} \right)_T \f]
463
464 Maxima code:
465 p(T,v) := R*T/(v-b) - a(T)/(v*(v+b)+b*(v-b))
466 */
467 double pengrob_betap(double T, double rho, const FluidData *data, FpropsError *err){
468 double p = pengrob_p(T, rho, data, err);
469 return -1/p * SQ(rho) * pengrob_dpdrho_T(T,rho,data,err);
470 }
471
472 /**
473 Saturation calculation for a pure Peng-Robinson fluid. Algorithm as
474 outlined in Sandler 5e, sect 7.5. Another source of information is at
475 https://www.e-education.psu.edu/png520/m17.html
476
477 @return psat(T)
478 @param T temperature [K]
479 @param rhof_ret (output) saturated liquid density
480 @param rhog_ret (output) saturated vapour density
481 @param err (output) error flag, if any. should be reset first by caller if needed.
482
483 TODO implement accelerated successive substitution method (ASSM) or the
484 'Minimum Variable Newton Raphson (MVNR) Method' as detailed at the above
485 link.
486
487 Possible reference: Ghanbari & Davoodi Majd, 2004, "Liquid-Vapor Equilibrium
488 Curves for Methane System by Using Peng-Robinson Equation of State",
489 Petroleum & Coal 46(1) 23-27.
490 http://www.vurup.sk/sites/vurup.sk/archivedsite/www.vurup.sk/pc/vol46_2004/issue1/pdf/pc_ghanbari.pdf
491 http://www.doaj.org/doaj?func=abstract&id=251368
492 */
493 double pengrob_sat(double T,double *rhof_ret, double *rhog_ret, const FluidData *data, FpropsError *err){
494 /* rewritten, using GSOC code from Sean Muratet as a starting point -- thanks Sean! */
495
496 if(fabs(T - data->T_c) < 1e-3){
497 MSG("Saturation conditions requested at critical temperature");
498 *rhof_ret = data->rho_c;
499 *rhog_ret = data->rho_c;
500 return data->p_c;
501 }
502
503 //double rhog;
504 double A, B;
505 double sqrt2 = sqrt(2);
506
507 double p = fprops_psat_T_acentric(T, data);
508 MSG("Initial guess: p = %f from acentric factor",p);
509
510 int i = 0;
511 double Zg, Z1, Zf, vg, vf;
512
513 FILE *F1 = fopen("pf.txt","w");
514
515 // FIXME test upper iteration limit required
516 while(++i < 300){
517 MSG("iter %d: p = %f, rhof = %f, rhog = %f", i, p, 1/vf, 1/vg);
518 // Peng & Robinson eq 17
519 double sqrtalpha = 1 + PD->kappa * (1 - sqrt(T / PD_TCRIT));
520 // Peng & Robinson eq 12
521 double a = PD->aTc * SQ(sqrtalpha);
522 // Peng & Robinson eq 6
523 A = a * p / SQ(data->R*T);
524 B = PD->b * p / (data->R*T);
525
526 // use GSL function to return real roots of polynomial: Peng & Robinson eq 5
527 if(3 == cubicroots(-(1-B), A-3*SQ(B)-2*B, -(A*B-SQ(B)*(1+B)), &Zf,&Z1,&Zg)){
528 //MSG(" roots: Z = %f, %f, %f", Zf, Z1, Zg);
529 //MSG(" Zf = %f, Zg = %f", Zf, Zg);
530 // three real roots in this case
531 vg = Zg*data->R*T / p;
532 vf = Zf*data->R*T / p;
533 if(vf < 0 || vg < 0){
534 // FIXME find out what does this mean; how does it happen?
535 MSG("Got a density root less than 0");
536 *err = FPROPS_SAT_CVGC_ERROR;
537 return 0;
538 }
539 //MSG(" vf = %f, vg = %f",vf,vg);
540 //MSG(" VMf = %f, VMg = %f",vf*data->M,vg*data->M);
541
542 // TODO can we use a function pointer for the fugacity expression, so that we can reuse this function for other cubic EOS?
543 #define FUG(Z,A,B) \
544 exp( (Z-1) - log(Z - B) - A/(2*sqrt2*B)*log( (Z + (1+sqrt2) * B) / (Z + (1-sqrt2) * B)) )
545
546 double ff = FUG(Zf,A,B);
547 double fg = FUG(Zg,A,B);
548 double fratio = ff/fg;
549 MSG(" ff = %f, fg = %f, fratio = %f", ff, fg, fratio);
550
551 //double hf = pengrob_h(T, 1/vf, data, err);
552 //double hg = pengrob_h(T, 1/vg, data, err);
553 //MSG(" HMf = %f, HMg = %f", hf*data->M/1000, hg*data->M/1000);
554
555 if(fabs(fratio - 1) < 1e-7){
556 *rhof_ret = 1 / vf;
557 *rhog_ret = 1 / vg;
558 p = pengrob_p(T, *rhog_ret, data, err);
559 MSG("Solved for T = %f: p = %f, rhof = %f, rhog = %f", T, p, *rhof_ret, *rhog_ret);
560 fclose(F1);
561 return p;
562 }
563 fprintf(F1,"%f\t%f\n",p,fratio);
564 p *= fratio;
565 }else{
566 /* In this case we need to adjust our guess p(T) such that we get
567 into the narrow range of values that gives multiple solutions. */
568 p = MidpointPressureCubic(T, data, err);
569 if(*err){
570 ERRMSG("Failed to solve for a midpoint pressure");
571 fclose(F1);
572 return p;
573 }
574 MSG(" single root: Z = %f. new pressure guess: %f", Zf, p);
575 }
576 }
577 MSG("Did not converge");
578 *err = FPROPS_SAT_CVGC_ERROR;
579 fclose(F1);
580 return 0;
581 }
582
583 /*
584 FIXME can we generalise this to work with other *cubic* EOS as well?
585 Currently not easily done since pointers are kept at a higher level in our
586 data structures than FluidData.
587 */
588
589 typedef struct{
590 const FluidData *data;
591 FpropsError *err;
592 double T;
593 } MidpointSolveData;
594
595 static ZeroInSubjectFunction resid_dpdrho_T;
596
597 /**
598 This function is trying to find the locations of the stationary points
599 of the p(rho,T) function at a specified temperature, assumed to be
600 close to the critical temperature.
601
602 We return the midpoint of the pressure between these two.
603
604 It is assumed that if using this function we are close enough to the
605 critical point that vf < vc < vg, and that the stationary points
606 will be within those interval, ie vf < v1 < vc < v2 < vg.
607 */
608 double MidpointPressureCubic(double T, const FluidData *data, FpropsError *err){
609 MidpointSolveData msd = {data, err, T};
610 double rhomin = 0.5 * data->rho_c;
611 double rhomax = data->rho_c;
612 double rho, resid;
613
614 if(T > data->T_c){
615 ERRMSG("Invalid temperature T > T_c");
616 *err = FPROPS_RANGE_ERROR;
617 return data->p_c;
618 }
619
620 // look for a stationary point in density range less than rho_c
621 int res = zeroin_solve(&resid_dpdrho_T, &msd, rhomin, rhomax, 1e-9, &rho, &resid);
622 if(res){
623 ERRMSG("Failed to solve density for first stationary point");
624 *err = FPROPS_NUMERIC_ERROR;
625 return data->p_c;
626 }
627 double p1 = pengrob_p(T,rho, data, err);
628
629 // look for the other stationary point in density range less than rho_c
630 rhomin = data->rho_c;
631 rhomax = 2 * data->rho_c;
632 if(rhomax + 1e-2 > 1./(data->corr.pengrob->b)) rhomax = 1./(data->corr.pengrob->b) - 1e-3;
633
634 res = zeroin_solve(&resid_dpdrho_T, &msd, rhomin, rhomax, 1e-9, &rho, &resid);
635 if(res){
636 ERRMSG("Failed to solve density for second stationary point");
637 *err = FPROPS_NUMERIC_ERROR;
638 return data->p_c;
639 }
640
641 double p2 = pengrob_p(T,rho, data, err);
642 return 0.5*(p1 + p2);
643 }
644
645 static double resid_dpdrho_T(double rho, void *user_data){
646 #define D ((MidpointSolveData *)user_data)
647 return pengrob_dpdrho_T(D->T,rho,D->data,D->err);
648 #undef D
649 }
650
651
652 #if 0
653
654 /* we would like to try the Akasaka approach and use it with cubic EOS.
655 but at this stage it's not working correctly. Not sure why... */
656
657 /**
658 Solve saturation condition for a specified temperature using approach of
659 Akasaka, but adapted for general use to non-helmholtz property correlations.
660 @param T temperature [K]
661 @param psat_out output, saturation pressure [Pa]
662 @param rhof_out output, saturated liquid density [kg/m^3]
663 @param rhog_out output, saturated vapour density [kg/m^3]
664 @param d helmholtz data object for the fluid in question.
665 @return 0 on success, non-zero on error (eg algorithm failed to converge, T out of range, etc.)
666 */
667 double pengrob_sat_akasaka(double T, double *rhof_out, double * rhog_out, const FluidData *data, FpropsError *err){
668 if(T < data->T_t - 1e-8){
669 ERRMSG("Input temperature %f K is below triple-point temperature %f K",T,data->T_t);
670 return FPROPS_RANGE_ERROR;
671 }
672
673 if(T > data->T_c){
674 ERRMSG("Input temperature is above critical point temperature");
675 return FPROPS_RANGE_ERROR;
676 }
677
678 // we're at the critical point
679 if(fabs(T - data->T_c) < 1e-9){
680 *rhof_out = data->rho_c;
681 *rhog_out = data->rho_c;
682 return data->p_c;
683 }
684
685 // FIXME at present step-length multiplier is set to 0.4 just because of
686 // ONE FLUID, ethanol. Probably our initial guess data isn't good enough,
687 // or maybe there's a problem with the acentric factor or something like
688 // that. This factor 0.4 will be slowing down the whole system, so it's not
689 // good. TODO XXX.
690
691 // initial guesses for liquid and vapour density
692 double rhof = fprops_rhof_T_rackett(T,data);
693 double rhog= fprops_rhog_T_chouaieb(T,data);
694 double R = data->R;
695 double pc = data->p_c;
696
697 MSG("initial guess rho_f = %f, rho_g = %f\n",rhof,rhog);
698 MSG("calculating for T = %.12e",T);
699
700 int i = 0;
701 while(i++ < 70){
702 if(rhof > 1/PD->b){
703 MSG("limit rhof to 1/b");
704 rhof = 1/PD->b;
705 }
706
707 MSG("iter %d: T = %f, rhof = %f, rhog = %f",i,T, rhof, rhog);
708
709 double pf = pengrob_p(T,rhof,data,err);
710 double pg = pengrob_p(T,rhog,data,err);
711 double gf = pengrob_g(T,rhof,data,err);
712 double gg = pengrob_g(T,rhog,data,err);
713 double dpdrf = pengrob_dpdrho_T(T,rhof,data,err);
714 double dpdrg = pengrob_dpdrho_T(T,rhog,data,err);
715 if(*err){
716 ERRMSG("error returned");
717 }
718
719 MSG("gf = %f, gg = %f", gf, gg);
720
721 // jacobian for [F;G](rhof, rhog) --- derivatives wrt rhof and rhog
722 double F = (pf - pg)/pc;
723 double G = (gf - gg)/R/T;
724
725 if(fabs(F) + fabs(G) < 1e-12){
726 //fprintf(stderr,"%s: CONVERGED\n",__func__);
727 *rhof_out = rhof;
728 *rhog_out = rhog;
729 return pengrob_p(T, *rhog_out, data, err);
730 /* SUCCESS */
731 }
732
733 double Ff = dpdrf/pc;
734 double Fg = -dpdrg/pc;
735 MSG("Ff = %e, Fg = %e",Ff,Fg);
736
737 double Gf = dpdrf/rhof/R/T;
738 double Gg = -dpdrg/rhog/R/T;
739 MSG("Gf = %e, Gg = %e",Gf,Gg);
740
741 double DET = Ff*Gg - Fg*Gf;
742 MSG("DET = %f",DET);
743
744 MSG("F = %f, G = %f", F, G);
745
746 // 'gamma' needs to be increased to 0.5 for water to solve correctly (see 'test/sat.c')
747 #define gamma 1
748 rhof += gamma/DET * (Fg*G - Gg*F);
749 rhog += gamma/DET * ( Gf*F - Ff*G);
750 #undef gamma
751
752 if(rhog < 0)rhog = -0.5*rhog;
753 if(rhof < 0)rhof = -0.5*rhof;
754 }
755 *rhof_out = rhof;
756 *rhog_out = rhog;
757 *err = FPROPS_SAT_CVGC_ERROR;
758 ERRMSG("Not converged: with T = %e (rhof=%f, rhog=%f).",T,*rhof_out,*rhog_out);
759 return pengrob_p(T, rhog, data, err);
760 }
761
762 #endif
763
764

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22