Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/physical_properties/viscosity_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ The Quiñones-Cisneros and Firoozabadi (2000) friction theory relates viscosity
$$\eta = \eta_0 + \kappa_a P_a + \kappa_{aa} P_a^2 + \kappa_r P_r + \kappa_{rr} P_r^2$$

where:
- $\eta_0$ is the dilute gas viscosity (Chung correlation)
- $\eta_0$ is the dilute gas viscosity (Chung low-density term + Wilke mixture rule)
- $P_a$ is the attractive pressure from EoS
- $P_r$ is the repulsive pressure from EoS
- $\kappa$ are friction coefficients (EoS-dependent)
Expand All @@ -98,6 +98,12 @@ where:

**Automatic EoS detection:** The method automatically selects SRK or PR constants based on the phase type.

**Implementation notes in NeqSim (2026 update):**
- SRK and PR friction constants are selected automatically.
- The dilute-gas baseline now uses **Wilke mixing**, which is the standard state-of-practice for mixture dilute viscosities.
- The Chung correction factor $F_c$ includes acentric factor, dipole moment, and component viscosity correction term to better handle polar species.
- If friction theory is called for a non-EoS phase, NeqSim falls back to total pressure as repulsive contribution and zero attractive contribution, with a warning in logs.

Comment on lines +101 to +106
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The friction-theory equation shown in this section includes an attractive quadratic term ($\kappa_{aa} P_a^2$), but the current FrictionTheoryViscosityMethod.calcViscosity() implementation only applies a linear attractive term and a quadratic repulsive term (no P_a^2). Since this section is being updated, consider adjusting the documented equation (or adding an explicit note) so it matches the implemented model variant in NeqSim.

