Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates NeqSim’s friction-theory viscosity implementation to use more standard transport-property practice for the dilute baseline (Wilke mixing) and improves robustness when the method is invoked for non-EoS phases. It also updates the viscosity model documentation with implementation notes and references.
Changes:
- Replaced log-averaged dilute baseline with a Wilke-mixed dilute-gas baseline and added explicit unit conversion + viscosity floor.
- Enhanced Chung low-density correction factor to include dipole contribution and component viscosity correction.
- Added non-EoS fallback behavior (repulsive = total pressure, attractive = 0) with a warning, and updated docs accordingly.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/main/java/neqsim/physicalproperties/methods/commonphasephysicalproperties/viscosity/FrictionTheoryViscosityMethod.java |
Implements Wilke mixing for the baseline, enriches Chung correction, adds numerical safety constants/floor, and adds a non-EoS fallback path. |
docs/physical_properties/viscosity_models.md |
Updates friction-theory documentation to reflect the new baseline approach and fallback behavior. |
| 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; | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
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.
| 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())); | |
| } | |
| /** |
| } 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."); |
There was a problem hiding this comment.
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.
| } 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."); |
There was a problem hiding this comment.
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.
| **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. | ||
|
|
There was a problem hiding this comment.
The friction-theory equation shown in this section includes an attractive quadratic term (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.
Motivation
Description
calcDiluteGasMixtureViscosityMicroPoiseand replacing logarithmic averaging inFrictionTheoryViscosityMethod.calcViscosity().F_cininitChungPureComponentViscosity()to include the dipole-based term and component viscosity correction viacomp.getViscosityCorrectionFactor().MICRO_POISE_TO_PA_S,MIN_VISCOSITY_PA_S), enforce a minimum viscosity floor, and convert the micropoise result toPa·s.localPhase.getPressure()as repulsive contribution and zero attractive contribution), and update documentationdocs/physical_properties/viscosity_models.mdwith implementation notes and added references (Chung,Wilke).Testing
./mvnw -q -Dtest=FrictionTheoryViscosityMethodTest test, which completed successfully../mvnw -q -Dtest=FrictionTheoryViscosityMethodTest,LBCViscosityMethodTest test, which completed successfully.Codex Task