use crate::composition::{Composition, CompositionError};
use crate::gerg2008const::*;
use crate::DensityError;
#[derive(Default, Clone, Copy)]
pub struct Gerg2008 {
pub t: f64,
pub p: f64,
pub d: f64,
pub z: f64,
pub mm: f64,
pub dp_dd: f64,
pub d2p_dd2: f64,
pub d2p_dtd: f64,
pub dp_dt: f64,
pub u: f64,
pub h: f64,
pub s: f64,
pub cv: f64,
pub cp: f64,
pub w: f64,
pub g: f64,
pub jt: f64,
pub kappa: f64,
x: [f64; NC_GERG + 1],
dr: f64,
tr: f64,
told: f64,
a: f64,
a0: [f64; 3],
ar: [[f64; 4]; 4],
dpddsave: f64,
taup: [[f64; MAXTRMP + 1]; MAXFLDS + 1],
taupijk: [[f64; MAXTRMM + 1]; MAXFLDS + 1],
}
impl Gerg2008 {
pub fn new() -> Self {
Default::default()
}
pub fn set_composition(&mut self, comp: &Composition) -> Result<(), CompositionError> {
comp.check()?;
self.x[0] = 0.0;
self.x[1] = comp.methane;
self.x[2] = comp.nitrogen;
self.x[3] = comp.carbon_dioxide;
self.x[4] = comp.ethane;
self.x[5] = comp.propane;
self.x[6] = comp.isobutane;
self.x[7] = comp.n_butane;
self.x[8] = comp.isopentane;
self.x[9] = comp.n_pentane;
self.x[10] = comp.hexane;
self.x[11] = comp.heptane;
self.x[12] = comp.octane;
self.x[13] = comp.nonane;
self.x[14] = comp.decane;
self.x[15] = comp.hydrogen;
self.x[16] = comp.oxygen;
self.x[17] = comp.carbon_monoxide;
self.x[18] = comp.water;
self.x[19] = comp.hydrogen_sulfide;
self.x[20] = comp.helium;
self.x[21] = comp.argon;
(self.dr, self.tr) = self.reducingparameters();
Ok(())
}
pub fn molar_mass(&mut self) {
self.mm = 0.0;
for (i, mmi_gerg) in MMI_GERG.iter().enumerate().skip(1) {
self.mm += self.x[i] * mmi_gerg;
}
}
pub fn pressure(&mut self) -> f64 {
self.alphar(0);
self.z = 1.0 + self.ar[0][1];
let p = self.d * RGERG * self.t * self.z;
self.dpddsave = RGERG * self.t * (1.0 + 2.0 * self.ar[0][1] + self.ar[0][2]);
p
}
pub fn density(&mut self, iflag: i32) -> Result<(), DensityError> {
let mut nfail: i32 = 0;
let mut ifail: i32 = 0;
const TOLR: f64 = 0.000_000_1;
let (dcx, _tcx) = self.pseudocriticalpoint();
if self.d > -EPSILON {
self.d = self.p / RGERG / self.t;
if iflag == 2 {
self.d = dcx * 3.0;
}
} else {
self.d = self.d.abs();
}
let plog = self.p.ln();
let mut vlog = -self.d.ln();
for it in 1..=50 {
if !(-7.0..=100.0).contains(&vlog) || it == 20 || it == 30 || it == 40 || ifail == 1 {
ifail = 0;
if nfail > 2 {
self.d = self.p / RGERG / self.t;
return Err(DensityError::IterationFail);
}
nfail += 1;
if nfail == 1 {
self.d = dcx * 3.0; } else if nfail == 2 {
self.d = dcx * 2.5; } else if nfail == 3 {
self.d = dcx * 2.0; }
vlog = -self.d.ln();
}
self.d = (-vlog).exp();
let p2 = self.pressure();
if self.dpddsave < EPSILON || p2 < EPSILON {
let mut vinc = if self.d > dcx { -0.1 } else { 0.1 };
if it > 5 {
vinc /= 2.0;
}
if it > 10 && it < 20 {
vinc /= 5.0;
}
vlog += vinc;
} else {
let dpdlv = -self.d * self.dpddsave; let vdiff = (p2.ln() - plog) * p2 / dpdlv;
vlog += -vdiff;
if vdiff.abs() < TOLR {
if self.dpddsave < 0.0 {
ifail = 1;
} else {
self.d = (-vlog).exp();
if iflag > 0
&& ((self.properties() <= 0.0
|| self.dp_dd <= 0.0
|| self.d2p_dtd <= 0.0)
|| (self.cv <= 0.0 || self.cp <= 0.0 || self.w <= 0.0))
{
self.d = self.p / RGERG / self.t;
return Err(DensityError::IterationFail);
}
return Ok(()); }
}
}
}
self.d = self.p / RGERG / self.t;
Err(DensityError::IterationFail)
}
pub fn properties(&mut self) -> f64 {
self.molar_mass();
self.alpha0();
self.alphar(1);
let rt = RGERG * self.t;
self.z = 1.0 + self.ar[0][1];
let p = self.d * rt * self.z;
self.dp_dd = rt * (1.0 + 2.0 * self.ar[0][1] + self.ar[0][2]);
self.dp_dt = self.d * RGERG * (1.0 + self.ar[0][1] - self.ar[1][1]);
self.d2p_dtd = RGERG
* (1.0 + 2.0 * self.ar[0][1] + self.ar[0][2] - 2.0 * self.ar[1][1] - self.ar[1][2]);
self.a = rt * self.a0[0] + self.ar[0][0];
self.g = rt * (1.0 + self.ar[0][1] + self.a0[0] + self.ar[0][0]);
self.u = rt * (self.a0[1] + self.ar[1][0]);
self.h = rt * (1.0 + self.ar[0][1] + self.a0[1] + self.ar[1][0]);
self.s = RGERG * (self.a0[1] + self.ar[1][0] - self.a0[0] - self.ar[0][0]);
self.cv = -RGERG * (self.a0[2] + self.ar[2][0]);
if self.d > EPSILON {
self.cp = self.cv + self.t * (self.dp_dt / self.d) * (self.dp_dt / self.d) / self.dp_dd;
self.d2p_dd2 =
rt * (2.0 * self.ar[0][1] + 4.0 * self.ar[0][2] + self.ar[0][3]) / self.d;
self.jt = (self.t / self.d * self.dp_dt / self.dp_dd - 1.0) / self.cp / self.d;
} else {
self.cp = self.cv + RGERG;
self.d2p_dd2 = 0.0;
self.jt = 1E+20;
}
self.w = 1000.0 * self.cp / self.cv * self.dp_dd / self.mm;
if self.w < 0.0 {
self.w = 0.0;
}
self.w = self.w.sqrt();
self.kappa = self.w.powi(2) * self.mm / (rt * 1000.0 * self.z);
p
}
fn reducingparameters(&mut self) -> (f64, f64) {
let mut dr: f64 = 0.0;
let mut tr: f64 = 0.0;
let mut vr: f64 = 0.0;
let mut xij: f64;
let mut f: f64;
self.told = 0.0;
for i in 1..=NC_GERG {
if self.x[i] > EPSILON {
f = 1.0;
for j in i..=NC_GERG {
if self.x[j] > EPSILON {
xij = f * (self.x[i] * self.x[j]) * (self.x[i] + self.x[j]);
vr += xij * GVIJ[i][j] / (BVIJ[i][j] * self.x[i] + self.x[j]);
tr += xij * GTIJ[i][j] / (BTIJ[i][j] * self.x[i] + self.x[j]);
f = 2.0;
}
}
}
}
if vr > EPSILON {
dr = 1.0 / vr;
}
(dr, tr)
}
fn alpha0(&mut self) {
let mut loghyp: f64;
let mut th0t: f64;
let mut logxd: f64;
let mut sumhyp0: f64;
let mut sumhyp1: f64;
let mut sumhyp2: f64;
let mut em: f64;
let mut ep: f64;
let mut hcn: f64;
let mut hsn: f64;
self.a0[0] = 0.0;
self.a0[1] = 0.0;
self.a0[2] = 0.0;
let logd = if self.d > EPSILON {
self.d.ln()
} else {
EPSILON.ln()
};
let logt = self.t.ln();
for (i, th0i) in TH0I.iter().enumerate().skip(1) {
if self.x[i] > EPSILON {
logxd = logd + self.x[i].ln();
sumhyp0 = 0.0;
sumhyp1 = 0.0;
sumhyp2 = 0.0;
for (j, th0ij) in th0i.iter().enumerate().take(8).skip(4) {
if th0ij > &EPSILON {
th0t = th0ij / self.t;
ep = th0t.exp();
em = 1.0 / ep;
hsn = (ep - em) / 2.0;
hcn = (ep + em) / 2.0;
if j == 4 || j == 6 {
loghyp = hsn.abs().ln();
sumhyp0 += N0I[i][j] * loghyp;
sumhyp1 += N0I[i][j] * th0t * hcn / hsn;
sumhyp2 += N0I[i][j] * (th0t / hsn) * (th0t / hsn);
} else {
loghyp = hcn.abs().ln();
sumhyp0 -= N0I[i][j] * loghyp;
sumhyp1 -= N0I[i][j] * th0t * hsn / hcn;
sumhyp2 += N0I[i][j] * (th0t / hcn) * (th0t / hcn);
}
}
}
self.a0[0] += self.x[i]
* (logxd + N0I[i][1] + N0I[i][2] / self.t - N0I[i][3] * logt + sumhyp0);
self.a0[1] += self.x[i] * (N0I[i][3] + N0I[i][2] / self.t + sumhyp1);
self.a0[2] += -self.x[i] * (N0I[i][3] + sumhyp2);
}
}
}
fn alphar(&mut self, itau: i32) {
let mut ex: f64;
let mut ex2: f64;
let mut ex3: f64;
let mut cij0: f64;
let mut eij0: f64;
let mut ndt: f64;
let mut ndtd: f64;
let mut ndtt: f64;
let mut xijf: f64;
let mut delp: [f64; 7 + 1] = [0.0; 7 + 1];
let mut expd: [f64; 7 + 1] = [0.0; 7 + 1];
for i in 0..=3 {
for j in 0..=3 {
self.ar[i][j] = 0.0;
}
}
let del = self.d / self.dr;
let tau = self.tr / self.t;
let lntau = tau.ln();
delp[1] = del;
expd[1] = (-delp[1]).exp();
for i in 2..8 {
delp[i] = delp[i - 1] * del;
expd[i] = (-delp[i]).exp();
}
if (self.t - self.told).abs() > 0.000_000_1 {
self.tterms(lntau);
}
self.told = self.t;
for i in 1..=NC_GERG {
if self.x[i] > EPSILON {
for k in 1..=KPOL[i] {
ndt = self.x[i] * delp[DOIK[i][k]] * self.taup[i][k];
ndtd = ndt * DOIK[i][k] as f64;
self.ar[0][1] += ndtd;
self.ar[0][2] += ndtd * (DOIK[i][k] as f64 - 1.0);
if itau > 0 {
ndtt = ndt * TOIK[i][k];
self.ar[0][0] += ndt;
self.ar[1][0] += ndtt;
self.ar[2][0] += ndtt * (TOIK[i][k] - 1.0);
self.ar[1][1] += ndtt * DOIK[i][k] as f64;
self.ar[1][2] += ndtt * DOIK[i][k] as f64 * (DOIK[i][k] as f64 - 1.0);
self.ar[0][3] +=
ndtd * (DOIK[i][k] as f64 - 1.0) * (DOIK[i][k] as f64 - 2.0);
}
}
for k in 1 + KPOL[i]..=KPOL[i] + KEXP[i] {
ndt = self.x[i] * delp[DOIK[i][k]] * self.taup[i][k] * expd[COIK[i][k]];
ex = COIK[i][k] as f64 * delp[COIK[i][k]];
ex2 = DOIK[i][k] as f64 - ex;
ex3 = ex2 * (ex2 - 1.0);
self.ar[0][1] += ndt * ex2;
self.ar[0][2] += ndt * (ex3 - COIK[i][k] as f64 * ex);
if itau > 0 {
ndtt = ndt * TOIK[i][k];
self.ar[0][0] += ndt;
self.ar[1][0] += ndtt;
self.ar[2][0] += ndtt * (TOIK[i][k] - 1.0);
self.ar[1][1] += ndtt * ex2;
self.ar[1][2] += ndtt * (ex3 - COIK[i][k] as f64 * ex);
self.ar[0][3] += ndt
* (ex3 * (ex2 - 2.0)
- ex * (3.0 * ex2 - 3.0 + COIK[i][k] as f64) * COIK[i][k] as f64);
}
}
}
}
for i in 1..NC_GERG {
if self.x[i] > EPSILON {
for j in i + 1..=NC_GERG {
if self.x[j] > EPSILON {
let mn = MNUMB[i][j];
if mn > 0 {
xijf = self.x[i] * self.x[j] * FIJ[i][j];
for k in 1..=KPOLIJ[mn] {
ndt = xijf * delp[DIJK[mn][k]] * self.taupijk[mn][k];
ndtd = ndt * DIJK[mn][k] as f64;
self.ar[0][1] += ndtd;
self.ar[0][2] += ndtd * (DIJK[mn][k] as f64 - 1.0);
if itau > 0 {
ndtt = ndt * TIJK[mn][k];
self.ar[0][0] += ndt;
self.ar[1][0] += ndtt;
self.ar[2][0] += ndtt * (TIJK[mn][k] - 1.0);
self.ar[1][1] += ndtt * DIJK[mn][k] as f64;
self.ar[1][2] +=
ndtt * DIJK[mn][k] as f64 * (DIJK[mn][k] as f64 - 1.0);
self.ar[0][3] += ndtd
* (DIJK[mn][k] as f64 - 1.0)
* (DIJK[mn][k] as f64 - 2.0);
}
}
for k in 1 + KPOLIJ[mn]..=KPOLIJ[mn] + KEXPIJ[mn] {
cij0 = CIJK[mn][k] * delp[2];
eij0 = EIJK[mn][k] * del;
ndt = xijf
* NIJK[mn][k]
* delp[DIJK[mn][k]]
* (cij0 + eij0 + GIJK[mn][k] + TIJK[mn][k] * lntau).exp();
ex = DIJK[mn][k] as f64 + 2.0 * cij0 + eij0;
ex2 = ex * ex - DIJK[mn][k] as f64 + 2.0 * cij0;
self.ar[0][1] += ndt * ex;
self.ar[0][2] += ndt * ex2;
if itau > 0 {
ndtt = ndt * TIJK[mn][k];
self.ar[0][0] += ndt;
self.ar[1][0] += ndtt;
self.ar[2][0] += ndtt * (TIJK[mn][k] - 1.0);
self.ar[1][1] += ndtt * ex;
self.ar[1][2] += ndtt * ex2;
self.ar[0][3] += ndt
* (ex * (ex2 - 2.0 * (DIJK[mn][k] as f64 - 2.0 * cij0))
+ 2.0 * DIJK[mn][k] as f64);
}
}
}
}
}
}
}
}
fn tterms(&mut self, lntau: f64) {
let i: usize = 5;
let mut taup0: [f64; 12 + 1] = [0.0; 12 + 1];
for (k, taup) in taup0.iter_mut().enumerate().skip(1) {
*taup = (TOIK[i][k] * lntau).exp();
}
for i in 1..=NC_GERG {
if self.x[i] > EPSILON {
if i > 4 && i != 15 && i != 18 && i != 20 {
for (k, taup) in taup0.iter().enumerate().skip(1) {
self.taup[i][k] = NOIK[i][k] * taup;
}
} else {
for k in 1..=KPOL[i] + KEXP[i] {
self.taup[i][k] = NOIK[i][k] * (TOIK[i][k] * lntau).exp();
}
}
}
}
for (i, mnumbi) in MNUMB.iter().enumerate().skip(1) {
if self.x[i] > EPSILON {
for (j, mnumbij) in mnumbi.iter().enumerate().skip(i + 1) {
if self.x[j] > EPSILON {
let mn = *mnumbij;
if mn > 0 {
for k in 1..=KPOLIJ[mn] {
self.taupijk[mn][k] = NIJK[mn][k] * (TIJK[mn][k] * lntau).exp();
}
}
}
}
}
}
}
fn pseudocriticalpoint(&self) -> (f64, f64) {
let mut dcx = 0.0;
let mut tcx = 0.0;
let mut vcx: f64 = 0.0;
for i in 1..=NC_GERG {
tcx += self.x[i] * TC[i];
vcx += self.x[i] / DC[i];
}
if vcx > EPSILON {
dcx = 1.0 / vcx;
}
(dcx, tcx)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone_gerg() {
let mut detail = Gerg2008::new();
let mut gas_comp = Composition::default();
gas_comp.nitrogen = 1.0;
_ = detail.set_composition(&gas_comp);
detail.t = 273.15;
detail.p = 200.0;
_ = detail.density(0);
detail.properties();
let mut detail_clone = detail.clone();
detail_clone.p = 100.0;
_ = detail_clone.density(0);
detail_clone.properties();
assert!((detail_clone.t - 273.15).abs() < 0.000001);
assert!((detail_clone.p - 100.0).abs() < 0.000001);
let expected_density = 0.0446; assert!((detail_clone.d - expected_density).abs() < 0.001);
}
#[test]
fn copy_gerg() {
let mut detail = Gerg2008::new();
let mut gas_comp = Composition::default();
gas_comp.nitrogen = 1.0;
_ = detail.set_composition(&gas_comp);
detail.t = 273.15;
detail.p = 200.0;
_ = detail.density(0);
detail.properties();
let mut detail_copy = detail;
detail_copy.p = 100.0;
_ = detail_copy.density(0);
detail_copy.properties();
assert!((detail_copy.t - 273.15).abs() < 0.000001);
assert!((detail_copy.p - 100.0).abs() < 0.000001);
let expected_density = 0.0446; assert!((detail_copy.d - expected_density).abs() < 0.001);
}
}