Copilot uses AI. Check for mistakes.
**Usage:**
```java
fluid.getPhase("oil").getPhysicalProperties().setViscosityModel("friction theory");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
* <p>References:
* Quiñones-Cisneros, R., and Firoozabadi, A. (2000). Friction theory for the viscosity of fluids.
* AIChE Journal, 46(4), 16–23.
* Chung, T.-H., Ajlan, M., Lee, L. L., and Starling, K. E. (1988). Generalized multiparameter
* correlation for nonpolar and polar fluid transport properties. Industrial & Engineering Chemistry
* Research, 27(4), 671–679.
* Wilke, C. R. (1950). A viscosity equation for gas mixtures. The Journal of Chemical Physics,
* 18(4), 517–519.
* </p>
*
* @author esol
Expand Down Expand Up @@ -53,6 +58,8 @@ public class FrictionTheoryViscosityMethod extends Viscosity

// Second-order repulsive term constant
protected double kaprr_fconst = 1.35994e-8;
private static final double MICRO_POISE_TO_PA_S = 1.0e-7;
private static final double MIN_VISCOSITY_PA_S = 1.0e-6;

/**
* <p>
Expand Down Expand Up @@ -131,22 +138,23 @@ public double calcViscosity() {
PhaseInterface localPhase = phase.getPhase();
int numComp = localPhase.getNumberOfComponents();
double[] molarMassPow = new double[numComp];
double visk0 = 0.0;
double visk0 = calcDiluteGasMixtureViscosityMicroPoise(localPhase);
double MM = 0.0;
for (int i = 0; i < numComp; i++) {
ComponentInterface comp = localPhase.getComponent(i);
molarMassPow[i] = Math.pow(comp.getMolarMass(), 0.3);
visk0 += comp.getx() * Math.log(getPureComponentViscosity(i));
MM += comp.getx() / molarMassPow[i];
}
visk0 = Math.exp(visk0);
double Prepulsive = 1.0;
double Pattractive = 1.0;
double Pattractive = 0.0;
try {
Prepulsive = ((neqsim.thermo.phase.PhaseEosInterface) localPhase).getPressureRepulsive();
Pattractive = ((neqsim.thermo.phase.PhaseEosInterface) localPhase).getPressureAttractive();
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
Prepulsive = localPhase.getPressure();
Pattractive = 0.0;
logger.warn(
"Friction-theory viscosity called for non-EOS phase type. Falling back to total pressure for repulsive contribution and zero attractive contribution.");
Comment on lines 153 to +157
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch (Exception ex) here will also swallow unexpected runtime failures from getPressureRepulsive()/getPressureAttractive() and silently switch to the fallback behavior. Consider narrowing this to ClassCastException (the expected non-EoS case) and include the caught exception in the warning (e.g., as a second argument) so real defects aren’t masked and debugging remains possible.

Copilot uses AI. Check for mistakes.
Comment on lines 153 to +157
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior: when friction theory is used on a non-EoS phase, the method now falls back to using total pressure (repulsive) and zero attractive contribution. There’s currently no unit test exercising this path (e.g., using SystemIdealGas / PhaseIdealGas) to ensure the fallback stays deterministic and returns a finite viscosity. Adding a dedicated test would prevent regressions in this safety-critical branch.

Copilot uses AI. Check for mistakes.
}
double kaprmx = 0.0;
double kapamx = 0.0;
Expand Down Expand Up @@ -176,9 +184,33 @@ public double calcViscosity() {
double visk1 = kaprmx * Prepulsive + kapamx * Pattractive + kaprrmx * Prepulsive * Prepulsive;

if ((visk0 + visk1) < 1e-20) {
return 1e-6;
return MIN_VISCOSITY_PA_S;
}
return (visk0 + visk1) * 1.0e-7;
return Math.max((visk0 + visk1) * MICRO_POISE_TO_PA_S, MIN_VISCOSITY_PA_S);
}

/**
* Calculate dilute-gas mixture viscosity in micropoise using Wilke's mixing rule.
*
* @param localPhase phase containing composition and molar masses
* @return mixture dilute-gas viscosity in micropoise
*/
private double calcDiluteGasMixtureViscosityMicroPoise(PhaseInterface localPhase) {
double viscosity = 0.0;
for (int i = 0; i < localPhase.getNumberOfComponents(); i++) {
double denominator = 0.0;
for (int j = 0; j < localPhase.getNumberOfComponents(); j++) {
double phiij = Math
.pow(1.0 + Math.sqrt(pureComponentViscosity[i] / pureComponentViscosity[j])
* Math.pow(localPhase.getComponent(j).getMolarMass()
/ localPhase.getComponent(i).getMolarMass(), 0.25), 2.0)
/ Math.sqrt(8.0 * (1.0 + localPhase.getComponent(i).getMolarMass()
/ localPhase.getComponent(j).getMolarMass()));
denominator += localPhase.getComponent(j).getx() * phiij;
}
viscosity += localPhase.getComponent(i).getx() * pureComponentViscosity[i] / denominator;
}
return viscosity;
}

/**
Comment on lines +199 to 216
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Wilke mixing implementation here duplicates the same double-loop/phi_ij logic used in ChungViscosityMethod.calcViscosity() (see src/main/java/neqsim/physicalproperties/methods/gasphysicalproperties/viscosity/ChungViscosityMethod.java:63-78). To avoid future drift between the two implementations, consider factoring the Wilke mixture calculation into a shared helper/utility (or reusing the existing implementation) and calling it from both places.

Suggested change
double viscosity = 0.0;
for (int i = 0; i < localPhase.getNumberOfComponents(); i++) {
double denominator = 0.0;
for (int j = 0; j < localPhase.getNumberOfComponents(); j++) {
double phiij = Math
.pow(1.0 + Math.sqrt(pureComponentViscosity[i] / pureComponentViscosity[j])
* Math.pow(localPhase.getComponent(j).getMolarMass()
/ localPhase.getComponent(i).getMolarMass(), 0.25), 2.0)
/ Math.sqrt(8.0 * (1.0 + localPhase.getComponent(i).getMolarMass()
/ localPhase.getComponent(j).getMolarMass()));
denominator += localPhase.getComponent(j).getx() * phiij;
}
viscosity += localPhase.getComponent(i).getx() * pureComponentViscosity[i] / denominator;
}
return viscosity;
}
/**
return calcWilkeMixtureViscosityMicroPoise(localPhase, pureComponentViscosity);
}
/**
* Calculate mixture viscosity in micropoise from pure-component viscosities using Wilke's
* mixing rule.
*
* @param localPhase phase containing composition and molar masses
* @param componentViscosities pure-component viscosities in micropoise, indexed by component
* number
* @return mixture viscosity in micropoise
*/
private double calcWilkeMixtureViscosityMicroPoise(PhaseInterface localPhase,
double[] componentViscosities) {
double viscosity = 0.0;
for (int i = 0; i < localPhase.getNumberOfComponents(); i++) {
double denominator = 0.0;
for (int j = 0; j < localPhase.getNumberOfComponents(); j++) {
denominator += localPhase.getComponent(j).getx()
* calcWilkePhiij(localPhase.getComponent(i), localPhase.getComponent(j),
componentViscosities[i], componentViscosities[j]);
}
viscosity += localPhase.getComponent(i).getx() * componentViscosities[i] / denominator;
}
return viscosity;
}
/**
* Calculate the Wilke interaction parameter for a pair of components.
*
* @param componentI the numerator-side component in the Wilke expression
* @param componentJ the denominator-side component in the Wilke expression
* @param viscosityI pure-component viscosity of component {@code i} in micropoise
* @param viscosityJ pure-component viscosity of component {@code j} in micropoise
* @return Wilke interaction parameter for the component pair
*/
private double calcWilkePhiij(ComponentInterface componentI, ComponentInterface componentJ,
double viscosityI, double viscosityJ) {
return Math.pow(1.0 + Math.sqrt(viscosityI / viscosityJ)
* Math.pow(componentJ.getMolarMass() / componentI.getMolarMass(), 0.25), 2.0)
/ Math.sqrt(8.0 * (1.0 + componentI.getMolarMass() / componentJ.getMolarMass()));
}
/**

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -237,7 +269,10 @@ public void initChungPureComponentViscosity() {
double temperature = localPhase.getTemperature();
for (int i = 0; i < localPhase.getNumberOfComponents(); i++) {
ComponentInterface comp = localPhase.getComponent(i);
Fc[i] = 1.0 - 0.2756 * comp.getAcentricFactor();
double relativeDipole =
131.3 * comp.getDebyeDipoleMoment() / Math.sqrt(comp.getCriticalVolume() * comp.getTC());
Fc[i] = 1.0 - 0.2756 * comp.getAcentricFactor() + 0.059035 * Math.pow(relativeDipole, 4.0)
+ comp.getViscosityCorrectionFactor();

double tempVar = 1.2593 * temperature / comp.getTC();
double varLast = -6.435e-4 * Math.pow(tempVar, 0.14874)
Expand Down
Loading